Skip to content

Instantly share code, notes, and snippets.

@kawing-ho
Last active April 5, 2018 13:19
Show Gist options
  • Save kawing-ho/2a04bbaee30077c7010fe94f8529b26c to your computer and use it in GitHub Desktop.
Save kawing-ho/2a04bbaee30077c7010fe94f8529b26c to your computer and use it in GitHub Desktop.
This is NOT a write-up, I did not solve the challenge by myself but in terms of understanding the technicals required to solve it I would say that I've solved it. This gist is about highlighting my thought process and how I can improve it to avoid making the same mistake I did here ...

Overview / TL; DR

  • As said in the description, this is NOT a write-up, there are many online that get straight to the point (what is required to solve the challenge) which unfortunately this gist is not 📚
  • I didn't solve the challenge mainly because I got ahead of myself and missed a crucial detail in one of the many approcahes I took, in a sense I dived too deep down a rabbit hole which closed me off from the real answer, while I was digging deeper for one that simply wasn't there
  • natas15 is a Blind SQL Injection challenge, which I did not have knowledge of before (but I do now), in fact the only SQLi I knew of before this was the simple auto bypass method (basically ' OR 1=1; -- ), so I'm actually glad I did this challenge til the very end because I ended up learning a lot !

Wtf is a "Blind" SQL Injection ?

  • It's basically an SQL Injection but under the circumstances where the results aren't returned directly to you
  • More often than not you will only get responses along the lines of
    • The query didn't return anything (which means False)
    • The query returned a row / multuple rows (which means True)
    • An error in your query (which also might return nothing)
  • As you can imagine it makes things a lot harder for an attacker (but not imppossible), as they can only ask the database True/False questions

An example implementation in the natas15 sourcecode:

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    $res = mysql_query($query, $link);
    if($res) {
    if(mysql_num_rows($res) > 0) {
        echo "This user exists.<br>";
    } else {
        echo "This user doesn't exist.<br>";
    }
    } else {
        echo "Error in query.<br>";
    } 

⚠️ In this case the username variable is exploitable as it isn't being sanitized properly

What I did at the beginning

Although this approach was incorrect it taught me a lot about the UNION and INTO OUTFILE clauses and how they can be abused

One of my first ideas was that since the results weren't returned directly to the user, I could use UNION to link a new select statement which would ignore the old results and dump any new results into a temporary file to be read later. The many possible issues with this approach is :

  • The user currently executing the query may not have write access (It didn't)
  • The file has to be written to somewhere accessible by the user and within the webroot directory
  • The user has to have the privileges (in this case FILE privileges) to even use commands like LOAD FILE or INTO OUTFILE

Example URL:

http://natas15.natas.labs.overthewire.org/?username=" AND 1=2 UNION SELECT * INTO OUTFILE '/tmp/out' FROM users ; --

Server-side query :

SELECT * from users where username="" AND 1=2 UNION SELECT * INTO OUTFILE '/tmp/out' FROM users ; -- "

Side Note: A much more dangerous version of this injection would be something like

SELECT * from users where username="" AND 1=2 UNION SELECT '1','<?php system($_GET['cmd']) ?>' INTO OUTFILE '/whateverpath/directory/script.php'; -- 

which basically stores a PHP script containing a shell that you can then use to execute arbitrary commands on

I executed variations of this query mutliple times to no success, so I began to suspect that this user account had very limited privileges, unfortunately I was right

Using the built-in functions

mySQL has very handy functions like user() that returns who the current user is, version() that returns the running version , etc ...

I also found out that SQL supports the LIKE clause, which is perfect for avoiding true equality "="

Starting off with a simple query like this : SELECT * from users where username="" OR user() LIKE "%"

  • % when used alone matches everything so when saying user() LIKE "%" its basically saying user() matches ANYTHING, which is true as long as the thing that user() returns is not NULL
  • % when used with other characters will also match up to a certain pattern such as "%a" matching everything that ends with "a" and "a%" matching everything starting with "a"
  • The _ character functions similarly to "." in regex. It matches any character, so as you can see in the gif below, it can be used to guess the length of a certain field, while combined with the % character can also be used to match patterns

Underscore

(After guessing the value of a certain field the LIKE can be replace with = to ensure the correct value, since the query would evaluate to False otherwise)

Guess

  • Using trial-and-error method or even a brute-force method its a slow but surefire method of leaking useful data from the database such as
    • The current user (as shown above)

    • What privileges this user had (USAGE = no privileges besides logging in and SELECT'ing) Usage

    • The version of mySQL running

      URL:

      http://natas15.natas.labs.overthewire.org/?username=" OR @@version LIKE "5.5.55-0%2Bdeb8u1& debug (%2B = +)

      Query:

      SELECT * from users where username="" OR @@version LIKE "5.5.55-0+deb8u1"
    • The dummy user accounts being used pw

I was stupid

This is where I screwed up, I had already acquired a method to leak usernames and passwords from the users table, but somehow it never occured to me to look for a user called natas16, instead my smart-ass brain kept pestering me to find a way to execute arbitrary commands so I could cat the password file:/etc/natas_webpass/natas16 😕

Although I did most of this brute-forcing stuff by hand (since it was fairly short) a proper script is recommended for brute-forcing the password since it is 32 characters long and there are 26 lowercase letters + 26 uppercase + 10 digits = 62 possible characters for each of the 32 character slots, which is 62 ^ 32 possible combinations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment