جلوگیری از SQL Injection در PHP با Prepared Statement
آموزش

جلوگیری از SQL Injection در PHP با Prepared Statement

اگر کوئری‌ات را با چسباندن رشته می‌سازی، فرقی ندارد چقدر فرم زیبا طراحی کرده باشی؛ یک آپاستروف در ورودی کافی است

تحریریه‌ی دینا کد
نویسنده
۳ دقیقه مطالعه۲ بازدید
اشتراک گذاری۲
فهرست مطالب
  1. ۱حمله‌ای که واقعاً کار می‌کند
  2. ۲همان ورودی، از یک prepared statement رد می‌شود
  3. ۳چرا escape‌کردن دستی کافی نیست
  4. ۴قانون عملی برای کل پروژه
  5. ۵حتی با prepare درست، یک آرایه را نمی‌شود به IN(...) بایند کرد

هیچ‌وقت ورودی کاربر را مستقیم داخل رشته‌ی SQL نچسبان. همیشه PDO::prepare با پارامتر جای‌گذاری‌شده (? یا :name) بنویس؛ نه به‌خاطر استایل، بلکه چون نسخه‌ی رشته‌ای واقعاً هک می‌شود.

حمله‌ای که واقعاً کار می‌کند

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

php$username = "' OR '1'='1";
$sql = "SELECT * FROM users WHERE username = '$username'";
$rows = $pdo->query($sql)->fetchAll();

کوئری نهایی که اجرا می‌شود:

sqlSELECT * FROM users WHERE username = '' OR '1'='1'
rows returned: 2
 - admin
 - sara

ورودی حتی رمز عبور هم نداشت؛ فقط یک آپاستروف و یک شرطِ همیشه‌درست کافی بود تا کل جدول کاربران، از جمله admin، برگردد.

همان ورودی، از یک prepared statement رد می‌شود

php$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ?');
$stmt->execute([$username]);
$rows = $stmt->fetchAll();
rows returned: 0

با prepared statement، مقدار $username هیچ‌وقت به‌عنوان بخشی از متن SQL دیده نمی‌شود؛ جدا از متن کوئری به دیتابیس فرستاده می‌شود، پس آپاستروف داخلش فقط یک کاراکتر عادی است و syntax را نمی‌شکند.

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

توابعی مثل addslashes همیشه با انکودینگ واقعی دیتابیس هماهنگ نیستند؛ هر جایی که یک راه فراموش شود، حمله دوباره باز می‌شود. Prepared statement این مشکل را از ریشه حذف می‌کند؛ چیزی برای فراموش‌کردن باقی نمی‌ماند، چون داده هیچ‌وقت وارد متن کوئری نمی‌شود.

قانون عملی برای کل پروژه

  • هر جا کوئری با متغیر کاربر ساخته می‌شود، یا prepare است یا باگ.
  • اسم جدول و ستون را نمی‌شود parameterize کرد؛ آن‌ها را از یک allowlist ثابت در کد بردار، نه از ورودی کاربر.
  • ORMها (مثل Eloquent یا Doctrine) این کار را زیر کاپوت انجام می‌دهند؛ خطر واقعی جایی است که کسی برای «سرعت» یک کوئری خام با رشته می‌نویسد.

حتی با prepare درست، یک آرایه را نمی‌شود به IN(...) بایند کرد

php$ids = [1, 2];
$stmt = $pdo->prepare('SELECT * FROM users WHERE id IN (?)');
$stmt->execute($ids);
// PDOException: SQLSTATE[HY000]: General error: 25 column index out of range

یک placeholder فقط یک مقدار می‌گیرد، نه یک آرایه؛ execute($ids) سعی می‌کند دو مقدار را به یک ? بدهد و خطا می‌دهد. پیام خطا به درایور بستگی دارد؛ خروجی بالا را PDO با SQLite می‌دهد و روی MySQL همان جای‌گذاری اشتباه با کد HY093 و پیام Invalid parameter number برگشت می‌خورد. راه درست، ساختن همان تعداد ? که آرایه عضو دارد:

php$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($ids);
// rows: 2

اسم ستون و تعداد ? از کد می‌آید، نه از ورودی کاربر؛ فقط خودِ مقادیر آرایه پارامتر می‌شوند. قاعده‌ی همیشگی هم همین است: داده جای داده، ساختار کوئری جای کد.

اگر PHP را از پایه و با پروژه‌ی واقعی یاد می‌گیری، دوره‌ی PHP همین‌جور اشتباه‌های امنیتی رایج را قبل از پروداکشن نشانت می‌دهد.

تحریریه‌ی دینا کد
نویسنده

نظرها

اولین نفری باشید که نظر می‌دهد
برای ثبت نظر وارد شوید

سؤالتان را بپرسید یا تجربه‌تان را بنویسید تا نویسنده‌ی مطلب پاسخ بدهد.

مطالب مرتبط

از همین دسته
مدیریت خطا با try و except در پایتون
آموزش

مدیریت خطا با try و except در پایتون

راهنمای گام‌به‌گام مهار خطاهای زمان اجرا در پایتون با بلوک‌های try و except همراه با بررسی خطاهای رایج و ساختار اصولی.

تحریریه‌ی دینا کد۵ دقیقه
مدیریت خطا در Rust با Result و Option
آموزش

مدیریت خطا در Rust با Result و Option

زبان Rust به جای ساختارهای سنتی استثنا از نوع‌های Result و Option برای مدیریت شفاف و پیش‌بینی‌پذیر خطاها در زمان کامپایل استفاده می‌کند.

تحریریه‌ی دینا کد۶ دقیقه
تفاوت واحدهای px و em و rem در CSS و کاربرد هرکدام
آموزش

تفاوت واحدهای px و em و rem در CSS و کاربرد هرکدام

واحدهای px، em و rem هرکدام برای بخش مشخصی از صفحه‌آرایی وب طراحی شده‌اند و انتخاب نادرست آن‌ها مقیاس‌پذیری و دسترسی‌پذیری سایت را مختل می‌کند.

تحریریه‌ی دینا کد۶ دقیقه