主机扫描——服务识别篇

时间:2024-03-08 17:05:22

最近想做一个扫描平台,包含主机漏洞扫描这一块东西,本来主机扫描想接巡风的,但是接进来发现巡风在服务识别这一块漏报、误报有点多(应该是我不会用,巡风肯定是没有问题的)。用的不顺手,所以打算换一个。

一、为什么不用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