Introdução
Boa noite, boa tarde ou bom dia pessoal ! Hoje como pedido pela leitora Camila, falarei um pouco sobre a técnica de invasão feita por "injeção de SQL", popularmente conhecido como SQL Injection. PS: fica a dica para quem quer pedir tema, eu escuto seus pedidos viu!
Começarei pelas tentativas mais comuns, e em quais áreas do site e no final do post eu irei ensinar como evitar que isso aconteça no PHP, que é atualmente a linguagem WEB mais utilizada, e talvez a que mais vacilam com essas brechas de segurança ! Ja mexi em muitos sistemas antigos que, por não serem atualizados, fizeram clientes perderem databases inteiros de dados importantes. =/
No more talk, mãos a obra.
O que é SQL Injection?
O SQL Injection, como já diz o nome, vai se aproveitar da maneira como o SQL funciona e alterar a string de busca com algum input do usuário. Literalmente falando, o usuário irá injetar um código sql dentro do seu sql.
Como isso? vamos começar pelo exemplo abaixo de uma "query normal".
SELECT * FROM `USERS` WHERE `PASS`='$senha' AND `EMAIL`='$email';
-- considere $senha e $email, duas variáveis que vem do PHP
-- e são concatenadas à nossa string de pesquisa
Nessa string de pesquisa não existe nenhum erro, quando o usuário entrar com email e senha dele, nós iremos buscar no banco se existe algum usuario e senha com aquele email, caso verdadeiro nossa busca irá retornar aquele usuário e voilà, ele estará conectado.
Injections 1=1
Vamos a nossa primeira injeção, 1 = 1, todos que estão lendo concordam que um é igual a um correto? Baseado nessa condição, que retornará sempre verdadeiro, usuários mal intencionados conseguem acessar áreas que existam login simplesmente injetando um pedaço de código SQL no input de email, considerando que o campo de senha passará por uma função hash.
Ae voce rebate, "grandes coisa, ele vai por 1=1 caindo dentro das aspas e não vai acontecer nada", se voce pensou isso sinto lhe informar que é um terrível engano, sabendo como funciona a engine do sql o invasor ja sabe(ou pelo menos imagina) que o campo email é um campo varchar e possui aspas na comparação, pois voce passará uma string.
Dessa forma o pedaço de código que ele põe no campo de email será algo parecido como.
Dessa forma quando essa string do email for concatenada com a string da nossa query, resultará em:
SELECT * FROM `USERS` WHERE `PASS`='um_hash_qualquer' AND `EMAIL`='' or ''='';
-- ''='' retornará sempre true, então todos os usuários serão resgatados
-- nessa query, simulando um login bem-sucedido !
Se fosse algum campo numérico, como a busca de algum código por exemplo, o usuário poderia fazer a mesma coisa usando exatamente o 1=1.
SELECT * FROM `PRODUCTS` WHERE `ID`=0 or 1=1;
-- 1 = 1 retornará true, então o usuário terá acesso a todos os
-- produtos do seu banco de dados.
Realmente, talvez obter a listagem de produtos não irá causar tanto estrago no seu sistema mas isso abre brecha para o próximo tipo de injeção.
Injections de SQL statements
Essa injeção irá adicionar um código SQL no meio da nossa query, acabando com o comando anterior usando ponto e vírgula, o invasor bota um comando para ser executado logo depois pelo sql.
Vamos continuar na última query na tabela de produtos. O que aconteceria se o invasor botasse essa entrada no campo numérico?
Se voce respondeu que ele irá obter toda a listagem de usuários, parabéns, o invasor acabou de conseguir a lista de todos os seus clientes, e também, caso você não salvasse as senhas usando uma função hash como md5, ou mesmo utilizando o md5 mas sem um prefixo SALT, ele conseguiu as senhas dos seus usuários também.
SELECT * FROM `PRODUCTS` WHERE `ID`=0; SELECT * FROM USERS;
-- fechando o comando anterior com ';' o usuário malicioso
-- consegue fazer uma busca por todos os campos da tabela USERS
E isso estamos pensando em um invasor bonzinho que só quer roubar as informações dos seus usuários. Porque ele poderia muito bem fazer isso:
SELECT * FROM `PRODUCTS` WHERE `ID`=0; DROP TABLE USERS;
-- O invasor acabou de derrubar a tabela de todos os usuários do seu sistema
Se voce não tiver um backup, realmente estará em maus lençois, afinal toda a tabela de usuários do seu site foi derrubada, perdeu todos os clientes em uma "simples tentativa de login".
Maneiras de se proteger
Existem coisas básicas que voce pode fazer para se proteger de tais tentativas, realizar a validação dos campos sempre através de Regexp(expressões regulares), assim o usuário malicioso não poderá botar um ponto e virgula ou uma aspas no campo de e-mail por exemplo, ou um comando inteiro em um campo que só espera números.
Lembrando que essas verificações devem acontecer também no lado do servidor, afinal o javascript qualquer um consegue editar ou injetar um novo no seu site.
mysqli no PHP
Se voce ainda utiliza as funções da lib mysql(), CORRA, porque além de ela estar deprecated, ela é totalmente aberta a invasões desse tipo. Nela, todas as queries são passadas manualmente, como se ocorresse nos exemplos acima de concatenação de strings, logo os ataques ocorrem exatamente igual o que vimos acima.
Mas claro que eu não falaria para voce correr dessa lib sem te dar uma alternativa, a classe mysqli, ela usa parametros em "bind" para não haver nenhuma concatenação direta, e nesse meio do bind a classe trata o input para nada funcionar como uma injeção.
Por exemplo, caso o usuário entrasse com o código acima mostrado no email, a query resultado do bind_param()no nosso statement, será:
SELECT * FROM `USERS` WHERE `PASS`='um_hash_qualquer' AND `EMAIL`=''' or ''=''';
-- o resultado da string de query irá por todo o conteudo dentro das aspas
-- independente de ele ja ter aspas ou não
Botando todo o conteúdo dentro das aspas, a pesquisa do usuário irá procurar literalmente pelo email que corresponda à '' or ''='', como sabemos que não existe nenhum email assim, nada será encontrado no banco de dados e a invasão terá sido mal sucedida.
Código php
"Muito show Matheus, mas e o código dessa bagaça ae?", para quem se perguntou isso, irei mostrar brevemente como funciona o código e deixar a documentação da classe Mysqli em baixo.
<?php
// aqui instanciamos a classe mysqli, passando como parametro
// o servidor do db, user do db, senha do db, e o nome do db
$mysqli = new mysqli('servidor', 'usuario', 'senha', 'database' );
// com a classe instanciada, nós chamamos o método prepare
// para "preparar" a nossa query que receberá os valores
// LEMBRANDO QUE onde nós botarmos "?"(interrogação) será os locais
// que entrarão os nossos valores
// ps: não precisa se preocupar com aspas no caso de strings
$stmt = $mysqli->prepare("SELECT name, tel FROM USERS WHERE email=? AND senha=?");
// depois de nosso statement estar preparado com a string da query e as interrogações
// nós iremos definir quais variaveis entram em quais lugares, e seus tipos
// através da função ->bind_param(string de tipos, ...variaveis em ordem da interrogação )
$stmt->bind_param('ss', $email, $senha);
// ps: a "string de tipos" que é o primeiro parametro é composta pela inicial dos tipos
// das variáveis que estão sendo adicionadas.
// Os tipos permitidos são:
// i - variaveis inteiras
// d - variaveis double
// s - variaveis string
// b - variaveis que fornecem dados para um blob
// após definirmos quais variáveis entraram em quais lugares,
// nós executamos nosso statement
if($stmt->execute()){
// outra função legal do statement é que podemos fazer
// algo parecido com o processo inverso do bind_params
$stmt->bind_result($name, $tel);
// a função bind results, irá atribuir os valores obtidos
// nas queries, no nosso caso name e tel, a variáveis que nós
// escolhemos os nomes, eu preferi manter o nome, mas poderia
// ter posto $var1 e $var2, $var1 receberia o nome e
// $var2 receberia o telefone
// E aqui pegamos todos os resultados imprimindo eles na tela
// com os nomes das variáveis que definimos no bind_result()
while($stmt->fetch()){
echo "nome do usuarrio: $name \n tel do usuario: $tel\n";
}
}
?>
Esse é o código mais básico, mostrei ele somente para vocês terem uma idéia de como funciona o processo para manter as strings de queries limpas. Falta tratamento de erro e outras coisas, sendo assim o que mais indico é dar uma estudada na classe mysqli e fazer os seus testes !
Conclusão
Espero que tenham gostado do post, ele acabou sendo mais explicativo do que técnico porque muita gente ainda não sabe como funciona as injeções de sql e por isso deixam brechas abertas para pessoas mal intencionadas.
Existem implementações de coisas parecidas em várias linguagens, e praticamente todos os frameworks famosos de PHP fazem isso sob os panos. Inclusive se voce ainda desenvolve sem um framework PHP(claro que nem sempre é necessário), indico dar uma estudada neles, agilizam muito o trabalho e em proejtos grandes te trazem uma segurança bem maior do que voce ter que se preocupar com tudo na unha. Eu uso o Laravel, mas existem outros como ZendPHP, cakePHP, Yii, CodeIgniter e outros vários até perder de vista.
Obrigado a todos que gastaram o seu precioso tempo lendo até aqui e lembrem que qualquer dúvida, sugestão(como esse post), crítica, pode deixar nos comentários ! O post fica por aqui e até a próxima ! :D