Linguaggi di templating

Durante lo sviluppo di applicativi web più o meno complessi, spesso si rende necessario poter inserire dati dinamici all'interno di pagine. Ad esempio, si potrebbe voler mostrare un messaggio del tipo "Benvenuto, [nome utente]", in cui il valore di "nome utente" deriva dalla sessione corrente.

Naturalmente, contenuti dinamici possono essere molto più complessi di un semplice messaggio di benvenuto. Al fine di semplificare lo sviluppo e l'inserimento di tali dati all'interno delle pagine, vengono in aiuto i cosiddetti motori di templating, o template engine in inglese. Con questi si può definire un template mediante l'utilizzo di un linguaggio di templating, rappresentate la sola parte grafica della pagina, contenente dei segnaposto da essere riempiti con i dati dinamici provenienti dal server.

Quindi, ad esempio, potremmo scrivere un template di questo tipo:

<div>Benvenuto, {{nome}}!</div>

Il motore di templating poi ci rende possibile generare l'HTML definitivo contenente i nostri dati dinamici mediante un processo di compilazione e rendering del template, a cui vengono passati i valori da inserire nei segnaposto, qualcosa del tipo template.render({ nome: 'Mario Rossi' }).

I motori di templating sono uno strumento fondamentale per la realizzazione di applicativi web complessi, e, oltre a semplificare il processo di sviluppo, sono in grado di garantire anche determinati meccanismi di sicurezza. Tutti i motori di templating, infatti, solitamente di default, inseriscono i dati nei segnaposto effettuando opportune sanificazioni, che permettono di evitare attacchi quali XSS.

Tuttavia, sebbene i motori di templating possano essere usati a nostro favore per migliorare la sicurezza dei nostri applicativi, un utilizzo improprio può portare a vulnerabilità estremamente gravi.

Espressioni complesse

Quelli chiamati fin'ora "segnaposto", nella maggior parte dei motori di templating, sono in realtà molto di più. Si tratta infatti di espressioni che nel caso semplice contengono semplicemente il nome di una variabile (il segnaposto), ma in casi più complessi possono essere utilizzate per calcolare valori composti.

Fingiamo ad esempio di voler avere un'interfaccia in cui il nome utente è mostrato tutto in maiuscolo e la mail in minuscolo. Tale trasformazione potrebbe essere fatta a livello di codice prima di passare i valori al template, ma trattandosi di un'esigenza puramente legata all'interfaccia, e volendo separare questa dalla logica di business, ha più senso che il template accetti valori in qualsiasi formato e li converta lui in base alle proprie esigenze. Avremmo così, ad esempio

<div>Benvenuto, {{ nome|upper }} ({{ email|lower }})!</div>

Un altro esempio potrebbe essere il template di una pagina "carrello" di un negozio online, in cui vogliamo calcolare in maniera dinamica il prezzo totale di un prodotto, tenendo conto del prezzo unitario e della quantità aggiunta al carrello:

<div>
	<div>{{ item.name }}</div>
	<div>Costo unitario: {{ item.price }}</div>
	<div>Quantità: {{ item.quantity }}</div>
</div>
<div>
	<strong>Totale: {{ item.price * item.quantity }}</strong>
</div>

Molti motori di templating, per comodità, invece di inventare un nuovo particolare linguaggio, limitato nelle sue capacità, da utilizzare all'interno delle espressioni, permettono di utilizzare codice arbitrario. Così abbiamo che un motore di templating per Java ci permette di scrivere qualsiasi espressione valida in Java, un linguaggio per Python qualsiasi espressione Python, e così via. Queste espressioni vengono valutate e il loro valore inserito nel template.

Template injection

Poiché molti template permettono di eseguire codice arbitrario, è di fondamentale importanza far sì che nessun input utente venga compilato e renderizzato come template, ma che venga solo passato all'interno di variabili passate al template durante la fase di rendering. Nel caso in cui un attaccante fosse in grado di inserire input arbitrario all'interno di un template ci troveremmo davanti a una vulnerabilità di tipo Template injection (talvolta abbreviata in SSTI, ovvero Server Side Template Injection).

L'impatto di tale vulnerabilità dipende dal motore di templating utilizzato, in particolare dalla tipologia di espressioni di cui permette la valutazione e dai costrutti del linguaggio accessibili da dentro il template. Nei casi più gravi, ma spesso molto comuni, template injection può portare a Remote Code Execution (RCE), ovvero la possibilità per un attaccante di eseguire codice totalmente arbitrario sulla macchina bersaglio, permettendo anche di uscire dallo scope del servizio web attaccato. Ciò avviene generalmente sfruttando i costrutti del linguaggio in uso per eseguire comandi su una shell (ad esempio con funzioni tipo system()).

Vediamo ora alcuni esempi di template injection con alcuni dei motori di templating più comuni.

Java - Spring Framework

Il framework Spring permette l'utilizzo di diversi delimitatori per le espressioni all'interno dei template, quali ${...}, #{...}, *{...}, @{...}, ~{...}. Tutti questi possono essere testati per una template injection. Per ottenere RCE si può sfruttare il seguente payload, dove id è il comando che vogliamo eseguire:

*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}

Java - Expression Language

Expression Language è un importante elemento di JavaEE che permette di facilitare l'interazione tra la logica di business e le view (come le pagine web) presentate all'utente. Come Spring permette l'utilizzo di diversi delimitatori, quali ${...}, ${{...}}, #{...}. Un modo per ottenere RCE è il seguente:

${"".getClass().forName("java.lang.Runtime").getRuntime().exec("id")}

.NET - Razor

Razor è uno strumento di .NET per la generazione di pagine tramite template e scriping. Il delimitatore utilizzato per le espressioni è @. Un'espressione di esempio potrebbe essere quindi @(7*7). Per ottenere RCE si può utilizzare il seguente payload:

@System.Diagnostics.Process.Start("cmd.exe","/c dir");

Python - Jinja2

Jinja2 è il motore di templating più comune in Python. Il delimitatore utilizzato è {{...}}. Ottenere RCE in Jinja è generalmente sempre possibile, ma non è sempre immediato scrivere il payload corretto, in quanto dipende dalla versione di Python, Jinja e dalle librerie installate. Un esempio di payload potrebbe essere:

{{''.__class__.mro()[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()[0].strip()}}

PHP - Twig

Twig è un motore di templating per PHP. Anche il suo delimitatore è {{...}}. Per ottenere RCE si può utilizzare un payload come il seguente:

{{['id']|filter('system')}}

Riconoscere la presenza di template injection

Riconoscere la presenza di una template injection non è sempre necessariamente immediato, specialmente se non si conosce quale tecnologia si ha di fronte. Come si è visto, infatti, diversi linguaggi di templating utilizzano diversi delimitatori, e diversi linguaggi richiedono payload particolarmente diversi.

Un primo tentativo consiste nell'inserire all'interno dell'input utente sequenze di caratteri che potrebbero definire espressioni all'interno di template, come ad esempio ${{}}. Studiando la risposta del server si può notare la presenza di injection. Un indicatore potrebbe essere la presenza di un errore del server, o l'assenza dei caratteri ${{}} dall'output, che essendo stati valutati dal motore di templating non fanno parte della stringa ritornata.

Alcuni trucchi possono essere utilizzati per riconoscere il linguaggio che si ha di fronte. Ad esempio, inserire un payload come {{7*'7'}} permette di definire facilmente se il server utilizza Python o meno. La maggior parte dei linguaggi, infatti, nel valutare l'espressione 7*'7' convertono la stringa '7' in numero e restituiscono 49; Python, invece, riconosce '7'*7 come valida operazione tra stringa e numero e restituisce 7777777. Se inserendo {{'7'*7}} nell'input utente vediamo quindi come risultato del server la stringa 7777777 possiamo ritenerci abbastanza sicuri di avere di fronte una template injection in Python.