Python渗透编程01_Nmap&Scapy上车
1. 既是工具也是强大的三方库
熟悉渗透测试业务的小伙伴想必对Nmap和Scapy两款工具不陌生。Nmap在安全技术发展历史的长河中无疑画上了浓墨重彩的一笔,几乎所有使用过该工具的学习者都惊叹于这款工具具备的强大扫描功能。它能够对目标进行探测,以审计其安全性,目前已经成为几乎所有安全从业者和学习者手中必备工具之一。而Scapy工具就更加接近于底层了,我们可以通过这个模块实现数据包的发送、接收和解析,与nmap不同,Scapy工具仅仅只是返回相应的数据包,而返回结果的含义需要测试者自行分析,因此对于Scapy的使用,需要测试者具备扎实的网络功底。
“人生苦短,我用python。”python语言的优点之一就是其强大的生态----我们可以调用三方库中的函数对功能的实现进行辅助。Nmap和Scapy都是基于python语言开发的工具,其自然也是我们在编程中能够调用的三方库,在python的加持下,我们可以利用者两款工具实现许多操作。
2. Nmap工具的使用
下面对Nmap工具的基础使用进行再入门,这里采用的是kali中自带的nmap,其他环境下需要自行下载脚本进行配置。对于Nmap,无论我们仅仅是将其作为一个工具来使用,还是希望基于Nmap进行二次开发,都需要对其基础功能有一个清晰的认知。本章将分为两个小节进行介绍,第一小节将根据Nmap的官方文档,对其常见命令和参数进行解释;第二小节将在实验环境中展现Nmap的常用功能。
2.1 Nmap常见命令广度列举
目标指定
指定主机名:scanme.nmap.org
指定IP地址:192.168.0.1
指定网段:10.0.0-255.1-254
参数:
-
-iL : 导入一个文件,这个文件中存放着需要扫描目标的list
-
-iR : 随机端口扫描
-
–exclude <host1[,host2][,host3],…>: 排除这些目标不扫描
-
–excludefile <exclude_file>: 排除文本中的目标不扫描
主机探测技术:
-
-sL: 列出扫描地址
-
-sn: Ping 扫描,这里不做端口扫描,就是ping一下,看看ip是否在线
-
-Pn: 过滤没有返回信息的主机地址,防止因为各种限制漏掉主机
-
-PS:SYN扫描
-
-PA:ACK扫描.
-
-PU:UDP扫描
-
-PE/PP/PM: ICMP 诊断/ 时间戳/ 使用ICMP掩码进行探测
-
-PO[protocol list]: 根据IP协议进行扫描;
-
-n:不做DNS解析
-
-R:做DNS解析
-
–dns-servers <serv1[,serv2],…>: 指定DNS服务器
-
–system-dns: 指定使用系统默认的DNS服务器
-
–traceroute:跟踪路由,查看域名沿途的服务器
扫描技术
- -sS:TCP SYN半开扫描,扫描速度较快
- -sT:connect()扫描,因为是基于完整TCP连接建立的,所以相对更加准确
- -sA:TCP ACK扫描, 检测目标主机哪些端口被防火墙屏蔽 ,防止目标上的DNS反向解析需要搭配"-n"使用
- -sU:基于UDP的扫描
- -sN:TCP空包探测扫描
- -sF:FIN扫描
- -sX: Xmas 扫描
- -sI <zombie host[:probeport]>: 使用僵尸机进行扫描
- -sY/sZ: SCTP协议扫描/COOKIE-ECHO 扫描
- -sO: IP 协议扫描
- -b : FTP 扫描
指定端口&扫描命令
-
-p : 仅扫描指定端口
-
–exclude-ports : 指定不扫描端口
-
-F: 快速扫描,仅扫描一些特殊端口
-
-r: 不随机的连续扫描一些端口
-
–top-ports : 扫描<n个> 最常见的端口,不写就扫描10个最常见的端口
-
–port-ratio : 扫描大于<n%>的常见端口
服务/版本探测
- -sV:根据端口匹配服务,例如3306匹配mysql
–version-intensity : 设置扫描强度(1~9级)
–version-light:2级扫描强度
–version-all: 9级扫描强度
–version-trace: 展示扫描细节
脚本引擎
- -sC: 等价于 --script=default,也就是采用默认脚本进行扫描
- –script=: 采用指定脚本进行扫描
- –script-trace: 展示脚本扫描的细节
- –script-updatedb: 升级脚本库
操作系统检测
- -O: 检测操作系统
- –osscan-limit: 检测特定对象
- –osscan-guess: 操作系统猜测
WAF绕过和欺骗技术
-
-f; --mtu : 数据包分片指定每个数据包的MTU (optionally w/given MTU)
-
-D <decoy1,decoy2[,ME],…>: 使用decoy对扫描进行伪装
-
-S <IP_Address>: 伪装源IP
-
-e : 指定界面
-
-g/–source-port : 使用给定的源端口号
-
–proxies <url1,[url2],…>: 使用代理节点
-
–data : 增加一个客户载荷进行数据包的发送
-
–data-string : 增加一个客户ASCLL码进行数据包的发送
-
–data-length : 增加随机长度的数据包进行发送
-
–ip-options : 指定特定的IP选项进行数据包的发送
-
–ttl : 设置IP的TTL
-
–spoof-mac <mac address/prefix/vendor name>: 伪装MAC地址
-
–badsum: 使用伪造的 TCP/UDP/SCTP 校验和进行发送
其他
- -6: IPV6检测
- -A: 全方位扫描
2.2 Nmap实用命令演示
nmap -sn address
nmap -sP address
用ping,但TCP不一定会禁用,因此在探测主机存活情况时可以采用-sn

如图案例中,目标服务器存活
nmap -p num_port address
对熟知应用端口进行扫描,确定服务是否存在:

如图案例中,确定主机存在MySQL服务
nmap -sS -p 1-65535 -p address
如果不需要全面扫描,就修改-p后的端口范围
【较为耗时,不展示扫描结果】
nmap -O address
探测结果并非完全可靠,仅作参考,案例中机器是WIN10操作系统,但是工具模糊猜测了WIN的其他版本。

nmap -v -A address
【完整报告不展示】

nmap -Pn -A -n address
不少服务器都是禁用Ping的,因此采用-Pn组合其他参数进行扫描,-n禁用DNS解析,防止反向解析。部分结果如下

3. python-nmap模块的使用
经过上述介绍,阅读和观察扫描结果,可以发现Nmap最主要的功能可以归纳成如下四点:
- 主机存活探测:目标是否在线
- 端口扫描:向端口发送数据包,根据返回判断状态
- 服务类型和版本判断
- 操作系统检测
除了以上四项常见功能外,Nmap具备许多实用渗透功能(WAF绕过、隐蔽扫描等),当我们想要自定义一些操作时,就可以选择接收python和nmap的双重加持,开发属于我们自己的漏扫工具。由于python-nmap库是三方库,因此需要在终端中执行:pip install python-nmap。另外需要注意,这个库是基于nmap工具的二次开发,因此本机需要安装nmap。kali下自带nmap工具,如果是在win下进行编程,需要实现安装namp工具http://www.nmap.com.cn/(跟随引导就好,基本无脑安装,安装程序自动配置环境变量,比较人性化)。
3.1 PortScanner类
PortScanner类是Nmap的核心,其中封装了Nmap的功能,通过这个类的实例化和方法调用,可以在程序中实现Nmap的操作。根据PortScanner的函数语法特性,个人将其分成两类,一类用法和原生的python语法相似,个人称为“原生形态”,另外一类由于语法中用中括号指定了一些参数,所以我将其称为“方框形态”。
3.1.1“原生形态”下的常用函数
- scan( )函数
scan(self,tar,port,argu,sudo)
该函数中需要指定三个参数:tar(扫描对象),port(端口号),argu(扫描命令)
当如下情况时
tar = '192.168.124.23'
port = '80'
argu = '-sS'
其实就相当于nmap工具中执行:nmap -p 80 -sS 192.168.124.23
- all_hosts( )函数
在scan函数执行后,执行all_host()函数,会返回一个所有被扫描主机的list,如下代码
import nmap
#PortScanner的实例化
nm = nmap.PortScanner()
tar = '192.168.124.23'
port = '80'
argu = '-sS'
nm.scan(tar,port,argu)
print(nm.all_hosts())
返回结果为
['192.168.124.23']
但一般来说,这个函数用于扫描C段(同一网段)存活主句,因此更加实际的写法是
import nmap
#PortScanner的实例化
nm = nmap.PortScanner()
tar = '192.168.124.23/24'
nm.scan(tar,ports=None,arguments="-sP")
print(nm.all_hosts())
返回结果就是C段中存活主机的主机号
['192.168.124.1', '192.168.124.13', '192.168.124.2', '192.168.124.21', '192.168.124.23']
- has_host()函数
依然是扫描C段的场景下,现在我们需要知道C段中特定主机是否存活,就可以采用has_host函数进行,例如我们需要知道当前C段中192.168.124.1是否存活,代码就能够这样设计:
import nmap
#PortScanner的实例化
nm = nmap.PortScanner()
tar = '192.168.124.23/24'
nm.scan(tar,ports=None,arguments="-sP")
print(nm.has_host('192.168.124.1'))
返回结果
True
表示,该主机存活
- command_line()函数
这个函数可以让我们知道当前终端执行的命令,更好的学习nmap语法
import nmap
#PortScanner的实例化
nm = nmap.PortScanner()
tar = '192.168.124.23/24'
nm.scan(tar,ports=None,arguments="-sP")
print(nm.command_line())
返回结果:
nmap -oX - -sP 192.168.124.23/24
3.1.2“括号形态”下的常用函数
主机状态相关
#获取主机名
nmap.PortScanner()[tar].hostname()
#获取主机状态
nmap.PortScanner()[tar].state()
获取协议相关所有端口号
#获取执行的协议
nmap.PortScanner()[tar].all_protocols()
#获取SCTP所有端口
nmap.PortScanner()[tar].all_sctp()
#获取IP所有端口
nmap.PortScanner()[tar].all_ip()
#获取UDP所有端口
nmap.PortScanner()[tar].all_udp()
#获取TCP所有端口
nmap.PortScanner()[tar].all_tcp()
指定检查端口状态
#获取22端口所有信息
nmap.PortScanner()[tar]['tcp'][22]
#获取22端口状态信息
nmap.PortScanner()[tar]['tcp'][22]['state']
案例
import nmap
#指定目标
tar = '192.168.124.23'
#实例化类
nm = nmap.PortScanner()
#指定半开扫描命令
nm.scan(tar,ports="1-500",arguments="-sS")
#获取当前主句状态
print(nm[tar].state())
#获取当前协议
print(nm[tar].all_protocols())
#获取当前TCP端口
print(nm[tar].all_tcp())
#获取当前22端口信息
print(nm[tar]['tcp'][22])
返回结果
up
['tcp']
[22, 80, 139, 143, 443, 445]
{'state': 'open', 'reason': 'syn-ack', 'name': 'ssh', 'product': '', 'version': '', 'extrainfo': '', 'conf': '3', 'cpe': ''}
3.2 PortScannerAsync类
PortScannerAsync类和PortScanner类的作用相似。但在实际业务中我们需要用到异步扫描,而PortScannerAsync类就能很好的支持异步,该类的核心也是scan函数。与PortScanner不同的是,该类中的scan函数中多了一个回调参数:scan(self,tar,port,arg,call_back,sudo)。scan函数的语法与Portscanner类相似。
still_scanning()函数
这个函数用于查看当前扫秒状态,如果扫描正在进行,那么返回值为True,否则返回值为False。
stop()函数
该函数用于停止当前扫描
wait()函数
wait(self,delay),其中delay参数用于控制延迟时间,例如wait(2)表示延时两秒
3.3 一个简单的Nmap扫描器
现在考虑如下需求,编写程序,收集C段中主机存活情况,端口协议信息
代码如下:
import nmap
#方法实例化
nm = nmap.PortScanner()
#指定目标
targ = input("请输入目标:")
port = input('请输入目标端口:')
#半开扫描获取信息
nm.scan(targ,port)
host_list=nm.all_hosts()
for h in host_list:
state=nm[h].state()
print(f"当前主机:{h} 的状态是:{state}")
protos = nm[h].all_protocols()
for p in protos:
port_state = nm[h][p][int(port)]["state"]
print(f'端口{port} 状态{port_state}\n 协议:{p}')
print("="*40)
运行结果

4.Scapy工具的使用
Scapy在内部已经实现了大量的网络协议,利用其编写工具需要一定的网络功底。相比于Nmap的服务到位,Scapy仅仅返回数据包,并不会直接告诉你某个数据包具体代表的含义,因此返回的数据包需要我们自行进行含义的判断,通过它也能让我们更加深入的理解网络理论和网络编程。Kali下自带Scapy,在win下使用需要自行安装,具体可以参考网络资料。介于win下的终端小黑窗不够美观,因此本部分工具演示演示在kali中进行,编程部分在win环境下进行。
4.1 Scapy常见操作
无论win下还是linux下,scapy安装完成后都配置好了环境变量,用户只需要在终端中输入命令:scapy就能够进入交互界面:如图

4.1.1 常见类
Scapy将每个协议封装成类,在scapy中只需要将类实例化(语法和python类似),就能创建一个协议的数据包,如下创建发往192.168.124.23的IP数据包
>>> ip=IP(dst='192.168.124.23')
>>> ip
<IP dst=192.168.124.23 |>
相当于创建了ip这样一个方法,然后调用。在上一部分中,Nmap可以实现C段主机的存活判定,Scapy也同样可以,一种探测思路是向目标发送数据包,根据回应判断主机是否存活,Scapy的IP()类中,dst参数可以是一个明确的地址,也能够是一个网段,示例如下:
>>> ip=IP(dst='192.168.124.23/24') #创建数据包
>>> ip
<IP dst=Net('192.168.124.23/24') |>
>>> [p for p in ip] #展示将发送的数据包
[<IP dst=192.168.124.0 |>,
<IP dst=192.168.124.1 |>,
<IP dst=192.168.124.2 |>,
<IP dst=192.168.124.3 |>,
<IP dst=192.168.124.4 |>,
<IP dst=192.168.124.5 |>,
<IP dst=192.168.124.6 |>,
<IP dst=192.168.124.7 |>,
......
<IP dst=192.168.124.255 |>]
如下命令可以利用Scapy发送ARP广播包
Ether(dst='MAC地址')
Scapy的特性是分层构造数据包,由于Scapy将常用的协议进行了分层,熟悉网络协议的情况下,可以根据协议间的关系调用Scapy中的类,构造数据包。不同层次的协议用”/“进行分割,例如我们要构造一个UDP数据包,scapy中就可以这样写:
>>> udp=Ether()/IP()/UDP()
>>> udp
<Ether type=IPv4 |<IP frag=0 proto=udp |<UDP |>>>
许多文献中,和实际脚本编写中,做常用的几个类就是Ether,IP,TCP,UDP,因此这里重点查看这几个类的属性,其中显示出的参数都能够修改,人为进行指定,例如先前指定的ip地址发送。


4.1.2 常见方法
数据包的发送
- 链路层的发送:sendp(),这个方法工作在数据链路层
>>> ether=Ether()
>>> send(ether)
WARNING: Mac address to reach destination not found. Using broadcast.
WARNING: Mac address to reach destination not found. Using broadcast.
.
Sent 1 packets.
- 网络层的发送:send().这个方法工作在网络层,ip数据报可以采用这种方法发送
>>> ip=IP()
>>> send(ip)
.
Sent 1 packets.
数据包的发送和接收
send()、sendp()都只具备发送功能,并不能接收数据包,接下来介绍的函数既能够满足数据包的收发,根据收到的数据包分析,我们能够判断诸如端口开放情况的一系列信息。
- sr()函数
>>> sr(icmp)
Begin emission:
Finished sending 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
(<Results: TCP:0 UDP:0 ICMP:1 Other:0>,
<Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
- sr1()函数
>>> icmp=IP(dst='192.168.124.23')/TCP()
>>> sr1(icmp)
Begin emission:
Finished sending 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4 ihl=5 tos=0x0 len=28 id=18218 flags= frag=0 ttl=128 proto=icmp chksum=0x9dc6 src=192.168.124.23 dst=192.168.88.136 |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |<Padding load='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |>>>
- sniff()函数
sniff函数用于监听捕获数据,见如下指令:
#filter参数指定主机和协议
>>> sniff(filter="host 192.168.124.1")
>>> sniff(filter="host 127.0.0.1 and icmp")
#iface指定网卡
>>> sniff(iface="eth1")
#count指定捕获n个数据包后停止扫描
>>> sniff(count=3)
下面尝试监听192.168.124.23相关的数据包
>>> sniff(filter="host 192.168.124.23 and icmp",iface='eth0',count=3)
<Sniffed: TCP:0 UDP:0 ICMP:3 Other:0>
>>> a=_
>>> a.nsummary()
0000 Ether / IP / ICMP 192.168.88.136 > 192.168.124.23 echo-request 0 / Raw
0001 Ether / IP / ICMP 192.168.124.23 > 192.168.88.136 echo-reply 0 / Raw
0002 Ether / IP / ICMP 192.168.88.136 > 192.168.124.23 echo-request 0 / Raw
5.python-scapy模块的使用
端口状态扫描器案例,第三方库的安装等不再这里展示,本章将利用Scapy设计一个端口扫描器。
代码如下所示:
from scapy.layers.inet import IP,TCP
from scapy.all import sr,fuzz
host = input("请输入目标主机号:")
port = int(input("请输入端口号:"))
tar = IP(dst=host)/fuzz(TCP(dport=[port],flags="S"))
ans,unans=sr(tar,timeout=1)
for s,r in ans:
if r[TCP].flags == 18:
print(f"{host}主机的端口{port}是开放的")
elif r[TCP].flags == 20:
print(f"{host}主机的端口{port}是关闭的")
flags=18时,代表返回数据包flags的值为0x012 (SYN,ACK),出现SYN,ACK表示连接同步已建立,发送确认帧;
flags=20时,代表返回数据包flags的值为0x014(RST,ACK)代表拒绝连接。上述代码运行效果如下:

到此为止,我们入门了渗透编程的两个重要的模块。
本文介绍了Python在渗透测试中的两个关键工具——Nmap和Scapy。Nmap是一款强大的网络扫描工具,可用于主机探测、端口扫描和服务识别,而Scapy则是一个数据包构造和分析库,允许自定义网络协议和数据包。在Python中,Nmap可以通过python-nmap库进行调用,实现自动化扫描任务,如主机存活探测、端口扫描等。Scapy则能构建并发送各种网络协议的数据包,进行端口状态检测。通过这两个工具,开发者可以构建自己的渗透测试工具。
1548

被折叠的 条评论
为什么被折叠?



