• 1 رای - 5 میانگین
  • 1
  • 2
  • 3
  • 4
  • 5
یک نکته مهم درمورد SQL Injection و mysql_real_escape_string
#1
من خودم چون از یک کلاس و تابعی که خودش این موارد رو هندل میکنه استفاده میکنم، دیگه این مسئله زیاد جلوی چشمم نیست و وقتی افراد از SQL Injection و mysql_real_escape_string صحبت میکنن حضور ذهن ندارم تا این نکته رو بهشون یادآوری کنم. ولی الان به ذهنم اومد و گفتم شاید خیلی مبتدی ها این قضیه رو نمیدونن یا به راحتی ازش غفلت میکنن.

اون نکته چیه؟

نکته اینه که تابع mysql_real_escape_string برای Escape کردن داده های رشته ای که موقع درج در کوئری توی کوتیشن میذاریم کارایی داره. اما درمورد داده های عددی که در کوتیشن نمیذاریم امنیت نمیده.

مثلا:
کد پی‌اچ‌پی:
<?php

mysql_connect
('''root''');

$input='4 or 1=1';

echo 
mysql_real_escape_string($input);

?>

خروجیش:
کد:
4 or 1=1

فرض کنید متغییر input ورودی کاربر هست که حالا از طریق GET یا POST دریافت شده.
اینجا با وارد شدن عبارت 4 or 1=1 با اینکه ما اون رو از mysql_real_escape_string عبور دادیم اما مشاهده میکنید که رشتهء ورودی عملا هیچ تغییری نکرده و بنابراین اگر ما ورودی کاربر رو همینطور در یک کوئری ترکیب کنیم، عبارت وارد شده بعنوان دستورات SQL تفسیر و اجرا میشه.

مثلا کوئری:

کد:
select * from table1 where id=$input

عملا تبدیل میشه به:

کد:
select * from table1 where id=4 or 1=1

که مسلما اون چیزی نیست که ما میخوایم.

خلاصه این نکته رو باید همیشه مد نظر داشته باشید.

البته میتونید بجاش همیشه از یک تابعی مثل این استفاده کنید:

کد پی‌اچ‌پی:
function quote_smart($value) {

    if(
is_numeric($value)) return $value;

    if(
get_magic_quotes_gpc()) $value stripslashes($value);
    return 
"'" .mysql_real_escape_string($value) . "'";



این تابع اول بررسی میکنه که آیا مقدار وارد شده یک عدد است و اگر عدد بود همونطور دست نخورده اون رو برمیگردونه، ولی اگر عدد نبود اون رو بوسیلهء mysql_real_escape_string اسکیپ میکنه. ضمنا دوتا کار مفید دیگه هم انجام میده:
1- اگر magic_quotes_gpc روشن باشه اثر اون رو خنثی میکنه تا رشتهء ورودی Escape مضاعف نشه.
2- بعد از Escape کردن ورودی اون رو داخل کوتیشن هم قرار میده و برمیگردونه؛ یعنی خروجی این تابع برای درج در کوئری آماده است و دیگه لازم نیست و نباید شما اون رو داخل کوتیشن بذارید.

حالا همون مثال رو با این تابع جدید بررسی میکنیم:

کد پی‌اچ‌پی:
<?php

function quote_smart($value) {

    if(
is_numeric($value)) return $value;

    if(
get_magic_quotes_gpc()) $value stripslashes($value);
    return 
"'" .mysql_real_escape_string($value) . "'";

}

mysql_connect('''root''');

$input='4 or 1=1';

echo 
quote_smart($input);

?>

خروجیش:
کد:
'4 or 1=1'

حالا مشاهده میفرمایید که درسته رشتهء ورودی ما تغییری نکرده، اما چون توسط این تابع در کوتیشن قرار گرفته دیگه بعنوان دستور SQL تفسیر نمیشه و نهایتا منجر به یک خطای سینتاکس MySQL میشه و نه چیز دیگه. یعنی خطری برای سایت نداره.

البته بنده نمیگم که این تابع لزوما برای هر کاری مناسبه و روشهای دیگر یا دستی خودتون رو بکار نبرید، ولی این تابع حداقل برای منکه کار رو خیلی راحت کرده تاحالا. ضمنا این تابع رو خودم ننوشتم و از منبع دیگری برداشت کرده بودم. البته برای کار خودم مقداری تغییر درش داده بودم و مثلا چون magic_quotes_gpc رو خودم در جای دیگری برای تمام داده های GPC (به معنای GET, POST و COOKIE) خنثی میکنم اون خط if(get_magic_quotes_gpc()) رو درش حذف کرده بودم.

خلاصه حواستون به داده های عددی که داخل کوتیشن قرار نمیگیرن باشه. mysql_real_escape_string وقتی اثر میکنه که داده ها در کوئری در کوتیشن محصور بشن.

البته شما میتونید داده های عددی رو بصورت جداگانه ولیدیت کنید. یعنی قبل از درج در کوئری چک کنید که دقیقا فرمت یک عدد رو داشته باشن و هیچ چیز دیگری درشون نباشه. همونطور که در این تابع quote_smart مشاهده میکنید میشه با تابع is_numeric این مسئله رو براحتی چک کرد. البته این تابع اعداد اعشاری رو هم قبول میکنه، ولی بهرحال یک عدد اعشاری هم خطر SQL Injection نداره.

ضمنا فراموش نکنید SQL Injection فقط بخشی و یک نوع از ملاحظات امنیتی هر برنامه ای است. خیلی چیزها هست که به منطق و جزییات خود برنامه برمیگرده و برنامه نویس خودش شخصا میتونه تشخیص بده و هندل کنه. مثلا یه وقتی هست باید چک کنید که ورودی وارد شده علاوه بر اینکه یک عدد هست باید در محدودهء خاصی باشه یا از بین چند عدد خاص باشه یا کاربر جاری اجازهء دسترسی رو داشته باشه و غیره. اینا کار خودش شما هست و هیچ تابعی علم غیب نداره و این کارها رو نمیتونه بصورت خودکار برای شما انجام بده.
  پاسخ
#2
آخه ورودی عدد رو که اینجوری اسکیپ نمی کنن Smile

اینجوری هم خوبه :
کد پی‌اچ‌پی:
<?PHP

$input 
'4 or 1=1';
$i = (int)$input;

var_dump($i); 
  پاسخ
تشکر شده توسط : undefined admin vejmad Bojbaj ali786 mohsenkw
#3
امین روش بهتری رو گفت. یه روش کلی هست که کلا نوعت درست باشه. جالبه بدونی که mysql هست که ورودی استرینگ رو به فیلد عددی بدی تبدیلش می کنه. ولی توی بقیه دیتابیس ها منجر به اخطار سطح دیتابیس میشه.
  پاسخ
تشکر شده توسط : vejmad oia
#4
البته اگر از pdo استفاده بشه دردسر ها خیلی کمتره
http://net.tutsplus.com/tutorials/php/wh...se-access/
  پاسخ
تشکر شده توسط : oia vejmad zoghal payam
#5
(۱۳۹۱ خرداد ۰۵, ۰۹:۳۱ ب.ظ)oia نوشته: آخه ورودی عدد رو که اینجوری اسکیپ نمی کنن Smile

اینجوری هم خوبه :
...
آره به چند روش مختلف میشه.
منظور من بیشتر هشدار دادن نسبت به این نکته بود که استفاده از mysql_real_escape_string برای هر نوع داده ای که به هر شکلی در کوئری درج بشه امنیت رو تامین نمیکنه. چون بنظرم خیلی افراد ممکنه این نکته رو ندونن یا ازش براحتی غفلت کنن. چون تازگی یک نفر در فروم برنامه نویس میگفت که از mysql_real_escape_string هم استفاده کرده ولی باز عبارتهای خطرناک ازش عبور میکنن و SQL Inject میشه.

ضمنا من بازم فکر میکنم روش خودم هم بد نیست و شاید بهتر هم باشه Big Grin
چون انعطافش بیشتره و فرضا اگر نوع داده از عددی به غیرعددی تغییر کرد نیازی به تغییرات بیشتر در کد نیست.
به عبارتی روشی که بنده بکار بردم Generic تره.
کلا هرچی درصورت تغییر و گسترش برنامه نیاز به تغییرات دستی کمتری داشته باشه بهتره.
اون تابع quote_smart که گذاشتم چیز خوبیه. چه اشکالی داره؟ برای هر نوع داده ای میشه ازش استفاده کرد.

ضمنا اگر عبارت وارد شده که قرار بود عددی باشه عددی نبود یا چیزهای اضافه داشت بنظرت بهتر نیست منجر به خطا بشه تا کاربر و برنامه نویس متوجه باگ و اشکال احتمالی برنامه بشن تا اینکه هیچ خطایی داده نشه و فرضا اطلاعات و عملیات اشتباهی هم انجام بشه؟



(۱۳۹۱ خرداد ۰۵, ۰۹:۵۱ ب.ظ)admin نوشته: امین روش بهتری رو گفت. یه روش کلی هست که کلا نوعت درست باشه. جالبه بدونی که mysql هست که ورودی استرینگ رو به فیلد عددی بدی تبدیلش می کنه. ولی توی بقیه دیتابیس ها منجر به اخطار سطح دیتابیس میشه.
میتونیم تبدیل نوع رو هم درصورت عددی بودن به تابع quote_smart اضافه کنیم (درصورت true بودن is_numeric).
البته بقول خودت درمورد MySQL که ضرورتی نداره.
  پاسخ
تشکر شده توسط : oia
#6
نقل قول:با تابع is_numeric این مسئله رو براحتی چک کرد. البته این تابع اعداد اعشاری رو هم قبول میکنه، ولی بهرحال یک عدد اعشاری هم خطر SQL Injection نداره.
برای چک کردن ورودی int به جای این تابعی که فرمودین از filter_var استفاده کنین .
کد پی‌اچ‌پی:
function _is_numeric($int)
    {
        if (
filter_var($intFILTER_VALIDATE_INT))
            return 
true;
        return 
false;
    } 

یا حتی یه همچین چیز ساده ای هم میشه .
کد پی‌اچ‌پی:
function _check_id($id='')
    {
    return (
md5(intval($id)) === md5($id))?true false;       
    } 


پ.ن: فقط داشتم از اینجا رد میشدمSleepy
وبلاگ rezaonline.net/blog
سفارش برنامه نویسی reza.biz
Php , mysql , postgresql , redis , Yii and ... Cool
  پاسخ
تشکر شده توسط : masoud1990
#7
البته همه اینها با bigint مشکل داره. Tongue
  پاسخ
تشکر شده توسط : Reza
#8
نقل قول:البته همه اینها با bigint مشکل داره.
تا 10 رقم رو پشتیبانی میکنه دیگه یعنی 9 میلیارد!
فکر کنم همین کافی باشه واسه کارمونBig Grin
وبلاگ rezaonline.net/blog
سفارش برنامه نویسی reza.biz
Php , mysql , postgresql , redis , Yii and ... Cool
  پاسخ
تشکر شده توسط :
#9
خب جایی که نیاز باشه type رو bigint بزاری. اعداد بزرگتر از int به مشکل بر می خوره. البته یه رگولار اکسپرشن ساده برای validate کردن داره
  پاسخ
تشکر شده توسط : Reza
#10
بنظرم یک نکته مهم شد دو نکته مهم!

در صفحهء نمایش اکانتهای سیستم رجیستر و لاگین خودم امکان مرتب کردن بر اساس ستون رو گذاشتم. اسم هر ستون بصورت یک لینک است که وقتی کاربر روش کلیک میکنه اسم ستونی که نتایج باید بر اساس اون مرتب بشن در Query string پاس میشه و همون اسم هست که در کوئری بعد از order by درج میشه.
مثل این:
کد:
admin-accounts.php?per_page=10&page=1&sort_by=username&sort_dir=asc
که نهایتا وارد کوئری میشه:
کد پی‌اچ‌پی:
$sort_by=$_GET['sort_by'];
$query="select * from `accounts` ... order by `$sort_by` ..."
خب ما طبیعتا نباید این پارامتر رو که از سمت کلاینت دریافت میشه بدون ولیدیت/پاکسازی/Escape کردن مستقیما در کوئری درج کنیم، وگرنه میتونه منجر به SQL Injection بشه.
اما نکته اینه که mysql_real_escape_string درمورد identifier ها قابل استفاده نیست. منظور از identifier همون اسم ستون و اینطور چیزهاست (یعنی داده نیست و اسم متغییر و اسم ستون و این حرفهاست).
یعنی این کد به ما امنیت نمیده:
کد پی‌اچ‌پی:
$sort_by=$_GET['sort_by'];
$sort_by=mysql_real_escape_string($sort_by);
$query="select * from `accounts` ... order by `$sort_by` ..."

مثلا ما اسم ستونها رو بین backquote/Backtick میذاریم، و mysql_real_escape_string هم که با Backtick اصلا کاری نداره و اون رو Escape نمیکنه.
پس باید چکار کنیم؟
بنده قبلا در یک منبع امنیتی معتبر یادم هست در مورد استفاده از داده های غیرقابل اعتماد در identifier چیزهایی خونده بودم و توی اون منبع گفته بود که ایمن کردن این مورد خیلی ظریف و مشکله و بهتره کلا طرفش نرید!! حالا من نمیدونم دقیقا دلیلش چیه، چون توضیح زیادی نداده بود، ولی در پروژه خودم سعی کردم به این توصیه عمل کنم؛ بخاطر همین از یک روش امن تری برای ولیدیت کردن اینطور پارامترها استفاده کردم.
اینم اون روشی که بکار بردم:
کد پی‌اچ‌پی:
if(isset($_GET['sort_by']) and 
in_array($_GET['sort_by'], array('uid''auto''username''email''gender''banned')))
  
$sort_by=$_GET['sort_by'];
else 
$sort_by='auto'
در این روش همونطور که ملاحظه میفرمایید، بجای اینکه سعی کنیم کاراکترهای خطرناک داده های ورودی رو Escape یا حذف کنیم، چک میکنیم که دادهء ورودی برابر یکی از اعضای یک گروه محدود از عبارات (در اینجا اسم ستونها) باشه. درواقع این یک روش white list است که امنیت بالایی داره، بخصوص که هیچ کاراکتر و عبارت خاص و خطرناکی بین اعضای لیست ما وجود نداره.

اگر شما هم اطلاعات، تجربه، یا ایده ای در این مورد دارید بگید.

راستی جالب نیست که MySQL تابعی برای امن کردن identifier ها نداره؟
شاید فکر کنید این کار به سادگی اینه که چیزهایی مثل backquote رو در داده های ورودی حذف کنیم، و خودمون میتونیم تابع و روشی برای این کار بسازیم، اما من فکر میکنم ریسک این کارها خیلی زیاد باشه، چون اون منبع معتبری که خوندم (فکر میکنم owasp.org بود) به صراحت خلاف این رو گفته بود و در این مورد واقعا هشدار داده بود؛ بنابراین من عاقلانه دیدم که شخصا امن ترین روشی رو که بلدم و به ذهنم میرسه استفاده کنم. دیگه از اون روشی که گذاشتم امن تر سراغ ندارم. یعنی شما چک میکنید که داده های ورودی یکی از اعضای یک white list باشن و نه چیزی غیر از اون. لیست سفید که خودش یکی از امن ترین روشهاست، و از طرف دیگر چون در اعضای این لیست سفید بخصوص هیچ کاراکتر و عبارت خطرناک و مشکوکی نداریم دیگه میشه گفت که 99% خیالمون میتونه راحت باشه که نکتهء دیگری در کار نیست.
  پاسخ
تشکر شده توسط : admin Reza mr_irani


پرش به انجمن:


کاربران در حال بازدید این موضوع: 1 مهمان