PortSwigger靶场之将 XSS 存储到onclick带有尖括号和双引号 HTML 编码以及单引号和反斜杠转义的事件中通关秘籍
一、实验室挑战分析
漏洞类型: 存储型 XSS(在评论功能中)。
目标: 提交评论,使得点击评论作者姓名时触发
alert()
函数。注入点: 评论作者的姓名或网站输入,被反射到
onclick
事件处理程序中。防御机制(需要绕过):
尖括号 (
<
,>
) HTML 编码:尖括号<
和>
被 HTML 编码为<
和>,
阻止注入新的 HTML 标签。无法使用<script>
、<img>
、<svg>
等标签执行 JS,
❌ 无法使用onload
、onerror
等事件处理器(除非通过其他方式)双引号 (
"
) HTML 编码:双引号"
被 HTML 编码为",
阻止通过闭合属性值来注入新属性或修改现有属性如onfocus
、onclick
),
❌ 无法添加onmouseover=
、onload=
等属性单引号 (
'
) 转义: 单引号'
被转义为\'
, 防止在 JavaScript 字符串中闭合字符串反斜杠 (
\
) 转义: 反斜杠\
被转义为\\
|,防止利用反斜杠“逃逸”转义机制。
利用机制:如果输入被插入到 HTML 属性中(如 onclick),浏览器会先解码 ' 为 ',从而绕过服务端对 ' 的转义;也可以利用表达式执行函数,即使字符串被闭合保护。
在 HTML 中,
'
是表示单引号'
的字符实体。浏览器在解析 HTML 标签的属性或文本内容时,会自动将'
解码为实际的单引号字符'
。然而,在 JavaScript 字符串中,
'
仅被视为普通字符串序列,不会被自动转换为单引号。服务器在接收用户输入时,通常不会自动解码 HTML 实体,也就是说,如果输入中包含
'
,服务器默认将其作为字面字符串处理,而非'
。但某些后端框架或应用程序可能会显式调用 HTML 实体解码函数,将'
转换为'
后再进行后续处理(如转义),这种情况下就可能影响安全过滤逻辑。因此,能否利用
'
来绕过对单引号的转义,关键在于服务器端是否执行了 HTML 实体解码。
二、理论分析
我们进入上面评论页面,输入任意评论和信息并提交,之后查看页面代码如下:
<a id="author" href="https:/1" onclick="var tracker={track(){}};tracker.track('https:/1');">1</a>
<a>
标签是一个超链接,显示作者名1。href="https:/1"
:显示我们评论填写的 URLonclick="..."
:当用户点击这个链接时,会执行 JavaScript 代码。- 脚本内容:
var tracker = { track() {} }; tracker.track('https:/1');
- 它定义了一个对象
tracker
,包含一个空的track()
方法。 - 然后调用
tracker.track(...)
,但由于方法为空,实际什么也不做。
- 它定义了一个对象
分析完关键语句的脚本内容,我们继续分析。
我们输入的 “Website” 被同时写入了 HTML 标签的 href
属性和 onclick
事件中,具体出现在 tracker.track('http://1')
这一函数调用里。由于该 track
方法体为空,因此即使被调用也不会产生实际影响。
此时,我们可以利用 HTML 实体编码的特性,使用 '
来绕过对单引号的转义限制。因为 onclick
是 HTML 标签的属性,浏览器在解析该属性值时,会优先将其中的 HTML 实体(如 '
)解码为对应的字符(即 '
),然后再将解码后的结果传递给 JavaScript 引擎执行。这意味着,'
会在 JavaScript 执行前被转换为单引号,从而实现字符串的提前闭合。
然而,在提交评论时,系统对 “Website” 字段有格式校验(如必须为合法 URL),因此无法直接输入 JavaScript 函数或恶意脚本。为了绕过这一限制,我们需要将 payload 封装在数学表达式或合法 URL 形式中,其中的 HTML 实体(包括 '
)进行解码,然后再将解码后的结果传递给 JavaScript 引擎执行。这意味着,即使服务器未解码,只要 '
出现在 HTML 属性中,浏览器就会将其转换为 '
,从而绕过基于字符过滤的防御机制。
三、解决靶场
Payload:http://'-alert(1)-'
在提交评论时候提交网站输入上面payload,对应js会解析为:
'http://' - alert() - ''
当 JavaScript 遇到非数字类型的值参与算术运算(如减法),会尝试将其隐式转换为数字,这里的'http://'
→ 不是一个合法的数字格式 → 转换结果为 NaN
(Not-a-Number);同时alert()
函数没有返回值,在 JavaScript 中,没有返回值的函数默认返回 undefined
。
接着,JavaScript 开始从左到右计算减法(减法是左结合):
'http://' - undefined
→ 两者都转为数字:'http://'
→NaN
undefined
→NaN
→NaN - NaN = NaN
然后计算:
NaN - ''
''
(空字符串)→ 转为数字是0
→NaN - 0 = NaN
虽然表达式结果是 NaN
,但关键在于:alert()
已经执行了!这里我们巧妙的利用数学运算“包装”恶意函数调用。最后成功通关