轻松理解 NTLM 协议工作流程

2020-06-16 14:48:10 浏览数 (1)

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 这个协议的工作流程,也没有涉及某某字段的意思,想要深入理解的可以自己去找相关资料深入研究。

0 人点赞