端口扫描的几种方式

Posted by Leo on 2023-10-18
Estimated Reading Time 16 Minutes
Words 3.6k In Total

TCP连接扫描

客户端先发送一个带有 SYN 标识和端口号的 TCP 数据包给服务器,如果端口是开放的,则服务器会接受这个连接并返回一个带有 SYN 和 ACK 标识的数据包给客户端,随后客户端会返回带有 ACK 和 RST 标识的数据包,此时客户端与服务器建立了连接

  • 如果完成一次三次握手,那么服务器上对应的端口肯定就是开放的
  • 如果服务器端返回一个带 RST 标识的数据包,则说明端口处于关闭状态
1
nmap的-sT模式

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#! /usr/bin/python
# 也可以使用socket套接字直接实现

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *

dst_ip = "10.0.0.1"
src_port = RandShort()
dst_port=80

tcp_connect_scan_resp = sr1(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags="S"),timeout=10)
if(str(type(tcp_connect_scan_resp))==""):
print( "Closed")
elif(tcp_connect_scan_resp.haslayer(TCP)):
if(tcp_connect_scan_resp.getlayer(TCP).flags == 0x12):
send_rst = sr(IP(dst=dst_ip)/TCP(sport=src_port,dport=dst_port,flags="AR"),timeout=10)# 全连接 AR => ACK+RST
print( "Open")
elif (tcp_connect_scan_resp.getlayer(TCP).flags == 0x14):
print( "Closed")

TCP SYN 扫描

Nmap的默认扫描方式

客户端向服务器发送一个带有 SYN 标识和端口号的数据包,这种技术主要用于躲避防火墙的检测,且不留下建立连接的记录

  • 如果目标端口开发,则会返回带有 SYN 和 ACK 标识的 TCP 数据包。但是,这时客户端不会返回 RST+ACK 而是返回一个只带有 RST 标识的数据包
  • 如果目标端口处于关闭状态,那么同之前一样,服务器会返回一个 RST 数据包
1
nmap的-sS模式

img

TCP 圣诞树(Xmas Tree)扫描

客户端向服务器发送带有 PSH,FIN,URG 标识和端口号的数据包给服务器

  • 如果目标端口是开放的,那么不会有任何来自服务器的回应
  • 如果服务器返回了一个带有 RST 标识的 TCP 数据包,那么说明端口处于关闭状态
  • 如果服务器返回了一个 ICMP 数据包,其中包含 ICMP 目标不可达错误类型3以及 ICMP 状态码为1,2,3,9,10或13,则说明目标端口被过滤了无法确定是否处于开放状态
1
nmap -sX模式

img

FIN扫描

客户端向服务器发送带有 FIN 标识和端口号的 TCP 数据包

  • 如果没有服务器端回应则说明端口开放
  • 如果服务器返回一个 RST 数据包,则说明目标端口是关闭的
  • 如果服务器返回了一个 ICMP 数据包,其中包含 ICMP 目标不可达错误类型3以及 ICMP 代码为1,2,3,9,10或13,则说明目标端口被过滤了无法确定端口状态
1
nmap -sF模式

img

TCP 空扫描(Null)

客户端发出的 TCP 数据包仅仅只会包含端口号而不会有其他任何的标识信息

  • 如果目标端口是开放的则不会回复任何信息
  • 如果服务器返回了一个 RST 数据包,则说明目标端口是关闭的
  • 如果返回 ICMP 错误类型3且代码为1,2,3,9,10或13的数据包,则说明端口被服务器过滤了
1
nmap -sN模式

img

TCP ACK扫描

客户端会发送一个带有 ACK 标识和端口号的数据包给服务器

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

  • 如果服务器返回一个带有 RST 标识的 TCP 数据包,则说明端口没有被过滤,不存在状态防火墙
  • 如果目标服务器没有任何回应或者返回 ICMP 错误类型3且代码为1,2,3,9,10或13的数据包,则说明端口被过滤且存在状态防火墙

TCP窗口扫描

TCP 窗口扫描的流程同 ACK 扫描类似,同样是客户端向服务器发送一个带有 ACK 标识和端口号的 TCP 数据包,但是这种扫描能够用于发现目标服务器端口的状态

在 ACK 扫描中返回 RST 表明没有被过滤,但在窗口扫描中,当收到返回的 RST 数据包后,它会检查窗口大小的值

  • 如果窗口大小的值是个非零值,则说明目标端口是开放的
  • 如果返回的 RST 数据包中的窗口大小为0,则说明目标端口是关闭的
1
nmap -sW模式

img

UDP扫描

TCP 是面向连接的协议,而UDP则是无连接的协议

面向连接的协议会先在客户端和服务器之间建立通信信道,然后才会开始传输数据。如果客户端和服务器之间没有建立通信信道,则不会有任何产生任何通信数据

无连接的协议则不会事先建立客户端和服务器之间的通信信道,只要客户端到服务器存在可用信道,就会假设目标是可达的然后向对方发送数据

客户端会向服务器发送一个带有端口号的 UDP 数据包

  • 如果服务器回复了 UDP 数据包,则目标端口是开放的
  • 如果服务器返回了一个 ICMP 目标不可达的错误和代码3,则意味着目标端口处于关闭状态
  • 如果服务器返回一个 ICMP 错误类型3且代码为1,2,3,9,10或13的数据包,则说明目标端口被服务器过滤了
  • 如果服务器没有任何相应客户端的 UDP 请求,则可以断定目标端口可能是开放或被过滤的,无法判断端口的最终状态

还有就是一些特殊的UDP回馈,比如SQL Server服务器,对其1434号端口发送“x02”或者“x03”就能够探测得到其连接端口。由于UDP是无连接的不可靠协议,因此这种技巧的准确性 很大程度上取决于与网络及系统资源的使用率相关的多个因素。另外,当试图扫描一个大量应用分组过滤功能的设备时,UDP扫描将是一个非常缓慢的过程。如果 要在互联网上执行UDP扫描,那么结果就是不可靠的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#! /usr/bin/python

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *

dst_ip = "10.0.0.1"
src_port = RandShort()
dst_port=53
dst_timeout=10

def udp_scan(dst_ip,dst_port,dst_timeout):
udp_scan_resp = sr1(IP(dst=dst_ip)/UDP(dport=dst_port),timeout=dst_timeout)
if (str(type(udp_scan_resp))==""):
retrans = []
for count in range(0,3):
retrans.append(sr1(IP(dst=dst_ip)/UDP(dport=dst_port),timeout=dst_timeout))
for item in retrans:
if (str(type(item))!=""):
udp_scan(dst_ip,dst_port,dst_timeout)
return ("Open|Filtered")
elif (udp_scan_resp.haslayer(UDP)):
return( "Open")
elif(udp_scan_resp.haslayer(ICMP)):
if(int(udp_scan_resp.getlayer(ICMP).type)==3 and int(udp_scan_resp.getlayer(ICMP).code)==3):
return( "Closed")
elif(int(udp_scan_resp.getlayer(ICMP).type)==3 and int(udp_scan_resp.getlayer(ICMP).code) in [1,2,9,10,13]):
return( "Filtered")

print udp_scan(dst_ip,dst_port,dst_timeout)

代码部分(半连接、ACK、FIN、Null、Xmas、windows)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
from scapy.all import *
from IPy import IP as IPY

conf.L3socket=L3RawSocket
def sem_port_scan(ip, port):
p = IP(dst=ip) / TCP(dport=int(port), flags="S")
ans = sr1(p, timeout=1, verbose=False)
if ans == None:
print(ip, "\033[0;32;47m\t" + "port", str(port), "is filtered." + "\033[0m")
elif ans[TCP].flags=='SA':
print(ip, "\033[0;32;47m\t" + "port", str(port), "is open." + "\033[0m")
elif ans[TCP].flags == 'RA':
print(ip, "\033[0;32;47m\t" + "port", str(port), "is closed." + "\033[0m")
# ans.display()

def sem_Ping(dest):
ip_addr=IPY(dest)
for ip in ip_addr:
#print(ip)
packet=IP(dst=str(ip))/ICMP()/b'rootkit'
ping=sr1(packet,timeout=1,verbose=False)
if ping:
print("\033[0;32;47m\t"+str(ip)+" is up!"+"\033[0m")
else:
print("\033[0;32;47m\t"+str(ip)+" is down!"+"\033[0m")

def fin_port_scan(ip, port):
p = IP(dst=ip) / TCP(dport=int(port), flags="F")
ans = sr1(p, timeout=1, verbose=False)
if ans == None:
print(ip, "\033[0;32;47m\t" + "port", str(port), "is open." + "\033[0m")
elif ans[TCP].flags == 'RA':
ans.display()
print(ip, "\033[0;32;47m\t" + "port", str(port), "is closed." + "\033[0m")
elif(ans[ICMP]):
if(int(ans[ICMP].type)==3 and int(ans[ICMP].type.code) in [1,2,3,9,10,13]):
print ("Filtered")


def fin_Ping(dest):
ip_addr=IPY(dest)
for ip in ip_addr:
#print(ip)
packet=IP(dst=str(ip))/ICMP()/b'rootkit'
ping=sr1(packet,timeout=1,verbose=False)
if ping:
print("\033[0;32;47m\t"+str(ip)+" is up!"+"\033[0m")
else:
print(str(ip)+" is down!")

def null_port_scan(ip, port):
p = IP(dst=ip) / TCP(dport=int(port), flags="")
ans = sr1(p, timeout=1, verbose=False)
if ans == None:
print(ip, "\033[0;32;47m\t" + "port", str(port), "is open." + "\033[0m")
elif ans[TCP].flags == 'RA':
ans.display()
print(ip, "\033[0;32;47m\t" + "port", str(port), "is closed." + "\033[0m")
elif(ans[ICMP]):
if(int(ans[ICMP].type)==3 and int(ans[ICMP].type.code) in [1,2,3,9,10,13]):
print(ip, "\033[0;32;47m\t" + "port", str(port), "is filtered." + "\033[0m")


def null_Ping(dest):
ip_addr=IPY(dest)
for ip in ip_addr:
#print(ip)
packet=IP(dst=str(ip))/ICMP()/b'rootkit'
ping=sr1(packet,timeout=1,verbose=False)
if ping:
print("\033[0;32;47m\t"+str(ip)+" is up!"+"\033[0m")
else:
print("\033[0;32;47m\t"+str(ip)+" is down!"+"\033[0m")

def windows_port_scan(ip, port):
p = IP(dst=ip) / TCP(dport=int(port), flags="A")
ans = sr1(p, timeout=1, verbose=False)
if ans == None:
print(ip, "\033[0;32;47m\t" + "port", str(port), "is filtered." + "\033[0m")
elif ans[TCP].window > 0:
print(ip, "\033[0;32;47m\t" + "port", str(port), "is open." + "\033[0m")
elif ans[TCP].window == 0:
print(ip, "\033[0;32;47m\t" + "port", str(port), "is closed." + "\033[0m")
# ans.display()


def windows_Ping(dest):
ip_addr=IPY(dest)
for ip in ip_addr:
#print(ip)
packet=IP(dst=str(ip))/ICMP()/b'rootkit'
ping=sr1(packet,timeout=1,verbose=False)
if ping:
print("\033[0;32;47m\t"+str(ip)+" is up!"+"\033[0m")
else:
print("\033[0;32;47m\t"+str(ip)+" is down!"+"\033[0m")

def xmas_port_scan(ip, port):
p = IP(dst=ip) / TCP(dport=int(port), flags="FPU")
ans = sr1(p, timeout=1, verbose=False)
if ans == None:
print(ip, "\033[0;32;47m\t" + "port", str(port), "is open or filtered." + "\033[0m")
elif ans[TCP].flags == 'RA':
ans.display()
print(ip, "\033[0;32;47m\t" + "port", str(port), "is closed." + "\033[0m")
elif(ans[ICMP]):
if(int(ans[ICMP].type)==3 and int(ans[ICMP].type.code) in [1,2,3,9,10,13]):
print(ip, "\033[0;32;47m\t" + "port", str(port), "is filtered." + "\033[0m")


def xmas_Ping(dest):
ip_addr=IPY(dest)
for ip in ip_addr:
#print(ip)
packet=IP(dst=str(ip))/ICMP()/b'leo-port-lab'
ping=sr1(packet,timeout=1,verbose=False)
if ping:
print("\033[0;32;47m\t"+str(ip)+" is up!"+"\033[0m")
else:
print("\033[0;32;47m\t"+str(ip)+" is down!"+"\033[0m")

def ack_port_scan(ip, port):
p = IP(dst=ip) / TCP(dport=int(port), flags="A")
ans = sr1(p, timeout=1, verbose=False)
# print(ans[TCP].flags)
if ans == None:
print(ip, "\033[0;32;47m\t" + "port", str(port), "is filtered. Firewall exists" + "\033[0m")
elif ans[TCP].flags=='R':
print(ip, "\033[0;32;47m\t" + "No firewall" + "\033[0m")
ans.display()
elif(ans[ICMP]):
if(int(ans[ICMP].type)==3 and int(ans[ICMP].code) in [1,2,3,9,10,13]):
print ("Stateful firewall presentn(Filtered)")


def ack_Ping(dest):
ip_addr=IPY(dest)
for ip in ip_addr:
#print(ip)
packet=IP(dst=str(ip))/ICMP()/b'rootkit'
ping=sr1(packet,timeout=1,verbose=False)
if ping:
print("\033[0;32;47m\t"+str(ip)+" is up!"+"\033[0m")
else:
print(str(ip)+" is down!")

if __name__ == '__main__':
if len(sys.argv) > 1:
method = sys.argv[1]
if method.startswith("-"):
method = method[1:] # 去除参数前的 "-"
dest = sys.argv[2]
port = sys.argv[3]
if method == "sem":
print("半连接扫描结果:#########################")
sem_Ping(dest)
sem_port_scan(dest,port)
if method == "ack":
print("ACK结果:#########################")
ack_Ping(dest)
ack_port_scan(dest,port)
if method == "fin":
print("FIN结果:#########################")
fin_Ping(dest)
fin_port_scan(dest,port)
if method == "null":
print("Null扫描结果:#########################")
null_Ping(dest)
null_port_scan(dest,port)
if method == "xmas":
print("Xmas扫描结果:#########################")
xmas_Ping(dest)
xmas_port_scan(dest,port)
if method == "windows":
print("windows扫描结果:#########################")
windows_Ping(dest)
windows_port_scan(dest,port)

IP切片扫描

这种方法并不是直接发送TCP协议探测数据包,而是将数据包分成两个较小的IP协议段。这样就将一个TCP协议头分成好几个数据包,从而过滤器就很难探测到。但必须小心,一些程序在处理这些小数据包时会有些麻烦

高级ICMP扫描技术

Ping是利用ICMP协议实现的,高级的ICMP扫描技术主要 利用ICMP协议最基本的用途——报错。根据网络协议,如果接收到的数据包协议项出现了错 误,那么接收端将产生一个“Destination Unreachable”(目标主机不可达)ICMP的错误报文。这些错误报文不是主动发送的,而是由于错误,根据协议自动产生的。
当IP数据包出现Checksum(校验和)和版本的错误的时候,目标主机将抛弃这个数据包;如果是Checksum出现错误,那么路由器就直接丢弃这个数据包。有些主机比如AIX、HP/UX等,是不会发送ICMP的Unreachable数据包的。
例 如,可以向目标主机发送一个只有IP头的IP数据包,此时目标主机将返回“Destination Unreachable”的ICMP错误报文。如果向目标主机发送一个坏IP数据包,比如不正确的IP头长度,目标主机将返回“Parameter Problem”(参数有问题)的ICMP错误报文。
注意:如果是在目标主机前有一个防火墙或者一个其他的过滤装置, 可能过滤掉提出的要求,从而接收不到任何的回应。这时可以使用一个非常大的协议数字作为 IP头部的协议内容,而且这个协议数字至少在今天还没有被使用,主机一定会返回Unreachable;如果没有Unreachable的ICMP数据包 返回错误提示,那么,就说明被防火墙或者其他设备过滤了,也可以用这个方法探测是否有防火墙或者其他过滤设备存在。


本着互联网开源的性质,欢迎分享这篇文章,以帮助到更多的人,谢谢!