jun 23 2010

Evite SQL Injection usando Prepared Statements

Uma das maiores vulnerabilidades de sites, a injeção de SQL (SQL Injection) é também, no caso do PHP, uma das mais fáceis de prevenir. Infelizmente, muitos não tomam as devidas precauções e acabam tendo os seus dados comprometidos.

Neste tutorial, irei demonstrar como trabalhar com prepared statements usando a extensão PDO do PHP.

SQL Injection

Antes de começar, vale a pena ilustrar como funciona um ataque típico de SQL Injection:

// Temos esta consulta simples aonde os dados $username e $password vem de um formulário preenchido pelo usuário
$query = "SELECT * FROM tabela WHERE username = '$username'";

// Se não houver validação correta um usuário mal-intencionado poderia colocar algum código SQL no lugar do username:
$username = "' OR 1'";

// A consulta ficaria assim:
$query = "SELECT * FROM tabela WHERE username = "'' OR 1";

// Como a expressão 'OR 1' sempre resulta em TRUE, a consulta retornaria os dados de todos os usuários no sistema

Convenhamos, o exemplo acima é bobo, mas serve para mostrar a teoria por trás da técnica de injeção de sql. Se ainda não se convenceu de que é necessário, veja um exemplo mais grave:

// começamos com a mesma consulta
$query = "SELECT * FROM tabela WHERE username = '$username'";

// desta vez, o código inserido é bem mal-intencionado mesmo...
$username = "'; DELETE FROM tabela WHERE 1 OR username = '";

// a consulta final ficaria assim:
$query = "SELECT * FROM tabela WHERE username = ''; DELETE FROM tabela WHERE 1 OR username = ''";

// ou seja, se executada, a consulta excluiria todos os registros da tabela

Validação sozinha não resolve!
Você pode estar questionando se uma boa validação já não resolveria o problema, já que em ambos exemplos validar para aceitar somente letras funcionaria para bloquear ambas tentavas. Bom a resposta é: SIM e NÃO!

Por mais que a validação ajude, mesmo usando expressões regulares complexas, há meios de burlá-la utilizando outros charsets e técnicas maliciosas. Segurança nunca é demais e não custa se prevenir para proteger os seus dados ou os dos seus clientes.

O que são prepared statements?

Nada mais são do que consultas “pré-prontas”… A diferença é que em lugar das variáveis você coloca um placeholder (marcador de lugar) e na hora da consulta informa a ordem das variáveis a serem substituidas.

É mais fácil de explicar com um exemplo!

// a interrogação vai no lugar da variável
$query = "SELECT * FROM tabela WHERE username = ?";

// para fazer com vários parametros é a mesma coisa
$query = "SELECT * FROM tabela WHERE username = ? OR username = ?";

Depois, é só informar o que vai no lugar dos respectivos ‘?’ e a consulta estará protegida! Isto funciona porque ao prepararmos a consulta, avisamos ao MySQL (ou outro RDBMS que suporte prepared statements) como é a consulta e exatamente aonde vão as variáveis. Repare que nem precisamos mais colocar aspas em volta da variável, pois ele já sabe que é uma variável e a trata de acordo.

No PHP, a extensão MySQLi também suporta statements preparados, mas recomendo sempre utilizar o PDO pois ele facilita a migração para outros bancos, além de oferecer uma API concisa entre eles.

Como Funciona com PDO

// vamos partir do pressuposto que temos um objeto PDO instanciado e devidamente configurado e iremos trabalhar com a mesma consulta dos exemplos anteriores
$query = "SELECT * FROM tabela WHERE username = ?";

// o método PDO::prepare() retorna um objeto da classe PDOStatement ou FALSE se ocorreu algum erro (neste caso use $pdo->errorInfo() para descobrir o que deu errado)
$stmt = $pdo->prepare($query);

// agora que temos o statement preparado, precisamos "bindar" a variável
$username = "fulano";

// utilizamos o método PDOStatement::bindValue() que aceita como parâmetros a posição do ? que a variável irá substituir (a primeira é 1) e a própria variável
$stmt->bindValue(1, $username);

// executamos o statement
$ok = $stmt->execute();

// agora podemos pegar os resultados (partimos do pressuposto que não houve erro)
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

Parece meio chato, ter que escrever tanto código a mais para executar uma simples consulta e se a questão da segurança não for motivo sufiente, que tal este: statements preparados são mais rápidos que consultas normais! Especialmente se a mesma consulta for executada diversas vezes durante um request (mudando ou não as variáveis).

Como o assunto é extenso, incluirei algumas informações adicionais para esclarecer alguns itens, se não precisar de tanto detalhe técnico pode pular ao final!

Informações Complementares

  • Há outro método para fazer “bind” das variáveis: o PDO::bindParam(), a síntaxe é exatamente a mesma mas ele recebe a variável por referência, enquanto PDO::bindValue() recebe por valor.
  • Tanto o bindValue() quanto o bindParam() aceitam um terceiro parâmetro opcional que é o tipo da variável (PDO::PARAM_STR, PDO::PARAM_INT, etc)
  • Se não gostou da sintaxe de colocar varias interrogações e depois substituir as variáveis de acordo com a posição da interrogação, também é possível utilizar placeholders nomeados:
    // em vez da interrogação utilizamos uma palavra prefixada de um ":"
    $query = "SELECT * FROM tabela WHERE username = :usuario OR username = :administrador";
    
    // na hora de fazer bind fica mais claro qual variável vai aonde
    $stmt->bindValue(":usuario", $username);
    $stmt->bindValue(":administrador", "admin");
    
    // este método também facilita fazer binds dentro de loops foreach aonde o placeholder é a chave e a variável é o valor
    foreach($variaveis as $k => $v){
        $stmt->bindValue(":$k", $v);
    }
    

E é isto, chegamos ao final! Espero que o artigo tenha ajudado e lembre-se, pular estes simples passos é o típico barato que sai caro!

[]s


ago 13 2009

Email Seguro – Divulgue o seu email com segurança


Já reparou que ninguém gosta de escrever o email no seu site?  Muitos usam formulários de contato ou o escrevem usando o formato: “nome arroba dominio.com” Inclusive, se ver a minha página de contato eu decidi colocar uma imagem com meu email ao invés de escrevê-lo.

Mas porque tudo isso? É simples, a internet está cheia de bots que vasculham o conteúdo das páginas procurando endereços de email para extrair e depois adicionar em listas de recebimento de spam.  Não é nada grave pois os sistemas anti-spam já estão muito avançados, mas não custa nada tomar uma precaução a mais.

Email Seguro é uma ferramenta que converte textos em Unicode. O navegador consegue interpretá-lo sem problemas mas muitos spam bots não conseguem entendê-lo. É ideal para inclusão de endereços de email na página sem correr o risco de ser alvo de Spam.

É só digitar o seu email e clicar em “Converter”!

Link: Email Seguro

OBS – Os emails convertidos não são armazenados em lugar nenhum.  Eu odeio spam tanto quanto você e nunca faria isso.