‹ بازگشت به کتابخانه‌ی رایت‌آپ‌ها
Path Traversal / LFI شدت: بحرانی

Path Traversal در دانلود فایل پیوست با ../ برای خواندن /etc/passwd و فایل‌های env

endpoint دانلود فایل، نام فایل را مستقیم از کاربر می‌گیرد و به مسیر دیسک می‌چسباند؛ با ../../ می‌شود از پوشه‌ی مجاز خارج شد.

نویسنده: تیم PentesterHub

کد زیر (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) با یک رکورد دیتابیسی که مسیر واقعی را نگه می‌دارد استفاده کنید، نه نام فایلِ تایپ‌شده توسط کاربر.

تمرین عملی در آزمایشگاه‌های Path Traversal / LFI ›