Unsafe Deserialization: Memahami Ancaman dan Mencegah Serangan di Aplikasi Web Anda
1. Pendahuluan
Di balik layar aplikasi web modern yang kompleks, seringkali terjadi proses “pengemasan” dan “pembongkaran” data. Kita menyebutnya serialisasi dan deserialisasi. Bayangkan Anda ingin mengirimkan sebuah puzzle yang sudah jadi (objek data kompleks) kepada teman Anda. Daripada mengirim setiap kepingan secara terpisah, Anda membongkarnya menjadi instruksi tertulis (serialisasi), mengirim instruksi tersebut, dan teman Anda merakitnya kembali (deserialisasi).
Proses ini sangat vital untuk berbagai fungsi: komunikasi antar microservices, penyimpanan data di cache, persistensi sesi pengguna, hingga pengiriman payload API. Namun, di balik kemudahan ini, tersimpan sebuah ancaman keamanan yang sering diremehkan namun sangat berbahaya: Unsafe Deserialization.
⚠️ Unsafe Deserialization adalah salah satu kerentanan yang masuk dalam daftar OWASP Top 10 (biasanya di kategori “A08:2021-Software and Data Integrity Failures” atau sebelumnya sebagai kategori tersendiri “A08:2017-Insecure Deserialization”). Kerentanan ini memungkinkan penyerang untuk mengeksekusi kode dari jarak jauh (Remote Code Execution/RCE), melakukan Denial of Service (DoS), atau bahkan bypass autentikasi, hanya dengan memanipulasi data yang dideserialisasi.
Mengapa ancaman ini begitu kritis dan sering luput dari perhatian developer? Karena masalahnya bukan pada proses deserialisasi itu sendiri, melainkan pada apa yang dideserialisasi dan bagaimana aplikasi menangani objek yang dihasilkan. Artikel ini akan menyelami lebih dalam tentang Unsafe Deserialization, bagaimana serangan ini bekerja, dan yang terpenting, bagaimana Anda bisa melindungi aplikasi web Anda.
2. Apa Itu Serialisasi dan Deserialisasi?
Mari kita bahas lebih detail konsep dasarnya.
💡 Serialisasi adalah proses mengubah struktur data kompleks (seperti objek dalam memori program) menjadi format yang dapat disimpan atau ditransmisikan. Format ini biasanya berupa urutan byte, string JSON, XML, atau format biner lainnya. Tujuannya agar objek tersebut bisa:
- Dikirim melalui jaringan (misal: antar server, dari server ke browser).
- Disimpan di database, file, atau cache.
- Digunakan kembali di lain waktu atau di sistem yang berbeda.
Contoh sederhananya, objek User di aplikasi Anda:
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
}
}
const user1 = new User(1, 'Budi', 'budi@example.com');
Setelah diserialisasi ke JSON, mungkin akan terlihat seperti ini:
{"id": 1, "name": "Budi", "email": "budi@example.com"}
📌 Deserialisasi adalah kebalikannya: proses mengambil format serialisasi tersebut dan mengubahnya kembali menjadi objek asli dalam memori program. Ini memungkinkan aplikasi untuk “membaca” kembali data yang sebelumnya disimpan atau diterima dari jaringan, dan berinteraksi dengannya seolah-olah data tersebut baru dibuat di dalam program.
Sebagai developer, Anda pasti sering berinteraksi dengan proses ini:
- Menggunakan
JSON.stringify()danJSON.parse()di JavaScript. - Menggunakan
json.dumps()danjson.loads()di Python. - Menggunakan
serialize()danunserialize()di PHP (⚠️ sangat berbahaya jika tidak hati-hati!). - Menggunakan
ObjectOutputStreamdanObjectInputStreamdi Java (⚠️ juga sangat berbahaya!).
Berbagai format serialisasi memiliki karakteristiknya masing-masing. Beberapa, seperti JSON dan XML, bersifat human-readable dan cross-language. Lainnya, seperti Java Serializable atau Python pickle, bersifat language-specific dan mampu menyerialisasi tidak hanya data, tetapi juga struktur dan tipe objek secara lebih mendalam, termasuk referensi ke fungsi atau kelas. Tipe yang terakhir inilah yang seringkali menjadi pintu masuk serangan Unsafe Deserialization.
3. Bagaimana Unsafe Deserialization Terjadi?
Inti masalah dari Unsafe Deserialization adalah ketika aplikasi Anda mendeserialisasi data yang tidak terpercaya (berasal dari input pengguna, sistem eksternal, atau sumber lain yang tidak dapat Anda kontrol sepenuhnya) tanpa validasi atau batasan yang memadai.
Penyerang dapat memanfaatkan ini dengan cara membuat payload data serialisasi yang berbahaya. Payload ini dirancang sedemikian rupa sehingga ketika aplikasi Anda mencoba mendeserialisasinya, ia akan memicu perilaku yang tidak diinginkan atau mengeksekusi kode arbitrer.
🎯 Skenario Umum Terjadinya Serangan:
- Input Pengguna: Aplikasi menerima data yang diserialisasi langsung dari input pengguna (misal: dalam parameter URL, body request POST, header kustom, atau cookie).
- Sistem Eksternal: Aplikasi mengambil data serialisasi dari sistem eksternal yang mungkin telah dikompromikan atau tidak terpercaya (misal: cache pihak ketiga, log, atau database yang diakses publik).
- Library Rentan: Aplikasi menggunakan library serialisasi/deserialisasi yang memiliki kerentanan bawaan atau miskonfigurasi, terutama dalam bahasa-spesifik.
Ketika data berbahaya ini dideserialisasi, bukan hanya nilai-nilai data yang dimuat, tetapi juga metadata tentang tipe objek, konstruktor, dan metode yang harus dipanggil. Penyerang dapat menyuntikkan objek yang, saat “dibangun kembali” oleh deserializer, akan memanggil fungsi-fungsi sistem atau memodifikasi state aplikasi secara tidak sah.
4. Mekanisme Serangan Unsafe Deserialization
Mekanisme serangan Unsafe Deserialization seringkali melibatkan konsep “gadget chains”.
💡 Gadget Chains: Ini adalah serangkaian kelas atau fungsi yang sudah ada di classpath aplikasi (yaitu, library yang digunakan aplikasi Anda). Penyerang tidak perlu menyuntikkan kode baru; mereka hanya perlu mengetahui kelas-kelas apa yang tersedia dan bagaimana cara menyusunnya dalam payload serialisasi sehingga, ketika dideserialisasi, mereka akan “berantai” memanggil satu sama lain hingga mencapai fungsi yang berbahaya (misalnya, eksekusi perintah sistem).
Mari kita lihat contoh konseptual untuk beberapa bahasa:
Contoh di Java (dengan ObjectInputStream)
Java memiliki mekanisme serialisasi objek bawaan (java.io.Serializable). Ini sangat kuat, tetapi juga sangat berbahaya jika digunakan dengan input tidak terpercaya.
// Sisi Server: Aplikasi yang rentan
import java.io.*;
public class VulnerableApp {
public static void main(String[] args) throws Exception {
// Anggap ini adalah input dari pengguna yang tidak terpercaya
byte[] serializedData = getUntrustedInput(); // Data serialisasi dari penyerang
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serializedData))) {
Object obj = ois.readObject(); // ⚠️ Titik kerentanan!
System.out.println("Objek dideserialisasi: " + obj.getClass().getName());
} catch (InvalidClassException | ClassNotFoundException e) {
System.err.println("Gagal mendeserialisasi: " + e.getMessage());
}
}
// Metode placeholder untuk mendapatkan input tidak terpercaya
private static byte[] getUntrustedInput() throws IOException {
// Dalam skenario nyata, ini bisa berupa data dari HTTP request body, cookie, dll.
// Penyerang akan membuat payload seperti ini:
// ByteArrayOutputStream bos = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(bos);
// oos.writeObject(new GadgetChainObject()); // Objek berbahaya yang dibuat penyerang
// oos.flush();
// return bos.toByteArray();
// Untuk demo, kita simulasikan payload yang akan menyebabkan ClassNotFoundException
// jika kelas tidak ada, atau RCE jika gadget chain ditemukan.
// Contoh payload RCE (menggunakan ysoserial, misal CommonsCollections6)
// new ProcessBuilder("calc.exe").start(); // Akan dieksekusi oleh gadget chain
// Simulasikan payload sederhana yang bukan RCE tapi menunjukkan bahaya
// Misalnya, penyerang mengirim objek dari kelas yang tidak seharusnya ada di sini
return new byte[] {
(byte)0xac, (byte)0xed, (byte)0x00, (byte)0x05, (byte)0x73, (byte)0x72, (byte)0x00, (byte)0x0d,
(byte)0x4a, (byte)0x61, (byte)0x76, (byte)0x61, (byte)0x50, (byte)0x61, (byte)0x79, (byte)0x6c,
(byte)0x6f, (byte)0x61, (byte)0x64, (byte)0x31, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x78,
(byte)0x70
}; // Ini adalah payload dummy, akan menyebabkan ClassNotFoundException
}
}
Penyerang dapat menggunakan tool seperti ysoserial untuk menghasilkan payload Java serialisasi yang, ketika dideserialisasi, akan mengeksekusi perintah sistem (misal: membuka kalkulator, shell reverse, dll.) jika aplikasi menggunakan library tertentu yang rentan (seperti Apache Commons Collections, Spring, dsb.).
Contoh di Python (dengan pickle)
Python memiliki modul pickle untuk serialisasi/deserialisasi objek Python. Sama seperti Java, ini sangat kuat dan berbahaya.
import pickle
import os
import base64
# Sisi Server: Aplikasi yang rentan
def vulnerable_deserialization(data_str):
try:
# Anggap data_str adalah input dari pengguna yang tidak terpercaya
data_bytes = base64.b64decode(data_str)
obj = pickle.loads(data_bytes) # ⚠️ Titik kerentanan!
print(f"Objek dideserialisasi: {obj}")
if hasattr(obj, 'run'):
obj.run() # Jika objek memiliki metode run, ia akan dipanggil
except Exception as e:
print(f"Gagal mendeserialisasi: {e}")
# --- Sisi Penyerang (membuat payload) ---
class Exploit:
def __reduce__(self):
# Ini adalah "gadget" yang akan dipanggil saat deserialisasi
return (os.system, ('echo "Perintah berbahaya dieksekusi!" && touch /tmp/pwned_by_pickle',))
# Buat payload berbahaya
dangerous_payload_bytes = pickle.dumps(Exploit())
dangerous_payload_b64 = base64.b64encode(dangerous_payload_bytes).decode('utf-8')
print(f"Payload berbahaya (Base64): {dangerous_payload_b64}")
# --- Sisi Server (menerima dan memproses payload) ---
print("\nMencoba mendeserialisasi payload berbahaya:")
vulnerable_deserialization(dangerous_payload_b64)
# Contoh payload aman
safe_data = {"message": "Hello, world!"}
safe_payload_bytes = pickle.dumps(safe_data)
safe_payload_b64 = base64.b64encode(safe_payload_bytes).decode('utf-8')
print("\nMencoba mendeserialisasi payload aman:")
vulnerable_deserialization(safe_payload_b64)
Dalam contoh Python di atas, penyerang membuat kelas Exploit dengan metode __reduce__ khusus. Metode ini memberitahu pickle bagaimana cara membuat ulang objek tersebut, dan penyerang menggunakannya untuk memanggil os.system dengan perintah arbitrer. Ketika aplikasi yang rentan mendeserialisasi payload ini, perintah tersebut akan dieksekusi di server.
Catatan untuk JavaScript/Node.js dan PHP
- JavaScript/Node.js:
JSON.parse()tidak rentan terhadap RCE melalui gadget chains karena secara desain hanya mem-parsing data (string, number, array, object literal) dan tidak memanggil konstruktor atau metode. Namun, Prototype Pollution (lihat artikel Menguak Ancaman Prototype Pollution) bisa terjadi jika ada library yang salah menangani input JSON, yang bisa menyebabkan manipulasi properti objek global atau injeksi kode logis. Validasi skema tetap krusial. - PHP: Fungsi
unserialize()di PHP sangat rentan terhadap Unsafe Deserialization, mirip dengan Java dan Python. Penyerang dapat menyuntikkan objek yang memiliki metode “magic” (__wakeup,__destruct,__call) yang, saat dideserialisasi, memicu perilaku berbahaya.
📌 Penting: Masalahnya bukan hanya pada “binary” serialization. Bahkan format seperti XML bisa rentan terhadap serangan XXE (XML External Entity) yang mirip, jika parser XML tidak dikonfigurasi dengan aman.
5. Strategi Pencegahan Efektif ✅
Melindungi aplikasi Anda dari Unsafe Deserialization memerlukan pendekatan berlapis dan kesadaran akan risiko.
✅ 1. Hindari Deserialisasi Data Tidak Terpercaya
🎯 Ini adalah pertahanan terbaik dan paling fundamental. Jika Anda tidak perlu mendeserialisasi data dari sumber yang tidak dapat dipercaya, jangan lakukan. Sesederhana itu. Jika data harus berasal dari pengguna atau sistem eksternal, perlakukan selalu sebagai tidak terpercaya dan terapkan kontrol ketat.
✅ 2. Gunakan Format Data yang Lebih Aman dan Parser yang Cerdas
🛡️ Preferensi Format:
- JSON atau YAML: Lebih disukai daripada format serialisasi bahasa spesifik (seperti Java
Serializable, Pythonpickle, PHPunserialize). JSON dan YAML dirancang untuk pertukaran data, bukan objek kompleks dengan perilaku. - Protocol Buffers (Protobuf), Avro, Thrift: Ini adalah format serialisasi biner yang schema-driven. Artinya, Anda mendefinisikan skema