امن سازی و اعتبار سنجی ورودی ها

input validation

یک تذکر مهم درباره امنیت فرم PHP

PHP_SELF می تواند توسط هکرها مورد استفاده قرار گیرد.

یک هکر می تواند در آدرس بار مرورگرش بعد از آدرس فایل، یک اسلش (/) قرار دهد و سپس دستورات XSS را برای اجرا تایپ کند.

Cross-site scripting

XSS یک نوع قابلیت آسیب پذیری امنیت کامپوتر است. معمولاً در برنامه های کاربردی web بکار می رود. XSS، هکرها را قادر می سازد تا صفحات وب را از طریق تزریق اسکریبت سمت client هک کنند.

دلیل به وجود آمدن این آسیب پذیری عدم اعتبارسنجی ورودی‌های کاربر می‏ باشد، و مهاجم می تواند با تزریق اسکریپت‌های مخرب در سایت از این آسیب پذیری سو استفاده کند.

فرض کنید، فرم زیر را در یک فایل بنام "test_form.php" داریم:

form method="post" action="<?php echo $_SERVER["PHP_SELF"];?

حالا اگر یک کاربر در آدرس بار مرورگرش "http://www.example.com/test_form.php" را وارد کند، کد بالا بصورت زیر ترجمه خواهد شد:

<form method="post" action="test_form.php">

خوب تا اینجا همه چیز خوب است.

اما درنظر بگیرید که کاربری URL زیر را در آدرس بار وارد کند:

http://www.example.com/test_form.php/%22%3E%3Cscript%3Ealert('hacked')%3C/script%3E

در این صورت کد بالا بصورت زیر ترجمه خواهد شد:

<form method="post" action="test_form.php"/><script>alert('hacked')</script>

دومین URL، باعث اضافه شدن تگ <script> و یک دستور alert در بین کدهای ما شده است. و زمانی که صفحه لود می شود، کد JavaScript اجرا می شود (کاربر یک جعبه پیغام خواهد دید). این فقط یک مثال ساده و بی ضرر است که نحوه هک کردن متغییر PHP_SELF را نشان می دهد.

توجه داشته باشید که هر کد JavaScript دیگری را می توان در تگ <script> قرار داد...! یک هکر می تواند کاربر را به یک فایل دیگر روی سروری دیگر redirect کند، و از طریق آن فایل، اطلاعات کاربر را ذخیره کند.

نحوه مقابله با هک از طریق"PHP_SELF"

با استفاده از تابع ()htmlspecialchars، می توان با هک از طریق "PHP_SELF" مقابله نمود.

کد آن شبیه زیر است:

<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">

تابع ()htmlspecialchars، کاراکترهای خاص را به HTML entity تبدیل می کند. حالا اگر کاربر بخواهد از متغییر "PHP_SELF" سوء استفاده کند، با نتیجه زیر روبرو خواهد شد:

<form method="post" action="test_form.php/&quot;&gt;&lt;script&gt;alert('hacked')&lt;/script&gt;">

و از این طریق هیچ آسیبی وارد نخواهد شد...!

اعتبارسنجی داده های فرم با PHP

اولین کاری که باید انجام دهید این است که تمام متغییرها را به تابع ()htmlspecialchars پاس دهیم.

حالا اگر کاربر تلاش کند که متنی مانند زیر را ارسال کند:

<script>location.href('http://www.hacked.com')</script>

در اینصورت اسکریپت بالا اجرا نخواهد شد، چونکه کاراکترهای خاص در متن بالا به HTML entity معادلاشان تبدیل شده اند:

&lt;script&gt;location.href('http://www.hacked.com')&lt;/script&gt;

حالا این کد برای نمایش در یک صفحه یا داخل یک ایمیل، امن شده است.

همچنین ما دو کار دیگر را هنگام ارسال داده ها به سرور انجام می دهیم:

  1. با استفاده از تابع ()trim کاراکترهای غیرضروری (مثل: فاصله های اضافی، tab و خطوط خالی) را حذف می کنیم.
  2. با استفاده از تابع ()stripslashes، بک اسلش ها (\) را حذف می کنیم

گام بعدی، ایجاد یک تابع، برای انجام تمام کارهای بالاست (بجای اینکه کدهای مربوط به این قسمت را بارها و بارها بنویسیم، مناسب تر است که از یک تابع استفاده کنیم)

ما این تابع را ()test_input می نامیم.

حالا ما می توانیم به ازای هر متغییر POST_$ مقدار آنرا با تابع ()test_input چک کنیم و کد آن شبیه زیر است:

<!DOCTYPE HTML> 
<html>
<head>
<style>
  span{min-width: 200px;float: right;}
</style>
</head>
<body style="direction:rtl;"> 

<?php
$name = $email = $gender = $comment = $website = "";
if ($_SERVER["REQUEST_METHOD"] == "POST")
{
  $name = test_input($_POST["name"]);
  $email = test_input($_POST["email"]);
  $website = test_input($_POST["website"]);
  $comment = test_input($_POST["comment"]);
  $gender = test_input($_POST["gender"]);
}

function test_input($data)
{
  $data = trim($data);
  $data = stripslashes($data);
  $data = htmlspecialchars($data);
  return $data;
}
?>

<h2>مثال اعتبارسنجی فرم ها در PHP</h2>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>"> 
<div><span>نام:</span><input type="text" name="name"></div>
<div><span>ایمیل:</span><input type="text" name="email"></div>
<div><span>وب سایت:</span><input type="text" name="website"></div>
<div><span>توضیحات:</span><textarea name="comment" rows="5" cols="40"></textarea>
</div>
<div><span>جنسیت:</span>
<input type="radio" name="gender" value="female">زن
<input type="radio" name="gender" value="male">مرد
</div>
</div><input type="submit" name="submit" value="ارسال اطلاعات"></div>
</form>

<?php
if (isset($name) || isset($email) || isset($gender) || isset($comment) || isset($website))
 {
  echo "<br /><h2>خروجی کدتان</h2>";
  echo "نام :$name";
  echo "<br />";
  echo "ایمیل: $email";
  echo "<br />";
  echo "وب سایت: $website";
  echo "<br />";
  echo "توضیحات: $comment";
  echo "<br />";
  echo "جنسیت: $gender";
 }
?>

</body>
</html>

خروجی کد بالا:

توجه داشته باشید که در ابتدای اسکریپت، با استفاده از متغییر "REQUEST_METHOD" نحوه ارسال داده های فرم را چک می کنیم. اگر نحوه ی ارسال داده های فرم، از طریق متد "POST" است، اطلاعات فرم پردازش خواهد شد وگرنه با یک صفحه خالی روبرو خواهیم شد.

توجه: برای بار اول که کاربر درخواست مشاهده فایل مثال بالا را به سرور ارسال می کند، بدلیل اینکه متد پیش فرض برای مشاهده صفحات از نوع get است، بنابراین شرط "$_SERVER["REQUEST_METHOD"] == "POST درست نخواهد بود و دستورات داخل شرط اجرا نخواهد شد. اما بعد از اینکه کاربر اطلاعات فرم را پر کرده و روی دکمه ارسال (submit) کلیک کرد، چون ویژگی method فرم را با مقدار "post" تنظیم کرده ایم، شرط ذکر شده درست خواهد بود و دستورات داخل آن اجرا خواهد شد.

اما در مثال بالا، تمام فیلدهای ورودی اختیاری است. حتی اگر کاربر هیچ کدام از فیلدها را پر نکند، اسکریپت بالا باز هم کار خواهد کرد.

تابع کارامد برای بی خطر کردن

function safe_input($data)
{
  $data = trim($data);
  $data = stripslashes($data);
  $data = htmlspecialchars($data);
  return $data;
}
    

استفاده از متد ()filter_var

ساختار کلی:

filter_var(var, filtername, options)

var:نام متغیر

filtername:نام فیلتر مورد استفاده

options:اپشن ها

filter name:

FILTER_VALIDATE_BOOLEAN یک مقدار بولی را اعتبارسنجی می کند.
FILTER_VALIDATE_EMAIL آدرس ایمیل را اعتبارسنجی می کند.
FILTER_VALIDATE_FLOAT یک مقدار اعشاری/float را اعتبارسنجی می کند.
FILTER_VALIDATE_INT مقدار عدد صحیح را اعتبارسنجی می کند.
FILTER_VALIDATE_IP آدرس IP را اعتبارسنجی می کند.
FILTER_VALIDATE_REGEXP یک عبارت باقاعده/Regular expression را اعتبارسنجی می کند.
FILTER_VALIDATE_URL یک آدرس URL را اعتبارسنجی می کند.
FILTER_SANITIZE_EMAIL تمامی کاراکترهای غیرمجاز را از داخل آدرس ایمیل حذف می کند.
FILTER_SANITIZE_ENCODED تمامی کاراکترهای خاص (special character) را حذف کرده یا آن ها را کدگذاری می کند.
FILTER_SANITIZE_MAGIC_QUOTES این ثابت معادل فراخوانی تابع addslashes() می باشد و قبل از کاراکترهای ویژه backslash اضافه می نماید.
FILTER_SANITIZE_NUMBER_FLOAT تمامی کاراکترها را به استثنای اعداد، +- و .،eE از عدد اعشاری حذف می کند.
FILTER_SANITIZE_NUMBER_INT تمامی کاراکترهای غیرمجاز به استثنای اعداد و + - را از عدد صحیح حذف می کند.
FILTER_SANITIZE_SPECIAL_CHARS تمامی کاراکترهای خاص را حذف می کند.
FILTER_SANITIZE_FULL_SPECIAL_CHARS
FILTER_SANITIZE_STRING تمامی تگ ها/کاراکترهای ویژه را از یک رشته حذف می کند.
FILTER_SANITIZE_STRIPPED نام دیگری برای ثابت FILTER_SANITIZE_STRING می باشد و عملکردی مشابه آن را ارائه می دهد.
FILTER_SANITIZE_URL تمامی کاراکترهای غیرمجاز را از آدرس URL حذف می کند.
FILTER_UNSAFE_RAW هیچ کار خاصی انجام نمی دهد ولی این قابلیت را هم دارد که کاراکترهای خاص را رمزگذاری کرده یا آن ها را حذف کند.
FILTER_CALLBACK یک تابع اختصاصی و تعریف شده توسط برنامه نویس را جهت فیلتر و اعتبارسنجی داده ها فراخوانی می کند.^

مثال:

filter_var($email, FILTER_VALIDATE_EMAIL);

این متد در صورت صحیح بودن خود متغیر را برمیگرداند و در صورت غلط بودن مقدار فالس برمیگرداند.

مثال:

function fnValidateNumber($value){
    return filter_var($value, FILTER_VALIDATE_INT);
}

چک میکند که عدد صحیح باشد.