تبليغاتX
UNiComp.iR | Download Direct Tutorials Video , Film | دانلودمستقیم فیلم آموزشی،کتاب،جزوه،مقاله

SQL Injection و مقابله با آن ...


سلام دوستان، 
این مقاله در زمینه SQL Injection (تزریق SQL یا Query) هستش ...

همون طور که می دونید Query یک واسطه بین برنامه و DataBase هست ...
و همچنین می دونید که برای داشتن برنامه های Dynamic باید Query ها را با توجه به ورودی (های) کاربر تغییر بدیم ...

فرض کنید ما یه فرم Login داریم و برای ورود کاربر از قطعه کد زیر استفاده می کنیم:

كد:

$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 رو به این شکل تغییر می دیم:


كد:

$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);


با آرزوی موفقیت و سربلندی برای همه شما عزیزان. Wink

Search Engine Submission - AddMe