8

利用MS-SAMR协议修改/重置用户密码

 3 years ago
source link: https://www.anquanke.com/post/id/245482
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
robots

本文为Windows RPC利用系列文章的第一篇,主要介绍关于MS-SAMR的部分利用,在后续的文章中将继续介绍RPC在渗透测试中的应用

作者: Loong716@Amulab

在渗透测试过程中,经常遇到拿到用户的NTLM哈希但无法解密出明文密码的情况。本文介绍并分析一种在仅知道域用户密码哈希时修改用户密码,并在使用完后恢复用户原密码的方法。完成相应工具实现,提出检测方法和缓解措施。

PS:本文提出的场景在实战中也有很多其他的解决办法,但本文仅讨论与changentlm、setntlm相关的内容

0x00 利用

考虑以下几个场景:

  1. 我们拿下域控后,经常要搜集目标架构内用户的各种信息来寻找靶标,比如登录邮箱服务器、OA、NAS等可能使用域身份认证的系统
  2. 我们收集的攻击路径中的其中一环是利用某账户重置/修改目标账户密码
  3. 我们拿到某用户hash后,同样想通过该用户账户登录某系统,但目标系统不支持pth

我们虽然拿到了修改/重置密码的权限,但我们又不想直接修改目标用户的密码,因为这样用户在登录时就会发现自己的密码被修改了,此时有两种情况:

  1. 如果我们有重置密码权限就可以使用SetNTLM来将用户密码重置
  2. 如果有hash的话可以使用ChangeNTLM修改

登录目标系统后,再将目标密码还原

1. SetNTLM

该功能的效果是直接将域用户的密码或hash重置为新的密码或hash

(1) 利用条件

当前身份对要修改的用户有Reset Password权限

(2) Demo

假设我们此时拿到域控,想修改域内用户ntlmtest的密码来登录某系统,先Dcsync看一下用户当前的hash:

由于我们是域管了,基本上对目标用户都是有重置密码权限的,然后利用以下命令重置密码:

lsadump::setntlm /server:<DC's_IP_or_FQDN> /user:<username> /password:<new_password>

登录目标系统以后,再通过以下命令还原密码:

lsadump::setntlm /server:<DC's_IP_or_FQDN> /user:<username> /ntlm:<Original_Hash>

2. ChangeNTLM

(1) 利用条件

需要对目标用户有Change Password权限,但该权限一般是Everyone拥有的,所以基本上拿到目标用户的hash/密码后都可以进行密码更改

(注意此处的更改密码权限并不是说可以直接任意改用户密码,而是在知道用户的密码的情况下更改一个新密码)

该方法受到域内密码策略的限制,比如域内默认的“密码最短使用期限”为1天,因此用户每天只能修改一次自己的密码

而且如果域内存在“强制密码历史”规则时,该方法在恢复原密码时便不能成功,但如果没有“密码最短使用期限”的限制的话,我们多修改几次密码直到原密码在历史中清除,然后再修改为原密码即可

(2) Demo

修改用户test2密码:

lsadump::changentlm /server:<DC's_IP_or_FQDN> /user:<username> /old:<current_hash> /newpassword:<newpassword>

恢复原密码:

lsadump::changentlm /server:<DC's_IP_or_FQDN> /user:<username> /oldpassword:<current_password_plain_text> /new:<original_hash>

0x01 原理

ChangeNTLM和SetNTLM的原理本质都是调用MS-SAMR协议

不同的是ChangeNTLM是调用SamrChangePasswordUser这一API来修改用户密码:

而SetNTLM是通过SamrSetInformationUser来重置用户密码

大体过程是差不多的,只不过核心操作调用API不同,这也是为什么两种方法需要的参数、权限都不同,此处以分析ChangeNTLM为例

虽然原理本质是通过调用RPC,但mimikatz并不是直接调用RPC来修改,而是使用了一组以Sam开头的API,下图所示为部分API:

Mimikatz – kuhl_m_lsadump.c#L2267

最终调用SamiChangePasswordUser来修改用户的密码

Mimikatz – kuhl_m_lsadump.c#L2171

这些API由samlib.dll导出:

查看SamiChangePasswordUser函数调用树,可以看到调用了NdrClientCall3,是不是很熟悉?这明显是进行RPC调用的标志(xpn在他的文章exploring-mimikatz-part-2里有提到过)

我们再看一下调用处的反编译代码,参数刚好可以和SamrChangePasswordUser的操作数对应

其实从流量中也可以看出调用的是MS-SAMR协议:

0x02 实现

实现主要有两种思路,一种是跟mimikatz一样直接调用samlib.dll的导出函数,第二种是直接调用SAMR协议的API

两种方法原理一样,但前者的调用要更加简单,因为samlib里的导出函数对应了SAMR的API,其实相当于SAMR的上层实现,比如SamiChangePasswordUser对应SamrChangePasswordUser,并且参数更加简化

整个过程调用的API作用如下:

  • SamrConnect5: 获取Server对象的句柄
  • SamrEnumerateDomainsInSamServer: 枚举Server上的域名
  • SamrLookupDomainInSamServer: 获取域名对应域的SID
  • SamrOpenDomain: 获取Domain对象的句柄
  • SamrLookupNamesInDomain: 获取指定用户的RID
  • SamrOpenUser: 获取User对象的句柄
  • SamrChangePasswordUser: 修改用户对象的密码

1. 调用samlib的导出函数

原理前面已经提过了,直接调用samlib.dll里对应的导出函数即可,直接嫖参考mimikatz的源码即可实现,源码戳这里

2. 直接调用MS-SAMR

这里以实现changentlm为例,setntlm同理

(1) C语言

微软官方已经把MS-SAMR的IDL给我们了:[MS-SAMR] – Appendix A: Full IDL,直接拿下来使用midl生成.h和.c文件即可(使用时还需要稍作修改):

注意这里有一个坑点,如果对SamrChangePasswordUser只指定第5、6、7个参数的话,会产生STATUS_LM_CROSS_ENCRYPTION_REQUIRED错误:

因此必须再指定LMCrossNewLmEncryptedWithNewNt这两个参数,而后者是用新密码的NTLM Hash加密新密码的LM Hash得到的,这里我一开始很疑惑:从mimikatz的功能来看,并不需要我们传递新密码LM Hash,那么它这个加密操作是怎么完成的呢?

由于LM Hash早已在高版本Windows中弃用,于是我猜测这个LM Hash可能跟新密码并没有关系(比如有些工具需要使用LMHASH:NTHASH的格式来指定hash,但LM Hash的值是多少并没有关系),于是我直接使用新密码的NTLM Hash来加密空密码对应的LM Hash:

...
unsigned char newLM[16];
PCWCHAR newLMHash = "AAD3B435B51404EEAAD3B435B51404EE";
StringToHex(newLMHash, newLM, sizeof(newLM));
status = RtlEncryptLmOwfPwdWithLmOwfPwd(newLM, newNT, &NewLMEncryptedWithNewNT);
if (!NT_SUCCESS(status))
{
    wprintf(L"[!] Calc NewLMEncryptedWithNewNT Error: %08X\n", status);
    exit(1);
}
...

最终成功修改目标用户的密码,Demo实现效果如下,源码戳这里

(2) Impacket

既然是调用RPC,而且刚好impacket对SAMR协议也有实现,所以也可以用impacket来写

完成后我向impacket项目提交了Pull Request,源码可以在commit中看到

效果如下,修改用户的密码:

恢复用户原hash:

0x03 检测与缓解

1. ChangeNTLM

(1) 产生事件

ChangeNTLM会产生4723、4738两条日志,并且日志中的使用者和目标账户并不是同一个账户:

(2) 流量特征

SamrOpenUser这个操作中(操作数为34),Samr User Access Change Password标志位被设置为1,在该步操作中还可以看到用户对应的RID:

以及调用SamrChangePasswordUser(操作数为38):

2. SetNTLM

(1) 产生事件

SetNTLM会产生4724、4661、4738这三条日志:

(2) 流量特征

同样在SamrOpenUser这个操作中(操作数为34),Samr User Access Set Password标志位被设置为1,也可以看到用户对应的RID:

调用SamrSetUserInformation(操作数为37):

3. 缓解措施

对于ChangeNTLM,我们可以通过设置域内密码策略来增大攻击者的利用难度:

  • “密码最短使用期限” >= 1天
  • “强制密码历史” >= 5个

而对于SetNTLM,基本是攻击者拿到较高权限时才会进行的操作,因此主要靠我们前期的一些用户实体行为来检测攻击者的行为,即使其拿到高权限,我们也可以通过事件迅速检测出SetNTLM行为。

0x04 参考

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/4df07fab-1bbc-452f-8e92-7853a3c7e380

https://github.com/gentilkiwi/mimikatz/blob/master/mimikatz/modules/kuhl_m_lsadump.c#L2165

https://stealthbits.com/blog/manipulating-user-passwords-with-mimikatz/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK