1 背景
在推特上看到一个 XSS chanllege,凭借我shi一样的代码审计能力,当然是没有做出来,时间都花在分析正则表达式上了,唉。看了答案之后,其实也并不很难,好多点我也注意到了,比方说参数targetUrl
被提取了两次,但我把 [?|&]
理解错了,所以一直找不到利用方式(还得多练emmm)
2 描述
打开网页后,映入眼帘的是一个重定向,参数名为 targetUrl
。不说这是个 XSS 题,我还以为要 SSRF 呢。
3 分析
首先依旧是看源码(如下),总共有两个函数 getQuerystring()
和 queryPass()
,主函数为后者。有两种方式调用 queryPass()
函数,一种是 setTimeout
,打开页面五秒后自动调用;一种是点击按钮 “Go go go” 后调用。而 queryPass()
只干了一件事,将 location.href
设置为 getQuerystring()
的返回值。
1 | setTimeout(function () { |
getQuerystring()
函数开始就是一个正则 /^((([A-Za-z]{4,5}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/
,不过用不用得到分析一下,权当练手了。
结构化展示如下,用来匹配 URL 的。
1 | ^( |
([A-Za-z]{4,5}:(?:\/\/)?)
匹配 URL 的协议部分,如:https://, ftp:,(?:...)
表示非捕获组,匹配的内容不会被保存,不影响分组的编号(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+
匹配用户信息前缀(HTTP身份验证)+主机名,如: expected-host:fakepassword@evil-host(想到橘师傅那篇 SSRF 的文章了…)(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+
匹配无协议头(scheme) 的 url(?:\/[\+~%\/.\w-_]*)?
匹配路径,以 / 打头,如: /my+path, /users/~john/documents, /encoded%20value, /path/to/file.html\??(?:[-\+=&;%@.\w_]*)
匹配查询参数,以 ? 打头#?(?:[\w]*)
匹配片段标识符,hash #
这有个工具 Regexper 可以帮助分析正则表达式。怎么样?是不是一目了然?!看到这张图的时候,我感觉分析了这个正则好长好长时间的我真纯纯🤡 emmm
既然是 XSS,又要传 URL,显然应该用 javascript:
伪协议执行 js 代码。上图红框框住的这条路径允许使用 以内的字符,故可以传入 javascript:
,但只有这些个字符如何执行任意 js 代码呢?后面还有个 @
符号,没法用 //
把它注释掉。
如何只使用
-;:&=+$,\w
中的字符执行 js?搞不懂 (个_个)
再次查看源码,发现参数 targetUrl
被提取了两次:一次是在 getURLParameter
获取参数的时候,用以验证输入有效性;一次是在 else
条件分支使用参数的时候,用来执行具体操作。应该可以算 TOCTOU (time of check, time of use)。
由此形成思路,构造的 payload 需要绕过验证顺利进入 else
分支,进入条件为:1. url 不为空;2. domain 部分包含 fransrosen.com 或 fransrosen.se;3. regex.test(url) 为 true。之后,在 else
分支中重新提取参数信息,返回值赋给 location.href
,从而执行任意 js 代码。
注意两次提取参数的方式也有所不同,第一次在getURLParameter()
函数中,使用的正则是 "[?|&]targetUrl=([^&;]+?)(&|#|;|$)
其中 [?|&]
中的 |
并不是“或”的意思,而是原本的 “|” 号,匹配的是三种字符 ?|&
及其组合。
第二次在 else
分支中,使用的是 [\\?&]targetUrl=([^\0]*)
。不难发现,对于 ?|targetUrl=...
,第一次能匹配到参数的值,但第二个正则匹配不到,因为 targetUrl
前面是 |,而不是 ? 或 &。
所以,如果我们提供两个 targetUrl
参数,?|targetUrl
和 &targetUrl
,这两个正则会分别匹配到不同的参数值,正则一由于未使用修饰符 g
,仅返回第一个匹配项,故只能匹配到 ?|targetUrl
,用来绕过验证;正则二匹配到 &targetUrl
的值,用来将恶意 js 赋值给 location.href
。
构造 payload: ?|targetUrl=www.fransrosen.com&targetUrl=javascript:alert(0)