منظور از SQL Injection چیست؟
نیاز به توضیح نیست که SQL نام زبانی است که برای کار با سیستم مدیریت دیتابیسی همچون MySQL به کار گرفته میشود و واژهٔ Injection هم به معنی «تزریق» است و به طور کلی منظور از SQL Injection فرایندی است که از آن طریق هکرها دستوراتی از جنس اسکیوال ایجاد کرده و به سادگی با استفاده از فرمهایی که داخل سایتها قرار دارند میتوانند این دستورات را به سمت دیتابیس مد نظرشان ارسال نموده و به اهداف خود (شامل فراخوانی دادهها، حذف دادهها و دیگر اقداماتی از این دست) دست یابند.
(برای مطالعهی بیشتر در مورد SQL Injection ها، می توانید به دورهی آموزش OWASP Top 10 در سایت سکان آکادمی مراجعه کنید.)
به نظر میرسد اگر ترجمهٔ فارسی لغت به لغت «تزریق دستورات اسکیوال» را برای SQL Injection انتخاب نماییم، معادل خیلی بدی انتخاب نکرده باشیم زیرا همانطور که از نام این اصطلاح برمیآید، هکرها با تزریق کدهای مد نظر خود به راحتی اقدام به نوشتن یکسری دستورات واقعی SQL میکنند و با استفاده از آنها به راحتی تَسکهای مد نظر خود را عملی میکنند (برای این منظور، هکرها معمولاً از فرمهای اینترنتی مثل فرم تماس با ما، فرم ورود به ناحیهٔ کاربری، فرم ثبتنام و ... استفاده میکنند.) برای روشن شدن این مسئله، فرم زیر را در نظر میگیریم:
<form action="script.php" method="post">
<input type="text" name="username" placeholder="Please Enter Your Username">
<input type="password" name="password" placeholder="Please Enter Your Password">
<input type="submit" value="Login">
</form>
خروجی کدهای فوق به صورت زیر خواهد بود:
در حقیقت، فرمی داریم که حاوی دو فیلد است که یکی برای نام کاربری و دیگری برای رمزعبور است و اَکشنی هم که برای این فرم در نظر گرفتهایم یک فایل PHP است تحت عنوان script.php حاوی کدهای زیر:
session_start();
$username_set = false;
$password_set = false;
$username = $_POST['username'];
$password = $_POST['password'];
if (empty($username)) {
echo "<p class=\"error\">Please Enter your username</p>";
} else {
$username_set = true;
}
if (empty($password)) {
echo "<p class=\"error\">Please Enter your password</p>";
} else {
$password = sha1($_POST['password']);
$password_set = true;
}
if ($username_set == true && $password_set == true) {
$sql = "SELECT * FROM `users` WHERE `username` = '$username' AND `password` = '$password'";
$query = mysql_query($sql, $connection);
$result = mysql_fetch_assoc($query);
$_SESSION['id'] = $result['id'];
$_SESSION["user_name"] = $result["username"];
echo "You are logged in. In a second or two, you`ll be redirected to your profile page";
header("location:logged-in.php");
}
در واقع، کاربران برای ورود به یک ناحیهٔ کاربری فرضی، باید نام کاربری و رمزعبور خود را وارد فرم فوق نمایند و در صورتی که این کار با موفقیت انجام شود، وارد صفحهای تحت عنوان logged-in.php میشوند که حاوی اسکریپت زیر است:
session_start();
if (!$_SESSION['user_name']) {
header("location:./");
}
$title = "Hi " . $_SESSION['user_name'] . " you are logged in";
$userID = $_SESSION['id'];
define("TITLE", $title);
include("includes/header.php");
require("includes/db_config.php");
$sql = "SELECT * FROM `users` WHERE `id` = '$userID'";
$query = mysql_query($sql, $connection);
$result = mysql_fetch_assoc($query);
<table>
<tr>
<td>ID</td>
<td>Username</td>
<td>Password</td>
</tr>
<tr>
<td><?php echo $result['id']; ?></td>
<td><?php echo $result['username']; ?></td>
<td><?php echo $result['password']; ?></td>
</tr>
</table>
<a href="logout.php">Logout</a>
هم نام کاربری و هم رمزعبور در نظر گرفته شده در این آموزش admin است و پس از وارد کردن آنها در فیلدهای مربوطه، با تصویر زیر مواجه خواهیم شد:
میبینیم که توانستیم با موفقیت وارد ناحیهٔ کاربری شویم. پس از کلیک روی لینک Logout، به فایلی تحت عنوان logout.php ارجاع داده میشویم که حاوی اسکریپت زیر است:
session_start();
session_destroy();
define("TITLE", "Logout");
include("includes/header.php");
require("includes/db_config.php");
header("location:./");
اجرای یک حملهٔ فرضی SQL Injection
در ادامه، قصد داریم تا این وب اپلیکیشن را دور بزنیم؛ برای این منظور خواهیم داشت:
همانطور که در تصویر فوق مشخص است، هم برای نام کاربری و هم برای رمزعبور علائم زیر را وارد کردهایم:
' OR ' 1' = ' 1
حال روی دکمهٔ Login کلیک میکنیم:
میبینیم که بدون آگاهی از نام کاربری و رمزعبور، به راحتی توانستیم این وب اپلیکیشن را دور زده و وارد ناحیهٔ کاربری آن هم از نوع ادمین شویم!
اساساً در زبان SQL مقادیر مد نظر داخل علامت های ' ' قرار میگیرند. کدهای فوق بدون قرار دادن مقادیر برای username و password به صورت زیر خواهند بود:
$sql = "SELECT * FROM `users` WHERE `username` = ' ' AND `password` = ' ' ";
حال با وارد کردن این کوئری به عنوان نام کاربری و رمزعبور:
' OR ' 1' = ' 1
دستور فوق به صورت زیر در خواهد آمد:
$sql = "SELECT * FROM `users` WHERE `username` = ' ' OR ' 1' = ' 1' AND `password` = ' ' OR ' 1' = ' 1' ";
همانطور که در کد فوق میبینیم، ابتدا مقدار username و یا password را برابر با ' ' قرار داده (یعنی خالی) سپس از دستور OR به معنی «یا» استفاده نمودهایم. کاری که این دستور اسکیوال انجام میدهد این است که امکانی به ما میدهد تا بتوانیم دو مقدار مختلف برای username یا password در نظر بگیریم. مقدار اول که خالی بود اما پس از دستور OR گفتهایم اگر مقدار عدد 1 برابر با 1 بود (که جواب این شرط همواره TRUE است)، پس این مقدار برای نام کاربری و رمزعبور انتخاب میشود که با این کار به سادگی توانستهایم سیستم را دور زنیم که چنین کاری اصطلاحاً SQL Injection نامیده میشود.
چگونه جلوی حملات SQL Injection را بگیریم؟
برای گرفتن جلوی این نوع حملهٔ سایبری، در زبان پیاچپی متدی وجود دارد با نام ()mysql_real_escape_string که این وظیفه را دارا است تا هرگونه کد اسکیوال که داخل کوئری بود را از بین ببرد:
session_start();
$username_set = false;
$password_set = false;
$username = $_POST['username'];
$password = $_POST['password'];
//Function to prevent SQL Injection
$secure_password = mysql_real_escape_string($password);
if (empty($username)) {
echo "<p class=\"error\">Please Enter your username</p>";
} else {
$username_set = true;
}
if (empty($password)) {
echo "<p class=\"error\">Please Enter your password</p>";
} else {
$password_set = true;
}
if ($username_set == true && $password_set == true) {
$sql = "SELECT * FROM `users` WHERE `username` = '$username' AND `password` = '$secure_password'";
$query = mysql_query($sql, $connection);
$result = mysql_fetch_assoc($query);
$_SESSION['id'] = $result['id'];
$_SESSION["user_name"] = $result["username"];
echo "You are logged in. In a second or two, you`ll be redirected to your profile page";
header("location:logged-in.php");
}
همانطور که در کد فوق میبینیم، متغیر جدیدی ساختهایم تحت عنوان secure_password$ که مقدار آن را برابر با متد ()mysql_real_escape_string قرار داده و پارامتر که به این متد پاس دادهایم همان متغیر password$ است به طوری که از این پس متد ()mysql_real_escape_string کلیهٔ علائم همچون ' و غیره که برای نوشتن دستورات SQL مورد استفاده قرار میگیرند را حذف مینماید که این یکی از گامهای مؤثر برای خنثیسازی حملاتی از این دست است به طوری که اگر یک بار دیگر عبارت مد نظر را وارد فرم خود کنیم، خواهیم دید که قادر به ورود به ناحیهٔ ادمین نخواهیم بود.
آیا کد فوق ایمن است؟
پاسخ کوتاه خیر میباشد زیرا با اسکریپت فوق ما قصد داریم تا پسورد را به صورت اصطلاحاً Plain Text در دیتابیس ذخیره سازیم که چنین سیاستی به راحتی امنیت کاربران را به مخاطر میاندازد چرا که اگر به هر دلیلی هکر به دیتابیس دسترسی پیدا کند، متوجه میشود که هر کاربری از چه علائمی به عنوان رمزعبور خود استفاده کرده است که از آنجا که درصد قابلتوجهی از کاربران در سرویسهای مختلف از نام کاربری (ایمیل) و پسورد یکسان استفاده میکنند، هکر به سادگی خواهد توانست در درصد قابلتوجهی از موارد به حریم خصوصی افراد وارد شود. در همین راستا، باید با الگوریتمهای هَشینگ مثل ()md5 آشنا باشیم تا علائم در نظر گرفته شده توسط کاربر به عنوان پسورد را هَش (رمزگذاری) کنیم.
تقریباً میشود گفت که در همهٔ فریمورکهای مطرح پیاچپی، چنانچه طبق دستورالعملی که در مستندات آنها آمده به دیتابیس مد نظر خود کوئری بزنیم، سولوشنهای مختلفی به مراتب کاملتر از چیزی که در این مقاله ذکر شد به کار میگیرند اما نکتهای که در اینجا حائز اهمیت است اینکه هرگز نباید اعتماد ۱۰۰٪ حتی به فریمورکها داشت و حتماً باید از عملکردها صحیح آنها در مقابله با اسکیوال اینجکشن و همچنین نحوهٔ درست کوئری زدن به دیتابیس اطمینان حاصل کرد (در پایان، چنانچه علاقمند به فراگیری گام به گام زبان برنامهنویسی PHP هستید، میتوانید به دورهٔ آموزش PHP در سکان آکادمی مراجعه نمایید).