sábado, 24 de outubro de 2015

André Alves Lima - Parâmetros do ADO.NET: por favor, pare de concatenar strings nas suas sentenças SQL!


Eu tenho certeza que, em alguma parte do código das suas aplicações, você já precisou realizar alguma consulta no seu banco de dados, não é mesmo? Isso é uma tarefa tão comum que acabamos nem nos dando conta se estamos fazendo da forma correta.
Ao realizarmos consultas utilizando as classes base do ADO.NET (DbConnection, DbCommand, etc), é muito fácil expormos a nossa aplicação a SQL injection. Fazemos isso ao concatenar strings quando precisamos passar algum valor externo para as nossas consultas. Hoje você aprenderá o jeito certo de se passar valores a comandos do ADO.NET: utilizando parâmetros!

O cenário de exemplo

Para entendermos exatamente o que estou querendo dizer, suponha que temos uma tabela muito simples no nosso banco de dados, chamada “ExemploTabela“:
1
2
3
4
5
6
CREATE TABLE ExemploTabela
(
    ID INT IDENTITY(1,1) NOT NULL,
    Descricao VARCHAR(50) NOT NULL,
    CONSTRAINT PK_ExemploTabela PRIMARY KEY CLUSTERED (ID ASC) ON [PRIMARY]
) ON [PRIMARY]
1
2
3
INSERT INTO ExemploTabela (Descricao) VALUES ('André Alves de Lima');
INSERT INTO ExemploTabela (Descricao) VALUES ('Fulano de Tal');
INSERT INTO ExemploTabela (Descricao) VALUES ('Ciclano Beltrano');
Em uma aplicação do tipo “Windows Forms Application“, vamos ajustar um formulário para que ele fique parecido com a imagem abaixo:
Como você pode ver, temos um TextBox (pesquisaTextBox), um botão (pesquisarButton) e um DataGridView (pesquisaDataGridView). A ideia é que, no clique no botão, vamos fazer uma pesquisa na tabela “ExemploTabela” utilizando o conteúdo do TextBox.

O jeito errado: concatenando strings

Antes de mostrarmos o jeito certo de se fazer consultas desse tipo, vamos ver como 90% das pessoas fazem: do jeito errado. Não me canso de ver pessoas nos fóruns da MSDN postando código concatenando strings nas consultas! Isso abre o seu sistema para uma infinidade de problemas, inclusive SQL injection, como eu mencionei no início desse artigo.
Enfim, o jeito errado seria algo como isto:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void pesquisarButton_Click(object sender, EventArgs e)
{
    using (var conn = new System.Data.SqlClient.SqlConnection(@"Server=seuserver;Database=seubanco;User Id=seuuser;Password=suasenha;"))
    {
        conn.Open();
 
        using (var comm = conn.CreateCommand())
        {
            comm.CommandText = "SELECT * FROM ExemploTabela WHERE Descricao LIKE '%" + pesquisaTextBox.Text + "%'";
            var dataTable = new DataTable();
            using (var reader = comm.ExecuteReader())
            {
                dataTable.Load(reader);
            }
            pesquisaDataGridView.DataSource = dataTable;
        }
    }
}
Esse código aparentemente funciona:
Mas, quer ver alguns dos problemas que ele pode trazer? Imagine que o usuário queira pesquisar algum nome que tenha apóstrofe no meio. Por exemplo: “O’neal”. Qual o resultado? Exception!
Mas, isso não é o pior! Está preparado para ficar chocado? Suponha que algum usuário mal-intencionado saiba que você tem essa brecha no seu sistema. Sabe o que esse usuário mal-intencionado consegue fazer? Ter acesso completo ao seu banco de dados, inclusive deletar tabelas se ele quiser!
Veja só. Imagine que você tenha uma outra tabela no seu banco de dados, chamada “OutraTabela“. Se o usuário mal-intencionado utilizar a seguinte string de busca na sua aplicação: “fula’; DROP TABLE OutraTabela;–“, ele vai conseguir deletar essa outra tabela sem muito esforço! Com o ponto e vírgula ele finaliza o comando anterior (o “SELECT” que faz a busca na tabela “ExemploTabela“) e, em seguida, adiciona o comando de “DROP” para excluir a outra tabela. Entendeu a gravidade dessa situação?

O jeito certo: parâmetros do ADO.NET

Agora que você já viu o perigo de se concatenar strings em queries do ADO.NET, que tal aprender o jeito certo de passar valores para as suas sentenças? Desde a primeira versão, os comandos do ADO.NET suportam o que chamamos de parâmetros. Os parâmetros nada mais são que uma maneira de disponibilizarmos valores para uma sentença, de forma que os valores sejam passados para a query de forma segura.
Por exemplo, se o parâmetro é do tipo string, não precisamos nos preocupar com a questão da apóstrofe (nem aspas simples), uma vez que o ADO.NET vai formatar o valor corretamente de forma que o comando seja executado sem problemas. Outro exemplo é a questão de datas. Dependendo do idioma do SQL Server, você precisa passar o formato de data de maneiras diferentes, senão a sentença não é executada corretamente. Ao utilizar os parâmetros do ADO.NET, você não precisa se preocupar mais com formato de data. O ADO.NET se encarrega de tudo.
E como é que fazemos para utilizar os parâmetros nas nossas sentenças com o ADO.NET? Simples, veja só:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void pesquisarButton_Click(object sender, EventArgs e)
{
    using (var conn = new System.Data.SqlClient.SqlConnection(@"Server=alves01dbserver\sql2012;Database=ExemploDB;User Id=sa;Password=savcor;"))
    {
        conn.Open();
 
        using (var comm = conn.CreateCommand())
        {
            comm.CommandText = "SELECT * FROM ExemploTabela WHERE Descricao LIKE @Descricao";
            comm.Parameters.AddWithValue("@Descricao", string.Format("%{0}%", pesquisaTextBox.Text));
            var dataTable = new DataTable();
            using (var reader = comm.ExecuteReader())
            {
                dataTable.Load(reader);
            }
            pesquisaDataGridView.DataSource = dataTable;
        }
    }
}
Como você pode ver, ao invés de concatenar o valor no meio da query, você deve dar um nome para o parâmetro (no exemplo acima utilizei “@Descricao“). Depois, é só utilizar a propriedade Parameters do comando e chamar o método “AddWithValue“, passando o nome que você definiu para o parâmetro e o valor desejado.
Um único detalhe: a sintaxe para a definição do parâmetro no meio da sentença varia de provider para provider. O exemplo acima funciona para o provider do SQL Server (System.Data.SqlClient). O provider OleDb, por exemplo, não aceita parâmetros nomeados. Ao invés de definir um nome para o parâmetro, você precisa colocar “?” (ponto de interrogação) no lugar onde o valor deve ser colocado. Veja como ficaria o mesmo exemplo caso estivéssemos trabalhando com um banco de dados do Microsoft Access:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void pesquisarButton_Click(object sender, EventArgs e)
{
    using (var conn = new System.Data.OleDb.OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\myFolder\myAccessFile.accdb;Persist Security Info=False;"))
    {
        conn.Open();
 
        using (var comm = conn.CreateCommand())
        {
            comm.CommandText = "SELECT * FROM ExemploTabela WHERE Descricao LIKE ?";
            comm.Parameters.AddWithValue("Descricao", string.Format("%{0}%", pesquisaTextBox.Text));
            var dataTable = new DataTable();
            using (var reader = comm.ExecuteReader())
            {
                dataTable.Load(reader);
            }
            pesquisaDataGridView.DataSource = dataTable;
        }
    }
}
Para encontrar a referência de outros providers, busque no Google por “xxxCommand parameter” (por exemplo, MySqlCommand parameter ou NpgsqlCommand parameter).

Concluindo

SQL injection é um problema muito grave, mas, infelizmente, deixado de lado pela maioria dos programadores. Não seja um desses programadores.
Nesse artigo você aprendeu o porquê você não deve concatenar strings nas suas sentenças SQL e como fazer para passar valores sem ficar concatenando strings, utilizando a funcionalidade de parâmetros do ADO.NET.
E você, já realizou consultas da maneira errada? Eu já. Mas, não tem problema. O que importa é que você aprendeu agora a maneira correta de passar valores para as suas sentenças. Só me prometa que você nunca mais vai concatenar valores nas suas queries a partir de hoje, OK?
Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado, ficará sabendo em primeira mão sobre o artigo da próxima semana e receberá também dicas “bônus” que eu só compartilho por e-mail. Além disso, você já deve ter percebido que eu recebo muitas sugestões de temas e eu costumo dar prioridade às sugestões vindas de inscritos da minha newsletter. Inscreva-se utilizando o formulário logo abaixo.
Até a próxima!

André Lima

Nenhum comentário:

Postar um comentário