如何使用字符编码绕过 XSS 清理程序?

信息安全 php xss 编码
2021-08-25 01:48:40

我在不同的博客中读到,htmlspecialchars()当没有将预期的字符集作为可选参数时,PHP 函数会出现某些问题。

有人可以通过一些与字符编码相关的示例来解释一些关于 XSS 漏洞利用的基本内容吗?

这也会影响现代浏览器吗?

3个回答

问题

即使有过滤器,滥用字符编码也是让 XSS 工作的流行技巧。当它起作用时有许多不同的情况,但它们都有共同的先决条件:

  • 攻击者以字符编码 A 发送有效载荷。
  • 执行过滤或清理的服务器正在使用字符编码 B。
  • 受害者浏览器将页面解释为字符编码 A。

让我们看两个如何发生这种情况的例子。

示例 #1:htmlspecialchars 中没有编码参数

这在 PHP 中很常见:

echo htmlspecialchars($_GET["query"], ENT_COMPAT | ENT_HTML401);

这里的问题是当没有指定编码时 PHP 会回退到默认行为。手册

如果省略,则编码的默认值会根据使用的 PHP 版本而有所不同。在 PHP 5.6 及更高版本中,default_charset 配置选项用作默认值。PHP 5.4 和 5.5 将使用 UTF-8 作为默认值。早期版本的 PHP 使用 ISO-8859-1。

所以 PHP 使用什么编码取决于你的版本和配置。伟大的。因此,现在你和深渊之间的所有障碍都是有人对php.ini. 我也喜欢危险地生活……但不是那么危险。

请注意,此示例与浏览器无关。现代或旧式都没关系,因为这里的问题是服务器而不是浏览器。

解决方案当然是指定正确的编码并确保Content-Type在响应的 HTTP 标头中指定相同的编码:

echo htmlspecialchars($_GET["query"], ENT_COMPAT | ENT_HTML401, "UTF-8");

示例#2:浏览器启发式攻击你

如果您的服务器没有指定它在响应中使用的编码(或者如果它只在一个远低于浏览器关心它的元标记中使用它),那么这是一个问题。如果你不告诉浏览器使用什么编码,它就不得不猜测。不幸的是,所有浏览器都不是那么擅长

如果某些用户输入字符串(例如,+ADw-script+AD4-alert(1)+ADw-/script+AD4-)在 HTML 页面中足够早地回显,Internet Explorer 可能会错误地猜测该页面是用 UTF-7 编码的。突然间,原本无害的用户输入变成了活跃的 HTML 并开始执行。

引用中的有效负载以<script>alert(1)</script>UTF-7 编码。以 UTF-8 工作的消毒剂不会在该有效负载中看到任何危险并让它通过,但被欺骗以 UTF-7 工作的浏览器仍会运行它。

我的理解是,这是一个问题的主要是旧版本的 IE 。但我不确定,所以我很高兴看到另一个答案得到澄清。

编辑:有关它适用于现代浏览器的情况,请参阅Xavier59 的答案。

解决方案

理论上,您需要在服务器上执行的操作很简单。您需要确保以下内容始终为真:

  • 响应的字符编码在 HTTP 标头中正确设置。
  • XSS 过滤器以与上面指定的相同编码工作。

在实践中,很容易出错。

这是对安德斯答案的补充(顺便说一句)。

我的理解是,这是一个问题的主要是旧版本的 IE。但我没有这方面的来源,我也不确定,所以我很高兴看到另一个答案得到澄清。

是的,这会影响现代浏览器。


让我们进行以下消毒:

<?php
    header('Content-Type: text/html;charset=utf-8');
    echo preg_replace('/<\w+/', '', $_GET['name']).", can you p0wn it ?"
?>

这似乎并不容易受到攻击,因为:

  • <后跟一个或多个字母被删除,因此攻击者无法打开新标签。
  • Content-Type标头正确设置为utf-8

现在,假设我们发送%00%3C%00,正则表达式解析器将失败,因为<( %3C) 后面不是字母(由 定义\w)而是%00(空字节)。UTF-8中,反射的输入不会执行任何操作,但如果我们能找到一种方法将其反映在UTF-16...

以下是我们可以从 W3 中读取的内容

如果文件开头有 UTF-8 字节顺序标记 (BOM),则 Internet Explorer 10 或 11 以外的最新浏览器版本将使用它来确定页面的编码是 UTF-8。它的优先级高于任何其他声明,包括 HTTP 标头。

如果您有 BOM,您可以跳过元编码声明,但我们建议您保留它,因为它可以帮助人们查看源代码来确定页面的编码是什么。

中的BOM字符UTF-16是 unicode 字符U+FEFF(不同的BOM编码最好在Wikipedia上进行描述)。因此,因为我们的输入反映在 . 的开头dom,我们可以将字符集更改为UTF-16并让我们的代码执行。

完整的有效载荷:

%FE%FF%00%3C%00s%00c%00r%00i%00p%00t%00%3E%00a%00l%00e%00r%00t%00(%00%22%00P%000%00w%00n%00e%00d%00%22%00)%00;%00%3C%00/%00s%00c%00r%00i%00p%00t%00%3E

这是我制作的 POC。大多数 xss 审核员不会喜欢它,但 Firefox 会因为它的审核员默认禁用。(在 Firefox Nightly 60.0a1 上测试 - 截至今天的最新版本)

不过,htmlspecialchars也不htmlentities会上当。尽管如此,这表明总会有棘手的边缘情况!


其他对编码的攻击包括字符映射,直到今天仍然相关。

OWASP XSS页面:

“跨站点脚本攻击是一种注入问题,其中恶意脚本被注入到其他良性和受信任的网站中。当攻击者使用 Web 应用程序发送恶意代码时,通常会发生跨站点脚本 (XSS) 攻击以浏览器端脚本的形式发送给不同的最终用户。允许这些攻击成功的缺陷非常普遍,并且发生在 Web 应用程序在其生成的输出中使用来自用户的输入而不对其进行验证或编码的任何地方。

攻击者可以使用 XSS 向毫无戒心的用户发送恶意脚本。最终用户的浏览器无法知道该脚本不应被信任,并将执行该脚本。因为它认为脚本来自受信任的来源,所以恶意脚本可以访问您的浏览器保留并与该站点一起使用的任何 cookie、会话令牌或其他敏感信息。这些脚本甚至可以重写 HTML 页面的内容。”

这是您不清理用户输入的不良编码实践的示例。

假设您是一名 Web 开发人员,并且您在您的网站 ( name.php) 中创建了这个文件:

<form action="" method="GET">
  What is your name: <input type="text" name="username"><br>
  <input type="submit" value="Submit">
</form>

<?php
  print("Entered name is: ".$_GET["username"]);
?>

在浏览器上打开此页面时,您将看到如下内容:

欢迎页面,要求用户名稍后打印

让我们给这个简单的文件起个名字,看看它的行为,当我们使用GET方法时,我们将能够在 URL 上看到发送的数据:

带有示例名称的欢迎页面,而非恶意输入

但是,如果有人试图在这个input框中注入一些 HTML 代码会发生什么,比如

<marquee><h1>Andrew ng</h1></marquee>

请参阅下图中的结果:

未清理字段中的代码注入

用户的输入被呈现为好像它是文件原始源代码的一部分。

现在如果我们用 Javascript 代码尝试同样的事情,让我们看看会发生什么,在浏览器上测试的注入代码将是 2 种 XSS 方式:

<h1>Andrew</h1><script>alert("XSS");</script>

<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg">

在这两种情况下,谷歌浏览器都阻止了这个脚本的执行: Chrome 中的第一次 XSS 尝试

Chrome 中的第二次 XSS 尝试

但是,在 Mozilla Firefox 中,两个脚本都成功执行:

Firefox 中的第一次 XSS 尝试

Firefox 中的第二次 XSS 尝试

希望这可以让您更好地了解 XSS 和现代浏览器的现状,这是在以下环境中测试的:

  • Google Chrome 64.0.3282.119(官方版本)(64 位)
  • Mozilla Firefox Quantum 58.0(64 位)

关于功能,您可以在此处htmlspecialchars()找到更多信息

您可能感兴趣的其他 XSS 示例是我的博客中的这个。

希望能帮助到你。