Dinamicamente criado SQL vs Parâmetros no SQL Server
-
05-07-2019 - |
Pergunta
Se eu selecionar uma linha de uma tabela que tem basicamente duas opções, ou como este
int key = some_number_derived_from_a_dropdown_or_whatever
SqlCommand cmd = new SqlCommand("select * from table where primary_key = " + key.ToString());
ou usar um parâmetro
SqlCommand cmd = new SqlCommand("select * from table where primary_key = @pk");
SqlParameter param = new SqlParameter();
param.ParameterName = "@pk";
param.Value = some_number_derived_from_a_dropdown_or_whatever;
cmd.Parameters.Add(param);
Agora, eu sei que o primeiro método é desaprovado por causa de possíveis ataques de injeção SQL, mas neste caso o parâmetro é um inteiro e, portanto, não deveria ser possível injetar código malicioso.
A minha pergunta é esta: Do que você use a opção 1 no código de produção, porque você considerar o seguro uso por causa da facilidade de uso e controle sobre o parâmetro inserido (como o acima, ou se o parâmetro é criado no código)? Ou você sempre usar parâmetros não importa o quê? São parâmetros 100% seguro injeção?
Solução
eu vou pular o argumento de injeção SQL, que é muito bem conhecida e se concentrar apenas no aspecto SQL de parâmetros versus não parâmetros.
Quando você enviar um lote SQL para o servidor, qualquer lote, tem que ser analisado para ser compreendido. Como qualquer outro compilador, o compilador SQL tem de produzir um AST do texto e, em seguida, operar em a árvore de sintaxe. Em última análise, o otimizador transforma a árvore de sintaxe em uma árvore de execução e, finalmente, produz um plano de execução e que é realmente executado. Voltar na idade das trevas de circa 1995 ele fez a diferença se o lote foi uma consulta Ad-Hoc ou um procedimento armazenado, mas hoje não faz absolutamente nenhum, todos eles o mesmo.
Agora, onde parâmetros fazer a diferença é que um cliente que envia uma consulta como select * from table where primary_key = @pk
irá enviar exatamente o mesmo texto SQL todas as vezes, não importa o valor é interessado. O que acontece então é que o inteira processo que eu descrevi acima é curto-circuito. SQL irá procurar na memória um plano de execução para a matéria, não analisada, texto que recebeu (com base em um hash digerir da entrada) e, se encontrado, irá executar esse plano. Isso significa que nenhum de análise, sem otimização, nada, o lote vai em linha reta em execução . Em sistemas OLTP que rodam centenas e milhares de pequenos pedidos a cada segundo, esse caminho rápido faz uma diferença enorme desempenho.
Se você enviar a consulta no select * from table where primary_key = 1
forma, em seguida, SQL terá que pelo menos analisá-lo para entender o que está dentro do texto, já que o texto é provável um novo, diferente de qualquer lote anterior lo visto (mesmo um único caractere como 1
vs 2
faz com que todo o lote diferente). Ela irá então operar na árvore de sintaxe resultou e tentar um processo chamado Parametrização simples . Se a consulta pode ser paremeterized-auto, em seguida, SQL provavelmente vai encontrar um plano de execução em cache para ele de outras consultas que são executadas anteriormente com outros valores pk e reutilizar esse plano, por isso, pelo menos sua consulta não precisa ser otimizado e você pular o o passo de gerar um plano de execução real. Mas não média você conseguiu a curto-circuito completo, o caminho mais curto possível você conseguir com uma verdadeira consulta parametrizada cliente.
Você pode olhar para o SQL Server, SQL Estatísticas Objeto contador de desempenho do seu servidor. O contador Auto-Param Attempts/sec
mostrará muitas vezes por segundo SQL tem que traduzir uma consulta recebida sem parâmetros em um único parametrizado automaticamente. Cada tentativa pode ser evitado se você parametrizar corretamente a consulta no cliente. Se você também tem um elevado número de Failed Auto-Params/sec
é ainda pior, isso significa que as consultas estão indo o ciclo completo de otimização e plano de execução geração.
Outras dicas
Use sempre a opção 2).
O primeiro é não segura sobre ataques de injeção SQL .
O segundo é não só muito mais seguro, mas também terá um desempenho melhor porque o otimizador de consulta tem melhores chances de criar um bom plano de consulta para ele, porque os olhares de consulta o mesmo todo o tempo, esperamos que os parâmetros do curso.
Por que você deve evitar Opção 1
Mesmo que pareça que você está recebendo este valor de elemento select
, alguém pode fingir um pedido HTTP e pós o que quiserem dentro de determinados campos. Seu código na opção 1 deve, pelo menos, substituir alguns caracteres / combinações perigosas (ie. Aspas simples, colchetes etc.).
Por que você está encorajado a usar a opção 2
mecanismo de consulta SQL é capaz de armazenar em cache plano de execução e gestão de estatísticas para várias consultas lançadas contra ele. consultas para ter unificadas (o melhor seria, evidentemente, ter um procedimento armazenado em separado) acelera a execução no longo prazo.
Eu ainda tenho que ouvir de qualquer exemplo onde os parâmetros podem ser sequestrado por injeção de SQL. Até eu ver que se prove o contrário, eu consideraria-los seguros.
SQL dinâmico nunca deve ser considerado seguro. Mesmo com "confiança" de entrada, é um mau hábito de entrar. Note que isso inclui SQL dinâmica em procedimentos armazenados; injeção de SQL pode ocorrer lá também.
Porque você tem o controle que o valor é um número inteiro, eles são praticamente equivalentes. I geralmente não utilizar a primeira forma, e normalmente, não permitem que o segundo tanto, porque geralmente não permitem acesso à tabela e requerem o uso de procedimentos armazenados. E, embora você pode fazer a execução SP sem usar a coleção de parâmetros, eu ainda recomendo usar SPs e parâmetros:
-- Not vulnerable to injection as long as you trust int and int.ToString()
int key = some_number_derived_from_a_dropdown_or_whatever ;
SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString());
-- Vulnerable to injection all of a sudden
string key = some_number_derived_from_a_dropdown_or_whatever ;
SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString());
Note que, embora a opção 1 é seguro em seu caso , o que acontece quando alguém vê e usa essa técnica com uma variável não-inteiro - agora você está treinando as pessoas a usar uma técnica que poderia ser aberto a injeção.
Note que você pode proativamente endurecer o seu banco de dados para injeção evitar ser eficaz mesmo quando o código do aplicativo está com defeito. Para contas / papéis que os aplicativos usam:
- Apenas permitir o acesso a tabelas como absolutamente necessário
- Só permitir o acesso a pontos de vista como absolutamente necessário
- Não permita instruções DDL
- Permitir executar para SPs em base papel
- Qualquer SQL dinâmico em SPs devem ser revistos como absolutamente necessário
Pessoalmente, eu sempre utilizar a opção 2 como uma questão de hábito. Dito isto:
Uma vez que você está forçando a conversão no menu suspenso valor para um int, que irá fornecer alguma proteção contra ataques de injeção SQL. Ele alguém tenta injetar informações adicionais para as informações publicadas, C # irá lançar uma exceção ao tentar converter o código malicioso em um valor inteiro frustrando a tentativa.