Network Scanning Based on TCP

概述

TCP 协议的很多特性可以被攻击者广泛利用,如进行网络扫描和端口嗅探。基于 TCP 的常用的端口扫描类型包括:TCP 连接扫描、TCP SYN扫描、TCP Xmas 扫描、TCP FIN 扫描、TCP Null 扫描等等。

我们将使用 Scapy 第三方库来做实际的扫描测试。Scapy 可以用来做 package 嗅探和伪造 package,并且它已经在内部实现了大量的网络协议。

TCP 连接扫描

该方式尝试通过与目标主机上的待扫描 TCP 端口建立完整的 TCP 连接,根据连接的成败推断端口的工作状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from scapy.all import *
from scapy.layers.inet import TCP, IP

data = 'hello, world'

## 构造 package
pkt = IP(src = '127.0.0.1', dst = '127.0.0.1')/TCP(sport=12345, dport=8088)/data
resp = sr1(pkt, timeout=10)
if str(type(resp)) == '':
print("Closed")
elif resp.haslayer(TCP):
if resp.getlayer(TCP).flags == 0x12: # SYN+ACK
# 发送 ACK
sr(IP(src = '127.0.0.1', dst = '127.0.0.1')/TCP(sport=12345, dport=8088, flags="AR"), timeout=10)
print("Opened")
elif resp.getlayer(TCP).flags == 0x14: # RST+ACK
print("Closed")
else:
print("Closed")

TCP SYN 扫描

该类型扫描是向主机上的待扫描端口发送一个 SYN 标志被设置为 1 的 TCP 报文, 此动作与 TCP 的三次握手中的第一阶段相同。如果被扫描的端口处于监听状态,它将返回 SYN+ACK,响应连接请求
源主机在收到响应报文后,发送 RST 标志为 1 的报文,中断与主机的连接。如果被扫描主机的端口是关闭的,则会回复 RST 为 1 的报文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from scapy.all import *
from scapy.layers.inet import TCP, IP

pkt = IP(src = '127.0.0.1', dst = '127.0.0.1')/TCP(sport=12345, dport=8088, flags="S")
resp = sr1(pkt, timeout=10)
if str(type(resp)) == "":
print("Filtered")
elif resp.haslayer(TCP):
if resp.getlayer(TCP).flags == 0x12: # SYN+ACK
# 发送 ACK
sr(IP(src='127.0.0.1', dst='127.0.0.1') / TCP(sport=12345, dport=8088, flags="R"), timeout=10)
print("Opened")
elif resp.getlayer(TCP).flags == 0x14: # RST + ACK
print("Closed")
else:
print("Filtered")

TCP Xmas 扫描(圣诞树扫描)

在该扫描类型中,源主机会向目标主机发送设置 PSH、FIN、URG 标识的数据包。判断逻辑如下:

  • 如果目标主机的端口是开放的,不会有任何响应
  • 如果目标主机返回了 RST 报文,那么说明端口处于关闭状态
  • 如果目标主机返回一个 ICMP 数据包,其中错误类型为 3 且状态码为 1, 2, 3, 9, 10 或 13, 则说明目标端口被过滤了无法确定是否处于开放状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from scapy.all import *
from scapy.layers.inet import TCP, IP, ICMP

pkt = IP(src = '127.0.0.1', dst = '127.0.0.1')/TCP(sport=12345, dport=8088, flags="FPU")
resp = sr1(pkt, timeout=5)
if str(type(resp)) == "" or resp is None:
print("Open|Filtered")
elif resp.haslayer(TCP):
if resp.getlayer(TCP).flags == 0x14:
print("Closed")
elif resp.haslayer(ICMP):
if (int(resp.getlayer(ICMP).type) == 3 and
int(resp.getlayer(ICMP).code) in [1, 2, 3, 9, 10, 13]):
print("Filtered")
else:
print("Filtered")

TCP ACK 扫描

ACK 扫描不是用于发现端口开启或关闭状态的,而是用于发现服务器上是否存在有状态防火墙的。它的结果只能说明端口是否被过滤。

TCP 窗口扫描

TCP 窗口扫描的流程同 ACK 扫描类似,同样是客户端向服务器发送一个带有 ACK 标识和端口号的 TCP 数据包,但是这种扫描能够用于发现目标服务器端口的状态。在 ACK 扫描中返回 RST 表明没有被过滤,但在窗口扫描中,当收到返回的 RST 数据包后,它会检查窗口大小的值。

  • 如果窗口大小的值是个非零值,则说明目标端口是开放的。
  • 如果返回的 RST 数据包中的窗口大小为0,则说明目标端口是关闭的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from scapy.all import *
from scapy.layers.inet import TCP, IP, ICMP

pkt = IP(src = '127.0.0.1', dst = '127.0.0.1')/TCP(sport=12346, dport=8089, flags="A")
resp = sr1(pkt, timeout=10)
if str(type(resp)) == "" or resp is None:
print("No Response")
elif resp.haslayer(TCP):
if resp.getlayer(TCP).window == 0:
print("Closed")
elif resp.getlayer(TCP).window > 0:
print("Opened")
elif resp.haslayer(ICMP):
if (int(resp.getlayer(ICMP).type) == 3 and
int(resp.getlayer(ICMP).code) in [1, 2, 3, 9, 10, 13]):
print("Filtered")
else:
print("Filtered")

参考 端口扫描之开放端口扫描方式