Because in the end, what does matter is having fun.
CSAW CTF 2015: Lawn Care Simulator
It’s CTF season! and CSAW CTF, one of the funniest CTFs that takes place every year in september has ended this weekend, here is a writeup for one of the web challenges that were provided during the contest.
This 200 points challenge was a web application running a nicely useless javascript for “growing lawn”, and was providing all the typical features available in a standard web application, including registration and login.
The objective of this challenge was to login as admin.
Clone all the things
By looking at the index source code we could notice that the version number, present in the bottom corner of the page, was calculated based on the git repository hash.
Since the .git directory is available in the webroot, we can crawl it and download the entire repository, to obtain the source code and spot bugs.
To make my life easier i used DVCS-Pillage: https://github.com/evilpacket/DVCS-Pillage
to automate the whole process, and at the end i had all the website sources available to me, except.. of course for the flag.
The first was in the registration page sign_up.php
1234567891011121314151617181920
<?phpif($_SERVER['REQUEST_METHOD']==='POST'){require_once'db.php';$link=mysql_connect($DB_HOST,$SQL_USER,$SQL_PASSWORD)ordie('Could not connect: '.mysql_error());mysql_select_db('users')ordie("Mysql error");$user=mysql_real_escape_string($_POST['username']);// check to see if the username is available$query="SELECT username FROM users WHERE username LIKE '$user';";$result=mysql_query($query)ordie('Query failed: '.mysql_error());$line=mysql_fetch_row($result,MYSQL_ASSOC);if($line==NULL){// Signing up for premium is still in developmentecho'<h2 style="margin: 60px;">Lawn Care Simulator 2015 is currently in a private beta. Please check back later</h2>';}else{echo'<h2 style="margin: 60px;">Username: '.$line['username']." is not available</h2>";}}else{?>
As it’s possible to notice the query used to verify if a user already existed, used a LIKE statement, this means that by submitting a username value with the character %, we could disclose the already registered users; of course, since the registration was still closed, the only user we were expecting to find as already registered was the admin username, which happened to be ~~FLAG~~.
The second one was in the way the login process was handled by the validate_pass.php
12345678910111213141516171819202122232425
<?functionvalidate($user,$pass){require_once'db.php';$link=mysql_connect($DB_HOST,$SQL_USER,$SQL_PASSWORD)ordie('Could not connect: '.mysql_error());mysql_select_db('users')ordie("Mysql error");$user=mysql_real_escape_string($user);$query="SELECT hash FROM users WHERE username='$user';";$result=mysql_query($query)ordie('Query failed: '.mysql_error());$line=mysql_fetch_row($result,MYSQL_ASSOC);$hash=$line['hash'];if(strlen($pass)!=strlen($hash))returnFalse;$index=0;while($hash[$index]){if($pass[$index]!=$hash[$index])returnfalse;# Protect against brute force attacksusleep(300000);$index+=1;}returntrue;}?>
What the script implements as a protection against brute force attacks is faulty, and leave the script open to a byte by byte bruteforce attack on the hash, which wasn’t calculated by the server, but directly by the javascript on the client side.
By analyzing the timings of the server response it was possible to guess the characters that were composing the md5 hash for the ~~FLAG~~ user.
I built this horrible python script to do the job and retrieve the flag.