前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 DNS-SD 和 SSDP 扫描内网主机

使用 DNS-SD 和 SSDP 扫描内网主机

作者头像
Seebug漏洞平台
发布2021-10-18 16:16:31
3.7K0
发布2021-10-18 16:16:31
举报
文章被收录于专栏:Seebug漏洞平台Seebug漏洞平台

作者:0x7F@知道创宇404实验室

时间:2021年09月28日

0x00 前言

前段时间看到一款局域网扫描的 App「Fing」,相比于 Nmap 的端口服务扫描,他可以扫描获取目标主机的设备名称和用户名,在内网资产梳理时这些信息能够提供一定的帮助。

本文从 Fing 的功能入手,学习和介绍了目前常用的局域网服务发现协议,并根据这些协议,尝试编写 Python 扫描脚本。

0x01 Fing

Fing App 设备扫描功能演示:

[1.Fing设备扫描]

使用 Wireshark 抓包和测试发现,Fing 同样也通过常规的扫描技术进行主机发现,然后通过嗅探通信在 5353 端口的 MDNS 报文,从中提取设备信息。(关于 Fing 的扫描原理这里就不展开了)

MDNS 是常见的局域网主机/服务发现协议,这类协议常用于局域网设备之间进行自动发现,从而上层应用或服务可以实现零配置使用;由于这些协议功能的需要,所以协议中都包含了大量的描述信息,可以用于内网主机的扫描。

除了 mDNS 以外,局域网服务发现协议还有很多,比如

1. DNS-SD(DNS Service Discovery):基于DNS协议的服务发现

2. SSDP(Simple Service Discovery Protocol):简答服务发现协议

3. NBNS(NetBIOS name service):NetBIOS名称服务(已过时)

4. etc

0x02 DNS-SD

DNS-SD(DNS Service Discovery)是一种基于 DNS 协议的服务发现协议,设备之间可以通过该协议自动发现服务;DNS-SD 兼容 mDNS 协议,同样使用 UDP 5353 端口,在 Wireshark 中统一标注为 MDNS

使用 DNS-SD 协议的设备会周期性的在组播地址 224.0.0.251 广播自己感兴趣的服务名称,若有设备开启指定服务就会发送服务的详细信息给源设备;除此之外,这个报文还包含了源设备的信息,Fing 就是通过这个报文获取到的设备信息。通过 Wireshark 抓包如下:

[2.嗅探周期性的DNS-SD/mDNS报文]

除了通过嗅探周期性的报文获取信息,我们还发现 DNS-SD 协议提供了一种主动查询服务的功能(https://datatracker.ietf.org/doc/html/rfc6763#section-9),通过向目标主机发送查询名为 _services._dns-sd._udp.local,类型为 PTR 记录的 DNS 查询报文,目标主机将返回自身开放的服务名称。(这里我们只讨论在内网环境下 DNS-SD 使用的场景,DOMAIN=.local)

使用 Python scapy 包的功能展示该请求和响应报文格式(未显示字段为默认值):

[3.dnssd.local查询(scapy)]

随后再以目标的服务名称为查询名,发送 PTR 记录 DNS 查询报文,查询服务的详细信息,请求和响应报文格式如下:

[4.service详情查询(scapy)]

响应报文的附加字段里包含了服务的详细信息,从中我们可以提取到服务的协议、端口、以及设备信息:

[5.从dnssd响应报文中提取信息]

根据以上交互流程,我们编写 dnssd 的扫描脚本如下:

代码语言:javascript
复制
#/usr/bin/python3
#!coding=utf-8

import socket
import sys
from scapy.all import raw, DNS, DNSQR

def get_service_info(sock, target, resp):
    service = (resp.an.rdata).decode()

    # query each service detail informations
    req = DNS(id=0x0001, rd=1, qd=DNSQR(qtype="PTR", qname=service))
    #req.show()
    sock.sendto(raw(req), target)
    data, _ = sock.recvfrom(1024)
    resp = DNS(data)
    #resp.show()

    # parse additional records
    repeat = {}
    for i in range(0, resp.arcount):
        rrname = (resp.ar[i].rrname).decode()
        rdata  = resp.ar[i].rdata

        if rrname in repeat:
            continue
        repeat[rrname] = rdata

        if hasattr(resp.ar[i], "port"):
            rrname += (" " + str(resp.ar[i].port))

        if rrname.find("._device-info._tcp.local.") > 0:
            print(" "*4, rrname, rdata)
        else:
            print(" "*4, rrname)
# end get_service_info()

def dnssd_scan(target):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(2)

    # query all service name
    req = DNS(id=0x0001, rd=1, qd=DNSQR(qtype="PTR", qname="_services._dns-sd._udp.local"))
    #req.show()
    try:
        sock.sendto(raw(req), target)
        data, _ = sock.recvfrom(1024)
    except KeyboardInterrupt:
        exit(0)
    except:
        print("[%s] OFFLINE" % target[0])
        return
    resp = DNS(data)
    #resp.show()

    print("[%s] ONLINE" % target[0])
    for i in range(0, resp.ancount):
        get_service_info(sock, target, resp)
# end dnssd_scan()

if __name__ == "__main__":
    if not (len([sys.argv]) > 0 and sys.argv[1].endswith(".0")):
        print("usage: python3 dnssd.py 192.168.1.0")
        exit(0)

    print("dnssd scan start")
    network = sys.argv[1].rstrip("0")

    # scan local network
    for i in range(1, 256):
        target = (network + str(i), 5353)
        dnssd_scan(target)

    print("dnssd scan end")
# end main()

运行效果如下:

[6.dnssd.py运行结果]

后来发现 Nmap 也提供了 dns-service-discovery.nse 的扫描脚本(https://github.com/nmap/nmap/blob/master/scripts/dns-service-discovery.nse)

0x03 SSDP

不过目前 DNS-SD 使用范围还是比较小的,最成熟 Zeroconf 实现是苹果家的 Bonjour,底层使用 DNS-SD 协议,用上面的脚本扫出来的大部分都是苹果的产品。

相比之下,SSDP(Simple Service Discovery Protocol)就使用得非常广泛了,他是 UPnP(Universal Plug and Play) 的核心实现;在 SSDP 协议中,请求和响应报文会附加一些主机信息(RFC文档未强制规定),我们同样可以利用这一点来扫描内网主机。

SSDP 的标准查询服务的报文格式如下,其中 USER-AGENT 字段未强制规定:

代码语言:javascript
复制
M-SEARCH * HTTP/1.1
S: uuid:ijklmnop-7dec-11d0-a765-00a0c91e6bf6
Host: 239.255.255.250:reservedSSDPport
Man: "ssdp:discover"
ST: ge:fridge
MX: 3
USER-AGENT: Google Chrome/93.0.4577.82 Mac OS X

SSDP 客户端会周期性的发送该查询报文,以寻找自己感兴趣的服务,我们可以通过嗅探提取 USER-AGENT 字段,获得主机的操作系统信息。

除此之外,如果设置 SSDP 请求报文中 ST: ssdp:all 字段,并将报文发向组网地址 239.255.255.250:1900,SSDP 服务端收到报文后,会将自身服务响应给源地址,响应报文格式如下,其中 Server 字段未强制规定:

代码语言:javascript
复制
HTTP/1.1 200 OK
S: uuid:ijklmnop-7dec-11d0-a765-00a0c91e6bf6
Ext:
Cache-Control: no-cache="Ext", max-age = 5000
ST: ge:fridge
USN: uuid:abcdefgh-7dec-11d0-a765-00a0c91e6bf6
AL: <blender:ixl><http://foo/bar>
Server:Microsoft-Windows/6.3 UPnP/1.0 UPnP-Device-Host/1.0

通过解析响应报文提取 Server 字段,也可以获得主机的操作系统信息。

根据以上交互流程,我们编写 ssdp 的扫描脚本如下:

代码语言:javascript
复制
#/usr/bin/python3
#!coding=utf-8

import socket
import struct

address = ("239.255.255.250", 1900)
result = {}

def get_serv_ua(resp):
    lines = resp.split("\r\n")
    for i in lines:
        array = i.split(":")
        if array[0].upper() == "SERVER" or array[0].upper() == "USER-AGENT":
            return array[1]
    # end-for
# end get_serv_ua()


def ssdp_scan():
    print("[scan mode]")
    req = b'M-SEARCH * HTTP/1.1\r\nHost: 239.255.255.250:1900\r\nST:ssdp:all\r\nMan: "ssdp:discover"\r\nMX:1\r\n\r\n'

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(5)

    # send "ssdp:all" query request
    sock.sendto(req, address)

    # receive and print
    while True:
        try:
            resp, raddr = sock.recvfrom(1024)
        except:
            break
        if raddr[0] not in result:
            data = get_serv_ua(resp.decode())
            result[raddr[0]] = data
            print(raddr[0], data)
    # end-while
# ssdp_scan()

def ssdp_sniffer():
    print("[sniffer mode] (stop by Ctrl-C)")

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(address)

    # join the multicast group
    maddr = struct.pack("4sl", socket.inet_aton("239.255.255.250"), socket.INADDR_ANY)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, maddr)

    # receive and print
    while True:
        try:
            resp, raddr = sock.recvfrom(1024)
        except:
            break
        if raddr[0] not in result:
            data = get_serv_ua(resp.decode())
            result[raddr[0]] = data
            print(raddr[0], data)
# ssdp_sniffer()

if __name__ == "__main__":
    print("ssdp scan start")

    ssdp_scan()
    ssdp_sniffer()

    print("ssdp scan end")
# end main()

运行效果如下:

[7.ssdp.py运行结果]

0x04 总结

除了文中提到的 DNS-SD 和 SSDP 协议,还有很多其他的协议可以帮助我们对内网主机进行梳理,读者可以自行扩展学习。


References:

https://www.ietf.org/rfc/rfc6762.txt

https://www.ietf.org/rfc/rfc6763.txt

https://datatracker.ietf.org/doc/html/draft-cai-ssdp-v1-03

https://apps.apple.com/cn/app/fing-%E7%BD%91%E7%BB%9C%E6%89%AB%E6%8F%8F%E4%BB%AA/id430921107

https://zyun.360.cn/blog/?p=42

https://github.com/nmap/nmap/blob/master/scripts/dns-service-discovery.nse

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-09-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Seebug漏洞平台 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 作者:0x7F@知道创宇404实验室
  • 前段时间看到一款局域网扫描的 App「Fing」,相比于 Nmap 的端口服务扫描,他可以扫描获取目标主机的设备名称和用户名,在内网资产梳理时这些信息能够提供一定的帮助。
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档