SRVLOC协议与端口扫描

ovie  871天前

摘要

最近Goby团队在优化Goby的UDP协议扫描模块,新增了一些UDP协议扫描。在实现 SRVLOC 协议扫描的过程中,发现一些主流端口扫描工具没有识别该协议,而nmap支持该协议,但在扫描该协议端口时,仅发送一个包类型为SrvRqst的请求包。我们在翻看 SRVLOC 协议的RFC文档时,发现该协议的包类型不仅仅只有srvRqst,通过深入分析,实现在端口扫描时,相较nmap,可多获取到一些资产信息,如:产品名称、MAC地址等。

本文先介绍 SRVLOC 协议的体系结构和数据包格式,最后再讲一讲Goby对该协议扫描时的发包步骤。

SRVLOC 协议简介

SRVLOC (Service Location Protocol),服务定位协议, 是一种服务发现协议,它可以使计算机及其他设备在未经预先配置的情况下找到局域网中的服务。 通常监听在U:427端口。

SRVLOC 体系结构

该协议中,主要有以下三种角色:

  1. User Agent (UA):客户端程序,从 Service Agents 或 Directory Agents 获取服务信息
  2. Service Agent (SA) :存储着服务列表,告知其它 Agent 有哪些已注册的服务。
  3. Directory Agent (DA):收集所有 SA 端告知给它的服务,相当于一个集中的仓库,对 SA 的一个高速缓存。使得 UA 不用去访问多个 SA,而只去访问一个或少数几个DA即可。可以不部署 DA,而当网络规模增大或者网络环境不支持多播路由时,则需要 DA 。

当只有 UA 和 SA 时,基本通信模式如下:

+------------+ ----Multicast SrvRqst----> +---------------+
| User Agent |                            | Service Agent |
+------------+ <----Unicast SrvRply------ +---------------+

UA 通过发送广播(广播地址为239.255.255.253)或配置找到 SA 地址;UA 向 SA 发送SrvRqst请求获取服务信息,SA返回包含相应的服务信息的SrvRply响应。

当加上 DA 时, 基本通信模式如下:

+-------+ -Unicast SrvRqst-> +-----------+ <-Unicast SrvReg- +--------+
| User  |                    | Directory |                   |Service |
| Agent |                    |   Agent   |                   | Agent  |
+-------+ <-Unicast SrvRply- +-----------+ -Unicast SrvAck-> +--------+

这种模式下,UA 不与 SA 直接通信,而是与 DA 通信,向 DA 请求获取服务信息。而存储在 DA 的服务信息,由 SA 发过去:SA 发送SrvReg请求向 DA 注册服务, DA存储该服务信息,并响应SrvAck表示注册成功。

协议格式

头部格式:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Version    |  Function-ID  |            Length             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length, contd.|O|F|R|       reserved          |Next Ext Offset|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Next Extension Offset, contd.|              XID              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      Language Tag Length      |         Language Tag          \   
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • Version:1字节。协议版本,通常为2

  • Function-ID:1字节。数据包类型,可选值:

    1  Service Request          SrvRqst
    2  Service Reply            SrvRply
    3  Service Registration     SrvReg
    4  Service Deregister       SrvDeReg
    5  Service Acknowledge      SrvAck
    6  Attribute Request        AttrRqst
    7  Attribute Reply          AttrRply
    8  DA Advertisement         DAAdvert   (Directory Agent Advertisement)
    9  Service Type Request     SrvTypeRqst
    10  Service Type Reply      SrvTypeRply
    11  SA Advertisement        SAAdvert  (Service Agent Advertisement)
    
  • Length:3字节。数据包长度,包括头部和消息部分

  • Flag:3 bit,即上方|O|F|R 这3 bit 和 后面reserved部分。可选值:

    • REQUEST MCAST (0x20) :表示该请求为多播请求
    • FRESH (0x40):表示该SrvReg请求 (这个Flag只用在 SrvReg 请求)要注册新的服务,而不是更新旧的已注册过的服务。
    • OVERFLOW (0x80):表示该数据包已超出1400字节(不包括UDP头和其他协议头),超出的部分已被截断。
  • Next Ext Offset:3字节。通常为0,表示没有包含协议扩展数据。

  • XID:2字节。数据包ID。SA 或 DA 可以缓存响应,如果收到带有相同XID的请求,可以直接用缓存数据响应回去。

  • Language Tag Length:2字节。Language Tag字段长度

  • Language Tag:用于支持多语言,通常为en。(上图里,Language Tag后面的 \ 符号,表示该字段长度不固定)

协议头部后面跟消息部分。不同类型的数据包(由Function-ID指定数据包类型)消息部分的格式也不一样。下面说说几个类型的数据包格式:

SrvRqst 包

SrvRqst包由 UA 发起,向 SA 或 DA 请求获取服务信息,或者发往广播地址,用于寻找 SA 或 DA。这类包又可以再分为这三类:

  • 发送<service-type>service:service-agentSrvRqst请求,服务端返回SAAdvert响应 (该类请求是发给SA的)。该类请求主要用于寻找 SA 。
  • 发送<service-type>service:directory-agentSrvRqst请求,服务端返回DAAdvert响应 (该类请求是发给DA的)。该类请求主要用于寻找 DA 。
  • 发送正常(不带以上两种特殊的<service-type>)的SrvRqst请求,获取指定service-type的服务信息,服务端返回SrvRply响应。

数据包格式为:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Service Location header (function = SrvRqst = 1)        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      length of <PRList>       |        <PRList> String        \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   length of <service-type>    |    <service-type> String      \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    length of <scope-list>     |     <scope-list> String       \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  length of predicate string   |  Service Request <predicate>  \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  length of <SLP SPI> string   |       <SLP SPI> String        \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • PRList (Prev Response List):如果SrvRqst,SrvTypeRqst或AttrRqst请求是多播请求,则该字段包含之前有响应过该多播请求的服务器的ip地址(多个地址之间用,号分割)。第一次请求,该字段就为空。而当 DA 或 SA 看到请求中的PRList包含它的地址,就不会去响应该请求。如果是单播请求,该字段只能为空。
  • service-type:格式见下方的“Service URL 和 Service Type”介绍
  • scope-list:表示请求属于指定scope的服务。多个 scope 之间用 , 号分割。scope 可以理解为分组,一个组包含多个服务, 方便管理员通过组管理多个服务。服务的 scope 默认为 DEFAULT。
  • predicate:为LDAPv3查询语句格式的字符串,用于过滤,可以为空
  • SLP SPI:认证用。

SrvRply包

用于响应 SrvRqst 包。数据包格式为:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Service Location header (function = SrvRply = 2)       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Error Code             |        URL Entry count        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       <URL Entry 1>          ...       <URL Entry N>          \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • Error Code:出现错误时,Error Code不为0,且该字段后面可能不会再接什么数据。错误响应只用于响应单播请求,对于多播请求,如果出现错误,则忽略掉请求。

  • URL Entry:一个 URL Entry的格式为:

     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Reserved    |          Lifetime             |   URL Length  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |URL len, contd.|            URL (variable length)              \
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |# of URL auths |            Auth. blocks (if any)              \
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    关键信息是URL字段,该字段为service URL,格式见下方的“Service URL 和 Service Type”介绍。

SAAdvert 包

用于SA,响应service-type为service:service-agent的 SrvRqst 包。数据包格式为:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Service Location header (function = SAAdvert = 11)     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Length of URL         |              URL              \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Length of <scope-list>    |         <scope-list>          \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Length of <attr-list>     |          <attr-list>          \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| # auth blocks |        authentication block (if any)          \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • URL:service URL
  • scope-list:scope列表。多个 scope 之间用 , 号分割
  • attr-list:服务的属性。格式见下方“AttrributeList 格式”介绍
  • auth blocks:authentication block的个数。
  • authentication block:用于认证

Service URL 和 Service Type

Service URL 格式: service:<srvtype>://<addrspec>,addrspec 为主机名或IP,有时包含冒号和端口号(一般是该服务如果没有监听在标准端口时,就会接端口号),如service:tftp://bad.glad.org:8080

Service Type 格式: service:<abstract-type>:<concrete-type>,如service:printer这个type可匹配如下两个URL:

service:printer:lpr://hostname
service:printer:http://hostname

service:printer:http这个service type只能匹配后第二个url。

可理解为 Service URL 的前缀就是 service type。

AttrributeList 格式

AttrributeList 示例: (service-type=service:ws-discovery.canon:http,service:printer.canon),x-unknown,(x-key=vi)

小括号内为一个属性, 多个属性用,号隔开。在一个属性内,=号左边是属性名,右边是属性值,属性值可以有多个,用,隔开。 属性也可以没有值,如示例中的x-unknown

如果要表示,号等保留字符,可以用反引号转义来表示,如,号用\29表示,\号用\5c表示。

抓包实验环境搭建

如果想要抓几个srvloc包看看,可以搭个环境。先装个wireshark。再下载实现了srvloc协议的openslp工具的源码,编译安装。

在kali下编译安装openslp:

  1. 下载源码:http://www.openslp.org/download.html
  2. 解压并进到目录
  3. 执行./configure
  4. 指定make
  5. ./slptool/slptool 为 UA 端, ./slpd/slpd 为 SA 端 或 DA端。

启动 SA 服务, 执行以下命令:

sudo mkdir -p /usr/local/var/log/
sudo touch /usr/local/var/log/slpd.log
sudo slpd/slpd -d -c $(pwd)/etc/slp.conf -r $(pwd)/etc/slp.reg

slpd默认为 SA ,如果要改为 DA,则取消掉 etc/slp.conf 配置文件里对net.slp.isDa = true行的注释。

可以在etc/slp.reg服务端静态注册服务,或用slptool动态注册服务。

slptool工具的使用:

slptool/slptool findsrvs service:service-agent  # 寻找 SA 的地址
slptool/slptool register service:ntp://myntp.com "(attr1=val1),(attr2=val2)" # 假设在myntp.com主机上开着一个ntp服务。可以向 SA 注册一个service URL为 service:ntp://myntp.com 的服务,带两个属性值。
slptool/slptool findsrvtypes  # 列出已注册的服务
slptool/slptool findsrvs service:ntp  # 查找当前网络中已注册的 ntp 服务
slptool/slptool findattrs service:ntp://myntp.com # 列出 service:ntp://myntp.com 的属性

slptool发的请求都是都是发到广播地址,要发到指定地址,用-u选项。如:slptool/slptool -u 127.0.0.1 findsrvs service:ntp

端口扫描

作为扫描器该如何判断某主机开了U:427端口的srvloc服务?发送什么数据包来获取信息?(在公网测试时,没有发现监听着U:427的 DA,只发现了 SA,所以下面只说对 SA 的扫描)。目前我们的发包步骤是:

  1. 往U:427端口发送service:service-agent SrvRqst请求,如果目标有响应,则判断该端口是否开放,解析响应包,看看是否有已注册服务列表
  2. 如果没有则尝试发送SrvTypeRqst请求来获取已注册服务列表
  3. 获取列表后,在对每个已注册服务去发送SrvRqstAttrRqst请求,获取它们的服务地址和服务属性。

下面具体说说这四种请求包:

service:service-agent SrvRqst

对该端口第一个探测包通常是一个service-typeservice:service-agent且带着Multicast FlagSrvRqst 包。这个包可以用nmap来发:sudo nmap -PN -sU -sV -p 427 x.x.x.x。具体的payload数据见这里。在该payload里,scope-list字段设置为default。该字段可以设置为空,这样对只配置了non-default scope的SA服务也能探测到,另外,包里的XID字段也可以随机化一下。(附:SrvRqst (除了带service:service-agentservice:directory-agent的SrvRqst),SrvReg,AttrRqst,SrvTypeRqst, DAAdvert 和 SAAdvert 包 的 scope-list 字段都不能为空)

SA端接收到该请求后,响应SAAdvert包,里面包含一个URL字段,格式为service:service-agent://<addr><addr>是 SA 主机的主机名或ip地址。有的 SA 响应的 SAAdvert 包还会包含一个service-type的属性,这个属性值就是存储在该 SA 里已注册的服务。如下图这个SAAdvert 响应包,它表示有两个服务,service:ws-discovery.canon:httpservice:printer.canon

srvloc-1.png

SrvTypeRqst

如果SAAdvert包里没有带service-type属性,可以尝试发一个SrvTypeRqst包,来列出已注册服务。这类包并非所有 SA 都支持,如果支持则会响应一个SrvTypeRply包

SrvRqst

在获取到service-type列表后,挨个发SrvRqst包(每个service-type值放在每个SrvRqst包里的service-type字段),目标会响应SrvRply包。这一步主要是获取每个service-type的service URL,即服务地址。(SrvRqst包的scope-list字段指定为空可能导致不会收到响应,通常指定为 DEFAULT)。比如下图这个SrvRply响应包,它表示一个叫service:webm:https的https服务监听在5989端口:

srvloc-2.png

nmap上有两个nse脚本,用于通过srvloc协议获取指定服务的地址:

  • broadcast-versant-locate.nse:发service-type为service:odbms.versant:vod的srvRqst包,探测Versant对象数据库。
  • broadcast-novell-locate.nse:发service-type为bindery.novell的srvRqst包,探测Novell NetWare Core Protocol (NCP)服务器。

这一步,与这两个nse脚本的原理是一样的。不过我们上一步已经获取到service-type列表了,就已经知道有没有必要运行上面两个nse脚本了。

AttrRqst

对每个service-type挨个发AttrRqst包(每个service-type值放在每个AttrRqst包里的URL字段)。这一步用于获取服务的属性,属性里可能包含其它信息,如产品名、mac地址等。 (scope list不能为空, 否则服务端返回SCOPE_NOT_SUPPORTED错误代码,通常该字段值设为DEFAULT)

公网上很多srvloc服务就带有一个叫 service:VMwareInfrastructure 服务,且该服务有个product属性表明这是VMware ESXi产品以及它的版本。

srvloc-3.png

VMware ESXi产品的srvloc端口还有过漏洞。关于漏洞可参考这两个链接:

结语

从翻看RFC文档,理解、分析协议,到编写代码,最后实现该协议在资产收集阶段的往前一小步。后续Goby新版本也会加上该协议的扫描。如果读者对协议扫描和资产识别有其他什么想法,欢迎与我们交流。

参考

最新评论

昵称
邮箱
提交评论