Então você acha que seu banco de dados SQL tem bom desempenho e está protegido contra destruição instantânea? Bem, SQL Injection discorda!
Sim, estamos falando de destruição instantânea, porque não quero abrir este artigo com a terminologia esfarrapada usual de “reforçar a segurança” e “impedir o acesso malicioso”. SQL Injection é um truque tão antigo no livro que todo mundo, todo desenvolvedor, conhece muito bem e sabe como evitá-lo. Exceto por aquele momento estranho em que eles escorregam, e os resultados podem ser nada menos que desastrosos.
Se você já sabe o que é SQL Injection, fique à vontade para pular para a segunda metade do artigo. Mas para aqueles que estão começando no campo do desenvolvimento web e sonham em assumir funções mais importantes, alguma introdução é necessária.
últimas postagens
O que é SQL Injection?
A chave para entender SQL Injection está em seu nome: SQL + Injection. A palavra “injeção” aqui não tem nenhuma conotação médica, mas sim o uso do verbo “injetar”. Juntas, essas duas palavras transmitem a ideia de colocar o SQL em um aplicativo da web.
Colocando SQL em um aplicativo da web. . . hmmm . . . Não é isso que estamos fazendo de qualquer maneira? Sim, mas não queremos que um invasor controle nosso banco de dados. Vamos entender isso com a ajuda de um exemplo.
Digamos que você esteja construindo um site PHP típico para uma loja de comércio eletrônico local, então decide adicionar um formulário de contato como este:
<form action="record_message.php" method="POST"> <label>Your name</label> <input type="text" name="name"> <label>Your message</label> <textarea name="message" rows="5"></textarea> <input type="submit" value="Send"> </form>
E vamos supor que o arquivo send_message.php armazene tudo em um banco de dados para que os donos da loja possam ler as mensagens do usuário posteriormente. Pode ter algum código como este:
<?php $name = $_POST['name']; $message = $_POST['message']; // check if this user already has a message mysqli_query($conn, "SELECT * from messages where name = $name"); // Other code here
Então você está primeiro tentando ver se este usuário já tem uma mensagem não lida. A consulta SELECT * de mensagens onde nome = $nome parece bastante simples, certo?
ERRADO!
Em nossa inocência, abrimos as portas para a destruição instantânea de nosso banco de dados. Para que isso aconteça, o invasor deve ter as seguintes condições atendidas:
- O aplicativo está sendo executado em um banco de dados SQL (hoje, quase todos os aplicativos são)
- A conexão de banco de dados atual tem permissões de “editar” e “excluir” no banco de dados
- Os nomes das tabelas importantes podem ser adivinhados
O terceiro ponto significa que, agora que o invasor sabe que você está executando uma loja de comércio eletrônico, é muito provável que você esteja armazenando os dados do pedido em uma tabela de pedidos. Armado com tudo isso, tudo o que o invasor precisa fazer é fornecer isso como seu nome:
Joe; ordens truncadas;? Sim senhor! Vamos ver como a consulta se tornará quando for executada pelo script PHP:
SELECT * FROM mensagens WHERE nome = Joe; ordens truncadas;
Ok, a primeira parte da consulta tem um erro de sintaxe (sem aspas em torno de “Joe”), mas o ponto e vírgula força o mecanismo do MySQL a começar a interpretar um novo: truncar ordens. Assim, de uma só vez, todo o histórico de pedidos desaparece!
Agora que você sabe como o SQL Injection funciona, é hora de ver como pará-lo. As duas condições que precisam ser atendidas para uma injeção de SQL bem-sucedida são:
Evitando a injeção de SQL no PHP
Agora, dado que conexões de banco de dados, consultas e entradas do usuário fazem parte da vida, como evitamos a injeção de SQL? Felizmente, é bem simples e há duas maneiras de fazer isso: 1) higienizar a entrada do usuário e 2) usar instruções preparadas.
Higienize a entrada do usuário
Se você estiver usando uma versão mais antiga do PHP (5.5 ou inferior, e isso acontece muito em hospedagem compartilhada), é aconselhável executar todas as entradas do usuário por meio de uma função chamada mysql_real_escape_string(). Basicamente, o que ele faz é remover todos os caracteres especiais de uma string para que eles percam o significado quando usados pelo banco de dados.
Por exemplo, se você tiver uma string como I’m a string, o caractere de aspas simples (‘) pode ser usado por um invasor para manipular a consulta do banco de dados que está sendo criada e causar uma injeção de SQL. Executá-lo através de mysql_real_escape_string() produz que sou uma string, que adiciona uma barra invertida às aspas simples, escapando dela. Como resultado, toda a string agora é passada como uma string inofensiva para o banco de dados, em vez de poder participar da manipulação da consulta.
Há uma desvantagem nessa abordagem: é uma técnica muito, muito antiga que acompanha as formas mais antigas de acesso ao banco de dados em PHP. A partir do PHP 7, essa função nem existe mais, o que nos leva à nossa próxima solução.
Usar declarações preparadas
As instruções preparadas são uma maneira de fazer consultas ao banco de dados com mais segurança e confiabilidade. A ideia é que, em vez de enviar a consulta bruta ao banco de dados, primeiro informamos ao banco de dados a estrutura da consulta que enviaremos. Isso é o que queremos dizer com “preparar” uma declaração. Uma vez preparada uma declaração, passamos as informações como entradas parametrizadas para que o banco de dados possa “preencher as lacunas” conectando as entradas à estrutura de consulta que enviamos anteriormente. Isso tira qualquer poder especial que as entradas possam ter, fazendo com que sejam tratadas como meras variáveis (ou cargas úteis, se preferir) em todo o processo. Veja como são as declarações preparadas:
<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } // prepare and bind $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // set parameters and execute $firstname = "John"; $lastname = "Doe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Mary"; $lastname = "Moe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Julie"; $lastname = "Dooley"; $email = "[email protected]"; $stmt->execute(); echo "New records created successfully"; $stmt->close(); $conn->close(); ?>
Eu sei que o processo parece desnecessariamente complexo se você for novo em declarações preparadas, mas o conceito vale o esforço. aqui está uma boa introdução a ele.
Para aqueles que já estão familiarizados com a extensão PDO do PHP e a utilizam para criar declarações preparadas, tenho um pequeno conselho.
Aviso: Tenha cuidado ao configurar o PDO
Ao usar o PDO para acesso ao banco de dados, podemos ser sugados por uma falsa sensação de segurança. “Ah, bem, estou usando DOP. Agora não preciso pensar em mais nada” — é assim que nosso pensamento geralmente funciona. É verdade que o PDO (ou declarações preparadas pelo MySQLi) é suficiente para evitar todos os tipos de ataques de injeção de SQL, mas você deve ter cuidado ao configurá-lo. É comum apenas copiar e colar o código de tutoriais ou de seus projetos anteriores e seguir em frente, mas essa configuração pode desfazer tudo:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
O que essa configuração faz é dizer ao PDO para emular instruções preparadas em vez de realmente usar o recurso de instruções preparadas do banco de dados. Conseqüentemente, o PHP envia strings de consulta simples para o banco de dados, mesmo que seu código pareça estar criando declarações preparadas e definindo parâmetros e tudo isso. Em outras palavras, você está tão vulnerável à injeção de SQL quanto antes. 🙂
A solução é simples: verifique se essa emulação está definida como falsa.
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Agora o script PHP é forçado a usar instruções preparadas em um nível de banco de dados, impedindo todos os tipos de injeção de SQL.
Prevenção usando WAF
Você sabia que também pode proteger aplicativos da Web contra injeção de SQL usando WAF (firewall de aplicativo da Web)?
Bem, não apenas a injeção de SQL, mas muitas outras vulnerabilidades da camada 7, como script entre sites, autenticação quebrada, falsificação entre sites, exposição de dados etc.
Injeção de SQL e estruturas PHP modernas
A injeção de SQL é tão comum, tão fácil, tão frustrante e tão perigosa que todos os frameworks web PHP modernos vêm com contramedidas integradas. No WordPress, por exemplo, temos a função $wpdb->prepare(), ao passo que se você estiver usando um framework MVC, ele faz todo o trabalho sujo para você e você nem precisa pensar em impedir a injeção de SQL. É um pouco chato que no WordPress você tenha que preparar declarações explicitamente, mas ei, é do WordPress que estamos falando. 🙂
De qualquer forma, o que quero dizer é que a geração moderna de desenvolvedores da Web não precisa pensar em injeção de SQL e, como resultado, eles nem estão cientes da possibilidade. Dessa forma, mesmo que eles deixem um backdoor aberto em seu aplicativo (talvez seja um parâmetro de consulta $_GET e os velhos hábitos de disparar uma consulta suja), os resultados podem ser catastróficos. Portanto, é sempre melhor reservar um tempo para mergulhar mais fundo nas fundações.
Conclusão
SQL Injection é um ataque muito desagradável em um aplicativo da web, mas é facilmente evitado. Como vimos neste artigo, ter cuidado ao processar a entrada do usuário (aliás, SQL Injection não é a única ameaça que o manuseio da entrada do usuário traz) e consultar o banco de dados é tudo o que há para fazer. Dito isso, nem sempre estamos trabalhando na segurança de um framework web, então é melhor ficar atento a esse tipo de ataque e não cair nessa.