¿Puedo proteger contra la inyección de SQL escapando de comillas simples y rodeando la entrada del usuario con comillas simples?

139

Me doy cuenta de que las consultas SQL parametrizadas son la forma óptima de desinfectar la entrada del usuario al crear consultas que contienen entrada del usuario, pero me pregunto qué hay de malo en tomar la entrada del usuario y escapar de las comillas simples y rodear toda la cadena con comillas simples. Aquí está el código:

sSanitizedInput = "'" & Replace(sInput, "'", "''") & "'"

Cualquier comilla simple que ingrese el usuario se reemplaza con comillas simples dobles, lo que elimina la capacidad de los usuarios para finalizar la cadena, por lo que cualquier otra cosa que puedan escribir, como punto y coma, signos de porcentaje, etc., formará parte de la cadena y en realidad no se ejecuta como parte del comando.

Estamos utilizando Microsoft SQL Server 2000, por lo que creo que la comilla simple es el único delimitador de cadena y la única forma de escapar del delimitador de cadena, por lo que no hay forma de ejecutar nada que el usuario escriba.

No veo ninguna manera de lanzar un ataque de inyección SQL contra esto, pero me doy cuenta de que si esto fuera tan a prueba de balas como me parece, alguien más lo habría pensado y sería una práctica común.

¿Qué hay de malo con este código? ¿Hay alguna manera de obtener un ataque de inyección SQL más allá de esta técnica de desinfección? Sería muy útil la entrada de muestra del usuario que explota esta técnica.


ACTUALIZAR:

Todavía no conozco ninguna forma de lanzar efectivamente un ataque de inyección SQL contra este código. Algunas personas sugirieron que una barra invertida escaparía a una comilla simple y dejaría que la otra terminara la cadena para que el resto de la cadena se ejecute como parte del comando SQL, y me doy cuenta de que este método funcionaría para inyectar SQL en una base de datos MySQL, pero en SQL Server 2000 la única forma (que he podido encontrar) de escapar de una comilla simple es con otra comilla simple; las barras invertidas no lo harán.

Y a menos que haya una manera de detener el escape de la comilla simple, ninguna de las entradas del resto del usuario se ejecutará porque se tomará como una cadena contigua.

Entiendo que hay mejores formas de desinfectar la información, pero estoy realmente más interesado en saber por qué el método que proporcioné anteriormente no funcionará. Si alguien sabe de alguna forma específica de montar un ataque de inyección SQL contra este método de desinfección, me encantaría verlo.

Patricio
fuente
17
@BryanH Admitir no entender cómo se aplica la sabiduría comúnmente aceptada a un caso específico y pedir un ejemplo sobre ese caso específico no es arrogancia, es humildad. Enfadarse cuando alguien pregunta por un ejemplo de por qué la sabiduría comúnmente aceptada es correcta, por otro lado, puede parecer arrogante. Razonar con ejemplos específicos es a menudo una excelente manera de investigar y aprender. La forma en que el OP resolvió esta duda fue muy útil para mi comprensión del tema, especialmente cuando explicó la respuesta que encontró.
SantiBailors
@patrik Acabo de encontrar esto ya que estoy trabajando en el mismo fragmento de código pero tratando de escapar de la cadena y anidar una consulta. ¿Alguna vez te diste cuenta?
3therk1ll
1
@ 3therk1ll es mejor no intentarlo, es mejor usar SQL parametrizado: blog.codinghorror.com/…
Patrick
@Patrick, ¡me estoy acercando desde la perspectiva de los atacantes!
3therk1ll

Respuestas:

87

En primer lugar, es solo una mala práctica. La validación de entrada siempre es necesaria, pero también siempre es dudosa.
Peor aún, la validación de la lista negra siempre es problemática, es mucho mejor definir explícita y estrictamente qué valores / formatos acepta. Es cierto que esto no siempre es posible, pero en cierta medida siempre debe hacerse.
Algunos trabajos de investigación sobre el tema:

El punto es que cualquier lista negra que haga (y las listas blancas demasiado permisivas) se puede omitir. El último enlace a mi artículo muestra situaciones en las que incluso se puede omitir el escape de citas.

Incluso si estas situaciones no se aplican a usted, sigue siendo una mala idea. Además, a menos que su aplicación sea trivialmente pequeña, tendrá que lidiar con el mantenimiento y tal vez una cierta cantidad de gobierno: ¿cómo se asegura de que se haga bien, en todas partes todo el tiempo?

La forma correcta de hacerlo:

  • Validación de la lista blanca: tipo, longitud, formato o valores aceptados
  • Si quieres hacer una lista negra, adelante. Citar escapar es bueno, pero dentro del contexto de las otras mitigaciones.
  • Usar objetos de comando y parámetro para preparar y validar
  • Llamar solo consultas parametrizadas.
  • Mejor aún, use los procedimientos almacenados exclusivamente.
  • Evite usar SQL dinámico y no use la concatenación de cadenas para generar consultas.
  • Si usa SP, también puede limitar los permisos en la base de datos para ejecutar solo los SP necesarios y no acceder a las tablas directamente.
  • También puede verificar fácilmente que toda la base de código solo accede a la base de datos a través de SP ...
Ávido
fuente
2
Cuando se usa correctamente, el SQL dinámico y la concatenación de cadenas se pueden usar de forma segura con consultas parametrizadas (es decir, con en sp_executesqllugar de EXEC). Es decir, puede generar dinámicamente su declaración SQL siempre que ninguno de los textos concatenados provenga del usuario. Esto también tiene beneficios de rendimiento; sp_executesqladmite almacenamiento en caché.
Brian
2
@Brian, bueno duh :). Pero en realidad, ¿con qué frecuencia ves que los programadores hacen eso? Además, el escenario típico donde se "necesita" SQL dinámico, requiere la entrada del usuario como parte de la consulta (supuestamente). Si pudieras hacer sp_executesql, no necesitarías (generalmente) el sql dinámico en primer lugar.
AviD
Finalmente me encontré con una situación que me hizo darme cuenta de que es posible usar Unicode para pasar sigilosamente el reemplazo de la cuerda. El texto de entrada se escribió en Word, que cambió el apóstrofe de la versión directa a un apóstrofe "rizado" (que se parece más a una coma), que no fue afectado por el reemplazo de la cadena, pero fue tratado como un delimitador de cadena por SQL Servidor. Gracias por la respuesta AviD (y todos los demás)!
Patrick el
1
@ElRonnoco seguro, pero no lo hago de actualización que, desde que he visto en los tiempos más salvajes de lo que parece ...
Avid
1
@AviD Actualicé el enlace al PDF de contrabando de SQL que escribiste en la única versión que pude encontrar en línea ... avísanos si hay otra ubicación para tu trabajo.
Michael Fredrickson
41

De acuerdo, esta respuesta se relacionará con la actualización de la pregunta:

"Si alguien sabe de alguna forma específica de montar un ataque de inyección SQL contra este método de desinfección, me encantaría verlo".

Ahora, además del escape de la barra diagonal inversa de MySQL, y teniendo en cuenta que en realidad estamos hablando de MSSQL, en realidad hay 3 formas posibles de inyectar su código SQL

sSanitizedInput = "'" & Reemplazar (sInput, "'", "''") & "'"

Tenga en cuenta que estos no serán todos válidos en todo momento y dependen mucho de su código real:

  1. Inyección de SQL de segundo orden: si una consulta SQL se reconstruye en función de los datos recuperados de la base de datos después de escapar , los datos se concatenan sin escape y pueden inyectarse indirectamente en SQL. Ver
  2. Truncamiento de cadenas: (un poco más complicado): el escenario es que tiene dos campos, digamos un nombre de usuario y una contraseña, y el SQL concatena ambos. Y ambos campos (o solo el primero) tienen un límite estricto de longitud. Por ejemplo, el nombre de usuario está limitado a 20 caracteres. Digamos que tienes este código:
username = left(Replace(sInput, "'", "''"), 20)

Entonces, lo que obtienes es el nombre de usuario, escapado y luego recortado a 20 caracteres. El problema aquí: pegaré mi cita en el vigésimo carácter (p. Ej., Después de 19 a), y su cita de escape se recortará (en el carácter 21). Entonces el SQL

sSQL = "select * from USERS where username = '" + username + "'  and password = '" + password + "'"

combinado con el nombre de usuario con formato incorrecto mencionado anteriormente, la contraseña ya estará fuera de las comillas y solo contendrá la carga directamente.
3. Contrabando Unicode: en ciertas situaciones, es posible pasar un personaje Unicode de alto nivel que parece una cita, pero no lo es , hasta que llega a la base de datos, donde de repente está . Dado que no es una cita cuando la valida, será fácil ... Vea mi respuesta anterior para obtener más detalles y enlace a la investigación original.

Ávido
fuente
28

En pocas palabras: nunca se escape la consulta. Estás obligado a equivocarte. En su lugar, use consultas parametrizadas, o si no puede hacerlo por alguna razón, use una biblioteca existente que lo haga por usted. No hay razón para hacerlo tú mismo.

Nick Johnson
fuente
2
¿Qué sucede si tiene que lidiar con algo como "tablas de Google Fusion" donde, afaik, no hay ninguna biblioteca de abstracción disponible que admita su dialecto? ¿Qué sugieres?
systempuntoout
20

Me doy cuenta de que esto es mucho tiempo después de que se hizo la pregunta, pero ...

Una forma de lanzar un ataque contra el procedimiento 'citar el argumento' es con el truncamiento de cadenas. Según MSDN, en SQL Server 2000 SP4 (y SQL Server 2005 SP1), una cadena demasiado larga se truncará silenciosamente.

Cuando cita una cadena, la cadena aumenta de tamaño. Cada apóstrofe se repite. Esto se puede usar para empujar partes del SQL fuera del búfer. Por lo tanto, podría recortar eficazmente partes de una cláusula where.

Esto probablemente sería principalmente útil en un escenario de página de 'administrador de usuarios' en el que podría abusar de la declaración de 'actualización' para no hacer todas las verificaciones que se suponía que debía hacer.

Por lo tanto, si decide citar todos los argumentos, asegúrese de saber qué sucede con los tamaños de cadena y asegúrese de que no se encuentre con el truncamiento.

Yo recomendaría ir con los parámetros. Siempre. Solo desearía poder aplicar eso en la base de datos. Y como efecto secundario, es más probable que obtenga mejores resultados de caché porque más de las declaraciones se ven iguales. (Esto fue ciertamente cierto en Oracle 8)

Jørn Jensen
fuente
1
Después de publicar, decidí que la publicación de AviD cubre esto, y con más detalle. Espero que mi publicación siga siendo de ayuda para alguien.
Jørn Jensen
10

Utilicé esta técnica cuando trabajé con la funcionalidad de 'búsqueda avanzada', donde construir una consulta desde cero era la única respuesta viable. (Ejemplo: permitir que el usuario busque productos basados ​​en un conjunto ilimitado de restricciones en los atributos del producto, mostrando columnas y sus valores permitidos como controles GUI para reducir el umbral de aprendizaje para los usuarios).

En sí mismo es seguro AFAIK. Sin embargo, como señaló otro respondedor, es posible que también deba lidiar con el escape de retroceso (aunque no al pasar la consulta a SQL Server utilizando ADO o ADO.NET, al menos, no puede responder por todas las bases de datos o tecnologías).

El inconveniente es que realmente debe estar seguro de qué cadenas contienen la entrada del usuario (siempre potencialmente maliciosa) y qué cadenas son consultas SQL válidas. Una de las trampas es si usa valores de la base de datos: ¿fueron originalmente proporcionados por el usuario? Si es así, también deben escapar. Mi respuesta es tratar de desinfectar lo más tarde posible (¡pero no más tarde!), Al construir la consulta SQL.

Sin embargo, en la mayoría de los casos, el enlace de parámetros es el camino a seguir: es simplemente más simple.

Pontus Gagge
fuente
2
Todavía puede usar la sustitución de parámetros incluso si está creando sus propias consultas.
Nick Johnson
1
Debería construir la cadena de la declaración SQL desde cero, pero aún así usar la sustitución de parámetros.
JeeBee
No, NUNCA construya sus declaraciones SQL desde cero.
AviD
8

El saneamiento de entrada no es algo que quieras burlar. Usa todo tu trasero. Use expresiones regulares en los campos de texto. Pruebe a aplicar sus números al tipo numérico adecuado e informe un error de validación si no funciona. Es muy fácil buscar patrones de ataque en su entrada, como '-. Suponga que todas las entradas del usuario son hostiles.

tom.dietrich
fuente
44
Y cuando se olvida de que UNO caso de UNO de entrada, usted es pwnd.
BryanH
44
"Algunas personas, cuando se enfrentan a un problema, piensan" Lo sé, usaré expresiones regulares ". Ahora tienen dos problemas".
MickeyfAgain_BeforeExitOfSO
1
@mickeyf Sé que este es un sentimiento común, pero sinceramente, las expresiones regulares son bastante impresionantes una vez que las comprendes.
tom.dietrich
@ tom.dietrich Siempre depende de la situación de la vida real. F.ex. La sintaxis de regexpr no es estándar, por lo que, en general, recomendaría no usar regexpr en contextos en los que diferentes sistemas están integrados para trabajar juntos. Esto se debe a que diferentes motores regexpr evalúan los regexprs de manera diferente y, lo que es más importante, este hecho difícil generalmente se minimiza o se ignora, lo que puede llevar a los desarrolladores a no preocuparse por estas incompatibilidades hasta que sean mordidos. Hay muchas incompatibilidades de este tipo; ver f.ex. regular-expressions.info/shorthand.html (buscar flavorsen esa página).
SantiBailors
6

Es una mala idea de todos modos, como parece saber.

¿Qué pasa con algo como escapar de la cita en una cadena como esta: \ '

Su reemplazo resultaría en: \ ''

Si la barra diagonal inversa escapa a la primera cita, entonces la segunda cita ha finalizado la cadena.

WW.
fuente
3
¡Gracias por la respuesta! Sé que el ataque funcionaría para una base de datos mySQL, pero estoy bastante seguro de que MS SQL Server no aceptará una barra invertida como carácter de escape (lo intenté). Varias búsquedas en Google no revelaron ningún otro personaje de escape, lo que realmente me hizo preguntarme por qué esto no funcionaría.
Patrick
6

Respuesta simple: funcionará a veces, pero no todo el tiempo. Desea usar la validación de la lista blanca en todo lo que hace, pero me doy cuenta de que eso no siempre es posible, por lo que se ve obligado a elegir la mejor lista negra. Del mismo modo, desea utilizar procesos almacenados parametrizados en todo , pero una vez más, eso no siempre es posible, por lo que se ve obligado a usar sp_execute con parámetros.

Hay formas de evitar cualquier lista negra utilizable que se te ocurra (y algunas listas blancas también).

Un informe decente está aquí: http://www.owasp.org/index.php/Top_10_2007-A2

Si necesita hacer esto como una solución rápida para darle tiempo para obtener uno real, hágalo. Pero no pienses que estás a salvo.

Carácter invalido
fuente
6

Hay dos formas de hacerlo, sin excepciones, para estar a salvo de las inyecciones de SQL; declaraciones preparadas o procedimientos almacenados prameterizados.

olle
fuente
4

Si tiene consultas parametrizadas disponibles, debería usarlas en todo momento. Todo lo que se necesita es que una consulta se deslice por la red y su base de datos está en riesgo.

Kev
fuente
4

Sí, eso debería funcionar hasta que alguien ejecute SET QUOTED_IDENTIFIER OFF y use una cita doble para usted.

Editar: no es tan simple como no permitir que el usuario malintencionado apague los identificadores citados:

El controlador ODBC de SQL Server Native Client y el proveedor OLE DB de SQL Server Native Client para SQL Server establecen automáticamente QUOTED_IDENTIFIER en ON al conectarse. Esto se puede configurar en orígenes de datos ODBC, en atributos de conexión ODBC o en propiedades de conexión OLE DB.El valor predeterminado para SET QUOTED_IDENTIFIER es OFF para conexiones desde aplicaciones de DB-Library.

Cuando se crea un procedimiento almacenado, las configuraciones SET QUOTED_IDENTIFIER y SET ANSI_NULLS se capturan y se utilizan para invocaciones posteriores de ese procedimiento almacenado .

SET QUOTED_IDENTIFIER también corresponde a la configuración QUOTED_IDENTIFER de ALTER DATABASE.

SET QUOTED_IDENTIFIER se establece en tiempo de análisis . Establecer en tiempo de análisis significa que si la instrucción SET está presente en el procedimiento por lotes o almacenado, surte efecto, independientemente de si la ejecución del código realmente alcanza ese punto; y la instrucción SET entra en vigencia antes de que se ejecute cualquier instrucción.

Hay muchas formas en que QUOTED_IDENTIFIER podría estar desactivado sin que usted lo sepa necesariamente. Es cierto que este no es el exploit de la pistola humeante que estás buscando, pero es una superficie de ataque bastante grande. Por supuesto, si también escapaste de las comillas dobles, entonces volvemos a donde comenzamos. ;)

Mark Brackett
fuente
1
Eso podría funcionar, pero de nuevo, ¿cómo podrían hacer que ese código se ejecute cuando toda la entrada del usuario está rodeada de comillas simples? Sería muy útil una línea o líneas específicas de código que pudieran inyectar SQL en el código anterior. ¡Gracias!
Patrick
4

Su defensa fallaría si:

  • la consulta espera un número en lugar de una cadena
  • había otra forma de representar una comilla simple, que incluye:
    • una secuencia de escape como \ 039
    • un personaje unicode

(en el último caso, tendría que ser algo que se expandió solo después de haber reemplazado)

AJ
fuente
4

Patrick, ¿estás agregando comillas simples alrededor de TODAS las entradas, incluso las entradas numéricas? Si tiene entrada numérica, pero no pone las comillas simples alrededor, entonces tiene una exposición.

Rob Kraft
fuente
1

¡Qué código tan feo sería toda esa desinfección de la entrada del usuario! Luego, el torpe StringBuilder para la declaración SQL. El método de declaración preparado da como resultado un código mucho más limpio, y los beneficios de inyección SQL son una adición realmente agradable.

Además, ¿por qué reinventar la rueda?

JeeBee
fuente
1

En lugar de cambiar una cita simple a (lo que parece) dos citas simples, ¿por qué no simplemente cambiarla a un apóstrofe, una cita o eliminarla por completo?

De cualquier manera, es un poco un error ... especialmente cuando legítimamente tienes cosas (como nombres) que pueden usar comillas simples ...

NOTA: Su método también supone que todos los que trabajan en su aplicación siempre recuerdan desinfectar la entrada antes de que llegue a la base de datos, lo que probablemente no sea realista la mayor parte del tiempo.

Kevin Fairchild
fuente
Votado abajo porque la respuesta no responde a la pregunta. La pregunta es sobre las cadenas de escape en SQL. Cuando escapas de una cadena arbitraria (como intenta hacer el interrogador, para tratar con datos no desinfectados), no puedes simplemente reemplazar los caracteres problemáticos por otros arbitrarios; eso corrompe los datos. (Además, una cita simple ES un apóstrofe (al menos en ASCII).)
andrewf
-1

Si bien puede encontrar una solución que funcione para las cadenas, para los predicados numéricos también debe asegurarse de que solo pasen números (la comprobación simple es ¿se puede analizar como int / double / decimal?).

Es mucho trabajo extra.

Joseph Daigle
fuente
-2

Puede funcionar, pero me parece un poco tonto. Recomiendo verificar que cada cadena sea válida probándola contra una expresión regular.

Robar
fuente
-3

Sí, puedes, si ...

Después de estudiar el tema, creo que la información desinfectada como sugirió es segura, pero solo bajo estas reglas:

  1. nunca permite que los valores de cadena que provienen de los usuarios se conviertan en algo más que literales de cadena (es decir, evite dar la opción de configuración: "Ingrese nombres / expresiones de columna SQL adicionales aquí:"). Tipos de valor que no sean cadenas (números, fechas, ...): conviértalos a sus tipos de datos nativos y proporcione una rutina para el literal SQL de cada tipo de datos.

    • Las declaraciones SQL son problemáticas para validar
  2. puede usar nvarchar/ ncharcolumnas (y prefijos literales de cadena con N) O limitar los valores que van a varchar/ charcolumnas a caracteres ASCII solamente (por ejemplo, lanzar una excepción al crear una declaración SQL)

    • de esta manera evitará la conversión automática de apóstrofo de CHAR (700) a CHAR (39) (y tal vez otros hacks Unicode similares)
  3. siempre valida la longitud del valor para que se ajuste a la longitud real de la columna (lanzar excepción si es más larga)

    • había un defecto conocido en SQL Server que permitía eludir el error de SQL generado en el truncamiento (lo que conduce a un truncamiento silencioso)
  4. te aseguras de que SET QUOTED_IDENTIFIERsiempre seaON

    • cuidado, se aplica en tiempo de análisis, es decir, incluso en secciones de código inaccesibles

Cumpliendo con estos 4 puntos, debe estar seguro. Si viola alguno de ellos, se abre una forma de inyección SQL.

miroxlav
fuente
1
Es como si no hubiera leído todas las otras respuestas a esta pregunta de 8 años , ya que muchas de estas respuestas señalan que su método no detiene la inyección si el atacante simplemente usa caracteres Unicode.
Hogan
@Hogan: lo hice, pero creo que hay un valor adicional en mi pregunta. Tengo mucha experiencia y pruebas detrás de lo que escribí. Sé que usar los parámetros de consulta es mejor, pero también entiendo completamente la situación en la que alguien debe evitarlo debido a varias razones (por ejemplo, las demandas del empleador de seguir el camino anterior). En este caso, creo que mi respuesta es muy completa y tiene un valor más alto que las respuestas que dicen "simplemente no hagas eso", porque muestra el camino. Muéstrame otras respuestas aquí que muestren lo mismo y consideraré eliminar la mía.
miroxlav
Ok, cuando (no si) su sistema se vea comprometido, vuelva y elimine esta respuesta ... o podría usar una consulta parametrizada.
Hogan
@Hogan: no tengo ningún problema para hacerlo :) Pero actualmente afirmo que no hay forma conocida de evitar esto si mantiene las 4 reglas que publiqué. Si realmente crees que hay una forma de evitarlo, entonces solo indica dónde.
miroxlav
Mal consejo hombre. Cualquier interpolación puede ser derrotada.
Shayne