‹ بازگشت به کتابخانه‌ی رایت‌آپ‌ها
SSRF شدت: بحرانی

SSRF از طریق ویژگی «پیش‌نمایش لینک» برای خواندن متادیتای سرویس‌های ابری

فیچر «آدرس عکس پروفایل را وارد کن، ما دانلودش می‌کنیم» بدون فیلتر، سرور را وادار می‌کند به آدرس‌های داخلی/متادیتای cloud درخواست بزند.

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

بسیاری از سایت‌ها یک فیچر «آدرس عکس را بده، ما دانلودش می‌کنیم و نمایش می‌دهیم» دارند. نمونه‌ی کد (Node.js/Express):

// نسخه‌ی آسیب‌پذیر
app.post('/profile/avatar-from-url', async (req, res) => {
    const { url } = req.body;
    const response = await fetch(url);          // <- هیچ فیلتری روی url نیست
    const buffer = await response.arrayBuffer();
    saveAvatar(req.user.id, buffer);
    res.json({ ok: true });
});

شناسایی

به‌جای یک آدرس عکس واقعی، یک آدرس داخلی می‌فرستیم:

POST /profile/avatar-from-url
{ "url": "http://127.0.0.1:6379/" }   // بررسی سرویس‌های داخلی سرور (مثل redis)

اگر پاسخ یا زمان پاسخ تفاوت معناداری با یک آدرس نامعتبر داشته باشد، یعنی سرور واقعاً به آن آدرس داخلی وصل شده (SSRF تایید شد).

بهره‌برداری جدی‌تر: متادیتای Cloud

در AWS/GCP/Azure یک endpoint داخلی ویژه برای گرفتن credential موقت ماشین وجود دارد که فقط از خودِ سرور قابل دسترسی است:

POST /profile/avatar-from-url
{ "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }

اگر سرور این مسیر را واکشی کند و نتیجه (حتی به‌صورت غیرمستقیم، مثلاً در پیام خطا) برگردانده شود، مهاجم می‌تواند credential موقت IAM را به دست بیاورد — یعنی دسترسی به سرویس‌های ابری پشت سرور، نه فقط خودِ وب‌اپ.

راه‌حل

// نمونه‌ی رفع با allowlist + چک IP
const { URL } = require('url');
const dns = require('dns').promises;

async function isSafeUrl(rawUrl) {
    const u = new URL(rawUrl);
    if (u.protocol !== 'https:') return false;
    const { address } = await dns.lookup(u.hostname);
    const blocked = /^(127\.|10\.|172\.(1[6-9]|2\d|3[0-1])\.|192\.168\.|169\.254\.)/;
    return !blocked.test(address);
}

تمرین عملی در آزمایشگاه‌های SSRF ›