LFI2RCE via Eternal waiting
Informazioni di base
Per impostazione predefinita, quando un file viene caricato su PHP (anche se non lo sta aspettando), verrà generato un file temporaneo in /tmp
con un nome come php[a-zA-Z0-9]{6}
, anche se ho visto alcune immagini docker in cui i file generati non contengono cifre.
In una inclusione di file locale, se riesci ad includere quel file caricato, otterrai RCE.
Nota che per impostazione predefinita PHP consente di caricare solo 20 file in una singola richiesta (impostato in /etc/php/<version>/apache2/php.ini
):
Altre tecniche
Altre tecniche si basano sull'attacco ai protocolli PHP (non sarà possibile se si controlla solo l'ultima parte del percorso), sulla divulgazione del percorso del file, sull'abuso dei file attesi, o facendo sì che PHP subisca un errore di segmentazione in modo che i file temporanei caricati non vengano eliminati. Questa tecnica è molto simile alla precedente ma senza la necessità di trovare una vulnerabilità zero day.
Tecnica dell'attesa eterna
In questa tecnica abbiamo solo bisogno di controllare un percorso relativo. Se riusciamo a caricare file e a far sì che l'LFI non finisca mai, avremo "abbastanza tempo" per forzare la ricerca dei file caricati e trovare uno qualsiasi di quelli caricati.
Vantaggi di questa tecnica:
È sufficiente controllare un percorso relativo all'interno di un include
Non richiede nginx o un livello di accesso inaspettato ai file di log
Non richiede una vulnerabilità zero day per causare un errore di segmentazione
Non richiede la divulgazione del percorso
I principali problemi di questa tecnica sono:
È necessario che un file specifico (potrebbero essercene di più) sia presente
La quantità folle di nomi di file potenziali: 56800235584
Se il server non utilizza cifre, il totale potenziale è: 19770609664
Per impostazione predefinita, solo 20 file possono essere caricati in una singola richiesta.
Il numero massimo di worker paralleli del server utilizzato.
Questo limite insieme ai precedenti può far durare troppo a lungo questo attacco
Timeout per una richiesta PHP. Idealmente questo dovrebbe essere eterno o dovrebbe interrompere il processo PHP senza eliminare i file temporanei caricati, altrimenti sarà un problema anche questo
Quindi, come si può far sì che un include PHP non finisca mai? Semplicemente includendo il file /sys/kernel/security/apparmor/revision
(purtroppo non disponibile nei container Docker).
Prova a chiamarlo semplicemente:
Apache2
Di default, Apache supporta 150 connessioni simultanee, seguendo https://ubiq.co/tech-blog/increase-max-connections-apache/ è possibile aumentare questo numero fino a 8000. Segui questo per utilizzare PHP con quel modulo: https://www.digitalocean.com/community/tutorials/how-to-configure-apache-http-with-mpm-event-and-php-fpm-on-ubuntu-18-04.
Di default, (come posso vedere nei miei test), un processo PHP può durare eternamente.
Facciamo un po' di calcoli:
Possiamo utilizzare 149 connessioni per generare 149 * 20 = 2980 file temporanei con la nostra webshell.
Quindi, utilizzare l'ultima connessione per forzare potenziali file.
A una velocità di 10 richieste al secondo i tempi sono:
56800235584 / 2980 / 10 / 3600 ~= 530 ore (50% di probabilità in 265h)
(senza cifre) 19770609664 / 2980 / 10 / 3600 ~= 185h (50% di probabilità in 93h)
Nota che nell'esempio precedente stiamo completamente facendo un DoS ad altri client!
Se il server Apache viene migliorato e potremmo abusare di 4000 connessioni (metà del numero massimo). Potremmo creare 3999*20 = 79980
file e il numero sarebbe ridotto a circa 19.7 ore o 6.9 ore (10 ore, 3.5 ore 50% di probabilità).
PHP-FMP
Se invece di utilizzare il modulo php regolare per apache per eseguire gli script PHP la pagina web sta utilizzando PHP-FMP (questo migliora l'efficienza della pagina web, quindi è comune trovarlo), c'è qualcos'altro che può essere fatto per migliorare la tecnica.
PHP-FMP consente di configurare il parametro request_terminate_timeout
in /etc/php/<php-version>/fpm/pool.d/www.conf
.
Questo parametro indica il massimo numero di secondi quando la richiesta a PHP deve terminare (infinito per impostazione predefinita, ma 30s se il parametro è scommentato). Quando una richiesta viene elaborata da PHP per il numero di secondi indicato, viene terminata. Ciò significa che se la richiesta stava caricando file temporanei, poiché l'elaborazione php è stata interrotta, quei file non verranno eliminati. Pertanto, se riesci a far durare una richiesta quel tempo, puoi generare migliaia di file temporanei che non verranno eliminati, il che accelererà il processo di trovarli e ridurrà la probabilità di un DoS alla piattaforma consumando tutte le connessioni.
Quindi, per evitare un DoS supponiamo che un attaccante utilizzerà solo 100 connessioni contemporaneamente e il tempo massimo di elaborazione php tramite php-fmp (request_terminate_timeout
) è 30s. Pertanto, il numero di file temporanei che possono essere generati al secondo è 100*20/30 = 66.67
.
Quindi, per generare 10000 file un attaccante avrebbe bisogno di: 10000/66.67 = 150s
(per generare 100000 file il tempo sarebbe 25 minuti).
Quindi, l'attaccante potrebbe utilizzare quelle 100 connessioni per eseguire una ricerca brute-force. **** Supponendo una velocità di 300 req/s il tempo necessario per sfruttare questo è il seguente:
56800235584 / 10000 / 300 / 3600 ~= 5.25 ore (50% di probabilità in 2.63h)
(con 100000 file) 56800235584 / 100000 / 300 / 3600 ~= 0.525 ore (50% di probabilità in 0.263h)
Sì, è possibile generare 100000 file temporanei in un'istanza di dimensioni medie EC2:
Nota che per attivare il timeout sarebbe sufficiente includere la pagina LFI vulnerabile, in modo che entri in un loop di inclusione eterno.
Nginx
Sembra che di default Nginx supporti 512 connessioni parallele contemporaneamente (e questo numero può essere migliorato).
Last updated