This commit is contained in:
wizardforcel 2016-11-25 16:04:06 +08:00
parent b333207579
commit 11c263284b
1 changed files with 384 additions and 0 deletions

384
ch2.md
View File

@ -1215,3 +1215,387 @@ root@KaliLinux:~# cat output.txt
### 工作原理
我们需要进行一些调整,才能使用`hping3`对多个主机或地址范围执行主机发现。 提供的秘籍使用 bash 脚本顺序执行 ICMP 回应请求。 这是可性的,因为成功和不成功的请求能够生成唯一响应。 通过将函数传递给一个循环,并将唯一响应传递给`grep`,我们可以高效开发出一个脚本,对多个系统依次执行 ICMP 发现,然后输出活动主机列表。
## 2.11 使用 Scapy 探索第四层
多种不同方式可以用于在第四层执行目标发现。可以使用用户数据报协议UDP或传输控制协议TCP来执行扫描。 Scapy 可以用于使用这两种传输协议来制作自定义请求,并且可以与 Python 脚本结合使用以开发实用的发现工具。 此秘籍演示了如何使用 Scapy 执行 TCP 和 UDP 的第四层发现。
### 准备
使用 Scapy 执行第三层发现不需要实验环境,因为 Internet 上的许多系统都将回复 TCP 和 UDP 请求。但是,强烈建议你只在您自己的实验环境中执行任何类型的网络扫描,除非你完全熟悉您受到任何管理机构施加的法律法规。如果你希望在实验环境中执行此技术,你需要至少有一个响应 TCP/UDP 请求的系统。在提供的示例中,使用 Linux 和 Windows 系统的组合。有关在本地实验环境中设置系统的更多信息,请参阅第一章中的“安装 Metasploitable2”和“安装 Windows Server”秘籍。此外本节还需要使用文本编辑器如 VIM 或 Nano将脚本写入文件系统。有关编写脚本的更多信息请参阅第一章中的“使用文本编辑器VIM 和 Nano”秘籍。
### 操作步骤
为了验证从活动主机接收到的 RST 响应,我们可以使用 Scapy 向已知的活动主机发送 TCP ACK 数据包。 在提供的示例中ACK 数据包将发送到 TCP 目标端口 80。此端口通常用于运行 HTTP Web 服务。 演示中使用的主机当前拥有在此端口上运行的 Apache 服务。 为此,我们需要构建我们的请求的每个层级。 要构建的第一层是IP层。 看看下面的命令:
```
root@KaliLinux:~# scapy Welcome to Scapy (2.2.0)
>>> i = IP()
>>> i.display()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= ip
chksum= None
src= 127.0.0.1
dst= 127.0.0.1
\options\
>>> i.dst="172.16.36.135"
>>> i.display()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= ip
chksum= None
src= 172.16.36.180
dst= 172.16.36.135
\options\
```
这里,我们将`i`变量初始化为`IP`对象,然后重新配置标准配置,将目标地址设置为目标服务器的 IP 地址。 请注意,当为目标地址提供除回送地址之外的任何 IP 地址时,源 IP 地址会自动更新。 我们需要构建的下一层是我们的 TCP 层。 这可以在以下命令中看到:
```
>>> t = TCP()
>>> t.display()
###[ TCP ]###
sport= ftp_data
dport= http
seq= 0
ack= 0
dataofs= None
reserved= 0
flags= S
window= 8192
chksum= None
urgptr= 0
options= {}
>>> t.flags='A'
>>> t.display()
###[ TCP ]###
sport= ftp_data
dport= http
seq= 0
ack= 0
dataofs= None
reserved= 0
flags= A
window= 8192
chksum= None
urgptr= 0
options= {}
```
这里,我们将`t`变量初始化为`TCP`对象。 注意,对象的默认配置已经将目标端口设置为 HTTP 或端口 80。这里我们只需要将 TCP 标志从`S`SYN更改为`A`ACK。 现在,可以通过使用斜杠分隔每个层级来构建栈,如以下命令中所示:
```
>>> request = (i/t)
>>> request.display()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= tcp
chksum= None
src= 172.16.36.180
dst= 172.16.36.135
\options\
###[ TCP ]###
sport= ftp_data
dport= http
seq= 0
ack= 0
dataofs= None
reserved= 0
flags= A
window= 8192
chksum= None
urgptr= 0
options= {}
```
这里,我们将整个请求栈赋给`request`变量。 现在,可以使用`send `和`recieve`函数跨线路发送请求,然后可以评估响应来确定目标地址的状态:
```
>>> response = sr1(request)
Begin emission:
.......Finished to send 1 packets.
....*
Received 12 packets, got 1 answers, remaining 0 packets
>>> response.display()
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 40
id= 0
flags= DF
frag= 0L
ttl= 64
proto= tcp
chksum= 0x9974
src= 172.16.36.135
dst= 172.16.36.180
\options\
###[ TCP ]###
sport= http
dport= ftp_data
seq= 0
ack= 0
dataofs= 5L
reserved= 0L
flags= R
window= 0
chksum= 0xe21
urgptr= 0
options= {}
###[ Padding ]###
load= '\x00\x00\x00\x00\x00\x00'
```
请注意,远程系统使用设置了 RST 标志的 TCP 数据包进行响应。 这由分配给`flags`属性的`R`值表示。 通过直接调用函数,可以将堆叠请求和发送和接收响应的整个过程压缩为单个命令:
```
>>> response = sr1(IP(dst="172.16.36.135")/TCP(flags='A'))
.Begin emission:
................
Finished to send 1 packets.
....*
Received 22 packets, got 1 answers, remaining 0 packets
>>> response.display()
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 40
id= 0
flags= DF
frag= 0L
ttl= 64
proto= tcp
chksum= 0x9974
src= 172.16.36.135
dst= 172.16.36.180
\options\
###[ TCP ]###
sport= http
dport= ftp_data
seq= 0
ack= 0
dataofs= 5L
reserved= 0L
flags= R
window= 0
chksum= 0xe21
urgptr= 0
options= {}
###[ Padding ]###
load= '\x00\x00\x00\x00\x00\x00'
```
现在我们已经确定了与发送到活动主机上的打开端口的 ACK 数据包相关联的响应,让我们尝试向活动系统上的已关闭端口发送类似的请求,并确定响应是否有任何变化:
```
>>> response = sr1(IP(dst="172.16.36.135")/TCP(dport=1111,flags='A'))
.Begin emission:
.........
Finished to send 1 packets.
....*
Received 15 packets, got 1 answers, remaining 0 packets
>>> response.display()
###[ IP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 40
id= 0
flags= DF
frag= 0L
ttl= 64
proto= tcp
chksum= 0x9974
src= 172.16.36.135
dst= 172.16.36.180
\options\
###[ TCP ]###
sport= 1111
dport= ftp_data
seq= 0
ack= 0
dataofs= 5L
reserved= 0L
flags= R
window= 0
chksum= 0xa1a
urgptr= 0
options= {}
###[ Padding ]###
load= '\x00\x00\x00\x00\x00\x00'
```
在此请求中,目标 TCP 端口已从默认端口 80 更改为端口 1111未在其上运行服务的端口。 请注意,从活动系统上的打开端口和关闭端口返回的响应是相同的。 无论这是否是在扫描端口上主动运行的服务,活动系统都会返回 RST 响应。 另外,应当注意,如果将类似的扫描发送到与活动系统无关的 IP 地址,则不会返回响应。 这可以通过将请求中的目标 IP 地址修改为与实际系统无关的 IP 地址来验证:
```
>>> response = sr1(IP(dst="172.16.36.136")/TCP(dport=80,flags='A'),timeo ut=1)
Begin emission:
......................................................................... ......................................................................... ......
Finished to send 1 packets.
.....................
Received 3559 packets, got 0 answers, remaining 1 packets
```
因此,通过查看,我们发现对于发送到活动主机任何端口的 ACK 数据包,无论端口状态如何,都将返回 RST 数据包,但如果没有活动主机与之相关,则不会从 IP 接收到响应。 这是一个好消息,因为它意味着,我们可以通过只与每个系统上的单个端口进行交互,在大量系统上执行发现扫描。 将 Scapy 与 Python 结合使用,我们可以快速循环访问`/ 24`网络范围中的所有地址,并向每个系统上的仅一个 TCP 端口发送单个 ACK 数据包。 通过评估每个主机返回的响应,我们可以轻易输出活动 IP 地址列表。
```py
#!/usr/bin/python
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *
if len(sys.argv) != 2:
print "Usage - ./ACK_Ping.py [/24 network address]"
print "Example - ./ACK_Ping.py 172.16.36.0"
print "Example will perform a TCP ACK ping scan of the 172.16.36.0/24 range"
sys.exit()
address = str(sys.argv[1])
prefix = address.split('.')[0] + '.' + address.split('.')[1] + '.' + address.split('.')[2] + '.'
for addr in range(1,254):
response = sr1(IP(dst=prefix+str(addr))/TCP(dport=80,flags='A'), timeout=1,verbose=0)
try:
if int(response[TCP].flags) == 4:
print "172.16.36."+str(addr)
except:
pass
```
提供的示例脚本相当简单。 当循环遍历 IP 地址中的最后一个八位字节的每个可能值时ACK 封包被发送到 TCP 端口 80并且评估响应来确定响应中的 TCP 标志的整数转换是否具有值`4` (与单独 RST 标志相关的值)。 如果数据包具有 RST 标志,则脚本将输出返回响应的系统的 IP 地址。 如果没有收到响应Python 无法测试响应变量的值,因为没有为其赋任何值。 因此,如果没有返回响应,将发生异常。 如果返回异常,脚本将会跳过。 生成的输出是活动目标 IP 地址的列表。 此脚本可以使用句号和斜杠,后跟可执行脚本的名称执行:
```
root@KaliLinux:~# ./ACK_Ping.py
Usage - ./ACK_Ping.py [/24 network address]
Example - ./ACK_Ping.py 172.16.36.0
Example will perform a TCP ACK ping scan of the 172.16.36.0/24 range
root@KaliLinux:~# ./ACK_Ping.py
172.16.36.1
172.16.36.2
172.16.36.132
172.16.36.135
```
类似的发现方法可以用于使用 UDP 协议来执行第四层发现。 为了确定我们是否可以使用 UDP 协议发现主机,我们需要确定如何从任何运行 UDP 的活动主机触发响应,而不管系统是否有在 UDP 端口上运行服务。 为了尝试这个,我们将首先在 Scapy 中构建我们的请求栈:
```
root@KaliLinux:~# scapy Welcome to Scapy (2.2.0)
>>> i = IP()
>>> i.dst = "172.16.36.135"
>>> u = UDP()
>>> request = (i/u)
>>> request.display()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= udp
chksum= None
src= 172.16.36.180
dst= 172.16.36.135
\options\
###[ UDP ]###
sport= domain
dport= domain
len= None
chksum= None
```
注意UDP对象的默认源和目标端口是域名系统DNS。 这是一种常用的服务,可用于将域名解析为 IP 地址。 发送请求是因为它是有助于判断IP 地址是否与活动主机相关联。 发送此请求的示例可以在以下命令中看到:
```
>>> reply = sr1(request,timeout=1,verbose=1)
Begin emission:
Finished to send 1 packets.
Received 7 packets, got 0 answers, remaining 1 packets
```
尽管与目标 IP 地址相关的主机是活动的,但我们没有收到响应。 讽刺的是,缺乏响应实际上是由于 DNS 服务正在目标系统上使用。这是因为活动服务通常配置为仅响应包含特定内容的请求。 你可能会自然想到,有时可以尝试通过探测未运行服务的 UDP 端口来高效识别主机,假设 ICMP 流量未被防火墙阻止。 现在,我们尝试将同一请求发送到不在使用的不同 UDP 端口:
```
>>> u.dport = 123
>>> request = (i/u)
>>> reply = sr1(request,timeout=1,verbose=1)
Begin emission: Finished to send 1 packets.
Received 5 packets, got 1 answers, remaining 0 packets
>>> reply.display()
###[ IP ]###
version= 4L
ihl= 5L
tos= 0xc0
len= 56
id= 62614
flags=
frag= 0L
ttl= 64
proto= icmp
chksum= 0xe412
src= 172.16.36.135
dst= 172.16.36.180
\options\
###[ ICMP ]###
type= dest-unreach
code= port-unreachable
chksum= 0x9e72
unused= 0
###[ IP in ICMP ]###
version= 4L
ihl= 5L
tos= 0x0
len= 28
id= 1
flags=
frag= 0L
ttl= 64
proto= udp
chksum= 0xd974
src= 172.16.36.180
dst= 172.16.36.135
\options\
###[ UDP in ICMP ]###
sport= domain
dport= ntp
len= 8
chksum= 0x5dd2
```
通过将请求目标更改为端口 123然后重新发送它我们现在会收到一个响应表明目标端口不可达。如果检查此响应的源 IP 地址,你可以看到它是从发送原始请求的主机发送的。此响应随后表明原始目标 IP 地址处的主机处于活动状态。不幸的是在这些情况下并不总是返回响应。这种技术的效率在很大程度上取决于你正在探测的系统及其配置。正因为如此UDP 发现通常比 TCP 发现更难执行。它从来不会像发送带有单个标志的 TCP 数据包那么简单。在服务确实存在的情况下,通常需要服务特定的探测。幸运的是,有各种相当复杂的 UDP 扫描工具,可以使用各种 UDP 请求和服务特定的探针来确定活动主机是否关联了任何给定的IP地址。
### 工作原理
这里提供的示例使用 UDP 和 TCP 发现方式。 我们能够使用 Scapy 来制作自定义请求,来使用这些协议识别活动主机。 在 TCP 的情况下,我们构造了自定义的 ACK 封包并将其发送到每个目标系统上的任意端口。 在接收到 RST 应答的情况下,系统被识别为活动的。 或者,空的 UDP 请求被发送到任意端口,来尝试请求 ICMP 端口不可达响应。 响应可用作活动系统的标识。 然后这些技术中的每一个都可以在 Python 脚本中使用,来对多个主机或地址范围执行发现。