SQL Injection چیست و چگونه آن را جلوگیری کنید؟
تفکیک عملی حملات SQL injection، نمونههای واقعی، و راهحلهای مشخصی که واقعاً در کد تولیدی جلوگیری میکنند.
SQL injection از اواخر دهه ۱۹۹۰ وجود داشته و همچنان یکی از رایجترین راههای سوء استفاده از برنامههای وب است. خرابی ساده است: مهاجم ورودی میفرستد که به جای دادهی ساده به عنوان کد SQL تفسیر میشود و پایگاه داده کاری را انجام میدهد که هرگز فرض نشده بود.
نحوه کار حقیقی حملات
فرض کنید فرم ورودکنندهای یک پرسوجو را مثل این در PHP میسازد:
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
اگر برنامه ورودی را پاکسازی نکند، مهاجم admin' -- را در فیلد نام کاربری تایپ میکند. پرسوجو به این تبدیل میشود:
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
-- بقیه خط را به عنوان نظر نشانهگذاری میکند، بنابراین بررسی رمز هرگز اتفاق نمیافتد. این یک دوری احراز هویت کلاسیک است. مهاجمان همچنین از UNION SELECT برای کشیدن داده از جداول دیگر استفاده میکنند، یا پرسوجوها را با نقطهویرگول پشتهبندی میکنند تا کار مخربی مثل DROP TABLE users; را اجرا کنند اگر درایور چند دستور را پذیرفته باشد.
SQL injection کور نیز وجود دارد، جایی که برنامه نتایج پرسوجو را مستقیماً نشان نمیدهد. مهاجمان اطلاعات را از طریق زمانبندی (SLEEP(5) در MySQL) یا پاسخهای بولی استنتاج میکنند (صفحه برای شرط درست یا نادرست فرق میکند). ابزارهایی مثل sqlmap این نوع استخراج را خودکار میکنند وقتی پارامتر آسیبپذیر پیدا شود.
چرا الحاق رشتهها مشکل اساسی است
هر نوعی از این حمله به یک چیز برمیگردد: مخلوط کردن کد و داده در یک رشته. پایگاه داده نمیتواند تفاوت بین مقدار واقعی و نحو تزریق شده را تشخیص دهد زیرا هردو از یک کانال میرسند. اسکیپ کردن نقلقولها در برخی موارد کمک میکند اما شکننده است — کدگذاریهای مختلف، injection درجه دوم (داده یکبار ذخیره، سپس بعداً نامحفوظانه دوباره استفاده)، و غرایب مخصوص درایور همگی مسیر دوری ایجاد میکنند. اسکیپ کردن وصله است، نه حل.
پرسوجوهای پارامتری راهحل واقعی هستند
حل این است که کد SQL را از دادههای کاربری در سطح درایور جدا کنید، با استفاده از پرسوجوهای پارامتری (همچنین دستورهای آمادهشده نامیده میشوند). پایگاه داده ابتدا ساختار پرسوجو را دریافت میکند، سپس مقادیر را بعداً محدود میکند، بنابراین ورودی کاربر هرگز نمیتواند معنی پرسوجو را تغییر دهد.
در Python با psycopg2:
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
در Node.js با mysql2:
connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
در Java با JDBC:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);
الگو را ببینید: جایگذینها (%s, ?) جای داده را گرفتند و مقادیر واقعی به طور جداگانه ارسال میشوند. بدون الحاق رشته، بدون اسکیپ کردن دستی. این برای اکثریت قریبالاتفاق پرسوجوهایی که در برنامهای معمولی خواهید نوشت کار میکند.
نام جدول یا ستون پویا چطور؟
پارامتریکردن مقادیر را کنترل میکند اما نه شناسهها — نمیتوانید نام جدول را به عنوان پارامتر محدود کنید. اگر برنامه باید به صورت پویا یک جدول را انتخاب کند (نادر و اغلب بوی بد طراحی)، مقادیر مجاز را در مقابل یک فهرست سختکد شده فهرست سفید کنید نه اینکه به طور مستقیم به ورودی کاربر اعتماد کنید:
allowed_tables = {'orders', 'invoices', 'customers'}
if table_name not in allowed_tables:
raise ValueError("Invalid table")
هرگز نام شناسه را از طریق قالببندی رشته از ورودی کاربری نسازید، حتی با اسکیپ کردن.
دفاعهای لایهای فراتر از خود پرسوجو
پرسوجوهای پارامتری کنترل اصلی هستند، اما چند چیز دیگر اهمیت دارند:
- کمترین امتیاز برای حساب پایگاه داده. کاربر DB برنامه نباید
DROP،ALTER، یا دسترسی به اسکیماهای نامرتبط داشته باشد. اگر injection عبور کند، امتیازات محدود آسیب را محدود میکند. - ORMها به طور پیشفرض کمک میکنند. Django ORM، SQLAlchemy، و Hibernate پرسوجوها را خودکار پارامتری میکنند وقتی روشهای استاندارد ساخت پرسوجو را استفاده کنید. خطر دوباره ظاهر میشود وقتی توسعهدهندگان به SQL خام بروند یا از
.extra()/text()با الحاق رشته استفاده کنند — بنابراین آن نقاط را به طور خاص بررسی کنید. - اعتبارسنجی ورودی یک لایه ثانویه است، نه جایگزین. بررسی اینکه فیلد ایمیل شبیه ایمیل است عملی خوب است، اما به تنهایی جلوی injection را نمیگیرد — مهاجمان بار خلاقانه پیدا میکنند که همچنان از اعتبارسنجی شل عبور کنند.
- WAF میتواند الگوهای حمله شناخته شده را بگیرد، اما یک لایه تشخیص است، نه حل برای کد زیرین.
آزمایش کد خود برای این خرابی
پرسوجوهای خود را از طریق یک ابزار تجزیه ایستا اجرا کنید (Bandit برای Python، Semgrep با مجموعه قوانین SQL injection) به عنوان بخشی از CI. برای آزمایش دستی، سعی کنید یک نقلقول تنها (') را در هر فیلد ورودی تزریق کنید و برای پیامهای خطای SQL نشتی در پاسخ ببینید — اغلب اولین نشانه پرسوجو پارامتری نیست.
اگر میخواهید بیشتر درباره این موضوع بدانید، Web Security track کتابخانهی Korra Studio injection را در کنار XSS و auth bypass پوشش میدهد و بخشهای Databases از طریق الگوهای طراحی پرسوجو میروند که این کلاس خرابی را کاملاً میگیرند.
با کمک هوش مصنوعی نوشتهشده، بازبینی و منتشرشده توسط Michal Pilch (CISSP)، Korra Studio.
این یکی از یادداشتهای پایگاه دانش Korra Studio است — پلتفرم هر موضوع را با مربی یکبهیک جفت میکند.
شروع رایگانarrow_forward