El XSS reflejado es un tipo de vulnerabilidad XSS donde un script malicioso es reflejado en el navegador del usuario, normalmente vía una URL construida o formulario de subida. Considera una query de búsqueda que contenga <script>alert(document.cookie)</script>. Muchos usuarios no sospecharían de dicha URL incluso si la miraran de cerca. Si se procesa por un aplicación vulnerable, será ejecutada en el contexto del navegador del usuario.


Aplicación Web Vulnerable

Una vulnerabilidad XSS reflejada simple es cuando el usuario busca por algún término y el string de búsqueda está incluido en los resultados de búsqueda. Este escenario simple ofrece un objetivo fácil para que el atacante lo explote.

Aunque descubir este tipo de vulnerabiliidades no es siempre fácil, arreglarlos sí lo es. Un input de usuario tal como <script>alert('XSS')</script> debería ser sanitizado o HTML-encodeado a &lt;script&gt;alert('XSS')&lt;/script&gt;.

En las siguientes subsecciones veremos ejemplos de código vulnerable en los siguientes lenguajes/frameworks.

  • PHP
  • JavaScript (Node.js)
  • Python (Flask)
  • C# (ASP.NET)

PHP

Código Vulnerable

php
<?php
$search_query = $_GET['q'];
echo "<p>You searched for: $search_query</p>";
?>

Si estás familiarizado con PHP, $_GET es una array de PHP que contiene valores de la URL. Además, $_GET['q'] se refiere al parámetro de la query de string q. Por ejemplo, en http://shop.thm/search.php?q=table, $_GET['q'] tiene el valor table.

El problema aquí es que el input del usuario no se está sanitizando y está siendo mostrado en la página resultante directamente. Por lo que un atacante puede añadir código malicioso a la URL como http://shop.thm/search.php?q=<script>alert(document.cookie)</script> y si el sitio es vulnerable mostrará la cookie de sesión.

Código Arreglado

php
<?php
$search_query = $_GET['q'];
$escaped_search_query = htmlspecialchars($search_query);
echo "<p>You searched for: $escaped_search_query</p>";
?>

La función htmlspecialchars() convierte caracteres especiales a entidades HTML. Los caracteres <, >, &, ", ' se reemplazan para prevenir el input de scripts al ejecutar. Puedes leer su documentación aquí.


JavaScript (Node.js)

Código Vulnerable

javascript
const express = require('express');
const app = express();

app.get('/search', function(req, res) {
    var searchTerm = req.query.q;
    res.send('You searched for: ' + searchTerm);
});

app.listen(80);

Si no estás familiarizado con Node.js, el código de arriba usa Express, un framework de aplicaciones web popular para Node.js. El req.query.q tiene el valor table. Por último se genera la respuesta haciendo append (añadiendo) dicho ítem de búsqueda a la string.

Como se añade sin sanitizar, es fácil insertar una query maliciosa: http://shop.thm/search?q=<script>alert(document.cookie)</script>.

Código Arreglado

javascript
const express = require('express');
const sanitizeHtml = require('sanitize-html');

const app = express();

app.get('/search', function(req, res) {
    const searchTerm = req.query.q;
    const sanitizedSearchTerm = sanitizeHtml(searchTerm);
    res.send('You searched for: ' + sanitizedSearchTerm);
});

app.listen(80);

Conseguimos solucionarlo usando la función sanitizeHtml() de la librería sanitize-html. Esta función elimina elementos y atributos no seguros. Puedes leer su documentación aquí.

Otra opción sería escapeHtml() que escapa caracteres como <, >, &, " y '. Puedes mirar su documentación aquí.


Python (Flask)

Código Vulnerable

python
from flask import Flask, request

app = Flask(__name__)

@app.route("/search")
def home():
    query = request.args.get("q")
    return f"You searched for: {query}!"

if __name__ == "__main__":
    app.run(debug=True)

Si no estás familiarizado con Flask, request.args.get() se usa para acceder a los parámetros de la string query de la URL solicitada. De hecho, request.args contiene todos los parámetros de la URL en un objeto de estilo diccionario. Es decir, que en http://shop.thm/search?q=table, request.args.get("q") tiene el valor table.

Por esto es fácil inyectar código malicioso (no está sanitizado): http://shop.thm/search?q=<script>alert(document.cookie)</script>.

Código Arreglado

python
from flask import Flask, request
from html import escape

app = Flask(__name__)

@app.route("/search")
def home():
    query = request.args.get("q")
    escaped_query = escape(query)
    return f"You searched for: {escaped_query}!"

if __name__ == "__main__":
    app.run(debug=True)

El cambio principal es que ahora usamos la función escape() del módulo html. La función html.escape() en Flask es enrealidad un alias para markupsafe.escape(). Convierte caracteres como <, >, " y ' a entidades escapadas HTML.


ASP.NET

Código Vulnerable

csharp
public void Page_Load(object sender, EventArgs e)
{
    var userInput = Request.QueryString["q"];
    Response.Write("User Input: " + userInput);
}

Si no estás familiarizado con ASP.NET y C#, el código de arriba usa Request.QueryString que devuelve una colección de claves y valores asociados. En el ejemplo de arriba guardamos en userInput el valor de q.

Código Arreglado

csharp
using System.Web;

public void Page_Load(object sender, EventArgs e)
{
    var userInput = Request.QueryString["q"];
    var encodedInput = HttpUtility.HtmlEncode(userInput);
    Response.Write("User Input: " + encodedInput);
}

Aquí hacemos lo mismo pero usamos el método HttpUtility.HtmlEncode() que convierte caracteres como <, > y & a sus respectivas entidades HTML-encodeadas.