Como faço para criar uma consulta SQL parametrizada? Por que eu deveria?

94

Ouvi dizer que "todos" estão usando consultas SQL parametrizadas para se proteger contra ataques de injeção de SQL, sem ter que validar cada entrada do usuário.

Como você faz isso? Você obtém isso automaticamente ao usar procedimentos armazenados?

Então, meu entendimento é não parametrizado:

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

Isso seria parametrizado?

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

Ou preciso fazer algo mais extenso como esse para me proteger da injeção de SQL?

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

Existem outras vantagens em usar consultas parametrizadas além das considerações de segurança?

Atualização: Este ótimo artigo foi linkado em uma das referências de questões por Grotok. http://www.sommarskog.se/dynamic_sql.html

Jim Counts
fonte
Achei chocante que, aparentemente, essa pergunta não tenha sido feita no Stackoverflow antes. Muito bom!
Tamas Czinege
3
Oh, sim. Com palavras muito diferentes, é claro, mas foi.
Joel Coehoorn
10
Você deve usar consulta parametrizada para evitar que Little Bobby Tables destrua seus dados. Não pude resistir :)
zendar
4
O que há de tão ruim no bloco With?
Lurker De fato
1
Alguém tem uma pergunta # para a pergunta "O que há de tão ruim no bloco With"?
Jim Counts

Respostas:

77

Seu exemplo EXEC NÃO seria parametrizado. Você precisa de consultas parametrizadas (declarações preparadas em alguns círculos) para evitar que uma entrada como esta cause danos:

'; DROP TABLE bar; -

Tente colocar isso em sua variável fuz (ou não, se você valoriza sua mesa de bar). Perguntas mais sutis e prejudiciais também são possíveis.

Aqui está um exemplo de como você faz parâmetros com o 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

Os procedimentos armazenados às vezes são creditados com a prevenção da injeção de SQL. No entanto, na maioria das vezes, você ainda precisa chamá-los usando parâmetros de consulta ou eles não ajudam. Se você usa procedimentos armazenados exclusivamente , pode desativar as permissões para SELECT, UPDATE, ALTER, CREATE, DELETE, etc (quase tudo, exceto EXEC) para a conta de usuário do aplicativo e obter alguma proteção dessa forma.

Joel Coehoorn
fonte
Você pode explicar melhor isso, cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Bazpor favor?
Cary Bondoc
1
@CaryBondoc, o que você quer saber? Essa linha cria um parâmetro chamado @Bazque é do tipo varchar(50)que é atribuído ao valor da Bazstring.
JB King
você também pode dizer "command.parameters.addiwthvalue (" @ Baz ", 50)"
Gavin Perkins de
2
@GavinPerkins Supondo que você quisesse dizer AddWithValue("@Baz", Baz), você poderia fazer isso, mas não deveria , especialmente porque converter valores de string que mapeiam por padrão nvarcharpara o varchartipo real é um dos lugares mais comuns que podem desencadear os efeitos mencionados naquele link.
Joel Coehoorn
15

Definitivamente o último, ou seja

Ou eu preciso fazer algo mais extenso ...? (Sim, cmd.Parameters.Add())

As consultas parametrizadas têm duas vantagens principais:

  • Segurança: é uma boa maneira de evitar vulnerabilidades de injeção SQL
  • Desempenho: Se você invocar regularmente a mesma consulta apenas com parâmetros diferentes, uma consulta parametrizada pode permitir que o banco de dados armazene em cache suas consultas, o que é uma fonte considerável de ganho de desempenho.
  • Extra: você não terá que se preocupar com problemas de formatação de data e hora em seu código de banco de dados. Da mesma forma, se o seu código for executado em máquinas com um local diferente do inglês, você não terá problemas com casas decimais / vírgulas decimais.
Tamas Czinege
fonte
5

Você quer seguir com seu último exemplo, pois este é o único que está verdadeiramente parametrizado. Além das questões de segurança (que são muito mais prevalentes do que você imagina), é melhor deixar o ADO.NET lidar com a parametrização, pois você não pode ter certeza se o valor que está passando requer aspas simples ou não sem inspecionar o Typede cada parâmetro .

[Editar] Aqui está um exemplo:

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
fonte
3
Tenha cuidado com isso: as strings .Net são unicode e, portanto, o parâmetro assumirá NVarChar por padrão. Se for realmente uma coluna VarChar, isso pode causar grandes problemas de desempenho.
Joel Coehoorn
2

A maioria das pessoas faria isso por meio de uma biblioteca de linguagem de programação do lado do servidor, como PDO de PHP ou Perl DBI.

Por exemplo, no PDO:

$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*/}

Isso evita o escape de seus dados para inserção no banco de dados.

Uma vantagem é que você pode repetir uma inserção muitas vezes com uma instrução preparada, obtendo uma vantagem de velocidade.

Por exemplo, na consulta acima, eu poderia preparar a instrução uma vez e, em seguida, fazer um loop na criação do array de dados a partir de um monte de dados e repetir o -> execute quantas vezes forem necessárias.

JAL
fonte
1

Seu texto de comando deve ser como:

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

cmdText = "EXEC foo_from_baz ?"

Em seguida, adicione valores de parâmetro. Desta forma, garante que o valor con acabe sendo usado apenas como um valor, enquanto com o outro método se a variável fuz for definida como

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

você pode ver o que pode acontecer?

Tony Andrews
fonte
0

Aqui está uma curta aula para começar com SQL e você pode construir a partir daí e adicionar à classe.

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
fonte