Evitando SQL Injection em aplicações PHP

Veja neste artigo como evitar invasões em seu sistema PHP através de SQL Injection, garantindo a segurança das informações.

Figura 1. Evitando injeção de SQL na sua aplicação

Hoje, com o aumento da tecnologia da informação, dispositivos móveis, facilidade de acesso à internet e a migração dos meios de pagamento para web, os roubos de dados de usuários se tornaram mais frequentes. Um dos mecanismos usados por invasores para obter informações do seu banco de dados é chamado de SQL Injection, que nada mais é que comandos SQL inseridos em seus formulários ou na barra de endereço do browser.

Seu sistema pode estar vulnerável independentemente da linguagem de programação utilizada na arquitetura de seu sistema, visto que os comandos de SQL injection tem como base instruções SQL, como o próprio nome sugere. Existem métodos e ferramentas de segurança capazes de evitar e apontar falhas na segurança do seu sistema e é isso que vamos ver.

Uma das ferramentas para o suporte a prevenção do SQL Injection é o SQL INJECTION ME: Plugin do Firefox que através do submit de seus formulários acusa se há ou não abertura para o uso de instruções SQL nos mesmos. Alguém mal intencionado pode usar seu formulário de login, por exemplo, para resgatar os dados de acesso de usuários de seu banco para acessar o sistema e modificar seus dados ou obter alguma informação importante.

Outro recurso que os programadores utilizam para prevenir ataques do tipo é a utilização de expressões regulares em seus códigos para “limpar” as variáveis enviadas para o sistema. No exemplo abaixo limpamos a variável de login de um post:

$_POST['login'] = preg_replace('/[^[:alpha:]_]/', '',$_POST['login']);
Listagem 1. Função para limpar caracteres da string

[:alpha:] é a mesma coisa que [a-zA-Z], portanto não são permitidos símbolos nem o _, evitando aquele comando clássico utilizado nos logins como: or ‘ 1=’1 além de todos outros possíveis utilizando caracteres especiais e números. Para comprovarmos fazemos o teste:

<?php $login = "Um teste de or '1='1;"; $resultado = preg_replace('/[^[:alpha:]_]/', '',$login); echo $resultado; ?>
Listagem 2. Limpando string
Nota: Verificar na documentação do PHP a utilização da função PREG_REPLACE.

Assim como a expressão [:alpha:] está para os caracteres, a função [:alnum:] está para os números e caracteres. [:alnum:] é equivalente a [a-zA-Z0-9]. Para exemplificar vamos retirar todos os caracteres especiais de uma variável. Utilizando o último exemplo, temos:

$senha = "Um teste de or '1='1;"; $resultado = preg_replace('/[^[:alnum:]_]/', '',$senha); echo $resultado;
Listagem 3. Deixando apenas letras e números na variável

Observe que diferente do primeiro exemplo, agora ele limpa os caracteres especiais deixando apenas as letras e números. Com esse tipo de prática, diga-se de passagem, bem simples, evitamos problemas de nível crítico em nossas aplicações.

Outro método utilizado por programadores na prevenção de invasões através de comandos SQL é a utilização do PDO (PHP Data Objects) na camada de abstração da aplicação. Esse tipo de recurso é bastante útil e fácil de implementar, além de aumentar a portabilidade entre banco de dados sem muita ou quase nenhuma alteração de instruções SQL.

O PDO utiliza “prepared statements” na formação de suas queries. O que nada mais é que um template que irá nos ajudar a escrever uma instrução. Porque isso ajuda a prevenir ataques de injeção de SQL? Como é um “template”, a estrutura nos permite saber onde exatamente irão entrar os valores para as nossas queries. Por exemplo:

<?php $pdo = new PDO('mysql:host=localhost;dbname=crud', 'root', ''); $stmt = $pdo->prepare('select * from agenda where nome = :nome'); $stmt->bindValue(':nome', 'kalil'); $run = $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); var_dump($result); ?>
Listagem 4. Exemplo de uso de prepared statements PDO

Repare na utilização da função “bindValue”, nela passamos a variável e o valor da mesma para nossa query. Desta forma colocamos cada variável no seu devido lugar e qualquer código anormal que entre irá gerar erro.

Agora que já vimos algumas formar de evitar invasões, vamos criar um exemplo usual. Vamos simular um login onde conseguimos obter dados da tabela utilizando de SQL Injection e depois vamos alterar o código para evitar tal invasão. Mãos na massa!

SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `login` -- ---------------------------- DROP TABLE IF EXISTS `login`; CREATE TABLE `login` ( `id_usuario` int(11) NOT NULL AUTO_INCREMENT, `nome` varchar(150) DEFAULT NULL, `email` varchar(50) DEFAULT NULL, `senha` int(10) DEFAULT NULL, PRIMARY KEY (`id_usuario`) ) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1; -- ---------------------------- -- Records of login -- ---------------------------- INSERT INTO `login` VALUES ('1', 'kalil kelvin', 'kalil@teste.com.br', '123'); INSERT INTO `login` VALUES ('2', 'devmedia', 'devmedia@teste.com.br', '321');
Listagem 5. Arquivo SQL do exemplo

Crie um banco chamado login e crie uma tabela login dentro do mesmo através do script acima.

<!DOCTYPE html> <html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <div>Formulário de Login</div> <div> <form method="post" action="modelo.php"> <label>Nome:</label><br/> <input type="text" name="nome" value="" /><br /> <label>Senha:</label><br/> <input type="text" name="senha" value="" /><br /> <input type="submit" value="submit" name="enviar" /> <input type="reset" value="limpar" name="limpar" /> </form> </div> </body> </html>
Listagem 6. Arquivo index.html

Este é nosso formulário de teste ao qual enviaremos os dados para o banco.

<?php class Conexao { var $pdo; function __construct() { $this->pdo = new PDO('mysql:host=localhost;dbname=login', 'root', ''); } public function select($nome, $senha) { $stmt = $this->pdo->prepare("select * from login where nome = '$nome' and senha = '$senha'"); $run = $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); return $result; } } ?>
Listagem 7. Arquivo conexao.php

Com este arquivo de conexão, iremos simular a invasão, ou seja, queries desprotegidas sem a utilização de nenhum recurso visto até agora neste artigo.

Por último, o modelo, que recebe os dados do formulário e o envia para a conexão.

<?php require 'conexao.php'; $conn = new Conexao(); if($_POST){ $nome = $_POST['nome']; $senha = $_POST['senha']; $select = $conn->select($nome, $senha); var_dump($select); } ?>
Listagem 8. Arquivo modelo.php

Ok, acessando nosso arquivo index.php, vamos realizar o login da seguinte maneira. No campo login deixaremos em branco e no campo senha vamos inserir o seguinte código: ' or '1=1. Ao clicar no botão submit, vamos ter como retorno um debug com todos os usuários cadastrados no nosso banco de dados, provando que o sistema contém falhas.

Corrigindo nossas falhas, iremos editar nossos arquivos modelo.php e conxao.php. No arquivo modelo.php vamos utilizar a técnica da expressão regular para filtrar caracteres indesejados e no arquivo conexao.php vamos utilizar o recurso do PDO para alocação de variáveis com a função bindValue. Veja como ficam nossos arquivos:

<?php require 'conexao.php'; $conn = new Conexao(); if($_POST){ $nome = preg_replace('/[^[:alpha:]_]/', '',$_POST['nome']); $senha = preg_replace('/[^[:alnum:]_]/', '',$_POST['senha']); $select = $conn->select($nome, $senha); var_dump($select); } ?>
Listagem 9. Alteração arquivo modelo.php

Note que agora trabalhamos as variáveis antes de enviar para o banco de dados, eliminando possíveis caracteres não desejados.

<?php class Conexao { var $pdo; function __construct() { $this->pdo = new PDO('mysql:host=localhost;dbname=login', 'root', ''); } public function select($nome, $senha) { $stmt = $this->pdo->prepare("select * from login where nome = '$nome' and senha = '$senha'"); $stmt->bindValue(':nome', $nome); $stmt->bindValue(':senha', $senha); $run = $stmt->execute(); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); return $result; } }
Listagem 10. Alteração arquivo conexao.php

Note que agora temos mais duas linhas no arquivo, com o auxilio do função bindValue preenchemos o template de nossa query, proporcionando assim uma maior segurança.

Segurança da informação é muito importante nos sistemas de hoje, não somente na aplicação, mas também nos servidores físicos, rede de dados, etc. Lembre-se que com pequenas atitudes podemos evitar problemas futuros gigantescos. Existem muitos outros meios de aumentar a segurança na sua aplicação, neste artigo mostro-lhes apenas o ponta pé inicial da jornada. Obrigado a todos.

Artigos relacionados