各位听众,下午好!欢迎来到今天的技术讲座。在网络安全的领域中,跨站脚本(Cross-Site Scripting, XSS)攻击始终占据着一个重要位置。尽管防御技术日益成熟,但攻击者也在不断演进他们的技巧。今天,我们将深入探讨XSS攻击中的一些进阶玩法,特别是如何利用innerHTML属性、javascript:伪协议以及SVG标签来绕过常见的防护机制,实现攻击。
XSS攻击的基石与演进
XSS是一种注入攻击,攻击者通过在受信任的网站中注入恶意脚本,当其他用户浏览该网站时,这些脚本就会在他们的浏览器上执行。这些恶意脚本可以窃取用户的会话Cookie、修改网页内容、重定向用户到恶意网站,甚至利用浏览器漏洞进行更深层次的攻击。
传统的XSS攻击通常聚焦于直接注入<script>标签。然而,随着Web开发的进步和安全意识的提高,许多现代Web应用都实现了对<script>标签的过滤。这就迫使攻击者寻找新的、更隐蔽的攻击向量。今天我们将要探讨的innerHTML、javascript:伪协议和SVG标签,正是这些高级绕过技巧的典型代表。
一、innerHTML:危险的DOM操作接口
innerHTML属性是JavaScript中一个强大且常用的DOM操作接口,它允许我们直接获取或设置一个元素内部的HTML内容。其便利性不言而喻,但如果处理用户提供的不可信数据时,不进行充分的过滤和编码,innerHTML就会成为XSS攻击的温床。
1.1 innerHTML 的基本利用
假设一个Web应用允许用户提交评论,并将评论内容直接赋值给某个元素的innerHTML。
// 后端或前端获取用户提交的评论内容
const userComment = "<p>这是一条用户评论。</p>"; // 假设用户提交了正常的HTML
document.getElementById("comment-display").innerHTML = userComment;
如果攻击者提交如下内容:
<img src="x" onerror="alert('XSS via innerHTML!')">
当这段内容被赋值给innerHTML时,浏览器会尝试解析它。<img>标签的src="x"会加载失败,从而触发onerror事件,执行alert('XSS via innerHTML!')。
更直接的脚本注入:
<script>alert('XSS via innerHTML!')</script>
这段代码同样会被执行。许多WAF和前端过滤会尝试移除<script>标签,但还有其他方式。
1.2 innerHTML 与上下文执行:不仅仅是<script>
innerHTML的危险之处在于它不仅能插入HTML元素,更重要的是,浏览器在解析这些HTML时,会根据上下文执行其中的JavaScript代码。这包括:
- 事件属性(Event Attributes):如
onload,onerror,onclick,onmouseover等。 - URL属性(URL Attributes):如
href,src,data等,它们可以接受javascript:伪协议。 - HTML标签自身:如
<style>、<object>、<embed>、<iframe>等,它们都可以包含或引用可执行内容。
案例分析:绕过<script>标签过滤
如果一个系统过滤了<script>标签,我们可以转向利用事件属性。
// 假设用户输入经过了<script>标签的过滤
const sanitizedInput = `<img src="nonexistent.png" onerror="alert('XSS by onerror!')">`;
document.getElementById("content").innerHTML = sanitizedInput;
这里,即使<script>被移除了,<img>标签的onerror事件仍然能够触发。常见的可利用事件属性有很多,例如:
| 标签 | 事件属性 | 描述 |
|---|---|---|
<img> |
onerror |
图片加载失败时触发 |
<body> |
onload |
页面或框架加载完成时触发 |
<a> |
onclick, onmouseover |
链接被点击或鼠标悬停时触发 |
<svg> |
onload, onanimationend |
SVG元素加载完成或动画结束时触发 |
<video> |
onplay, onerror |
视频播放或加载错误时触发 |
<form> |
onsubmit |
表单提交时触发 |
<iframe> |
onload |
Iframe加载完成时触发 |
利用HTML实体编码绕过
某些过滤器可能只会简单地查找字符串中的字面量,例如onerror。这时,HTML实体编码就可以派上用场。
<img src="x" onerror="alert('XSS by encoded onerror!')">
这里的e是字符e的十六进制HTML实体编码。当浏览器解析innerHTML时,它会先解码HTML实体,然后再解析HTML结构和属性,从而使onerror事件被识别并触发。
利用CSS表达式(IE Only,已废弃)
虽然在现代浏览器中已不再适用,但了解历史上的攻击方式有助于理解XSS的演变。IE浏览器曾支持在CSS中使用expression()来执行JavaScript。
<div style="width: expression(alert('XSS by CSS expression!'))"></div>
当这个div被渲染时,expression内的代码就会执行。
1.3 innerHTML 与 Mutation XSS
Mutation XSS (MXSS) 是一种特殊的XSS,它发生在浏览器对DOM进行“修复”或“重构”时。当某些HTML片段通过innerHTML或其他DOM操作被插入后,浏览器可能会自动调整这些HTML,使其符合W3C标准或内部解析规则,而这种调整有时会意外地创建出可执行的XSS向量。
示例:不完整的HTML标签
假设一个过滤器移除了所有可疑的HTML标签,但保留了部分字符。
// 假设用户输入:
const userInput = `<<img src="x" onerror="alert('MXSS!')">`;
// 经过某种不完善的过滤,可能变成:
// 过滤器移除了第一个 <,或者仅仅因为不完整而认为无害
// 但如果保留了部分:
const filteredInput = `<img src="x" onerror="alert('MXSS!')">`; // 假设过滤器没有处理好
document.getElementById("content").innerHTML = filteredInput;
更复杂的Mutation XSS通常涉及浏览器对不规范HTML的容错处理。例如:
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
当这段HTML被插入到innerHTML中时,浏览器会尝试修复不匹配的<noscript>标签。在某些情况下,它可能会将</noscript>解析为结束标签,从而导致后面的<img>标签及其onerror属性被激活。
另一个常见的Mutation XSS场景是与SVG结合,我们将在后面详细讨论。
防御innerHTML的建议:
- 避免使用
innerHTML:如果可能,尽量使用textContent或innerText来插入纯文本内容。 - 白名单过滤:对允许的HTML标签和属性进行严格的白名单过滤,而不是黑名单。
- 上下文敏感编码:根据输出的HTML上下文,对用户输入进行适当的编码。
- 使用DOMPurify等库:对于必须接收HTML内容的场景,使用成熟的HTML净化库(如DOMPurify)来安全地处理用户输入。
二、javascript: 伪协议:隐藏的执行通道
javascript:伪协议是一种特殊的URI方案,它允许在浏览器中执行JavaScript代码。当浏览器遇到一个以javascript:开头的URI时,它会将其后的内容作为JavaScript代码来执行。这个特性在许多地方都可能被利用,尤其是在那些期望URL作为输入的场景。
2.1 javascript: 伪协议的基本原理与利用
最常见的利用方式是在<a>标签的href属性中:
<a href="javascript:alert('XSS via javascript: pseudo-protocol!')">点击我</a>
当用户点击这个链接时,alert('XSS via javascript: pseudo-protocol!')就会执行。
除了<a>标签,javascript:伪协议还可以出现在其他期望URL的属性中:
<iframe>的src属性:<iframe src="javascript:alert('XSS in iframe!')"></iframe>这会在iframe内部执行JavaScript。
<img>的src属性(通常不可行):
通常情况下,<img>标签的src属性不直接支持javascript:伪协议,因为它期望的是图片数据。但在某些浏览器或特定上下文下,可能存在解析漏洞。不过,更常见的是利用onerror。location.href或window.open():
在JavaScript代码中,如果将用户输入拼接到location.href或window.open()中,也可能导致XSS。const userUrl = "javascript:alert('XSS via location.href!')"; window.location.href = userUrl;这会导致当前页面跳转并执行JavaScript。
2.2 绕过URL过滤和编码
许多Web应用会尝试过滤URL,移除javascript:前缀。攻击者可以通过多种编码和混淆技术来绕过这些过滤器。
URL编码
最常见的绕过方式是URL编码。例如,将:编码为%3a。
<a href="java%73cript:alert('XSS via URL encoding!')">点击我</a>
这里的%73是s的URL编码。通常,浏览器在解析href属性时会先进行URL解码,然后再识别javascript:伪协议。
十六进制编码/Unicode编码
一些浏览器和解析器可能支持更复杂的编码。
<a href="javascript:alert('XSS via HTML entity encoding!')">点击我</a>
这里,javascript:被完全转换为HTML实体编码。当HTML被解析后,这些实体会被解码回原始字符。
Unicode字符集混淆
在某些旧的或不严格的过滤器中,可以使用宽字符或特殊Unicode字符来混淆javascript:字符串。例如,使用全角字符:
<a href="javascript:alert('XSS via full-width chars!')">点击我</a>
这在现代浏览器和严格的过滤器中很少有效,但说明了尝试不同字符集混淆的可能性。
换行符和制表符
在某些URL解析器中,javascript:伪协议的某些部分可以包含换行符或制表符,而浏览器仍能正确识别。
<a href="javascript
:alert('XSS with newline!')">点击我</a>
这通常取决于具体浏览器和解析上下文。
data: URI 与 javascript: 的结合
data: URI允许我们将小文件(如图片、HTML、CSS或JavaScript)直接嵌入到HTML中,而无需外部请求。虽然它本身不是javascript:伪协议,但有时可以作为传递恶意内容的载体。
例如,一个iframe的src属性可以接受data: URI来加载一个包含XSS的HTML页面:
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTIHZpYSBkYXRhOicpPC9zY3JpcHQ+"></iframe>
PHNjcmlwdD5hbGVydCgnWFNTIHZpYSBkYXRhOicpPC9zY3JpcHQ+是<script>alert('XSS via data:')</script>的Base64编码。这种方式可以绕过那些只关注http://或https://的URL白名单。
2.3 javascript: 伪协议的防御建议
- URL白名单:严格限制允许的URL协议,只允许
http://、https://等安全协议。 - URL解析库:使用安全的URL解析库来处理用户提供的URL,而不是手动字符串操作。
- 上下文敏感编码:在将用户输入插入到URL上下文中时,进行严格的URL编码,并确保不解码
javascript:伪协议。 - CSP策略:通过Content Security Policy (CSP) 禁用
unsafe-inline和data:URI,并限制script-src来源,可以有效阻止javascript:伪协议的执行。
三、SVG标签:艺术与攻击的交汇
可伸缩矢量图形(Scalable Vector Graphics, SVG)是一种基于XML的图像格式,用于描述二维矢量图形。由于SVG本身就是XML文档,它可以包含脚本和事件处理器,这使其成为XSS攻击的强大载体。
3.1 SVG中的脚本执行能力
SVG文件可以包含<script>标签,就像HTML一样。
<!-- example.svg -->
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<script type="text/javascript">
alert('XSS from inline SVG script!');
</script>
</svg>
如果这个SVG文件被加载到浏览器中(例如,通过<img>或<iframe>),其中的脚本就会执行。
3.2 SVG中的事件处理器
与HTML元素类似,SVG元素也支持各种事件处理器,如onload, onclick, onerror, onmouseover等。
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<rect x="0" y="0" width="200" height="100" fill="red"
onload="alert('XSS from SVG onload event!')" />
</svg>
当这个SVG被加载时,rect元素的onload事件就会触发。
常见的可利用SVG标签和属性:
| SVG标签/元素 | 可利用属性/事件 | 描述 |
|---|---|---|
<svg> |
onload, <script> |
SVG根元素,可直接包含脚本或事件 |
<animate> |
onbegin, onend |
动画开始或结束时触发 |
<set> |
onbegin |
设置属性时触发 |
<use> |
xlink:href |
引用外部资源,可导致SSRF或XSS |
<image> |
xlink:href, onerror |
引用外部图片,onerror可触发 |
<rect>, <circle>, <path>等 |
onload, onclick, onmouseover |
各种图形元素,支持事件处理器 |
<a> |
xlink:href |
SVG中的链接,支持javascript:伪协议 |
3.3 SVG作为攻击载体的多种形式
SVG的强大之处在于它可以以多种形式被嵌入到HTML页面中,每种形式都有其独特的攻击潜力。
a) 作为<img>标签的src
通常,<img>标签加载SVG文件时,会禁用其中的脚本。这是浏览器的一种安全机制。
<img src="malicious.svg">
然而,如果SVG文件本身包含data: URI,并且内容类型被错误处理,则可能绕过此限制。例如,如果浏览器将data:image/svg+xml;base64,...解析为HTML上下文,而非图像上下文。
b) 作为<iframe>或<object>标签的src/data
这是最直接且强大的方式。<iframe>和<object>标签会创建一个独立的浏览上下文,并完全解析和执行SVG中的脚本。
<iframe src="malicious.svg"></iframe>
<object data="malicious.svg" type="image/svg+xml"></object>
这种方式常常用于绕过那些只过滤<img>标签的防御。
c) 直接内联SVG到HTML中
将SVG代码直接嵌入到HTML文档中,此时SVG元素被视为DOM的一部分,其中的脚本和事件处理器将与页面的其他脚本在同一上下文中执行。
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert('XSS from inline HTML SVG!')</script>
</svg>
如果用户输入可以控制HTML的innerHTML,那么内联SVG是一个非常有效的攻击手段。
d) SVG与data: URI结合
将恶意SVG代码Base64编码后,通过data: URI嵌入到HTML中。
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxzY3JpcHQ+YWxlcnQoJ1hTUycpPC9zY3JpcHQ+PC9zdmc+" />
这里的Base64编码内容是<svg xmlns="http://www.w3.org/2000/svg"><script>alert('XSS')</script></svg>。
在某些浏览器或旧版本中,<img>标签加载data:image/svg+xml时,其中的脚本会被执行。这是一个著名的绕过技巧,尤其是在CSP不严格的情况下。
3.4 绕过Content-Type检查
有时,服务器会根据文件扩展名或MIME类型来判断文件内容。攻击者可以通过上传一个名为image.svg但实际内容是HTML或脚本的文件,然后通过某种方式使其以错误的MIME类型被渲染。
例如,一个上传接口允许上传图片,并检查MIME类型为image/png或image/jpeg。但如果它允许上传image/svg+xml,并且在查看时没有正确设置X-Content-Type-Options: nosniff头部,那么浏览器可能会“嗅探”到其内部的HTML或脚本并执行。
SVG与Mutation XSS的结合
SVG标签的解析复杂性也使其成为Mutation XSS的沃土。例如,在某些浏览器中,不完整的SVG标签结构可能会被浏览器“修复”,从而产生XSS。
<svg><script>//<![CDATA[
alert('XSS via CDATA in SVG script!');
//]]></script>
<![CDATA[ 块通常用于在XML中包含不应被解析为XML标记的文本。如果过滤器未能正确处理CDATA块,或者在解析后对其进行了不当的“修复”,可能导致脚本执行。
3.5 SVG的防御建议
- 严格的MIME类型验证:在文件上传时,不仅检查文件扩展名,还要严格验证实际的文件内容MIME类型。
- 内容安全策略(CSP):
img-src:限制<img>标签的来源,避免加载不受信任的data:URI或外部SVG。script-src:严格限制可执行脚本的来源,禁用unsafe-inline和unsafe-eval。object-src:限制<object>、<embed>等标签的来源。frame-src:限制<iframe>的来源。
- SVG净化:如果必须允许用户上传或嵌入SVG,使用专业的SVG净化库(如DOMPurify)来移除所有可执行内容。
X-Content-Type-Options: nosniff:在提供用户上传内容时,设置此HTTP头部,防止浏览器进行MIME类型嗅探。
四、高级绕过策略的编织
以上我们分别探讨了innerHTML、javascript:伪协议和SVG标签的利用。在实际攻击中,这些技术往往不是孤立使用的,而是相互结合,形成更复杂的绕过策略。
4.1 编码和混淆的艺术
攻击者会利用各种编码和混淆技术来绕过过滤器。
a) 多层编码
当过滤器在处理输入时,可能只进行一次解码。如果攻击者进行多次编码,就有可能绕过。
例如,一个过滤器移除了onerror,但允许URL编码。
攻击者提交:on%2565rror (双重URL编码的onerror)
过滤器解码一次:on%65rror (仍然无法识别)
innerHTML或浏览器最终解码:onerror (触发)
b) Unicode转义
JavaScript字符串支持Unicode转义序列,如u0061表示字符a。
<img src="x" onerror="alu0065rt('XSS by Unicode escape!')">
这种方式对JavaScript字符串有效,但在HTML属性中直接使用通常需要HTML实体编码。
c) 零宽度字符
在某些不严格的过滤器中,插入零宽度字符(如​)可能扰乱过滤器的正则匹配,但浏览器在渲染时会忽略它们。
<img src="x" on​error="alert('XSS!')">
这种技巧的效果取决于过滤器的具体实现。
4.2 利用浏览器解析差异
不同的浏览器、甚至同一浏览器的不同版本,在解析不规范HTML时可能会有细微的差异。攻击者会利用这些差异来构造XSS。
a) 标签闭合与属性注入
如果输入被插入到某个属性值中,但没有被正确引用或编码,攻击者可以提前闭合属性,注入新属性或标签。
<input type="text" value="USER_INPUT">
如果USER_INPUT是" onfocus="alert('XSS!')",则最终HTML会变成:
<input type="text" value="" onfocus="alert('XSS!')">
导致XSS。
b) <textarea> 和 <title> 标签的特殊性
<textarea>和<title>标签内部的内容通常被视为纯文本,直到遇到它们的闭合标签。这意味着在它们内部,<script>等标签不会被解析。
<textarea>用户输入:<script>alert('不会执行')</script></textarea>
然而,如果攻击者能闭合<textarea>,再注入自己的标签:
<textarea>用户输入:</textarea><img src="x" onerror="alert('XSS!')">
这就会导致XSS。
4.3 结合CSP绕过
Content Security Policy (CSP) 是现代Web防御XSS的重要手段。然而,CSP本身也可能被绕过。
a) 开放的JSONP端点
如果CSP允许加载来自某些域的脚本,而这些域又托管了开放的JSONP端点,攻击者就可以利用这些端点来执行任意代码。
例如,CSP允许script-src 'self' https://example.com;
如果https://example.com/jsonp?callback=alert(document.domain)返回alert(document.domain)({"data":"..."});,那么攻击者可以构造:
<script src="https://example.com/jsonp?callback=alert(document.domain)"></script>
来执行JavaScript。
b) 允许data: URI的CSP
如果CSP配置允许data: URI (script-src 'self' data:),那么前面提到的data:text/html;base64,...或data:image/svg+xml;base64,...就可能成为XSS的攻击面。
c) 滥用Hash-based CSP
一些CSP策略使用哈希值来允许特定的内联脚本块。如果应用动态生成脚本,并且哈希值被泄露或可预测,攻击者可以利用它。
4.4 DOM XSS与反射/存储型XSS的结合
DOM XSS发生在客户端,通过修改DOM环境来触发。反射型和存储型XSS则涉及服务器端将恶意数据返回给客户端。高级攻击往往是这三者的结合。
例如,一个存储型XSS漏洞允许攻击者在数据库中存储恶意HTML。当这个HTML被加载到客户端,并由JavaScript代码(例如,通过document.write()或element.innerHTML)处理时,就可能触发DOM XSS。
五、构建坚固的防御体系
面对如此多变的XSS攻击手法,我们需要一套多层次、纵深防御的策略。
5.1 输入验证与净化
- 白名单验证:这是最严格也是最安全的策略。只允许已知安全的字符集、标签和属性。所有不在白名单中的内容都将被移除或拒绝。
- 上下文敏感的输出编码:根据数据输出到HTML的不同上下文(HTML内容、属性值、JavaScript字符串、URL等),选择最合适的编码方式。
- HTML内容:使用
HTMLEncode(如<for<)。 - HTML属性:使用
HTMLAttributeEncode(如"for")。 - JavaScript字符串:使用
JavaScriptEncode(如x3cfor<)。 - URL:使用
URLEncode(如%3cfor<),并严格限制协议。
- HTML内容:使用
- 使用DOMPurify等HTML净化库:对于必须接受和渲染用户提供的HTML的场景,使用经过充分测试和广泛使用的HTML净化库。DOMPurify通过严格的白名单策略和DOM解析来确保输出的HTML是安全的。
5.2 内容安全策略(CSP)
CSP是W3C标准的安全机制,通过HTTP头部告诉浏览器哪些资源(脚本、样式、图片等)可以被加载和执行。
- 严格的
script-src:只允许加载来自可信域的脚本,并禁用unsafe-inline和unsafe-eval。Content-Security-Policy: script-src 'self' https://trusted.cdn.com; - 限制其他资源:同样对
img-src,style-src,font-src,media-src,object-src,frame-src等进行限制。 default-src:定义所有未明确指定类型的资源的默认策略。report-uri或report-to:配置报告XSS尝试的URL,以便监控和分析攻击。
5.3 HTTP头部安全增强
X-Content-Type-Options: nosniff:阻止浏览器对响应内容进行MIME类型嗅探,强制使用Content-Type头部声明的类型,防止SVG或其他文件被错误解析为HTML。X-XSS-Protection: 0:现代浏览器已弃用X-XSS-Protection头部,并建议将其设置为0以禁用浏览器内置的XSS过滤器,因为这些过滤器有时可能引入新的安全漏洞。CSP才是推荐的防御手段。
5.4 现代前端安全特性
- Trusted Types:这是一个较新的浏览器安全特性,旨在从根本上防止DOM XSS。它通过强制对可能导致DOM XSS的API(如
innerHTML、setAttribute等)进行类型检查,确保只有经过“信任”的、安全的数据才能赋值给这些API。- 它需要严格的CSP配置,如
require-trusted-types-for 'script'。 - 开发者需要显式地将数据标记为TrustedHTML、TrustedScript等,或者使用安全函数来生成这些类型。
// 假设通过DOMPurify净化后的HTML const sanitizedHtml = DOMPurify.sanitize(userInput, { RETURN_TRUSTED_TYPE: true }); // 只有TrustedHTML类型的值才能赋值给innerHTML document.getElementById("content").innerHTML = sanitizedHtml;
- 它需要严格的CSP配置,如
- Web Components (Shadow DOM):Shadow DOM可以将组件的内部结构与外部文档隔离,提供更好的封装性。虽然它本身不是XSS防护机制,但它可以在一定程度上限制恶意脚本对整个页面的影响范围。
5.5 开发流程中的安全实践
- 安全编码培训:对开发人员进行定期的安全编码培训,提高对XSS及其他安全漏洞的认识。
- 代码审查:在代码发布前,进行严格的代码审查,特别是对涉及用户输入的DOM操作进行重点关注。
- 自动化安全测试:集成SAST(静态应用安全测试)和DAST(动态应用安全测试)工具到CI/CD流程中,自动发现潜在的XSS漏洞。
- 安全库和框架:优先使用自带安全防护机制的现代Web框架(如React, Angular, Vue),它们在一定程度上能自动处理输出编码。
结语
XSS攻击的演进永无止境,攻击者总能找到新的绕过技巧。从innerHTML的滥用,到javascript:伪协议的隐蔽执行,再到SVG标签的强大潜能,这些都要求我们作为防御者,必须对这些高级技术有深入的理解。唯有持续学习,并采用多层次、全方位的防御策略,才能有效地保护我们的Web应用和用户免受XSS的威胁。安全是一个持续的对抗过程,没有一劳永逸的解决方案,只有不断进步的防护体系。