Tenemos otra discusión aquí en el trabajo sobre el uso de consultas sql parametrizadas en nuestro código. Tenemos dos lados en la discusión: yo y algunos otros que dicen que siempre deberíamos usar parámetros para protegernos de las inyecciones de sql y los otros chicos que no creen que sea necesario. En su lugar, quieren reemplazar apóstrofos simples con dos apóstrofes en todas las cadenas para evitar inyecciones de sql. Todas nuestras bases de datos ejecutan Sql Server 2005 o 2008 y nuestra base de código se ejecuta en .NET framework 2.0.
Déjame darte un ejemplo simple en C #:
Quiero que usemos esto:
string sql = "SELECT * FROM Users WHERE Name=@name";
SqlCommand getUser = new SqlCommand(sql, connection);
getUser.Parameters.AddWithValue("@name", userName);
//... blabla - do something here, this is safe
Mientras que los otros chicos quieren hacer esto:
string sql = "SELECT * FROM Users WHERE Name=" + SafeDBString(name);
SqlCommand getUser = new SqlCommand(sql, connection);
//... blabla - are we safe now?
Donde la función SafeDBString se define de la siguiente manera:
string SafeDBString(string inputValue)
{
return "'" + inputValue.Replace("'", "''") + "'";
}
Ahora, siempre que usemos SafeDBString en todos los valores de cadena en nuestras consultas, deberíamos estar seguros. ¿Correcto?
Hay dos razones para utilizar la función SafeDBString. Primero, es la forma en que se ha hecho desde la edad de piedra, y segundo, es más fácil depurar las declaraciones sql ya que ve la consulta exacta que se ejecuta en la base de datos.
Por lo que entonces. Mi pregunta es si realmente es suficiente usar la función SafeDBString para evitar ataques de inyección SQL. He estado tratando de encontrar ejemplos de código que rompa esta medida de seguridad, pero no puedo encontrar ningún ejemplo de ello.
¿Hay alguien ahí fuera que pueda romper esto? ¿Como lo harias?
EDITAR: Para resumir las respuestas hasta ahora:
- Nadie ha encontrado una manera de sortear SafeDBString en Sql Server 2005 o 2008 todavía. Eso es bueno, creo.
- Varias respuestas señalaron que se obtiene una ganancia de rendimiento cuando se utilizan consultas parametrizadas. La razón es que los planes de consulta se pueden reutilizar.
- También estamos de acuerdo en que el uso de consultas parametrizadas proporciona un código más legible que es más fácil de mantener.
- Además, es más fácil usar siempre parámetros que usar varias versiones de SafeDBString, conversiones de cadena a número y conversiones de cadena a fecha.
- Usando parámetros obtienes conversión automática de tipos, algo que es especialmente útil cuando estamos trabajando con fechas o números decimales.
- Y finalmente: no intente hacer la seguridad usted mismo como escribió JulianR. Los proveedores de bases de datos gastan mucho tiempo y dinero en seguridad. No hay forma de que podamos hacerlo mejor y no hay razón por la que debamos intentar hacer su trabajo.
Entonces, aunque nadie pudo romper la seguridad simple de la función SafeDBString, obtuve muchos otros buenos argumentos. ¡Gracias!
fuente
Respuestas:
Creo que la respuesta correcta es:
No intente hacer la seguridad usted mismo . Use cualquier biblioteca estándar de la industria confiable que esté disponible para lo que está tratando de hacer, en lugar de intentar hacerlo usted mismo. Cualesquiera sean las suposiciones que haga sobre la seguridad, pueden ser incorrectas. Por más seguro que parezca su propio enfoque (y, en el mejor de los casos, parece inestable), existe el riesgo de que esté pasando por alto algo y ¿realmente quiere arriesgarse cuando se trata de seguridad?
Utilice parámetros.
fuente
Y luego alguien va y usa "en lugar de". Los parámetros son, en mi opinión, la única forma segura de hacerlo.
También evita muchos problemas de i18n con fechas / números; que fecha es 01/02/03? A cuanto esta el 123,456? ¿Sus servidores (app-server y db-server) están de acuerdo entre sí?
Si el factor de riesgo no les convence, ¿qué tal el rendimiento? El RDBMS puede reutilizar el plan de consulta si usa parámetros, lo que ayuda al rendimiento. No puede hacer esto solo con la cuerda.
fuente
El argumento es un fracaso. Si logra encontrar una vulnerabilidad, sus compañeros de trabajo simplemente cambiarán la función SafeDBString para dar cuenta de ella y luego le pedirán que demuestre que es inseguro nuevamente.
Dado que las consultas parametrizadas son una mejor práctica de programación indiscutible, la carga de la prueba debería recaer en ellos para indicar por qué no están utilizando un método que sea más seguro y de mejor rendimiento.
Si el problema es reescribir todo el código heredado, el compromiso fácil sería usar consultas parametrizadas en todo el código nuevo y refactorizar el código antiguo para usarlas cuando se trabaja en ese código.
Supongo que el problema real es el orgullo y la terquedad, y no hay mucho más que puedas hacer al respecto.
fuente
En primer lugar, su muestra para la versión "Reemplazar" es incorrecta. Debes poner apóstrofos alrededor del texto:
Esa es otra cosa que los parámetros hacen por usted: no necesita preocuparse por si un valor debe estar entre comillas o no. Por supuesto, podría incorporar eso en la función, pero luego necesita agregar mucha complejidad a la función: cómo saber la diferencia entre 'NULL' como nulo y 'NULL' como solo una cadena, o entre un número y una cadena que simplemente contiene muchos dígitos. Es solo otra fuente de errores.
Otra cosa es el rendimiento: los planes de consulta parametrizados a menudo se almacenan en caché mejor que los planes concatenados, por lo que quizás le ahorre un paso al servidor al ejecutar la consulta.
Además, escapar de las comillas simples no es suficiente. Muchos productos de bases de datos permiten métodos alternativos para escapar de caracteres que un atacante podría aprovechar. En MySQL, por ejemplo, también puede escapar una comilla simple con una barra invertida. Y así, el siguiente valor de "nombre" haría explotar MySQL con solo la
SafeDBString()
función, porque cuando duplica la comilla simple, la primera todavía se escapa por la barra invertida, dejando la segunda "activa":Además, JulianR trae un buen punto a continuación: NUNCA intente hacer trabajo de seguridad usted mismo. Es muy fácil equivocarse en la programación de seguridad de formas sutiles que parecen funcionar, incluso con pruebas exhaustivas. Luego pasa el tiempo y un año después descubres que tu sistema se rompió hace seis meses y ni siquiera lo sabías hasta entonces.
Confíe siempre tanto como sea posible en las bibliotecas de seguridad proporcionadas para su plataforma. Serán escritos por personas que se dedican a crear códigos de seguridad, mucho mejor probados que lo que puede administrar y atendidos por el proveedor si se encuentra una vulnerabilidad.
fuente
Entonces yo diría:
1) ¿Por qué intentas volver a implementar algo que está integrado? está ahí, fácilmente disponible, fácil de usar y ya depurado a escala global. Si se encuentran errores en el futuro, se solucionarán y estarán disponibles para todos muy rápidamente sin que tenga que hacer nada.
2) ¿Qué procesos existen para garantizar que nunca se pierda una llamada a SafeDBString? Perderlo en un solo lugar podría generar una gran cantidad de problemas. ¿Cuánto vas a observar estas cosas y considerar cuánto esfuerzo desperdiciado es cuando la respuesta correcta aceptada es tan fácil de alcanzar?
3) ¿Qué tan seguro está de haber cubierto todos los vectores de ataque que Microsoft (el autor de la base de datos y la biblioteca de acceso) conoce en su implementación de SafeDBString ...
4) ¿Qué tan fácil es leer la estructura del sql? El ejemplo usa + concatenación, los parámetros son muy parecidos a string.Format, que es más legible.
Además, hay 2 formas de averiguar qué se ejecutó realmente: ejecute su propia función LogCommand, una función simple con sin preocupaciones de seguridad , o incluso mirar un rastreo de sql para averiguar lo que la base de datos cree que realmente está sucediendo.
Nuestra función LogCommand es simplemente:
Bien o mal, nos brinda la información que necesitamos sin problemas de seguridad.
fuente
Con las consultas parametrizadas, obtiene más que protección contra la inyección de SQL. También obtiene un mejor potencial de almacenamiento en caché del plan de ejecución. Si usa el generador de perfiles de consultas del servidor sql, aún puede ver el 'sql exacto que se ejecuta en la base de datos', por lo que tampoco está perdiendo nada en términos de depuración de sus declaraciones sql.
fuente
He utilizado ambos enfoques para evitar ataques de inyección de SQL y definitivamente prefiero las consultas parametrizadas. Cuando he usado consultas concatenadas, he usado una función de biblioteca para escapar de las variables (como mysql_real_escape_string) y no estaría seguro de haber cubierto todo en una implementación propietaria (como parece que tú también lo estás).
fuente
No puede realizar fácilmente ninguna verificación de tipo de la entrada del usuario sin utilizar parámetros.
Si usa las clases SQLCommand y SQLParameter para realizar sus llamadas a la base de datos, aún puede ver la consulta SQL que se está ejecutando. Mire la propiedad CommandText de SQLCommand.
Siempre soy un poco sospechoso del enfoque de roll-your-own para prevenir la inyección de SQL cuando las consultas parametrizadas son tan fáciles de usar. En segundo lugar, solo porque "siempre se ha hecho de esa manera" no significa que sea la forma correcta de hacerlo.
fuente
Esto solo es seguro si tiene la garantía de que va a pasar una cadena.
¿Qué pasa si no está pasando una cadena en algún momento? ¿Qué pasa si pasa solo un número?
Finalmente se convertiría en:
fuente
Usaría procedimientos o funciones almacenados para todo, por lo que la pregunta no surgiría.
Donde tengo que poner SQL en código, uso parámetros, que es lo único que tiene sentido. Recuérdeles a los disidentes que hay piratas informáticos más inteligentes que ellos y con mejores incentivos para romper el código que intenta burlarlos. Usando parámetros, simplemente no es posible, y no es difícil.
fuente
Muy de acuerdo en las cuestiones de seguridad.
Otra razón para utilizar parámetros es la eficiencia.
Las bases de datos siempre compilarán su consulta y la almacenarán en caché, luego volverán a utilizar la consulta en caché (que obviamente es más rápida para solicitudes posteriores). Si usa parámetros, incluso si usa parámetros diferentes, la base de datos reutilizará su consulta almacenada en caché ya que coincide con la cadena SQL antes de vincular los parámetros.
Sin embargo, si no vincula los parámetros, la cadena SQL cambia en cada solicitud (que tiene parámetros diferentes) y nunca coincidirá con lo que está en su caché.
fuente
Por las razones ya dadas, los parámetros son una muy buena idea. Pero odiamos usarlos porque crear el parámetro y asignar su nombre a una variable para su uso posterior en una consulta es un desastre de triple indirección.
La siguiente clase envuelve el generador de cadenas que utilizará habitualmente para crear solicitudes SQL. Le permite escribir consultas paramaterizadas sin tener que crear un parámetro , para que pueda concentrarse en SQL. Tu código se verá así ...
La legibilidad del código, espero que esté de acuerdo, ha mejorado mucho y el resultado es una consulta parametrizada adecuada.
La clase se ve así ...
fuente
Desde el muy poco tiempo que tuve para investigar los problemas de inyección de SQL, puedo ver que hacer que un valor sea 'seguro' también significa que está cerrando la puerta a situaciones en las que realmente podría querer apóstrofos en sus datos, ¿qué pasa con el nombre de alguien? , por ejemplo, O'Reilly.
Eso deja parámetros y procedimientos almacenados.
Y sí, siempre debe intentar implementar el código de la mejor manera que conoce ahora, no solo cómo se ha hecho siempre.
fuente
''
como un literal'
, por lo que su cadena se verá internamente como la secuencia de caracteresO'Reilly
. Eso es lo que la base de datos almacenará, recuperará, comparará, etc. Si desea mostrar al usuario sus datos después de haberlos escapado, guarde una copia de la cadena sin escape en el lado de la aplicación.Aquí hay un par de artículos que pueden resultarle útiles para convencer a sus compañeros de trabajo.
http://www.sommarskog.se/dynamic_sql.html
http://unixwiz.net/techtips/sql-injection.html
Personalmente, prefiero no permitir que ningún código dinámico toque mi base de datos, requiriendo que todos los contactos sean a través de sps (y no uno que use SQl dinámico). Esto significa que no se puede hacer nada excepto lo que he dado permiso a los usuarios para hacer y que los usuarios internos (excepto los pocos con acceso de producción para fines de administración) no pueden acceder directamente a mis tablas y crear estragos, robar datos o cometer fraude. Si ejecuta una aplicación financiera, esta es la forma más segura de hacerlo.
fuente
Puede romperse, sin embargo, los medios dependen de las versiones / parches exactos, etc.
Uno que ya se ha mencionado es el error de desbordamiento / truncamiento que se puede aprovechar.
Otro medio futuro sería encontrar errores similares a otras bases de datos; por ejemplo, la pila MySQL / PHP sufrió un problema de escape porque ciertas secuencias UTF8 podrían usarse para manipular la función de reemplazo; la función de reemplazo sería engañada para introducir los caracteres de inyección.
Al final del día, el mecanismo de seguridad de reemplazo se basa en la funcionalidad esperada pero no prevista . Dado que la funcionalidad no era el propósito previsto del código, existe una alta probabilidad de que alguna peculiaridad descubierta rompa la funcionalidad esperada.
Si tiene una gran cantidad de código heredado, el método de reemplazo podría usarse como una solución provisional para evitar una reescritura y pruebas prolongadas. Si está escribiendo un código nuevo, no hay excusa.
fuente
Utilice siempre consultas parametrizadas siempre que sea posible. A veces, incluso una entrada simple sin el uso de caracteres extraños ya puede crear una inyección SQL si no se identifica como entrada para un campo en la base de datos.
Así que deje que la base de datos haga su trabajo de identificar la entrada en sí misma, sin mencionar que también ahorra muchos problemas cuando realmente necesita insertar caracteres extraños que de otro modo se escaparían o cambiarían. Incluso puede ahorrar algo de tiempo de ejecución valioso al final por no tener que calcular la entrada.
fuente
No vi ninguna otra respuesta que aborde este lado del 'por qué hacerlo usted mismo es malo', pero considere un ataque de truncamiento de SQL .
También existe la función
QUOTENAME
T-SQL que puede ser útil si no puede convencerlos de que usen params. Captura muchas (¿todas?) De las preocupaciones de qoute escapadas.fuente
2 años después , reciví ... Cualquiera que encuentre los parámetros un dolor es bienvenido a probar mi extensión VS, QueryFirst . Edita su solicitud en un archivo .sql real (Validación, Intellisense). Para agregar un parámetro, simplemente escríbalo directamente en su SQL, comenzando con '@'. Cuando guarde el archivo, QueryFirst generará clases contenedoras para permitirle ejecutar la consulta y acceder a los resultados. Buscará el tipo de base de datos de su parámetro y lo asignará a un tipo .net, que encontrará como entrada a los métodos Execute () generados.No podría ser más simple. Hacerlo de la manera correcta es radicalmente más rápido y fácil que hacerlo de cualquier otra manera, y crear una vulnerabilidad de inyección SQL se vuelve imposible, o al menos perversamente difícil. Hay otras ventajas extraordinarias, como poder eliminar columnas en su base de datos y ver inmediatamente errores de compilación en su aplicación.
descargo de responsabilidad legal: escribí QueryFirst
fuente
A continuación, se muestran algunas razones para utilizar consultas con parámetros:
fuente
Hubo pocas vulnerabilidades (no recuerdo qué base de datos era) relacionadas con el desbordamiento del búfer de la declaración SQL.
Lo que quiero decir es que SQL-Injection es más que simplemente "escapar de la cita", y no tienes idea de lo que vendrá después.
fuente
Otra consideración importante es realizar un seguimiento de los datos con y sin escape. Hay toneladas y toneladas de aplicaciones, web y de otro tipo, que no parecen realizar un seguimiento adecuado de cuándo los datos son Unicode sin formato, y codificados, HTML formateados, etc. Es obvio que será difícil hacer un seguimiento de qué cadenas están
''
codificadas y cuáles no.También es un problema cuando terminas cambiando el tipo de alguna variable; quizás solía ser un número entero, pero ahora es una cadena. Ahora tienes un problema.
fuente