Tenho que me proteger contra injeção de SQL se usar um menu suspenso?

96

Eu entendo que você NUNCA deve confiar na entrada do usuário de um formulário, principalmente devido à chance de injeção de SQL.

No entanto, isso também se aplica a um formulário em que a única entrada é proveniente de uma (s) lista (s) suspensa (s) (veja abaixo)?

Estou salvando o $_POST['size'] em uma sessão que é então usada em todo o site para consultar os vários bancos de dados (com uma mysqliconsulta Select) e qualquer injeção de SQL definitivamente os prejudicaria (possivelmente eliminaria).

Não há área para entrada do usuário digitada para consultar os bancos de dados, apenas dropdown (s).

<form action="welcome.php" method="post">
<select name="size">
  <option value="All">Select Size</option> 
  <option value="Large">Large</option>
  <option value="Medium">Medium</option>
  <option value="Small">Small</option>
</select>
<input type="submit">
</form>
Farrapos
fonte
112
Sim. Nada impede que um invasor envie os valores que desejar em sua <select>entrada. Na verdade, até mesmo um usuário ligeiramente técnico pode adicionar opções adicionais usando o console do navegador. se você mantiver uma lista de permissões de array de valores disponíveis e comparar a entrada com ela, pode mitigar isso (e deve, porque evita valores indesejados)
Michael Berkowski
13
Você deve entender as coisas básicas de solicitação / resposta e a questão de que não importa como o front-end é construído sobre a solicitação, ou seja, neste caso, dropdown
Royal Bg
13
@YourCommonSense Porque é uma boa pergunta. Nem todo mundo percebe como um cliente é manipulável. Isso vai provocar respostas muito valiosas a este site.
Cruncher de
8
@Cruncher entendo. Para um stackoverflowian médio, é uma ciência de foguetes da qual já ouviram falar antes. Mesmo apesar da questão mais votada sob a tag PHP.
Seu senso comum de
9
"Eu entendo que você NUNCA deve confiar na entrada do usuário". Sem exceções.
Prinzhorn de

Respostas:

69

Você pode fazer algo tão simples quanto o exemplo a seguir para garantir que o tamanho postado seja o que você espera.

$possibleOptions = array('All', 'Large', 'Medium', 'Small');

if(in_array($_POST['size'], $possibleOptions)) {
    // Expected
} else {
    // Not Expected
}

Então use mysqli_ * se você estiver usando uma versão do php> = 5.3.0 que você deveria estar, para salvar seu resultado. Se usado corretamente, ajudará na injeção de sql.

Oliver Bayes-Shelton
fonte
esta é uma versão abreviada de uma 'lista branca' OliverBS?
Tatters de
Não apenas uma versão muito básica de um, você pode adicionar os valores em um banco de dados para verificar para torná-lo mais fácil e reutilizável. Ou talvez fazer uma classe de lista branca com um método específico para cada lista de permissão verificar. se você não quiser usar um banco de dados, as propriedades da lista de permissão podem estar dentro de uma propriedade de array em sua classe da lista de permissão.
Oliver Bayes-Shelton de
9
No entanto, você deve usar as declarações preparadas (recomendado) ou mysqli_real_escape_string. Também escape adequadamente os valores ao gerá-los (por exemplo, use htmlspecialchars () em um documento HTML).
ComFreek de
4
Eu sugiro definir o terceiro parâmetro de in_arraypara truepara comparação estrita. Não tenho certeza do que pode dar errado, mas a comparação solta é muito peculiar.
Brilliand de
1
Os valores de @OliverBS $ _POST também podem ser arrays. Sequências numéricas são comparadas como números ('5' == '05'). Não acho que você tenha uma brecha de segurança em seu exemplo específico, mas as regras são complexas e as brechas podem se abrir por motivos que nem entendo. A comparação estrita é mais fácil de raciocinar e, portanto, mais fácil de usar com segurança.
Brilliand
195

Sim, você precisa se proteger contra isso.

Deixe-me mostrar por quê, usando o console de desenvolvedor do Firefox:

Eu editei um dos valores no menu suspenso para ser uma declaração de tabela suspensa

Se você não limpar esses dados, seu banco de dados será destruído. (Esta pode não ser uma instrução SQL totalmente válida, mas espero ter entendido bem.)

Só porque você limitou as opções disponíveis em sua lista suspensa , não significa que você limitou os dados que posso enviar ao seu servidor.

Se você tentou restringir isso ainda mais usando o comportamento em sua página, minhas opções incluem desativar esse comportamento ou apenas escrever uma solicitação HTTP personalizada para seu servidor que imita o envio deste formulário de qualquer maneira. Existe uma ferramenta chamada curl usada exatamente para isso, e acho que o comando para enviar essa injeção de SQL de qualquer maneira seria mais ou menos assim:

curl --data "size=%27%29%3B%20DROP%20TABLE%20*%3B%20--"  http://www.example.com/profile/save

(Este pode não ser um comando curl totalmente válido, mas, novamente, espero ter entendido bem.)

Então, vou reiterar:

NUNCA confie na entrada do usuário. SEMPRE se proteja.

Não presuma que nenhuma entrada do usuário seja segura. É potencialmente inseguro, mesmo se chegar por algum meio diferente de um formulário. Nada disso é confiável o suficiente para deixar de se proteger da injeção de SQL.

doppelgreener
fonte
24
Sem mencionar a criação de uma carga útil personalizada com curl. SEMPRE limpe a entrada do lado do servidor !
recursion.ninja
14
Uma imagem diz mais do que mil palavras.
davidkonrad,
4
Esta deve ser a resposta padrão. Também deve incluir algo sobre curl. As pessoas não entendem que você pode enviar uma solicitação HTTP de qualquer lugar para qualquer lugar usando qualquer formato e passando quaisquer valores, cabe ao servidor verificar se a solicitação é válida antes de processá-la.
retrohacker
2
Que fofo! São pequenas mesas do Bobby!
Aura de
45

Como esta pergunta foi marcada com , aqui está uma resposta sobre este tipo específico de ataque:

Como você foi informado nos comentários, você deve usar instruções preparadas para cada consulta envolvendo qualquer dado variável, sem exceções .

Independentemente de qualquer coisa HTML!
É essencial entender que as consultas SQL devem ser formatadas corretamente, independentemente de quaisquer fatores externos, seja uma entrada HTML ou qualquer outra coisa.

Embora você possa usar a lista de desbloqueio sugerida em outras respostas para o propósito de validação de entrada, ela não deve afetar nenhuma ação relacionada a SQL - elas devem permanecer as mesmas, não importa se você validou a entrada HTML ou não. Isso significa que você ainda deve usar instruções preparadas ao adicionar quaisquer variáveis ​​à consulta.

Aqui você pode encontrar uma explicação completa, por que as instruções preparadas são obrigatórias e como usá-las adequadamente e onde não são aplicáveis ​​e o que fazer nesse caso: O Guia do Mochileiro para proteção de injeção de SQL

Além disso, esta pergunta foi marcada com . Principalmente por acidente, presumo, mas de qualquer forma, devo avisá-lo que mysqli bruto não é uma substituição adequada para as antigas funções mysq_ * . Simplesmente porque, se usado no estilo antigo, não adicionará segurança alguma. Embora seu suporte para as instruções preparadas seja doloroso e problemático, a tal ponto que o usuário médio de PHP é simplesmente incapaz de empreendê-los. Assim, se nenhum ORM ou algum tipo de biblioteca de abstração for opção, então o PDO é sua única escolha.

Seu senso comum
fonte
5
i = aleatório (0,15); // alguma consulta usando i. Ainda preciso de uma declaração preparada aqui?
Cruncher de
2
Qual é a implementação de random?
Slicedpan de
9
@YourCommonSense estreito visualizado? Para mim, "Sempre faça X e não apresentarei nenhuma justificativa para isso" é uma visão limitada.
Cruncher de
5
@Cruncher Não consigo pensar em nenhuma razão para não usar sempre declarações preparadas, toda vez, para tudo, sempre. Você pode me dizer um?
Wesley Murch
3
@Cruncher: Concordo com isso, parece que você está bancando o advogado do diabo. A estipulação na resposta também é "envolvendo quaisquer dados variáveis" . Existem alguns casos como PHP int casting, talvez, mas parece melhor apenas usar instruções preparadas. Dizer (sem experiência) às pessoas que um pequeno "aumento" no desempenho é mais importante do que a segurança é algo errado. A resposta é "incompleta", mas envia uma mensagem forte de que os outros estão ignorando. Vou encerrar meu caso.
Wesley Murch de
12

Sim.

Qualquer um pode falsificar qualquer coisa pelos valores que realmente são enviados -

PORTANTO, para validar menus suspensos, você pode apenas verificar se o valor com o qual está trabalhando estava no menu suspenso - algo como este seria a melhor (mais sensatamente paranóica) maneira:

if(in_array($_POST['ddMenu'], $dropDownValues){

    $valueYouUseLaterInPDO = $dropDownValues[array_search("two", $arr)];

} else {

    die("effin h4x0rs! Keep off my LAMP!!"); 

}
rm-vanda
fonte
5
Esta resposta está incompleta. Além disso, você deve usar declarações preparadas, de modo que a injeção não seja possível, independentemente de como o valor está definido
Slicedpan
7
@Slicedpan Sério? Parece que você está entrando no movimento sem saber por quê ... Se eu tiver um punhado de entradas possíveis em uma consulta, de forma que possa verificar se estão todas bem (o que eu sei, porque eu as fiz), então você não acumula benefícios de segurança adicionais ao usar uma declaração preparada
Cruncher
3
Exceto que impor o uso de instruções preparadas como uma convenção protege você de introduzir vulnerabilidades de injeção SQL no futuro.
Slicedpan de
1
@Cruncher Fazer uma promessa "todos os valores no menu suspenso sempre estarão seguros" é uma promessa muito perigosa de se fazer. Os valores são alterados, o código não é necessariamente atualizado. Quem está atualizando os valores pode nem saber o que é um valor inseguro! Especialmente no domínio da programação web, que está repleto de todos os tipos de questões de segurança, pular em algo como isso é simplesmente irresponsável (e outras palavras menos agradáveis).
hyde de
1
@Cruncher Se você manuseia adequadamente o seu material SQL, pode aceitar qualquer entrada sem interferir no funcionamento adequado do SQL. A parte SQL deve estar preparada e pronta para aceitar qualquer coisa, não importa de onde venha. Todo o resto está sujeito a erros.
glglgl
8

Uma maneira de se proteger contra usuários que alteram seus menus suspensos usando o console é usar apenas valores inteiros neles. Em seguida, você pode validar se o valor POST contém um inteiro e usar uma matriz para convertê-lo em texto quando necessário. Por exemplo:

<?php
// No, you don't need to specify the numbers in the array but as we're using them I always find having them visually there helpful.
$sizes = array(0 => 'All', 1 => 'Large', 2 => 'Medium', 3 => 'Small');
$size = filter_input(INPUT_POST, "size", FILTER_VALIDATE_INT);

echo '<select name="size">';
foreach($sizes as $i => $s) {
    echo '<option value="' . $i . '"' . ($i == $size ? ' selected' : '') . '>' . $s . '</option>';
}
echo '</select>';

Então você pode usar $sizeem sua consulta com o conhecimento de que ela conterá apenas FALSEum número inteiro.

Styphon
fonte
2
@OliverBS O que é filter_inputsenão uma verificação no lado do servidor? Ninguém pode postar nada, exceto um número inteiro.
Styphon
Obrigado Styphon, Então isso substitui o formulário no OP? E isso seria aplicável a formulários maiores com vários menus suspensos? Se eu adicionar outra lista suspensa para dizer 'cor'?
Tatters de
@SamuelTattersfield Sim e sim. Você pode usar isso para quantos menus suspensos desejar, com quantas opções desejar. Basta criar uma nova matriz para cada menu suspenso e inserir todas as opções para o menu suspenso na matriz.
Styphon de
Agradável! Eu gosto disso. Vou tentar e ver o que ganho nos testes.
Tatters de
@SamuelTattersfield Ótimo, apenas certifique-se de usar a filter_inputparte também para a validação, caso contrário, é tão útil quanto um bule de chocolate por segurança.
Styphon de
7

As outras respostas já cobrem o que você precisa saber. Mas talvez ajude a esclarecer um pouco mais:

Existem DUAS COISAS que você precisa fazer:

1. Valide os dados do formulário.

Como a resposta de Jonathan Hobbs mostra muito claramente, a escolha do elemento html para a entrada do formulário não oferece nenhuma filtragem confiável para você.

A validação geralmente é feita de uma forma que não altera os dados, mas que mostra o formulário novamente, com os campos marcados como "Corrija isso".

A maioria das estruturas e CMSs tem construtores de formulários que o ajudam nessa tarefa. E não apenas isso, eles também ajudam contra o CSRF (ou "XSRF"), que é outra forma de ataque.

2. Sanitize / Escape variáveis ​​em instruções SQL.

.. ou deixe que as declarações preparadas façam o trabalho por você.

Se você construir uma instrução (My) SQL com quaisquer variáveis, fornecidas pelo usuário ou não, você precisa escapar e citar essas variáveis.

Geralmente, qualquer variável inserida em uma instrução MySQL deve ser uma string ou algo que o PHP pode ser transformado de forma confiável em uma string que o MySQL pode digerir. Por exemplo, números.

Para strings, você precisa escolher um dos vários métodos para escapar da string, ou seja, substituir quaisquer caracteres que teriam efeitos colaterais no MySQL.

  • No antigo MySQL + PHP, mysql_real_escape_string () faz o trabalho. O problema é que é muito fácil esquecê-lo, então você deve usar instruções preparadas ou construtores de consulta.
  • No MySQLi, você pode usar instruções preparadas.
  • A maioria das estruturas e CMSs fornece construtores de consulta que o ajudam nessa tarefa.

Se estiver lidando com um número, você pode omitir o escape e as aspas (é por isso que as instruções preparadas permitem especificar um tipo).

É importante ressaltar que você escapa das variáveis para a instrução SQL, e NÃO para o próprio banco de dados . O banco de dados armazenará a string original, mas a instrução precisa de uma versão com escape.

O que acontece se você omitir um desses?

Se você não usar a validação de formulário , mas limpar sua entrada SQL, poderá ver todos os tipos de coisas ruins acontecendo, mas não verá injeção de SQL! (*)

Primeiro, ele pode levar seu aplicativo a um estado que você não planejou. Por exemplo, se você deseja calcular a idade média de todos os usuários, mas um usuário deu "aljkdfaqer" para a idade, seu cálculo falhará.

Em segundo lugar, pode haver todos os tipos de outros ataques de injeção que você precisa considerar: Por exemplo, a entrada do usuário pode conter javascript ou outras coisas.

Ainda pode haver problemas com o banco de dados: por exemplo, se um campo (coluna da tabela do banco de dados) estiver limitado a 255 caracteres e a string for maior do que isso. Ou se o campo aceitar apenas números e você tentar salvar uma string não numérica. Mas isso não é "injeção", é apenas "travar o aplicativo".

Mas, mesmo se você tiver um campo de texto livre onde permite qualquer entrada sem nenhuma validação, você ainda pode salvar isso no banco de dados assim, se você escapar apropriadamente quando for para uma instrução de banco de dados. O problema surge quando você deseja usar esta string em algum lugar.

(*) ou isso seria algo realmente exótico.

Se você não escapar variáveis ​​para instruções SQL , mas validar a entrada do formulário, ainda poderá ver coisas ruins acontecendo.

Em primeiro lugar, você corre o risco de que, ao salvar os dados no banco de dados e carregá-los novamente, não sejam mais os mesmos dados, "perdidos na tradução".

Em segundo lugar, isso pode resultar em instruções SQL inválidas e, portanto, travar seu aplicativo. Por exemplo, se qualquer variável contém uma aspa ou caractere de aspas duplas, dependendo do tipo de aspas que você usa, você obterá uma instrução MySQL inválida.

Em terceiro lugar, ele ainda pode causar injeção de SQL.

Se sua entrada de usuário de formulários já estiver filtrada / validada, a injeção SQl intencional pode se tornar menos provável, SE sua entrada for reduzida a uma lista de opções codificada ou se for restrita a números. Mas qualquer entrada de texto livre pode ser usada para injeção de SQL, se você não escapar adequadamente das variáveis ​​nas instruções SQL.

E mesmo que você não tenha nenhuma entrada de formulário, você ainda pode ter strings de todos os tipos de fontes: lidas do sistema de arquivos, copiadas da Internet, etc. Ninguém pode garantir que essas strings sejam seguras.

Don Quixote
fonte
Nenhum dado muito longo, nem uma string no campo numérico, travará nada
Seu senso comum,
hmm, acabei de tentar isso agora e, de fato, mostra um aviso em vez de um erro. Acho que é o PDO que torna isso um erro. Tenho certeza de que discuti uma dúzia de questões no drupal.org em que algumas strings são muito longas para um varchar.
donquixote
6

Seu navegador não "sabe" que está recebendo uma página do php, tudo o que vê é html. E a camada http sabe ainda menos do que isso. Você precisa ser capaz de lidar com quase qualquer tipo de entrada que possa cruzar a camada http (felizmente, para a maioria das entradas, o php já apresentará um erro). Se você está tentando evitar que solicitações maliciosas bagunçam seu banco de dados, então você precisa assumir que o cara do outro lado sabe o que está fazendo, e que ele não está limitado ao que você pode ver em seu navegador em circunstâncias normais ( sem mencionar o que você pode mexer com as ferramentas de desenvolvedor de um navegador). Então, sim, você precisa atender a qualquer entrada de sua lista suspensa, mas para a maioria das entradas, você pode fornecer um erro.

Inominável
fonte
6

O fato de você ter restringido o usuário a usar apenas valores de uma determinada lista suspensa é irrelevante. Um usuário técnico pode capturar a solicitação http enviada ao seu servidor antes que ela saia da rede, alterá-la usando uma ferramenta como um servidor proxy local e, em seguida, continuar seu caminho. Usando a solicitação alterada, eles podem enviar valores de parâmetro que não sejam aqueles que você especificou na lista suspensa. Os desenvolvedores devem ter a mentalidade de que as restrições do cliente geralmente não fazem sentido, pois qualquer coisa em um cliente pode ser alterada. A validação do servidor é necessária em cada ponto em que os dados do cliente entram. Os invasores contam com a ingenuidade dos desenvolvedores apenas neste aspecto.

Jason Higgins
fonte
5

É melhor usar uma consulta parametrizada para evitar a injeção de SQL. Nesse caso, a aparência da consulta seria esta:

SELECT * FROM table WHERE size = ?

Quando você fornece uma consulta como a acima com texto cuja integridade não foi verificada (a entrada não é validada no servidor) e contém código de injeção SQL, ela será tratada corretamente. Em outras palavras, a solicitação resultará em algo assim acontecendo na camada do banco de dados:

SELECT * FROM table WHERE size = 'DROP table;'

Isso simplesmente selecionará 0 resultados conforme retorna, o que tornará a consulta ineficaz em realmente causar danos ao banco de dados sem a necessidade de uma lista de permissões, verificação ou outras técnicas. Observe que um programador responsável fará a segurança em camadas e frequentemente fará a validação, além de parametrizar as consultas. No entanto, há muito poucos motivos para não parametrizar suas consultas de uma perspectiva de desempenho e a segurança adicionada por esta prática é um bom motivo para se familiarizar com as consultas parametrizadas.

Dan Smith
fonte
4

O que quer que seja enviado do seu formulário chega ao servidor como texto através dos fios. Não há nada que impeça ninguém de criar um bot para imitar o cliente ou digitá-lo em um terminal, se quiserem. Nunca presuma que, por ter programado o cliente, ele agirá como você pensa que fará. Isso é realmente fácil de falsificar.

Exemplo do que pode e vai acontecer quando você confia no cliente.

GenericJam
fonte
4

Um hacker pode ignorar o navegador completamente, incluindo a verificação de formulário Javascript, enviando uma solicitação usando Telnet. Claro, ele vai olhar o código da sua página html para obter os nomes dos campos que precisa usar, mas a partir de então é 'vale tudo' para ele. Portanto, você deve verificar todos os valores enviados no servidor como se eles não tivessem origem na sua página html.

user1452686
fonte