flawopen.com/Inyección SQL/Python
Imagina un formulario que solo espera un número de ticket, como 482. La inyección SQL ocurre cuando alguien escribe algo malicioso en esa casilla en lugar de un número — una frase trampa que hace que el sistema responda "muéstrame todos los tickets" en lugar de solo el ticket 482, porque el sistema nunca verificó que lo que recibió fuera realmente solo un número.
La inyección SQL ocurre cuando una entrada controlada por el usuario se inserta directamente en el texto de una consulta a la base de datos, en lugar de pasarse como un valor independiente. Si el código construye una consulta uniendo cadenas de texto, un atacante puede proporcionar una entrada que cambie la estructura real de la consulta — convirtiendo una consulta de "buscar una fila" en una que devuelve todas las filas, o que elimina una tabla.
En Python, esto casi siempre aparece de la misma forma: una llamada a la base de datos construida con un f-string, formato con %, o concatenación con +, en lugar de los marcadores de parámetros integrados del driver de la base de datos.
En 2015, la operadora británica TalkTalk sufrió una brecha que afectó a más de 150.000 clientes después de que unos atacantes explotaran una falla de inyección SQL en una página web heredada de una adquisición. El organismo regulador de protección de datos del Reino Unido multó a TalkTalk con £400.000, describiendo el fallo como evitable y básico.
Fuente: notificación de sanción de la Information Commissioner's Office del Reino Unido, 2016 — ver Referencias más abajo.# user_id viene directamente de la petición
def get_user(cursor, user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
return cursor.fetchone()
# el valor se pasa por separado, nunca insertado
def get_user(cursor, user_id):
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
return cursor.fetchone()
La versión corregida pasa el texto de la consulta y el valor a execute() como dos argumentos separados. El driver de la base de datos también los envía por separado a la base de datos — la estructura de la consulta queda fijada antes de que el valor se le adjunte, así que el valor nunca puede interpretarse como parte de la sintaxis SQL, sin importar qué caracteres contenga. Un f-string no puede lograr esto, porque, para cuando execute() recibe la consulta, el valor ya se incorporó al texto como si siempre hubiera formado parte del comando.
cursor.execute("... WHERE id = %s" % user_id) es tan vulnerable como un f-string. El marcador solo se vuelve seguro cuando el valor se pasa como el segundo argumento de execute() en sí — cursor.execute("...WHERE id = %s", (user_id,)) — para que sea el driver, y no el formateo de cadenas de Python, quien haga la sustitución.
El ORM de Django y el constructor de consultas de SQLAlchemy parametrizan automáticamente en las consultas normales. El riesgo vuelve en cuanto usas Model.objects.raw() o text() de SQLAlchemy y construyes ese SQL crudo con un f-string.
psycopg2 (PostgreSQL) usa %s sin importar el tipo de columna; sqlite3 usa ?. Copiar el estilo de marcador de la documentación de un driver a otro falla de forma silenciosa — comprueba el estilo de parámetros de tu driver concreto en lugar de asumirlo.
Cierto para la API de consulta normal del ORM — falso en cuanto usas raw() o text() y construyes esa cadena tú mismo.
El riesgo no está en el tipo del valor en tiempo de ejecución — está en que la consulta se construye por interpolación de cadenas. En cuanto esa suposición falle en cualquier momento del ciclo de vida del código, la vulnerabilidad ya estará ahí esperando.
El escapado manual depende del driver y es fácil de hacer mal de forma sutil. Las consultas parametrizadas no son una versión más estricta del escapado — evitan el problema por completo, porque el valor nunca forma parte del texto de la consulta.
grep -rn "execute(f\"" --include="*.py" .
grep -rn "execute(.*%\s*(" --include="*.py" .
grep -rn "\.raw(\|text(" --include="*.py" .
Para sus métodos de consulta normales, sí. Sus vías de escape para consultas crudas no — son exactamente tan seguras como SQL escrito a mano, ni más.
No. Cualquier cosa efectivamente controlada por un tercero cuenta — cabeceras HTTP, nombres de archivos subidos, incluso un valor proveniente de una API externa en la que confía tu aplicación.
Puedes, pero es frágil y depende del driver. Las consultas parametrizadas son la corrección real, no una versión más estricta del escapado.