سلام دوستان،
همون طور که قول داده بودم مي خوام در زمينه SQL Injection (تزريق SQL يا Query) صحبت کنم...
همون طور که مي دونيد Query يک واسطه بين برنامه و DataBase هست ...
و همچنين مي دونيد که براي داشتن برنامه هاي Dynamic بايد Query ها را با توجه به ورودي (هاي) کاربر تغيير بديم ...
فرض کنيد ما يه فرم Login داريم و براي ورود کاربر از قطعه کد زير استفاده مي کنيم:
کد:
<?php
$Username = $_POST['Username'];
$Password = $_POST['Password'];
$SQL = "SELECT * FROM `users` WHERE `u_name` = '$Username' AND `u_pass` = '$Password'";
$Rslt = mysql_query( $SQL);
if( mysql_num_rows( $Rslt) > 0)
{
print('welcome to your profile...');
} else {
die('The specified username and/or password is invalid!');
}
?>
خوب مشاهده مي کنيد که به ظاهر همه چيز مرتبه و کاربر با وارد کردن user و pass خودش مي تونه Login کنه و اگه اشتباه بود نمي تونه...
خوب يه کاربر شيطون اول مي آد يه username که Single Quotation توش هست مثل jo'hn رو امتحان مي کنه تا ببينه که ميتونه از اين روش استفاده ببره يا نه ... خوب بعد از فهميدن ورودي ها رو به اين شکل وارد مي کنه:
کد:
username: a
password: ' or '1'='1
خوب ببينيم SQL ما به چه شکل در مي آد:
کد:
SELECT * FROM `users` WHERE `u_name` = 'a' AND `u_pass` = '' or '1'='1'
خوب نتيجه Query رو خودتون مي دونيد ديگه اگه حداقل يک کاربر عضو سايت باشه به راحتي مي تونيم Login کنيم.
حالا به فرض اين که هکر username رو مي دونه
کد:
username: admin'--
password: 123
خوب SQL:
کد:
SELECT * FROM `users` WHERE `u_name` = 'admin'--' AND `u_pass` = '123'
همون طور که مي دونيد در MySQL -- علامت Comment هست
البته علامت هاي ديگري مثل /* ... */ و # هم در Mysql هستند...
يعني در واقع Query ما به اين شکل در مي آد:
کد:
SELECT * FROM `users` WHERE `u_name` = 'admin'
خوب اين هم که بديهيه و Login با موفقيت انجام مي شه.
البته همه چيز به يک Login ختم نمي شه بلکه هکر مي تونه کل اطلاعات مربوط به ساختار و داده هاي DataBase ما رو در بياره ... به اين شکل:
البته به اين شکلي که نوشته شده MySQL خطا نمي ده ولي SQL server و ... خطا مي دن
کد:
username: ' having 1=1--
password: 123
کد SQL :
کد:
SELECT * FROM `users` WHERE `u_name` = '' having 1=1--' AND `u_pass` = '123'
پيغام خطايي که SQL server ميده اينه:
کد:
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'user.u_name' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.
که از اون مي شه اسم Table و اولين فيلد جدول رو به دست آورد...
همين طور که ميبينيد هکر از Error ها خيلي بهره مي بره...
به همين ترتيب مي شه کار هاي ديگه اي هم کرد مثلا:
کد:
username: '; DROP TABLE `users` --
password: 123
SELECT * FROM `users` WHERE `u_name` = ''; DROP TABLE `users` -- AND `u_pass` = '123'
خوب مي بينيد که اين اصلا خوب نيست...
استفاده از UNION:
کد:
username: ' UNION select max(`u_name`) from `users` --
password: 123
SELECT * FROM `users` WHERE `u_name` = '' UNION select max(`u_name`) from `users` -- AND `u_pass` = '123'
خطايي که سرور به ما ميده اينه :
کد:
#1222 - The used SELECT statements have a different number of columns
خوب ما تعداد فيلد هاي SQL برنامه رو نمي دونيم به همين دليل کار خودمون رو با افزودن يک فيلد ديگر در UNION ادامه ميديم:
کد:
username: ' UNION select max(`u_name`),0 from `users` --
password: 123
SELECT * FROM `users` WHERE `u_name` = '' UNION select max(`u_name`),0 from `users` -- AND `u_pass` = '123'
#1222 - The used SELECT statements have a different number of columns
باز هم همون خطاي قبلي رو ميده ... اين کار رو اين قدر ادامه ميديم تا تعداد فيلد ها رو به دست بياريم.
همچنين با استفاده از اين باگ ميتونيم اعمالي چون Insert, Delete, ... رو هم انجام بديم...
در ضمن فقط فرم Login که نيست هر جايي که با ديتابيس کار کنه ميتونه مورد حمله واقع بشه اما Login حساسيت بيشتري داره...
* * *
خوب حالا چاره چيه ...
اول بريم سراغ Login، کد مربوط به Login رو به اين شکل تغيير مي ديم:
کد:
<?php
$Username = $_POST['Username'];
$Password = md5( $_POST['Password']);
$Username = strtolower( $Username);
$Username = preg_replace("([^a-z0-9_]*)", '', $Username);
$SQL = "SELECT `u_pass` FROM `users` WHERE `u_name` = '$Username' LIMIT 0,1";
$Rslt = mysql_query( $SQL);
if( mysql_num_rows( $Rslt) == 1 && $Password == mysql_result( $Rslt , 0))
{
print('welcome to your profile...');
} else {
die('The specified username and/or password is invalid!');
}
?>
اولا که Password رو حتما Hash کنيد که الگوريتم هاي مختلفي در اين زمينه وجود داره ... البته باز هم محدود به رد کردن يک يا چند بار از md5 نشيد بلکه با چيزهاي ديگري مثل تاريخ عضويت يا کد منحصر به فردي مخلوطش کنيد تا حسابي قرو قاطي بشه ... مثلا اين طوري:
کد:
$HashedPass = md5( strrev(md5( $Password) . $RegisterDate));
خلاصه يه روال من در آوردي ... که حتي تو هر پروژه اي که مي نويسيد هم عوض بشه.
نکته بعد که خيلي مهمه اينه که نام کاربري فقط همونايي باشه که مي خواييم و ديگه کاراکتر هاي اضافي توش نباشه:
کد:
$Username = preg_replace("([^a-z0-9_]*)", '', $Username);
که البته موقع ثبت نام کاربر هم Username رو از اين فيلتر رد ميکنيم و بهش ميگيم که از چه چيز هايي مي تونه استفاده کنه.
نکته بعد طرض نوشتن SQL مون هست:
کد:
SELECT `u_pass` FROM `users` WHERE `u_name` = '$Username' LIMIT 0,1
همون طور که مي بينيد فقط Password کاربر از ديتابيس بيرون کشيده شده (البته توي برنامه هاي واقعي چيزاي ديگه اي هم هست که مي خونيم) ... بعد هم به شرط SQL دقت کنيد که فقط username رو مورد بررسي قرار مي ده همچنين به LIMIT 0,1 هم دقت کنيد که فقط يه رکورد رو بيرون مي کشيم.
نکته خيلي مهم اينجاست که Password رو خود ما چک مي کنيم و اونو به دست SQL نمي سپاريم :
کد:
if( mysql_num_rows( $Rslt) == 1 && $Password == mysql_result( $Rslt , 0))
يعني اگه به هر شکل هکر بتواند SQL رو هم دور بزنه، اين جا رو نمي تونه دور بزنه...
---
نکات ديگه اي هم هست که براي جاهاي ديگه استفاده مي شه و اون اينه که کاراکتر هاي اضافي رو از طريق استفاده از تابع زير از ورودي هامون حذف کنيم:
کد:
function EscapeString( $str , $Conn = 0)
{
if(function_exists('mysql_real_escape_string') && $Conn) {
return mysql_real_escape_string( $str, $Conn );
} else {
return mysql_escape_string( $str );
}
}
نکته بسيار مهم ديگه اينه که براي ديتابيسمون دوتا user بسازيم، يکي فقط مي تونه SELECT رو انجام بده و ديگري فقط SELECT, UPDATE, INSERT, LOCK TABLE رو بتونه انجام بده، و موقعي که مي خواهيم Query فقط خوندن رو اجرا کنيم با user اولي به ديتابيس Connect مي شيم، که اگه هکر بتونه چيزي رو هم تزريق کنه که توي اطلاعات ما تغيير ايجاد کنه، خود DBMS اجازه همچين کاري رو نده ... در ضمن به user هاي ديتابيس دسترسي هاي خطر ناکي چون DROP TABLE, ALTER , ... رو نديم.
يک نکته مهم ديگه اينه که مثلا در فيلد هايي که int هستند و بر اساس اين فيلد جستجو انجام ميدين ورودي GET يا POST رو چک کنيد که حتما همون چيزي باشه که بايد باشه مثلا اگه قراره int باشه اون رو از تابع intval رد کنيد مثال:
کد:
$NewsID = intval( $_GET[ 'ID' ]);
$SQL = "SELECT * FROM `news` WHERE `ID` = $NewsID LIMIT 0,1";
و نکته بسيار مهم آخر اينه که نگذاريد کسي خطاهاي برنامه شما را ببيند (حتي اطلاعات سرور و زبان برنامه نويسي رو هم مخفي کنيد خيلي خوبه)، چون همين خطاها هستند که به هکر ها اطلاعات ميدن ... براي اين کار بعد از اين که کل پروژه ساخته شد و خواستيد اون رو publish کنيد بالاي يکي از فايل هايي که توي همه فايل هاي برنامه include مي شه تابع زير رو به اين شکل صدا بزنيد:
کد:
error_reporting( 0);
با آرزوي موفقيت و سربلندي براي همه شما عزيزان.
