FlawOpen

flawopen.com/SQL Injection/IDs numéricos

¿Por qué el formato de cadenas es peligroso incluso para los IDs numéricos?

Página de referencia
Respuesta breve

El riesgo no depende del significado real del valor: la consulta se construye concatenando texto. “Esto siempre es un número” es una suposición sobre los datos, no una garantía aplicada por el código.

Dónde falla la suposición

Los parámetros HTTP siempre llegan como cadenas

Un parámetro de URL o un campo de formulario viaja como texto, independientemente del tipo que espere la columna de la base de datos. Sin validación explícita, el framework no impide que alguien envíe id=1 OR 1=1 en vez de un número antes de que el código lo reciba.

Una refactorización futura puede cambiar silenciosamente el origen del valor

Un código que hoy es seguro porque el ID siempre procede de una fuente interna confiable puede volverse vulnerable cuando otro llamador pasa un valor del usuario a la misma función. La vulnerabilidad ya estaba latente, esperando ese cambio.

La coerción de tipos puede ocultar el valor real

Sobre todo en lenguajes de tipado dinámico, un valor que parece numérico en los registros puede seguir siendo una cadena con caracteres adicionales. Sin validación explícita, el tipo esperado no garantiza el tipo recibido.

Parametriza la consulta incluso después de validar el tipo

Valida el ID según el formato esperado por la aplicación y luego pásalo mediante la API de parámetros del controlador de base de datos. La validación protege el contrato de entrada; la parametrización evita que la entrada se interprete como sintaxis SQL. El comportamiento del controlador en el protocolo puede variar, así que no supongas que todos transmiten los valores del mismo modo. Los parámetros de PostgreSQL no representan identificadores de tablas o columnas; selecciona la estructura dinámica de la consulta de una lista fija permitida por la aplicación.

Vincula el valor numérico; valida su significado

Valida el ID como un entero positivo seguro si ese es el tipo que requiere la aplicación, pero aun así vincúlalo como valor de la consulta. La validación protege el contrato de entrada de la aplicación; la parametrización mantiene la entrada fuera de la sintaxis SQL. Los parámetros de PostgreSQL no sustituyen identificadores de tablas o columnas, así que elige cualquier estructura dinámica de consulta de una lista fija permitida por la aplicación.

INSEGURO — interpola el ID en SQL
const rawId = new URL(request.url).searchParams.get("id");
const result = await pool.query(
  `SELECT id FROM users WHERE id = ${rawId}`
);
SEGURO — valida el ID y vincula el valor
const rawId = new URL(request.url).searchParams.get("id");
if (rawId === null || !/^[0-9]+$/.test(rawId)) {
  return new Response("Invalid id", { status: 400 });
}
const id = Number(rawId);
if (!Number.isSafeInteger(id) || id < 1) {
  return new Response("Invalid id", { status: 400 });
}
const result = await pool.query(
  "SELECT id FROM users WHERE id = $1",
  Array.of(id)
);

Referencias

Véase también Inyección SQL (CWE-89): cómo funciona y cómo evitarla en cada lenguaje — flawopen.com ¿Qué es una consulta parametrizada? ¿Prisma $queryRaw es seguro frente a SQL Injection? ¿Es segura la interpolación en where() de Rails? ¿Sequelize query() es seguro frente a SQL Injection? ¿SQLAlchemy text() es seguro frente a SQL Injection? ¿La verificación de consultas de sqlx en compilación evita la inyección SQL?