Path Traversal در دانلود فایل پیوست با ../ برای خواندن /etc/passwd و فایلهای env
endpoint دانلود فایل، نام فایل را مستقیم از کاربر میگیرد و به مسیر دیسک میچسباند؛ با ../../ میشود از پوشهی مجاز خارج شد.
کد زیر (PHP خام) فایل پیوست یک تیکت را سرو میکند:
// download.php — نسخهی آسیبپذیر
$filename = $_GET['file'];
$path = '/var/www/app/storage/attachments/' . $filename;
header('Content-Type: application/octet-stream');
readfile($path);
شناسایی و بهرهبرداری
استفادهی عادی:
GET /download.php?file=report.pdf
ولی چون $filename عیناً به مسیر چسبانده میشود، با ../ میشود از پوشهی attachments بیرون رفت:
GET /download.php?file=../../../../etc/passwd
GET /download.php?file=../../../../var/www/app/.env <- خیلی بدتر: کلیدهای دیتابیس/APP_KEY
خروجی .env معمولاً شامل DB_PASSWORD، MAIL credentials، و کلیدهای API سرویسهای ثالث است — یعنی این یک باگ تنها نیست، سکوی پرشی به کل زیرساخت است.
چرا فیلترهای ساده کافی نیستند
بعضی توسعهدهندهها فقط رشتهی ../ را حذف میکنند که دور زدنش ساده است:
// فیلتر ناقص: str_replace('../', '', $filename)
// بایپس با encoding دوبل یا تو در تو:
file=..%252f..%252f..%252fetc%252fpasswd
file=....//....//....//etc/passwd (بعد از حذف یکبارهی ../، دوباره ../ تشکیل میشود)
راهحل درست
هرگز مسیر دیسک را از ورودی کاربر نسازید؛ بهجایش یک شناسه (ID) در دیتابیس نگه دارید و نام واقعی فایل را سرور تصمیم بگیرد، یا حداقل basename + realpath را اعتبارسنجی کنید:
$base = realpath('/var/www/app/storage/attachments');
$target = realpath($base . '/' . basename($_GET['file']));
if ($target === false || !str_starts_with($target, $base . DIRECTORY_SEPARATOR)) {
http_response_code(404);
exit;
}
readfile($target);
در Laravel، بهتر است اصلاً مستقیم از دیسک سرو نشود؛ از Storage::download($id) با یک رکورد دیتابیسی که مسیر واقعی را نگه میدارد استفاده کنید، نه نام فایلِ تایپشده توسط کاربر.