Tuesday, 22 September 2015

CSAW CTF 2015 - Web 200 in two steps (using PHP's awfulness)

Some friends and I participated in this year's CSAW CTF under the name "Northwest Beer Drinkers". We placed 89th out of a total of 1,100+ teams, so I guess we can boast about being in the top-100 this year (woo). Sadly I couldn't participate myself too much this year as I had a family gathering to attend to, but I did spend some time early on and managed to solve Web 200.

Web 200, or "Lawn Care Simulator", was a simple web application that plays on the joke about "growth hackers". It was written to look like it was PHP-based, complete with a login page, registration form, and a suggestion that you join "their company". It definitely was quite tongue in cheek and they even went out of their way to make the grass grow in the blue square if you pressed the "grow" button.

When you attempt to login, it hashes the password field before sending off the form. This is done via an embedded JavaScript that makes use of the MD5 function in the CryptoJS library. The code executes as follows:

function init(){
            document.getElementById('login_form').onsubmit = function() {
                var pass_field = document.getElementById('password'); 
                pass_field.value = CryptoJS.MD5(pass_field.value).toString(CryptoJS.enc.Hex);

The registration page also refuses to let you sign up for an account, citing that it is currently in "private beta". It tries to play a trick on you in the form that it's looking for a hash value from the initial page, supplied by an improperly placed Git repository (which is important to note for later in this writeup), but I couldn't find a way to make use of this hash in the form, so I decided to attack the login mechanism instead since it was doing some weird hashing before sending the form off.

And this is where we sort of quickly solve the problem.

I decided to see what would happen if I just logged in with no credentials at all using Python Requests. The intention here was to see what sort of error would be produced and then use that to solve the problem. However, it sort of went sideways...
>>> import requests
>>> data = { 'username': '', 'password': '' }
>>> r = requests.post('', data=data)
>>> r.text
u'<html>\n<head>\n    <title>Lawn Care Simulator 2015</title>\n    <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>\n    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> \n    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"></link>\n</head>\n<body>\n<h1>

As we can see, this worked. At the time when the flag was revealed, I was not 100% certain that this was an intentional way to get the flag but while reviewing my notes and other information, I became conflicted.

If we go back to my remark about the Git repository, it was the repo for the entire source code to this challenge. And as it turns out in another write-up, it was key in solving the challenge for that person.

I did grab the source code from the challenge and took a look at why I was successful with fewer steps.
    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 "<h1>" . $flag . "</h1>";
        else {
            echo "<h1>Not Authorized</h1>";
    else {
        echo "<h1>You must supply a username and password</h1>";
So now we know it uses validate function from validate_pass.php, so let's examine that to see why this worked.

function validate($user, $pass) {
    require_once 'db.php';
    $link = mysql_connect($DB_HOST, $SQL_USER, $SQL_PASSWORD) or die('Could not connect: ' . mysql_error());
    mysql_select_db('users') or die("Mysql error");
    $user = mysql_real_escape_string($user);
    $query = "SELECT hash FROM users WHERE username='$user';";
    $result = mysql_query($query) or die('Query failed: ' . mysql_error());
    $line = mysql_fetch_row($result, MYSQL_ASSOC);
    $hash = $line['hash'];

    if (strlen($pass) != strlen($hash))
        return False;

    $index = 0;
        if ($pass[$index] != $hash[$index])
            return false;
        # Protect against brute force attacks
    return true;
While my PHP is not up to snuff, it should at least from what I understand here have came up with no results unless the database itself had a row that was completely blank for both the hash and username field. If the result variable was not able to be created then it should have died outright, but it was able to fetch a row and feed it into the line variable.

In any event, the solution was not to ram it with a bunch of requests but to just post a blank username and blank password. Whether or not this is the official solution I am not 100% sure.

Edit: PHP is an awful language

I had a chat with a friend of mine (pr0zac) who knows PHP better than me and he pointed that that mysql_fetch_row returns "false" if no rows are found. What likely happened here is that SQL failed to return any rows but the query technically succeeded so mysql_query returned successfully.

Then mysql_fetch_row returned "false" when it executed and then strlen reading of that made the result "null".

After all that, it then just passes the check as it remains "null" after hashing and returns "true".

PHP is fucking awful.

Thursday, 10 September 2015

A look at Something Awful's moderation by the numbers

Something Awful has been on the Internet for over a decade and a half. In that time, it has been responsible for many aspects of Internet culture. I myself have been on the website's forums since December of 2000, so I've seen a lot of stuff come and go.

One aspect of the forums that is unique when you compare it to other Internet communities is that it keeps a public ledger of all of the punitive actions made by moderators and administrators to the site's users. This was implemented in 2004 and it has been kept since.

During an attempt to get over some jet lag, I decided to see what sort of numbers could be retrieved from the "Leper's Colony" which is the aforementioned ledger. After some attempts, I managed to download the data and then compile it into JSON which will be provided after I finish digging through the information.

I've also crunched some numbers and made pretty graphs to see how the site has behaved since the ledger was started. I'll start with the numbers in this entry and then provide some pretty stuff in the next.

No data outside of the Leper's Colony was retrieved other than a count for total users.

There's a lot of information you can gleam from this information including seeing how much involved Richard "Lowtax" Kyanka has been throughout the years and even how much money accounts can cost.

Base Statistics

The ban data covers all moderator and administrator data from August 7th, 2004 through to September 9th, 2015. During this time there were 136,845 events, meaning that on average there were 33 to 34 events per day.

When the data was compiled, there were approximately 193,000 accounts--this value fluctuates so we're going to leave it at this. Additionally, there are three punishment types marked in this ledger; they are ban, permabanned, and probated.
This table breaks it down by the numbers:


Based on the unique values, this means that around 18% of all users have had their accounts probated for a period of time, 9% have been banned, and about 1% have been permabanned entirely. The numbers also tell us that less than 4% of permanent banishments are not really all that permanent.



One aspect of the Something Awful forums is that temporary banishments (and not-so-temporary) are given quite frequently. Users that receive these probations are able to view the forums and send private messages, but they would not be able to post any new threads or reply.

Probations can be given one of two ways: either a moderator directly probates someone for whatever reason or the user posts a thread that gets removed ("gassed") which results in a 15 minute inability to post--however the latter does not end up on the ledger.

The first reported probation in the dataset was on September 27, 2004, and the infraction was "grasshopper leeching in BYZT".

The following table shows the length of a probation and the number of them given.

<6 hours36 hours22,095
12+ hours6,2801 day34,895
3 days24,0501 week13,607
2 weeks11 month3,227
>1 month2100,000 hours192

For the last one, 100,000 hours is about 11.5 years. It's given out periodically for those who may invoke the ire of an administrator who decides that it's much more humourous to just remove them for a decade. The first person to suffer this got the punishment on May 8th, 2005, which means that on October 4th 2016, or about a year from now, that account will be able to post once again--the account has not posted since being probated.

The total number of probation hours given would add up to just slightly over 3,000 years.


Bans are as they described: you are removed from the forums if you're found to be in violation of the rules or you've been probated so many times that a message needs to be sent.

Unlike many other websites such as Reddit or Digg, Something Awful does require you to pay in order to sign up. This hasn't always been the case, but accounts registered past late 2001 are typically paid at a rate of $9.99 USD. Numbers are hard to determine, but at the time before paid accounts became a part of the forums' operation, there were about 20,000 users, meaning that from just account registrations alone, around $1.7 million has been paid by new users. This could be impressive if it weren't for the fact that this is over a span of 14 years, meaning that it would just be $100,000 per year if it to remain consistent.

However, unique to Something Awful is the ability to pay for the ability to return to the forums. With exception to a permanent ban, all one has to do to return is pay the $9.99 fee and they'll have their account back--there is one catch: if you have any upgrades which too also cost $9.99, you'll have to pay for those upgrades once again too.

And it has worked. Accounts have re-registered several times as indicated by the ban data itself. Here's a table that breaks down the ban counts and how many unique users per count.

# of bansCount# of bansCount# of bansCount# of bansCount

As you can see, it can get quite impressive, but it should be kept in mind that if someone does get banned that they won't necessarily come back. However, multiple bans does indicate that the person has at least paid $9.99 once to return. If one were to assume that everyone has paid to come back, Mr. Kyanka would have raised about $240,000 from re-registrations alone.

One user who takes the top with 35 bans actually has more: the person in second place is the same user, which means the user has been banned 66 times, or has contributed at least $660 to Something Awful.

Or maybe they're not the top-most. Another user had registered 77 times under different but similar aliases, meaning they've spent almost $770.

What this speaks of is that you're not going to get rid of all problematic users by banning them, but it does mean that you can at least get some compensation for having to put up with them.


This account punishment is as it reads: a permanent ban. As mentioned earlier, some accounts do get the permanent ban lifted: it appears to be about 3.5%.

To break it down, 2,307 accounts have been permanently banned once. For accounts permabanned twice, it's at 73. Accounts permanently banned three and four times are both at 3.

It should be kept in mind that some accounts permabanned once may also be twice or more as well as they may be registrations under a different name.

Coming up...

In the next entry, I'll show pretty things in graphs. This one will take a bit longer than a few days but it should be fun.

Monday, 7 September 2015

Geotrust/Symantec has revoked all SSL certificates for .PW TLD domains

I just came off of vacation and had this show up in my e-mail regarding some problems with Canary:
Good morning Colin,
I hope your weekend was awesome.
Just a quick email to let you know that I am having issues with a possible certificate problem on Firefox, chrome, ie and even edge.
It works fine on safari on an iPad.
Needless to say I initially passed it off as someone having their client not configured correctly or running some outdated software (I really should avoid having these biases but I digress), but just as a sanity check, I decided to take a look.

Being that I didn't revoke the certificate myself, I reached out to the reseller that issued the certificate and had this relayed to me:
Reseller rep.:
We regret to inform you that certificate [number] for www.canary.pw domain has been revoked by the Certificate Authority due to the site being flagged as potentially containing malware in a recent site scanning by Symantec (owner of GeoTrust). Unfortunately we were not warned of the upcoming revocation, so we apologize for any inconvenience that this may cause.


Reseller rep.:
As per our check with Symantec, they will no longer be issuing SSL certs to .PW domains. You are advised to remove the SSL certificate from the server to avoid security errors related to a revoked certificate.
I was not happy to read this, but my reseller was awesome enough to issue me a refund so I could go ahead and just switch the certificate to another provider. There is no malware on Canary to say the least so the statement by Symantec is irrevocably false.

But here's the thing: why did Geotrust just go ahead and revoke the certificates for all .PW domains without any warning? Why did they believe that this was the best course of action and why did they decide to put domains at risk? It is because of these questions that I cannot recommend using them as a certificate authority.

Geotrust has done a great job demonstrating the problem with certificate authorities: they're closed organizations that you cannot put any trust into.