Cache Poisoning
La configurazione del caching sul server web unito a vulnerabilità dell'applicazione possono diventare un problema critico per gli utenti. Per comprendere a fondo come sfruttare questo tipo di vulnerabilità è necessario comprendere a fondo come funziona il caching dei contenuti su un server web.
Caching 101
La necessità di avere contenuti in cache nasce da due principali esigenze: la prima è quella rendere più veloce il caricamento di una pagina abbassando la latenza. La seconda è quella di alleggerire il carico del server (upstream) che ospita l'applicazione o il sito web facendo in modo che riceva meno request HTTP. Il caching può avvenire sia a livello di webserver oppure, come in alcuni CMS, built-in sull'applicazione.
Ammettiamo che la nostra applicazione o il nostro sito siano su un server web non esposto direttamente su internet, e che riceva le request da un reverse proxy Nginx. La cache viene validata secondo specifiche configurazioni che ne determinano la scadenza e la chiave di indicizzazione. Come si intuisce dallo schema sopra, l'utente 1 richiede il contenuto la cui response è configurata per essere salvata in cache da Nginx. L'utente 2, poco dopo, richiede lo stesso contenuto che verrà servito direttamente da Nginx senza più dover chiedere all'upstream. Stessa cosa avviene per i successivi utenti fino alla scadenza della cache.
Poisoning: come avviene?
Proseguendo con il nostro esempio, la cache salvata sul nostro Nginx viene indicizzata secondo una cache-key che permette al webserver di riproporre la response sulla base della request HTTP ricevuta. Ad esempio, una cache-key può essere un hash prodotto dalla concatenazione di: $scheme$request_method$host$request_uri
che potrebbero corrispondere a httpsGETexample.com/home
in questo modo una request GET verso https://example.com/home otterrebbe il contenuto in cache.
Ammettiamo ora che il sito ospitato sull'upstream sia vulnerabile a un reflected XSS. Se iniettassi script JavaScript all'interno della response (anche solo reflected) e questo fosse messo in cache, tutte le successive visite allo stesso contenuto potrebbero ricevere la response da me modificata.
Configurazione della cache su Nginx
Come si può dedurre dallo schema precedente, proxy_cache_key
rappresenta la cache-key con cui Nginx indicizzerà le response HTTP sul filesystem. proxy_cache_valid
indica come Nginx deve stabilire se una response può essere salvata in cache e per quanto tempo deve rimanerci. Nel caso dello schema, una response è valida per essere inserita in cache se il response code è 200, 301 o 302 e può essere utilizzata per 1 giorno, mentre se il response code è 404 allora potrà essere utilizzata per 1 minuto. proxy_cache_use_stale
permette a Nginx di utilizzare una response in cache scaduta (o non più valida) in particolari situazioni, ad esempio se l'upstream non risponde più. La variabile $upstream_cache_status
permette di controllare lo stato di un contenuto in cache. Può avere diversi valori tra cui: MISS (la response non proviene dalla cache), HIT (la response proviene dalla cache), EXPIRED (la response in cache non è più valida).
Ma come vengono salvate le response HTTP in cache? Abbiamo detto che Nginx indicizza i contenuti in cache secondo una cache-key, ecco un esempio: