2012-03-11 22 views
10

Estoy generando algunos SQL dinámicos y me gustaría asegurarme de que mi código esté a salvo de SQL injection.Sanitize nombre de tabla/columna en SQL dinámico en .NET? (Impedir ataques de inyección SQL)

En aras de la discusión aquí es un ejemplo de cómo mínimo que se genera:

var sql = string.Format("INSERT INTO {0} ({1}) VALUES (@value)", 
    tableName, columnName); 

En lo anterior, tableName, columnName, y todo lo que está obligado a @value provenir de una fuente no confiable. Dado que los marcadores de posición se están utilizando, @value está a salvo de los ataques de inyección de SQL y puede ignorarse. (El comando se ejecuta a través de SqlCommand.)

Sin embargo, tableName y columnName no puede ser obligado como marcadores de posición y para ello son vulnerables a ataques de inyección. Como este es un escenario "verdaderamente dinámico", no hay una lista blanca de tableName o columnName disponible.

La pregunta es, pues:

¿Existe una norma , una función de manera de comprobar y/o desinfectar tableName y columnName? (SqlConnection, o una clase auxiliar, etc.) En caso negativo, ¿cuál es una buena forma de realizar esta tarea sin usando una biblioteca de terceros?

Notas:

  • todos los identificadores de SQL, incluyendo el esquema, debe por aceptada: por ejemplo, [schema].[My Table].column es tan "seguro" como table1.
  • Puede esterilizar los identificadores o detectar un identificador no válido. (No es necesario para asegurar que la tabla/columna es realmente válida en su contexto; el SQL resultante puede ser válida, pero debe ser "seguro".)

Actualización:

acaba de encontrar esto, y pensó que era algo interesante: ¿Hay una función SqlFunctions.QuoteName en .NET4 (EF4?). De acuerdo, no es realmente ayúdame aquí ...

Respuesta

5

Dado que estás utilizando una SqlConnection, se supone que se trata de una base de datos SQL Server.

Dada esa suposición, puede validar los nombres de tabla y campo utilizando una expresión regular que siga las reglas de identificación del servidor SQL como se define in MSDN. Aunque soy un novato total y absoluta en las expresiones regulares, encontré ésta que debe acercarse:

[\p{L}{\p{Nd}}$#_][\p{L}{\p{Nd}}@$#_]* 

Sin embargo, una expresión regular no se ocupará de palabras clave de SQL Server y no garantiza que la tabla y/o la columna realmente existe (aunque usted indicó que no era un gran problema).

Si esta fuera mi aplicación, primero me aseguraría de que el usuario final no intentara realizar la inyección rechazando cualquier solicitud que contuviera puntos y comas (;).

A continuación, validaría la existencia de la tabla eliminando los delimitadores de nombre válidos (",", [,]), dividiendo el nombre de la tabla por un período para ver si se especificó un esquema y ejecutando una consulta contra INFORMATION_SCHEMA. tablas para determinar la existencia de la tabla

Por ejemplo:..

SELECT 1 
FROM INFORMATION_SCHEMA.TABLES 
WHERE TABLE_NAME = 'tablename' 
AND TABLE_SCHEMA = 'tableschema' 

Si crea esta consulta mediante parámetros, entonces usted debe protegerse aún más a partir de la inyección

Por último, me gustaría validar la existencia de cada nombre de columna mediante la realización de un conjunto similar de pasos, utilizando únicamente INFORMATION_SCHEMA.COLUMNS para determinar la validez de la (s) columna (s) una vez que se haya determinado que la tabla es válida.

Probablemente buscaría la lista de columnas válidas para esta tabla de SQL Server, luego verificaría que cada columna de solicitud estuviera en la lista dentro de mi código. De esta forma, podría decir exactamente qué columnas fueron erróneas y proporcionar esa retroalimentación al usuario.

+0

Tiene razón, de hecho es SQL Server. Me estoy inclinando por una ruta de expresión regular * aceptada mínimamente *, pero esperaba que existiera algo prefabricado (y probado ;-). Me gusta mucho la idea adicional de compararla con los metadatos del esquema. –

+0

Si usa una consulta parametrizada para probar la tabla/esquema, entonces verifique la existencia de cada nombre de columna en el código contra la lista completa de nombres de columna para la tabla, entonces realmente no necesita realizar ninguna verificación de validez en los valores entrantes , que sería lo último en minimización :). –

+0

Bueno, es una * pequeña * implementación más grande :) –

20

No estoy seguro de si todavía está investigando esto, pero la clase DbCommandBuilder proporciona un método QuoteIdentifier para este propósito. Los principales beneficios de esto son que es independiente de la base de datos y no implica ningún desastre RegEx.

A partir de .NET 4.5, usted tiene todo lo necesario para desinfectar los nombres de tabla y columna usando sólo su objeto DbConnection:

DbConnection connection = GetMyConnection(); // Could be SqlConnection 
DbProviderFactory factory = DbProviderFactories.GetFactory(connection); 

// Sanitize the table name 
DbCommandBuilder commandBuilder = factory.CreateCommandBuilder(); 

string tableName = "This Table Name Is Long And Bad"; 
string sanitizedTableName = commandBuilder.QuoteIdentifier(tableName); 

IDbCommand command = connection.CreateCommand(); 
command.CommandText = "SELECT * FROM " + sanitizedTableName; 

// Becomes 'SELECT * FROM [This Table Name Is Long And Bad]' in MS-SQL, 
// 'SELECT * FROM "This Table Name Is Long And Bad"' in Oracle, etc. 

(pre-4.5, necesitará alguna otra manera de conseguir su DbProviderFactory . - tal vez del nombre del proveedor de datos en la configuración de la aplicación o no modificable en alguna parte)

+0

Gracias, ni siquiera sabía que existía la clase. Todavía estoy usando .NET3.5, sin ayuda de fábrica de magia :(estoy seguro de que será útil para otras personas, aunque (o tal vez para mí en unos pocos años). –

+0

Oh, todavía puedes hacerlo en .NET 3.5, solo necesita alguna otra forma de obtener un objeto 'DbProviderFactory' o incluso simplemente crear un' DbCommandBuilder' manualmente.Si está seguro de que siempre usará MS-SQL, puede hacer 'DbCommandBuilder commandBuilder = new SqlCommandBuilder();' y saltarse todo el desastre de fábrica. –

+0

Ahh, sí. Eso tiene sentido - Estoy usando LINQ2SQL así que ... sí, siempre SQL Server :) –

1

para SQL Server, que es bastante fácil de desinfectar un identificador:

// To make a string safe to use as an SQL identifier : 
// 1. Escape single closing bracket with double closing bracket 
// 2. Wrap in square brackets 
string.Format("[{0}]", identifier.Replace("]", "]]")); 

Una vez envuelto entre corchetes y escapado, lo único que no funcionará como identificador es una cadena vacía/nula.

+0

No sé por qué estabas abajo, porque esto es exactamente lo que hace 'DbCommandBuilder.QuoteIdentifier'. – Stijn

Cuestiones relacionadas