【i-TRACING案例】自定义过滤器的陷阱:XSS 注入和过滤器绕过

使用自定义过滤器来保护应用程序免受 XSS 注入攻击被认为是一种不良做法,尽管这种做法仍然很常见。此类过滤器通常不足以阻止恶意用户输入破坏应用程序的安全。

以下案例研究描述了在渗透测试中遇到的一个"简单"过滤器,以及绕过此过滤器并利用反射型跨站脚本攻击 (XSS) 所需的推理。

XSS注入:漏洞实验室

在深入探讨遇到的过滤器及其绕过方案之前,我们建议先下载此 HTML 文件来测试此反射型 XSS 漏洞:
从 I-TRACING GitHub 下载 >

usernameURL 中的参数用作测试有效载荷的注入点。

利用 XSS 漏洞

XSS注入的演示依赖于上一节中提供的代码。

XSS过滤器注入的上下文发现

与所有反射型 XSS 注入一样,发现的第一步是检查用户输入注入的上下文,确定哪些特殊字符是允许的,哪些是不允许的(被移除的),哪些是被转义的以及如何转义的。

XSS i注射语境

用户输入会在页面上的两个位置注入,如下图所示:

对 JavaScript 代码的分析表明,注入到<span>标签中的代码使用了 `.`innerText方法。这种方法会自动转义 HTML 字符,因此无法在此处进行注入。

然而,第二个注入点位于<script>标签内的字符串中,具体位置在变量的定义处username。突破这个字符串的限制,就可以执行任意 JavaScript 代码。

用户输入处理

要实现功能注入,必须先了解用户输入是如何处理的。在本例中,处理过程在客户端进行,因此只需阅读 JavaScript 代码即可了解其行为。如果处理过程在服务器端进行,则需要进行测试才能发现每个特殊字符的处理方式。
JavaScript

复制代码
material-theme-lighter 复制代码
<span style="color:#000000"><span style="color:inherit !important"><span style="background-color:#fafafa"><code><span style="color:#9c3eda">function</span> <span style="color:#6182b8">escapeXss</span><span style="color:#39adb5">(</span><span style="color:#90a4ae"><em>input</em></span><span style="color:#39adb5">)</span> <span style="color:#39adb5">{</span>
    <span style="color:#39adb5"><em>return</em></span> <span style="color:#90a4ae">input</span><span style="color:#39adb5">.</span><span style="color:#6182b8">replace</span><span style="color:#e53935">(</span><span style="color:#39adb5">/</span><span style="color:#91b859">"</span><span style="color:#39adb5">/</span><span style="color:#f76d47">g</span><span style="color:#39adb5">,</span> <span style="color:#39adb5">'</span><span style="color:#90a4ae">\\</span><span style="color:#91b859">"</span><span style="color:#39adb5">'</span><span style="color:#e53935">)</span><span style="color:#39adb5">.</span><span style="color:#6182b8">replace</span><span style="color:#e53935">(</span><span style="color:#39adb5">/</span><span style="color:#90a4ae">\/</span><span style="color:#39adb5">/</span><span style="color:#f76d47">g</span><span style="color:#39adb5">,</span> <span style="color:#39adb5">'</span><span style="color:#90a4ae">\\</span><span style="color:#91b859">/</span><span style="color:#39adb5">'</span><span style="color:#e53935">)</span><span style="color:#39adb5">;</span>
<span style="color:#39adb5">}</span>

<span style="color:#9c3eda">const</span><span style="color:#90a4ae"> params </span><span style="color:#39adb5">=</span> <span style="color:#39adb5">new</span> <span style="color:#6182b8">URLSearchParams</span><span style="color:#90a4ae">(window</span><span style="color:#39adb5">.</span><span style="color:#90a4ae">location</span><span style="color:#39adb5">.</span><span style="color:#90a4ae">search)</span><span style="color:#39adb5">;</span>
<span style="color:#9c3eda">const</span><span style="color:#90a4ae"> usernameParam </span><span style="color:#39adb5">=</span><span style="color:#90a4ae"> params</span><span style="color:#39adb5">.</span><span style="color:#6182b8">get</span><span style="color:#90a4ae">(</span><span style="color:#39adb5">'</span><span style="color:#91b859">username</span><span style="color:#39adb5">'</span><span style="color:#90a4ae">)</span><span style="color:#39adb5">;</span>
<span style="color:#9c3eda">const</span><span style="color:#90a4ae"> escapedUsername </span><span style="color:#39adb5">=</span><span style="color:#90a4ae"> usernameParam </span><span style="color:#39adb5">?</span> <span style="color:#6182b8">escapeXss</span><span style="color:#90a4ae">(usernameParam) </span><span style="color:#39adb5">:</span> <span style="color:#39adb5">'</span><span style="color:#91b859">ANONYMOUS</span><span style="color:#39adb5">'</span><span style="color:#39adb5">;</span>

<span style="color:#9c3eda">var</span><span style="color:#90a4ae"> script </span><span style="color:#39adb5">=</span><span style="color:#90a4ae"> document</span><span style="color:#39adb5">.</span><span style="color:#6182b8">getElementById</span><span style="color:#90a4ae">(</span><span style="color:#39adb5">"</span><span style="color:#91b859">user-script</span><span style="color:#39adb5">"</span><span style="color:#90a4ae">)</span>
<span style="color:#90a4ae">script</span><span style="color:#39adb5">.</span><span style="color:#90a4ae">innerText </span><span style="color:#39adb5">=</span><span style="color:#90a4ae"> script</span><span style="color:#39adb5">.</span><span style="color:#90a4ae">innerText</span><span style="color:#39adb5">.</span><span style="color:#6182b8">replace</span><span style="color:#90a4ae">(</span><span style="color:#39adb5">"</span><span style="color:#91b859">PLACEHOLDER</span><span style="color:#39adb5">"</span><span style="color:#39adb5">,</span><span style="color:#90a4ae">escapedUsername)</span>
<span style="color:#6182b8">eval</span><span style="color:#90a4ae">(script</span><span style="color:#39adb5">.</span><span style="color:#90a4ae">innerText)</span></code></span></span></span>

在将用户输入重新注入脚本标签之前,会对其进行两种处理:

  • "被替换为\",阻止字符串转义;
  • /被替换为\/,阻止创建关闭的 HTML 标签和 JavaScript 注释。

构建用于 XSS 注入的功能性有效载荷

转义字符串

构建有效载荷的第一步依赖于一个常见的过滤器绕过技巧:`\ n` 通过在它前面"添加 `\n` 进行转义,但 `\ n` 本身并未被转义。因此,输入变为`\n` 。添加的 `\n`会被转义,而 `\n`会被再次解释以结束字符串。在实验室中测试此操作会导致 JavaScript 语法错误:\``\``\"``\\\"``\``"

\在有效载荷中最后一个字符串之前添加的` "<br>`(允许重新打开字符串,以避免"脚本中已有的字符串导致问题)不在字符串内部,并且在 JavaScript 语法中无效。注入的代码不会被解释执行,而是会在控制台中抛出一个错误。

注释掉剩余的 JavaScript 代码

解决此错误的方法是注释掉该行的其余部分,这样"字符串的结尾就不会再被解释。然而,JavaScript 注释(无论是 `<script>`//还是 `<script> /* ... */`)都以 `<script>` 开头/,这与 `<script>` 存在相同的语法问题",因为过滤器会在注释前添加 `<script>` \

这个技巧不如\之前用的转义符那么常用:我们会使用 HTML 解析器注释掉行尾。实际上,HTML 注释不使用 `<code>` 标签,/而是用 `<code>` 标签打开<!--,浏览器会在标签结束时自动关闭它们。

避免使用自定义过滤器来防止 XSS 注入和 XSS 过滤器规避。

正如本案例研究所探讨和证明的那样,使用自定义过滤器来保护应用程序是无效的,因此不建议使用。现代编程语言和框架提供了专门的功能,可以确保对注入攻击提供最佳保护。应优先使用这些工具,以确保强大的安全性并降低与 XSS 漏洞或其他类型注入相关的风险。

作者

Amos GEORGE,审计与攻击性安全,I-TRACING

2026年2月10日

相关推荐
特别关注外国供应商2 个月前
Qualys 如何做 云合规 解决方案?
安全架构·gdpr·云合规·网络安全合规·pci dss 4.0·hipaa 2023·qualys