
Found a 0-Day in Ghidra: Shared Project File Became a Code Execution Vector
Advisory: GHSA-pcfh-853f-q3gh · Severity: High (CVSS 7.5) · Affected: Ghidra ≤ 12.1.2 · Fixed: c03a70d (Ghidra 12.1.3)
Opening someone else's Ghidra project was enough to execute their code.
Ghidra 12.1.2 stores analyzer options inside the program database, and one of those options is a filesystem path to a Swift toolchain directory. When analysis runs, Ghidra joins that path with swift-demangle and executes it no signature check, no integrity check, no prompt. A shared project, an imported .gzf, or a cloned research repo silently carries the attacker's path with it.
The result is arbitrary code execution in the Ghidra user's context. On headless and CI setups, there is no human in the loop at all.
📖 Full technical writeup: https://sn0xs-organization.gitbook.io/sn0x-order.org/bb-web-hunt/critical/how-i-found-a-0-day-in-ghidra-shared-project-file-became-a-code-execution-vector.
The Swift demangler analyzer restores a configurable Swift tool directory from saved program state, and the native demangler wrapper executes whatever binary resolves under it.
// SwiftDemanglerAnalyzer.java — value restored from program-persisted state
String swiftDir = options.getString(SWIFT_TOOL_DIR_OPTION, null);
SwiftNativeDemangler demangler = new SwiftNativeDemangler(new File(swiftDir));
// SwiftNativeDemangler.java — the resolved binary is executed twice
private File swiftDemanglerPath = new File(swiftToolDir, "swift-demangle");
new ProcessBuilder(swiftDemanglerPath.getAbsolutePath(), "--version").start(); // SINK 1
new ProcessBuilder(swiftDemanglerPath.getAbsolutePath(), mangled).start(); // SINK 2
SINK 1 is a validation probe that fires during initialization attacker code runs before a single symbol is demangled.
CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H → 7.5 High
CWE-427 Uncontrolled Search Path Element (primary)
CWE-494 Execution of Code Without Integrity Check (related)
A single auditable research tool packaging the code-execution conditions found while reviewing Ghidra 12.1.2. The tool is deliberately precise about classification three reviewed surfaces, three different risk levels (full detail in docs/classification.md):
| Mode | Surface | Classification |
|---|---|---|
swift | Swift demangler process-launch sink (the accepted advisory) | Conditional ACE local code execution when a configured/restored Swift tool directory holds attacker content |
tracermi | TraceRMI debugger-agent execute / pyeval sinks | Conditional RCE code execution once an untrusted peer can drive an agent channel |
sevenzip | SevenZipJBinding native archive parser reachability | RCE-class parser exposure source reachability only, no code-execution claim |
Source-to-sink evidence for each is in evidence/source-evidence.md.
Responsible-disclosure tool. Nothing here is networked or destructive. Every process launch is opt-in. The traditional harmless calculator is the only "arbitrary process" it will ever spawn, and only when you explicitly ask for it. The vulnerability it demonstrates is patched.
git clone https://github.com/sn0x-sharma/CVE-2026-18718.git
cd CVE-2026-18718
python3 CVE-2026-18718-POC.py --mode swift --execute --launch-calc
10:15:09 | INFO | swift-ace: launching fake demangler as Ghidra would
Swift demangler calc PoC (sn0x-sharma)
10:15:09 | INFO | [PASS ] swift-ace: Swift demangler sink reproduced; attacker binary executed.
10:15:09 | INFO | - Execution marker: artifacts/swift-demangler-calc/swift_demangler_calc_marker.txt
cat artifacts/swift-demangler-calc/swift_demangler_calc_marker.txt
ran with: --version
That marker is the whole advisory. --version is the argument Ghidra passes at SINK 1, so the fake binary executed in exactly the position the real attacker binary would.
Python 3.9+, standard library only. No third-party dependencies.
A Ghidra 12.1.2 source tree is optional needed only for the tracermi and sevenzip modes. The swift mode is fully self-contained and fabricates its own fake toolchain.
python3 CVE-2026-18718-POC.py --help
With no flags every applicable mode runs in dry-run it stages the fake Swift tool and scans source, but launches nothing:
python3 CVE-2026-18718-POC.py
--execute runs the fake swift-demangle exactly as Ghidra would, and drops a marker proving the attacker binary ran. --launch-calc makes that fake binary spawn the platform calculator:
python3 CVE-2026-18718-POC.py --mode swift --execute # marker only
python3 CVE-2026-18718-POC.py --mode swift --execute --launch-calc # marker + calc
Note that the calculator is spawned by the fake tool, not by the harness in the real bug the attacker's binary is what executes, so in the PoC the attacker's binary is what pops calc.
Resolved from --ghidra-source, then $GHIDRA_SOURCE, then a nearby ghidra-12.1.2/ directory:
python3 CVE-2026-18718-POC.py --ghidra-source /path/to/ghidra-12.1.2
export GHIDRA_SOURCE=/path/to/ghidra-12.1.2
python3 CVE-2026-18718-POC.py --mode tracermi
python3 CVE-2026-18718-POC.py --mode sevenzip --ghidra-source /path/to/ghidra-12.1.2 --harmless-zip
| Flag | Effect |
|---|---|
--mode {swift,tracermi,sevenzip,all} | Select a surface (default all) |
--ghidra-source PATH | Ghidra 12.1.2 source tree |
--artifacts-dir PATH | Evidence output directory (default ./artifacts) |
--execute | Actually launch local sinks (else dry-run) |
--launch-calc | Allow the benign calculator to spawn (implies --execute) |
--harmless-zip | Emit a benign ZIP during sevenzip mode |
-v, --verbose | Debug-level logging |
--version | Print tool version |
Everything lives in one entrypoint, CVE-2026-18718-POC.py, split into focused, independently-testable classes:
CVE-2026-18718-POC.py
├── PlatformProfile OS-specific behaviour (calc command, script header, chmod), resolved once
├── ResearchConfig run configuration + Ghidra-source resolver
├── SourceScanner read-only substring scanner over a Ghidra tree (file:line)
├── EvidenceCollector owns artifacts/, records every file written
├── ResearchComponent base contract for a reviewed surface
│ ├── SwiftAnalyzer swift-ace — conditional ACE (self-contained)
│ ├── TraceManager tracermi-rce — conditional RCE evidence + calc-only payload shapes
│ └── SevenZipProbe sevenzip-reachability — native parser reachability chain
├── EnvironmentValidator pre-flight checks (Python version, source tree, calc availability)
├── ReportGenerator deterministic summary → log + artifacts/summary.txt
└── ResearchRunner orchestration: validate → run → report → exit code