NTLM 使用在 Windows NT 和 Windows 2000 Server(or later)工作组环境中(Kerberos 用在域模 式下)。
在 AD 域环境中,如果需要认证 Windows NT 系统,也必须采用 NTLM。相比 Kerberos,基于 NTLM 的认证过程要简单很多。NTLM 采用一种质询/应答(Challenge/Response)消息交换模式,下图是 NTLM 的认证过程:
NTLM 认证协议可以使用在各种协议中,比如 HTTP、SMB 等等,下面以 HTTP 来说明其具体认证流 程:
先来找个目标吧,也可以自己搭建一个基于域的 401 认证服务,我这里测试就找个实际的环境来吧,通 常 exchange 的邮件服务器都会有 ews 的接口,我们可以随便找一个做测试。
在 bing 上使用语法搜一下:
/owa/auth/logon.aspx
找一个邮件服务器是用 exchange 搭建的,并且支持 http 的,因为转包查看的话,https 经过加密的包不太好看,我这找了一个:
http://ex.myhosting.com/ews
开启 wireshark,然后访问上面的连接,随便输入账号和密码,点击登录之后,会得到一些 http 的数据包,如图:
验证流程分三步
第一步:获取服务器的一些基本信息,比如:认证的协议、服务器版本,如图:
第二步:获取服务器返回给客户端的 challenge 值,如图:
这个值在后面的数据校验中会使用,这里先不做过多介绍
第三步:根据前面获取的数据,组合之后提交给服务器,在服务器端进行验证
这里的 NTProofStr 是一个用做数据验签的 hash 值,为了保证前面获取的 challenge 和后面的数据是完整的未经过修改的。
我们把上图中 NTLMv2 的 Response 数值复制处理如下:
c9ce34e95466b7e956dd63da0b17e2e801010000000000008351a367173bd601afd3469d61d432470000000002000800430041004500580001000e0045005800430041005300310032000400140063006100650078002e006c006f00630061006c000300240045005800430041005300310032002e0063006100650078002e006c006f00630061006c000500140063006100650078002e006c006f00630061006c00070008008351a367173bd601060004000200000008003000300000000000000001000000002000002cd5deecf08e9ba9b3e2e91b120999526fba9ded3b72687fcae5218d3b4301120a0010000000000000000000000000000000000009002a0048005400540050002f00650078002e006d00790068006f007300740069006e0067002e0063006f006d000000000000000000
最前面的 32 个字符就是验证数据完整性的 hash 值,也就是 NTProofStr 的值,后面的数据中包含了一些服务器的信息,如图:
这些数据是第三步的请求阶段,也就是客户端提交给服务器的数据,服务器会拿这些数据进行验证,判断是否验证通过。
验签 hash 如何产生
从上面我们没有看到账号密码信息在哪里发挥作用,下面我们来看看具体账号密码在哪里被使用,在哪里发挥作用的。
我们在内网渗透的时候,经常会抓本地密码,本地存储的 hash 就是用户密码经过一系列加密后的结果,具体加密方式如下:
NTLM hash = md4(unicode(hex(password)))
函数解释:hex 为将字符串转为 hex 值、unicode 是将字符串转为双字节字符串、md4 为 md4 hash
用 python 为例来编写关键函数的代码如下:
hex 函数:
代码语言:javascript复制def str_to_hex(s):
return ' '.join([hex(ord(c)).replace('0x', '') for c in s])
转双字节函数:
代码语言:javascript复制def hex_to_unicode(hex_str):
hex_str_ = hex_str.replace(" ","00") "00"
return hex_str_
md4 hash 函数:
代码语言:javascript复制from Crypto.Hash import MD4
def str_to_md4(str_):
m = MD4.new()
m.update(str_)
return m.hexdigest()
只有密码不行,怎么把用户名和域信息给用上呢?这里就涉及了一个 NTLMv2 HASH ,具体算法如下:
NTLMv2 hash = md5(unicode(hex(upper(username domain))), ntlm)
关键函数:upper 是将字符串都转为大写字母、md5 就是将 ntlm 的 hash 作为 salt,对用户名和域组合的信息进行 md5 hash
md5 hash 函数:
代码语言:javascript复制import hmac
import hashlib
def str_to_hmac_md5(passwd_,salt_):
h_md5 = hmac.new(salt_, passwd_, hashlib.md5).hexdigest()
return h_md5
从上面的过程来看,最后得到的 NTLMv2 HASH 就包含了账号、密码、域的信息,有一个因素有问题,那么整个验证就不成功,那么这 hash 在后面怎么用?
服务器如何验证客户端提交的信息
最后一步,客户端提交的信息包括:
签名 hash NTProofStr
用来验证数据完整性,数值为:c9ce34e95466b7e956dd63da0b17e2e8
还有一串数据,在上面已经提供,前面的 32 位的字符串就是上面签名的 hash,去掉之后就是服务器相关的数据,提取出来之后如下:
01010000000000008351a367173bd601afd3469d61d432470000000002000800430041004500580001000e0045005800430041005300310032000400140063006100650078002e006c006f00630061006c000300240045005800430041005300310032002e0063006100650078002e006c006f00630061006c000500140063006100650078002e006c006f00630061006c00070008008351a367173bd601060004000200000008003000300000000000000001000000002000002cd5deecf08e9ba9b3e2e91b120999526fba9ded3b72687fcae5218d3b4301120a0010000000000000000000000000000000000009002a0048005400540050002f00650078002e006d00790068006f007300740069006e0067002e0063006f006d000000000000000000
在之前第二步的时候,获得了一个 challenge 的值: e8fd1257ab09ae57
到这里,该有的数据都有了,那么如何这些数据之间又有什么关系?主要是签名的获取算法,公式如下:
签名数据
NTProofStr
= md5(challenge 数据, NTLMv2 HASH)
理解上也简单,就是将第二步的 challenge 的值和服务器的数据连接起来,用包含账号密码域信息的 hash 作为 salt 进行 md5 哈希之后的结果就是签名的值,我们用一个小巧的计算器来计算下:
以上就是 NTLM 协议组成验证数据的过程,最后数据发送到服务器端,服务器根据提交的数据进行类似的加密验证,从而判断用户提交的凭证是否正确,整个过程保证了认证的安全性。
总结
在服务器端,保存的不是用户的明文密码,而是密码经过 NTLM 加密后的字符串,因为是 hash 算法,所以不可逆,对于 NTLM 的哈希只能进行暴力枚举无法进行解密。
如果 401 认证的服务是 http 的协议,我们可以在流量包中截获相关数据包,比如 challenge 的值、NTLMv2 的返回包、用户名、域名,有了这些信息,就可以通过暴力枚举的方式破解用户的密码,如果 https 协议,我们就无法通过数据包来截获明文的数据包了,所以 https 的是多么重要。
这不是一个攻击的文章,旨在理解 NTLM 这个协议的工作流程,也没有涉及某某字段的意思,想要深入理解的可以自己去找相关资料深入研究。