Today I want to mix things up a bit, and do a challenge focused exclusively on web vulnerabilities. For the target, I will use the Web for Pentester ISO offered by PentesterLab. They host many challenges dedicated to web hacking, and I recommend you give them a try!
Navigating to the VM, we are greeted by this page:
As you can see, we’ve got plenty of exercises, so let’s begin!
XSS
Example 1
Source code:
1 2 3 4 5 6 7 8 |
|
This is the most basic type of injection:
1
|
|
Now you will get an alert box and if you look in the source, you will see the exact same injection, with no filtering whatsoever.
Example 2
Source code:
1 2 3 4 5 6 7 8 9 10 |
|
This one looks like the previous one, but in the source, we can see that the script tags are replaced with empty strings, but only for lowercase. I changed the case of the script tags to SCRIPT, and the alert box was back!
Example 3
The source looks like this:
1 2 3 4 5 6 7 8 9 10 11 |
|
This time the script tags are filtered regardless of the case. To see what’s going on, I inserted script tags inside a placeholder value:
1
|
|
As expected, they disappear into the abyss, and the message echoed in the page is Hello placeholder . I then tried doubling the script tags:
1
|
|
The message changed to Hello place<>holder
. This is great, it means we can have nested tags that will be interpreted! I built the payload to:
1
|
|
Example 4
Source:
1 2 3 4 5 6 7 8 9 |
|
This time, the script throws an error if it sees anything resembling script tags. Luckily, we can get an XSS payload to execute in different ways. For this I used:
1
|
|
Example 5
Source code:
1 2 3 4 5 6 7 8 9 |
|
All right, it looks like script tags aren’t blacklisted anymore, but this time the alert word is. I just substituted the alert for prompt:
1
|
|
Example 6
PHP source:
1 2 3 4 5 6 |
|
This time, whatever we place inside the name variable is assigned to a variable. The page source looks like this:
1 2 3 4 |
|
We want to build on the existing code and add our own. This means we will have to close the quotes and add our payload. First, I inserted something to see the changes:
1
|
|
And in the HTML code I saw this:
1
|
|
The part up to the first semicolon is what we want, then we have to place our payload, but a stray doublequote and semicolon remain. To get rid of them, I commented them out, and the final injection looked like this:
1
|
|
And in the source it is rendered the following way:
1
|
|
Example 7
PHP code:
1 2 3 4 5 6 7 |
|
It appears that now there is encoding performed on the name variable to prevent the previous type of bypass. Let’s see how the source looks with the previous payload:
1
|
|
The attack was nullified, but if you look at the htmlentities
PHP manual page, you will see that, if not otherwise specified, the default flag is ENT_COMPAT, which converts double-quotes and leaves single-quotes alone. So all I had to do was replace the double-quotes with single-quotes:
1
|
|
And this is translated in the HTML as:
1
|
|
Example8
This looks a little different, we input something and it gets echoed back in a greeting: HELLO something
Source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
PHP_SELF returns the currently executing script and its path from the root folder. In our case, where the URL is http://192.168.217.138/xss/example8.php , PHP_SELF would be /xss/example8.php .
The PHP_SELF variable is vulnerable to XSS if not enclosed within htmlentities(). Which means we can put our payload in the URL: http://192.168.217.138/xss/example8.php/INJECTION
Now, we need to take note of how to build the payload:
1
|
|
My successful injection was:
1
|
|
And the source now looks like this:
1
|
|
The first double-quote and >
closed the form action part, then came the injection, and then I put another double-quote to keep company to the stray one that remained.
Example9
Whatever we put after the pound sign gets rendered on the page. Here’s the code that does it:
1 2 3 |
|
First, we have to understand the substring method:
string.substring(start, end)
This method extracts the characters in a string between “start” and “end”, not including “end” itself. Characters start at index 0
And the location.hash returns the anchor part of the URL. So if our URL is http://192.168.217.138/xss/example9.php#moo , it will return #moo . And combined with the substring function, whatever comes after the hash is written to the page, as we saw at the beginning.
This page is vulnerable to DOM-based XSS, where a modification the DOM environment affects how the client code is running. Just put the payload after the hash sign, and reload the page (I had to use Chrome to see the alert):
1
|
|
SQL injections
Example1
Ok, before tampering with anything, let’s look at the source:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
So, the code looks in the users table for whatever we give it, and if a match is found, it displays some info. You can see there is no filtering on our input, and the DB query is SELECT * FROM users where name=‘input’
. This is trivial to bypass, by using root' or 1=1—
(there is a space after —), and don’t forget to URL encode for it to work! The query now becomes SELECT * FROM users where name=‘root’ or 1=1— ‘
, and the stray quote is commented out.
Example2
This example looks the same like the previous one, but the difference is in the code:
1 2 3 4 5 6 7 |
|
Basically, the space characters are being filtered out. However, we can achieve the same results by using tabs (\t
), which would change the previous injection to: SELECT * FROM users where name=‘root’ or 1=1#
, where the same outcome is achieved by substituting the spaces for tabs and the comment characters from —
to #
. Again, don’t forget URL encoding though!
Example3
Same-looking page, but slightly different code:
1 2 3 4 5 6 7 |
|
This time, a regular expression prevents the use of any whitespace characters, including tabs, carriage returns, etc. It is possible to bypass this by using C-style comments: /**/
. Here, our injection would be:
1
|
|
And the DB query would look like this:
1
|
|
As always, don’t forget to URL encode when injecting!
Example4
Here, the URL looks just a tiny bit different: http://192.168.217.138/sqli/example4.php?id=2
And the code:
1 2 3 |
|
So, the query performed is SELECT * FROM users where id=INPUT
, but this time, the mysql_real_escape_string
function is used to escape the characters: \x00, \n, \r, \, ‘, “ and \x1a
. All that protection is for naught however, because the id parameter is an integer, where there is no need to use quotes. So I injected 2 or 1=1—
and again saw all results.
Example5
We have the same example as the previous one, but with some modifications to the underlying code:
1 2 3 4 5 6 7 |
|
The regular expression will make the script throw an error and stop executing if it encounters non-integer characters in the input. But the check is only made at the beginning of the input, as can be seen from the fact that there is a ^
, but not a $
delimiter for the end. Again, the injection is trivial: id=2 or 1=1—
.
Example6
The developer tried to fix the previous mistake with:
1 2 3 4 5 6 7 8 |
|
Now the code checks for the end of the string to be an integer, but not for the beginning xD Well, we can inject without worrying that we have to comment something afterwards: id=2 or 1=1
Example7
Finally, the code checks for integers both at the beginning and the end:
1 2 3 4 5 6 7 |
|
There is something new in this regex: a PCRE_MULTILINE pattern modifier. Let’s see what this does:
m (PCRE_MULTILINE)
By default, PCRE treats the subject string as consisting of a single “line” of characters (even if it actually contains several newlines). The “start of line” metacharacter (^) matches only at the start of the string, while the “end of line” metacharacter ($) matches only at the end of the string, or before a terminating newline (unless D modifier is set). This is the same as Perl. When this modifier is set, the “start of line” and “end of line” constructs match immediately following or immediately before any newline in the subject string, respectively, as well as at the very start and end. This is equivalent to Perl’s /m modifier. If there are no “\n” characters in a subject string, or no occurrences of ^ or $ in a pattern, setting this modifier has no effect.
So, the regex checks will be performed correctly on the first line, but we can insert new lines and inject our payload there, and no checks will be performed! With the payload id=2%0A or 1=1
, a new line has been injected, and then the rest of the payload.
Example8
This example introduces the ORDER BY statement:
1 2 3 |
|
The SQL query looks like this:
1
|
|
For this exploitation, I used this very helpful article about time-based injection in the ORDER BY clause:
1
|
|
First, the successful injection will be reflected by the time it takes to load the page, specified inside the SLEEP function. The DUAL table is a special table that can be used in queries that don’t need data from any tables. DATABASE() returns the value of the currently selected DB, and the LIKE operator is used to compare a value with another similar one. For the injection to work, don’t forget to URL encode! Now, let’s see how the full query would look like:
1
|
|
That LIKE statement is useful, because it induces a condition that is matched by the SELECT statement.
Example9
Source code:
1 2 3 |
|
No more backticks here. I used the same payload as in the previous example, but without the backticks (and URL encoded):
1
|
|
Directory traversal
Example1
If you inspect the image link, you will see: dirtrav/example1.php?file=hacker.png
. The source code for this challenge is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
There is no filtering involved, so I displayed the /etc/passwd file with: http://192.168.217.138/dirtrav/example1.php?file=../../../../../etc/passwd
Example2
The path for this one is dirtrav/example2.php?file=/var/www/files/hacker.png
. In the code, we are only interested in the parts that manipulate the file to be included:
1 2 3 4 |
|
The script tries to enforce the location of the file to be in /var/www/files, but we can leave it as it is and just go up a few directories and include the passwd file again: file=/var/www/files/../../../etc/passwd
.
Example 3
Here is the path: dirtrav/example3.php?file=hacker
And the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The code gives the hint of exploiting this: the null byte, which will make the system ignore anything that comes after it. So the extension check will be bypassed: file=../../../../../etc/passwd%00
File Include
Example 1
Let’s see the code:
1 2 3 4 5 6 7 |
|
There is no validation performed on the file to be included. For this challenge, PentesterLab provides a sample include file that calls phpinfo() at http://assets.pentesterlab.com/test_include.txt
Just include the above file to see the output of phpinfo(): page=http://assets.pentesterlab.com/test_include.txt
Example2
The URL is slightly different now: http://192.168.217.138/fileincl/example2.php?page=intro
And the code:
1 2 3 4 5 6 7 8 |
|
So the code automatically appends a PHP extension to the file, but is also vulnerable to null bytes. I included the same file as in the previous example, with a %00
at the end: page=http://assets.pentesterlab.com/test_include.txt%00
Code injection
Example 1
Source code:
1 2 3 4 5 |
|
The string echo “Hello hacker!!!”;
is passed to eval. Whatever else we input after the name variable comes right after hacker and before the exclamation signs: Hello hacker@!!! (with an example of adding @ after hacker). However, if we input a double-quote, we get an error:
1
|
|
That is because eval now has to evaluate the broken syntax of echo “Hello hacker”!!!“;
. We can take advantage of this by appending a command and then commenting out the remaining !!!”;
:
1
|
|
URL encode it and you will see the output:
This worked because what eval saw was: echo “Hello hacker” . system(‘hostname’);//!!!“;
Example 2
PHP code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
The line to focus on is:
1
|
|
Here, the PentesterLab course explains the vulnerability exists in the usort(array, user-defined function) function, because it uses create_function(args, code), which is used to create an anonymous (lambda-style) function. create_function is vulnerable to unfiltered input because it uses eval() at its core. There is a known exploit that allows code injection and executing the code without creating and calling the function. Let’s see how this works:
create_function is a ZEND_FUNCTION defined in zend_builtin_functions.c. Here is part of the vulnerable code:
The folks at PentesterLab were nice enough to help me understand this, I was in one of those states where you don’t see the forest from the trees! Basically, the code for the lambda function is the string inside curly brackets, and there is no filtering for {}. So later, this same user-supplied string gets evaluated by zend_eval_string. We can inject code in the $order parameter, so our code gets in this line:
1
|
|
And that line will be used in the format string. The payload I used is : order=id);}echo ‘INJECTION SUCCESSFUL’;//
. Now, inside eval(), the lambda function without injection looks something like this:
1
|
|
Let’s deconstruct it step by step. Adding ); closes the call to strcmp, but leaves garbage behind and also an unclosed {} , because the end curly bracket is now part of the discarded code:
1
|
|
Adding a closing curly bracket correctly closes the format string, so now we have:
1
|
|
To test where I was with the injection, I left it at that and got this error:
1
|
|
So the code picks up after the curly bracket, we know we have to comment out the rest. And between the curly bracket and the comment characters we can put our code:
1
|
|
And the result:
eval() now sees a strcmp, which is executed (but only with 1 parameter, hence the warning). Then eval goes further, sees our code and executes it (the warning doesn’t stop the execution). And finally, eval reaches the leftovers, which have been commented out, so it does nothing.
This here was my favorite part in all the challenge!
Example 3
Here we have the familiar hello hacker message, but with a twist on the URL: http://192.168.217.138/codeexec/example3.php?new=hacker&pattern=/lamer/&base=Hello%20lamer
Let’s see the code:
1 2 3 4 |
|
Ok, preg_replace searches inside the base parameter (Hello lamer) for matches to the pattern parameter (hacker) and replaces them with what’s in the new parameter (hacker).
The problem with preg_replace is that it can be passed the modifier e (PREG_REPLACE_EVAL) that causes PHP to execute the replacement value.
To exploit this, first we have to add the modifier to the pattern: pattern=/lamer/e
. Then we put our code inside the new parameter: new=phpinfo()
. The URL now looks like this: http://192.168.217.138/codeexec/example3.php?new=phpinfo%28%29&pattern=/lamer/e&base=Hello%20lamer
Example 4
This one looks like the first example, but with a new addition in the code:
1 2 3 |
|
assert checks if the assertions is false, but the most important thing that we need to know is: if the assertion is given as a string it will be evaluated as PHP code by assert().
The assert returns true, because it checks that the name parameter = ‘hacker’, which it is. When injecting a single quote, we see this error:
1
|
|
I used a PHP sandbox to help me with this. Let’s imagine that the name parameter is a simple string with the value of hacker:
1
|
|
Thanks to the use of trim, assert will see (and try to evaluate) the value of ‘hacker’. Knowing this, if I try injecting hacker'.phpinfo().‘
, inside the code I assume it will look like this:
1
|
|
With the string concatenation, phpinfo() is displayed, but we also see at the bottom of the page: Hello hacker'.phpinfo().‘
. The assert was successful, and the code was executed.
Commands injection
Example 1
It looks like a ping program:
1 2 3 |
|
You can chain another command after the ping one by doing this: ip=127.0.0.1;whoami
. Below the ping output, you can now see the whoami output, which in this case is www-data
Example 2
This looks the same like the previous one, but let’s see the code:
1 2 3 4 5 6 |
|
There is some regex added now to validate that the ip parameter will indeed look like in IP. However, similar to a previous example, the check won’t look on further lines. We can use %0a
to introduce a new line, and run our command there: ip=127.0.0.1%0Apwd
. And the output is:
1 2 3 4 5 6 7 8 |
|
Example 3
Same-looking URL, and slightly different code:
1 2 3 4 5 6 |
|
This time, the script will redirect the user if the ip parameter doesn’t match the regex. However, the execution will continue, even if the browser gets redirected. To stop the execution altogether, it would be necessary to call the die() function after the header() function, which isn’t happening in this case. So we can still inject a command, but we’ll need to do it through another medium than the browser, or use a proxy. For this, I used Burp, sent the payload ip=127.0.0.1;ls
, and looked inside the 302 response to find what I wanted to see:
1 2 3 4 |
|
LDAP attacks
Example 1
This is a new type of exercise, involving LDAP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
First, let’s learn more about LDAP:
LDAP Injection is an attack technique used to exploit web sites that construct LDAP statements from user- supplied input.
Lightweight Directory Access Protocol (LDAP) is an open-standard protocol for both querying and manipulating X.500 directory services. The LDAP protocol runs over Internet transport protocols, such as TCP. Web applications may use user-supplied input to create custom LDAP statements for dynamic web page requests.
When a web application fails to properly sanitize user-supplied input, it is possible for an attacker to alter the construction of an LDAP statement. When an attacker is able to modify an LDAP statement, the process will run with the same permissions as the component that executed the command. (e.g. Database server, Web application server, Web server, etc.). This can cause serious security problems where the permissions grant the rights to query, modify or remove anything inside the LDAP tree. The same advanced exploitation techniques available in SQL Injection can also be similarly applied in LDAP Injection.
This particular example is vulnerable to NULL bind, where an anonymous user can access information from the LDAP directory without authentication. For this, the values sent to the server have to be null, so instead of just sending empty values, we have to completely remove them:
Example 2
Let’s see the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
The goal here is to be able to authenticate with any password, and get information from the directory. Now, since the password is being hashed, the only likely place to inject into is the username. To learn more about LDAP, its rules and types of injection, I found a nice BlackHat whitepaper. The key takeaway is that the injection will happen in the filter. In our case, the filter is:
1
|
|
We see here that filter uses &, which specifies an AND condition, where both username and password have to be true. Remembering that we can only inject inside the username parameter, here’s what we can do:
1
|
|
The ) closes the name filter, then we introduce an always true condition with the * wildcard, the closing ) matches the one at the beginning of the filter, and the null byte..well, nullifies the rest of the filter.
File Upload
Example 1
It looks like we can upload an image and find it in the /upload/images/
directory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Obviously, I am thinking about uploading a PHP shell instead. But first, I wanted to see if there is anything already in the images folder, and I wasn’t disappointed:
Ok, now it’s time for the shell. I created a basic PHP shell:
1 2 3 |
|
Uploaded it, navigated to it and passed a command:
1 2 |
|
Example 2
Same functionality, but this time the script doesn’t allow PHP extensions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
The code checks that an extension may not end in php. Uploading a .php file followed by a non-existent extension will cause the server to ignore the unknown extension and switch to the next one it knows. I uploaded the same shell as ashell.php.fail
:
1 2 |
|
XML attacks
Example 1
A twist on the previous challenges, this time with the use of XML:
1 2 3 4 5 |
|
This particular vulnerability revolves around XML injection:
Custom entities can be defined by including them directly in an optional DOCTYPE and the expanded value they represent may reference an external resource to be included. It is this capacity of ordinary XML to carry custom references which can be expanded with the contents of an external resources that gives rise to an XXE vulnerability.
So, I defined the following entity:
1
|
|
This entity can be referenced with &hacker;
. Of course, we will need to URL encode it. Here’s the URL with the XML injection:
1
|
|
Example 2
Another hello page, but the URL is a bit different: http://192.168.217.138/xml/example2.php?name=hacker
1 2 3 4 5 6 7 8 |
|
We see here some XML data containing users and passwords, and user input being used to construct an XPATH query. XPATH is used to navigate through elements and attributes in an XML document.
With a name of hacker, the XPATH would look like this:
1
|
|
XPATH injection is similar to SQL injection. Here we can inject inside the name input to create an always true condition and get rid of the remaining query as follows: hacker' or 1=1]%00
. Now the query looks like this:
1
|
|
Wow, this was a really fun challenge, and I hadn’t done a web app challenge in a while! Many thanks to PentesterLab for this challenge and their help when I had questions! They also have their own course for the challenge here
Until next time, straight from the cow’s mouth:
1 2 3 4 5 6 7 8 |
|