c# - net - optimizar consultas linq
¿Es seguro no parametrizar una consulta SQL cuando el parámetro no es una cadena? (12)
Bueno ... una cosa es segura: la seguridad NO está bien, cuando concatena una cadena (tomada por el usuario) con su cadena de comando SQL. No importa siempre que la cláusula where se refiera a un entero o a cualquier tipo; Pueden ocurrir inyecciones.
Lo que importa en la inyección SQL es el tipo de datos de la variable que solía obtener el valor del usuario.
Supongamos que tenemos un número entero en la cláusula where y:
-
La variable de usuario es una cadena. Entonces, ok, no es muy fácil inyectarse (usando UNION) pero es muy fácil evitarlo usando ''OR 1 = 1'' - como ataques ...
-
Si la variable de usuario es un entero. Por otra parte, podemos ''probar'' la fuerza del sistema al pasar pruebas inusuales de grandes números para detectar fallas del sistema o incluso un desbordamiento de búfer oculto (en la cadena final) ...;)
Tal vez los parámetros para las consultas o (aún mejor - imo) para los procedimientos almacenados no son 100% seguros para las amenazas, pero son la medida menos requerida (o la primaria si lo prefiere) para minimizarlos.
En términos de
inyección SQL
, entiendo completamente la necesidad de parametrizar un parámetro de
string
;
Ese es uno de los trucos más antiguos del libro.
Pero, ¿cuándo puede justificarse
no
parametrizar un
SqlCommand
?
¿Hay algún tipo de datos considerado "seguro" para no parametrizar?
Por ejemplo: no me considero
cerca de
un experto en SQL, pero no puedo pensar en ningún caso en el que sea potencialmente vulnerable a la inyección de SQL aceptar un
bool
o un
int
y simplemente concatenarlo directamente en la consulta.
¿Es correcta mi suposición o podría dejar una gran vulnerabilidad de seguridad en mi programa?
Para aclarar, esta pregunta está etiquetada
c#
que es un lenguaje fuertemente tipado;
cuando digo "parámetro", piensa en algo como
public int Query(int id)
.
Creo que es seguro ... técnicamente , pero es un hábito terrible. ¿De verdad quieres escribir consultas como esta?
var sqlCommand = new SqlCommand("SELECT * FROM People WHERE IsAlive = " + isAlive +
" AND FirstName = @firstName");
sqlCommand.Parameters.AddWithValue("firstName", "Rob");
También lo deja vulnerable en la situación en la que un tipo cambia de un entero a una cadena (piense en el número de empleado que, a pesar de su nombre, puede contener letras).
Entonces, hemos cambiado el tipo de EmployeeNumber de
int
a
string
, pero olvidamos actualizar nuestras consultas sql.
Ups
Cuando utiliza una plataforma fuertemente tipada en una computadora que controla (como un servidor web), puede evitar la inyección de código para consultas con solo valores
bool
,
DateTime
o
int
(y otros valores numéricos).
Lo que es preocupante son los problemas de rendimiento causados por obligar al servidor SQL a volver a compilar cada consulta y evitar que obtenga buenas estadísticas sobre qué consultas se ejecutan con qué frecuencia (lo que perjudica la gestión de la memoria caché).
Pero esa parte de "en una computadora que controlas" es importante, porque de lo contrario un usuario puede cambiar el comportamiento utilizado por el sistema para generar cadenas a partir de esos valores para incluir texto arbitrario.
También me gusta pensar a largo plazo. ¿Qué sucede cuando la base de código fuertemente tipeada de hoy en día se transfiere a través de la traducción automática al nuevo lenguaje dinámico, y de repente pierde la verificación de tipo, pero aún no tiene todas las pruebas unitarias correctas para el código dinámico? ?
Realmente, no hay una buena razón para no usar parámetros de consulta para estos valores. Es la forma correcta de hacerlo. Siga adelante y codifique los valores en la cadena sql cuando realmente sean constantes, pero de lo contrario, ¿por qué no solo usar un parámetro? No es como si fuera difícil.
En última instancia, no llamaría a esto un error , per se, pero lo llamaría un olor : algo que no llega a ser un error en sí mismo, pero es una fuerte indicación de que los errores están cerca, o lo estarán eventualmente. Un buen código evita dejar olores, y cualquier buena herramienta de análisis estático lo marcará.
Agregaré que, desafortunadamente, este no es el tipo de argumento que puedes ganar directamente. Parece una situación en la que "estar en lo correcto" ya no es suficiente, y pisar los pies de tus compañeros de trabajo para solucionar este problema por tu cuenta no es probable que promueva una buena dinámica de equipo; En última instancia, podría doler más de lo que ayuda. Un mejor enfoque en este caso puede ser promover el uso de una herramienta de análisis estático. Eso daría legitimidad y credibilidad a los esfuerzos dirigidos y a retroceder y corregir el código existente.
En algunos casos, ES posible realizar un ataque de inyección SQL con variables no parametrizadas (concatenadas) que no sean valores de cadena; consulte este artículo de Jon: http://codeblog.jonskeet.uk/2014/08/08/the-bobbytables-culture/ .
La cosa es que cuando se llama a
ToString
, algún proveedor de cultura personalizada puede transformar un parámetro que no sea de cadena en su representación de cadena que inyecta algo de SQL en la consulta.
En mi opinión, si puede garantizar que el parámetro con el que trabaja nunca contendrá una cadena, es seguro, pero no lo haría en ningún caso. Además, verá una ligera caída en el rendimiento debido al hecho de que está realizando una concatenación. La pregunta que le haría es por qué no quiere usar parámetros.
En realidad hay dos preguntas en una. Y la pregunta del título tiene muy poco que ver con las preocupaciones expresadas por el OP en los comentarios posteriores.
Aunque me doy cuenta de que para OP es su caso particular lo que importa, para los lectores que vienen de Google, es importante responder a la pregunta más general, que puede expresarse como "es la concatenación tan segura como las declaraciones preparadas si me aseguro que cada literal que estoy concatenando es seguro ". Entonces, me gustaría concentrarme en este último. Y la respuesta es
Definitivamente no.
La explicación no es tan directa como a la mayoría de los lectores les gustaría, pero haré lo mejor que pueda.
He estado reflexionando sobre el asunto por un tiempo, lo que resultó en el article (aunque basado en el entorno PHP) donde intenté resumir todo. Se me ocurrió que la cuestión de la protección contra la inyección SQL a menudo se elude hacia algunos temas relacionados pero más específicos, como el escape de cadenas, la conversión de tipos y demás. Aunque algunas de las medidas pueden considerarse seguras cuando se toman por sí mismas, no existe un sistema ni una regla simple a seguir. Lo que lo hace muy resbaladizo, poniendo demasiada atención y experiencia del desarrollador.
La cuestión de la inyección de SQL no se puede simplificar a una cuestión de sintaxis particular. Es más amplio que el desarrollador promedio solía pensar. Es una pregunta metodológica también. No es solo "qué formato particular tenemos que aplicar", sino también " cómo debe hacerse".
(Desde este punto de vista, un artículo de Jon Skeet citado en la otra respuesta está haciendo más bien que mal, ya que de nuevo se centra en algún caso extremo, concentrándose en un problema de sintaxis particular y no aborda el problema en su totalidad).
Cuando intenta abordar la cuestión de la protección no como un todo sino como un conjunto de problemas de sintaxis diferentes, se enfrenta a una multitud de problemas.
- La lista de posibles opciones de formato es realmente enorme. Significa que uno puede pasar por alto fácilmente algunos. O confúndalos (mediante el uso de cadenas de escape para el identificador, por ejemplo).
-
Concatenación significa que todas las medidas de protección deben ser realizadas por el programador, no por el programa.
Este problema solo lleva a varias consecuencias:
- Tal formato es manual. Manual significa extremadamente propenso a errores. Uno simplemente podría olvidarse de aplicar.
- Además, existe la tentación de trasladar los procedimientos de formateo a alguna función centralizada, lo que complica aún más las cosas y estropea los datos que no van a la base de datos.
- Cuando hay más de un desarrollador involucrado, los problemas se multiplican por un factor de diez.
- cuando se usa la concatenación, no se puede ver una consulta potencialmente peligrosa de un vistazo: ¡ todas son potencialmente peligrosas!
A diferencia de ese desastre, las declaraciones preparadas son de hecho El Santo Grial:
- se puede expresar en la forma de una regla simple que es fácil de seguir.
- es esencialmente una medida inalcanzable, significa que el desarrollador no puede interferir y, voluntaria o involuntariamente, estropea el proceso.
- la protección contra la inyección es realmente solo un efecto secundario de las declaraciones preparadas, cuyo propósito real es producir declaraciones sintácticamente correctas. Y una declaración sintácticamente correcta es 100% a prueba de inyección. Sin embargo, necesitamos que nuestra sintaxis sea correcta a pesar de cualquier posibilidad de inyección.
- Si se usa por completo, protege la aplicación independientemente de la experiencia del desarrollador. Digamos, hay una cosa llamada inyección de segundo orden . Y una ilusión muy fuerte que dice "para proteger, Escape All Input suministrado por el usuario ". Combinados, conducen a la inyección, si un desarrollador se toma la libertad de decidir, qué necesita ser protegido y qué no.
(Pensando más, descubrí que el conjunto actual de marcadores de posición no es suficiente para las necesidades de la vida real y debe ampliarse, tanto para las estructuras de datos complejas, como las matrices, e incluso las palabras clave o identificadores SQL, que a veces deben agregarse al consulta dinámicamente también, pero un desarrollador queda desarmado para tal caso, y se ve obligado a recurrir a la concatenación de cadenas, pero eso es otra cuestión).
Curiosamente, la controversia de esta pregunta es provocada por la naturaleza muy controvertida de . La idea del sitio es hacer uso de preguntas particulares de los usuarios que preguntan directamente para lograr el objetivo de tener una base de datos de respuestas de propósito general adecuada para los usuarios que vienen de la búsqueda . La idea no es mala per se , pero falla en una situación como esta: cuando un usuario hace una pregunta muy limitada , particularmente para discutir en una disputa con un colega (o para decidir si vale la pena refactorizar el código). Si bien la mayoría de los participantes experimentados están tratando de escribir una respuesta, teniendo en cuenta la misión de en general, hacer que su respuesta sea buena para la mayor cantidad de lectores posible, no solo para el OP.
Está bien, pero nunca es seguro ... y la seguridad siempre depende de las entradas, por ejemplo, si el objeto de entrada es TextBox, los atacantes pueden hacer algo complicado ya que el cuadro de texto puede aceptar cadenas, por lo que debe colocar algún tipo de validación / conversión para evitar que los usuarios ingresen incorrectamente. Pero la cosa es que no es seguro. Tan simple como eso.
Esto no es seguro incluso para los tipos sin cadenas. Siempre use parámetros. Período.
Considere el siguiente ejemplo de código:
var utcNow = DateTime.UtcNow;
var sqlCommand = new SqlCommand("SELECT * FROM People WHERE created_on <= ''" + utcNow + "''");
A primera vista, el código parece seguro, pero todo cambia si realiza algunos cambios en la Configuración regional de Windows y agrega la inyección en formato de fecha corta:
Ahora el texto del comando resultante se ve así:
SELECT * FROM People WHERE created_on <= ''26.09.2015'' OR ''1''<>'' 21:21:43''
Se puede hacer lo mismo para el tipo
int
ya que el usuario puede definir signos negativos personalizados que se pueden cambiar fácilmente en inyección SQL.
Se podría argumentar que la cultura invariante debería usarse en lugar de la cultura actual, pero he visto concatenaciones de cuerdas como esta muchas veces y es bastante fácil pasarla por alto al concatenar cadenas con objetos que usan
+
.
No solo pensemos en la seguridad o en las consideraciones de tipo seguro.
La razón por la que usa consultas parametrizadas es para mejorar el rendimiento a nivel de la base de datos. Desde la perspectiva de la base de datos, una consulta parametrizada es una consulta en el búfer de SQL (para usar la terminología de Oracle, aunque imagino que todas las bases de datos tienen un concepto similar internamente). Entonces, la base de datos puede contener una cierta cantidad de consultas en la memoria, preparadas y listas para ejecutarse. No es necesario analizar estas consultas y serán más rápidas. Las consultas que se ejecutan con frecuencia generalmente estarán en el búfer y no necesitarán análisis cada vez que se usen.
A NO SER QUE
Alguien no usa consultas parametrizadas. En este caso, el búfer se vacía continuamente por un flujo de consultas casi idénticas, cada una de las cuales debe ser analizada y ejecutada por el motor de la base de datos y el rendimiento sufre en general, ya que incluso las consultas ejecutadas con frecuencia terminan siendo analizadas muchas veces. día. He ajustado las bases de datos para vivir y esta ha sido una de las mayores fuentes de fruta de bajo perfil.
AHORA
Para responder a su pregunta, SI su consulta tiene un pequeño número de valores numéricos distintos, probablemente no causará problemas y, de hecho, puede mejorar el rendimiento infinitamente. Sin embargo, si hay potencialmente cientos de valores y la consulta se llama mucho, afectará el rendimiento de su sistema, así que no lo haga.
Sí, puede aumentar el búfer de SQL, pero siempre es a expensas de otros usos más críticos para la memoria, como el almacenamiento en caché de índices o datos. Moral, use consultas parametrizadas de manera bastante religiosa para que pueda optimizar su base de datos y usar más memoria del servidor para las cosas que importan ...
No, puede obtener un ataque de inyección SQL de esa manera. He escrito un viejo artículo en turco que muestra cómo here . Ejemplo de artículo en PHP y MySQL pero el concepto funciona igual en C # y SQL Server.
Básicamente atacas de la siguiente manera. Consideremos que tiene una página que muestra información de acuerdo con el valor de identificación de entero. No parametriza esto en valor, como a continuación.
http://localhost/sqlEnjeksiyon//instructors.aspx?id=24
Bien, supongo que estás usando MySQL y ataco de la siguiente manera.
http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII((SELECT%20DATABASE()))
Tenga en cuenta que aquí el valor inyectado no es una cadena. Estamos cambiando el valor de char a int usando la función ASCII. Puede lograr lo mismo en SQL Server usando "CAST (YourVarcharCol AS INT)".
Después de eso, uso las funciones de longitud y subcadena para encontrar el nombre de su base de datos.
http://localhost/sqlEnjeksiyon//instructors.aspx?id=LEN((SELECT%20DATABASE()))
http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII(SUBSTR(SELECT%20DATABASE(),1,1))
Luego, utilizando el nombre de la base de datos, comienza a obtener nombres de tablas en la base de datos.
http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII(SUBSTR((SELECT table_name FROM INFORMATION_SCHEMA.TABLES LIMIT 1),1,1))
Por supuesto, debe automatizar este proceso, ya que solo obtiene UN carácter por consulta. Pero puedes automatizarlo fácilmente. Mi artículo muestra un ejemplo en watir . Usando solo una página y sin valor de ID parametrizado. Puedo aprender cada nombre de tabla en su base de datos. Después de eso puedo buscar tablas importantes. Tomará tiempo pero es factible.
Para agregar alguna información a la respuesta de Maciek:
Es fácil alterar la información cultural de una aplicación de terceros .NET llamando a la función principal del ensamblado por reflexión:
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Assembly asm = Assembly.LoadFile(@"C:/BobbysApp.exe");
MethodInfo mi = asm.GetType("Test").GetMethod("Main");
mi.Invoke(null, null);
Console.ReadLine();
}
static Program()
{
InstallBobbyTablesCulture();
}
static void InstallBobbyTablesCulture()
{
CultureInfo bobby = (CultureInfo)CultureInfo.InvariantCulture.Clone();
bobby.DateTimeFormat.ShortDatePattern = @"yyyy-MM-dd'''' OR '' ''=''''";
bobby.DateTimeFormat.LongTimePattern = "";
bobby.NumberFormat.NegativeSign = "1 OR 1=1 OR 1=";
Thread.CurrentThread.CurrentCulture = bobby;
}
}
}
Esto solo funciona si la función principal de BobbysApp es pública. Si Main no es público, podría haber otras funciones públicas a las que podría llamar.
"SELECT * FROM Table1 WHERE Id =" + intVariable.ToString ()
Seguridad
Está bien.
Los atacantes no pueden inyectar nada en su variable int escrita.
Actuación
No está bien.
Es mejor usar parámetros, por lo que la consulta se compilará una vez y se almacenará en caché para el próximo uso. La próxima vez, incluso con diferentes valores de parámetros, la consulta se almacena en caché y no necesita compilarse en el servidor de bases de datos.
Estilo de codificación
Mala práctica.
- Los parámetros son más legibles
- Tal vez te haga acostumbrarte a consultas sin parámetros, luego tal vez cometiste un error una vez y utilizaste un valor de cadena de esta manera y probablemente deberías decir adiós a tus datos. ¡Mal hábito!
"SELECT * FROM Product WHERE Id =" + TextBox1.Text
Aunque no es su pregunta, puede ser útil para futuros lectores:
Seguridad
¡Desastre!
Incluso cuando el campo
Id
es entero, su consulta puede estar sujeta a inyección de SQL.
Supongamos que tiene una consulta en su aplicación
"SELECT * FROM Table1 WHERE Id=" + TextBox1.Text
, un atacante puede insertar en el cuadro de texto
1; DELETE Table1
1; DELETE Table1
y la consulta será:
"SELECT * FROM Table1 WHERE Id=1; DELETE Table1"
Si no desea usar la consulta parametrizada aquí, debe usar valores escritos:
string.Format("SELECT * FROM Table1 WHERE Id={0}", int.Parse(TextBox1.Text))
Tu pregunta
Mi pregunta surgió porque un compañero de trabajo escribió un montón de consultas que concatenaban valores enteros, y me preguntaba si era una pérdida de tiempo revisarlas y solucionarlas.
Creo que cambiar esos códigos no es una pérdida de tiempo. De hecho, se recomienda el cambio!
si su compañero de trabajo usa variables int, no tiene ningún riesgo de seguridad, pero creo que cambiar esos códigos no es una pérdida de tiempo y, de hecho, se recomienda cambiar esos códigos. Hace que el código sea más legible, más fácil de mantener y hace que la ejecución sea más rápida.