¿Por qué el mecanismo de prevención de inyección SQL evolucionó en la dirección de usar consultas parametrizadas?

59

De la forma en que lo veo, los ataques de inyección de SQL se pueden prevenir mediante:

  1. Selección, filtrado, entrada de codificación con cuidado (antes de la inserción en SQL)
  2. Usar declaraciones preparadas / consultas parametrizadas

Supongo que hay ventajas y desventajas para cada uno, pero ¿por qué despegó # 2 y se consideró que era más o menos la forma de facto para prevenir los ataques de inyección? ¿Es más seguro y menos propenso a errores o hubo otros factores?

Según tengo entendido, si el # 1 se usa correctamente y todas las advertencias están cubiertas, puede ser tan efectivo como el # 2.

Desinfección, filtrado y codificación

Hubo cierta confusión por mi parte entre lo que significaba sanitizing , filtering y encoding . Diré que para mis propósitos, todo lo anterior puede considerarse para la opción 1. En este caso, entiendo que la limpieza y el filtrado tienen el potencial de modificar o descartar los datos de entrada, mientras que la codificación de conserva los datos tal como están , pero los codifica correctamente para evitar ataques de inyección. Creo que los datos de escape pueden considerarse como una forma de codificarlos.

Consultas parametrizadas frente a la biblioteca de codificación

Hay respuestas donde los conceptos de parameterized queries y encoding libraries se tratan indistintamente. Corríjame si me equivoco, pero tengo la impresión de que son diferentes.

Entiendo que encoding libraries , no importa lo buenos que sean, siempre tienen el potencial para modificar el "Programa" de SQL, porque están realizando cambios en el propio SQL, antes de enviarlo a la RDBMS.

Por otra parte,

Parameterized queries , envía el programa SQL a RDBMS, que luego optimiza la consulta, define el plan de ejecución de la consulta, selecciona los índices que se van a utilizar, etc., y luego conecta los datos, como El último paso dentro del propio RDBMS.

Biblioteca de codificación

  data -> (encoding library)
                  |
                  v
SQL -> (SQL + encoded data) -> RDBMS (execution plan defined) -> execute statement

Consulta parametrizada

                                               data
                                                 |
                                                 v
SQL -> RDBMS (query execution plan defined) -> data -> execute statement

Significado histórico

Algunas respuestas mencionan que históricamente, las consultas parametrizadas (PQ) se crearon por razones de rendimiento, y antes de que los ataques de inyección que apuntaban a los problemas de codificación se volvieran populares. En algún momento se hizo evidente que los PQ también eran bastante efectivos contra los ataques de inyección. Para seguir con el espíritu de mi pregunta, ¿por qué PQ siguió siendo el método de elección y por qué floreció por encima de la mayoría de los otros métodos cuando se trata de prevenir los ataques de inyección SQL?

    
pregunta Dennis 12.09.2016 - 16:04

14 respuestas

146

El problema es que el # 1 requiere que analices e interpretes de manera efectiva la totalidad de la variante de SQL con la que estás trabajando para que sepas que está haciendo algo que no debería. Y mantenga ese código actualizado a medida que actualiza su base de datos. En todas partes usted acepta entradas para sus consultas. Y no lo arruines.

Entonces, sí, ese tipo de cosas detendría los ataques de inyección de SQL, pero su implementación es absurdamente más costosa.

    
respondido por el Telastyn 12.09.2016 - 16:08
79

Porque la opción 1 no es una solución. La detección y el filtrado significan rechazar o eliminar entradas inválidas. Pero cualquier entrada puede ser válida. Por ejemplo, apostrophe es un carácter válido en el nombre "O'Malley". Solo tiene que codificarse correctamente antes de usarse en SQL, que es lo que hacen las declaraciones preparadas.

Después de agregar la nota, parece que básicamente estás preguntando por qué usar una función de biblioteca estándar en lugar de escribir tu propio código funcional desde cero. Debe siempre preferir las soluciones de biblioteca estándar a escribir su propio código. Es menos trabajo y más mantenible. Este es el caso de la funcionalidad any , pero especialmente para algo que es sensible a la seguridad, no tiene ningún sentido reinventar la rueda por su cuenta.

    
respondido por el JacquesB 12.09.2016 - 18:07
60

Si estás tratando de hacer el procesamiento de cadenas, entonces realmente no estás generando una consulta SQL. Estás generando una cadena que puede producir una consulta SQL. Hay un nivel de direccionamiento que abre un lote de espacio para errores y errores. En realidad, es algo sorprendente, dado que en la mayoría de los contextos nos complace interactuar con algo mediante programación. Por ejemplo, si tenemos alguna estructura de lista y queremos agregar un elemento, generalmente no lo hacemos:

List<Integer> list = /* a list of 1, 2, 3 */
String strList = list.toString();   /* to get "[1, 2, 3]" */
strList = /* manipulate strList to become "[1, 2, 5, 3]" */
list = parseList(strList);

Si alguien sugiere hacer eso, deberías responder correctamente que es bastante ridículo y que solo debes hacerlo:

List<Integer> list = /* ... */;
list.add(5, position=2);

Que interactúa con la estructura de datos en su nivel conceptual. No introduce ninguna dependencia sobre cómo se puede imprimir o analizar esa estructura. Esas son decisiones completamente ortogonales.

Su primer enfoque es como la primera muestra (solo un poco peor): está asumiendo que puede construir programáticamente la cadena que se analizará correctamente como la consulta que desea. Eso depende del analizador y de un montón de lógica de procesamiento de cadenas.

El segundo enfoque del uso de consultas preparadas es mucho más parecido a la segunda muestra. Cuando usa una consulta preparada, básicamente analiza una pseudo consulta que es legal pero tiene algunos marcadores de posición y luego usa una API para sustituir correctamente algunos valores allí. Ya no implica el proceso de análisis y no tiene que preocuparse por el procesamiento de cadenas.

En general, es mucho más fácil, y mucho menos propenso a errores, interactuar con las cosas en su nivel conceptual. Una consulta no es una cadena, una consulta es lo que obtienes cuando analizas una cadena, o construyes una programáticamente (o cualquier otro método te permite crear una).

Aquí hay una buena analogía entre las macros de estilo C que hacen el reemplazo de texto simple y las macros de estilo Lisp que hacen la generación de código arbitrario. Con las macros de estilo C, puede reemplazar el texto en el código fuente, y eso significa que tiene la capacidad de introducir errores sintácticos o comportamientos engañosos. Con las macros de Lisp, está generando código en la forma en que el compilador lo procesa (es decir, está devolviendo las estructuras de datos reales que procesa el compilador, no el texto que el lector debe procesar antes de que el compilador pueda acceder a él) . Sin embargo, con una macro Lisp, no puede generar algo que sería un error de análisis. Por ejemplo, no puede generar (deje ((a b) a .

Sin embargo, incluso con las macros de Lisp, aún puede generar un código incorrecto, porque no necesariamente tiene que estar al tanto de la estructura que se supone que debe estar allí. Por ejemplo, en Lisp, (vamos a ((ab)) a) significa "establecer un nuevo enlace léxico de la variable a al valor de la variable b, y luego devolver el valor de a", y < strong> (vamos a (ab) a) significa "establecer nuevos enlaces léxicos de las variables a y b e inicializarlos en cero, y luego devolver el valor de a". Ambos son sintácticamente correctos, pero significan cosas diferentes. Para evitar este problema, podría usar más funciones semánticas y hacer algo como:

Variable a = new Variable("a");
Variable b = new Variable("b");
Let let = new Let();
let.getBindings().add(new LetBinding(a,b));
let.setBody(a);
return let;

Con algo como eso, es imposible devolver algo que no es sintácticamente válido, y es mucho más difícil devolver algo que accidentalmente no es lo que querías.

    
respondido por el Joshua Taylor 13.09.2016 - 00:06
21

Ayuda que la opción # 2 generalmente se considera una mejor práctica porque la base de datos puede almacenar en caché la versión sin parámetros de la consulta. Las consultas parametrizadas son anteriores a la cuestión de la inyección de SQL por varios años (creo), da la casualidad de que se pueden matar dos pájaros de un tiro.

    
respondido por el JasonB 12.09.2016 - 19:29
20

Simplemente dijo: No lo hicieron. Su declaración:

  

¿Por qué el mecanismo de prevención de inyección de SQL evolucionó en la dirección   de utilizar consultas parametrizadas?

es fundamentalmente defectuoso. Las consultas parametrizadas han existido por mucho más tiempo que la inyección SQL, al menos es ampliamente conocida. En general, se desarrollaron como una forma de evitar la concentración de cadenas en la funcionalidad habitual de "forma para búsqueda" que tienen las aplicaciones LOB (línea de negocio). Muchos, muchos años después, alguien encontró un problema de seguridad con la manipulación de dicha cadena.

Recuerdo haber estado haciendo SQL hace 25 años (cuando internet NO se usaba mucho, apenas estaba empezando) y recuerdo haber hecho SQL vs IBM DB5 IIRC versión 5, y eso ya tenía consultas parametrizadas.

    
respondido por el TomTom 14.09.2016 - 13:44
13

Además de todas las otras buenas respuestas:

La razón por la que # 2 es mejor es porque separa tus datos de tu código. En el # 1 sus datos son parte de su código y de ahí vienen todas las cosas malas. Con # 1 obtiene su consulta y necesita realizar pasos adicionales para asegurarse de que su consulta entienda sus datos como datos, mientras que en # 2 obtiene su código y su código y sus datos son datos.

    
respondido por el Pieter B 13.09.2016 - 08:56
11

Las consultas parametrizadas, además de proporcionar defensa de inyección SQL, a menudo tienen el beneficio adicional de compilarse solo una vez y luego ejecutarse varias veces con diferentes parámetros.

Desde el punto de vista de la base de datos SQL select * from employees where last_name = 'Smith' y select * from employees where last_name = 'Fisher' son claramente diferentes y, por lo tanto, requieren análisis, compilación y optimización por separado. También ocuparán ranuras separadas en el área de memoria dedicada al almacenamiento de declaraciones compiladas. En un sistema muy cargado con una gran cantidad de consultas similares que tienen diferentes parámetros, el cálculo y la sobrecarga de memoria pueden ser sustanciales.

Posteriormente, el uso de consultas parametrizadas a menudo proporciona importantes ventajas de rendimiento.

    
respondido por el mustaccio 12.09.2016 - 19:36
5

Espera pero ¿por qué?

La opción 1 significa que debe escribir rutinas de desinfección para cada tipo de entrada, mientras que la opción 2 es menos propensa a errores y menos código para que escriba / pruebe / mantenga.

Es casi seguro que "cuidar todas las advertencias" puede ser más complejo de lo que cree, y su idioma (por ejemplo, Java PreparedStatement) tiene más contenido que lo que cree.

Las declaraciones preparadas o las consultas parametrizadas se compilan previamente en el servidor de la base de datos, por lo que, cuando se configuran los parámetros, no se realiza la concatenación de SQL porque la consulta ya no es una cadena SQL. Una ventaja adicional es que el RDBMS almacena en caché la consulta y las llamadas subsiguientes se consideran el mismo SQL incluso cuando los valores de los parámetros varían, mientras que con el SQL concatenado cada vez que la consulta se ejecuta con diferentes valores, la consulta es diferente y el RDBMS tiene que analizarla , vuelve a crear el plan de ejecución, etc.

    
respondido por el Tulains Córdova 12.09.2016 - 16:17
1

Imaginemos cómo sería un enfoque ideal de "sanear, filtrar y codificar".

La desinfección y el filtrado pueden tener sentido en el contexto de una aplicación en particular, pero al final ambos se reducen a decir "no se pueden poner estos datos en la base de datos". Para su aplicación, podría ser una buena idea, pero no es algo que pueda recomendar como una solución general, ya que habrá aplicaciones que deben poder almacenar caracteres arbitrarios en la base de datos.

Eso deja la codificación. Puede comenzar por tener una función que codifique cadenas agregando caracteres de escape, de modo que pueda sustituirlos en usted mismo. Dado que las diferentes bases de datos necesitan diferentes caracteres de escape (en algunas bases de datos, tanto \' como '' son secuencias de escape válidas para ' , pero no en otras), esta función debe proporcionarla el proveedor de la base de datos.

Pero no todas las variables son cadenas. A veces es necesario sustituir en un número entero o una fecha. Estos se representan de forma diferente a las cadenas, por lo que necesita diferentes métodos de codificación (de nuevo, estos deberían ser específicos para el proveedor de la base de datos), y debe sustituirlos en la consulta de diferentes formas.

Entonces, tal vez las cosas serían más fáciles si la base de datos manejara la sustitución también para usted: ya sabe qué tipos espera la consulta, cómo codificar los datos de manera segura y cómo sustituirlos en su consulta de manera segura, por lo que no necesita preocuparse por eso en tu código.

En este punto, acabamos de reinventar las consultas parametrizadas.

Y una vez que se parametrizan las consultas, se abren nuevas oportunidades, como optimizaciones de rendimiento y supervisión simplificada.

La codificación es difícil de hacer bien, y encoding-done-right es indistinguible de la parametrización.

Si realmente te gusta la interpolación de cadenas como una forma de crear consultas, hay un par de idiomas (vienen a la mente Scala y ES2015) que tienen interpolación de cadenas conectables, así que there son bibliotecas que le permiten escribir consultas parametrizadas que parecen interpolación de cadenas, pero están a salvo de la inyección de SQL - Así en la sintaxis de ES2015:

import {sql} from 'cool-sql-library'

let result = sql'select *
    from users
    where user_id = ${user_id}
      and password_hash = ${password_hash}'.execute()

console.log(result)
    
respondido por el James_pic 14.09.2016 - 18:08
0

En la opción 1, está trabajando con un conjunto de entrada de tamaño = infinito que está tratando de asignar a un tamaño de salida muy grande. En la opción 2, ha limitado su entrada a lo que elija. En otras palabras:

  1. Examinando y filtrando cuidadosamente [ infinito ] para [ todas las consultas de SQL seguras ]
  2. Usar [ escenarios preconsiderados limitados a su alcance ]

Según otras respuestas, también parece haber algunos beneficios de rendimiento al limitar su alcance lejos del infinito y hacia algo manejable.

    
respondido por el Mutant Platypus 12.09.2016 - 22:34
0

Un modelo mental útil de SQL (especialmente dialectos modernos) es que cada instrucción o consulta SQL es un programa. En un programa ejecutable binario nativo, los tipos más peligrosos de vulnerabilidades de seguridad son los desbordamientos donde un atacante puede sobrescribir o modificar el código del programa con diferentes instrucciones.

Una vulnerabilidad de inyección SQL es isomorfa a un desbordamiento de búfer en un lenguaje como C. La historia ha demostrado que los desbordamientos de búfer son extremadamente difíciles de prevenir, incluso un código extremadamente crítico sujeto a revisión abierta a menudo ha contenido dichas vulnerabilidades.

Un aspecto importante del enfoque moderno para resolver vulnerabilidades de desbordamiento es el uso de hardware y mecanismos del sistema operativo para marcar partes particulares de la memoria como no ejecutables, y para marcar otras partes de la memoria como de solo lectura. (Consulte el artículo de Wikipedia en Protección de espacio ejecutable , por ejemplo). De esa manera, incluso si un atacante podría modificar los datos, el atacante no puede hacer que sus datos inyectados se traten como código.

Entonces, si una vulnerabilidad de inyección de SQL es equivalente a un desbordamiento de búfer, ¿cuál es el equivalente de SQL a un bit NX, o páginas de memoria de solo lectura? La respuesta es: declaraciones preparadas , que incluyen consultas parametrizadas y mecanismos similares para solicitudes de no consulta. La declaración preparada se compila con ciertas partes marcadas como de solo lectura, por lo que un atacante no puede cambiar esas partes del programa y otras partes marcadas como datos no ejecutables (los parámetros de la declaración preparada), en los que el atacante podría inyectar datos pero que nunca se tratará como código de programa, eliminando así la mayor parte del potencial de abuso.

Ciertamente, la entrada del usuario de desinfección es buena, pero para estar realmente seguro, debes ser paranoico (o, de manera equivalente, pensar como un atacante). Una superficie de control fuera del texto del programa es la forma de hacerlo, y las declaraciones preparadas proporcionan esa superficie de control para SQL. Por lo tanto, no debería sorprender que las declaraciones preparadas y las consultas parametrizadas sean el enfoque que recomienda la gran mayoría de los profesionales de la seguridad.

    
respondido por el Daniel Pryden 13.09.2016 - 08:23
0

Ya escribo sobre esto aquí: enlace

Pero, solo para mantenerlo simple:

La forma en que funcionan las consultas parametrizadas es que sqlQuery se envía como una consulta, y la base de datos sabe exactamente lo que hará esta consulta, y solo entonces insertará el nombre de usuario y las contraseñas simplemente como valores. Esto significa que no pueden afectar la consulta, porque la base de datos ya sabe lo que hará la consulta. Entonces, en este caso, buscaría un nombre de usuario de "Nadie OR 1 = 1 '-" y una contraseña en blanco, que debería aparecer como falsa.

Sin embargo, esto no es una solución completa, y aún será necesario realizar la validación de entrada, ya que esto no afectará otros problemas, como los ataques XSS, ya que aún podría poner javascript en la base de datos. Luego, si esto se lee en una página, se mostrará como un javascript normal, dependiendo de cualquier validación de salida. Entonces, lo mejor que se puede hacer es utilizar la validación de entrada, pero usar consultas parametrizadas o procedimientos almacenados para detener cualquier ataque de SQL

    
respondido por el Josip Ivic 16.09.2016 - 10:25
0

Nunca he usado SQL. Pero obviamente escuchas sobre los problemas que tiene la gente y los desarrolladores de SQL tuvieron problemas con esta "inyección de SQL". Durante mucho tiempo no pude entenderlo. Y luego me di cuenta de que las personas creaban sentencias de SQL, sentencias de origen de texto de SQL real, mediante la concatenación de cadenas, de las cuales algunas ingresaban por un usuario. Y mi primer pensamiento en esa realización fue de shock. Choque total. Pensé: ¿Cómo puede alguien ser tan ridículamente estúpido y crear declaraciones en cualquier lenguaje de programación como ese? Para un desarrollador de C, C ++, Java o Swift, esto es una absoluta locura.

Dicho esto, no es muy difícil escribir una función en C que toma una cadena en C como su argumento, y produce una cadena diferente que se ve exactamente como una cadena literal en el código fuente de C que representa la misma cadena. Por ejemplo, esa función traduciría abc a "abc", y "abc" a "\" abc \ "" y "\" abc \ "" a "\" \\ "abc \\" \ "". (Bueno, si esto te parece mal, eso es html. Tenía razón cuando lo escribí, pero no cuando se muestra) Y una vez que se escribe la función C, no es difícil generar el código fuente de C donde el texto de un campo de entrada proporcionado por el usuario se convierte en un literal de cadena C. Eso no es difícil de hacer seguro. Por qué los desarrolladores de SQL no usarían ese enfoque como una forma de evitar las inyecciones de SQL me supera.

"Desinfección" es un enfoque totalmente defectuoso. La falla fatal es que hace que ciertas entradas de usuarios sean ilegales. Terminas con una base de datos donde un campo de texto genérico no puede contener texto como; Suelta la tabla o lo que sea que usarías en una inyección SQL para causar daño. Me parece bastante inaceptable. Si una base de datos almacena texto, debería poder almacenar cualquier texto. Y la falla práctica es que parece que el desinfectante no puede hacerlo bien :-(

Por supuesto, las consultas parametrizadas son lo que esperaría cualquier programador que utilice un lenguaje compilado. Hace la vida mucho más fácil: tiene algo de entrada de cadena, y ni siquiera se molesta en traducirla en una cadena SQL, sino que simplemente la pasa como un parámetro, sin posibilidad de que ningún carácter de esa cadena cause ningún daño.

Entonces, desde el punto de vista de un desarrollador que usa lenguajes compilados, la desinfección es algo que nunca se me ocurriría. La necesidad de desinfectar es una locura. Las consultas parametrizadas son la solución obvia al problema.

(Me pareció interesante la respuesta de Josip. Básicamente dice que con las consultas parametrizadas puede detener cualquier ataque contra SQL, pero luego puede tener texto en su base de datos que se utiliza para crear una inyección de JavaScript :-( Bueno, tenemos la el mismo problema otra vez, y no sé si Javascript tiene una solución para eso.

    
respondido por el gnasher729 15.05.2017 - 23:45
-2

El principal problema es que los piratas informáticos encontraron formas de rodear el saneamiento mientras que las consultas parametrizadas eran un procedimiento existente que funcionaba perfectamente con los beneficios adicionales del rendimiento y la memoria.

Algunas personas simplifican el problema porque "es solo la comilla simple y la comilla doble", pero los piratas informáticos encontraron formas inteligentes de evitar la detección, como el uso de diferentes codificaciones o el uso de funciones de base de datos.

De todos modos, solo necesitaba olvidar una sola cadena para crear una violación de datos catastrófica. Los piratas informáticos podían automatizar los scripts para descargar la base de datos completa con una serie o consultas. Si el software es conocido como una suite de código abierto o una suite de negocios famosa, simplemente puede comunicarse con la tabla de usuarios y contraseñas.

Por otro lado, solo el uso de consultas concatenadas era solo una cuestión de aprender a usar y acostumbrarse.

    
respondido por el Borjab 14.09.2016 - 17:59

Lea otras preguntas en las etiquetas