Pregunta

Si tuviera que seleccionar una fila de una tabla, básicamente tengo dos opciones, como esta

int key = some_number_derived_from_a_dropdown_or_whatever
SqlCommand cmd = new SqlCommand("select * from table where primary_key = " + key.ToString());

o usa un parámetro

SqlCommand cmd = new SqlCommand("select * from table where primary_key = @pk");
SqlParameter param  = new SqlParameter();
param.ParameterName = "@pk";
param.Value         = some_number_derived_from_a_dropdown_or_whatever;
cmd.Parameters.Add(param);

Ahora, sé que el primer método está mal visto debido a posibles ataques de inyección de SQL, pero en este caso el parámetro es un número entero y, por lo tanto, no debería ser posible inyectar código malicioso.

Mi pregunta es la siguiente: ¿Utiliza la opción 1 en el código de producción porque considera que el uso es seguro debido a la facilidad de uso y el control sobre el parámetro insertado (como el anterior, o si el parámetro está creado en el código)? ¿O siempre usas parámetros sin importar qué? ¿Son seguros los parámetros de inyección al 100%?

¿Fue útil?

Solución

Me saltearé el argumento de Inyección de SQL, eso es demasiado conocido y solo me centraré en el aspecto de SQL de los parámetros frente a los no parámetros.

Cuando envía un lote de SQL al servidor, cualquier lote, tiene que ser analizado para ser comprendido. Como cualquier otro compilador, el compilador de SQL debe producir un AST a partir del texto y luego operar en El árbol de sintaxis. En última instancia, el optimizador transforma el árbol de sintaxis en un árbol de ejecución y, finalmente, produce un plan de ejecución que se ejecuta. En la oscuridad de alrededor de 1995, marcó la diferencia si el lote era una consulta Ad-Hoc o un procedimiento almacenado, pero hoy en día no hace absolutamente nada, todos son iguales.

Ahora, donde los parámetros marcan la diferencia es que un cliente que envía una consulta como selecciona * de la tabla donde primary_key = @pk enviará exactamente el mismo texto SQL cada vez , no importa en qué valor esté interesado. Lo que sucede entonces es que el proceso completo que describí anteriormente está en cortocircuito. SQL buscará en la memoria un plan de ejecución para el texto sin procesar, sin analizar, que recibió (basado en un resumen de hash de la entrada) y, si lo encuentra, ejecutará ese plan. Eso significa que no hay análisis, optimización, nada, el lote va directamente a la ejecución . En los sistemas OLTP que ejecutan cientos y miles de pequeñas solicitudes cada segundo, esta ruta rápida hace una gran diferencia de rendimiento.

Si envía la consulta en el formulario seleccione * de la tabla donde primary_key = 1 , entonces SQL deberá al menos analizarla para comprender qué hay dentro del texto, ya que es probable que el texto sea nuevo. una, diferente de cualquier lote anterior que haya visto (incluso un solo carácter como 1 vs. 2 hace que todo el lote sea diferente). Luego operará en el árbol de sintaxis resultante e intentará un proceso llamado Parametrización simple . Si la consulta puede medirse automáticamente, es probable que SQL encuentre un plan de ejecución almacenado en caché para otras consultas que se ejecutaron anteriormente con otros valores de pk y reutilizen ese plan, por lo que al menos no es necesario optimizar su consulta y omitirla. Paso de generar un plan de ejecución real. Pero de ninguna manera logró el cortocircuito completo, el camino más corto posible que logra con una verdadera consulta parametrizada por el cliente.

Puede consultar SQL Server, SQL Statistics Object Contador de rendimiento de su servidor. El contador Auto-Param Intempts / sec mostrará muchas veces por segundo. SQL tiene que traducir una consulta recibida sin parámetros en una auto-parametrizada. Cada intento puede evitarse si parametriza correctamente la consulta en el cliente. Si también tiene una gran cantidad de Parámetros automáticos fallidos / seg es aún peor, significa que las consultas están en el ciclo completo de optimización y generación del plan de ejecución.

Otros consejos

Siempre use la opción 2).

El primero es NO seguro con respecto a ataques de inyección SQL .

El segundo no solo es mucho más seguro, sino que también tendrá un mejor desempeño porque el optimizador de consultas tiene más posibilidades de crear un buen plan de consultas porque se ve Lo mismo todo el tiempo, espere los parámetros, por supuesto.

¿Por qué debes evitar la Opción 1

Aunque parece que está obteniendo este valor del elemento select , alguien podría falsificar una solicitud HTTP y publicar lo que quiera dentro de ciertos campos. Su código en la opción 1 debe al menos reemplazar algunos caracteres / combinaciones peligrosas (es decir, comillas simples, corchetes, etc.).

¿Por qué se le recomienda utilizar la Opción 2

El motor de consultas SQL es capaz de almacenar en caché el plan de ejecución y administrar las estadísticas para varias consultas que se le presentan. Por lo tanto, tener consultas unificadas (la mejor sería, por supuesto, tener un procedimiento almacenado por separado) acelera la ejecución a largo plazo.

Todavía no he escuchado de ningún ejemplo en el que los parámetros puedan ser secuestrados para la inyección de SQL. Hasta que vea que se demuestra lo contrario, los consideraría seguros.

El SQL dinámico nunca debe considerarse seguro. Incluso con " confiado " entrada, es un mal hábito para entrar. Tenga en cuenta que esto incluye SQL dinámico en procedimientos almacenados; La inyección de SQL también puede ocurrir allí.

Como tienes el control de que el valor es un entero, son bastante equivalentes. Por lo general, no uso el primer formulario y, por lo general, tampoco permito el segundo porque normalmente no permito el acceso a la tabla y requiero el uso de procedimientos almacenados. Y aunque puede realizar la ejecución de SP sin usar la colección de parámetros, todavía recomiendo usar los parámetros AND de SPs:

-- Not vulnerable to injection as long as you trust int and int.ToString()
int key = some_number_derived_from_a_dropdown_or_whatever ;
SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString()); 

-- Vulnerable to injection all of a sudden
string key = some_number_derived_from_a_dropdown_or_whatever ;
SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString()); 

Tenga en cuenta que aunque la opción 1 es segura en su caso , lo que sucede cuando alguien ve y usa esa técnica con una variable no entera, ahora está capacitando a las personas para que usen una técnica que podría estar abierto a inyección.

Tenga en cuenta que puede reforzar su base de datos de manera proactiva para evitar que la inyección sea efectiva incluso cuando el código de su aplicación es defectuoso. Para cuentas / roles que usan las aplicaciones:

  • Solo permitir el acceso a las tablas como absolutamente necesario
  • Solo permitir el acceso a las vistas como absolutamente necesario
  • No permitir declaraciones DDL
  • Permitir la ejecución a SP en función del rol
  • Cualquier SQL dinámico en SP debe revisarse como absolutamente necesario

Personalmente, siempre usaría la opción 2 como una cuestión de hábito. Dicho esto:

Ya que está forzando la conversión del valor desplegable a un int, eso brindará cierta protección contra los ataques de inyección de SQL. Si alguien intenta inyectar información adicional en la información publicada, C # lanzará una excepción cuando intente convertir el código malicioso en un valor entero que frustra el intento.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top