¿Cómo creo una consulta SQL parametrizada? ¿Por qué debería?

94

Escuché que "todo el mundo" está utilizando consultas SQL parametrizadas para protegerse contra ataques de inyección SQL sin tener que validar cada pieza de entrada del usuario.

¿Cómo haces esto? ¿Obtiene esto automáticamente cuando usa procedimientos almacenados?

Entonces, según tengo entendido, esto no está parametrizado:

cmdText = String.Format("SELECT foo FROM bar WHERE baz = '{0}'", fuz)

¿Se parametrizaría esto?

cmdText = String.Format("EXEC foo_from_baz '{0}'", fuz)

¿O necesito hacer algo más extenso como esto para protegerme de la inyección SQL?

With command
    .Parameters.Count = 1
    .Parameters.Item(0).ParameterName = "@baz"
    .Parameters.Item(0).Value = fuz
End With

¿Existen otras ventajas de utilizar consultas parametrizadas además de las consideraciones de seguridad?

Actualización: Este gran artículo fue vinculado en una de las preguntas de referencia de Grotok. http://www.sommarskog.se/dynamic_sql.html

Jim cuenta
fuente
Me pareció impactante que aparentemente esta pregunta no se haya hecho antes en Stackoverflow. ¡Muy buena!
Tamas Czinege
3
Oh, lo ha hecho. Dicho de manera muy diferente, por supuesto, pero lo ha hecho.
Joel Coehoorn
10
Debe utilizar una consulta parametrizada para evitar que Little Bobby Tables destruya sus datos. No pude resistir :)
zendar
4
¿Qué tiene de malo el bloque With?
Lurker Indeed
1
¿Alguien tiene una pregunta # para la pregunta "¿Qué tiene de malo el bloque With"?
Jim Counts

Respuestas:

77

Su ejemplo EXEC NO sería parametrizado. Necesita consultas parametrizadas (declaraciones preparadas en algunos círculos) para evitar que entradas como esta causen daños:

'; Barra DROP TABLE; -

Intente poner eso en su variable fuz (o no lo haga, si valora su mesa de bar). También son posibles consultas más sutiles y perjudiciales.

Aquí hay un ejemplo de cómo se hacen los parámetros con Sql Server:

Public Function GetBarFooByBaz(ByVal Baz As String) As String
    Dim sql As String = "SELECT foo FROM bar WHERE baz= @Baz"

    Using cn As New SqlConnection("Your connection string here"), _
        cmd As New SqlCommand(sql, cn)

        cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz
        Return cmd.ExecuteScalar().ToString()
    End Using
End Function

A los procedimientos almacenados a veces se les atribuye la prevención de la inyección de SQL. Sin embargo, la mayoría de las veces todavía tiene que llamarlos usando parámetros de consulta o no ayudan. Si utiliza procedimientos almacenados exclusivamente , puede desactivar los permisos para SELECCIONAR, ACTUALIZAR, ALTERAR, CREAR, ELIMINAR, etc. (casi todo menos EXEC) para la cuenta de usuario de la aplicación y obtener algo de protección de esa manera.

Joel Coehoorn
fuente
¿Puede explicar esto con más detalle, cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Bazpor favor?
Cary Bondoc
1
@CaryBondoc, ¿qué quieres saber? Esa línea crea un parámetro llamado @Bazque es del tipo al varchar(50)que se le asigna el valor de la Bazcadena.
JB King
también podría decir "command.parameters.addiwthvalue (" @ Baz ", 50)"
Gavin Perkins
2
@GavinPerkins Suponiendo que quiso decir AddWithValue("@Baz", Baz), podría hacer eso, pero no debería hacerlo , especialmente porque convertir los valores de cadena que se asignan de forma predeterminada al tipo nvarcharreal varchares uno de los lugares más comunes que pueden desencadenar los efectos mencionados en ese enlace.
Joel Coehoorn
15

Definitivamente el último, es decir

¿O necesito hacer algo más extenso ...? (Si, cmd.Parameters.Add())

Las consultas parametrizadas tienen dos ventajas principales:

  • Seguridad: es una buena forma de evitar vulnerabilidades de inyección SQL
  • Rendimiento: si invoca regularmente la misma consulta solo con diferentes parámetros, una consulta parametrizada podría permitir que la base de datos almacene en caché sus consultas, lo que es una fuente considerable de ganancia de rendimiento.
  • Extra: no tendrá que preocuparse por problemas de formato de fecha y hora en el código de su base de datos. De manera similar, si su código alguna vez se ejecutará en máquinas con una configuración regional que no sea en inglés, no tendrá problemas con los puntos decimales / comas decimales.
Tamas Czinege
fuente
5

Desea ir con su último ejemplo, ya que este es el único que está realmente parametrizado. Además de las preocupaciones de seguridad (que son mucho más frecuentes de lo que podría pensar) es mejor dejar que ADO.NET maneje la parametrización, ya que no puede estar seguro de si el valor que está pasando requiere comillas simples alrededor de él o no sin inspeccionar el Typede cada parámetro. .

[Editar] Aquí hay un ejemplo:

SqlCommand command = new SqlCommand(
    "select foo from bar where baz = @baz",
    yourSqlConnection
);

SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "@baz";
parameter.Value = "xyz";

command.Parameters.Add(parameter);
Andrew Hare
fuente
3
Tenga cuidado con esto: las cadenas .Net son Unicode, por lo que el parámetro asumirá NVarChar por defecto. Si realmente es una columna VarChar, esto puede causar grandes problemas de rendimiento.
Joel Coehoorn
2

La mayoría de la gente haría esto a través de una biblioteca de lenguaje de programación del lado del servidor, como PDO de PHP o Perl DBI.

Por ejemplo, en DOP:

$dbh=pdo_connect(); //you need a connection function, returns a pdo db connection

$sql='insert into squip values(null,?,?)';

$statement=$dbh->prepare($sql);

$data=array('my user supplied data','more stuff');

$statement->execute($data);

if($statement->rowCount()==1){/*it worked*/}

Esto se encarga de escapar sus datos para la inserción de la base de datos.

Una ventaja es que puede repetir una inserción muchas veces con una declaración preparada, obteniendo una ventaja de velocidad.

Por ejemplo, en la consulta anterior, podría preparar la declaración una vez, y luego recorrer la creación de la matriz de datos a partir de un montón de datos y repetir -> ejecutar tantas veces como sea necesario.

JAL
fuente
1

Su texto de comando debe ser como:

cmdText = "SELECT foo FROM bar WHERE baz = ?"

cmdText = "EXEC foo_from_baz ?"

Luego agregue los valores de los parámetros. De esta manera se asegura que el valor con solo termine usándose como valor, mientras que con el otro método si la variable fuz se establece en

"x'; delete from foo where 'a' = 'a"

¿Puedes ver lo que podría pasar?

Tony Andrews
fuente
0

Aquí hay una clase corta para comenzar con SQL y puede construir desde allí y agregar a la clase.

MySQL

Public Class mysql

    'Connection string for mysql
    Public SQLSource As String = "Server=123.456.789.123;userid=someuser;password=somesecurepassword;database=somedefaultdatabase;"

    'database connection classes

    Private DBcon As New MySqlConnection
    Private SQLcmd As MySqlCommand
    Public DBDA As New MySqlDataAdapter
    Public DBDT As New DataTable
    Public BindSource As New BindingSource
    ' parameters
    Public Params As New List(Of MySqlParameter)

    ' some stats
    Public RecordCount As Integer
    Public Exception As String

    Function ExecScalar(SQLQuery As String) As Long
        Dim theID As Long
        DBcon.ConnectionString = SQLSource
        Try
            DBcon.Open()
            SQLcmd = New MySqlCommand(SQLQuery, DBcon)
            'loads params into the query
            Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))

            'or like this is also good
            'For Each p As MySqlParameter In Params
            ' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
            ' Next
            ' clears params
            Params.Clear()
            'return the Id of the last insert or result of other query
            theID = Convert.ToInt32(SQLcmd.ExecuteScalar())
            DBcon.Close()

        Catch ex As MySqlException
            Exception = ex.Message
            theID = -1
        Finally
            DBcon.Dispose()
        End Try
        ExecScalar = theID
    End Function

    Sub ExecQuery(SQLQuery As String)

        DBcon.ConnectionString = SQLSource
        Try
            DBcon.Open()
            SQLcmd = New MySqlCommand(SQLQuery, DBcon)
            'loads params into the query
            Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))

            'or like this is also good
            'For Each p As MySqlParameter In Params
            ' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
            ' Next
            ' clears params

            Params.Clear()
            DBDA.SelectCommand = SQLcmd
            DBDA.Update(DBDT)
            DBDA.Fill(DBDT)
            BindSource.DataSource = DBDT  ' DBDT will contain your database table with your records
            DBcon.Close()
        Catch ex As MySqlException
            Exception = ex.Message
        Finally
            DBcon.Dispose()
        End Try
    End Sub
    ' add parameters to the list
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New MySqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub
End Class

MS SQL / Express

Public Class MSSQLDB
    ' CREATE YOUR DB CONNECTION
    'Change the datasource
    Public SQLSource As String = "Data Source=someserver\sqlexpress;Integrated Security=True"
    Private DBCon As New SqlConnection(SQLSource)

    ' PREPARE DB COMMAND
    Private DBCmd As SqlCommand

    ' DB DATA
    Public DBDA As SqlDataAdapter
    Public DBDT As DataTable

    ' QUERY PARAMETERS
    Public Params As New List(Of SqlParameter)

    ' QUERY STATISTICS
    Public RecordCount As Integer
    Public Exception As String

    Public Sub ExecQuery(Query As String, Optional ByVal RunScalar As Boolean = False, Optional ByRef NewID As Long = -1)
        ' RESET QUERY STATS
        RecordCount = 0
        Exception = ""
        Dim RunScalar As Boolean = False

        Try
            ' OPEN A CONNECTION
            DBCon.Open()

            ' CREATE DB COMMAND
            DBCmd = New SqlCommand(Query, DBCon)

            ' LOAD PARAMS INTO DB COMMAND
            Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))

            ' CLEAR PARAMS LIST
            Params.Clear()

            ' EXECUTE COMMAND & FILL DATATABLE
            If RunScalar = True Then
                NewID = DBCmd.ExecuteScalar()
            End If
            DBDT = New DataTable
            DBDA = New SqlDataAdapter(DBCmd)
            RecordCount = DBDA.Fill(DBDT)
        Catch ex As Exception
            Exception = ex.Message
        End Try


        ' CLOSE YOUR CONNECTION
        If DBCon.State = ConnectionState.Open Then DBCon.Close()
    End Sub

    ' INCLUDE QUERY & COMMAND PARAMETERS
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New SqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub
End Class
Chillzy
fuente