¿Por qué no solo hacer que las consultas no parametrizadas devuelvan un error?

22

La inyección de SQL es un problema de seguridad muy serio, en gran parte porque es muy fácil equivocarse: la forma obvia e intuitiva de crear una consulta que incorpore la entrada del usuario lo deja vulnerable, y la forma correcta de mitigarlo requiere que conozca los parámetros consultas e inyección SQL primero.

Me parece que la forma obvia de solucionar esto sería cerrar la opción obvia (pero incorrecta): arreglar el motor de la base de datos para que cualquier consulta recibida que use valores codificados en su cláusula WHERE en lugar de parámetros devuelva una descripción agradable y descriptiva mensaje de error que le indica que use parámetros en su lugar. Obviamente, esto debería tener una opción de exclusión para que cosas como las consultas ad-hoc de las herramientas administrativas aún se ejecuten fácilmente, pero deberían habilitarse de manera predeterminada.

Tener esto cerraría la inyección de SQL en frío, casi de la noche a la mañana, pero que yo sepa, ningún RDBMS realmente hace esto. ¿Hay alguna buena razón por la que no?

Mason Wheeler
fuente
22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'tendría valores codificados y parametrizados en una sola consulta. ¡Intente captar eso! Creo que hay casos de uso válidos para tales consultas mixtas.
amon
66
¿Qué tal seleccionar registros de hoySELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee
10
@MasonWheeler lo siento, quise decir "tratar de permitir eso". Tenga en cuenta que está perfectamente parametrizado y no sufre de inyección SQL. Sin embargo, el controlador de la base de datos no puede determinar si el literal "bad"es realmente literal o si es el resultado de la concatenación de cadenas. Las dos soluciones que veo son deshacerse de SQL y otros DSL incrustados en cadenas (sí, por favor), o promover lenguajes donde la concatenación de cadenas es más molesta que usar consultas parametrizadas (umm, no).
amon
44
y cómo detectaría el RDBMS si hacer esto? De la noche a la mañana, sería imposible acceder al RDBMS usando un mensaje SQL interactivo ... Ya no podrá ingresar comandos DDL o DML con ninguna herramienta.
Jwenting
8
En cierto sentido, puede hacer esto: no construya consultas SQL en tiempo de ejecución, en su lugar, use un ORM u otra capa de abstracción que evite que necesite construir consultas SQL. ¿ORM no tiene las características que necesita? Entonces SQL es un lenguaje destinado a personas que desean escribir SQL, por lo que en general les permite escribir SQL. El problema fundamental es que generar código dinámicamente es más difícil de lo que parece, pero la gente quiere hacerlo de todos modos y no estarán satisfechos con los productos que no los dejan.
Steve Jessop

Respuestas:

45

Hay demasiados casos en los que usar un literal es el enfoque correcto.

Desde el punto de vista del rendimiento, hay veces que desea literales en sus consultas. Imagine que tengo un rastreador de errores donde una vez que sea lo suficientemente grande como para preocuparse por el rendimiento, espero que el 70% de los errores en el sistema estén "cerrados", el 20% estarán "abiertos", el 5% estarán "activos" y 5 % estará en algún otro estado. Es razonable que desee tener la consulta que devuelve todos los errores activos

SELECT *
  FROM bug
 WHERE status = 'active'

en lugar de pasar el statuscomo una variable de enlace. Quiero un plan de consulta diferente según el valor que se haya pasado status: me gustaría hacer un escaneo de tabla para devolver los errores cerrados y un escaneo de índice en elstatuscolumna para devolver los préstamos activos. Ahora, diferentes bases de datos y diferentes versiones tienen diferentes enfoques para (más o menos exitosamente) permitir que la misma consulta use un plan de consulta diferente dependiendo del valor de la variable de enlace. Pero eso tiende a introducir una cantidad decente de complejidad que debe gestionarse para equilibrar la decisión de si molestarse en volver a analizar una consulta o si reutilizar un plan existente para un nuevo valor de variable de enlace. Para un desarrollador, puede tener sentido lidiar con esta complejidad. O puede tener sentido forzar una ruta diferente cuando tengo más información sobre cómo se verán mis datos que el optimizador.

Desde el punto de vista de la complejidad del código, también hay muchas veces que tiene mucho sentido tener literales en las declaraciones SQL. Por ejemplo, si tiene una zip_codecolumna que tiene un código postal de 5 caracteres y a veces tiene 4 dígitos adicionales, tiene mucho sentido hacer algo como

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

en lugar de pasar 4 parámetros separados para los valores numéricos. Estas no son cosas que cambiarán alguna vez, por lo que convertirlas en variables de enlace solo sirve para hacer que el código sea potencialmente más difícil de leer y para crear el potencial de que alguien enlace parámetros en el orden incorrecto y termine con un error.

Justin Cave
fuente
12

La inyección de SQL ocurre cuando una consulta se construye concatenando texto de una fuente no confiable y no validada con otras partes de una consulta. Si bien tal cosa ocurriría con mayor frecuencia con los literales de cadena, esa no sería la única forma en que podría ocurrir. Una consulta de valores numéricos puede tomar una cadena ingresada por el usuario (que se supone que solo contiene dígitos) y concatenarse con otro material para formar una consulta sin las comillas normalmente asociadas con literales de cadena; El código que confía demasiado en la validación del lado del cliente puede tener cosas como nombres de campo que provienen de una cadena de consulta HTML. No hay forma de que el código que mira una cadena de consulta SQL pueda ver cómo se ensambló.

Lo importante no es si una instrucción SQL contiene literales de cadena, sino más bien si una cadena contiene secuencias de caracteres de fuentes no confiables , y la validación para eso se manejaría mejor en la biblioteca que genera consultas. En general, no hay forma en C # de escribir código que permita un literal de cadena pero no permita otros tipos de expresión de cadena, pero uno podría tener una regla de prácticas de codificación que requiera que las consultas se construyan usando una clase de construcción de consultas en lugar de concatenación de cadenas, y cualquiera que pase una cadena no literal al generador de consultas debe justificar dicha acción.

Super gato
fuente
1
Como una aproximación para "es un literal", puede verificar si la cadena está internada.
CodesInChaos
1
@CodesInChaos: Verdadero, y tal prueba podría ser lo suficientemente precisa para este propósito, siempre que cualquiera que tuviera una razón para generar una cadena en tiempo de ejecución utilizara un método que aceptara una cadena no literal en lugar de internar la cadena generada en tiempo de ejecución y usar eso (dar al método de cadena no literal un nombre diferente facilitaría a los revisores de código inspeccionar todos los usos del mismo).
supercat
Tenga en cuenta que si bien no hay forma de hacerlo en C #, algunos otros lenguajes tienen facilidades que lo hacen posible (por ejemplo, el módulo de cadena contaminada de Perl).
Jules
Más sucintamente, este es un problema del cliente , no un problema del servidor.
Blrfl
7
SELECT count(ID)
FROM posts
WHERE deleted = false

Si desea poner los resultados de estos en el pie de página de su foro, necesitará agregar un parámetro ficticio solo para decir falso cada vez. O el ingenuo programador web busca cómo deshabilitar esa advertencia y luego continúa.

Ahora puede decir que agregaría una excepción para las enumeraciones, pero eso solo abre el agujero nuevamente (aunque más pequeño). Sin mencionar que las personas primero deben ser educadas para no usarlas varchars.

El verdadero problema de la inyección es construir mediante programación la cadena de consulta. La solución para eso es un mecanismo de procedimiento almacenado y hacer cumplir su uso o una lista blanca de consultas permitidas.

monstruo de trinquete
fuente
2
Si su solución para "es demasiado fácil olvidar, o no saber en primer lugar, usar consultas parametrizadas" es "hacer que todos recuerden, y saber en primer lugar, usar procedimientos almacenados", entonces usted Te estás perdiendo todo el punto de la pregunta.
Mason Wheeler
55
He visto inyección SQL a través de procedimientos almacenados en mi trabajo. Resulta que los procedimientos almacenados obligatorios para todo es MALO. Siempre hay ese 0.5% que son consultas dinámicas verdaderas (no puede parametrizar una cláusula where completa, y mucho menos una tabla que se une).
Joshua
En el ejemplo de esta respuesta, puede reemplazar deleted = falsecon NOT deleted, lo que evita el literal. Pero el punto es válido en general.
psmears
5

TL; DR : Tendría que restringir todos los literales, no solo los de las WHEREcláusulas. Por razones que no lo hacen, permite que la base de datos permanezca desacoplada de otros sistemas.

En primer lugar, su premisa es defectuosa. Desea restringir solo las WHEREcláusulas, pero ese no es el único lugar al que puede ir la entrada del usuario. Por ejemplo,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Esto es igualmente vulnerable a la inyección SQL:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Por lo tanto, no puede limitar los literales en la WHEREcláusula. Tienes que restringir todos los literales.

Ahora nos queda la pregunta: "¿Por qué permitir literales?" Tenga esto en cuenta: si bien las bases de datos relacionales se usan debajo de una aplicación escrita en otro idioma la mayor parte del tiempo, no es necesario que use el código de la aplicación para usar la base de datos. Y aquí tenemos una respuesta: necesitas literales para escribir código. La única otra alternativa sería requerir que todo el código se escriba en un idioma independiente de la base de datos. Así que tenerlos le da la capacidad de escribir "código" (SQL) directamente en la base de datos. Este es un desacoplamiento valioso, y sería imposible sin literales. (Intente escribir en su idioma favorito en algún momento sin literales. Estoy seguro de que puede imaginar lo difícil que sería).

Como ejemplo común, los literales a menudo se usan en la población de tablas de lista de valores / búsqueda:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

Sin ellos, necesitaría escribir código en otro lenguaje de programación solo para completar esta tabla. La capacidad de hacerlo directamente en SQL es valiosa .

Luego nos queda una pregunta más: ¿por qué no lo hacen las bibliotecas cliente de lenguaje de programación? Y aquí tenemos una respuesta muy simple: habrían vuelto a implementar todo el analizador de la base de datos para cada versión compatible de la base de datos . ¿Por qué? Porque no hay otra manera de garantizar que hayas encontrado todos los literales. Las expresiones regulares no son suficientes. Por ejemplo: esto contiene 4 literales separados en PostgreSQL:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

Intentar hacerlo sería una pesadilla de mantenimiento, especialmente porque la sintaxis válida a menudo cambia entre las principales versiones de las bases de datos.

jpmc26
fuente