¿Por qué siempre preferimos usar parámetros en declaraciones SQL?

114

Soy muy nuevo en el trabajo con bases de datos. Ahora puedo escribir SELECT, UPDATE, DELETE, y INSERTcomandos. Pero he visto muchos foros donde preferimos escribir:

SELECT empSalary from employee where salary = @salary

...en vez de:

SELECT empSalary from employee where salary = txtSalary.Text

¿Por qué siempre preferimos usar parámetros y cómo los usaría?

Quería conocer el uso y los beneficios del primer método. Incluso he oído hablar de la inyección SQL pero no lo entiendo del todo. Ni siquiera sé si la inyección SQL está relacionada con mi pregunta.

Arenoso
fuente
2
Tienes razón, esto está relacionado con la inyección SQL. La forma de tratar los parámetros suele ser responsabilidad de cualquier lenguaje / marco que esté ejecutando su programa, y ​​puede depender del idioma. Publique tanto su RDBMS (útil) como el marco ORM (necesario).
Clockwork-Muse
1
Estoy usando C # como lenguaje de programación y Sql Server 2008 como base de datos. Estoy usando Microsoft dotNet framework 4.0. Realmente lo siento mucho, no estoy seguro de lo que está preguntando (RDBMS u ORM), tal vez pueda darme mis versiones de marco RDBMS y ORM ahora :-). Muchas gracias
Sandy
1
RDBMS es su base de datos, en su caso SQL Server 2008. Su ORM es el método por el cual accede a su base de datos, en este caso, ADO.NET. Otros incluyen LINQ to SQL y Entity Framework . De hecho, una vez que aprenda los conceptos básicos de ADO.NET y SQL, le recomiendo usar un ORM como LINQ o EF, ya que se encargan de muchos de los problemas que encontrará al escribir SQL manualmente.
Chad Levy

Respuestas:

129

El uso de parámetros ayuda a prevenir ataques de inyección de SQL cuando la base de datos se utiliza junto con una interfaz de programa, como un programa de escritorio o un sitio web.

En su ejemplo, un usuario puede ejecutar directamente código SQL en su base de datos creando declaraciones en formato txtSalary.

Por ejemplo, si tuvieran que escribir 0 OR 1=1, el SQL ejecutado sería

 SELECT empSalary from employee where salary = 0 or 1=1

por lo que todos los empSalaries serían devueltos.

Además, un usuario podría ejecutar comandos mucho peores en su base de datos, incluida su eliminación si escribiera 0; Drop Table employee:

SELECT empSalary from employee where salary = 0; Drop Table employee

employeeLuego, la tabla se eliminaría.


En su caso, parece que está utilizando .NET. Usar parámetros es tan fácil como:

C#

string sql = "SELECT empSalary from employee where salary = @salary";

using (SqlConnection connection = new SqlConnection(/* connection info */))
using (SqlCommand command = new SqlCommand(sql, connection))
{
    var salaryParam = new SqlParameter("salary", SqlDbType.Money);
    salaryParam.Value = txtMoney.Text;

    command.Parameters.Add(salaryParam);
    var results = command.ExecuteReader();
}

VB.NET

Dim sql As String = "SELECT empSalary from employee where salary = @salary"
Using connection As New SqlConnection("connectionString")
    Using command As New SqlCommand(sql, connection)
        Dim salaryParam = New SqlParameter("salary", SqlDbType.Money)
        salaryParam.Value = txtMoney.Text

        command.Parameters.Add(salaryParam)

        Dim results = command.ExecuteReader()
    End Using
End Using

Editar 2016-4-25:

Según el comentario de George Stocker, cambié el código de muestra para que no lo use AddWithValue. Además, generalmente se recomienda que incluya IDisposables en usingdeclaraciones.

Chad Levy
fuente
gran solucion. Pero, ¿puede explicar un poco más por qué y cómo es seguro utilizar parámetros? Quiero decir, todavía parece que el comando sql será el mismo
Sandy
¿Podemos agregar múltiples parámetros al comando sql? ¿Como podemos requerir en un comando INSERTAR?
Sandy
2
SQL Server trata el texto dentro de los parámetros solo como entrada y nunca lo ejecutará.
Chad Levy
3
Sí, se puede añadir varios parámetros: Insert Into table (Col1, Col2) Values (@Col1, @Col2). En su código, agregaría varios AddWithValues.
Chad Levy
1
¡No utilice AddWithValue! Puede causar problemas de conversión implícitos. Establezca siempre el tamaño explícitamente y agregue el valor del parámetro con parameter.Value = someValue.
George Stocker
75

Tiene razón, esto está relacionado con la inyección SQL , que es una vulnerabilidad que permite a un usuario malicioso ejecutar declaraciones arbitrarias en su base de datos. Este cómic XKCD favorito de antaño ilustra el concepto:

Su hija se llama Ayuda. Estoy atrapada en una fábrica de licencias de conducir.


En su ejemplo, si solo usa:

var query = "SELECT empSalary from employee where salary = " + txtSalary.Text;
// and proceed to execute this query

Estás abierto a la inyección SQL. Por ejemplo, digamos que alguien ingresa txtSalary:

1; UPDATE employee SET salary = 9999999 WHERE empID = 10; --
1; DROP TABLE employee; --
// etc.

Cuando ejecute esta consulta, realizará un SELECTy un UPDATEo DROP, o lo que quieran. El --al final simplemente comenta el resto de su consulta, lo que sería útil en el ataque si estuviera concatenando algo después txtSalary.Text.


La forma correcta es utilizar consultas parametrizadas, por ejemplo (C #):

SqlCommand query =  new SqlCommand("SELECT empSalary FROM employee 
                                    WHERE salary = @sal;");
query.Parameters.AddWithValue("@sal", txtSalary.Text);

Con eso, puede ejecutar la consulta de forma segura.

Para obtener referencia sobre cómo evitar la inyección de SQL en varios otros idiomas, consulte bobby-tables.com , un sitio web mantenido por un usuario de SO .

NullUserException
fuente
1
gran solucion. Pero, ¿puede explicar un poco más por qué y cómo es seguro utilizar parámetros? Quiero decir que todavía parece que el comando sql será el mismo.
Sandy
1
@ user815600: un error común: todavía cree que la consulta con parámetros tomará el valor y sustituirá los parámetros por los valores reales, ¿verdad? ¡No, esto no está sucediendo! - en cambio, la declaración SQL con parámetros se transmitirá a SQL Server, junto con una lista de parámetros y sus valores - la declaración SQL no será la misma
marc_s
1
eso significa que la inyección de SQL está siendo monitoreada por un mecanismo interno o de seguridad del servidor de SQL. Gracias.
Sandy
4
Por mucho que me gusten los dibujos animados, si está ejecutando su código con privilegios suficientes para eliminar tablas, probablemente tenga problemas más amplios.
philw
9

Además de otras respuestas, es necesario agregar que los parámetros no solo ayudan a prevenir la inyección de SQL, sino que también pueden mejorar el rendimiento de las consultas . El servidor SQL almacena en caché los planes de consulta parametrizados y los reutiliza en la ejecución de consultas repetidas. Si no parametrizó su consulta, el servidor sql compilaría un nuevo plan en cada ejecución de la consulta (con alguna exclusión) si el texto de la consulta fuera diferente.

Más información sobre el almacenamiento en caché del plan de consultas

Oleg
fuente
1
Esto es más pertinente de lo que uno podría pensar. Incluso una consulta "pequeña" se puede ejecutar miles o millones de veces, vaciando efectivamente toda la caché de consultas.
James
5

Dos años después de mi primer intento , estoy reincidiendo ...

¿Por qué preferimos los parámetros? La inyección de SQL es obviamente una gran razón, pero podría ser que en secreto anhelemos volver a SQL como lenguaje . SQL en cadenas literales ya es una práctica cultural extraña, pero al menos puede copiar y pegar su solicitud en Management Studio. SQL construido dinámicamente con condicionales del lenguaje host y estructuras de control, cuando SQL tiene condicionales y estructuras de control, es simplemente una barbarie de nivel 0. Tienes que ejecutar tu aplicación en depuración, o con un seguimiento, para ver qué SQL genera.

No se detenga solo con los parámetros. Vaya hasta el final y use QueryFirst (descargo de responsabilidad: que escribí). Tu SQL vive en un archivo .sql. Lo edita en la fabulosa ventana del editor TSQL, con validación de sintaxis e Intellisense para sus tablas y columnas. Puede asignar datos de prueba en la sección de comentarios especiales y hacer clic en "reproducir" para ejecutar su consulta allí mismo en la ventana. Crear un parámetro es tan fácil como poner "@myParam" en su SQL. Luego, cada vez que guarde, QueryFirst genera el contenedor de C # para su consulta. Sus parámetros aparecen, fuertemente tipados, como argumentos para los métodos Execute (). Sus resultados se devuelven en un IEnumerable o Lista de POCO fuertemente tipados, los tipos generados a partir del esquema real devuelto por su consulta. Si su consulta no se ejecuta, su aplicación no se compilará. Si su esquema de base de datos cambia y su consulta se ejecuta pero algunas columnas desaparecen, el error de compilación apunta a la línea en su códigoque intenta acceder a los datos faltantes. Y hay muchas otras ventajas. ¿Por qué querría acceder a los datos de otra manera?

bbsimonbb
fuente
4

En Sql, cuando cualquier palabra contiene el signo @, significa que es variable y usamos esta variable para establecer un valor en ella y usarla en el área numérica en el mismo script sql porque solo está restringido en un único script, mientras que puede declarar muchas variables del mismo tipo y nombre en muchos guiones. Usamos esta variable en el lote de procedimientos almacenados porque los procedimientos almacenados son consultas precompiladas y podemos pasar valores en estas variables desde el script, el escritorio y los sitios web para obtener más información, lea Declarar variable local , Procedimiento almacenado SQL e inyecciones SQL .

También lea Protect from sql injection , le guiará sobre cómo puede proteger su base de datos.

Espero que te ayude a entender también cualquier pregunta comentenme.

Emaad Ali
fuente
3

Otras respuestas cubren por qué los parámetros son importantes, ¡pero hay una desventaja! En .net, existen varios métodos para crear parámetros (Add, AddWithValue), pero todos requieren que se preocupe, innecesariamente, por el nombre del parámetro, y todos reducen la legibilidad del SQL en el código. Justo cuando intentas meditar en SQL, necesitas buscar por encima o por debajo para ver qué valor se ha utilizado en el parámetro.

Humildemente afirmo que mi pequeña clase SqlBuilder es la forma más elegante de escribir consultas parametrizadas . Tu código se verá así ...

C#

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName);
myCommand.CommandText = bldr.ToString();

Su código será más corto y mucho más legible. Ni siquiera necesita líneas adicionales y, cuando está leyendo, no necesita buscar el valor de los parámetros. La clase que necesitas está aquí ...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

public class SqlBuilder
{
private StringBuilder _rq;
private SqlCommand _cmd;
private int _seq;
public SqlBuilder(SqlCommand cmd)
{
    _rq = new StringBuilder();
    _cmd = cmd;
    _seq = 0;
}
public SqlBuilder Append(String str)
{
    _rq.Append(str);
    return this;
}
public SqlBuilder Value(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append(paramName);
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public SqlBuilder FuzzyValue(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append("'%' + " + paramName + " + '%'");
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public override string ToString()
{
    return _rq.ToString();
}
}
bbsimonbb
fuente
Nombrar sus parámetros ciertamente ayuda a la hora de perfilar las consultas que está ejecutando el servidor.
Dave R.
Mi jefe dijo lo mismo. Si los nombres de parámetros significativos son importantes para usted, agregue un argumento paramName al método de valor. Sospecho que está complicando las cosas innecesariamente.
bbsimonbb
Mala idea. Como se dijo antes, AddWithValuepuede causar problemas de conversión implícita.
Adam Calvet Bohl
@Adam, tienes razón, pero eso no impide que AddWithValue () sea muy utilizado, y no creo que invalide la idea. Pero mientras tanto, se me ocurrió una forma mucho mejor de escribir consultas parametrizadas, y eso no usa AddWithValue () :-)
bbsimonbb
¡Correcto! ¡Prometo que lo revisaré pronto!
Adam Calvet Bohl
3

Publicación anterior, pero quería asegurarse de que los recién llegados conozcan los procedimientos almacenados .

Mi valor de 10 ¢ aquí es que si puede escribir su declaración SQL como un procedimiento almacenado , en mi opinión, ese es el enfoque óptimo. Yo SIEMPRE utilizar procedimientos almacenados y nunca se bucle a través de los registros en mi código principal. Por ejemplo: SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class.

Cuando utiliza procedimientos almacenados, puede restringir al usuario a EJECUTAR solo el permiso, lo que reduce los riesgos de seguridad. .

Su procedimiento almacenado está intrínsecamente paramerizado y puede especificar parámetros de entrada y salida.

Se SELECTpuede acceder al procedimiento almacenado (si devuelve datos a través de una declaración) y leerlo exactamente de la misma manera que lo haría con unSELECT declaración en su código.

También se ejecuta más rápido ya que se compila en SQL Server.

¿También mencioné que puede hacer varios pasos, por ejemplo, updateuna tabla, verificar valores en otro servidor de base de datos y luego, una vez que finalmente haya terminado, devolver los datos al cliente, todo en el mismo servidor y sin interacción con el cliente? Entonces esto es MUCHO más rápido que codificar esta lógica en su código.

Arvin Amir
fuente