Sunday, October 2, 2016

TUM CTF 2016 - totp writeup

Challenge Name: totp
Point Value: 100 Base + 100 Bonus
Description: It’s time to thank Google™ for making the Internet more secure for all of us.
Tags: web

The challenge is a website with a personal diary and user database.  We can create accounts and post messages to our diary.  A public Users page is listed, which shows the admin email address.  It's likely we need to login as an admin account and view their personal diary to find the flag.

The catch is that account logins use a Google Authenticator time-based one-time password (TOTP).  Usually, these are used in conjunction with a user selected password, but this site uses only the TOTP for login.

The Google Authenticator Wikipedia article, provided in the challenge description, explains how TOTP generates a secret key (displayed in the 2D barcode) at first creation, and that key is used along with the current time to generate 6 digit codes using HMAC every 30 seconds.  Looking at the source for the registration page, we see an example of one of these keys.

<div class="col-sm-9">
    <img src="">

    <input type="hidden" name="secret" value="6PP3WKGNATQYEV2V5ELFQPMD">
    <input type="password" id="password" placeholder="One Time Password" password="password" name="otp" required class="form-control">

Though a Google API is generating the barcode, the 120-bit (base32) secret key is being generated by the challenge site.  If we can guess what secret key was generated when the admin account was created, then we can create TOTP codes based off the current UTC time and log in as admin.  I believe this was the intent of the challenge designers, as they've conspicuously provided the time the admin account was created on the Users page.

Registered at: 2015-11-28 21:21

The key we eventually recover also gives a hint that a poor random number was used.  Without source code, there is still a lot of guessing involved in how the random numbers are generated by the site.  So while I continued to investigate that, I also setup a brute force password checker to run in the background, and that found the answer.

Brute Force

The TOTP number is 6 numeric digits, which gives 10^6 possible combinations, or 1,000,000.  Not a difficult brute force attack.  However, the correct number the server will accept changes ever 30 seconds.  But since that number is based off a SHA-1 HMAC, it has an even distribution. Each number in that 1,000,000 is just as likely to be correct for 30 seconds as any other number.  Let's say we can check 10,000 of the 1,000,000 keys within every 30 second period.  If we check the same 10,000 passwords every time (say 000 000 to 010 000) then after 50 attempts (1,000,000 / 10,000 / 2) we should have guessed the right password.  That's 50 attempts * 30 seconds, or 25 minutes, which is not long at all for an online brute force attack.

We could script this, but Burp Intruder has most of the tools we need already.  First, I benchmarked how fast Burp could send login requests from my connection to the web server.  I settled on 25 threads, which on my Internet connection, over 30 seconds could make about 3000 attempts.  Meaning an attack time closer to 84 minutes.  Next, it's not easy to script Burp to restart an Intruder attack every 30 seconds, exactly on the :00 and :30 marks.  So, I went the even lazier approach of trying all 1,000,000 passwords in a row.  But since the password the server picks is an even distribution, this shouldn't hurt our attack time too much in practice.

Lastly, playing with the site while using Burp Proxy showed no difference in the response from the "POST /?target=login.php" message on an unsuccessful login vs a successful login. Instead, the PHPSESSID returned in the cookie, has server side state, which marks the login as successful.  So we'd need to do an extra "GET /" request after every POST to check for success.  Well that would slow down the attack another 2x, so instead we'll check manually every 30 seconds or so.

So the simplified steps for the brute force attack are:

1) Generate a file of every possible TOTP possibility:

$ for i in {0..999999}; do printf "%06d\n" $i >> totp_999999.txt; done

2) Start Burp Intruder with POST login requests against the web server using the generated file:

3) Periodically refresh your web browser using the same PHPSESSID cookie as Burp:

I got lucky after about 75,000 request / 13 minutes.

4) Collect the flag: