Por que não fazer com que consultas não parametrizadas retornem um erro?


22

A injeção de SQL é um problema de segurança muito sério, em grande parte porque é muito fácil cometer erros: a maneira óbvia e intuitiva de criar uma consulta incorporando a entrada do usuário deixa você vulnerável, e a maneira certa de atenuá-lo exige que você conheça os parâmetros consultas e injeção de SQL primeiro.

Parece-me que a maneira óbvia de corrigir isso seria encerrar a opção óbvia (mas errada): conserte o mecanismo do banco de dados para que qualquer consulta recebida que use valores codificados na sua cláusula WHERE em vez de parâmetros retorne uma descrição agradável e descritiva mensagem de erro instruindo você a usar parâmetros. Obviamente, seria necessário ter uma opção de exclusão para que coisas como consultas ad-hoc de ferramentas administrativas ainda sejam executadas com facilidade, mas devem ser ativadas por padrão.

Isso interromperia a injeção de SQL a frio, quase da noite para o dia, mas até onde eu sei, nenhum RDBMS realmente faz isso. Existe alguma boa razão para não?


22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'teria valores codificados e parametrizados em uma única consulta - tente entender isso! Eu acho que existem casos de uso válidos para essas consultas mistas.
amon

6
Que tal selecionar registros a partir de hojeSELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee

10
@MasonWheeler desculpe, eu quis dizer "tente permitir isso". Observe que é perfeitamente parametrizado e não sofre injeção de SQL. No entanto, o driver do banco de dados não pode dizer se o literal "bad"é realmente literal ou se resultou da concatenação de cadeias. As duas soluções que vejo são livrar-se do SQL e outras DSLs incorporadas a cadeias de caracteres (sim, por favor) ou promover linguagens em que a concatenação de cadeias de caracteres é mais irritante do que usar consultas parametrizadas (umm, não).
11115 amon

4
e como o RDBMS detectaria se deveria fazer isso? Durante a noite, seria impossível acessar o RDBMS usando um prompt SQL interativo ... Você não seria mais capaz de inserir comandos DDL ou DML usando qualquer ferramenta.
jwenting

8
De certa forma, você pode fazer isso: não construa consultas SQL em tempo de execução, use um ORM ou alguma outra camada de abstração que evite a necessidade de construir consultas SQL. ORM não tem os recursos que você precisa? Então o SQL é uma linguagem destinada a pessoas que desejam escrever SQL, e é por isso que, no geral, permite que elas escrevam SQL. A questão fundamental é que gerar código dinamicamente é mais difícil do que parece, mas as pessoas querem fazê-lo de qualquer maneira e ficarão insatisfeitas com produtos que não permitem.
21815 Steve Jobs (

Respostas:


45

Existem muitos casos em que o uso de um literal é a abordagem correta.

Do ponto de vista de desempenho, há momentos em que você deseja literais em suas consultas. Imagine que eu tenho um rastreador de bugs onde, quando ele fica grande o suficiente para se preocupar com o desempenho, espero que 70% dos bugs do sistema sejam "fechados", 20% serão "abertos", 5% estarão "ativos" e 5 % estará em algum outro status. Talvez eu queira razoavelmente ter a consulta que retorna todos os erros ativos

SELECT *
  FROM bug
 WHERE status = 'active'

em vez de passar a statuscomo uma variável de ligação. Eu quero um plano de consulta diferente, dependendo do valor transferido status- eu gostaria de fazer uma varredura de tabela para retornar os bugs fechados e uma varredura de índice no diretóriostatuscoluna para retornar os empréstimos ativos. Agora, bancos de dados diferentes e versões diferentes têm abordagens diferentes para (com mais ou menos êxito) permitir que a mesma consulta use um plano de consulta diferente, dependendo do valor da variável de ligação. Mas isso tende a introduzir uma quantidade decente de complexidade que precisa ser gerenciada para equilibrar a decisão de se preocupar em analisar novamente uma consulta ou em reutilizar um plano existente para um novo valor de variável de ligação. Para um desenvolvedor, pode fazer sentido lidar com essa complexidade. Ou talvez faça sentido forçar um caminho diferente quando eu tiver mais informações sobre a aparência dos meus dados do que o otimizador.

Do ponto de vista da complexidade do código, também há muitas vezes que faz todo sentido ter literais nas instruções SQL. Por exemplo, se você tem uma zip_codecoluna com um CEP de 5 caracteres e às vezes possui 4 dígitos adicionais, faz todo o sentido fazer algo como

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

em vez de passar 4 parâmetros separados para os valores numéricos. Essas não são coisas que jamais mudarão, portanto, fazê-las vincular variáveis ​​serve apenas para tornar o código potencialmente mais difícil de ler e criar o potencial de alguém vincular parâmetros na ordem errada e acabar com um bug.


12

A injeção de SQL ocorre quando uma consulta é criada concatenando o texto de uma fonte não confiável e não validada com outras partes de uma consulta. Embora tal coisa ocorra com mais frequência com literais de strings, essa não seria a única maneira de ocorrer. Uma consulta para valores numéricos pode levar uma string inserida pelo usuário (que é deveria conter apenas dígitos) e concatenar com outro material para formar uma consulta sem as aspas normalmente associadas aos literais da string; o código que confia demais na validação do lado do cliente pode ter coisas como nomes de campo provenientes de uma string de consulta HTML. Não há como o código olhar para uma string de consulta SQL ver como ela foi montada.

O importante não é se uma instrução SQL contém literais de cadeias, mas se uma sequência contém seqüências de caracteres de fontes não confiáveis e a validação para isso seria melhor manipulada na biblioteca que cria consultas. Geralmente, não há como o C # escrever código que permita uma literal de string, mas não permita outros tipos de expressão de string, mas pode-se ter uma regra de práticas de codificação que exige que as consultas sejam construídas usando uma classe de construção de consulta em vez de concatenação de string e qualquer pessoa que passe uma string não literal para o construtor de consultas deve justificar essa ação.


1
Como uma aproximação para "é literal", você pode verificar se a sequência está internada.
CodesInChaos

1
@CodesInChaos: True, e esse teste pode ser preciso o suficiente para esse fim, desde que qualquer pessoa que tenha um motivo para gerar uma string em tempo de execução use um método que aceite uma string não literal em vez de internar a string gerada em tempo de execução e usar isso (atribuir um nome diferente ao método que não seja literal) tornaria mais fácil para os revisores de código inspecionar todos os usos dele.
Supercat

Observe que, embora não exista maneira de fazer isso em C #, alguns outros idiomas têm recursos que possibilitam isso (por exemplo, o módulo de string contaminado do Perl).
Jules

Mais sucintamente, esse é um problema do cliente , não do servidor.
Blrfl 13/08/2015

7
SELECT count(ID)
FROM posts
WHERE deleted = false

Se você quiser colocar os resultados no rodapé do seu fórum, será necessário adicionar um parâmetro fictício apenas para dizer falso sempre. Ou o ingênuo programador da Web pesquisa como desativar esse aviso e continua.

Agora você pode dizer que adicionaria uma exceção para enums, mas isso apenas abre o buraco novamente (embora menor). Sem mencionar que as pessoas primeiro precisam ser educadas para não usarvarchars -las.

O verdadeiro problema da injeção é construir programaticamente a string de consulta. A solução para isso é um mecanismo de procedimento armazenado e forçar seu uso ou uma lista de permissões de consultas permitidas.


2
Se a sua solução para "é muito fácil esquecer - ou não saber em primeiro lugar - usar consultas parametrizadas" é "fazer todo mundo lembrar - e saber em primeiro lugar - usar procs armazenados", então você está faltando o ponto inteiro da questão.
Mason Wheeler

5
Eu vi injeção de SQL através de procedimentos armazenados no meu trabalho. Acontece exigir procedimentos armazenados para tudo é ruim. Sempre há 0,5% de consultas dinâmicas verdadeiras (você não pode parametrizar uma cláusula where inteira, muito menos uma junção de tabela).
Joshua

No exemplo desta resposta, você pode substituir deleted = falsepor NOT deleted, o que evita o literal. Mas o ponto é válido em geral.
Psmears 13/08/2015

5

TL; DR : Você precisaria restringir todos os literais, não apenas osWHERE cláusulas. Por razões que não o fazem, ele permite que o banco de dados permaneça dissociado de outros sistemas.

Em primeiro lugar, sua premissa é falha. Você deseja restringir apenas WHEREcláusulas, mas esse não é o único local para entrada do usuário. Por exemplo,

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Isso é igualmente vulnerável à injeção de SQL:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Portanto, você não pode restringir literais na WHEREcláusula. Você tem que restringir tudo literais.

Agora ficamos com a pergunta: "Por que permitir literais?" Lembre-se disso: embora os bancos de dados relacionais sejam usados ​​sob um aplicativo gravado em outro idioma em uma grande porcentagem do tempo, não é necessário que você use o código do aplicativo para usar o banco de dados. E aqui temos uma resposta: você precisa de literais para escrever código. A única outra alternativa seria exigir que todo o código fosse escrito em algum idioma independente do banco de dados. Portanto, tê-los oferece a capacidade de escrever "código" (SQL) diretamente no banco de dados. Este é um desacoplamento valioso e seria impossível sem literais. (Tente escrever em seu idioma favorito em algum momento sem literais. Tenho certeza de que você pode imaginar o quão difícil isso seria.)

Como um exemplo comum, os literais são frequentemente usados ​​na população de tabelas de lista de valores / consulta:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

Sem eles, você precisaria escrever código em outra linguagem de programação apenas para preencher esta tabela. A capacidade de fazer isso diretamente no SQL é valiosa .

Ficamos então com mais uma pergunta: por que as bibliotecas cliente de linguagem de programação não o fazem? E aqui temos uma resposta muito simples: eles teriam que reimplementar o analisador de banco de dados inteiro para cada versão suportada do banco de dados . Por quê? Porque não há outra maneira de garantir que você encontrou todos os literais. Expressões regulares não são suficientes. Por exemplo: este contém 4 literais separados no PostgreSQL:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

Tentar fazer isso seria um pesadelo de manutenção, especialmente porque a sintaxe válida geralmente muda entre as principais versões dos bancos de dados.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.