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


jun 20 2010

Testando as suas classes com PHPUnit

Artigo originalmente escrito para o PHPBrasil, apenas o trouxe para cá!

O que é Unit Testing?

  • Testes para pequenos trechos de código (unidades)
  • Verifica se o comportamento de classes e funções é o esperado
  • Caso erros são encontrados exceções são lançadas
  • Não interfere com o seu código-fonte

Qual a vantagem?

  • Os testes são automatizados
  • São executados continuamente durante o ciclo de desenvolvimento
  • Detectam falhas tanto de digitação e lógica e também comportamentos inesperados
  • Escalam conforme o desenvolvimento

Exemplo

require_once('Classe1.php');
// Teste da classe

$foo = new Classe1();

$soma = $foo->somar(2, 2);
$subracao = $foo->subtrair(2, 2);

echo '2 mais 2 = ' . $soma;
echo '2 menos 2 = ' . $subtracao;

// Funciona conforme esperado!

Apesar do exemplo simples, ilutra uma prática bem comum: a de inserir vários echo() e print() para confirmar o funcionamento de uma classe ou função.

O PHPUnit é uma framework que tem como objetivo automatizar e padronizar a forma como nosso código é testado.

“Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead.” — Martin Fowler

Ou seja: “Toda vez que sentir vontade de escrever um print ou outra expressão de debug, pare e crie um teste!”

Testando a mesma classe do exemplo anterior com o PHPUnit

// Especifique o caminho do PHPUnit
require_once '/usr/share/php/PHPUnit/Framework.php';
// Especifique o caminho da classe a ser testada
require_once 'Classe1.php';

// Como a classe a ser testada se chama "Classe1", a classe de teste deverá se chamar "ClasseTest" e (neste caso) irá herdar de PHPUnit_Framework_TestCase
class Classe1Test extends PHPUnit_Framework_TestCase
{
        // Funções de teste devem ter o prefixo "test"
        public function testSoma()
        {
                // A classe a ser testada é instanciada
                $foo = new Classe1();
                // Testamos a afirmação que "4" é o resultado de passar "2" e "2" (ou seja, 2 + 2 = 4)
                $this->assertEquals('4', $foo->somar(2, 2));
        }

        public function testSubtracao()
        {
                // idem (veja função acima)
                $foo = new Classe1();
                $this->assertEquals('0', $foo->subtrair(2, 2));
        }
}

O resultado:

PHPUnit 3.3.16 by Sebastian Bergmann.

.

Time: 0 seconds

OK (2 tests, 2 assertions)

// Funciona conforme esperado!

O que aconteceu?
Criamos uma classe (Classe1Test) de teste para testar a nossa classe (Classe1) e usamos o PHPUnit para testar o funcionamento esperado

Não é mais complicado?
Sim e não! Inicialmente pode parecer bem mais simples inserir alguns print() ou echo() e verificar manaulmente, mas ao longo prazo usar o PHPUnit traz diversas vantagens:

  • Os testes ficam separados do código, assim não corremos o risco de esqueçer testes no meio do código. Devido à separação também não é necessário ficar voltando e comentando e descomentando as linhas de teste inseridas manualmente.
  • Para aplicações um pouco mais complexas (por exemplo com várias classes interagindo juntas ou com AJAX) as vezes não é possível colocar echo() ou print() em qualquer lugar. Até mesmo o FirePHP gera erros de ‘header’!
  • Usando o PHPUnit criamos vários testes e sempre que desejarmos podemos rodá-los novamente para testar modificações.
  • E o melhor de tudo, não precisamos fazer nada na hora de colocar a aplicação no ar. Simplesmente não fazemos o upload da pasta de testes! O código-fonte original segue intacto.

Como funciona?

  1. Crie uma classe de teste para testar uma classe já existente
  2. Execute os testes no terminal (ou prompt de comando) e veja os resultados
  3. A melhor maneira da aprender isto é através de exemplos então vamos instalar o PHPUnit e criar alguns testes!

Instalação do PHPUnit
Quem usa o linux pode fazer facilmente:

# se não tiver o PEAR instalado
sudo apt-get install pear

# descubra o canal do PHPUnit
sudo pear channel-discover pear.phpunit.de

# instale o phpunit
sudo pear install phpunit/PHPUnit

# Ele fica instalado por padrão no /usr/share/php/PHPUnit
# Agora é só incluir o framework nos arquivos de teste e começar!

Quem usa windows procure no google! Não deve ser muito complicado… (se não me engano ele já vem incluso no pacote XAMPP)

Guia para criar Unit Tests

  • Os testes para a classe “Classe1″ (Classe1.php) deverão ser localizados dentro da classe “Classe1Test” (Classe1Test.php), sempre use o sufixo “Test” (NomeDaClasseTest)
  • Na maioria dos casos a classe de teste irá herdar (extends) da classe PHPUnit_Framework_TestCase
  • Os testes deverão ser funções públicas com o prefixo “test” (testSomar, testSubtrair, etc)
  • Os testes são baseados em afirmações, ou seja: afirmamos que o resultado esperado da função X é Y, e testamos a afirmação pra ver se ela procede.
  • Usamos funções como assertEquals() para testar afirmações (exemplos a seguir)

Algumas Afirmações disponíveis (Funções de Teste)
Existem dezenas (veja a documentação), seguem algumas das mais comuns:

  • assertEquals($valor, funcao()) (afirma que o valor retornado da funcao é igual a $valor)
  • assertNotEquals($valor, funcao()) (afirma que o valor retornado da funcao não é igual a $valor)
  • assertSame($valor, funcao()) (testa se o objeto retornado é igual a $valor)
  • assertTrue(funcao()) (afirma que o valor retornado é === true)
  • assertFalse(funcao()) (=== false)
  • setExpectedException (avisa sobre uma exceção que deverá ser lançada)

Como rodar os testes
Através do terminal (ou prompt de comando), no diretório do arquivo de teste digite:

# para rodar os testes contidos no arquivo NomeDaClasseTest.php
phpunit NomeDaClasseTest

# também é possível rodar o PHPUnit nos conteudos de uma pasta inteira
phpunit NomeDaPasta/

Exemplos

O teste seguinte usa a função assertEquals() para checar o retorno de um método:

// Especifique o caminho do PHPUnit
require_once '/usr/share/php/PHPUnit/Framework.php';
// Especifique o caminho da classe a ser testada
require_once 'Classe2.php';

class Classe2Test extends PHPUnit_Framework_TestCase
{
    public function testCumprimentarSemParametro()
    {
        // O comportamento esperado é que retorne "Olá fulano", testa esta afirmação
        $this->assertEquals('Olá fulano', $foo->cumprimentar());
    }

    public function testCumprimentarComParametro()
    {
        // Instancia a classe e passa "Alex" ao construtor
        $foo = new Classe2('Alex');
        // O comportamento esperado é que retorne "Olá Alex", testa esta afirmação
        $this->assertEquals('Olá Alex', $foo->cumprimentar());
    }

        // Funções de teste também podem ser identificados pela anotação @test no DocBlock
        /**
         * @test
         */
         public function vaiDarErrado()
         {
                 // Instancia a classe e passa "ciclano" ao construtor
                $foo = new Classe2('ciclano');
                // O comportamento esperado é que retorne "Olá ciclano", mas vamos afirmar que retornará outro valor para forçar um erro
                $this->assertEquals('Olá Alex', $foo->cumprimentar());
         }
}

O teste retorna o seguinte:

PHPUnit 3.3.16 by Sebastian Bergmann.

..F

Time: 0 seconds

There was 1 failure:

1) vaiDarErrado(Classe2Test)
Failed asserting that two strings are equal.
expected string <Olá Alex>
difference      <     xxxx???>
got string      <Olá ciclano>
/caminho/para/o/arquivo/Classe2Test.php:35

FAILURES!
Tests: 3, Assertions: 3, Failures: 1.

Ou seja, quando um teste falha ele informa o arquivo e linha onde ocorreu o erro e explica também qual foi o erro:

O valor esperado era “Ola Alex” e o valor retornado foi “Ola ciclano”.

Fornecendo dados para os testes
As vezes não basta só um teste com um valor estático, neste caso podemos criar uma função para alimentar o teste usando vários valores (e nada impede que sejam de fontes externas como um banco de dados ou arquivo xml!). A função é identificada através da anotação @dataProvider no DocBlock.

// Demonstração de "Data Providers"
class Classe3Test extends PHPUnit_Framework_TestCase
{
    // Informa o nome do forneçedor de dados (@dataProvider) para a clase de teste
    /**
     * @dataProvider provider
     */

    // Recebe 3 parâmetros
    public function testCombine($a, $b, $c)
    {
        // Afirma que o valor do terceiro parâmetro é igual a concatenação dos primeiros dois separados por um espaço
        $this->assertEquals($c, $a . ' ' . $b);
    }

    // A função fornecedora de valores
    public function provider()
    {
        // retorna um array contendo 3 grupos de valores a serem testados
        return array(
            array('Hello', 'World', 'Hello World'),
            array('É', 'Nois', 'É Nois'),
            array('Deu', 'Errado', 'Deu Certo')
        );
    }
}

Resumindo

O assunto é bastante extenso e realmente fica difícil cobrir todas as possibilidades (que são quase infinitas), mas é possível criar testes para testar se Exceções são lançadas conforme esperado, para testar classes de acesso ao banco de dados e muito mais!

O ideal seria implementar isto em uma aplicação que esteja desenvolvendo, ou seja, crie uma pasta para os testes e vá criando testes para as suas classes nela.

Todo o código-fonte para os exemplos e outros exemplos mais complexos (todos bem documentados) estão disponíveis no link.

[]s


ago 29 2009

Turbine o seu CSS usando PHP

O grande inimigo dos desenvolvedores web é o Internet Explorer, devido ao seu suporte incompleto e má implementação dos padrões de CSS.

O Internet Explorer 8 até que melhorou bastante em relação às versões antigas mas ao invés de facilitar a nossa vida, apenas a complicou porque agora não basta fazer ajustes gerais para o IE; muitas vezes é necessário ajustar o CSS tanto para o IE8 quanto para o IE7 e 6.

Agora imagine uma situação em que não fosse necessário usar condicionais e hacks, em que servíssemos apenas um stylesheet e funcionasse perfeitamente em todos os navegadores.  Parece sonho né?  Mas nao é!

Com o PHP e algumas regras no Apache é possível criar folhas de estilo dinâmicos e resolver todos estes problemas.

Li um excelente artigo estes dias que explica todo este processo de maneira bastante compreensiva, vale a pena conferir!

Link: Supercharge Your CSS with PHP Under the Hood