Articles

Por qué es importante QUOTENAME

En algún momento, la gran mayoría de nosotros vamos a necesitar usar SQL dinámico en nuestra carrera. SQL dinámico no es algo malo, pero asegurarse de que es seguro es realmente importante. Una de las herramientas más simples para hacerlo es un buen uso de QUOTENAME.

Tratar con objetos dinámicos

Tomemos un procedimiento almacenado simple como el siguiente, donde alguien ha tratado de hacerlo «seguro»:

 CREATE PROC CreateTable_sp @TableName sysname AS DECLARE @DSQL nvarchar(MAX); SET @DSQL = N'CREATE TABLE (ID int)'; EXEC (@DSQL); GO

En la cara de la misma se puede ver que han tratado de evitar la inyección, sin embargo, está lejos de ser seguro. Al igual que con la concatenación de un conjunto de cadenas encerradas entre comillas simples, se pueden usar las mismas técnicas de inyección aquí.

 EXEC dbo.CreateTable_sp @TableName = 'asdklfhjgasdklf] (ID int); SELECT 1 AS test;--';

Esto evitaría fácilmente el parámetro que se encapsula, y crearía una tabla sin sentido con nombre, así como devolvería un conjunto de datos con la prueba de columna. Sin embargo, si implementamos el uso de QUOTENAME, entonces estamos en un lugar mucho mejor:

 CREATE PROC CreateTable_sp @TableName sysname AS DECLARE @DSQL nvarchar(MAX); SET @DSQL = N'CREATE TABLE ' + QUOTENAME(@TableName) + N' (ID int);'; EXEC sp_executesql @DSQL; GO

Si tuviéramos que ejecutar el mismo SQL que hicimos anteriormente, entonces terminaría con una tabla con el nombre «asdklfhjgasdklf] (ID int); SELECT 1 AS test;–«.

QUOTENAME, sin embargo, no debe considerarse solo cuando cree un objeto como el anterior. Los pivotes dinámicos son bastante comunes, y el uso de QUOTENAME es igual de importante allí. Es posible que sienta que está más seguro contra la inyección, ya que los nombres de las columnas son más conocidos. Sin embargo, eso no significa que alguien no haya sido lo suficientemente «tonto» para usar un carácter especial en el nombre de una columna. Si tienes una columna con un nombre como CustomerID, entonces (como antes) la sintaxis ‘+ N’] ‘ no va a funcionar. No va a inyectar nada con ese nombre, pero causará un error.

Cuando el elemento dinámico no es un objeto

Aunque estoy repitiendo lo que dice la documentación (enlazada arriba), creo que es importante tener en cuenta que QUOTENAME en realidad acepta 2 parámetros. El primero es la «cadena de caracteres» y el segundo parámetro (opcional) es el «carácter de comillas». De acuerdo con la documentación, la definición del carácter de comilla «Es una cadena de un solo carácter para usar como delimitador. Puede ser una comilla simple ( ‘), un corchete izquierdo o derecho ( ), o una comilla doble ( » ). Si no se especifica quote_character, se utilizan corchetes.»Sin embargo, he encontrado que otros personajes son aceptados. Por ejemplo, ambos ( y { trabajo y estoy seguro de que hay más. Si un carácter no es aceptable, se devolverá NULL; por ejemplo, QUOTENAME(‘someString’,’#’) devolverá NULL.

Al tratar con SQL dinámico, es posible que no siempre esté tratando con un objeto dinámico. Por ejemplo, puede estar utilizando SQL dinámico con OPENROWSET. Al declarar la ruta de archivo para su archivo, debe estar entre comillas simples ( ‘ ), no entre corchetes (). Esto significa que QUOTENAME (@ruta de archivo) no va a funcionar, pero cámbialo a QUOTENAME(@ruta de archivo,»»), y lo hará.

Si tomamos un procedimiento almacenado (no tan) simple:

 CREATE PROC ImportData_sp @NewTable sysname, @ImportFile nvarchar(512), @Worksheet nvarchar(128) AS DECLARE @DSQL nvarchar(MAX); SET @DSQL = N'SELECT ''' + @Worksheet + ''' AS Worksheet, *' + NCHAR(10) + N'INTO ' + NCHAR(10) + N'FROM OPENROWSET(''Microsoft.ACE.OLEDB.12.0'',' + NCHAR(10) + N' ''Excel 12.0; Database=' + REPLACE(@ImportFile,N'''',N'') + ',' + NCHAR(10) + N' );'; EXEC (@DSQL); GO

A diferencia de antes, tenemos algunos elementos dinámicos; el objeto de destino, el archivo de origen y la hoja de trabajo en ese archivo. Esto en realidad deja algunas áreas abiertas, lo cual está lejos de ser ideal. Pero aún podemos arreglarlo usando una metodología similar de antes:

 CREATE PROC ImportData_sp @NewTable sysname, @ImportFile nvarchar(512), @Worksheet nvarchar(128) AS DECLARE @DSQL nvarchar(MAX); SET @DSQL = N'SELECT @dWorkSheet AS WorkSheet, *' + NCHAR(10) + N'INTO ' + QUOTENAME(@NewTable) + + NCHAR(10) + N'FROM OPENROWSET(''Microsoft.ACE.OLEDB.12.0'',' + NCHAR(10) + N' ' + QUOTENAME('Excel 12.0; Database=' + @ImportFile,'''') + ',' + NCHAR(10) + N' ' + QUOTENAME(QUOTENAME(@Worksheet + '$','''')) + N');'; --PRINT @DSQL; EXEC sp_executesql @DSQL, N'@dWorkSheet nvarchar(128)', @dWorksheet = @Worksheet; GO

Hay un poco más para tomar aquí (por ejemplo, observe el NOMBRE de CITA anidado para la hoja de trabajo), esto hace que el SQL sea mucho más seguro de lo que era. Un usuario puede ejecutarlo fácilmente como el siguiente, y siempre que no tenga comillas simples u otros caracteres especiales incorrectos en el nombre de la hoja de trabajo (por más que lo intente, no pude hacer que funcione, si alguien sabe cómo, por favor, comente), entonces se importará con éxito:

 EXEC dbo.ImportData_sp @NewTable = 'JanData', @ImportFile = 'C:\My Files\ExcelDoc.xlsx;', @Worksheet = 'Jan data';

Conclusión

QUOTENAME parece, a veces, ser una función «olvidada». Cuando se trabaja con SQL dinámico, la concatenación de cadenas raw siempre es una mala idea. Asegurarse de parametrizar correctamente su consulta, y citar la cadena que no puede, sin embargo, reduce considerablemente la exposición que tiene a la inyección.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.