اجرای کد از راه دور با PHP Object Injection در کوکی سفارشی "تنظیمات نمایش"
یک کوکی حاوی دادهی serializeشدهی PHP، بدون امضا یا اعتبارسنجی، با unserialize() بازسازی میشود؛ مهاجم با ساختن یک آبجکت مخرب (gadget chain) به RCE میرسد.
برای ذخیرهی ترجیحات نمایش کاربر (مثلاً تم، زبان)، بعضی اپلیکیشنهای قدیمی مستقیم از serialize()/unserialize() روی یک کوکی خام استفاده میکنند:
// نسخهی آسیبپذیر
class UserPrefs {
public $theme = 'light';
}
// ذخیره
setcookie('prefs', serialize(new UserPrefs()));
// بازخوانی — نقطهی خطرناک:
$prefs = unserialize($_COOKIE['prefs']); // <- بدون هیچ اعتبارسنجی یا امضا
echo "تم فعال: " . $prefs->theme;
چرا unserialize خام خطرناک است
مقدار $_COOKIE['prefs'] کاملاً در کنترل کاربر است. PHP وقتی رشته را unserialize میکند، میتواند هر کلاسی که اسمش در رشته آمده را بسازد — نه فقط UserPrefs. اگر در کدبیس (یا یکی از کتابخانههای نصبشده) کلاسی با متد جادویی مثل __destruct() یا __wakeup() وجود داشته باشد که کار خطرناکی انجام میدهد (نوشتن فایل، اجرای دستور، include کردن مسیر دلخواه)، مهاجم میتواند با ساختن یک payload دستی، همان متد را با مقادیر دلخواه صدا بزند. به این الگو «gadget chain» میگویند. نمونهی سادهشده (فرضی) از یک کلاس gadget در یک کتابخانهی لاگ:
class LogFile {
public $path;
public $content;
public function __destruct() {
file_put_contents($this->path, $this->content); // نوشتن فایل دلخواه!
}
}
مهاجم payload زیر را بهجای کوکی عادی میگذارد:
O:7:"LogFile":2:{s:4:"path";s:20:"/var/www/html/x.php";s:7:"content";s:38:"<?php system($_GET['c']); ?>";}
وقتی این رشته unserialize میشود و PHP آبجکت را از بین میبرد (پایان request)، متد __destruct خودکار اجرا میشود و یک فایل PHP حاوی webshell در مسیر قابلدسترسی از وب مینویسد — یعنی اجرای کد دلخواه روی سرور (RCE) از یک کوکی.
راهحل
- هیچوقت unserialize روی دادهای که از کاربر میآید اجرا نکنید. از JSON استفاده کنید (
json_encode/json_decodeهیچ آبجکت دلخواهی نمیسازد). - اگر واقعاً نیاز به serialize دارید، داده را امضا کنید (HMAC) و قبل از unserialize امضا را چک کنید.
- از PHP >= 7 به بعد، آپشن
allowed_classesرا روی unserialize محدود کنید:
// حداقل محافظت در صورت نیاز اجباری به unserialize
$prefs = unserialize($_COOKIE['prefs'], ['allowed_classes' => [UserPrefs::class]]);
بهترین راه همچنان این است: json_decode($_COOKIE['prefs'], true) و کار با آرایهی ساده، نه آبجکت PHP.