Problem :lock:

We arrive at a login portal for admin(s) and non-admins.

Solution :key:

Let’s look at the “source” (btw I prettified it a bit because it was so hard to look at and was causing errors).

<?

$maxid = 640; // 640 should be enough for everyone

function isValidAdminLogin() { 
    if($_REQUEST["username"] == "admin") {
    /* This method of authentication appears to be unsafe and has been disabled for now. */
        //return 1;
    }

    return 0;
}

function isValidID($id) { 
    return is_numeric($id);
}

function createID($user) { 
    global $maxid;
    return rand(1, $maxid);
}

function debug($msg) { 
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}

function my_session_start() { 
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
        if(!session_start()) {
            debug("Session start failed");
            return false;
        } 
        else 
        {
            debug("Session start ok");
            if(!array_key_exists("admin", $_SESSION)) {
                debug("Session was old: admin flag set");
                $_SESSION["admin"] = 0; // backwards compatible, secure
            }
            return true;
        }
    }

    return false;
}

function print_credentials() { 
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
        print "You are an admin. The credentials for the next level are:<br>";
        print "<pre>Username: natas19\n";
        print "Password: <censored></pre>";
    } else {
        print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
    }
}


$showform = true;
if(my_session_start()) {
    print_credentials();
    $showform = false;
} else {
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {
        session_id(createID($_REQUEST["username"]));
        session_start();
        $_SESSION["admin"] = isValidAdminLogin();
        debug("New session started");
        $showform = false;
        print_credentials();
    }
}

if($showform) {
?>

<p>
Please login with your admin account to retrieve credentials for natas19.
</p>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>

<? } ?> 
Full Page Source
 <html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas18", "pass": "<censored>" };</script></head>
<body>
<h1>natas18</h1>
<div id="content">
<?

$maxid = 640; // 640 should be enough for everyone

function isValidAdminLogin() { 
    if($_REQUEST["username"] == "admin") {
    /* This method of authentication appears to be unsafe and has been disabled for now. */
        //return 1;
    }

    return 0;
}

function isValidID($id) { 
    return is_numeric($id);
}

function createID($user) { 
    global $maxid;
    return rand(1, $maxid);
}

function debug($msg) { 
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}

function my_session_start() { 
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
    if(!session_start()) {
        debug("Session start failed");
        return false;
    } else {
        debug("Session start ok");
        if(!array_key_exists("admin", $_SESSION)) {
        debug("Session was old: admin flag set");
        $_SESSION["admin"] = 0; // backwards compatible, secure
        }
        return true;
    }
    }

    return false;
}

function print_credentials() { 
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
    print "You are an admin. The credentials for the next level are:<br>";
    print "<pre>Username: natas19\n";
    print "Password: <censored></pre>";
    } else {
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
    }
}


$showform = true;
if(my_session_start()) {
    print_credentials();
    $showform = false;
} else {
    if(array_key_exists("username", $_REQUEST) && array_key_exists("password", $_REQUEST)) {
    session_id(createID($_REQUEST["username"]));
    session_start();
    $_SESSION["admin"] = isValidAdminLogin();
    debug("New session started");
    $showform = false;
    print_credentials();
    }
} 

if($showform) {
?>

<p>
Please login with your admin account to retrieve credentials for natas19.
</p>

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

So many functions. Let’s try and work out the actual flow of this thing, this problem was so complicated for me not to scribble on a piece of paper, but since my handwriting is so bad, I made this ASCII diagram instead, just for you :kissing_heart:

# here's the rough outline of this whole script's flow

                              my_session_start()
                                   +     +
                                F  |     | T
                                   |     |
       array_key_exists() <--------+     +---------> print_credentials()
       username & password                     $_SESSION['admin'] must be 1
             +   +                                         ^
          F  |   |  T                                      |
             |   |                                         |
showform <---+   +---> session_start()                     |
                       $_SESSION['admin'] = 0 +------------+

So first we see the return value of my_session_start(), if it’s

So from here, we pretty much can’t really change anything. We can’t override the $_SESSION['admin'] value from anywhere.

Although, there is one interesting thing at the beginning of the script which is $maxid = 640, which is then used to limit the max number of possible PHPSESSIDs. Why would they do that? Why not have as many PHPSESSIDs as possible? It’s like asking to be bruteforced, so let’s give them that!

Let’s do it with burp, we got the packet in our intruder, and we’re going to bruteforce the PHPSESSID cookie value.

Set it to go from 0 to 640.

Let’s also use the grep so we don’t have to scroll through 640 responses later.

Now let’s wait a bit and see if we managed to fire a cookie that belongs to an admin.

Looks like we did. But I still haven’t fully grasped the theory behind it yet, let’s scribble some more.

# innerworkings of my_session_start()

       PHPSESSID is numeric
         +             +
      F  |             |  T
         v      F      v
return FALSE  <---+ session_start()
                            +
                            | T
                            v
      array_key_exists("admin", $_SESSION)
             +                      +
          F  |                      |  T
             v                      v
$_SESSION['admin'] = 0  +---->  return TRUE
                           (just prints the creds)

From the previous diagram, we know that we need to have my_session_start() return TRUE, because if not then $_SESSION['admin'] will be set to 0.

From this diagram, we see that the only way not to set $_SESSION['admin'] = 0 was to already have the $_SESSION['admin'] variable set from the beginning, or at least from before this code was “updated”, as is hinted throughout the source. The reason is that the $_SESSION['admin'] var is only going to be assigned 0 if it wasn’t already set to something else.

Flag :checkered_flag:

4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs

Takeaway :books: