¿Puede la declaración parametrizada detener todas las inyecciones de SQL?

80

Si es así, ¿por qué todavía hay tantas inyecciones de SQL exitosas? ¿Solo porque algunos desarrolladores son demasiado tontos para usar declaraciones parametrizadas?

águila de hielo
fuente
6
Esta es una gran pregunta, con respuestas absolutamente terribles (como la vez que estoy comentando)
Ibu
Deseo que alguien con una buena reputación de 15k al menos o con buena experiencia pueda aportar información valiosa a esta pregunta.
Ibu
4
Consulte la charla y las diapositivas sobre los mitos y falacias de la inyección SQL de Bill Karwin para obtener más información sobre este tema. Explica qué es la inyección SQL, cómo escapar no suele ser lo suficientemente bueno y cómo los procedimientos almacenados y las declaraciones parametrizadas pueden verse comprometidas.
Mike
2
Vea también algunas de las respuestas de Bill Karwin a preguntas similares: ¿Qué es la inyección SQL?
Mike

Respuestas:

66

Los enlaces que he publicado en mis comentarios a la pregunta explican muy bien el problema. He resumido mis sentimientos sobre por qué persiste el problema, a continuación:

  1. Aquellos que recién comienzan pueden no tener conocimiento de la inyección SQL.

  2. Algunos son conscientes de la inyección SQL, pero piensan que escapar es la (¿única?) Solución. Si realiza una búsqueda rápida en Google php mysql query, la primera página que aparece es la mysql_querypágina, en la que hay un ejemplo que muestra la interpolación de la entrada de escape del usuario en una consulta. No se menciona (al menos no que yo pueda ver) de usar declaraciones preparadas en su lugar. Como han dicho otros, hay tantos tutoriales que usan la interpolación de parámetros, que no es realmente sorprendente la frecuencia con la que todavía se usa.

  3. Falta de comprensión de cómo funcionan las declaraciones parametrizadas. Algunos piensan que es solo un medio elegante para escapar de los valores.

  4. Otros son conscientes de las declaraciones parametrizadas, pero no las usan porque han escuchado que son demasiado lentas. Sospecho que muchas personas han escuchado lo increíblemente lentas que son las declaraciones paramterizadas, pero en realidad no han hecho ninguna prueba por su cuenta. Como señaló Bill Karwin en su charla, la diferencia en el desempeño rara vez debe usarse como un factor al considerar el uso de declaraciones preparadas. Los beneficios de preparar una vez, ejecutar muchas , a menudo parecen haberse olvidado, al igual que las mejoras en la seguridad y la capacidad de mantenimiento del código.

  5. Algunos usan declaraciones parametrizadas en todas partes, pero con interpolación de valores no verificados, como nombres de tablas y columnas, palabras clave y operadores condicionales. Las búsquedas dinámicas, como las que permiten a los usuarios especificar varios campos de búsqueda diferentes, condiciones de comparación y orden de clasificación, son ejemplos principales de esto.

  6. Falsa sensación de seguridad al utilizar un ORM. Los ORM aún permiten la interpolación de partes de la declaración SQL; consulte 5.

  7. La programación es un tema grande y complejo, la gestión de bases de datos es un tema grande y complejo, la seguridad es un tema grande y complejo. Desarrollar una aplicación de base de datos segura no es fácil, incluso los desarrolladores experimentados pueden quedar atrapados.

  8. Muchas de las respuestas sobre stackoverflow no ayudan. Cuando las personas escriben preguntas que usan SQL dinámico e interpolación de parámetros, a menudo hay una falta de respuestas que sugieran el uso de declaraciones parametrizadas. En algunas ocasiones, he tenido personas que refutan mi sugerencia de usar declaraciones preparadas, generalmente debido a la percepción de gastos generales inaceptables de desempeño. Dudo seriamente que aquellos que hacen la mayoría de estas preguntas estén en una posición en la que los pocos milisegundos extra que se toman para preparar una declaración parametrizada tengan un efecto catastrófico en su aplicación.

Miguel
fuente
65

Cuando los artículos hablan de consultas parametrizadas que detienen los ataques SQL, no explican realmente por qué, a menudo se trata de un caso de "Sí, así que no preguntes por qué", posiblemente porque no se conocen a sí mismos. Una señal segura de un mal educador es uno que no puede admitir que no sabe algo. Pero yo divago. Cuando digo que me pareció totalmente comprensible estar confundido es simple. Imagina una consulta SQL dinámica

sqlQuery='SELECT * FROM custTable WHERE User=' + Username + ' AND Pass=' + password

por lo que una simple inyección de sql sería solo poner el nombre de usuario como 'OR 1 = 1-- Esto efectivamente haría que la consulta sql:

sqlQuery='SELECT * FROM custTable WHERE User='' OR 1=1-- ' AND PASS=' + password

Esto dice seleccionar todos los clientes donde su nombre de usuario está en blanco ('') o 1 = 1, que es un booleano, lo que equivale a verdadero. Luego usa - para comentar el resto de la consulta. Entonces, esto simplemente imprimirá toda la tabla de clientes, o hará lo que quiera con ella, si inicia sesión, iniciará sesión con los privilegios del primer usuario, que a menudo puede ser el administrador.

Ahora las consultas parametrizadas lo hacen de manera diferente, con un código como:

sqlQuery='SELECT * FROM custTable WHERE User=? AND Pass=?'

parameters.add("User", username)
parameters.add("Pass", password)

donde el nombre de usuario y la contraseña son variables que apuntan al nombre de usuario y contraseña ingresados ​​asociados

Ahora, en este punto, puede estar pensando, esto no cambia nada en absoluto. Seguramente aún podría poner en el campo de nombre de usuario algo como Nadie O 1 = 1 '-, haciendo efectivamente la consulta:

sqlQuery='SELECT * FROM custTable WHERE User=Nobody OR 1=1'-- AND Pass=?'

Y esto parecería un argumento válido. Pero estarías equivocado.

La forma en que funcionan las consultas parametrizadas es que sqlQuery se envía como una consulta y la base de datos sabe exactamente qué hará esta consulta, y solo entonces insertará el nombre de usuario y las contraseñas simplemente como valores. Esto significa que no pueden efectuar 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 O 1 = 1 '-" y una contraseña en blanco, que debería resultar falsa.

Sin embargo, esta no es una solución completa, y aún será necesario realizar la validación de entrada, ya que esto no afectará a 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, lo mostraría como javascript normal, dependiendo de cualquier validación de salida. Entonces, realmente lo mejor que puede hacer es seguir usando la validación de entrada, pero usando consultas parametrizadas o procedimientos almacenados para detener cualquier ataque SQL.

Josip Ivic
fuente
1
Esto agrega mucho a lo que estaba buscando, pero ¿podría explicar más sobre lo que haría para la "validación de entrada"? También mencionaste que hay otros ataques que podrían ocurrir con la consulta, es decir, XSS, pero ¿podrías explicar cómo sucedería? Entonces, esencialmente, ¿cómo nos protegeríamos completamente contra la inyección de SQL, o estamos buscando todo tipo de inyección? Gracias.
XaolingBao
3
@JosipIvic: Dada la cantidad de personas que han preguntado cómo funcionan las declaraciones parametrizadas, es impactante ver tan pocas, si es que hay otras, realmente, desglosar la respuesta como lo hizo. Gracias por escribir una explicación tan clara con un ejemplo bastante intuitivo.
daOnlyBG
Brillante. ¡Un ejemplo pinta mil palabras como dicen!
Drenai
10

Bueno, buena pregunta. La respuesta es más estocástica que determinista e intentaré explicar mi punto de vista con un pequeño ejemplo.

Hay muchas referencias en la red que nos sugieren usar parámetros en nuestras consultas o usar procedimientos almacenados con parámetros para evitar la Inyección de SQL (SQLi). Les mostraré que los procedimientos almacenados (por ejemplo) no son la varita mágica contra SQLi. La responsabilidad sigue siendo del programador.

Considere el siguiente procedimiento almacenado de SQL Server que obtendrá la fila de usuario de una tabla 'Usuarios':

create procedure getUser
 @name varchar(20)
,@pass varchar(20)
as
declare @sql as nvarchar(512)
set @sql = 'select usrID, usrUName, usrFullName, usrRoleID '+
           'from Users '+
           'where usrUName = '''+@name+''' and usrPass = '''+@pass+''''
execute(@sql)

Puede obtener los resultados pasando como parámetros el nombre de usuario y la contraseña. Suponiendo que la contraseña está en texto libre (solo para simplificar este ejemplo), una llamada normal sería:

DECLARE @RC int
DECLARE @name varchar(20)
DECLARE @pass varchar(20)

EXECUTE @RC = [dbo].[getUser] 
   @name = 'admin'
  ,@pass = '!@Th1siSTheP@ssw0rd!!'
GO

Pero aquí tenemos una mala técnica de programación utilizada por el programador dentro del procedimiento almacenado, por lo que un atacante puede ejecutar lo siguiente:

DECLARE @RC int
DECLARE @name varchar(20)
DECLARE @pass varchar(20)

EXECUTE @RC = [TestDB].[dbo].[getUser] 
   @name = 'admin'
  ,@pass = 'any'' OR 1=1 --'
GO

Los parámetros anteriores se pasarán como argumentos al procedimiento almacenado y el comando SQL que finalmente se ejecutará es:

select usrID, usrUName, usrFullName, usrRoleID 
from Users 
where usrUName = 'admin' and usrPass = 'any' OR 1=1 --'

..que recuperará todas las filas de los usuarios

El problema aquí es que incluso si seguimos el principio "Crear un procedimiento almacenado y pasar los campos para buscar como parámetros" el SQLi todavía se realiza. Esto se debe a que simplemente copiamos nuestra mala práctica de programación dentro del procedimiento almacenado. La solución al problema es reescribir nuestro procedimiento almacenado de la siguiente manera:

alter procedure getUser
 @name varchar(20)
,@pass varchar(20)
as
select usrID, usrUName, usrFullName, usrRoleID 
from Users 
where usrUName = @name and usrPass = @pass

Lo que estoy tratando de decir es que los desarrolladores deben aprender primero qué es un ataque SQLi y cómo se puede realizar y luego salvaguardar su código en consecuencia. Seguir ciegamente las 'mejores prácticas' no siempre es la forma más segura ... y tal vez es por eso que tenemos tantas 'mejores prácticas': ¡fallas!

Andreas Venieris
fuente
Puedo entender su punto y soy culpable de esto. A veces existe la necesidad de crear consultas SQL dinámicas que utilicé concatenación de parámetros. ¿Cómo me sugieres que lo haga?
TheProvost
@TheProvost esa es una buena pregunta. Considere sp_executesql: msdn.microsoft.com/en-us/library/ms188001.aspx
Tim
@Tim Hola tim. Soy nuevo en sql dinámico. ¿Cuál es la diferencia entre sp_executesql y EXECUTE (@SqlQuery)?
TheProvost
2
Creo que esta publicación explica bien un ejemplo simple: codeproject.com/Tips/586207/… - pero básicamente, EXECUTE (@SqlQuery) no hace nada para evitar la inyección de sql, sin embargo sp_executesql (@SqlQuery, ..., ...) lo previene. Los ejemplos del artículo de microsoft deberían ayudar.
Tim
Tim tiene la solución TheProvost ...;) Puede usar sp_executesql (@QUERY, @PARAMETERS, @VARS) ... para un caso SQL dinámico ...;)
Andreas Venieris
5

Sí, el uso de declaraciones preparadas detiene todas las inyecciones de SQL, al menos en teoría. En la práctica, las declaraciones parametrizadas pueden no ser declaraciones preparadas reales, por ejemplo, PDOen PHP las emula de forma predeterminada, por lo que está abierto a un ataque de caso extremo .

Si está utilizando declaraciones preparadas reales, todo está seguro. Bueno, al menos siempre que no concatene SQL inseguro en su consulta como reacción a no poder preparar nombres de tablas, por ejemplo.

Si es así, ¿por qué todavía hay tantas inyecciones de SQL exitosas? ¿Solo porque algunos desarrolladores son demasiado tontos para usar declaraciones parametrizadas?

Sí, la educación es el punto principal aquí y las bases de código heredado. Muchos tutoriales utilizan el escape y, lamentablemente, no se pueden eliminar fácilmente de la web.

Kelunik
fuente
1
La respuesta vinculada no tiene nada que ver con las declaraciones preparadas en realidad.
Tu sentido común
1
@YourCommonSense: se trata de consultas parametrizadas, pueden no ser preparaciones reales sino emuladas según el controlador utilizado. Es importante saber y estar muy conectado ...
kelunik
1
Otra respuesta en la misma página tiene un muy buen comentario: "Si TODAS sus consultas están parametrizadas, también está protegido contra la inyección de segundo orden. La inyección de primer orden es olvidar que los datos del usuario no son confiables. La inyección de segundo orden es olvidar que los datos de la base de datos son no confiable (porque provino originalmente del usuario) ".
Rodrigo
@kelunik, la respuesta vinculada tampoco se trata de consultas parametrizadas, se trata de una biblioteca que esencialmente las falsifica. Una consulta parametrizada es aquella que se envía al servidor con valores de parámetro separados.
Panagiotis Kanavos
@PanagiotisKanavos: Conozco bastante bien el contenido de esa respuesta. Es sólo un ejemplo (y uno bastante común) que las consultas con parámetros que utiliza en realidad no puede implementarse como declaraciones preparadas ...
kelunik
3

Evito los absolutos en la programación; siempre hay una excepción. Recomiendo ampliamente los procedimientos almacenados y los objetos de comando. La mayor parte de mi experiencia es con SQL Server, pero juego con MySql de vez en cuando. Los procedimientos almacenados tienen muchas ventajas, incluidos los planes de consultas en caché; sí, esto se puede lograr con parámetros y SQL en línea, pero eso abre más posibilidades para ataques de inyección y no ayuda con la separación de preocupaciones. Para mí también es mucho más fácil proteger una base de datos ya que mis aplicaciones generalmente solo tienen permiso de ejecución para dichos procedimientos almacenados. Sin acceso directo a la tabla / vista, es mucho más difícil inyectar algo. Si el usuario de las aplicaciones se ve comprometido, solo se tiene permiso para ejecutar exactamente lo que estaba predefinido.

Mis dos centavos.

Derek
fuente
¿Cómo se relaciona esto con la pregunta? ¿Cómo va a llamar y pasar parámetros al procedimiento almacenado? ¿Usar concatenación de cadenas o usar una consulta parametrizada? Además, ¿qué pasa si alguien usa la concatenación de cadenas dentro del procedimiento almacenado para crear una consulta "dinámica"? El hecho de que sea un procedimiento almacenado no significa que sea más seguro
Panagiotis Kanavos
Generalmente utilizo un objeto de comando y también evito ejecutar "consultas dinámicas" por diseño.
Derek
2

Yo no diría "tonto".

Creo que los tutoriales son el problema. La mayoría de los tutoriales de SQL, libros, cualquier cosa que explique SQL con valores en línea, sin mencionar los parámetros de enlace en absoluto. Las personas que aprenden de estos tutoriales no tienen la oportunidad de aprenderlo correctamente.

Markus Winand
fuente
2
No es suficiente. ¿Por qué la gente no usa framework o alguna forma? ¿Por qué no prueban la "inyección tonta" con un probador estúpido? Porque a veces el jefe no te paga bien o te paga X dineros por un proyecto y necesitas correr de un proyecto a otro y de uno a otro para conseguir algo de dinero. Debes ser cada vez más rápido. El codificador está estresado y sobrecargado, por lo que el código funciona pero está mal escrito.
Jedi
2

Debido a que la mayoría del código no está escrito teniendo en cuenta la seguridad y la administración, dada la opción entre agregar características (especialmente algo visible que se puede vender) y seguridad / estabilidad / confiabilidad (que es mucho más difícil de vender), casi invariablemente elegirán el ex. La seguridad es solo una preocupación cuando se convierte en un problema.

otto malvado
fuente
2

¿Puede la declaración parametrizada detener todas las inyecciones de SQL?

Sí, siempre que el controlador de su base de datos ofrezca un marcador de posición para cada literal SQL posible. La mayoría de los controladores de declaraciones preparados no lo hacen. Digamos que nunca encontrará un marcador de posición para un nombre de campo o para una matriz de valores. Lo que hará que un desarrollador recurra a la adaptación de una consulta a mano, utilizando concatenación y formateo manual. Con resultado previsto.

Es por eso que creé mi contenedor Mysql para PHP que admite la mayoría de los literales que se pueden agregar a la consulta de forma dinámica, incluidas matrices e identificadores.

Si es así, ¿por qué todavía hay tantas inyecciones de SQL exitosas? ¿Solo porque algunos desarrolladores son demasiado tontos para usar declaraciones parametrizadas?

Como puede ver, en realidad es simplemente imposible tener todas sus consultas parametrizadas, incluso si no es tonto.

Tu sentido común
fuente
Si TODAS sus consultas están parametrizadas (provienen de los datos del usuario o de los datos de su base de datos), entonces parece que está protegido, como se indica en el comentario más votado aquí: stackoverflow.com/a/134138/1086511
Rodrigo
Le pedí su opinión, solo porque parecía lo suficientemente razonable. No creo que su método sea el mejor si se cumple lo que he leído en otros lugares. De todos modos, me encantará si mejora "con las herramientas habituales no puede tener TODAS sus consultas parametrizadas".
Rodrigo
Comencé a leer su respuesta, hasta que llegué a la idea de identificar "literales SQL". La idea no me pareció del todo correcta (parecía un exceso de trabajo). Si es cierto que las consultas parametrizadas evitan la inyección en PHP (todavía estoy investigando), entonces mi siguiente paso es evitar las inyecciones de JavaScript. Luego, volveré a estudiar tu solución. Además, estoy usando postgres, ¿y tal vez su solución sea específica de mysql?
Rodrigo
Ok, ahora lo leí (de nuevo), y no creo que "es simplemente imposible tener todas tus consultas parametrizadas" es una mejora. ¿Es imposible en MySQL? ¿Es imposible también en PostgreSQL? ¿Por qué? ¿Hay alguna consulta fuera de mi script php? ¿Dónde? Creo que por identificador te refieres a una palabra reservada que intentas eliminar de tu matriz $ _POST. Este no parece el camino a seguir, para mí (intuitivamente, puedo estar equivocado, por supuesto). Además, no entendí el "¿Intentaste unirlo alguna vez?" ¿Atar qué?
Rodrigo
No es tan fácil de encontrar en la web como pensaba. Agregue una referencia si puede.
Rodrigo
2

Primero, mi respuesta a su primera pregunta: Sí, hasta donde yo sé, al usar consultas parametrizadas, las inyecciones SQL ya no serán posibles. En cuanto a sus siguientes preguntas, no estoy seguro y solo puedo dar mi opinión sobre las razones:

Creo que es más fácil "simplemente" escribir la cadena de consulta SQL concatenar algunas partes diferentes (tal vez incluso dependiendo de algunas comprobaciones lógicas) junto con los valores que se insertarán. Es solo crear la consulta y ejecutarla. Otra ventaja es que puede imprimir (eco, salida o lo que sea) la cadena de consulta SQL y luego usar esta cadena para una consulta manual al motor de la base de datos.

Cuando trabajas con declaraciones preparadas, siempre tienes al menos un paso más: Tienes que construir tu consulta (incluyendo los parámetros, por supuesto) Tienes que preparar la consulta en el servidor Tienes que vincular los parámetros a los valores reales que deseas utilizar para su consulta Debe ejecutar la consulta.

Eso es algo más trabajo (y no tan sencillo de programar) especialmente para algunos trabajos "rápidos y sucios" que a menudo resultan ser muy duraderos ...

Atentamente,

Caja

TomS
fuente
2

La inyección de SQL es un subconjunto del problema mayor de la inyección de código, donde los datos y el código se proporcionan a través del mismo canal y los datos se confunden con el código. Las consultas parametrizadas evitan que esto ocurra formando la consulta usando el contexto sobre qué son datos y qué es código.

En algunos casos específicos, esto no es suficiente. En muchos DBMS, es posible ejecutar SQL dinámicamente con procedimientos almacenados, lo que introduce un defecto de inyección de SQL en el nivel de DBMS. Llamar a un procedimiento almacenado de este tipo mediante consultas parametrizadas no evitará que se aproveche la inyección SQL en el procedimiento. Otro ejemplo se puede ver en esta publicación de blog .

Más comúnmente, los desarrolladores usan la funcionalidad de manera incorrecta. Por lo general, el código se parece a esto cuando se hace correctamente:

db.parameterize_query("select foo from bar where baz = '?'", user_input)

Algunos desarrolladores concatenarán cadenas juntas y luego usarán una consulta parametrizada, que en realidad no hace la distinción de datos / código antes mencionada que proporciona las garantías de seguridad que estamos buscando:

db.parameterize_query("select foo from bar where baz = '" + user_input + "'")

El uso correcto de consultas parametrizadas proporciona una protección muy fuerte, pero no impenetrable, contra los ataques de inyección SQL.

Daniel Crowley
fuente
1

Para proteger su aplicación de la inyección SQL, realice los siguientes pasos:

Paso 1. Restrinja la entrada. Paso 2. Utilice parámetros con procedimientos almacenados. Paso 3. Utilice parámetros con SQL dinámico.

Consulte http://msdn.microsoft.com/en-us/library/ff648339.aspx

Fahad Hussain
fuente
8
Los procedimientos almacenados por sí solos no son realmente una ayuda. Es posible construir cadenas de consulta de forma dinámica en un procedimiento almacenado, al igual que en el código del cliente.
Phil Miller
@Fahad Podría reformular el número 2 como "Usar declaraciones parametrizadas en consultas y en procedimientos almacenados". +1 al comentario de Novelocrat de que usar procedimientos almacenados sin parámetros no le da mucho.
Mateo
1

Incluso si las declaraciones preparadas se utilizan correctamente en todo el código de la aplicación web, aún pueden existir fallas de inyección SQL si los componentes del código de la base de datos construyen consultas a partir de la entrada del usuario de una manera insegura. El siguiente es un ejemplo de un procedimiento almacenado que es vulnerable a la inyección de SQL en el parámetro @name:

CREATE PROCEDURE show_current_orders
(@name varchar(400) = NULL)
AS
DECLARE @sql nvarchar(4000)
SELECT @sql = ‘SELECT id_num, searchstring FROM searchorders WHERE ‘ +
‘searchstring = ‘’’ + @name + ‘’’’;
EXEC (@sql)
GO

Incluso si la aplicación pasa el valor del nombre proporcionado por el usuario al procedimiento almacenado de manera segura, el procedimiento en sí lo concatena directamente en una consulta dinámica y, por lo tanto, es vulnerable.

Gráficos Hadid
fuente