¿Por qué la inyección SQL no ocurre en esta consulta dentro de un procedimiento almacenado?

18

Hice el siguiente procedimiento almacenado:

ALTER PROCEDURE usp_actorBirthdays (@nameString nvarchar(100), @actorgender nvarchar(100))
AS
SELECT ActorDOB, ActorName FROM tblActor
WHERE ActorName LIKE '%' + @nameString + '%'
AND ActorGender = @actorgender

Ahora, intenté hacer algo como esto. Tal vez estoy haciendo esto mal, pero quiero estar seguro de que dicho procedimiento puede evitar cualquier inyección de SQL:

EXEC usp_actorBirthdays 'Tom', 'Male; DROP TABLE tblActor'

La siguiente imagen muestra el SQL anterior ejecutado en SSMS y los resultados se muestran correctamente en lugar de un error:

ingrese la descripción de la imagen aquí

Por cierto, agregué esa parte después del punto y coma después de que la consulta se terminó de ejecutar. Luego lo ejecuté nuevamente, pero cuando verifiqué si la tabla tblActor existe o no, todavía estaba allí. ¿Estoy haciendo algo mal? ¿O es esto realmente a prueba de inyección? Supongo que lo que estoy tratando de preguntar aquí también es que es un procedimiento almacenado como este seguro. Gracias.

Ravi
fuente
¿Has probado estoEXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'
MarmiK

Respuestas:

38

Este código funciona correctamente porque es:

  1. Parametrizado, y
  2. No hacer ningún SQL dinámico

Para que la inyección SQL funcione, debe crear una cadena de consulta (lo que no está haciendo) y no traducir apóstrofes individuales ( ') en apóstrofes escapados ( '') (se escapan a través de los parámetros de entrada).

En su intento de pasar un valor "comprometido", la 'Male; DROP TABLE tblActor'cadena es solo eso, una cadena simple.

Ahora, si estuvieras haciendo algo en la línea de:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = '
          + @InputParam;

EXEC(@SQL);

entonces eso sería susceptible a la inyección de SQL porque esa consulta no está en el contexto actual previamente analizado; esa consulta es solo otra cadena en este momento. Por lo tanto, el valor de @InputParampodría ser '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;y eso podría presentar un problema porque esa consulta se representaría y ejecutaría como:

SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;

Esta es una (de varias) razones principales para usar procedimientos almacenados: inherentemente más seguro (bueno, siempre y cuando no eludir esa seguridad mediante la creación de consultas como mostré anteriormente sin validar los valores de los parámetros utilizados). Aunque si necesita crear SQL dinámico, la forma preferida es parametrizar eso también usando sp_executesql:

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = @SomeDate_tmp';

EXEC sp_executesql
  @SQL,
  N'SomeDate_tmp DATETIME',
  @SomeDate_tmp = @InputParam;

Con este enfoque, alguien que intente pasar '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;un DATETIMEparámetro de entrada recibirá un error al ejecutar el Procedimiento almacenado. O incluso si el Procedimiento almacenado aceptara @InputParametercomo NVARCHAR(100), tendría que convertirse DATETIMEen a para pasar esa sp_executesqlllamada. E incluso si el parámetro en el SQL dinámico es un tipo de cadena, al entrar en el Procedimiento almacenado, en primer lugar, cualquier apóstrofe se escaparía automáticamente a un apóstrofe doble.

Hay un tipo de ataque menos conocido en el que el atacante intenta llenar el campo de entrada con apóstrofes de modo que una cadena dentro del Procedimiento almacenado que se usaría para construir el SQL dinámico pero que se declara demasiado pequeña no puede caber todo y empuja el apóstrofe final y de alguna manera termina con el número correcto de apóstrofes para que ya no se "escape" dentro de la cadena. Esto se llama Truncamiento SQL y se habló en un artículo de la revista MSDN titulado "Nuevos ataques de truncamiento SQL y cómo evitarlos", por Bala Neerumalla, pero el artículo ya no está en línea. El problema que contiene este artículo, la edición de noviembre de 2006 de MSDN Magazine , solo está disponible como un archivo de Ayuda de Windows (en .chmformato). Si lo descarga, es posible que no se abra debido a la configuración de seguridad predeterminada. Si esto sucede, haga clic derecho en el archivo MSDNMagazineNovember2006en-us.chm y seleccione "Propiedades". En una de esas pestañas, habrá una opción para "Confiar en este tipo de archivo" (o algo así) que debe verificarse / habilitarse. Haga clic en el botón "Aceptar" y luego intente abrir el archivo .chm nuevamente.

Otra variación del ataque de truncamiento es, suponiendo que se use una variable local para almacenar el valor "seguro" suministrado por el usuario, ya que cualquier comilla simple se duplicó para escapar, para completar esa variable local y colocar la comilla simple al final. La idea aquí es que si la variable local no tiene el tamaño adecuado, no habrá suficiente espacio al final para la segunda comilla simple, deje la variable terminando con una comilla simple que luego se combina con la comilla simple que finaliza el valor literal en el SQL dinámico, convirtiendo esa comilla simple final en una comilla simple escapada incrustada, y el literal de cadena en el SQL dinámico luego termina con la siguiente comilla simple que estaba destinada a comenzar el siguiente literal de cadena. Por ejemplo:

-- Parameters:
DECLARE @UserID      INT = 37,
        @NewPassword NVARCHAR(15) = N'Any Value ....''',
        @OldPassword NVARCHAR(15) = N';Injected SQL--';

-- Stored Proc:
DECLARE @SQL NVARCHAR(MAX),
        @NewPassword_fixed NVARCHAR(15) = REPLACE(@NewPassword, N'''', N''''''),
        @OldPassword_fixed NVARCHAR(15) = REPLACE(@OldPassword, N'''', N'''''');

SELECT @NewPassword AS [@NewPassword],
       REPLACE(@NewPassword, N'''', N'''''') AS [REPLACE output],
       @NewPassword_fixed AS [@NewPassword_fixed];
/*
@NewPassword          REPLACE output          @NewPassword_fixed
Any Value ....'       Any Value ....''        Any Value ....'
*/

SELECT @OldPassword AS [@OldPassword],
       REPLACE(@OldPassword, N'''', N'''''') AS [REPLACE output],
       @OldPassword_fixed AS [@OldPassword_fixed];
/*
@OldPassword          REPLACE output          @OldPassword_fixed
;Injected SQL--       ;Injected SQL--         ;Injected SQL--
*/

SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
           + @NewPassword_fixed + N''' WHERE [TableNameID] = '
           + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
           + @OldPassword_fixed + N''';';

SELECT @SQL AS [Injected];

Aquí, el SQL dinámico a ejecutar es ahora:

UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';

Ese mismo SQL dinámico, en un formato más legible, es:

UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';

Injected SQL--';

Arreglar esto es fácil. Solo realice una de las siguientes acciones:

  1. ¡NO USE SQL DINÁMICO A MENOS QUE ES ABSOLUTAMENTE NECESARIO! (Estoy enumerando esto primero porque realmente debería ser lo primero a considerar).
  2. Dimensione correctamente la variable local (es decir, debe ser el doble del tamaño que el parámetro de entrada, en caso de que todos los caracteres pasados ​​sean comillas simples).
  3. No use una variable local para almacenar el valor "fijo"; simplemente ponga el REPLACE()directamente en la creación de Dynamic SQL:

    SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
               + REPLACE(@NewPassword, N'''', N'''''') + N''' WHERE [TableNameID] = '
               + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
               + REPLACE(@OldPassword, N'''', N'''''') + N''';';
    
    SELECT @SQL AS [No SQL Injection here];

    El SQL dinámico ya no se ve comprometido:

    UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';

Notas sobre el ejemplo de Trunction anterior:

  1. Sí, este es un ejemplo muy artificial. No hay mucho que se pueda hacer en solo 15 caracteres para inyectar. Claro, quizás DELETE tableNamesea ​​destructivo, pero es menos probable que agregue un usuario de puerta trasera o cambie una contraseña de administrador.
  2. Este tipo de ataque probablemente requiera conocimiento del código, los nombres de las tablas, etc. Es menos probable que lo haga un extraño / script kiddie aleatorio, pero trabajé en un lugar que fue atacado por un ex empleado bastante molesto que sabía de una vulnerabilidad en una página web particular que nadie más conocía. Es decir, a veces los atacantes tienen un conocimiento íntimo del sistema.
  3. Claro, es probable que se investigue el restablecimiento de la contraseña de todos , lo que podría alertar a la compañía de que está ocurriendo un ataque, pero aún podría proporcionar suficiente tiempo para inyectar a un usuario de la puerta trasera o tal vez obtener información secundaria para usar / explotar más tarde.
  4. Incluso si este escenario es principalmente académico (es decir, no es probable que suceda en el mundo real), todavía no es imposible.

Para obtener información más detallada relacionada con la inyección SQL (que cubre varios RDBMS y escenarios), consulte lo siguiente del Proyecto de seguridad de aplicaciones web abiertas (OWASP):
Prueba de inyección SQL

Respuesta de desbordamiento de pila relacionada en inyección de SQL y truncamiento de SQL:
¿Qué tan seguro es T-SQL después de reemplazar el 'carácter de escape?

Solomon Rutzky
fuente
2
Oh, muchas gracias, esta es una gran respuesta. Entiendo ahora. Realmente me gustaría ver la técnica que mencionaste al final también donde el atacante intenta llenar el campo de entrada con apóstrofes, si puedes encontrarlo. Gracias por adelantado. Voy a mantener esto abierto, en caso de que no lo encuentre, elegiré esto como la respuesta.
Ravi
1
@Ravi Encontré el enlace pero ya no llega al artículo, ya que ahora están archivados. Pero agregué información y enlaces útiles y todavía estoy tratando de encontrar el artículo dentro de esos archivos.
Solomon Rutzky
1
Gracias srutzsky, leeré el artículo de OWASP y las pruebas de inyección. Si recuerdo correctamente, 'mutillidae', la aplicación web vulnerable para pruebas de seguridad, tiene una inyección SQL que había realizado en la universidad con la cadena 'OR 1 = 1' que en mutillidae me hizo iniciar sesión en la aplicación web como administrador I pensar. Fue entonces cuando me presentaron por primera vez la inyección SQL.
Ravi
1
Tampoco puedo ver el archivo .chm, pero gracias por esta respuesta perfecta y por todos los enlaces útiles, incluido el de stackoverflow y el de OWASP. Lo leí hoy y aprendí mucho de esto.
Ravi
2

El asunto simple es que no estás confundiendo los datos con el comando en absoluto. Los valores de los parámetros nunca se tratan como parte del comando y, por lo tanto, nunca se ejecutan.

Escribí un blog sobre esto en: http://blogs.lobsterpot.com.au/2015/02/10/sql-injection-the-golden-rule/

Rob Farley
fuente
Gracias Rob, tengo este favorito marcado para leer más tarde en el día.
Ravi