最近想做一個掃描平台,包含主機漏洞掃描這一塊東西,本來主機掃描想接巡風的,但是接進來發現巡風在服務識別這一塊漏報、誤報有點多(應該是我不會用,巡風肯定是沒有問題的)。用的不順手,所以打算換一個。
一、為什么不用nmap等
因為巡風用的不順手想換一個,首先想到的是nmap、masscan等比較成熟的開源工具,看到github上很多人也是直接用的nmap做服務識別的。然后開始測試一下namap,發現還是存在很多問題,比如當mongodb不用默認27017啟動的時候nmap就識別不到具體的服務(可能也是我不太會用,歡迎各位師傅指點)。以下是我自己測試的結果,分別在默認27017、7777端口啟動mongodb服務,用nmap掃描
C:\Users\admin>nmap -p 7777 x.x.x.x Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-20 16:41 Nmap scan report for x.x.x.x Host is up (0.00s latency). PORT STATE SERVICE 7777/tcp open cbt Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds
C:\Users\admin>nmap -p 27017 x.x.x.x Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-20 16:40 Nmap scan report for x.x.x.x Host is up (0.0010s latency). PORT STATE SERVICE 27017/tcp open mongod Nmap done: 1 IP address (1 host up) scanned in 0.26 seconds
二、服務識別三種方式
被動式:只進行TCP連接不發送其他多余數據,用返回的banner信息特征匹配服務
比如我們測試一個端口是否開啟,一般會用telnet
# telnet x.x.x.x 22 Trying x.x.x.x... Connected to x.x.x.x. Escape character is '^]'. SSH-2.0-OpenSSH_7.4
# telnet x.x.x.x 8000 Trying x.x.x.x... Connected to x.x.x.x. Escape character is '^]'.
可以看到22端口比8000端口多返回了一個banner“SSH-2.0-OpenSSH_7.4”,推測22端口開的是ssh服務
主動式:如果是TCP服務識別,先三次握手,然后發送payload,最后用返回的banner信息特征匹配服務,比如mongo。如果是UDP服務識別,因為UDP不需要三次握手建立連接,所以直接發送payload,然后用返回的banner信息特征匹配服務
默認端口式:只探測端口是否開啟,如果存活根據默認端口判斷服務。比如nmap探測到27017端口開了就認定這個是mongodb服務
三、什么是soket
上面講了服務識別的三種方式,涉及到TCP三次握手、TCP/UDP發送payload等步驟,這些步驟都是用soket編程去實現的,下圖介紹soket
在OSI模型中socket可以比作三、四層(網絡層跟傳輸層)的抽象,可以用soket編程實現三、四層或更高層的各種協議。
部分服務識別會用到的soket套接字函數 |
|
s.recv() |
接收TCP數據,數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其他信息,通常可以忽略。 |
s.send() |
發送TCP數據,將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。 |
s.sendall() |
完整發送TCP數據,完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。 |
s.recvfrom() |
接收UDP數據,與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 |
s.sendto() |
發送UDP數據,將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。 |
s.close() |
關閉套接字 |
四、TCP服務識別
部分代碼
# TCP端口探測(TCP服務被動識別) def TcpPort(ip, port): banner = '' status = 0 try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(3) sock.connect((ip, port)) status = 1 except: pass # 被動服務識別 if status: try: banner = sock.recv(1024) return {"banner": banner, "port": port} except: return {"banner": banner, "port": port} # TCP主動服務識別 def TcpServer(ip, port, feature): try: list = feature.split('|') if list[2] == 'banner' or list[2] == 'default': return 'nopayload' else: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(3) sock.connect((ip, port)) sock.send(list[2].decode('string_escape')) except Exception as e: return str(e) try: # while True: data = sock.recv(1024) if data: reg = list[3].decode('string_escape') matchObj = re.search(reg, data, re.I | re.M) if matchObj: return [ip, port, list[0]] except Exception as e: return str(e)
嘗試建立TCP連接,嘗試失敗說明這個端口沒有TCP服務。如果連接成功看是否返回banner,如果沒有返回banner再主動發送payload探測包獲取banner。然后用主動或者被動獲取的banner去特征匹配服務
五、UDP服務識別
部分代碼
# UDP端口探測、服務識別 def UdpPort(ip,feature): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(3) feature.split('|') try: list = feature.split('|') sock.sendto(list[2], (ip, int(list[1]))) # 發送特征數據 except Exception as e: return str(e) while True: try: data, ipport = sock.recvfrom(1024) if data: reg = list[3].decode('string_escape') matchObj = re.search(reg, data, re.I | re.M) if matchObj: return [ip, list[1], list[0]] break except: break
因為UDP沒有三次握手不用建立連接,所以直接發送探測包payload,然后用獲取的banner去特征匹配服務
六、獲取探測包payload方法
這里只是提供一種思路,獲取探測包payload的方式有很多。舉個例子獲取mongodb探測包payload的方法:mongo客戶端連接遠程服務—》Wireshark抓包—》獲取原始數據—》解碼成字節碼串
1、mongo客戶端連接遠程服務
(這里最好用命令的方式連接,我在測試的過程中就遇到過某些客戶端可能會緩存一些信息導致連接的時候不能抓到完整過程的包,用最原始命令連接功能比較單一可能沒有這些緩存信息。也不能帶一些不通用的特征比如認證的token,不然你測試的時候可能能探測出來實踐的時候又不行了)
C:\Users\admin>mongo -host x.x.x.x MongoDB shell version v3.4.0 connecting to: mongodb://x.x.x.x:27017/ MongoDB server version: 2.6.3 WARNING: shell and server versions do not match
2、Wireshark抓包
可以看到mongo連接前需要TCP三次握手,然后在發送一些額外的認證信息
3、獲取原始數據
Ascii碼
所以理論上只要往端口x發送這個探測payload
230100000000000000000000d40700000000000061646d696e2e24636d64000000000001000000fc0000001069734d6173746572000100000003636c69656e7400e1000000036170706c69636174696f6e001d000000026e616d65000e0000004d6f6e676f4442205368656c6c000003647269766572003a000000026e616d6500180000004d6f6e676f444220496e7465726e616c20436c69656e74000276657273696f6e0006000000332e342e300000036f73006c0000000274797065000800000057696e646f777300026e616d6500140000004d6963726f736f66742057696e646f77732038000261726368697465637475726500070000007838365f3634000276657273696f6e0011000000362e3220286275696c6420393230302900000000
然后在返回的banner中找到ismaster這個關鍵字就說明x端口開的服務是mongodb
4、解碼成字節碼串
Soket直接發送原始數據服務器是不行的,需要先轉成字節碼串,在python里可以用
binascii.unhexlify()函數轉換
#\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\x07\x00\x00\x00\x00\x00\x00admin.$cmd\x00\x00\x00\x00\x00\x01\x00\x00\x00\xfc\x00\x00\x00\x10isMaster\x00\x01\x00\x00\x00\x03client\x00\xe1\x00\x00\x00\x03application\x00\x1d\x00\x00\x00\x02name\x00\x0e\x00\x00\x00MongoDB Shell\x00\x00\x03driver\x00:\x00\x00\x00\x02name\x00\x18\x00\x00\x00MongoDB Internal Client\x00\x02version\x00\x06\x00\x00\x003.4.0\x00\x00\x03os\x00l\x00\x00\x00\x02type\x00\x08\x00\x00\x00Windows\x00\x02name\x00\x14\x00\x00\x00Microsoft Windows 8\x00\x02architecture\x00\x07\x00\x00\x00x86_64\x00\x02version\x00\x11\x00\x00\x006.2 (build 9200)\x00\x00\x00\x00
七、服務識別流程圖
八、效果參考
被動式發現TCP服務,IP:x.x.195.44,主機名:hostname,端口:3306,服務名稱:mysql,模式:banner 發現TCP端口跟banner但是沒有識別出服務,IP:x.x.195.37,主機名:bogon,端口:22222,banner: ___ __ __ ___ ___ ____ / _ \ / / / // _ ) / _ ) / __ \ / // // /_/ // _ |/ _ |/ /_/ / /____/ \____//____//____/ \____/ dubbo> 被動式發現TCP服務,IP:x.x.195.34,主機名:bogon,端口:139,服務名稱:netbios,模式:default 被動式發現TCP服務,IP:x.x.195.34,主機名:bogon,端口:445,服務名稱:smb,模式:default 被動式發現TCP服務,IP:x.x.195.37,主機名:bogon,端口:139,服務名稱:netbios,模式:default 被動式發現TCP服務,IP:x.x.195.37,主機名:bogon,端口:445,服務名稱:smb,模式:default 被動式發現TCP服務,IP:x.x.195.39,主機名:bogon,端口:139,服務名稱:netbios,模式:default 被動式發現TCP服務,IP:x.x.195.39,主機名:bogon,端口:445,服務名稱:smb,模式:default 被動式發現TCP服務,IP:x.x.195.42,主機名:bogon,端口:139,服務名稱:netbios,模式:default 被動式發現TCP服務,IP:x.x.195.42,主機名:bogon,端口:445,服務名稱:smb,模式:default 被動式發現TCP服務,IP:x.x.195.44,主機名:hostname,端口:445,服務名稱:smb,模式:default 被動式發現TCP服務,IP:x.x.195.44,主機名:hostname,端口:139,服務名稱:netbios,模式:default 主動式發現TCP服務,IP:x.x.195.34,主機名:bogon,端口:3389,服務名稱:rdp 主動式發現TCP服務,IP:x.x.195.37,主機名:bogon,端口:3389,服務名稱:rdp 主動式發現TCP服務,IP:x.x.195.39,主機名:bogon,端口:3389,服務名稱:rdp 主動式發現TCP服務,IP:x.x.195.42,主機名:bogon,端口:3389,服務名稱:rdp 主動式發現TCP服務,IP:x.x.195.44,主機名:hostname,端口:3389,服務名稱:rdp 主動式發現TCP服務,IP:x.x.195.44,主機名:hostname,端口:27017,服務名稱:mongodb