Entrada

XSS Lab 20

XSS Lab 20

Skills

  • Stored XSS into onclick event with angle brackets and double quotes HTML-encoded and single quotes and backslash escaped

Certificaciones

  • eWPT
  • eWPTXv2
  • OSWE
  • BSCP

Descripción

Este laboratorio contiene una vulnerabilidad stored XSS en la funcionalidad de comentarios. Para resolver este laboratorio, debemos enviar un comentario que llame a la función alert() cuando se haga clic en el nombre del autor del comentario. Esto implica inyectar código malicioso en el campo correspondiente al nombre del autor, de manera que se ejecute cuando un usuario interactúe con ese elemento


Resolución

Al acceder a la web nos sale esto

Si pulsamos sobre View post vemos una sección en la cual podemos comentar

Si rellenamos el formulario, lo enviamos y nos abrimos el código fuente de la web vemos esto

He intentado cerrar la ' pero no ha sido posible porque me la escapa con \

Si intentamos escapar la \ nos la escapa y por lo tanto no se interpreta

También nos escapa las /, las " y nos HTML encodea en los <>, por lo tanto la única manera que nos queda es HTML encodear nosotros el payload a enviar para ver si el desarrollador no ha tenido esto en cuenta. Hay varias formas de HTML encodear, la primera es la de codificación de entidades HTML y la segunda es la codificación numérica, de la cual hay varios tipos

1
http://foo?&apos;-alert(1)-&apos;
1
http://foo/?&#x27;-alert(1)-&#x27;

Para saber como funciona esta vulnerabilidad me he creado un pequeño laboratorio en local, lo primero es abrirnos nuestro editor de código y crear un archivo llamado index.html con el siguiente contenido

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
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ejemplo de HTML Encode y URI Decode</title>
  <script>
    // Función para HTML decodear el input
    function htmlDecode(str) {
      var element = document.createElement('div');
      if (str) {
        element.innerHTML = str;
        str = element.innerText || element.textContent;
      }
      return str;
    }

    // Función para URI decodear el input
    function uriDecode(str) {
      try {
        return decodeURIComponent(str);
      } catch (e) {
        return str;  // Si hay un error de decodificación, devuelve el valor original
      }
    }

    // Función para manejar el formulario y actualizar los atributos
    function updateLink() {
      // Obtener el input del usuario
      let userInput = document.getElementById('userInput').value;

      // Primero, decodificar el input de entidades HTML
      let decodedHtmlInput = htmlDecode(userInput);

      // Luego, decodificar posibles componentes URI
      let decodedUriInput = uriDecode(decodedHtmlInput);

      // Inyectar el input decodificado en los atributos href y onclick
      document.getElementById('author').href = "http://foo?" + decodedUriInput;
      document.getElementById('author').setAttribute('onclick', "var tracker={track(){}};tracker.track('http://foo-" + decodedUriInput + "');");
    }
  </script>
</head>
<body>

  <h1>Formulario de Entrada</h1>

  <label for="userInput">Ingresa algo:</label>
  <input type="text" id="userInput" placeholder="Escribe algo...">
  <button onclick="updateLink()">Actualizar Link</button>

  <p><a id="author" href="http://foo?&apos;-alert(1)-&apos;" onclick="var tracker={track(){}};tracker.track('http://foo?&apos;-alert(1)-&apos;');">test</a></p>

</body>
</html>

Posteriormente en el mismo directorio que ese archivo nos montamos un servidor http, en mi caso lo hago con python

1
# python -m http.server 80

Una vez hecho esto, en el navegador debemos acceder a nuestro localhost. Cuando lo hagamos veremos esto

Si introducimos este payload &apos;-alert(1)-&apos; y nos abrimos el inpector de Chrome vemos que se nos decodea el HTML

Si pinchamos sobre el link se nos ejecutará el código JavaScript

Si modificamos el código y ahora HTML encodeamos el input del usuario

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
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ejemplo de HTML Encode y URI Decode</title>
  <script>
    // Función para HTML encodear el input
    function htmlEncode(str) {
      return str.replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;');
    }

    // Función para URI decodear el input
    function uriDecode(str) {
      try {
        return decodeURIComponent(str);
      } catch (e) {
        return str;  // Si hay un error de decodificación, devuelve el valor original
      }
    }

    // Función para manejar el formulario y actualizar los atributos
    function updateLink() {
      // Obtener el input del usuario
      let userInput = document.getElementById('userInput').value;

      // Primero, HTML encodear el input del usuario
      let encodedHtmlInput = htmlEncode(userInput);

      // Luego, decodificar posibles componentes URI (si es necesario)
      let decodedUriInput = uriDecode(encodedHtmlInput);

      // Inyectar el input HTML encodeado y decodificado en los atributos href y onclick
      document.getElementById('author').href = "http://foo?" + decodedUriInput;
      document.getElementById('author').setAttribute('onclick', "var tracker={track(){}};tracker.track('http://foo-" + decodedUriInput + "');");
    }
  </script>
</head>
<body>

  <h1>Formulario de Entrada</h1>

  <label for="userInput">Ingresa algo:</label>
  <input type="text" id="userInput" placeholder="Escribe algo...">
  <button onclick="updateLink()">Actualizar Link</button>

  <p><a id="author" href="http://foo?&apos;-alert(1)-&apos;" onclick="var tracker={track(){}};tracker.track('http://foo?&apos;-alert(1)-&apos;');">test</a></p>

</body>
</html>

Si volvemos a introducir el payload &apos;-alert(1)-&apos; vemos que se está HTML encodeando nuestro input, por lo tanto, al web lo toma como si fuera texto y nos imposibilita la inyección de código JavaScript

En el laboratorio Portswigger se están escapando y HTML encodeando varios caracteres, pero no está HTML encodeando el input completo, solo ciertos caracteres específicos. Por lo tanto, podemos intentar HTML encodear nuestro payload para que no detecte los caracteres y no los escape o HTML encodee. De esta forma cuando el payload encodeado se carga se decodea automáticamente por navegador y es interpretado. Esto lo podemos comprobar introduciendo el payload &apos;-alert(1)-&apos; en el campo website a la hora de publicar un comentario

Si nos fijamos en el código fuente vemos que el input que introducimos está HTML encodeado, esto es porque este texto se carga del servidor y por lo tanto no es decodeado

Sin embargo, si nos abrimos el inpector de Chrome veremos que si está decodeado, esto es porque es el navegador el que lo HTML decodea. Por esto es importante HTML encodear el input del usuario, para que cuando el navegador cargue el contenido del servidor y lo decodee lo interprete como texto

Esta entrada está licenciada bajo CC BY 4.0 por el autor.