
در دنیای امنیت وب، هیچ مفهومی به اندازه SQL Injection (SQLi) کلاسیک و در عین حال خطرناک نیست. بسیاری از افراد تنها یاد میگیرند که چگونه با استفاده از ابزارهایی مثل sqlmap به یک سایت نفوذ کنند، اما یک متخصص واقعی، کسی است که میتواند «کد» را کالبدشکافی کند و بفهمد دقیقاً در کدام خط، امنیت فرو ریخته است.
در این مقاله، ما به سراغ آزمایشگاه DVWA (Damn Vulnerable Web Application) میرویم تا با بررسی کدهای PHP در سه سطح Low، Medium و High، بفهمیم چگونه یک ورودی ساده میتواند کل دیتابیس یک سازمان را به چالش بکشد.
فرض کنید یک سایت فروشگاهی دارید که برای نمایش مشخصات محصول، از یک شناسه (ID) در آدرس استفاده میکند: product.php?id=1.
برنامه برای پیدا کردن اطلاعات، این کد را به دیتابیس میفرستد:
SELECT first_name, last_name FROM users WHERE user_id = '$id';
مشکل اینجاست: اگر ما به جای عدد 1 از کدهای مخرب استفاده کنیم، آیا دیتابیس متوجه تفاوت بین «داده» و «دستور» میشود؟
در این سطح، برنامهنویس تمام اعتماد را به کاربر گذاشته است. کد PHP در DVWA به این صورت است:
$id = $_GET[ 'id' ]; $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; $result = mysqli_query($con, $query);
در اینجا متغیر $id مستقیماً از ورودی کاربر ($_GET) گرفته شده و بدون هیچگونه فیلتر یا بررسی، درون رشتهی کوئری قرار گرفته است.
چگونه حمله میکنیم؟
اگر ما به جای id=1 بنویسیم: id=1' OR '1'='1
کوئری نهایی به این شکل در میآید:
SELECT first_name, last_name FROM users WHERE user_id = '1' OR '1'='1';
چون عبارت '1'='1' همیشه درست (True) است، دیتابیس تمام ردیفهای جدول کاربران را به ما برمیگرداند! این یعنی نفوذ موفقیتآمیز.
در سطح Medium، برنامهنویس کمی هوشیارتر شده و سعی کرده با استفاده از توابعی مثل mysqli_real_escape_string جلوی حمله را بگیرد.
$id = $_GET[ 'id' ]; $id = mysqli_real_escape_string($con, $id); // تلاش برای پاکسازی $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; $result = mysqli_query($con, $query);
تابع mysqli_real_escape_string سعی میکند کاراکترهایی مثل ' (Single Quote) را با یک بکاسلش (\') خنثی کند تا از شکستن کوئری جلوگیری کند. اما آیا این کافی است؟
چگونه حمله میکنیم؟
اگر برنامه طوری تنظیم شده باشد که ورودی را فقط به عنوان یک عدد (Integer) در نظر بگیرد و در کوئری از کوتیشن (') استفاده نکند (مثلاً WHERE user_id = $id)، حتی با وجود این تابع، ما همچنان میتوانیم حمله کنیم. در این حالت ما نیازی به کوتیشن نداریم و میتوانیم مستقیماً بنویسیم:
1 OR 1=1
همچنین نفوذگران میتوانند با استفاده از تکنیکهای Encoding (مثل استفاده از %27 به جای ') یا سوءاستفاده از محدودیتهای فیلتر، این سد دفاعی را دور بزنند.
در سطح High، ما با یک برنامهنویس حرفهای روبرو هستیم که از استانداردهای امنیتی مدرن استفاده کرده است. در این سطح، از مفهومی به نام Prepared Statements استفاده میشود.
$id = $_GET[ 'id' ]; $stmt = $con->prepare("SELECT first_name, last_name FROM users WHERE user_id = ?"); $stmt->bind_param("i", $id); // تعیین نوع داده به عنوان integer $stmt->execute(); $result = $stmt->get_result();
اینجاست که جادو اتفاق میافتد! در روش Prepared Statements:
کامپایل اولیه: ابتدا ساختار دستور SQL به دیتابیس فرستاده میشود (بدون دادههای کاربر). دیتابیس میفهمد که دستور چیست: “من میخواهم از جدول کاربران، نام را بر اساس یک ID بخوانم”.
ارسال داده: سپس مقدار $id به عنوان یک «دادهی خالص» فرستاده میشود.
حتی اگر کاربر در $id بنویسد 1' OR '1'='1 ، دیتابیس به دنبال کاربری میگردد که شناسه او دقیقاً رشتهی '1' OR '1'='1' باشد! در واقع دیتابیس دیگر این ورودی را به عنوان «دستور» تفسیر نمیکند، بلکه آن را صرفاً یک «رشته متنی ساده» میبیند. در اینجا حمله کاملاً متوقف میشود.
ما یاد گرفتیم که امنیت در وب، یک انتخاب نیست، بلکه یک ضرورت است:
سطح Low به ما یاد داد که اعتماد به ورودی کاربر، بزرگترین گناه در برنامهنویسی است.
سطح Medium نشان داد که «پاکسازی سطحی» (Sanitization) همیشه کافی نیست و میتواند فریبنده باشد.
سطح High به ما ثابت کرد که تنها راه نجات، استفاده از Parameterized Queries است.
فراموش نکنید: یک هکر فقط به دنبال حفره میگردد، اما یک مهندس امنیت، به دنبال بستنِ ریشه این حفرههاست.
💬 سوال برای بحث:
به نظر شما، در پروژههای بزرگ و هنوز هم خطای انسانی باعث بروز چنین آسیبهای سادهای مثل SQL Injection میشود؟ یا ابزارهای خودکار (Static Analysis) این مشکل را کاملاً حل کردهاند؟ نظرات خود را در کامنتها بنویسید.
در قسمت های بعدی می خوام حملات دیگه رو نشون بدم پس تا پست های بعدی خداحافظ