前言
在这篇文章中我们探讨一下windows空会话
参考链接:
抓包分析
使用rpcclient
尝试建立空连接并调用querydispinfo
方法
rpcclient -U'%' 192.168.60.160 -c 'querydispinfo'
返回NT_STATUS_ACCESS_DENIED
从抓包结果上分析,认证和授权这两个部分是分开的
可以看到在执行Connect5
方法时已经开始报出STATUS_ACCES_DEBIED
错误,根据微软官方文档对SAMR
协议的描述并对比连接成功时的数据包,可以看出来客户端会逐一尝试Connect方法,直到Connect2失败,结束请求,返回错误
你可以认证到ipc$
共享上,也可以打开ipc$
共享,但是你没办法在其所暴露出来的samr
这个命名管道上调用QueryDisplayInfo
方法,也就是说你可以建立空连接,但是你无法执行特定的RPC方法
这里描述了各命名管道上能够调用的方法:
使用nmap的 smb-enum-users脚本可以枚举出远程系统的所有用户,有时间我要研究一下这个脚本是怎么回事
但是我本地复现时无法枚举远程系统的用户,网上搜了一下,说是需要使用比较老的nmap版本
我尝试找了一下哪些管道可以进行匿名连接,还有就是在这些管道上可以调用哪些方法
最后我锁定了netlogon
管道上的GetDcName
, DsrGetDcName
, DsrGetDcNameEx
和DsrGetDcNameEx2
这些方法
这些方法可以被用来检测在DC中是否存在特定的用户
直接调用DsrGetDcNameEx2方法
先用rpcclient
工具执行如下命令调用DsrGetDcNameEx2
方法
rpcclient 192.168.60.160 -U'%' -c 'dsr_getdcnameex2 Administrator 512 domain2.com'
- 192.168.60.160是DC
- domain2.com是域名
- Administrator 是我们要验证是否存在的用户名
- 512 是
AllowableAccountControlBits
的值(参考https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrpc/fb8e1146-a045-4c31-98d1-c68507ad5620?redirectedfrom=MSDN),0000000000000000000001000000000
代表域用户
可以根据响应结果来进行判断:
- 存在则返回一个结构体
- 不存在则返回
WERR_NO_SUCH_USER
下面看抓包情况
下面是DsrGetDcNameEx2
请求的参数,16进制的200就是512,代表域用户
用户存在会返回success
用户不存在则返回unknown
在抓取到的数据包中,可以看到如下数据包:
可以看到空连接建立之后我们可以通过调用lsarpc命名管道上的LsarQueryInformationPolicy2
方法来获取域的SID
root@ubuntu:/# rpcclient 192.168.60.160 -U'%' -c 'lsaquery'
Domain Name: DOMAIN2
Domain Sid: S-1-5-21-1986246999-2617435358-1981060215
根据上面的分析编写一个脚本,功能如下:
使用impacket框架调用DsrGetDcNameEx2
方法,接收一个用户名列表,然后依次使用该列表中的用户针对远程主机(DC)调用DsrGetDcNameEx2
方法,根据返回结果来进行枚举
使用上面这种直接调用RPC方法来枚举用户是有一定的局限性的的,因为如果目标DC使用Network security: Restrict NTLM: Incoming NTLM traffic
组策略禁用账户使用NTLM认证方式,那么直接调用RPC也就会失败
该策略是在Default Domain Controllers Policy
中设置的
构造数据包进行枚举
为了加速枚举,我调查了一下DsrGetDcNameEx2
是如何工作的,该方法和GetDcName
, DsrGetDcName
, DsrGetDcNameEx
都可以根据提供的域名来定位到该域的DC
这些方法会使用DNS、LDAP或者NetBIOS等方法获取域控制器在域内的位置
他们的工作方式大致如下所述:
- 1、通过DNS查询或者NetBIOS广播提供的域名获取到域控制器的IP
- 2、如果通过DNS获取到了域控制器的IP,则请求端会向DC发送一个
LDAP ping
报文,如果通过NetBIOS获取到了DC的IP,会向DC发送一个mailslot ping
报文,这两个数据包都是通过UDP协议进行发送的,这两个报文中都有一个Filter,其实就是请求的一些参数 - 3、DC检查自己是否符合这些要求并发送响应报文
- 4、最后调用方法的系统会处理发送回来的响应报文
LDAP ping
发起调用的主机会向远程主机发送一个CLDAP报文(基于UDP,无连接),对于用户存在的情况,DC会向源主机返回操作码为23的报文,如果用户不存在则返回操作码25
因此我需要自己构造出CLDAP
报文,Samba源代码中有一个示例脚本,该脚本可以构造出这样的CLDAP报文
根据那个示例脚本我写出了下面这个脚本:
https://github.com/sensepost/UserEnum/blob/master/UserEnum_LDAP.py
安装这个工具需要asn1tools
模块,安装的时候有需要安装diskcache
模块,但是使用python2
安装的时候他会出现错误,因为默认安装的是针对python3
的模块,因此需要指定diskcache
模块的版本
pip install diskcache==4.1.0
还有就是上面安装好之后还是会报错ImportError: cannot import name WordCompleter
然后我们使用如下方式解决
python2 -m pip install scapy==2.4.0
python2 -m pip install asn1tools==0.55.0
pip install prompt_toolkit==1 .0.15
如果运行的时候报下面的错误
asn1tools.codecs.EncodeError: protocolOp: typesOnly: Expected data of type bool, but got 0.
则使用如下方法解决
pip install asn1tools==0.100.0
之前写的脚本是直接调用的rpc方法,这种调用方法的方式相比较直接构造数据包发送请求的方式要慢很多
我测试了一下,1万多个用户名,只用了10秒左右
下面是抓的请求包
这里acc进行了映射,映射表是下面这俩:
从第一个表中我们可以找到普通域用户也就是16进制的200,对应的是UF_NORMAL_ACCOUNT
从第二个表中我们可以看到UF_NORMAL_ACCOUNT
对应的是USER_NORMAL_ACCOUNT
USER_ACCOUNT
在这里可以找到, 可以看到USER_NORMAL_ACCOUNT
的值是0x00000010
从上面wireshark抓的包我们可以看到显示的是10:00:00:00
这个不能直接读,细心的话你可以发现这里没有加0x
前缀,我们就必须将网络序(大端)转换成小端来读,转换之后就是00:00:00:10
,正好符合上面的映射
后面我又在想我可以使用通配符来猜解用户名,比如B*
、Bo*
等,但是当我这样写的时候,构造出来的CLDAP包就无法正常响应了
我又捣鼓了一会儿,发现确实没办法实现这种功能,不过既然CLDAP可以达到这种爆破用户名的效果,那么NetBIOS呢?
首先我使用rpcclient发起了一个DsrGetDcNameEx2
方法的调用,然后在wireshark中,我捕捉到了如下数据包:
这里发起请求的机器为DC01(192.168.57.2),目标机器为black(192.168.57.120)
- 1、
57.2
发送NBNS
广播报文,查询black这个名称 - 2、
120
向57.2
返回NBNS响应报文,其中包含自己的IP(192.168.57.120
) - 3、
57.2
发送SMB_NETLOGON
广播报文(UDP),这里明明已经知道了black域的DC的IP却还要发送广播,我也不知道为啥 - 4、
120
发送NBNS
广播报文,查询DC01这个名称 - 5、
57.2
向120发送SMB_NETLOGON
报文 - 6、
57.2
向120返回NBNS
报文,报告自己的IP - 7、
120
向57.2
返回SMB_NETLOGON
响应报文
从以上的报文我们可以看出来,通信双方都发起了NBNS查询,而且查询的名称我们是可以控制的,响应的IP地址我们也可以控制(这个我们只能控制DC01),但是通信过程一直都是UDP,没有SMB TCP
报文,我们无法使用Responder
来利用,但是我发现至少可以利用这个来方法来中毒目标系统的NETBIOS名称-ip对应表缓存
mailslot ping
看完了ldap之后我们再来看一下NetBIOS
可以使用这个脚本枚举
在运行该脚本的时候可能会遇到如下错误:
from scapy.all import *
ImportError: No module named scapy.all
解决方法
pip install scapy==2.4.3
使用方法:
Python2 UserEnum_NBS.py 192.168.60.148 192.168.60.160 DOMAIN2 userlist.txt
注意这里域名是NetBIOS名称,也就是大写字母,比如domain.local
在这里应该写成DOMAIN,注意这里写的并不是FQDN
可以看到netlogon
的响应包
参考对照表,0x17
(十进制的23)表示LOGON_SAM_LOGON_RESPONSE_EX
而并不是user unknown
,这里应该是wireshark的问题,LOGON_SAM_USER_UNKNOWN_EX
应该是0x19
因此操作码为0x17
说明用户存在
后记
以上提到的三种方式中,CLDAP是最快的,其实次NetBIOS,最慢的是直接调用DsrGetDcNameEx2 方法,且前两者不会产生日志,最后一种方法会产生匿名登录日志
这个是直接调用rpc方法产生的日志