在嵌套解析器中触发XSS
[TOC]
介绍
什么是嵌套解析器情况下的XSS
嵌套解析器 :这里的“嵌套”并不是指代码写成了嵌套结构,而是指数据的处理流程是串行的,导致一段被转换过的 HTML代码,又被扔进了下一个解析器里处理。
开发眼中预期的工作逻辑 :
1 | 用户输入一段话 → 经过 URL 解析器 → 经过邮箱解析器 → 输出 HTML。 |
漏洞原因 :
1 | 但是如果第二个解析器(如邮箱解析器)并不知道第一个解析器(URL 解析器)已经生成了 HTML 标签,就会导致payload被构造暴露出来; |
示例
这里有一个PHP的例子,假设有一个函数先转换 URL 为链接,再转换 Email 为链接。
输入 :
1 | http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)' ' |
预期 :应该生成一个包含 URL 的 <a> 标签。
实际 :
- URL 解析器先工作,生成了一个 href 属性,如
<a href="http://example.com/user@gmail.com">链接</a> - Email 解析器随后工作,在 URL 解析器生成的 href 属性值内部,又插入了一个
<a href="mailto:...">标签。 - 结果:HTML 结构被破坏,原本属于 Email 的属性值引号提前闭合了 URL 的 href 属性,导致
onmouseover变成了一个独立的事件处理属性,触发 XSS。
示意代码:
我们来看一段示意代码:
function returnCLickable($input)
{
$input = preg_replace('/(http|https|files):\/\/[^\s]*/', '<a href="${0}">${0}</a>', $input);
$input = preg_replace('/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)(\?\w*=[^\s]*|)/', '<a href="mailto:${0}">${0}</a>', $input);
$input = preg_replace('/\n/', '<br>', $input);
return $input . "\n\n";
}
完整测试代码:
<?php
$input = "http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)' '";
function returnCLickable($input)
{
$input = preg_replace('/(http|https|files):\/\/[^\s]*/', '<a href="${0}">${0}</a>', $input);
$input = preg_replace('/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)(\?\w*=[^\s]*|)/', '<a href="mailto:${0}">${0}</a>', $input);
$input = preg_replace('/\n/', '<br>', $input);
return $input . "\n\n";
}
$message = returnCLickable($input);
echo $message;
?>
运行结果分析:
这段代码的运行结果就是:
原始输入
1 | http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)' ' |
第一步:匹配 URL 并替换为 <a> 标签
[^\s]*会匹配到末尾,因为有空白字符,所以匹配
1 | http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)' |
结果就是:
1 | <a href="http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)'"> |
问题来了:虽然 href 属性值中包含双引号和 onmouseover,但因为整个 href 是用双引号包裹的(<a href="...">),所以 onmouseover 不会被浏览器解析为 HTML 属性,而是作为 URL 的一部分。因此,这一步本身不会导致 XSS。
第二步:匹配邮箱并替换为 mailto 链接
其中,正则
1 | /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)(\?\w*=[^\s]*|)/ |
- 这个正则试图匹配邮箱地址(如
user@gmail.com),并允许其后跟一个查询字符串(如?subject=...)。 - 但在当前上下文中,第一步已经把整个字符串包裹在
<a href="...">...</a>中,所以第二步是在 HTML 字符串上再次匹配。
问题所在 :正则中的 [^\s]* (匹配非空白字符)非常 贪婪 。
首先,它在 href 属性内部找到了 user@gmail.com 。
紧接着,它通过 ?hack= 匹配了参数部分。
那么由于整个 HTML 字符串中,只有结尾有空格
匹配到的内容(MATCH) :
user@gmail.com?hack='123'onmouseover='alert(/xss/)'">http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)'</a>
它将这部分内容替换为 <a href="mailto:MATCH">MATCH</a> ,导致出现了嵌套且混乱的 HTML 结构。
1 | <a href="http://google.com/<a href="mailto:user@gmail.com?hack='123'onmouseover='alert(/xss/)'"> |
第三步:浏览器解析
浏览器在解析时,会形成如下最终的DOM结构:
1 | <a href="http://google.com/<a href="mailto:user@gmail.com?hack='123'onmouseover='alert(/xss/)'"> |
源代码:
1 | <a href="http://google.com/<a href="mailto:user@gmail.com?hack='123'onmouseover='alert(/xss/)'">http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)'</a>">user@gmail.com?hack='123'onmouseover='alert(/xss/)'">http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)'</a></a> ' |
其中
<a href=":开始一个标签,开始 href 属性。https://google.com/<a href=浏览器读到这里,遇到了第二个双引号"。- 浏览器认为第一个属性的值结束了!
- 随后
mailto:user@gmail.com?hack被作为属性名,而onmouseover也因此被逃逸了出来。 - 那么剩下的其他值则是作为文本数据存在。
所以各步骤的处理如下:
1 | 0.http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)' ' |
评论



