Loading...

امنیت · 11 دقیقه مطالعه

تزریق SQL چیست؟ چطور کار می‌کند و چطور متوقفش کنیم

تزریق SQL آسیب‌پذیری‌ای است که در آن ورودی کاربر داخل یک کوئری دیتابیس concatenate می‌شود و دیتابیس بخشی از آن را به‌عنوان SQL اجرا می‌کند. این به یک مهاجم اجازه می‌دهد داده را بخواند، تغییر دهد یا حذف کند، و گاهی سرور را تصاحب کند. راه‌حل این است که کوئری و مقادیر را جدا بفرستید، با parameterised query؛ least privilege، validation و یک WAF وقتی چیزی از لای انگشت‌ها رد شد آسیب را محدود می‌کنند.

به‌روزرسانی

تزریق SQL چیست؟ چطور کار می‌کند و چطور متوقفش کنیم

تزریق SQL چیست

تزریق SQL (SQLi) آسیب‌پذیری‌ای است که در آن متنی که یک کاربر فرستاده داخل یک کوئری دیتابیس چسبانده می‌شود و دیتابیس آن را به‌عنوان SQL اجرا می‌کند. اپلیکیشن قرار بود یک مقدار بفرستد، مثل یک آدرس ایمیل یا شناسهٔ محصول؛ مهاجم به‌جایش یک قطعه از زبان کوئری می‌فرستد، و معنای کوئری عوض می‌شود.

علت ریشه‌ای همیشه یکسان است: کدی که یک کوئری را با concatenation رشته می‌سازد، و دو نوع داده را در یک رشته مخلوط می‌کند. متن کوئری دستورالعملی است که شما نوشته‌اید. مقدار داده‌ای است که کس دیگری نوشته. وقتی این دو به هم چسبیدند، دیتابیس هیچ راهی ندارد بفهمد دستورالعمل شما کجا تمام می‌شود و ورودی بازدیدکننده از کجا شروع.

برای همین تزریق SQL مخصوص MySQL، PostgreSQL، SQL Server یا SQLite نیست، و مخصوص PHP هم نیست. هر زبان، هر درایور و هر دیتابیسی از لحظه‌ای که یک کوئری از رشته‌های غیرقابل‌اعتماد ساخته شود آسیب‌پذیر است. به‌عنوان CWE-89 ثبت شده، و injection در هر نسخهٔ OWASP Top 10 ظاهر شده است. خوشهٔ جست‌وجوهای اطرافش، «حمله SQL»، «آسیب‌پذیری SQL»، «حمله تزریق SQL»، همگی همین یک اشتباه را توصیف می‌کنند.

خبر خوب این است که راه‌حل هم به‌همان‌اندازه یکدست است، دهه‌هاست وجود دارد، و در هر درایور دیتابیسی که امروز استفاده می‌شود جا افتاده: کوئری و مقادیر را جدا بفرستید.

چطور کار می‌کند: مثال کتابی

یک چک ورود را به همان شکلی که بی‌شمار آموزش زمانی می‌نوشتند در نظر بگیرید. نام کاربری و رمز از یک فرم می‌آیند و مستقیم داخل رشتهٔ SQL می‌افتند.

با ورودی عادی، کوئری همان کاری را می‌کند که باید. حالا فرض کنید بازدیدکننده‌ای ' OR '1'='1 را در فیلد رمز می‌نویسد. quote، literal رشته‌ای را که توسعه‌دهنده باز کرده می‌بندد، و بقیه بخشی از WHERE می‌شود. چون '1'='1' همیشه درست است و AND محکم‌تر از OR می‌بندد، این شرط با هر ردیفی مطابقت می‌کند، و کد بازدیدکننده را به‌عنوان اولین کاربر جدول، که اغلب همان مدیر است، لاگین می‌کند.

همان یک quote کل مفهوم است. هر چیز دیگری در این راهنما، انواع مختلف حمله، تأثیر و دفاع‌ها، از این واقعیت می‌آید که دیتابیس یک رشتهٔ تکی گرفته و متن مهاجم را به‌عنوان کد پارس کرده است.

(ذخیرهٔ رمزهای متن‌خام و مقایسه‌شان در SQL یک باگ دوم در این مثال است. کد واقعی کاربر را با نام می‌گیرد و رمز را با password_verify() در برابر یک hash چک می‌کند. اینجا نگه داشته شده چون کوتاه‌ترین راه برای نشان‌دادن injection است.)

آسیب‌پذیر: ورودی کاربر concatenate‌شده در کوئری (PHP)

<?php
// DO NOT DO THIS
$user = $_POST['username'];
$pass = $_POST['password'];

$sql = "SELECT id FROM users
        WHERE username = '$user' AND password = '$pass'";
$row = $pdo->query($sql)->fetch();

// With password  ' OR '1'='1  the database receives:
// SELECT id FROM users
// WHERE username = 'admin' AND password = '' OR '1'='1'
// ...which is true for every row.

راه‌حل: parameterised query، در PHP و Python

یک parameterised query (که prepared statement یا bound parameter هم نامیده می‌شود) متن SQL را با placeholder می‌فرستد، ? یا :name یا %s بسته به درایور، و مقادیر را جدا می‌فرستد. دیتابیس اول کوئری را پارس و برنامه‌ریزی می‌کند، بعد مقادیر را به‌عنوان داده جا می‌گذارد. یک quote درون یک مقدار فقط یک کاراکتر در یک رشته است؛ هرگز نمی‌تواند یک literal را ببندد یا شرطی اضافه کند، چون parsing از قبل تمام شده است.

این یک مرحلهٔ پاک‌سازی نیست که ممکن است یک حالت را از قلم بیندازد. مکانیزم را کاملاً حذف می‌کند، برای همین بالای هر فهرست پیشگیری می‌نشیند.

در PHP، از PDO یا mysqli با placeholder استفاده کنید. با PDO، character set را در DSN تنظیم کنید و emulated prepares را خاموش کنید تا درایور واقعاً prepared statementهای سمت سرور بفرستد، و exceptionها را روشن کنید تا شکست‌ها بی‌صدا نادیده گرفته نشوند. در Python، هر درایور DB-API (sqlite3، psycopg، mysqlclient، PyMySQL) مقادیر را به‌عنوان یک آرگومان جدا به execute() می‌گیرد. تلهٔ Python فرمت‌کردن رشته با f-string یا % خودتان پیش از صداکردن execute() است: نتیجه parameterised به نظر می‌رسد اما concatenation است.

همین قاعده در هر استک دیگری هم برقرار است: PreparedStatement در Java، SqlParameter در .NET، placeholderهای $1 در node-postgres، ? در database/sql در Go.

امن: placeholder، مقادیر جدا فرستاده می‌شوند (PHP PDO و Python)

<?php
$pdo = new PDO('mysql:host=db;dbname=shop;charset=utf8mb4', $dbUser, $dbPass, [
    PDO::ATTR_ERRMODE          => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);

$stmt = $pdo->prepare('SELECT id, password_hash FROM users WHERE username = ?');
$stmt->execute([$_POST['username']]);
$row = $stmt->fetch();
$ok  = $row && password_verify($_POST['password'], $row['password_hash']);

# Python (psycopg 3, PostgreSQL)
cur.execute(
    "SELECT id, password_hash FROM users WHERE username = %s",
    (username,),            # values go here, never into the string
)

# WRONG, still injectable: the string is built before execute() sees it
cur.execute(f"SELECT id FROM users WHERE username = '{username}'")

انواع تزریق SQL

مقاله‌های امنیتی تزریق SQL را بر اساس اینکه مهاجم چطور داده را پس می‌گیرد دسته‌بندی می‌کنند. آسیب‌پذیری در هر حالت یکسان است؛ چیزی که فرق می‌کند این است که اپلیکیشن چه چیزی را فاش می‌کند.

In-band، UNION-based. نتیجهٔ کوئری تزریق‌شده در خود صفحه برمی‌گردد. با اضافه‌کردن یک UNION SELECT، مهاجم ردیف‌هایی از جدول دیگر، مثلاً جدول کاربران، به فهرست محصولی که صفحه از قبل نشان می‌داد اضافه می‌کند. این سریع‌ترین نوع برای exploit‌کردن و ساده‌ترین برای یافتن در تست است.

In-band، error-based. اپلیکیشن پیام‌های خطای دیتابیس را چاپ می‌کند. مهاجم خطاهایی تحریک می‌کند که متنشان حاوی داده‌ای است که می‌خواهد، مثل نام یک جدول یا یک مقدار. نشان‌دادن خطاهای خام SQL به بازدیدکنندگان یک باگ کور را به یک باگ خوانا تبدیل می‌کند، که دلیل خوبی برای لاگ‌کردن خطاها سمت سرور و نشان‌دادن یک صفحهٔ عمومی به کاربران است.

Blind، boolean-based. صفحه نه داده‌ای نشان می‌دهد نه خطایی، اما وقتی یک شرط درست یا غلط باشد رفتار متفاوتی دارد: یک محصول ظاهر می‌شود یا نمی‌شود، یک صفحه 200 است یا 404. با پرسیدن سؤال‌های بله‌یا‌خیر یکی‌یکی، مهاجم داده را کمی‌کمی می‌خواند. آهسته، اما ابزارها آن را خودکار می‌کنند.

Blind، time-based. حتی محتوای صفحه یکسان است، پس مهاجم وقتی یک شرط درست باشد باعث می‌شود دیتابیس صبر کند و زمان پاسخ را می‌سنجد. اگر هر پاسخ یکسان به نظر برسد، زمان‌بندی همچنان یک کانال است.

Out-of-band. مهاجم باعث می‌شود خود سرور دیتابیس با سیستمی که کنترلش را دارد تماس بگیرد، معمولاً از طریق یک lookup DNS یا درخواست HTTP که با یک تابع دیتابیس تحریک شده. این به ویژگی‌های دیتابیس و دسترس‌بودن خروج شبکه بستگی دارد، که یک دلیل دیگر است که سرور دیتابیس نباید بتواند اتصالات خروجی‌ای باز کند که استفاده‌ای برایش ندارد.

Second-order (stored). ورودی بار اول به‌درستی و با parameterisation درست ذخیره می‌شود، و بعداً خوانده و در یک کوئری دیگر concatenate می‌شود، اغلب در یک گزارش مدیریتی یا یک job پس‌زمینه. توسعه‌دهندگان به داده‌ای که از دیتابیس خودشان آمده اعتماد می‌کنند؛ همان اعتماد باگ است. دفاعش همان چیزی است که همه‌جا هست: هر کوئری را parameterise کنید، حتی آن‌هایی که از مقادیری ساخته شده‌اند که خودتان ذخیره کرده‌اید.

یک مهاجم واقعاً چه چیزی به دست می‌آورد

تأثیر به چیزی محدود است که حساب دیتابیسی که اپلیکیشن استفاده می‌کند اجازهٔ انجامش را دارد، که دلیل اهمیت least privilege کمی پایین‌تر در این راهنماست.

خواندن داده. نتیجهٔ رایج: رکوردهای مشتری، آدرس‌های ایمیل، hashهای رمز، سفارش‌ها، کلیدهای API ذخیره‌شده در جدول‌های تنظیمات. هر جدولی که آن حساب بتواند از آن SELECT کند خواندنی است، نه فقط آن یکی که کوئری آسیب‌پذیر لمسش می‌کند.

دور‌زدن احراز هویت. مثل مثال ورود، شرطی که باید خاص باشد همیشه-درست می‌شود.

تغییر یا نابودکردن داده. اگر حساب بتواند UPDATE، INSERT یا DELETE کند، مهاجم هم می‌تواند: عوض‌کردن قیمت‌ها، ساختن کاربر مدیر، پاک‌کردن جدول‌ها. برخی ترکیب‌های درایور و دیتابیس چند statement را در یک فراخوانی اجازه می‌دهند، که این را بیشتر هم می‌کند.

رسیدن به سرور. با دسترسی‌های قدرتمند، برخی دیتابیس‌ها می‌توانند فایل‌های روی host را بخوانند یا بنویسند یا دستورات سیستم‌عامل اجرا کنند. روی یک حساب دیتابیس با اختیارات مدیریتی، یک تزریق SQL می‌تواند به تصاحب کامل سرور تبدیل شود.

این نظری نیست. تزریق SQL نقطهٔ ورود در نقض Heartland Payment Systems در سال 2008 بود، جایی‌که داده‌های کارت بیش از 100 میلیون کارت دزدیده شد؛ در نقض TalkTalk سال 2015 در بریتانیا، که به جریمهٔ نظارتی منجر شد؛ و در exploit انبوه MOVEit Transfer سال 2023 (CVE-2023-34362)، جایی‌که یک نقص تزریق در یک محصول انتقال فایل علیه هزاران سازمان استفاده شد. باگ قدیمی، تیتر امروزی.

پیشگیری، به ترتیب اهمیت

۱. parameterised query همه‌جا. هر کوئری، هر مقدار، حتی مقادیری از دیتابیس خودتان، کوکی‌ها، هدرها و سرویس‌های داخلی. همین به‌تنهایی آسیب‌پذیری را می‌بندد. مراحل دیگر وقتی کسی، جایی، فراموش کند آسیب را محدود می‌کنند.

۲. از ORM یا query builder درست استفاده کنید، و راه‌های فرارش را بشناسید. Eloquent، Doctrine، Django ORM، SQLAlchemy، Hibernate و Entity Framework کوئری‌های معمولی را برایتان parameterise می‌کنند. آن‌ها قطعه‌های raw را محافظت نمی‌کنند: whereRaw، DB::raw، selectRaw و orderByRaw در Laravel، .extra() و .raw() در Django، text() در SQLAlchemy، FromSqlRaw در EF Core. همهٔ آن‌ها binding می‌پذیرند؛ از آن‌ها استفاده کنید. grep‌کردن به‌دنبال آن نام‌های متد سریع‌ترین code review است که می‌توانید انجام دهید.

۳. چیزی که نمی‌تواند پارامتر باشد را allowlist کنید. placeholderها مقدار نگه می‌دارند، نه شناسه. نام یک جدول، یک ستون در ORDER BY یا کلمات ASC/DESC نمی‌توانند bind شوند، پس یک پارامتر «مرتب‌سازی بر اساس» باید در کد به یک فهرست ثابت از نام‌های ستون نگاشت شود. هرگز آن را مستقیم عبور ندهید.

۴. کمترین دسترسی برای کاربر دیتابیس. اپلیکیشن وب باید به‌عنوان کاربری وصل شود که می‌تواند SELECT، INSERT، UPDATE و DELETE را روی schema خودش انجام دهد و هیچ‌چیز دیگر: بدون DROP، بدون GRANT، بدون دسترسی فایل، بدون دیتابیس‌های دیگر، و هرگز حساب root یا sa. migrationها با یک کاربر جدا و قوی‌تر اجرا می‌شوند. اگر یک تزریق از لای انگشت‌ها رد شود، همین تعیین می‌کند آیا یک schema نشت می‌کند یا سرور را تصاحب می‌کند.

۵. validation ورودی به‌عنوان دفاع در عمق. یک شناسهٔ سفارش باید عدد صحیح باشد، یک تاریخ باید به‌عنوان تاریخ پارس شود، یک کد کشور دو حرف است. چک‌کردن نوع و فرمت در لبهٔ اپلیکیشن شما خیلی از ورودی‌های مخرب را زود رد می‌کند و باگ‌ها را می‌گیرد. راه‌حل نیست: یک فیلد نام باید O'Brien را بپذیرد، و یک فیلد نظر تقریباً هر چیزی را می‌پذیرد.

۶. خطا را فاش نکنید. خطاهای دیتابیس را سمت سرور با کوئری و context لاگ کنید؛ به بازدیدکننده یک صفحهٔ خطای عمومی نشان دهید. در PHP این یعنی display_errors=Off در production.

قطعه‌های raw با binding، یک مرتب‌سازی allowlist‌شده، و یک کاربر least-privilege

// Laravel: raw expressions must carry their own bindings
$orders = DB::table('orders')
    ->whereRaw('total > ? AND status = ?', [$min, $status])
    ->get();

// ORDER BY cannot be bound: map input to known columns
$sortable = ['created_at', 'total', 'status'];
$column   = in_array($request->sort, $sortable, true) ? $request->sort : 'created_at';
$dir      = $request->dir === 'asc' ? 'asc' : 'desc';
$orders   = Order::orderBy($column, $dir)->paginate(50);

-- MySQL: the app user gets data access to its own schema only
CREATE USER 'shop_app'@'10.0.%' IDENTIFIED BY '...';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'10.0.%';

چرا escape‌کردن کافی نیست

پیش از آنکه prepared statementها رایج شوند، توصیه escape‌کردن quoteها بود: addslashes()، بعد mysql_real_escape_string()، بعد mysqli::real_escape_string(). escaping هنوز در خیلی از کدها هست، و به شکل‌های قابل‌پیش‌بینی شکست می‌خورد.

فقط context‌های quoted را محافظت می‌کند. WHERE id = $id هیچ quoteای اطراف مقدار ندارد، پس چیزی برای escape‌کردن نیست، و ورودی‌ای مثل 1 OR 1=1 بدون تغییر عبور می‌کند. همین برای LIMIT، ORDER BY و هر چیز عددی هم صدق می‌کند.

به character set بستگی دارد. توابع escape باید با encoding اتصال هماهنگ باشند. ناهماهنگی بین character set کلاینت و سرور اجازه داده دنباله‌های multi-byte backslash escape را ببلعند؛ addslashes() هیچ‌وقت اصلاً encoding را نمی‌دانست.

باید هر بار به‌خاطر آورده شود. یک فراخوانی فراموش‌شده در یک کوئری، یا مقداری که برای HTML به‌جای SQL escape شده، و سوراخ برمی‌گردد. parameterised query مسیر امن را مسیر پیش‌فرض می‌کند.

همین استدلال برای blacklistها و توابع «پاک‌سازی»ای که کلماتی مثل SELECT یا -- را حذف می‌کنند هم صدق می‌کند. مهاجمان بیست سال صرف پیداکردن encodingها، commentها و تغییرات حروف کرده‌اند که از این فیلترها رد می‌شوند. هر فیلتر خودساخته را یک راحتی در نظر بگیرید، هرگز یک محافظت.

تست امن اپلیکیشن خودتان

از کد شروع کنید، نه از حمله. اکثر تزریق SQL در یک جست‌وجوی کد قابل‌دیدن است: کوئری‌هایی که با .، +، string interpolation یا sprintf ساخته شده‌اند، و متدهای raw ORM که با یک متغیر صدا زده شده‌اند. ابزارهای static analysis مثل Semgrep و CodeQL دقیقاً برای همین الگو قاعده دارند و می‌توانند در CI روی هر pull request اجرا شوند.

بعد تست‌هایی بنویسید که مقادیر خصمانه‌به‌نظر اما بی‌خطر، یک quote تکی، یک نام مثل O'Brien، یک رشتهٔ خیلی طولانی، را از میان فرم‌ها و APIهایتان رد می‌کنند، و اطمینان می‌یابند اپلیکیشن یک نتیجهٔ عادی یا یک خطای validation برمی‌گرداند، هرگز یک 500 و هرگز یک پیام خطای دیتابیس. این تست‌ها در suite می‌مانند و در برابر regression محافظت می‌کنند.

برای تست داینامیک، اسکنرهای متن‌باز مثل OWASP ZAP و sqlmap اپلیکیشن‌های در حال اجرا را برای پارامترهای تزریق‌پذیر probe می‌کنند. sqlmap به‌خصوص یک ابزار exploitation است: وقتی سوراخی پیدا کند داده را استخراج می‌کند. آن را فقط روی سیستم‌هایی که مالکشان هستید یا اجازهٔ نوشته‌شده برای تستشان دارید اجرا کنید، ترجیحاً یک کپی staging با داده‌ی تقلبی، چون probeهایش به لاگ می‌نویسند، می‌توانند داده را عوض کنند و می‌توانند monitoring شما را به‌صدا درآورند. اسکن‌کردن سایت کس دیگر بدون اجازه در اکثر کشورها غیرقانونی است، هر نیتی که داشته باشد.

اگر از طریق CDN خودتان تست می‌کنید، انتظار داشته باشید WAF خیلی از probeها را مسدود کند. این یعنی WAF کار خودش را می‌کند، اما رفتار واقعی اپلیکیشن را هم پنهان می‌کند، پس خود اپلیکیشن را روی staging تست کنید و WAF را یک لایهٔ جدا در نظر بگیرید.

SQL concatenate‌شده را در یک codebase پیش از هر کس دیگر پیدا کنید
# PHP: query calls that mention a variable (review each hit)
grep -rnE '(query|exec|prepare)\(.*\$[A-Za-z_]' --include='*.php' app/

# Laravel / Eloquent raw fragments: check each one has bindings
grep -rnE '(whereRaw|selectRaw|orderByRaw|havingRaw|DB::raw)\(' app/

# Python: f-strings or % formatting inside execute()
grep -rnE "execute\(\s*f['\"]|execute\(.*['\"]\s*%\s" --include='*.py' .

جایی که یک WAF می‌نشیند: یک لایه، نه راه‌حل

یک فایروال اپلیکیشن وب پیش از رسیدن هر درخواست به اپلیکیشن شما آن را بازرسی می‌کند و موردهایی را که شبیه حمله‌اند مسدود می‌کند. تزریق SQL شکل‌های قابل‌تشخیصی در query stringها و بدنهٔ فرم‌ها می‌گذارد، پس این یکی از چیزهایی است که یک WAF در آن خوب است: اسکنرهای خودکاری را که هر سایت روی اینترنت را probe می‌کنند متوقف می‌کند، و پیش از آنکه بتوانید پچ کنید، وقتی یک افزونهٔ آسیب‌پذیر فاش می‌شود زمان می‌خرد.

همچنان pattern matching در برابر آسیب‌پذیری‌ای است که در کد شما زندگی می‌کند. یک مهاجم هدفمند payload را شکل می‌دهد تا از الگوها فرار کند، و برخی نقاط تزریق، مثل فیلدهای JSON با یک encoding غیرمعمول، مقادیری که از طریق یک job پس‌زمینه به یک کوئری می‌رسند، یا second-order injection، در یک درخواست اصلاً مشکوک به نظر نمی‌رسند. راهنمای OWASP Top 10 نقشه می‌کشد که یک WAF چه چیزی را می‌تواند پوشش دهد و چه چیزی را نه. parameterised queryها همچنان راه‌حل‌اند.

هزینهٔ عملیاتی false positiveهاست. هر قاعده‌ای که به‌اندازهٔ کافی سفت باشد تا injection را بگیرد، گاهی یک انسان را هم مسدود می‌کند: ویرایشگری که یک پست با یک نمونه‌کد ذخیره می‌کند، فرم پشتیبانی‌ای که کسی پیام خطای حاوی SQL را در آن paste می‌کند. رویهٔ معمول این است که اول قواعد جدید را در حالت detection (فقط-لاگ) اجرا کنید، بخوانید چه چیزی مسدود می‌شد، و بعد enforce کنید، با استثناهای باریک جایی‌که یک فیلد واقعاً متن شبه-SQL حمل می‌کند.

روی CDN.com.tr، WAF ModSecurity با OWASP Core Rule Set است، که به‌ازای هر حساب از صفحهٔ قوانین تحویل روشن می‌شود. در آن rule set، خانوادهٔ 942 قواعد تزریق SQL است. یک بازدیدکنندهٔ مسدودشده یک صفحهٔ 403 برندشده با یک شناسهٔ reference می‌گیرد، که شناسهٔ درخواست در لاگ audit است، پس می‌توانید دقیقاً ببینید کدام قاعده و روی کدام فیلد مطابقت داشته. صفحهٔ لاگ WAF در پنل رویدادهای مسدودشده را با دسته‌ی حمله، کشور، IP و قاعده فهرست می‌کند، به CSV یا XLSX export می‌کند، و cdnctl waf logs همان را از خط فرمان نشان می‌دهد. وقتی یک فیلد مشروع یک قاعده را تحریک می‌کند، راه‌حل باید باریک باشد: همان قاعده، روی همان فیلد، روی همان مسیر، به‌جای خاموش‌کردن محافظت برای کل سایت. استثنا برای مسیرهای تکی هنوز در پنل نیست، پس شناسهٔ reference درخواست را به پشتیبانی بفرستید. برای فرم‌های ورود، rate limiting به‌ازای هر route روی همان صفحه می‌نشیند و ترافیک brute-force را که اغلب با اسکن‌های تزریق می‌آید کند می‌کند.

پرسش‌های پرتکرار دربارهٔ تزریق SQL

آیا تزریق SQL هنوز در 2026 مشکلی است؟

بله. فریمورک‌های مدرن مسیر امن را پیش‌فرض می‌کنند، اما کوئری‌های raw، کد قدیمی، افزونه‌ها و ابزارهای داخلی سریع همچنان رشته‌ها را concatenate می‌کنند، و نقص‌های جدید تزریق در محصولات پراستفاده همچنان فاش می‌شوند و در مقیاس بزرگ exploit می‌شوند. injection در هر نسخهٔ OWASP Top 10 ظاهر شده است.

آیا prepared statementها همهٔ تزریق‌های SQL را جلو می‌گیرند؟

injection را از طریق هر مقداری که bind می‌کنید جلو می‌گیرند. نمی‌توانند شناسه‌هایی مثل نام جدول یا ستون یا جهت مرتب‌سازی را bind کنند، و اگر اول یک رشته بسازید و بعد نتیجه را «prepare» کنید کمکی نمی‌کنند. شناسه‌ها را allowlist کنید و هرگز ورودی را داخل متن SQL فرمت نکنید.

آیا استفاده از یک ORM من را از تزریق SQL امن می‌کند؟

اکثراً، برای کوئری‌های عادی. هر ORM متدهای raw دارد، مثل whereRaw، DB::raw، .raw()، text() یا FromSqlRaw، که SQL را بدون تغییر عبور می‌دهند. آن‌ها را با binding استفاده کنید، و هر فراخوانی را بازبینی کنید.

آیا validation ورودی برای متوقف‌کردن تزریق SQL کافی است؟

نه. validation یک خط دفاعی دوم مفید است: یک شناسه باید عددی باشد و یک تاریخ باید به‌عنوان تاریخ پارس شود. اما خیلی از فیلدها باید quote و متن آزاد را بپذیرند، پس validation نمی‌تواند محافظت باشد. parameterised queryها هستند.

آیا یک WAF می‌تواند تزریق SQL را کاملاً متوقف کند؟

اکثریت بزرگ تلاش‌های خودکار را متوقف می‌کند و تلاش لازم برای تلاش‌های هدفمند را بالا می‌برد، اما الگوها را در درخواست‌ها مطابقت می‌دهد و یک مهاجم مصمم می‌تواند payload را برای فرارکردن از آن‌ها شکل دهد. second-order injection در یک درخواست اصلاً مشکوک به نظر نمی‌رسد. از یک WAF به‌عنوان عمق استفاده کنید، با parameterised query به‌عنوان راه‌حل.

آیا تست یک وب‌سایت برای تزریق SQL با sqlmap قانونی است؟

فقط روی سیستم‌هایی که مالکشان هستید یا اجازهٔ صریح نوشته‌شده برای تستشان دارید. اسکن‌کردن سایت کس دیگر در اکثر حوزه‌های قضایی دسترسی غیرمجاز است. محیط staging خودتان را با داده‌ی تقلبی تست کنید؛ اگر نقصی در محصول کس دیگری پیدا کردید، از طریق فرآیند افشای آن‌ها گزارش دهید.

تفاوت تزریق SQL و XSS چیست؟

تزریق SQL باعث می‌شود دیتابیس شما SQL نوشته‌شده توسط مهاجم را اجرا کند. Cross-site scripting باعث می‌شود مرورگر بازدیدکننده JavaScript نوشته‌شده توسط مهاجم را در context سایت شما اجرا کند. هر دو injection‌اند، با همان علت ریشه‌ای مخلوط‌کردن داده و کد، و هر کدام راه‌حل خودش را لازم دارد: parameterised query برای SQL، encoding خروجی آگاه به context برای HTML. XSS چیست؟ را ببینید.