یکی از جذابترین چالشهایی که حدوداً سال گذشته روی محصول هوش تجاریمون کار کردیم، مقابله با SQL Injection بود.
جالب اینجاست که درست زمانی که داشتیم این قابلیت رو تست میکردم و تحویل میدادم، مثل امروز روش عمیق نشده بودم. اما مطالعهای که بعداً انجام دادم، واقعاً هیجانانگیز و آموزنده بود. اگه شما هم در حوزه توسعه یا تست نرمافزار فعالیت میکنید، این ماجرا ممکنه براتون جذاب باشه.
SQL Injection چیه؟ (به زبان خیلی ساده)
برای توضیحش، بیایید یک صفحه لاگین رو تصور کنیم. کاربر نام کاربری و رمز عبور رو وارد میکنه و دکمه «ورود» رو میزنه.
پشت صحنه چه اتفاقی میافته؟ برنامه ما یک «دستور» یا «کوئری» به دیتابیس میفرسته تا وجود کاربر رو بررسی کنه.
SELECT * FROM Users WHERE Username = 'USERNAME_INPUT' AND Password = 'PASSWORD_INPUT'مثلاً اگه کاربر aria و رمز 1234 رو وارد کنه، دستور نهایی به این شکل میشه:
SELECT * FROM Users WHERE Username = 'aria' AND Password = '1234'دیتابیس جستجو میکنه و اگه کاربری وجود داشت، اجازه ورود میده.
حالا حمله چطور اتفاق میافته؟
هکر به جای یک نام کاربری معمولی، یک دستور SQL مخرب وارد میکنه! مثلاً در فیلد «نام کاربری» این رو تایپ میکنه:
' OR '1'='1و در فیلد رمز عبور هم یک عبارت دلخواه مثل abc رو میزنه. حالا، دستوری که به دیتابیس فرستاده میشه، به شکل عجیبی تغییر میکنه:
SELECT * FROM Users WHERE Username = '' OR '1'='1' AND Password = 'abc'این کوئری چه کاری انجام میده؟ دیتابیس اینطور تفسیرش میکنه: «یک کاربر رو انتخاب کن که یا نام کاربریاش خالی باشه، یا اینکه ‘۱’ برابر با ‘۱’ باشه (که همیشه درسته!) و رمزش abc باشه.»
نتیجه؟ اون شرط همیشه درست (OR ‘1’=‘1’) کل کوئری رو تحت تأثیر قرار میده و دیتابیس اولین کاربر از جدول (که احتمالاً یک ادمین است!) رو برمیگردونه. هکر بدون دانستن رمز عبور، وارد سیستم میشه!
این آسیبپذیری فقط مربوط به لاگین نیست!
این ضعف امنیتی میتونه در هر جایی که دیتای کاربر مستقیم به دیتابیس وصل میشه مثل فرم جستجو، فیلتر و مرتبسازی، پارامترهای URL، فرم تماس و نظرسنجی، HTTP Headers وجود داشته باشه. مثلاً برای فرم جستجو که با کوئری زیر نوشته شده باشه:
$query = "SELECT * FROM Products WHERE name LIKE '%" . $_GET['search'] . "%'";اگه کاربر در کادر جستجو این عبارت رو بنویسه:
'; DROP TABLE Products; --کوئری نهایی به این شکل تبدیل میشه:
SELECT * FROM Products WHERE name LIKE '%'; DROP TABLE Products; --%'این دستور اول همه محصولات رو برمیگردونه و سپس دستور DROP رو اجرا میکنه که کل جدول Products رو پاک میکنه! بخش – بقیه دستور رو کامنت میکنه.
حملات پیشرفتهتر چطور هستند؟
حملات کلاسیک فقط شروع ماجرا هستند. روشهای پیچیدهتری هم وجود داره:
Blind SQL Injection: هکر نتایج کوئری رو مستقیماً نمیبینه، اما با تحلیل زمان پاسخ سرور یا پیغامهای خطا میتونه دادهها رو استخراج کنه. (مثلاً اگر اولین حرف پسورد ادمین ‘a’ بود، سرور ۵ ثانیه تاخیر داشته باشه!).
Out-of-Band SQL Injection: هکر دادههای دزدیده شده رو از طریق یک کانال جانبی مثل درخواست DNS به سرور تحت کنترل خودش ارسال میکنه.
Second-Order SQL Injection:
payload مخرب اول در دیتابیس ذخیره میشه (مثلاً در یک نظر). وقتی بعدا یک کاربر privileged (مثل ادمین) اون داده رو میخونه، payload فعال میشه.
چطور میتونیم مقابله کنیم؟
مقابله با SQL Injection فقط یک «فیچر» نیست؛ یک مسئله معماریه که نیازمند یه استراتژی چندلایه (Defense in Depth) هست:
Prevention (پیشگیری): استفاده اجباری از Parameterized Queries یا Prepared Statements. این خط دفاعی اصلی و غیرقابل چشمپوشی است.
Sanitization (پاکسازی): اعتبارسنجی و فیلتر کردن تمامی ورودیهای کاربر بر اساس یک لیست مجاز (Allow-List).
Least Privilege (کمترین اختیار): اتصال برنامه به دیتابیس با یک کاربر که حداقل دسترسی لازم رو داره (مثلاً هیچگاه از کاربر root استفاده نکنید!).
Obfuscation (مخفیسازی): هیچ وقت پیغامهای خطای خام دیتابیس رو به کاربر نهایی نشون ندید.
Detection (آشکارسازی): استفاده از WAF برای شناسایی الگوهای مشکوک.
Hardening (سختسازی): غیرفعال کردن قابلیتهای خطرناک و غیرضروری دیتابیس.
میشه هر یکی از شیوههای ایمنسازی که برای این ضعف امنیتی استفاده میشه رو به تفصیل شرح داد که تو این پست نمیگنجه. ایمنسازی محصولمون در برابر این حمله، یه سفر بود، نه یه مقصد. حالا میتونیم با اعتماد به نفس بیشتری اپمون رو بزرگتر کنیم.