| 1 | # Joern query runner - how to invoke each script and read the output |
| 2 | |
| 3 | These are the Cyclops v2 **dataflow-confirmation** scripts. Each takes a source path, builds (or |
| 4 | loads) a CPG, and runs `reachableByFlows` source→sink taint queries for one sink class. |
| 5 | |
| 6 | > A `FINDING:` line is a **reachable dataflow path = HYPOTHESIS at SOURCE_CONFIRMED**. It is **never** |
| 7 | > a finding. Only the dynamic oracle (crash / popped shell / captured secret) promotes it. See |
| 8 | > `README.md` and `vault/00-doctrine/SEVERITY-GATE.md`. |
| 9 | |
| 10 | ## Binary |
| 11 | |
| 12 | ``` |
| 13 | /opt/joern/joern-cli/joern # symlinked to `joern` on the forge box |
| 14 | ``` |
| 15 | |
| 16 | ## Invoke one script against a target |
| 17 | |
| 18 | ```bash |
| 19 | joern --script /shared/projects/cyclops-v2/brain/joern-queries/<script>.sc \ |
| 20 | --param inputPath=/abs/path/to/target/src \ |
| 21 | --param projectName=mytarget # optional; defaults per-script |
| 22 | ``` |
| 23 | |
| 24 | `importCode` auto-detects the language (C/C++, Java/JVM, JS/TS, Python, Go, …). Point `inputPath` at |
| 25 | the repo root (or the relevant subtree). First run builds the CPG (slow on large trees); the |
| 26 | `projectName` workspace is cached under `~/.joern/workspace` for reuse. |
| 27 | |
| 28 | ### Scripts and what each targets |
| 29 | |
| 30 | | Script | Languages | Sink class (SEVERITY-GATE family) | |
| 31 | |---|---|---| |
| 32 | | `c-memory-corruption.sc` | C/C++ | strcpy/memcpy/sprintf/alloca/int-overflow-alloc/array-index (F1 RCE, F7) | |
| 33 | | `deserialization-rce.sc` | Python / Java / JS | pickle/yaml/torch/joblib/ObjectInputStream/node-serialize (F1) | |
| 34 | | `command-injection.sc` | Py / JS / Java / C | os.system/subprocess(shell=True)/child_process/Runtime.exec/eval (F1) | |
| 35 | | `path-traversal.sc` | Py / JS / Java / C | open/sendFile/extractall(zip-slip)/fopen (F7) | |
| 36 | | `ssrf.sc` | Py / JS / Java / C | requests/axios/fetch/urllib/curl tainted URL (F7) | |
| 37 | | `template-injection.sc` | Py / JS / Java | render_template_string/Handlebars.compile/Freemarker (F1 SSTI) | |
| 38 | | `js-electron-rce.sc` | JS/TS | ipcMain handler arg / custom-protocol arg → exec/eval/shell (F1) | |
| 39 | |
| 40 | ## Reading the output |
| 41 | |
| 42 | Each script prints machine-parseable lines on stdout: |
| 43 | |
| 44 | ``` |
| 45 | CANDIDATE_SINK: <file>:<line> class=<class> code=<sink call source text> |
| 46 | CANDIDATE_SINKS[<class>]=<n> |
| 47 | FINDING: <file>:<line> class=<class> source=<tainted source code> sink=<sink code> |
| 48 | DATAFLOW_PATHS[<class>]=<n> |
| 49 | CONFIG_CANDIDATE: <file>:<line> class=<class> code=<...> # electron only |
| 50 | SCAN_COMPLETE[<script>] |
| 51 | ``` |
| 52 | |
| 53 | - **`CANDIDATE_SINK` / `CANDIDATE_SINKS[..]`** - the sink call exists in the code. No taint proven |
| 54 | yet. This is the same signal the semgrep fast-pass gives; useful when dataflow under-resolves |
| 55 | (e.g. Python dynamic dispatch). Feed to manual review / the oracle's "reachability unknown" queue. |
| 56 | - **`FINDING` / `DATAFLOW_PATHS[..]`** - Joern found a taint path from an untrusted source to the |
| 57 | sink. The class suffix tells you the source set: `<-input` (IO/web/CLI calls), `<-argv` (C main), |
| 58 | `<-io` / `<-params` (dynamic-lang IO calls vs entrypoint parameters), `<-ipc` / `<-proto` / |
| 59 | `<-anyparam` (electron). `[metadata-reachable]` on an SSRF line means the path can reach a cloud |
| 60 | metadata host - prioritize. |
| 61 | - **`DATAFLOW_PATHS[..]=0` with `CANDIDATE_SINKS[..]>0`** - sink present but no taint path resolved. |
| 62 | Do **not** discard: dataflow can under-approximate (see limitations). Keep as a lower-priority |
| 63 | candidate for the oracle. |
| 64 | |
| 65 | ### Parse to JSONL (example) |
| 66 | |
| 67 | ```bash |
| 68 | joern --script .../command-injection.sc --param inputPath=/t/src 2>/dev/null \ |
| 69 | | grep '^FINDING:' \ |
| 70 | | sed -E 's/^FINDING: ([^ ]+) class=([^ ]+) source=(.*) sink=(.*)$/{"loc":"\1","class":"\2","source":"\3","sink":"\4"}/' \ |
| 71 | > candidates.jsonl |
| 72 | ``` |
| 73 | |
| 74 | Then hand `candidates.jsonl` to the orchestrator, which routes each to the dynamic oracle for proof. |
| 75 | |
| 76 | ## Batch all scripts over a target |
| 77 | |
| 78 | ```bash |
| 79 | TGT=/abs/path/to/target/src |
| 80 | for s in c-memory-corruption deserialization-rce command-injection path-traversal ssrf template-injection js-electron-rce; do |
| 81 | echo "### $s" |
| 82 | joern --script /shared/projects/cyclops-v2/brain/joern-queries/$s.sc --param inputPath="$TGT" 2>/dev/null \ |
| 83 | | grep -E '^(FINDING|DATAFLOW_PATHS|CANDIDATE_SINKS|SCAN_COMPLETE)' |
| 84 | done |
| 85 | ``` |
| 86 | |
| 87 | (The C and electron scripts no-op cleanly on the wrong language - empty candidate sets - so running |
| 88 | the full sweep is safe; only the language-matching ones produce output.) |
| 89 | |
| 90 | ## Limitations / fallbacks (honesty) |
| 91 | |
| 92 | - **Python / JS dynamic dispatch & aliasing.** `from pickle import loads as L; L(x)` or attribute |
| 93 | calls through variables can hide the sink from `.name(...)`. The deser/cmdi/ssti/path scripts |
| 94 | therefore match **both** the method name and the full call `.code` regex, and always print |
| 95 | `CANDIDATE_SINK`. Inter-procedural taint through deep callback chains may still under-resolve - |
| 96 | the candidate list is the safety net. |
| 97 | - **`shell=True` for subprocess** is not cheaply readable as a keyword value across Joern versions. |
| 98 | We flag all subprocess sinks and add a `py-subprocess-shelltrue` refinement that looks for a |
| 99 | literal `True`/`sh` argument near the call. Treat subprocess FINDINGs without confirmed |
| 100 | `shell=True` as lower confidence. |
| 101 | - **Array-index OOB & pure integer overflow** are not first-class taint sinks; the C script |
| 102 | approximates them via index operators and allocator size args. Lower confidence - the oracle's |
| 103 | ASan run is the arbiter. |
| 104 | - **Electron inline arrow callbacks** may not always resolve via `MethodRef` in every Joern JS |
| 105 | frontend version; `js-electron-rce.sc` adds an `<-anyparam` over-approximation source as a net. |
| 106 | - **Over-approximation is intentional.** More candidate paths = more hypotheses. The gate is the |
| 107 | oracle, not the query. A noisy candidate list is acceptable; a missed sink is not. |