浅析端口扫描原理 您所在的位置:网站首页 tcp全连接扫描原理 浅析端口扫描原理

浅析端口扫描原理

2024-04-21 03:51| 来源: 网络整理| 查看: 265

浅析端口扫描原理

阅读量519031

|评论2

|

发布时间 : 2021-05-27 17:30:39

x译文声明

本文是翻译文章

译文仅供参考,具体内容表达以及含义原文为准。

 

前言

自觉对于端口扫描只是浅显的停留在nmap的几条命令,因此花了点时间了解了一下端口扫描器是如何运作的,并且使用python写了一个简单的端口扫描器,笔者目的不在于替代nmap等主流端扫的作用,只是为了更深入地了解端口扫描器运作的原理。

本文以nmap中常见的扫描技术的原理展开叙述。

 

端口

每一个ip地址可以有2^16=65535个端口,其中分成了两类,一类是TCP,另一类是UDP;TCP是面向连接的,可靠的字节流服务,UDP是面向非连接的,不可靠的数据报服务;在实现端口扫描器时需要针对这两种分别进行扫描。

每一个端口只能被用于一个服务,例如常见的80端口就被用于挂载http服务,3306则是mysql,而nmap中对于端口的定义存在着六种状态:

open(开放的) close(关闭的) filtered(被过滤的) unfiltered(未被过滤的) open|filtered(开放或者被过滤的) closed|filtered(关闭或者被过滤的)

对于这6种状态我在后文中会陆续提到;端口扫描器要做的就是扫出各个端口的状态,并且探测到存活的端口中存在着的服务。

 

TCP

因为扫描TCP端口时需要用到TCP的知识,因此,在此之前有必要再复习一下TCP协议的那些事,也就是TCP的三次握手:

第一次握手:

客户端主动发送SYN给服务端,SYN序列号假设为J(此时服务器是被动接受)。

第二次握手

服务端接受到客户端发送的SYN(J)后,发送一个SYN(J+1)和ACK(K)给客户端。

第三次握手

客户端接受到新的SYN(K)和ACK(J+1)后,也发送一个ACK(K+1)给服务端,此时连接建立,双方可以进行通信。

而发送TCP包的连接状态是由一个名为标志位(flags)来决定的,它存在一下几种:

F : FIN – 结束; 结束会话 S : SYN – 同步; 表示开始会话请求 R : RST – 复位;中断一个连接 P : PUSH – 推送; 数据包立即发送 A : ACK – 应答 U : URG – 紧急 E : ECE – 显式拥塞提醒回应 W : CWR – 拥塞窗口减少

 

UDP

关于UDP的话就不多说拉,只需要知道它是一个无连接协议,因此也是一种不可靠的协议,因为你并不清楚你发的数据是否到达目标。

 

TCP SYN SCAN

对应于nmap中的-sS。

关于SYN前面稍稍提了一下,那么下面对此细节进行展开。

SYN扫描也称为半连接扫描,那么说到这里顺嘴提一下SYN FLOOD,它是利用了TCP协议的缺陷,在客户端伪造源地址发送SYN给服务端时,服务端会响应该SYN并且发送SYN跟ACK包,但因为客户端地址是伪造的,真实请求的客户端不认为该包是其发送的,因此不再响应从服务端从发送来的包,服务端收不到响应会进行重试3-5次并且等待一个SYN Time(一般30秒-2分钟)后,丢弃这个连接,这一过程会消耗服务端的资源,

而当大量伪造源地址的TCP请求使用此种方式去向服务端发送SYN包时,服务端会因为资源的大量消耗导致请求缓慢,此时用户无法正常使用服务。

回到端扫,项目采用的是scapy,那么用它来发送一个SYN包应该怎么做?

找到其文档会发现它已经直截了当地给出了答案:

sr1(IP(dst="101.132.132.179")/TCP(dport=80,flags="S"))

这里flag=”S”也就是将标志位置为SYN,说明此时发送的是SYN,而收到的内容:

Begin emission: Finished sending 1 packets. ...* Received 4 packets, got 1 answers, remaining 0 packets

发现flags=SA,即SYN+ACK,那么如何判断端口是否开放?

文档中给出的方法是判断响应包是否具有SA标识,有则默认端口开放,但是对于我们来说仅仅到这一步并不太满意,会发现nmap文档中早已给出答案:

它发送一个SYN报文, 就像您真的要打开一个连接,然后等待响应。 SYN/ACK表示端口在监听 (开放),而 RST (复位)表示没有监听者。如果数次重发后仍没响应, 该端口就被标记为被过滤。如果收到ICMP不可到达错误 (类型3,代码1,2,3,9,10,或者13),该端口也被标记为被过滤。

概括一下可以分成三种状态,open,closed,filtered,而我们很容易冲描述中判断出对应的行为应该给与什么样的状态:

行为 状态 数次重发未响应 filtered 收到ICMP不可达错误 filtered SYN/ACK open RST closed

对于判断响应是否具有TCP层 or ICMP层,scapy中也贴心的给出了相应的函数(getlayer和haslayer),而在收到SYN-ACK报文后要做的是发送RST报文中断连接而非传统TCP连接中的ACK报文,而当收到RA时表示端口关闭。

这里的RA也可以用16进制表示为0x14,其算法为:

URG=0,ACK=1,PSH=0,RST=1、SYN=0、FIN=0

010100=0x14

一个demo:

def tcp_syn_scan(host,port): send = sr1(IP(dst=host) / TCP(dport=port, flags="S"),retry=-2, timeout=2, verbose=0) if (send is None): # filtered pass elif send.haslayer(TCP): if send.getlayer(TCP).flags == 0x12:#SA sr1(IP(dst=host) / TCP(dport=port, flags="R"), timeout=2, verbose=0) print("[+] %s %d Open" % (host, port)) elif send.getlayer(TCP).flags == 0x14: #RA # closed pass elif send.haslayer(ICMP): if send.getlayer(TCP).type == 3 and send.getlayer(TCP).code in [1, 2, 3, 9, 10, 13]: # filtered pass

 

TCP CONNECT SCAN

对应于nmap中的-sT。

相对于SYN的半连接扫描,TCP CONNECT SCAN又称为全连接扫描,顾名思义这种扫描方式需要与目标建立完整的TCP连接,前面讲SYN扫描时说到了最后发送的是一个RST报文,其是为了躲避防火墙的检测,而在全连接扫描中则会留下记录,全连接扫描可以说是SYN扫描的下位选择,因为他们得到的信息相同,只有在SYN扫描不可用的情况下才会选择全连接扫描。

那么如果端口不开放,服务端同样返回的是一个RST报文,同样的RA。

因为基本上与SYN扫描类似,区别就是需要建立完整的TCP连接,只需改动一行,将R改成AR:

sr1(IP(dst=host) / TCP(dport=port, flags="AR"), timeout=2, verbose=0)

 

TCP Null,FIN,and Xmas扫描

nmap中将这三类扫描置为一类,看看文档中对于这一类扫描的描述:

如果扫描系统遵循该RFC,当端口关闭时,任何不包含SYN,RST,或者ACK位的报文会导致 一个RST返回,而当端口开放时,应该没有任何响应。只要不包含SYN,RST,或者ACK, 任何其它三种(FIN,PSH,and URG)的组合都行

因为三类扫描实际上原理相似,因此只着重讲一个。

首先看到nmap中对于这三类扫描的flags值:

case XMAS_SCAN: pspec->pd.tcp.flags = TH_FIN|TH_URG|TH_PUSH; break; case NULL_SCAN: pspec->pd.tcp.flags = 0; break; case FIN_SCAN: pspec->pd.tcp.flags = TH_FIN; break;

并且它们的回显置为了无回显:

noresp_open_scan = true;

很明显这三种扫描在行为上是一致的,当扫描到端口关闭时服务端会给出一个RST,而当端口开放时会得到一个未响应的结果(因为不设置SYN,RST,或者ACK位的报文发送到开放端口时服务端会丢弃该报文),那么就有如下行为/状态表:

行为 状态 未响应 Open/filtered 返回RST Closed ICMP不可达错误 filtered

这种扫描我在扫描本地时准确率不低,但扫描服务器时因为服务器大部分端口都是filtered,且它没能识别出来是open还是filtered,当然了其优点是比SYN扫描更为隐蔽,且能够躲过一些无状态防火墙和报文过滤路由器。

TCP Null

对应于nmap中的-sN。

Null扫描,不设置任何标志位(tcp标志头是0),那么做个测试,将flags位置空看看会得到什么结果?

当尝试扫描一个开放的端口时,会没有响应;当将该端口关闭后再次扫描,会得到如下结果:

###[ TCP ]### sport = opsession_prxy dport = ftp_data seq = 0 ack = 0 dataofs = 5 reserved = 0 flags = RA window = 0 chksum = 0xfe1c urgptr = 0 options = []

demo:

def tcp_null_scan(host,port): send = sr1(IP(dst=host) / TCP(dport=port, flags=0x00), timeout=10, verbose=0) if (send is None): print("[+] %s %d \033[92m Open/Filtered \033[0m" % (host, port)) elif send.haslayer(TCP): if send.getlayer(TCP).flags == 0x14: # closed pass elif send.haslayer(ICMP): if send.getlayer(TCP).type == 3 and send.getlayer(TCP).code in [1, 2, 3, 9, 10, 13]: # filtered pass FIN SCAN

对应于nmap中的-sF。

与null的差异就是只设置TCP FIN标志位。

其实与null无甚差异,因此扫描代码也只需要将flags位置为F:

sr1(IP(dst=host)/TCP(dport=port,flags="F"),timeout=10,verbose=0) Xmans SCAN

对应于nmap中的-sX。

根据nmap源码中给出也就是设置FIN\URG\PUSH三个标志位,其余遵循先前的行为状态规则。

sr1(IP(dst=host)/TCP(dport=port,flags="FUP"),timeout=10,verbose=0)

 

TCP ACK SCAN

对应于nmap中的-sA。

这种扫描技术无法确定端口的开放性,因为它做的就是发送一个ACK报文,但服务端在收到该报文时,无论端口是否开放(open/closed),只要未被过滤,都会返回一个RST报文,将它们认为是unfiltered,而当扫描一个端口未得到响应时则认为是filtered,同样的ICMP不可达也认为是filtered。

咋一看貌似无法用于确定端口的状态,也确实如此,这种扫描技术实际上是用于发现防火墙的规则,确定它们是有状态的还是无状态的,并且得到filtered的端口。

其行为状态表如下:

行为 状态 收到RST报文 unfiltered(open/closed) 未响应 filtered ICMP不可达 filtered

只设置ACK标志位即可。

demo:

def tcp_ack_scan(host,port): send = sr1(IP(dst=host) / TCP(dport=port, flags="A"), timeout=10, verbose=0) if (send is None): # filtered pass elif send.haslayer(TCP): if send.getlayer(TCP).flags == 0x04: print("[+] %s %d \033[92m Unfiltered \033[0m" % (host, port)) elif send.haslayer(ICMP): if send.getlayer(TCP).type == 3 and send.getlayer(TCP).code in [1, 2, 3, 9, 10, 13]: # filtered pass

 

TCP WINDOW SCAN

对应于nmap中的-sW。

同上一条的ACK扫描一般,窗口扫描也是发送一个ACK报文,但它能够区别出端口的开放(open/closed);现在尝试发送一个ACK报文到目标开放端口,查看一下返回内容:

###[ TCP ]### sport = http dport = ftp_data seq = 2170377956 ack = 1 dataofs = 6 reserved = 0 flags = SA window = 29200 chksum = 0x7761 urgptr = 0 options = [('MSS', 1318)]

有一个名为window的字段,这在前面是被忽略的一个字段,现在再次发送ACK报文到目标关闭的端口,会发现该值为0,因此窗口扫描技术是可以根据window值是正值 or 0来判断目标端口是open还是closed。

当然在大部分情况因为系统不支持的原因,得到的只是一个无响应或者是window值为0,从而令所有端口为filtered或者closed;例如用nmap去扫描的话得到的结果是所有端口都为filtered,而我本地发送报文到本地开放端口往往得到的也是一个window为0的结果,并且nmap文档中也提到了该扫描偶尔会得到一个相反的结果,如扫描1000个端口仅有3个端口关闭,此时这3个端口可能才是开放的端口。

其行为/状态表如下:

行为 状态 window>0 open window=0 closed 未响应 Filtered ICMP不可达 filtered

demo:

def tcp_window_scan(host,port): send = sr1(IP(dst=host) / TCP(dport=port, flags="A"), timeout=2, verbose=0) if type(send) == None: # filtered pass elif send.haslayout(TCP): if send.getlayer(TCP).window > 0: print("[+] %s %d \033[92m Open \033[0m" % (host, port)) elif send.getlayer(TCP).window == 0: # closed elif send.haslayer(ICMP): if send.getlayer(TCP).type == 3 and send.getlayer(TCP).code in [1, 2, 3, 9, 10, 13]: # filtered pass

 

TCP Maimon SCAN

对应于nmap中的-sM。

该扫描技术本人没使用过,也是看nmap文档才了解到,首先该扫描技术与前面提到的Null,FIN,Xmas扫描一般,可以看到nmap中也是将它与这三种扫描技术归到同一类:

case FIN_SCAN: case XMAS_SCAN: case MAIMON_SCAN: case NULL_SCAN: noresp_open_scan = true;

然而nmap中给它设置的标志位是TH_FIN|TH_ACK,而据文档所述,在RFC 793 (TCP)标准下无论端口开放还是关闭都应该响应RST给这样的探测,然而在许多基于BSD的系统中,如果端口开放,它只是丢弃该探测报文而非响应。

那么其行为/状态表与前面提到的Null等三项扫描技术一致。

send = sr1(IP(dst=host) / TCP(dport=port, flags="AF"), timeout=2, verbose=0)

 

UDP SCAN

对应于nmap中的-sU。

显然UDP扫描相较于TCP扫描而言使用的较少,就我个人来说我也不习惯去扫描UDP端口,而例如常见的DNS,SNMP,和DHCP (注册的端口是53,161/162,和67/68)同样也是一些不可忽略的端口,在某些情况下会有些用处。

同样的在nmap文档中给出了说明:

UDP扫描发送空的(没有数据)UDP报头到每个目标端口。 如果返回ICMP端口不可到达错误(类型3,代码3), 该端口是closed(关闭的)。 其它ICMP不可到达错误(类型3, 代码1,2,9,10,或者13)表明该端口是filtered(被过滤的)。 偶尔地,某服务会响应一个UDP报文,证明该端口是open(开放的)。 如果几次重试后还没有响应,该端口就被认为是 open|filtered(开放|被过滤的)。 这意味着该端口可能是开放的,也可能包过滤器正在封锁通信。

那么整理一下大概是如下:

行为 状态 ICMP端口不可到达错误(类型3,代码3) closed ICMP不可到达错误(类型3, 代码1,2,9,10,或者13) filtered 响应 open 重试未响应 open\ filtered

那么UDP不受欢迎的原因一个在于目前主流的服务大部分基于TCP,而其次便在于UDP扫描的特殊性,在一次扫描过后通常无法得到一个正确的响应,偶尔某服务会返回一个UDP报文说明该端口开放,因此需要进行多次探测;对于ICMP不可到达错误,需要知道的是一般主机在默认情况下限制ICMP端口不可到达消息,如一秒限制发送一条不可达消息。

可以看一下分别对本机的53端口跟一个不存在UDP服务的端口发送UDP报文查看它们的区别。

Open:

###[ UDP ]### sport = domain dport = domain len = 8 chksum = 0x172

closed:

###[ ICMP ]### type = dest-unreach code = port-unreachable chksum = 0xfc44 reserved = 0 length = 0 nexthopmtu= 0 ###[ UDP in ICMP ]### sport = domain dport = ntp len = 8 chksum = 0x0

一如TCP扫描一般,ICMP可以用getlayer(ICMP).type和code来获取到它们对应的状态,那么同样给出一个demo:

def udp_scan(host,port): send = sr1(IP(dst=host) / UDP(dport=port), retry=-2, timeout=2, verbose=0) if (send is None): # filtered pass elif send.haslayer(ICMP): if send.getlayer(ICMP).type == 3: if send.getlayer(ICMP).code in [1, 2, 9, 10, 13]: # filtered pass elif send.getlayer(ICMP).code == 3: # closed pass elif send.haslayer(UDP): print("[+] %s U%d \033[92m Open \033[0m" % (host, port))

 

IP SCAN

对应于nmap中的-sO。

严格来说这并不是端口扫描,IP SCAN所得到的结果是目标机器支持的IP协议 (TCP,ICMP,IGMP,等等)。

协议扫描扫的不是端口,它是在IP协议域的8位上循环,仅发送IP报文,而不是前面使用到的TCP or UDP报文,同时报文头(除了流行的TCP、ICMP、UDP协议外)会是空的,根据nmap中说的是如果得到对应协议的响应则将该协议标记为open, ICMP协议不可到达 错误(类型 3,代号 2) 导致协议被标记为 closed。其它ICMP不可到达协议(类型 3,代号 1,3,9,10,或者13) 导致协议被标记为 filtered (虽然同时他们证明ICMP是 open )。如果重试之后仍没有收到响应, 该协议就被标记为open|filtered。

scapy中可以简单的使用IP来发送默认的256个协议。

ans,unans = sr(IP(dst=host,proto=(0,255))/"SCAPY") ans.show() //0000 127.0.0.1 > 127.0.0.1 ip / Raw ==> 127.0.0.1 > 127.0.0.1 ip / Raw

 

版本探测

对应于nmap的-sV。

同协议探测,版本探测严格来讲不在端口扫描的范围内,但却也互相关联,尽管在探测到常见的80端口,3306端口等等会第一时间辨认出运行在其上的服务,然而有些人的想法是难以猜透的,在10086端口放个web服务也不是不可能的,总会有奇奇怪怪的人在一些奇奇怪怪的端口运行着一些常见的服务。

在扫描到开放的端口后,版本探测所做的就是识别出这些端口对应的服务,在nmap中的nmap-service-probes文件中存放着不同服务的探测报文和解析识别响应的匹配表达式,例如使用nmap进行版本探测,会得到如下结果:

PORT STATE SERVICE VERSION 80/tcp open http nginx 1.14.0 (Ubuntu) 443/tcp open ssl/http nginx 1.14.0 (Ubuntu)

对于版本探测的实现相对简单的方式就是通过无阻塞套接字和探查,然后对响应进行匹配以期得到一个正确的探测结果,在使用nmap探测一个nc监听的端口时会发现收到了一个GET / HTTP/1.0。

那么同样可以使用python的socket发送它来得到服务响应。

ip = "ip" port = 80 PROBE = 'GET / HTTP/1.0\r\n\r\n' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) result = sock.connect_ex((ip, port)) if result == 0: try: sock.sendall(PROBE.encode()) response = sock.recv(256) if response: print(response) except ConnectionResetError: pass else: pass sock.close() //b'HTTP/1.1 200 OK\r\nServer: nginx/1.14.0 (Ubuntu)\r\nDate: Mon

只需要一个合理的正则便够匹配出正确的服务和版本信息。

 

Reference

https://xz.aliyun.com/t/5376#toc-24

https://nmap.org/man/zh/

https://www.osgeo.cn/scapy/

https://www.imooc.com/article/286803

本文翻译自 原文链接。如若转载请注明出处。 商务合作,文章发布请联系 [email protected]

本文由HhhM原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/239641

安全客 - 有思想的安全新媒体

本文转载自:

如若转载,请注明出处:

安全客 - 有思想的安全新媒体

分享到:微信端口扫描+110赞收藏HhhM分享到:微信


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有