2009-08-07 3 views
11

Me encontré con algo extraño ... hay un código en nuestro sitio que está tomando una declaración de SQL gigante, modificándola en código haciendo una búsqueda y reemplazando basada en algunos valores de usuario, y luego pasándola a SQL Servidor como una consulta.¿Cómo debo pasar el nombre de una tabla a un proceso almacenado?

Estaba pensando que esto sería más limpio como una consulta parametrizada a un proceso almacenado, con los valores del usuario como los parámetros, pero cuando miré más de cerca, pude ver por qué podrían estar haciéndolo ... la tabla que ellos están seleccionando de es variablemente dependiente de esos valores de usuario.

Por ejemplo, en un caso, si los valores eran la consulta terminaría siendo algo así como "SELECT * FROM foo_bar"

("BAR" "foo") ¿Existe una manera fácil y clara para hacer ¿esta? Todo lo que intento parece poco elegante.

EDIT: Pude, por supuesto, generar dinámicamente el sql en el proceso almacenado y ejecutarlo (bleh), pero en ese punto me pregunto si he ganado algo.

Edit2: Refactorizando los nombres de las tablas de alguna manera inteligente, dicen que tener a todos en una mesa con los diferentes nombres como una nueva columna sería una buena manera de resolver todo esto, que varias personas han señalado directamente , o aludido a. Lamentablemente, no es una opción en este caso.

+0

SQL-Server 2008 permite variables TABLE. –

+2

@Lance: Cierto, sí, pero creo (podría estar equivocado) que funcionan de manera diferente a eso. Una variable de tabla almacena datos de tabla, no nombres de tabla. Es un concepto similar a una tabla temporal. – Beska

+0

No tiene que "refactorizar los nombres de la tabla", ya se encuentran en la vista suministrada por el sistema: INFORMATION_SCHEMA.TABLES. – RBarryYoung

Respuesta

32

En primer lugar, usted debe NUNCA hacer composiciones de comandos SQL en una aplicación de cliente de este tipo, que es lo Inyección SQL es. (Está bien para una herramienta de administración que no tiene sus propios privilegios, pero no para una aplicación de uso compartido).

En segundo lugar, sí, una llamada parametrizada a un procedimiento almacenado es más limpio y más seguro.

Sin embargo, ya que tendrá que utilizar Dynamic SQL para hacer esto, aún no desea incluir la cadena pasada en el texto de la consulta ejecutada. En su lugar, desea utilizar la cadena pasada para buscar los nombres de las tablas reales que el usuario puede consultar en el camino.

Aquí está un ejemplo sencillo ingenua:

CREATE PROC spCountAnyTableRows(@PassedTableName as NVarchar(255)) AS 
-- Counts the number of rows from any non-system Table, *SAFELY* 
BEGIN 
    DECLARE @ActualTableName AS NVarchar(255) 

    SELECT @ActualTableName = QUOTENAME(TABLE_NAME) 
    FROM INFORMATION_SCHEMA.TABLES 
    WHERE TABLE_NAME = @PassedTableName 

    DECLARE @sql AS NVARCHAR(MAX) 
    SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';' 

    EXEC(@SQL) 
END 

Algunos han preguntado bastante por qué esto es más seguro. Con suerte, mesitas Bobby pueden hacer esto más claro:

alt text


Las respuestas a las preguntas más:

  1. QUOTENAME por sí sola no es garantía de ser seguro. MS nos alienta a usarlo, pero no han garantizado que los hackers no lo puedan engañar. Para tu información, la seguridad real se trata de las garantías. La búsqueda de tabla con QUOTENAME es otra historia, es irrompible.

  2. QUOTENAME no es estrictamente necesario para este ejemplo, la traducción de búsqueda en INFORMATION_SCHEMA sola es normalmente suficiente. QUOTENAME está aquí porque es una buena forma de seguridad incluir una solución completa y correcta. QUOTENAME aquí en realidad está protegiendo contra un problema potencial distinto pero similar conocido como inyección latente.

+0

Aunque no me gusta usar SQL dinámico, esta parece ser la mejor opción para resolver el problema. +1 – Randolpho

+0

Lo siento, no puedo entender por qué es más SEGURO? ¿Más seguro en qué sentido? No hay garantía de que esa tabla exista, es decir, aún podemos obtener un error de sintaxis al hacer EXEC (@SQL). – AlexS

+0

Hmm ... No sé acerca de su primera afirmación ... tal vez esté usando una definición diferente para SQL Injection que yo. Ciertamente, si tomara esa opción, estaría "inyectando" texto en una consulta de SQL Server, pero la estoy usando en el sentido peyorativo en el que un usuario modifica algo a propósito para enviar algo a SQL que no debe enviarse. No tengo que preocuparme por eso, ya que el usuario nunca ve o tiene acceso a ninguno de los parámetros que se enviarían. – Beska

2

Yo argumentaría en contra de generar dinámicamente el SQL en el proceso almacenado; eso te meterá en problemas y podría causar una vulnerabilidad de inyección.

En su lugar, analizaría todas las tablas que podrían verse afectadas por la consulta y crearía algún tipo de enumeración que determinaría qué tabla usar para la consulta.

+0

Pensé en esto, pero el problema es que hay muchas (alrededor de 50) tablas en las que esto podría pasar, pero quizás más críticamente, se agregan más periódicamente. – Beska

+0

No estoy demasiado preocupado por la inyección SQL, ya que nunca consultamos al usuario por nada que vaya al parámetro ... son todos valores internos a los que un usuario no tendría acceso. Aún así, es probable que las personas mejores que yo probablemente pensaron que estaban a salvo cuando no lo estaban, por lo que la advertencia está bien tomada. – Beska

+0

Él ya tiene inyección SQL en el código del cliente. No hay más seguridad allí que en el Servidor SQL. Y es perfectamente posible utilizar SQL dinámico, incluso para este problema, sin ninguna inyección. – RBarryYoung

5

(Un) afortunadamente no hay forma de hacerlo - no puede usar el nombre de la tabla pasado como parámetro al código almacenado que no sea para la generación dinámica de SQL. Cuando se trata de decidir dónde generar el código sql, prefiero el código de la aplicación en lugar del código almacenado. El código de la aplicación generalmente es más rápido y más fácil de mantener.

En caso de que no le guste la solución con la que está trabajando, sugiero un rediseño más profundo (es decir, cambie la lógica de esquema/aplicación para que ya no tenga que pasar el nombre de la tabla como parámetro).

+0

Bueno, no es que no me guste tanto como esperaba que hubiera una mejor manera de hacerlo. Si la solución actual (modificar una cadena sql en el código) es la mejor manera, entonces, si quiero ser un buen programador, es mejor que me guste, ¿verdad? – Beska

+0

AlexS: Lo que usted está recomendando es Inyección SQL. – RBarryYoung

+0

@RBarryYoung: Recomiendo un 'rediseño más profundo ... para que ya no tenga que pasar un nombre de tabla como parámetro'. Esto no es 'Inyección SQL' Supongo ;-) – AlexS

2

Parece que estaría mejor con una solución ORM.

Me estremezco cuando veo sql dinámico en un procedimiento almacenado.

+0

Como todos nosotros ... – Beska

+2

Está perfectamente bien, * SI * lo haces correctamente. – RBarryYoung

+0

Algunas veces es inevitable, como cuando se usa el comando PIVOT. – BoltBait

0

Dependiendo de si el conjunto de columnas en las tablas es el mismo o diferente, me lo planteo de dos maneras en el largo plazo:

1) si mismo, por qué no crear una nueva columna que se usaría como selector, cuyo valor se deriva de los parámetros proporcionados por el usuario? (¿es una optimización del rendimiento?)

2) si son diferentes, es probable que su manejo también sea diferente. Como tal, parece que dividir el código de selección/manejo en bloques separados y luego llamarlos por separado sería un enfoque más modular para mí. Repita la parte "select * from", , pero en este escenario, el conjunto de tablas es, con suerte, finito.

Permitir que el código de llamada suministre dos partes arbitrarias del nombre de la tabla para realizar una selección parece muy peligroso.

+0

Tristemente, mientras podría tener una enumeración para decidir qué tabla seleccionar, sería muy grande (ahora hay alrededor de 50 tablas), pero peor, se agregarán más periódicamente, lo que llevaría a un problema de mantenimiento. – Beska

+0

para que tenga más un caso de (1) arriba en lugar de (2)? p.ej. algo así como una lista de tablas que contiene "Nombre, Apellido", mesas que se denominan "Programadores", "Administradores", "Directores", "Usuarios", etc. –

0

No sé la razón por la que tiene los datos repartidos en varias tablas, pero parece que está rompiendo uno de los fundamentos. Los datos deben estar en las tablas, no como nombres de tablas.

Si las tablas tienen más o menos el mismo diseño, considere si sería mejor colocar los datos en una sola tabla. Eso resolvería su problema con la consulta dinámica y haría que el diseño de la base de datos sea más flexible.

+0

Estoy de acuerdo en teoría, pero no puedo hacer. Es un problema de Commerce Server. No entiendo todos los detalles, pero es un sitio enorme, y las diversas tablas de catálogo forman parte del núcleo del sitio. No hay forma de que podamos cambiarlo en el corto plazo. – Beska

0

En lugar de consultar las tablas según los valores de entrada del usuario, puede elegir el procedimiento en su lugar. es decir
1. Cree un procedimiento FOO _ BAR _ prc y dentro de que ponga la consulta 'select * from foo_bar', de esa manera la consulta será precompilada por la base de datos.
2. A continuación, según la entrada del usuario, ejecute ahora el procedimiento correcto desde el código de la aplicación.

Dado que tiene alrededor de 50 tablas, esta podría no ser una solución viable, ya que requeriría mucho trabajo de su parte.

+1

Bueno, no le temo a un montón de trabajo si es una sola vez, pero mantener 50 procesos almacenados si necesitábamos, digamos, agregar una columna a la consulta, sería una pesadilla. – Beska

1

Una cosa que puede considerar es hacer una declaración de caso que contenga el mismo comando SQL que desee, una vez para cada tabla válida, luego pase como una cadena el nombre de la tabla en este procedimiento y elija el comando que ejecutar .

Por cierto, como persona de seguridad, la sugerencia anterior que le indica que seleccione de las tablas del sistema para asegurarse de tener una tabla válida me parece una operación desaprovechada. Si alguien puede inyectar pasado el QUOTENAME() entonces la inyección funcionaría en la tabla del sistema tan bien como en la tabla subyacente. Lo único que esto ayuda a garantizar que sea un nombre de tabla válido, y creo que la sugerencia anterior es un mejor enfoque, ya que no está utilizando QUOTENAME() en absoluto.

+0

Esta última observación es incorrecta. La consulta que RBarryYoung escribió NO es dinámica y no realiza ninguna actualización y no rellena ninguna variable con datos que no sean de confianza. Por lo tanto, existe un riesgo CERO de inyección SQL directa, latente o persistente. Muy diferente de hacer EXEC ('UPDATE' + @tablename + 'SET ...'). No solo no hay riesgo, sino que es el elemento clave que hace que todo sea seguro. – EGP

0

De hecho, quería saber cómo pasar el nombre de la tabla para crear una tabla en el procedimiento almacenado. Al leer algunas de las respuestas e intentar algunas modificaciones en mi extremo, finalmente pude crear una tabla con el nombre pasado como parámetro. Aquí está el procedimiento almacenado para que otros verifiquen cualquier error en él.

USO [Nombre de base de datos] GO /****** Objeto:. StoredProcedure [dbo] [sp_CreateDynamicTable] Guión Fecha: 06/20/2015 16:56:25 ******/ ANSI_NULLS SET en GO QUOTED_IDENTIFIER SET EN GO CREAR PROCEDIMIENTO [dbo] [sp_CreateDynamicTable] @tName varchar (255) AS COMENZAR SET NOCOUNT ON.; DECLARE @SQL nvarchar (max)

SET @SQL = N'CREATE TABLE [DBO].['+ @tName + '] (DocID nvarchar(10) null);' 

    EXECUTE sp_executesql @SQL 

FIN

0

@RBarry joven No es necesario añadir los soportes a @ActualTableName en la cadena de consulta porque ya está incluido en el resultado de la consulta en INFORMATION_SCHEMA.TABLES. De lo contrario, habrá error (s) cuando se ejecute.

CREATE PROC spCountAnyTableRows (@PassedTableName como NVarchar (255)) AS - cuenta el número de filas de cualquier tabla no es del sistema, SEGURA COMENZAR DECLARE @ActualTableName AS NVarchar (255)

SELECT @ActualTableName = QUOTENAME(TABLE_NAME) 
FROM INFORMATION_SCHEMA.TABLES 
WHERE TABLE_NAME = @PassedTableName 

DECLARE @sql AS NVARCHAR(MAX) 
--SELECT @sql = 'SELECT COUNT(*) FROM [' + @ActualTableName + '];' 

-- changed to this 
SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';' 

EXEC(@SQL) 

END

Cuestiones relacionadas