به عنوان یک قانون کلی، هر دادهای که برای سرور ارسال میگردد را باید مشکوک قلمداد کرد و از همین روی نیاز است تا کلیهٔ دادههایی که در قالب فرم، فیدهای مخفی، پارامترهای یوآرال و هر نوع دیتای ارسالی برای سرور (همچون ایجکس) قرار دارند را چک کرد. این کار اصطلاحاً Input Validation نامیده میشود و هدف از انجامش اطمینان حاصل کردن از این موضوع است که دیتای ناخواسته یا دیتایی که توسط هکرها با مقاصد خاصی درج میشوند به سمت سرور ارسال نگردد. در این ارتباط، باید کاراکترهای مجاز، طول رشتهٔ مجاز برای ثبت در دیتابیس، دیتا تایپ صحیح و ... در سمت سرور شود (در این پروسه، برخی دولوپرها در سمت کاربر و با استفاده از جاوااسکریپت اقدام به اعتبارسنجی دادهها میکنند که در این رابطه باید گفت حتماً در سمت سرور نیز باید دادهها مجدد چک شوند چرا که هکرها به سادگی میتوانند سازوکارهای موجود در سمت فرانتاِند را دور بزنند.)
اعتبارسنجی دادههای فرم
در ارتباط با فرمهای اچتیامال میتوان گفت که تقریباً تمامی فریمورکها دارای فانکشنهایی جهت اعتبارسنجی دادههای ارسالی از طریق فرمها هستند که این اطمینان را حاصل میکنند دقیقاً همان دیتایی که مد نظر دولوپر است به سمت سرور ارسال گردد و در غیر این صورت فرم نامعتبر قلمداد شده و اجازهٔ ارسال دادهها داده نخواهد شد. وقتی پای اعتبارسنجی دادهها به میان میآید، از چند بُعد مختلف باید دادهها را مورد بررسی قرار داد که برخی از مهمترین آنها عبارتند از:
- چک شود که فیلد خالی نباشد (البته فقط در صورتی که فیلد اجباری نیست.)
- حداقل و حداکثر طول کاراکتر مجاز دقیقاً مشخص گردد.
- دیتا اصطلاحاً Trim شود؛ به عبارتی، اِسپیسهای ابتدایی و انتهایی دیتا حذف گردد.
- کاراکترها و تگهای مجاز از طریق یک اصطلاحاً White List مشخص گردد.
- دیتا تایپ داده با دیتا تایپ موجود در فیلدهای دیتابیس یکسان باشد.
به طور کلی، منظور از White List لیستی از آیتمها است که دیتا بر آن اساس سنجیده میشود که چنانچه مثلاً تگی داخل لیست در نظر گرفته شده بود، کاربر میتواند آن را استفاده کند و در غیر این صورت حذف خواهد شد. اساساً توصیه میشود که به جای استفاده از Black List یا به عبارتی لیستی از آیتمهایی که مجاز نیستند بهتر است که از وایت لیست استفاده شود با این توضیح که نقطهٔ ضعف بِلک لیست آن است که احتمال دارد موردی که نباید معتبر شناخته شود را از قلم بیندازیم و همین مسئله وب اپلیکشنمان را آسیبپذیر سازد. برای درک بهتر این موضوع، بِلک لیست زیر را در زبان پیاچپی در نظر میگیریم:
$blackListTags = ['pre', 'code', 'a', 'blockquote'];
برای درک بهتر این موضوع، مثال فرضی فوق را در نظر میگیریم بدین صورت که چنانچه دیتای مد نظر از کانال لیست تگهای فوقالذکر بگذرد و حاوی یکی از اِلِمانهای این آرایه باشد، تگ/تگهای مذکور در سمت سرور از داخل دیتا حذف میشوند اما نقطهٔ ضعفی که این بِلک لیست دارد از اینجا ناشی میشود که دولوپر فراموش کرده مثلاً تگی همچون script
را به آن بیفزاید که یکی از تگهای مهم برای اجرای حملات XSS است! در عین حال اگر وی از وایت لیستی به صورت زیر استفاده کرده بود احتمال خطا به مراتب کمتر میشد:
$whiteListTags = [ 'p', 'span', 'strong', 'br', 'em'];
در آرایهٔ فوق فقط و فقط تگهای مجاز لیست شدهاند و از همین روی هر چیزی به غیر از آنها نامعتبر قلمداد خواهد شد.
ایمنسازی پارامترهای موجود در یوآرال
در ارتباط با پارامترهایی (کوئریهایی) که از طریق یوآرال به سمت سرور ارسال میشوند نیز باید اعتبارسنجی صورت گیرد. برای روشنتر شدن این موضوع، مثال زیر را مد نظر قرار میدهیم:
https://example.com?search=php+tutorials
حال با استفاده از اسکریپت زیر اقدام به چاپ کوئری فوق میکنیم:
<?php
echo $_GET['search'];
که به عنوان خروجی خواهیم داشت:
php tutorials
اکنون کوئری زیر را مد نظر قرار میدهیم:
https://example.com?search=<script>alert('not safe');</script>
حال پس از رِفرش کردن صفحه میبینیم که یک آلِرت جاوااسکریپتی در مرورگر حاوی پیام «not safe» در معرض دیدمان قرار میگیرد که این نشان از آسیبپذیر بودن سایت دارد با این توضیح که چنین وب اپلیکیشنی نسبت به حملات ایکساساس، که در ادامه پیرامونش بیشتر توضیح خواهیم داد، اصلاً ایمن نیست.
لازم به یادآوری است که مرورگر گوگل کروم به صورت پیشفرض این نوع کوئریها را مسدود میسازد و پیامی همچون ERR_BLOCKED_BY_XSS_AUDITOR در معرض دیدمان قرار میگیرد اما برای مقاصد آموزشی این پُست با افزودن کد ;('header('X-XSS-Protection:0
در ابتدای فایل میتوان این مورد را موقتاً رد نمود به طوری که خواهیم داشت:
<?php
header('X-XSS-Protection:0');
echo $_GET['search'];
برای رفع این نوع آسیبپذیری راهکارهای متفاوتی وجود دارد که یکی از آنها استفاده از فانکشنی در زبان پیاچپی تحت عنوان ()htmlspecialchars
است به طوری که کد فوق پس از ریفکتور شدن به صورت زیر خواهد بود:
<?php
echo htmlspecialchars($_GET['search']);
از این پس به عنوان خروجی نیز خواهیم داشت:
<script>alert('not safe');</script>
و این در حالی است که اگر به سورس صفحه نگاه کنیم میبینیم که کوئری مد نظر به صورت زیر درآمده است:
<script>alert('not safe');</script>gt;
اگر بخواهیم کد فوق را کمی بهبود بخشیم هم خواهیم داشت:
<?php
echo htmlspecialchars($_GET['search'], ENT_QUOTES, 'UTF-8');
در واقع، به عنوان پارامتر دوم این فانکشن از فِلگی تحت عنوان ENT_QUOTES
استفاده نمودهایم که این تضمین را ایجاد میکند که هم علامت '
و هم علامت "
اِنکود شوند مضاف بر اینکه درج پارامتر سوم نیز نوع انکودینگ را مشخص میکند که UTF-8 هم این تضمین را ایجاد میسازد که اکثر کاراکترها، که کاراکترهای زبان فارسی هم جزو آنها است، به خوبی ساپورت شوند. حال به عنوان خروجی خواهیم داشت:
<script>alert('not safe');</script>gt;
میبینیم که بر خلاف خروجی قبل، این دفعه کاراکترهای '
نیز اِنکود شدهاند. در حقیقت، کاری که فانکشن ()htmlspecialchars
که جزو فانکشنهای اصطلاحاً Built-in در زبان پیاچپی میباشد انجام میدهد آن است که یکسری کاراکترهای خاص همچون علائم >
یا <
را به HTML Entity متناظر آنها تبدیل میکند که به ترتیب عبارتند از ;lt&
و ;gt&
با این توضیح که کاراکترهای خاصی در زبان اچتیامال (همچون موارد فوقالذکر) به معادلهای اِنکودشدهٔ آنها مبدل میگردند به طوری که دیگر همچون اچتیامال استاندارد توسط مرورگر پردازش نخواهند شد. به عنوان یک قانون کلی، سعی کنید به جای علائمِ سمت چپ از معادلهای انکودشدهٔ آنها (سمت راست) استفاده نمایید:
& --> &
< --> <
> --> >
" --> "
' --> '
/ --> /
آشنایی با مفهوم XSS
آسیبپذیری Cross-Site Scripting که تحت عنوان XSS نیز شناخته میشود باعث میگردد کدهایی همچون اسکریپتهای جاوااسکریپتی در راستای منافع خاص هکرها روی وبسایت شما اجرا گردند. به طور مثال، وقتی جاوااسکریپت بتواند به document.cookie
دسترسی یابد این بدان معنا است که کاربرانی با نیات سوء خواهند توانست به کوکی اطلاعات سِشِن کاربران فعال دست یابند مضاف بر اینکه ایکساساس باعث میگردد تا هکر بتواند از طریق دامین شما ریکوئستهایی از جنس اچتیتیپی ارسال کرده و ریسپانسهای مربوطه را نیز مشاهده کند و یا کاربر را به سایتهای مد نظر خود ریدایرکت کند.
یکی از راههای مقاله با ایکساساس این است که کیوردهای javascript
و script
را از دادههای ورودی حذف کرد که به منظور پی بردن به اهمیت این موضوع، ابتدا کنسول مرورگری همچون گوگل کروم را باز کرده سپس در تِب Console یک کوکی به صورت زیر ایجاد میکنیم:
document.cookie = "username=b_moradi";
در واقع، پس از وارد کردن دستور فوق اینتر میکنیم که به محض این کار یک کوکی با نام username
ایجاد خواهد شد. همانطور که مشاهده میشود، با استفاده از دستور document.cookie
میتوانیم یک کوکی با هر مقداری که مد نظر داشته باشیم ایجاد کنیم که در مثال فوق کلید username
است که مقداری همچون استرینگ «b_moradi» برایش در نظر گرفته شده است. حال اسکریپتی به صورت زیر ایجاد میکنیم:
<?php
$input = "<script>console.log(document.cookie)</script>";
echo $input;
اگر صفحه را در حالی که پنجرهٔ Console باز باشد رِفرش کنیم، خواهیم دید که خروجی زیر در معرض دیدمان قرار خواهد گرفت:
username=b_moradi
نیاز به توضیح نیست که اسکریپت قرارگرفته مابین تگهای <script></script>
میتواند هر کد مخربی باشد که به منظور گرفتن جلوی این نوع آسیبپذیری، به سادگی میتوان کدهای پیاچپی فوق را به صورت زیر ریفکتور کرد:
<?php
$input = "<script>console.log(document.cookie)</script>";
echo htmlspecialchars($input);
حال اگر مجدد صفحه را رِفرش کنیم خواهیم داشت:
<script>console.log(document.cookie)</script>
در واقع، با این ترفند تا حد قابلتوجهی میتوان جلوی حملات ایکساساس را با استفاده از زبان پیاچپی گرفت و این در حالی است که زبانهای مختلف سازوکارهای اختصاصی خود را برای مقابله با این نوع آسیبپذیری را دارند به طوری که مثلاً در زبان پایتون فانکشن ()cgi.escape
برای این منظور تعبیه شده است.
خیلی اوقات هکر کدهای مخرب خود را داخل فیلدهای فرم وارد میسازد و چنانچه سازوکاری برای جلوگیری از حملات ایکساساس اندیشیده نشده باشد، وی قادر خواهد بود روی سایتی که کاربر به آن اعتماد کرده اهداف معمولاً غیرقانونی خود را عملی سازد. در یک دستهبندی کلی، حملات ایکساساس را میتوان به سه دستهٔ کلی تقسیم کرد که عبارتند از:
- Stored XSS: این نوع حملات زمانی رخ میدهند که هکر توانسته باشد با موفقیت کد مخربی را داخل دیتابیس ثبت کند به طوری که هر موقع کاربران آن ریسورس (مثلاً اطلاعات یک صفحهٔ وب) را از دیتابیس فراخوانی کنند، کدها اجرا شده و هکر به هدف خود دست خواهد یافت.
- Reflected XSS: این دست حملات زمانی رخ میدهند که از سمت وب اپلیکیشن دیتایی برای مرورگر کاربر ارسال میگردد به طوری که مرورگر دیتای مذکور را به عنوان کد اجرا میکند.
- DOM-based XSS: اگر هکر بتواند به DOM دست یابد و مثلاً یک لینک مخرب درست کند و کاربران نیز روی آن لینک کلیک کنند، به سادگی قادر خواهد بود تا به اطلاعاتی همچون کوکی، سِشِن و ... دست یابد.
معرفی هِدِرهایی که جلوی آسیبپذیری XSS را میگیرند
پیش از این با هِدِری تحت عنوان X-XSS-Protection
آشنا شدیم که میتواند در ریسپانسهایی که از سمت سرور برای مرورگر کاربران ارسال میگردند قرار گیرد (در مرورگرهای جدیدی همچون آخرین نسخههای گوگل کروم چنین قابلیتی به صورت پیشفرض فعال است اما محض اطمینان دولوپرها میتوانند در سمت کد نیز این قابلیت را اِعمال نمایند.)
به زبان ساده، کاری که این هِدِر انجام میدهد این است که جلوی آسیبپذیری ایکساساس را میگیرد به طوری که در زبان پیاچپی میتوان به صورت زیر آن را مورد استفاده قرار داد:
<?php
header('X-XSS-Protection: 1; mode = block');
عدد ۱ به عنوان مقدار این هِدِر هرگونه آسیبپذیری ایکساساس را در سمت مرورگر غیرفعال میکند و mode = block
نیز مرورگر را مجبور میسازد تا از رِندِر کردن صفحه جلوگیری به عمل آورد (نیاز به توضیح نیست که تابع ()header
در زبان پیاچپی به منظور سِت کردن هِدِرها مورد استفاده قرار میگیرد.)
هِدِر دیگری که در این ارتباط میتوانیم مورد استفاده قرار دهیم Content-Security-Policy
نام دارد که این امکان را در اختیار دولوپرهای وب میگذارد تا دقیقاً مشخص کنند که مرورگر چه منابعی را مجاز به بارگزاری است که این استراتژی هم به نوعی برای جلوگیری از حملات ایکساساس میتواند کاربردی واقع گردد که برای استفاده از این هِدِر میتوان مثال زیر را مد نظر قرار داد:
<?php
header("Content-Security-Policy: default-src 'self';");
در حقیقت، برای این هِدِر میباید یکسری پالِسی (استراتژی) تعریف کنیم که default-src
پالِسی پیشفرض است که دربرگیرندهٔ منابعی همچون فایلهای جاوااسکریپت، سیاساس، تصاویر، فونت، درخواستهای ایجکس، فریمها و ... است و 'self'
هم مروگر را ملزم میسازد تا این منابع را فقط و فقط از دامنهٔ اصلی سایت دانلود کند.
ایمنسازی دستورات AJAX
امروزه در طراحی و توسعهٔ وب اپلیکیشنها میبینیم که دولوپرها به منظور ایجاد تجربهٔ کاربری بهتر از یکسو و همچنین فراخوانی بلادرنگ دادهها از سوی دیگر از فناوری AJAX استفاده میکنند که در چنین مواقعی باید مراقب برخی آسیبپذیریهای مرتبط شد. به عنوان یک دستور ایجکس ساده داریم:
$.ajax({
url: '/api/getter/articles',
type: 'POST',
data: {
type: 'article',
count: 10,
}
});
دستور ایجکس فوق حاکی از آن است که ریکوئستی از جنس POST
برای یوآرال مربوطه ارسال میگردد که حاوی دادههای type
و count
است. آنچه مسلم است اینکه در سمت سرور این دادهها حتماً باید اعتبارسنجی شوند مضاف بر اینکه Content-Type
ریسپانس دریافتی هرگز نباید text/html
باشد:
HTTP/2 200
server: nginx
date: Mon, 28 Jan 2019 17:36:53 GMT
content-type: text/html; charset=UTF-8
بلکه در عوض به عنوان مقدار این کلید باید application/json
در نظر گرفته شود:
HTTP/2 200
server: nginx
date: Mon, 28 Jan 2019 17:36:53 GMT
content-type: application/json; charset=UTF-8
چنین کاری این تضمین را ایجاد میکند که اگر در ریسپانس کد مخربی وجود داشت، مرورگر هرگز آن را اجرا نکند.
اطمینان حاصل کردن از دیتا تایپها
یکی دیگر از نکاتی که در ارتباط با دادههایی که توسط کاربران وارد سیستم میشوند باید مد نظر قرار داده شود، یکسان بودن دیتا تایپ دادهها با آن چیزی است که در دیتابیس در نظر گرفته شده است. برای روشنتر شدن این موضوع، فرم زیر را مد نظر قرار میدهیم:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<form action="index.php" method="POST">
<label for="username">What is your name?</label>
<input name="username" type="text">
<label for="age">How old are you?</label>
<input name="age" type="number">
<button>Submit</button>
</form>
</body>
</html>
در بلوک فوق فرم بسیار سادهای ساختهایم که صرفاً این وظیفه را دارا است تا نام و سنِ کاربر را دریافت کند. آنچه در کد فوق حائز اهمیت است اینکه برای فیلد مرتبط با سنِ کاربر اتریبوت "type="number
در نظر گرفته شده که حاکی از آن است که نوع ورودی باید عدد باشد. آدرسی هم که برای اتریبیوت action
در نظر گرفتهایم نام فایلی تحت عنوان index.php
میباشد که حاوی کدهای زیر است:
<?php
echo "Your name is: " . $_POST['username'];
echo '<br>';
echo "Your age is: " . $_POST['age'];
حال اگر نامی همچون «Sahand» را برای فیلد نام و عددی همچون 30 را به عنوان دیتای ورودی فیلد مرتبط با سنِ کاربر در نظر گرفته و فرم را سابمیت (ارسال) کنیم، خروجی زیر در معرض دیدمان قرار خواهد گرفت:
Your name is: Sahand
Your age is: 30
اکنون فرض کنیم که قرار است این دیتا را در پایگاه داده ذخیره سازیم. برای این منظور، فرض کنیم جدولی تحت عنوان users
داریم که اِسکمای آن به صورت زیر است:
+----------+--------------+
| Field | Type |
+----------+--------------+
| username | varchar(255) |
| age | int(11) |
+----------+--------------+
همانطور که ملاحظه میشود، برای فیلدهای username
و age
به ترتیب از دیتا تایپهای استرنیگ و عدد صحیح استفاده کردهایم. به عبارتی، اگر در فیلد مرتبط با age
بخواهیم یک استرینگ وارد نماییم، سیستم مدیریت دیتابیس چنین اجازهای به ما نخواهد داد اما این دقیقاً همان چیزی است که یک هکر میتواند با دور زدن سیستم منجر به آن گشته و سیستم را با کِرَش مواجه سازد. فرض کنیم کاربری فرم فوق را از طریق ابزار دولوپر تولز مرورگر به صورت زیر دستخوش تغییر سازد:
<input name="age" type="text">
همانطور که میبینیم، به سادگی میتوان در سمتِ کاربر ساختار فرم را دستکاری کرد به طوری که در مثال فوق تایپ فیلد مرتبط با سن کاربر را از number
به text
تغییر دادهایم و از این پس میتوانیم هر نوع دادهای را وارد این فیلد کنیم:
Your name is: Sahand
Your age is: I`m thirty years old
با فرض اینکه در مثال فرضی فوق با دیتابیس کانکشن برقرار شده باشد، مسلماً وب اپلیکیشن ما ارور خواهد داد چرا که تایپ ستون age
در دیتابیس از نوع int
یا به عبارتی عدد صحیح است در حالی که قصد داریم استرینگی همچون «I`m thirty years old» داخل آن ثبت کنیم! در همین راستا، در سمتِ سرور همواره باید علاوه بر نکات فوق، دیتا تایپ را نیز چک کنیم به طوری که مثلاً خواهیم داشت:
<?php
echo "Your name is: " . $_POST['username'];
echo '<br>';
if (is_numeric($_POST['age']) && $_POST['age'] > 0) {
echo "Your age is: " . $_POST['age'];
} else {
echo "Invalid data";
}
حال اگر مجدد با همان دیتای قبلی فرم را سابمیت کنیم خواهیم داشت:
Your name is: Sahand
Invalid data
فانکشن ()is_numeric
که به صورت Built-in در زبان پیاچپی تعبیه شده است این امکان را در اختیار دولوپرهای این زبان میگذارد تا چک کنند آیا دیتا تایپ عدد صحیح است یا خیر و میبینیم که در بلوک فوق اجازهٔ ثبت هر نوع دادهای به جز عددی بزرگتر از صفر داده نخواهد شد. در عین حال، کار به اینجا ختم نمیشود چرا که حتی اگر کاربر یک عدد صحیح هم داخل فیلد وارد سازد، تایپ آن از دید زبانی همچون پیاچپی استرینگ خواهد بود! برای درک بهتر این موضوع، کد زیر را مد نظر قرار میدهیم:
<?php
echo gettype($_POST['age']);
اگر در فیلد مربوط به سن عددی همچون ۳۰ را وارد کنیم و فرم را سابمیت کنیم، فانکشن ()gettype
که مسئول مشخصسازی دیتا تایپ دادهها است مقدار زیر را ریترن میکند:
string
میبینیم علیرغم اینکه اتریبیوت "type="number
برای این فیلد در نظر گرفته شده که اطمینان حاصل میکند کاربر فقط و فقط بتواند عدد وارد این فیلد کند، اما در سمت سرور تایپ آن استرینگ شناخته میشود! برای رفع این مشکل، کدهای تکمیلی به صورت زیر خواهد بود:
<?php
echo "Your name is: " . $_POST['username'];
echo '<br>';
if (is_numeric($_POST['age']) && $_POST['age'] > 0) {
$age = (int) $_POST['age'];
echo "Your age is: " . $age . " and the type is: " . gettype($age);
} else {
echo "Invalid data";
}
در واقع، از مفهومی تحت عنوان Casting در برنامهنویسی استفاده کردهایم بدین صورت که مقدار ['POST['age_$
را داخل متغیری با نام age$
ریختهایم اما قبل از آن دستور (int)
را استفاده کردهایم که به مفسر پیاچپی دستور میدهد تا مقدار مربوطه را حتماً به عدد صحیح تبدیل کند (برای کسب اطلاعات بیشتر در خصوص کستینگ، توصیه میکنیم به آموزش آشنایی با مفهوم Variable (متغیر) در زبان PHP مراجعه نمایید.) حال از این پس به عنوان خروجی خواهیم داشت:
Your name is: Sahand
Your age is: 30 and the type is: integer
در واقع، از این به بعد با اطمینان میتوان دیتایی که از جنس عدد صحیح است و مسلماً با تایپ فیلد مربوطه در دیتابیس نیز همخوانی دارد را برای ثبت در دیتابیس مورد استفاده قرار داد.
جمعبندی
در پایان لازم به یادآوری است که لایبرری اپنسورسی تحت عنوان HTML Purifier میتواند به منظور ممانعت به عمل آوردن از آسیبپذیریهایی که در این آموزش مورد بحث قرار دادیم مورد استفاده قرار گیرد. همچنین اگر علاقمند به کسب اطلاعات بیشتر در خصوص آسیبپذیری ایکساساس هستید، میتوانید به مقالهٔ آشنایی با مفهوم XSS و جلوگیری از آن در زبان برنامهنویسی PHP مراجعه نمایید.