A set of the most common web vulnerabilities: XSS, SQLi, directory traversals, file includes, code injection, command injection, XML attacks, file uploads, and LDAP attacks. The ISO is downloadable here, and the official write-up is provided here.
For the XSS challenges, using Mozilla Firefox was sufficient, heavily relying on "View Page Source" and "Inspect Element" to look at the source code.
If you're unfamiliar with Cross-Site Scripting (XSS), start here. Since name
is supplied as a URL parameter and echoed directly back to us, we can input name=<script>alert(1)</script>
in order to get an alert box to pop up. We will be doing this as a proof-of-concept to demonstrate that arbitrary Javascript can be run.
If we again try name=<script>alert(1)</script>
, the page only echoes alert(1)
back to us in plaintext. This means the <script>
tag is being filtered. Changing the case to <sCrIpT>
successfully avoids this filter.
It looks like some more thorough filtering is now applied, as the previous input won't work and the script tag is removed entirely. To work around this, I avoided using the <script>
tag altogether and instead executed Javascript using name=<img src="nonexistent" onerror="alert(1)"/>
.
The filtering may have changed, but our solution to the previous level, name=<img src="nonexistent" onerror="alert(1)"/>
, still works.
After some trial and error, you can determine that the filtering is on alert()
this time and not <script>
. The input name=<script></script>
will pass the filtering successfully, but anything containing alert()
will not. In order to avoid filtering, we can obfuscate our input by combining Javascript's eval()
and String.fromCharCode()
functions to generate and evaluate alert()
using the string name=<script>eval(String.fromCharCode(97,108,101,114,116,40,41))</script>
.
In order to quickly obtain the correct ASCII values, I opened up the console and wrote a simple Javascript script:
for(i = 0; i < 7; i++)
{
console.log("alert()".charCodeAt(i);
}
This time, if we immediately examine the source, we can see that although the page doesn't print our name to the screen, it is used within Javascript:
Hello
<script>
var $a= "hacker";
</script>
In order to execute an alert()
, we just need to prematurely end the name
string, execute our own code, and ensure everything follows Javascript syntax. The following string should work: name="; alert();//
The solution is nearly the same as in the previous example, except now we can see than single quotes are being used:
Hello
<script>
var $a= 'hacker';
</script>
Therefore, we also need to change our input string to use single quotes: name='; alert();//
.
I got stuck on this one and had to look at PentesterLab's solution to learn that I should read up on PHP_SELF
exploits. I found a good resource for this here; read it now if you're not familiar. The source code for the HTML form is as follows:
<form action="/xss/example8.php" method="POST">
Your name:<input type="text" name="name" />
<input type="submit" name="submit"/>
Since this PHP script actually calls action="<?php echo $_SERVER['PHP_SELF']; ?>
instead of the hard-coded action="xss/example8.php"
, we can inject our script by entering http://192.168.56.101/xss/example8.php/"><script>alert(1)</script><foo class="
as the URL.
For the last level, the only interesting line in the source code is:
<script> document.write(location.hash.substring(1)); </script>
We're intended to exploit this page using DOM-based XSS, but it seems like Firefox and Chrome have both rolled out new protections. Using http://192.168.56.101/xss/example9.php#<script>alert(1);</script>
doesn't work because it's automatically HTML-encoded and does not appear to be exploitable.
If you're unfamiliar with SQL injection, start here. For the first level, there's a table to display users' name
s, id
s, and age
s, but only root
's information is shown. We would like to see this information for all users in the database. In the URL, we can see the parameter ?name=root
. Since we're supposed to find an SQL injection vulnerability, this parameter is probably directly used in a database query, which is most likely:
SELECT * FROM users WHERE name='root';
To extract all information from the database, we must replace root
with something that will be true for all users. root' OR '1'='1
should work, creating the query:
SELECT * FROM users WHERE name='root' OR '1'='1';
If we attempt to solve this challenge with the same solution, the web page responds with ERROR NO SPACE
. To remove all spaces from our input string, we can replace them with the tab character, %09
, resulting in a string like ?name=root'%09OR%09'1'='1
.
This time, tabs appear to be stripped, and we receive the ERROR NO SPACE
message again. Another option is to use /*c-style comments*/
, resulting in a string like ?name=root'/**/OR/**/'1'='1
.
This time, id=2
shows in the URL instead of name=root
. The query being made is probably
SELECT * FROM users WHERE id = 2;
The only difference this time is that it's a numeric query, so we can just directly append OR 1=1
to it so that it will always be true:
SELECT * FROM users WHERE id = 2 OR 1=1;
and our resulting input is ?id=2 OR 1=1
.
The same input, ?id=2 OR 1=1
solves this level.
The same input, ?id=2 OR 1=1
solves this level.
I really could not figure this one out blindly, so I went to the source code to figure out how to solve this level. The crucial section is:
if (!preg_match('/^-?[0-9]+$/m', $_GET["id"])) {
die("ERROR INTEGER REQUIRED");
}
It's forcing us to input only an integer, but the input is allowed to be multiple lines (that's what the trailing /m
means) and this condition is only enforced for the first line. Inserting a '\n'
character directly after the id
should make our previous solution pass this check, and ?id=2%0aOR 1=1
solves this level.
This time there's not much to do, since we already have our hands on all the information in the database. Nevertheless, this challenge is still vulnerable to SQL injection, and we can change the order things are sorted. Based on the URL parameter order=age
, we're either dealing with the statement:
SELECT * FROM users ORDER BY age;
or
SELECT * FROM users ORDER BY `age`;
Just for kicks, let's add in DESC
, with #
(ASCII %23
) to preserve syntax. We have to try both order=age DESC%23
and order=age` DESC%23
, before finding out that it's the latter.
This is the same as the previous challenge, except now it's the other syntax, and as a result we have to use order=age DESC%23
.