This task is an exploit task worth 200 points from the Nuit du Hack qualifications.
We are given a website with a login form, and a link to an other page : update.py
Thxer’s first idea was to look for pyc files.
We then decompiled update.pyc with uncompyle:
# 2015.04.04 01:50:48 CEST import config import sys KEY = config.KEY def xor(*args): if len(args) < 2: sys.exit(0) length = len(args[0]) for arg in args: if len(arg) != length: sys.exit(0) length = len(arg) cipher = args[0] for arg in args[1:]: cipher = ''.join([ chr(ord(arg[i]) ^ ord(cipher[i])) for i in range(len(arg)) ]) return cipher class Crypto: @staticmethod def encrypt(file): with open(file, 'r') as fd: content = fd.read() content = content.ljust(len(content) + (8 - len(content) % 8), '0') blocks = [ content[(i * 8):((i + 1) * 8)] for i in range(len(content) / 8) ] with open('%s.encrypted' % file, 'w') as fd: encrypted = [] for i in range(len(blocks)): if i == 0: encrypted.append(xor(KEY, blocks[i])) else: encrypted.append(xor(KEY, blocks[i], encrypted[(i - 1)])) fd.write(''.join(encrypted)) @staticmethod def decrypt(file): with open(file, 'r') as fd: content = fd.read() blocks = [ content[(i * 8):((i + 1) * 8)] for i in range(len(content) / 8) ] with open('.'.join(file.split('.')[:-1]), 'w') as fd: plain = [] for i in range(len(blocks)): if i == 0: plain.append(xor(KEY, blocks[i])) else: plain.append(xor(KEY, blocks[i], blocks[(i - 1)])) fd.write(''.join(plain).rstrip('0')) print 'Content-Type: text/html' print '\n<!DOCTYPE html>\n<html>\n <head>\n <meta charset="UTF-8">\n <title>Updator - Update system</title>\n <link rel="stylesheet" href="static/font-awesome/css/font-awesome.css">\n <link rel="stylesheet" href="static/css/style.css">\n </head>\n <body>\n <div id="info">\n The update managing system is still under construction but will be available soon.\n </div>\n </body>\n</html>\n'
You know what is fun in challenges ? Guessing for directories.
By guessing the /temp directory, you can find a file, log.py.encrypted.
Since we know the encryption routine, we made a quick script to make a known-plaintext attack.
<?php $file = 'log.py.encrypted'; $bin = file_get_contents($file); function xorStr($a, $b) { $c = ''; $na = strlen($a); $nb = strlen($b); for($i = 0; $i < max($na, $nb); $i++) $c .= chr(ord($a[$i % $na]) ^ ord($b[$i % $nb])); return $c; } // Finding the key : $b = strlen($bin) / 8; $block = array(); $block[] = substr($bin, 0, 8); for($i = 1; $i < $b; $i++) $block[$i] = xorStr($block[$i - 1], substr($bin, $i * 8, 8)); $bin2 = implode('', $block); $known = "import "; $key = substr(xorStr($bin2, $known), 0, strlen($known)); for($i = 0; $i < 8; $i++) { printf("Key : %s\n", $key); printf("Padding : %d\n", $i); printf("Code\n%s\n", xorStr($bin2, $key . str_repeat("\0", $i))); printf("------------------------\n"); } // At this point, we realize that the code starts with import datetime. // We then managed to retrieve the key since there is a repetition with // $known = "import datetime". $key = '6[@dq"&s'; $plain = xorStr($key, substr($bin, 0, 8)); for($i = 1; $i < $b; $i++) $plain .= xorStr(xorStr($key, substr($bin, $i * 8, 8)), substr($bin, ($i - 1) * 8, 8)); var_dump($plain);
Decrypted code is :
import datetime LOG_DIR = 'logs' class Logger(): @staticmethod def log(username, password): basename = '%s/%s_%s' % (LOG_DIR, str(datetime.date.today()), username) with open(basename, 'a+') as fd: fd.write('[%s] Login with password %s\n' % (str(datetime.datetime.today()), password))
By reading file /logs/2015-04-04_admin, we see the following connection attempts :
[2015-04-04 18:49:48.839448] Login with password Mpt2P4sse2Ouf [2015-04-04 18:49:54.044382] Login with password Mot2P4sse2Ouf
If you log in with admin:Mot2P4sse2Ouf, you get the following message :
Well played, here is your flag : zEpbiUFt5p7m84cxOxN6
Flag: zEpbiUFt5p7m84cxOxN6.
You didn’t need to guess the directory, it was referenced in robots.txt.
Thank you for all the writeups!!
Oh ok, indeed. Thanks dude 🙂