Un buscador que dice demasiado
Una tienda online de Sevilla tiene un buscador de pedidos. Escribes tu nombre y ves tus pedidos.
Un día alguien escribe un texto raro con comillas. De repente ve los pedidos de todos los clientes.
El fallo no está en la base de datos. Está en cómo el programa mezcla lo que escribe el usuario con su propio código.
Al final de esta lección
Nota
Puedo explicar cómo funcionan la inyección y el cross-site scripting.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Entrada | lo que escribe o envía el usuario | |
| Inyección SQL | entrada que se cuela como parte de una consulta | |
| Consulta con parámetros | consulta que lleva la entrada aparte, nunca pegada | WHERE customer = ? |
| Cross-site scripting | entrada que el navegador ejecuta como código | XSS |
| Escapar | cambiar caracteres especiales para que se lean como texto |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
En una inyección, la entrada se vuelve código
Muchos programas construyen una consulta SQL pegando lo que escribe el usuario. Si alguien escribe comillas y un trozo de SQL, la base de datos lo ejecuta como parte de la consulta.
Con OR '1'='1', la condición es verdadera para todas las filas. Así salen datos de otros clientes.
La defensa es una consulta con parámetros. La entrada viaja aparte y nunca se mezcla con el código.
Prueba: pegar frente a usar un parámetro
import sqlite3
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE orders (customer TEXT, total REAL)")
db.execute("INSERT INTO orders VALUES ('ana', 35.5), ('luis', 120.0)")
# Lo que escribe alguien en el buscador de pedidos
typed = "ana' OR '1'='1"
# MAL: el texto se pega dentro de la consulta
query = "SELECT * FROM orders WHERE customer = '" + typed + "'"
print(query)
print("Pegando el texto:", db.execute(query).fetchall())
# BIEN: el texto va aparte, como un parámetro
safe = "SELECT * FROM orders WHERE customer = ?"
print("Con parámetro:", db.execute(safe, (typed,)).fetchall())
# Cambia typed por "ana" y vuelve a ejecutar.
En un XSS, el navegador ejecuta la entrada
El cross-site scripting, o XSS, pasa en la página web y no en la base de datos.
Alguien deja una reseña con una etiqueta <script>. Si la web la muestra tal cual, el navegador de cada visitante ejecuta ese código. Puede robar su sesión.
La defensa es escapar la entrada antes de mostrarla. Así < se convierte en < y se ve como texto.
Prueba: escapar una reseña
import html
# Una reseña que alguien deja en la web de un restaurante
review = "Muy rico <script>alert('hola')</script>"
# MAL: la reseña se mete tal cual en la página
print("<p>" + review + "</p>")
# BIEN: los caracteres especiales se escapan antes
print("<p>" + html.escape(review) + "</p>")
# Escribe una reseña con <b>negrita</b> y mira qué sale.
Ejemplo: los dos ataques lado a lado
| Aspecto | Inyección SQL | Cross-site scripting |
|---|---|---|
| Dónde ocurre | En la base de datos | En el navegador |
| Qué entra | Un trozo de SQL | Una etiqueta script |
| Qué se pierde | Datos de otros clientes | Sesiones de visitantes |
| Defensa | Consulta con parámetros | Escapar al mostrar |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que basta con revisar la entrada en el navegador. Cualquiera puede saltarse el navegador y enviar la petición directamente. Usa parámetros y escapa siempre en el servidor, porque es la única parte que controlas tú.