Inyección: cuando la entrada se convierte en código
La inyección es el fallo grave más antiguo y todavía el más común. Ocurre cuando datos proporcionados por un usuario se tratan como instrucciones en lugar de como datos. Dos variantes importan para la mayoría de las apps.
Inyección — SQL (Structured Query Language, el lenguaje que las apps usan para pedirle datos a una ) — ocurre cuando la entrada del usuario se cuela dentro de una consulta a la base de datos. El patrón vulnerable clásico:
// VULNERABLE: the user's input is glued straight into the query
app.get("/user", (req, res) => {
const name = req.query.name;
db.query(`SELECT * FROM users WHERE name = '${name}'`);
});
// If someone passes name = '; DROP TABLE users; --
// your query becomes a command to delete the table.
La solución es una consulta parametrizada (también llamada sentencia preparada). Envías la consulta y los datos por separado, de modo que la base de datos nunca confunde uno con otro:
// SAFE: the value is passed as a parameter, never as code
app.get("/user", (req, res) => {
const name = req.query.name;
db.query("SELECT * FROM users WHERE name = ?", [name]);
});
La regla: nunca construyas una consulta por concatenación de cadenas de texto. Si ves comillas invertidas o + ensamblando SQL alrededor de una variable en un diff, detente y pide la versión parametrizada.
Los dos caminos meten la misma entrada del usuario en la base de datos, pero solo uno evita que la base de datos confunda esa entrada con comandos:
user input: '; DROP TABLE users; --
│
├─────────────────────────┬───────────────────────────┐
▼ ▼
✗ CONCATENATED ✓ PARAMETERIZED
"...WHERE name= "...WHERE name = ?", [input]
'" + input + "'" (query and data sent separately)
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ DATABASE │ │ DATABASE │
│ reads input │ │ treats input │
│ AS COMMANDS │ │ as DATA only │
│ → table gone │ │ → safe lookup │
└───────────────┘ └───────────────┘
BREACH SAFE
XSS (cross-site scripting, o secuencias de comandos entre sitios) es la misma idea pero en el navegador. Si tomas la entrada de un usuario y la vuelcas en una página como HTML crudo (HyperText Markup Language, el código que le indica al navegador qué mostrar), un atacante puede inyectar una etiqueta <script> que se ejecuta en los navegadores de tus otros usuarios, robando sus sesiones, por ejemplo. La solución es el escapado: renderizar el contenido del usuario como texto, no como HTML. Los frameworks modernos como React escapan por defecto, lo cual es genial, hasta que la IA recurre a una vía de escape como dangerouslySetInnerHTML o innerHTML para "hacer que funcione". Esas rutas evitan la protección. Trata cualquier llamada de ese tipo en un diff como algo que hay que cuestionar.
El principio unificador detrás de todo fallo de inyección: mantén los datos y el código separados. Cada vez que la entrada de un usuario cruza hacia una consulta, un comando, una plantilla o HTML, algo debe escaparla o parametrizarla.