امنیت در PHP

بازدید71.2kپست ها5آخرین فعالیت10 سال پیش
0
0

سلام به همه دوستان
مدتی بود که مطلبی ارسال نکرده بودم این شد که تصمیم گرفتم برای دوست داران زبان PHP این مطلب بسیار مهمو بذارم توصیه میکنم کسایی که واقعا میخوان یاد بگیرن فقط به خوندن همین بسنده نکنن.
دوستان اموزشهایی که در پایین قرار میگیره ازسایت iran-eng.com و barnamenevis.org برداشته شدهاما اونجا چند تا پست بود و سوال و جواب وجود داشت و بهم ریخته بود ما اینجا از اون پست ها مطالب مفیدش رو برداشتیم و اینجا متمرکز کردیم تا گیج کننده نباشه.
موفق باشید.

خب یکی از مهمترین مسائل امنیتی در مورد اشتباهات رایج و ساده در کد نویسی هستش
یکی از قسمت های مهم که مورد حمله قرار میگره پایگاه های SQL هست که به SQL Injection معروف هست.
در یک مثال ساده ممکن هست در یک اسکریپ PHP از کاربر نام کاربری و رمزعبور درخواست بشه و سرور نسبت به صحیح بودن این امر DBMS مراجعه کنه نمونه کد:
[SQL]
SELECT * FROM users WHERE name='$username' AND pass='$password';
[/SQL]
اگر فردی به جای رمز عبور عبارت زیر رو وارد کنه :
[SQL]' OR '1'='1 [/SQL]
حاصل Query تبدیل به کد زیر میشه و به پایگاه داده ارسال میشه:
[SQL]SELECT * FROM users WHERE name='known_user' AND pass='' OR '1'='1'; [/SQL]

با استفاده از خاصیت دستور منطقی OR و بدون تصدیق نام کاربری و رمز عبور صلاحیت اعمال میشه
برای حل این مشکل از تابع( ) addslashes استفاده میشه که قبل کوتیشن( ' )ها برش ایجاد میکنه (علامت بک اسلش()میذاره)
پس برای بهینه سازی نام کاربری و رمز عبور از کد زیر استفاده میکنیم:

$username = addslashes($_POST["username"]);  
$password = addslashes($_POST["password"]);

نمونه مثال برای توابع stripslashes و addslashes:

$str = "Is your name O'reilly?";

// Outputs: Is your name O\'reilly?
echo addslashes($str);
$str = "Is your name O\'reilly?";

// Outputs: Is your name O'reilly?
echo stripslashes($str);

اما این به تنظیمات PHP شما هم بستگی داره. با فعال کردن magic_quotes_gpc در PHP.ini میتونید addslashes رو برای تمامی مقدارهای گرفته شده از GET و POST یا کوکی ها به صورت خودکار اعمال کنید.
با استفاده از از تابع () stripslashes همچنین میتونید در صورت نیاز برش ها رو از مقادیر حذف کنید.
در این کد با بررسی مقدار magic_quotes_gpc عمل حذف برش درون مقادیر اعمال میشه

if (get_magic_quotes_gpc()){  
  $_GET = array_map('stripslashes', $_GET);  
  $_POST = array_map('stripslashes', $_POST);  
  $_COOKIE = array_map('stripslashes', $_COOKIE);  
}

در واقع کد فوق این رو میگه که اگر مقدار تابع مورد نظر true باشه ( خروجی تابع فوق زمانی که مقدار get_magic_quotes_gpc در فایل پیکربندی on باشه ، true خواهد بود ) پس بهتر هست که slash های مقدار گرفته شده رو با تابع stripslashes حذف کنیم . چون در ادامه قرار هست با تابع addslashes به مقدار گرفته شده ، slash اضافه کنیم چرا که اگه این کار انجام نشه در واقع ما به مقدارمون 2 بار backslash اضافه کردیم . البته این ایرادی نیست ( البته در این مواقع ) چون ما میخوایم که در هر صورت query خودمون رو نا مفهوم کنیم برای mysql اما مواقعی که مثلا خودمون به عنوان admin از پایگاه خروجی بگیریم و یا صفحه ای داشته باشیم که بخواد خروجی ای از بانک رو به کاربر نشون بده که دارای کاراکتر خاص باشه ، اونوقت هست که به مشکل برمیخوریم. مثال برای مقدار O'reilly

<?php
echo get_magic_quotes_gpc();         // 1
echo $_POST['lastname'];             // O\'reilly
echo addslashes($_POST['lastname']); // O\\\'reilly
 
if (!get_magic_quotes_gpc()) {
    $lastname = addslashes($_POST['lastname']);
} else {
    $lastname = $_POST['lastname'];
}
 
echo $lastname; // O\'reilly
$sql = "INSERT INTO lastnames (lastname) VALUES ('$lastname')";
?>

اگر بخوایم از تابع addslshes استفاده کنیم باید قبلش چک کنیم که get_magic_quotes_gpc فعال هست یا نه که اگر نبود addslash انجام بدیم. اما اگر میخوایم که تحت هر شرایطی از تابع addslashes استفاده کنیم پس بهتره بعد از چک کردن اگر مقدار مقدار on بود ابتدا stripslashes رو صدا بزنیم و بعد addslashesh که این هم توضیح داده شد.

اما چیزی که هست اینکه این تابع Addslashes در کل برای این هست که اگر یک زمانی شما خواستین مقادیر حاصل از فرمی رو یا خروجی حاصل از پایگاهتون رو که کاراکترهای خاص مثل ' یا '' دارن توی صفحه چاپ کنید به مشکل بر نخورید . برای همین از این تابع استفاده میکنیم که php خودش قبل از کاراکترهای خاص یه backslash اضافه کنه تا مشکلی برای پردازش اونها پیش نیاد ( برای php نه mysql )

تابعی که در php برای این کار ایجاد شده و خود manuel php هم سفارش میکنه ، تابع mysql_real_escape_string هست که این تابع هم همون کار فیلتر کردن کاراکترهای خاص رو انجام میده اما قبلش یادتون باشه که چک کنید اگر مقدار get_magic_quotes_gpc ، on بود دیگه دوباره کاری نشه .

اینهم مثالش که با پارامترهای ورودی اون آشا بشین :

<?php
 
if (isset($_POST['product_name']) && isset($_POST['product_description']) && isset($_POST['user_id'])) {
    // Connect
 
    $link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password');
 
    if(!is_resource($link)) {
 
        echo "Failed to connect to the server\n";
        // ... log the error properly
 
    } else {
 
        // Reverse magic_quotes_gpc effects on those vars if ON.
 
        if(get_magic_quotes_gpc()) {
            $product_name        = stripslashes($_POST['product_name']);
            $product_description = stripslashes($_POST['product_description']);
        } else {
            $product_name        = $_POST['product_name'];
            $product_description = $_POST['product_description'];
        }
 
        // Make a safe query
        $query = sprintf("INSERT INTO products (`name`, `description`, `user_id`) VALUES ('%s', '%s', '%d')",
                    mysql_real_escape_string($product_name, $link),
                    mysql_real_escape_string($product_description, $link),
                    $_POST['user_id']);
 
        mysql_query($query, $link);
 
        if (mysql_affected_rows($link) > 0) {
            echo "Product inserted\n";
        }
    }
} else {
    echo "Fill the form properly\n";
}
?>
آخرین ویرایش: 30-01-2013 ???? 16:00، توسط PHP.sec
0

یکی از اصل های مهم امنیت در برنامه نویسی این هست که میگه به داده ای که از خارج دریافت می کنی اعتماد نکن.
نکته دوم خام کردن داده قبل از ارسال هست که در اصطلاح به escape معروف هست
با ترکیب این دو قانون می توان یک اصل امنیت ایجاد کرد که داده ورودی رو فیلتـر و داده خروجی رو escape کنه که به عنوان FIEO شناخته شده است
دلیل اصلی در SQL injection عدم موفقیت در escape کردن داده هاست. در واقع SQL injection زمانی اتفاق می افته که داده خام به عنوان یک پرس و جو تلقی میشه
کد زیر یک ساختمان کلی از ارسال کوئری هست:

<?php
$query = "SELECT *
          FROM   users
          WHERE  name = '{$_GET['name']}'";
?>

ر این حالت مقدار $_GET['name'] نه فیـلتر شده است و نه عمل escape روی داده ارسال شده انجام شده است

همان طور که گفتیم هیچ اعتمادی بر روی داده ارسالی از خارج نداریم از این رو قبل از استفاده از متغییر نا مطمئن داده ها رو نسبت به کلمات حساسی چون ‘ و حروف دیگر بررسی می کنیم سپس برای احتیاط داده فیلـتر شده رو با استفاده از دستور mysql_real_escape_string() نسبت نبود قالب دستوری متغییر اطمینان حاصل می کنیم

دستور mysql_real_escape_string به دو صورت دو پارامتری و یک پارامتری ارسال میشه که در حالات نخست پارامتز اول به عنوان رشته و پارامتر دوم یک connection برای SQL استفاده میشه. در حالت تک پارامتر فقط پارامتر رشته ارسال میشه و connection اخرین ارتباط رو در نظر می گیره و اگر ارتباطی باز نبود با استفاده تابع بدون پارامتر mysql_connect() شروع به ایجاد ارتباط میکنه و اگر ارتباطی ایجاد نشد خطای warning ارسال می شود.
مقدار های بازگشتی در این دستور یا رشته escape شده هست یا false در هنگام خطا
وجود چنین کاراکترهایی باعث بروز خطا میشه چون قالب دستوری دارند:

\x00
\n
\r
\
'
"
\x1a

استفاده از این دو روش در ارسال داده، امنیت در عمق نامیده میشه

<?php
     
    // تعریف تابع به ترتیب برای فیـلتر و اسکیپ داده
    $clean = array();
    $sql = array();
     
    // فلـتر کردن داده (مثلا فقط داده های حروفی)
    if (ctype_alpha($_GET['name'])) {
        $clean['name'] = $_GET['name'];
    } else {
        // دستوراتی برای داده نامعتبر
    }
     
    // اسکیپ داده فیلـتر شده
    $sql['name'] = mysql_real_escape_string($clean['name']);
     
    // ارسال پرس و جو
    $query = "SELECT *
              FROM   users
              WHERE  name = '{$sql['name']}'";
     
?>

مثالی دیگر از کاربرد تابع mysql_real_escape_string:

<?php
// اتصال
$link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password')
    OR die(mysql_error());

// پرس و جو
$query = sprintf("SELECT * FROM users WHERE user='%s' AND password='%s'",
            mysql_real_escape_string($user),
            mysql_real_escape_string($password));
?>
0

روش جلوگیری از SQL Injection خوشبختانه کاملا ساده است.
ما باید مقدارهایی رو که از منابع غیرقابل اعتماد، مثل مقادیر فیلدهای فرمهایی که از سمت کاربران ارسال میشه، دریافت میکنیم، قبل از بکار بردن در کوئری های خودمون توسط تابع mysql_real_escape_string به اصطلاح Escape کنیم (بصورت دقیق، تنها کاراکترهای خاصی رو Escape میکنه).

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

function quote_smart($value)
{
 
if(!is_numeric($value)) {
if(get_magic_quotes_gpc()) $value = stripslashes($value);
return "'" .mysql_real_escape_string($value) . "'";
}
else return $value;
 
}

بجای اینکه تابع mysql_real_escape_string رو سوا بنویسید و بقیهء کد رو هم بریزید توی دل و رودهء برنامه میتونید از این تابع تر و تمیز استفاده کنید که ضمنا رشته های شما رو هم در داخل کوتیشن قرار میده.
اگر دقت کنید اول چک میکنه که مقدار پاس شده عددی هست یا خیر. اگر عدد نباشه یعنی وقتی داخل کوئری بعنوان مقدار یک فیلد درج میشه نیاز به کوتیشن داره و بنابراین این کوتیشن ها رو خودش بصورت خودکار اضافه میکنه و نیازی نیست شما در داخل کوئری یا جای دیگه این کار رو انجام بدید. اگر مقدار پاس شده عدد باشه، یعنی یک رشته مثلا بصورت 1.43، نیاز نه به کوتیشن داره و نه Escape کردن و بنابراین مقدار بدون تغییر برگردونده میشه.

یه مثال از روش استفاده:

$username=quote_smart($_POST['username']);
$password=quote_smart($_POST['password']);
 
$query="select * from `user_accounts` where `username`=$username and `password`=$password";

**نکته:**بخاطر امنیت و از نظر اصولی معمولا پسورد رو مستقیما در دیتابیس ذخیره نمیکنن، بلکه هش پسورد رو بجاش ذخیره میکنن (*) و موقعی که میخوان پسورد وارد شده توسط کاربر رو با دیتابیس چک کنن، از پسورد کاربر هش میگیرن که در اینصورت دیگه نیازی به اعمال quote_smart بر روی این هش نیست (چون در خروجی هش کاراکترهای خطرناک نداریم). اما ما اینجا بعنوان مثال و برای درک مطلب و سادگی فرض کردیم که پسورد مستقیما در دیتابیس ذخیره شده.

*: بعنوان یه مطلب که ربط مستقیمی با موضوع نداره ولی مفیده باید عرض کنم که همیشه از هش تولید شده همراه با salt استفاده کنید، نه هش تنها از پسورد.

0

خب این حمله راههای متنوعی داره. ولی بنده دو تا از متداول ترین اونا رو که میدونم و حضور ذهن دارم میگم و مورد دیگه ای اگر کسی به ذهنش رسید لطفا تذکر بده.

روش اول از طیق ورودیهای کاربر هست. دقیقا مثل SQL Injection در اینجا هم ما از یه تابع مخصوص برای کاری شبیه Escape کردن بعضی کاراکترها استفاده میکنیم. اسم این تابع htmlspecialchars هست. بجز تفاوت در این تابع با SQL Injection، تفاوت دیگر این هست که در SQL Injection ما ورودی کاربر رو موقع درج در دیتابیس Escape میکنیم، اما درمورد جلوگیری از XSS ما موقعی که ورودی کاربر رو در صفحهء وب نمایش میدیم از تابع htmlspecialchars استفاده میکنیم، چون این حمله و تهدیدی نسبت به دیتابیس نیست، بلکه حمله ای به سورس صفحهء HTML خروجی محسوب میشه.

البته چند نکته رو بگم. اول اینکه شما میتونید تابع htmlspecialchars رو موقع درج ورودیهای کاربر در دیتابیس هم اعمال کنید، به این شکل دیگه نیازی ندارید که موقع نمایش این اطلاعات در صفحات سایت تابع htmlspecialchars رو به اونا اعمال کنید، چون اطلاعات از قبل امن سازی شدن، اما بنده شخصا این روش رو ترجیح نمیدم چون اصالت اطلاعات رو حفظ نمیکنه (یعنی معلوم نمیشه شکل اولیهء اطلاعات کاربر دقیقا چی بوده) و ترجیح میدم موقع نمایش اطلاعات در صفحات تابع htmlspecialchars رو اعمال کنم.

دوم اینکه چه درمورد حملهء SQL Injection و چه درمورد حملهء XSS بعضیا ممکنه بجای اعمال این توابع، کلا کاراکترها یا عبارتهایی رو که میتونن مورد سوء استفاده قرار بگیرن از ورودیهای کاربر حذف کنن. این روش هم کار میکنه، اما انعطاف و آزادی برای نرم افزار و کاربران رو پایین میاره و همچنین حفظ اصالت اطلاعات وارد شده توسط کاربر رو به شدت مخدوش میکنه (این میتونه به بعضی مشکلات و اشتباهات برای کاربران هم منجر بشه)، و بنده در این کار ضرورتی هم نمیبینم (*).

ضمنا شما بهرصورت حتی اگر این کاراکترها رو حذف کردید، بازم از نظر اصولیش باید توابع mysql_real_escape_string و htmlspecialchars رو در جای همیشگی استفاده کنید تا مثلا اگر تنظیمات سایت تغییر کرد یا در آینده کاراکتر جدیدی به مجموعه کاراکترهای دارای معنای خاص اضافه شد، برنامه بصورت خودکار امن بمونه و وابسته به یادآوری و ویرایش دستی شما نباشه.

خب روش جلوگیری از حملهء XSS در روش اول (ورودیهای کاربر):

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

این یک نمونهء فرضی و ساده شده از درج اطلاعات وارد شده توسط کاربر در دیتابیس:

$comment=quote_smart($_POST['comment']);
$user=quote_smart($username_or_guest);
  
$query="insert into `comments` (`user`, `comment`) values ($user, $comment)";

توجه کنید که ما تابع quote_smart رو به نام کاربری هم اعمال کردیم، با اینکه در این مثال نام کاربری از طریق فرم کامنت وارد نشده و از نام از قبل ثبت نام شدهء کاربر لاگین شدهء فعلی یا عبارت «مهمان» استفاده میشه. این بخاطر این هست که نام کاربری هم جزو اطلاعاتی هست که منشاء اولیهء اون بهرحال یک منبع غیرقابل اعتماد، یعنی ورودیهای کاربر بوده و ممکنه شامل کاراکترهای معنادار در MySQL باشه. گرچه این اطلاعات موقع درج در دیتابیس Escape شدن، اما این Escape شدگی فقط موقع ترکیب و تفسیر کوئری کاربرد و وجود داره و بعد از اون اطلاعات بصورت خام اولیه در دیتابیس ذخیره میشن و بنابراین باید بیاد داشته باشیم که وقتی ما اونا رو از دیتابیس بیرون میکشیم بصورت Escape نشده هستن و بنابراین وقتی میخوایم اونا رو دوباره در یک کوئری درج کنیم، باید دوباره اونها رو Escape کنیم.

بعد موقعی که کامنت ها رو از دیتابیس بیرون میکشیم و در صفحه نمایش میدیم:

$query="select `user`, `comment`, `date` from `comments` where `article_no`=$article_no";
 
$result=mysql_query($query);
 
if($result) while($row = mysql_fetch_assoc($result)) {
    echo '<center>', htmlspecialchars($row['user'], ENT_QUOTES), ' - ', $row['date'], '</center>';
    echo '';
    echo htmlspecialchars($row['comment'], ENT_QUOTES);
    echo '<hr />';
}

دقت کنید که فلگ ENT_QUOTES رو هم در تابع htmlspecialchars بکار ببرید.
توجه کنید که ما علاوه بر کامنت، نام کاربری رو هم موقع نمایش در صفحه از تابع htmlspecialchars عبور دادیم؛ چون ممکنه نام کاربری هم شامل کاراکترهای خاص HTML باشه. حتی اگر سیستم ولیدیشن شما اجازهء ثبت نام کاربر با این کاراکترهای خاص رو نمیده، بازم باید تابع htmlspecialchars رو برای اصولی بودن برنامه و انعطاف لازم بکار ببرید.

*: کسانی که فکر میکنن باید کاراکترها و عبارتهای خطرناک رو بطور کلی حذف کرد و درج اونها در دیتابیس و اجازه دادن به کاربر در تعیین مشخصات و اطلاعات خودش بصورتی که حاوی این کاراکترها و عبارتها باشن اشتباه هست باید به نرم افزارهای معروفی مثل نرم افزار همین فروم (vBulletin) که توسط برنامه نویسان خبره نوشته شده و بصورت گسترده مورد استفاده و هدف مطلوبی برای نفوذگرها هست توجه کنن.

بطور مثال در اینجا شاید من نتونم از عبارتی مثل <script>alert('Hi!');</script> بعنوان نام کاربری استفاده کنم، اما در بخشهای دیگر هیچ محدودیتی در این زمینه نیست و مثلا این عبارت همین الان در این پست من درج شد و نمایش داده شد یا میتونم این عبارت رو بعنوان امضای خودم در این فروم قرار بدم (همین الان امتحان کردم). همچنین هر کد و کاراکتر دیگری رو میشه در این فروم درج کرد. پس صرف قبول کردن و درج این کاراکترها و عبارتها در دیتابیس و نمایش اونها هیچ خطری نداره و دلیلی برای ایجاد محدودیت در این زمینه وجود نداره.

راه دوم برای حمله های XSS، با استفاده از ضعفی به شرح زیر صورت میگیره.

احتمالا شما تاحالا کدی به این شکل نوشتید:

<form action="<?php echo $_SERVER['PHP_SELF']; ?>">

اگر صفحه ای رو که این کد درش هست، بطور مثال صفحهء test.php در ریشهء سایت، با آدرسی مشابه آدرس زیر باز کنید متوجه مشکلی میشید:

[url]http://localhost/test.php/%22%3E%3Cs...%3Cooo%20a=%22[/url]

یعنی یک کادر پیام جاوااسکریپت اجرا میشه که نشون میده که تزریق یک اسکریپت در صفحه صورت گرفته.

مشکل از استفاده از PHP_SELF ناشی میشه و البته شما هم حق دارید که از این قضیه اطلاع نداشته باشید، چون مورد عجیبی هست که مورد انتظار کسی نیست. بنده هم کدهایی که تاحالا نوشتم این ضعف رو داشتن!

روش جلوگیری:

<form action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES); ?>">

کنید که فلگ ENT_QUOTES رو هم در تابع htmlspecialchars بکار ببرید.

البته اگر از SCRIPT_NAME بجای PHP_SELF استفاده میکنید، خوشبختانه این مشکل با SCRIPT_NAME وجود نداره؛ ولی اینو بدونید که متغییر SCRIPT_NAME ممکنه روی بعضی نرم افزارهای سرویس دهندهء وب (وب سرور) تعریف نشده باشه.

بجز اینا بازم هست و حتی مواردی هست که تابع htmlspecialchars به تنهایی کفایت نمیکنه.
مثلا در همین بحث در یک فروم دیگه یکی از دوستان که آدم واردی هست و تاحالا کلی نرم افزارهای خفن مثل ویبالتین رو هک کرده نظیر چنین موردی رو مطرح کرد:

<html>
<body>
<a href="<?php echo htmlspecialchars($_REQUEST['link'], ENT_QUOTES); ?>">hey! click me!</a>
</body>
</html>

در اینجا ما آدرس لینک رو از ورودی کاربر میگیریم و در صفحه درج میکنیم.
حالا بنظر شما آیا این کد امن هست؟
البته خوشبختانه بنده سربلند بیرون آمدم و با اینکه تاحالا چنین کدی نداشتم و یادم نمیاد چیزی درموردش خونده باشم اما تونستم اشکال این روش و راه حل امن رو بگم. و این جز با درک و بینش کامل و عمیق مطلب و دانش لازم ممکن نیست؛ واسه همین میگم باید کامل و عمیق علت و مکانیزم و کارکرد روش همه چیز رو درک کرد چون بتونید حداقل یکسری موارد خاص درشت و چیزهایی رو هم که تاحالا برخورد نکردید پیشبینی کنید (البته این فقط یکی از دلایلش هست).
اول ببینیم ضعف این روش در چیه.
شما اگر این صفحه رو با آدرسی مثل:
http://localhost/test.php?link=javascript<b></b>:alert%281%29;

(اون کارکترهای <b></b> بین javascript: و alert رو از آدرس حذف کنید. من هرکاری میکنم نرم افزار فروم خودش اضافشون میکنه)

فراخوانی کنید، شاهد تزریق اسکریپت خواهید بود. با وجودی که از تابع htmlspecialchars استفاده کردیم.
البته این مشکل خوشبختانه درمورد PHP_SELF وجود نداره، چون PHP_SELF محتوی آدرس فراخوانی اسکریپت هست که کسی نمیتونه دستکاریش کنه تا با چیزی مثل javascript: شروع بشه.
البته درکنار این تابع و روش، اطمینان حاصل کنید که مشکل رجیسترگلوبالز وجود نداره و رجیسترگلوبالز خاموش هست یا از طریق کدها و روشهای دیگر چک کنید که PHP_SELF دستکاری نشده باشه. بعدا به مورد رجیسترگلوبالز هم خواهیم رسید که روشش خیلی ساده هست. اصلا همینجا یه روش سرراست و کارا رو بهتون میگم! این کد رو در ابتدای هر اسکریپت خودتون بذارید. قبل از هرچیزی دیگری:

if(ini_get('register_globals')) exit("<center><h3>Error: Turn that damned register globals off!</h3></center>");

خب برگردیم به مشکل لینک و javascript:. برای رفع این مشکل باید ورودی کاربر رو چک کنید که فقط با پروتکل های شناخته شده و بی خطری که شما تعیین میکنید شروع شده باشه. مثلا آدرس فقط با http: یا ftp: و امثالهم شروع شده باشه. به این میگن روش لیست سفید که روش سختگیرانه ولی لازم در چنین مواردی هست. در روش لیست سفید تعداد محدودی عبارتهای مجاز رو تعیین میکنیم و هرچیز دیگری بغیر از اونها رد میشه. در روش لیست سیاه (Black list) بعکس تعداد محدودی عبارتهای غیرمجاز رو تعیین میکنیم و هرچیز دیگری بغیر از اونها تایید میشه. لیست سیاه روش آزادتری هست.

اینکه از لیست سفید استفاده میکنیم بخاطر اینه که ممکنه تمام پروتکل ها و URI scheme های خطرناک رو ندونیم یا در آینده URI scheme های جدیدی بوجود بیان که قابل سوء استفاده باشن. ضمنا خوشبختانه این روش مشکل و محدودیت خاصی درمورد چنین چیزهایی مثل درج لینک های کاربران ایجاد نمیکنه چون میشه 99% آدرسهایی رو که کاربران نیاز دارن به این روش تایید کرد. مثلا حتی آدرسهایی مثل لینک فراخوانی یاهومسنجر و غیره رو.

0

CSRF چیست؟
فرض کنید کاربری درحال حاضر در سایت شما لاگین هست.
نفوذگر به روشهای مختلف کاری میکنه که درخواستی از جانب اون کاربر به سایت شما برسه. دقیقا درخواستی که موجب عملیات خاصی میشه. مثلا آدرس مخصوص عملیات لاگ آوت ساده ترین و کم خطرترین مورد هست. ولی اعمال پیچیده تر و خطرناک تری میتونن فراخوانی بشن. مثلا فرض کنید فراخوانی آدرس [url]http://yoursite.com/delete_post.php?post=23611[/url] از طرف کاربر موجب خواهد شد که پست شماره 23611 اون کاربر حذف بشه. ساده ترین و ناشیانه ترین روش برای نفوذگر اینه که این لینک رو تحت عنوان دیگری در اختیار کاربر بذاره، مثلا در یک ایمیل یا در یک صفحهء وب خاص، و کاربر بدون اینکه به آدرس حقیقی لینک دقت بکنه روش کلیک کنه. اما این تنها راه نیست، نفوذگر میتونه کاری کنه که حتی کاربران حرفه ای هم نتونن به اجرای این لینک پی ببرن. مثلا میتونه این لینک رو بعنوان آدرس src یک تگ img در سورس صفحه ای پنهان بکنه:

<img src="http://yoursite.com/delete_post.php?id=23611" />

ضمنا نفوذگر میتونه با دادن طول و عرض صفر و/یا دیگر روشهای دیگری که هست وجود این تگ تصویر رو در صفحه از دید کاربر مخفی بکنه.
وقتی کاربر صفحهء محتوی این تگ img رو بازدید میکنه، مرورگر درخواستی رو برای دریافت تصویر تگ مورد نظر به آدرس [url]http://yoursite.com/delete_post.php?id=23611[/url] ارسال میکنه. همراه این درخواست تمام اطلاعات احراز هویت کاربر هم که به سایت شما تعلق دارن، معمولا کوکی سشن یا لاگین سایت شما، ارسال خواهند شد. بنابراین سایت شما کاربر رو احراز هویت کرده و فرمان صادر شده رو که حذف پست شماره 23611 است انجام خواهد داد.
بجز تگ img نفوذگر میتونست از روشهای دیگری مثل فریم های پنهان در صفحات هم استفاده کنه.
توجه داشته باشید که نفوذگر فقط به لینک های مستقیم و متد GET محدود نیست و میتونه بطور مثال یک درخواست از نوع POST رو هم با اطلاعات مورد نظر خودش از طرف قربانی به سایت شما ارسال کنه. بطور مثال با فرمی که در یک فریم پنهان در صفحه ای قرار داره و بصورت خودکار توسط جاوااسکریپت سابمیت میشه.

خب راه حل چیست؟

روشهای مختلفی برای جلوگیری از این حمله اختراع شدن. اما ما یکی از ساده ترین و متداول ترین این روشها رو که بقدر کافی هم امن هست معرفی میکنیم.
این روش بر این مبتنی هست که هر زمان که صفحه ای رو برای کاربر تولید میکنید که شامل لینک یا فرمهایی هست که عملیات مهمی رو انجام میدن که باید در برابر CSRF ازشون محافظت بشه، یک رشتهء یکتا رو هم به ازای هر کاربر/بازدید تولید میکنید و یک نسخه از این رشته رو در سشن یا مکان دیگری که سمت سرور بتونید بهش دسترسی داشته باشید و نفوذگر بهش دسترسی نداشته باشه ذخیره میکنید و یک کپی دیگر از اون رو به لینک ها و فرمهای مورد حفاظت اضافه میکنید. بعد موقعی که درخواستی از طرف کاربر برای لینکها یا فرمهای ذکر شده به سایت شما ارسال شد، چک میکنید که رشتهء یکتا در اون آدرسها و فرمهای POST شده وجود داشته باشه و اون رشته برابر با رشته ای باشه که شما در مثلا سشن اون کاربر ذخیره کردید.
نفوذگر چون اطلاعی از این رشتهء یکتای مربوط به سشن هرکاربر نداره، نمیتونه یک لینک یا فرم جعلی رو با رشتهء یکتای صحیح ایجاد بکنه.

یک مثال کامل از بکارگیری این روش:

<?php
 
if(ini_get('register_globals'))
exit('register_globals is on! turn it off.');
 
//> anti-cache headers
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0");
header('Pragma: private');
header("Pragma: no-cache");
//< anti-cache headers
 
function random_string($length) {
 
$chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop  qrstuvwxyz0123456789';
$random_string='';
for($i=0; $i<$length; $i++) $random_string.=$chars[mt_rand(0, strlen($chars)-1)];
 
return $random_string;
}
 
session_start();
 
if(isset($_GET['action']))
if(
!isset($_GET['csrf_token']) or
!isset($_SESSION['csrf_token']) or
$_GET['csrf_token']!=$_SESSION['csrf_token']
)
exit('Request rejected!');
else
exit('Request accepted.');
 
if(isset($_POST['command']))
if(
!isset($_POST['csrf_token']) or
!isset($_SESSION['csrf_token']) or
$_POST['csrf_token']!=$_SESSION['csrf_token']
)
exit('Command rejected!');
else
exit('Command accepted.');
 
$csrf_token=$_SESSION['csrf_token']=random_string(32);
$self_address=htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES);
 
echo '<center>';
 
echo "<a href=\"$self_address?action=delete_record&csrf_tok  en=$csrf_token\">", 'Assume that this link performs an important action.</a>';
 
echo 'And an important form:',
"<form action=\"$self_address\" method=\"post\">",
"<input name=\"csrf_token\" type=\"hidden\" value=\"$csrf_token\" />",
'<input name="command" type="text" value="perform that dangerous action." size="40" />',
'<input type="submit" value="Execute" onclick="return confirm(\'Are you sure?\')" />',
'</form>';
 
echo '</center>';
 
?>

این مثال شامل یک لینک و یک فرم محافظت شده در برابر CSRF است.
موقعی که شما در صفحهء خود مثال روی لینک کلیک کنید یا فرم رو سابمیت کنید پیام قبول درخواست رو میده، اما بطور مثال آدرس لینک رو کپی کرده و در آدرسبار مرورگر Paste کنید و مقدار csrf_token رو تغییر بدید یا پارامتر csrf_token رو بطور کلی حذف کنید و بعد لینک رو اجرا کنید، پیامی مبنی بر عدم قبول درخواست نمایش داده میشه.
امیدوارم دیگه نیازی به توضیح خط به خط کد نباشه. اگر کسی چیزی رو متوجه نشد بگه تا توضیح بدم.
ضمنا این فقط یه مثال برای نشون دادن کلیت روش بود.


بحث امنیت بسیار گسترده و پیچیده هست و نیاز داره که وقت زیادی روش بذارید در انتها ما دو نکته رو تذکر میدیم:

مورد دیگه ای که باید توی کدهای php بهش توجه بشه این هست که error ها و پیغام های خطای کد شما مخصوصن زمانی که با DB ارتباط برقرار میکنه به کاربر نمایش داده نشه .چون به این ترتیب اطلاعاتی درباره web server و همین طور جداول db به همراه نام فیلدها و نوع دسترسی به اونها و .... نمایش داده میشه که اگه کاربر قصد شیطونی داشته باشه ! اونوقت میتونه مشکلی رو فراهم کنه .
برای اینکه پیغام های خطا نمایش داده نشه میتونید از طریق فایل htaccess. نمایش خطارو از کار بندازید که اکثر هاست ها این کار رو بطور پیش فرض انجام میدن اما خوب در مواردی آگاهی از پیغام های خطا برای admin میتونه راه گشا باشه برای همین میتونید پیغام خطا در فایل htaccess فعال کنید و به جای اون یا قبل ازشروع توابع علامت @ قرار بدین و یا اینکه ورود تابع ()error_reporting رو با مقدار 0 مقداردهی کنید .

انواع حملاتی که در وب انجام میگیره رو شما میتونید از این منبع پیدا کنید.

تا درود بعدی بدرود :smilingsmiley:

جمع اوری:[url]www.softafzar.net[/url]

0

آموزش خوبیه
در مواقعی میخواییم ID رو بگیریم میتونیم به این روش عمل کنیم:

$postid=(int)mysql_real_escape_string($_GET['postID']);

سوال برنامه نویسی دارید؟

ندونستن عیب نیست، نپرسیدن چرا!

خوش آمدید

برای طرح سوال، ایجاد بحث و فعالیت در سایت نیاز است ابتدا وارد حساب کاربری خود شوید. در صورتی که هنوز عضو سایت نیستید میتوانید در عرض تنها چند ثانیه ثبت نام کنید.