Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

falcao544

PDO - Prepared Statements - SQL Injection

Recommended Posts

Oi pessoal, eu tenho uma preocupação que é em relação a SQL Injection. Ja li pelo menos uns 10 artigos diferentes espalhados pela internet e já estou quase certo de qual seria a melhor maneira de avitar SQL Injection! Li inclusive um artigo do "fabyo" que é de 2005, mas ele pestou a 3 anos atrás se não me engano, que a melhor maneira para evitar SQL Injection, seria usando PDO e Prepared Statements! No entanto, li vários artigos falando sobre o mysql_real_escape_string, mas fiz alguns testes e tive problemas com o valor retornado, pode ser que eu não tenha utilizado direito essa função, mas enfim, PDO e prepared statements ou mysql_real_escape_string? Ou um conjunto de funções?

 

 

Obrigado!

Compartilhar este post


Link para o post
Compartilhar em outros sites

use o pdo sem duvidas, mais pratico

Compartilhar este post


Link para o post
Compartilhar em outros sites

alem do PDO ser prático é seguro no final achei até mais fácil trabalhar com ele, e é bom sempre fechar a conexão com DB quando for possível só para fortalecer o code

Compartilhar este post


Link para o post
Compartilhar em outros sites

Primeiro de tudo orbigado pelas respostas! Segundo, me desculpem pelos erros ao escrever a minha dúvida, eu estava no celular!

 

Bom, voces falaram sobre o PDO, mas não mencionaram os prepared statements! Devo utilizá-los, correto?

 

Obrigado!

Compartilhar este post


Link para o post
Compartilhar em outros sites

alem do PDO ser prático é seguro no final achei até mais fácil trabalhar com ele, e é bom sempre fechar a conexão com DB quando for possível só para fortalecer o code

A conexão aberta pela PDO é fechada automaticamente ao final do script

 

Primeiro de tudo orbigado pelas respostas! Segundo, me desculpem pelos erros ao escrever a minha dúvida, eu estava no celular!

 

Bom, voces falaram sobre o PDO, mas não mencionaram os prepared statements! Devo utilizá-los, correto?

 

Obrigado!

Segundo o manual você deve SIM utilizá-los não apenas porque interpolar os dados vindos do usuário com PDO::quote() é uma tremenda chateação que incha bastante o código mas também porque é mais rápido em termos de performance.

 

Mas pra mim o ponto principal é que assim como PDOStatement()::bindParam() e cia, PDO::quote() exige que um argumento com o tipo do dado seja passado através das constantes da PDO

 

Nos casos mais simples, não chega a ser um problema, pois um número tratado e armazenado como string, quando trazido de volta do banco será uma string numérica que, através de intval() (ou o cast para integer) será um número, tal qual era antes de ser armazenado

 

Mas em algumas circusntâncias mais específicas, que eventualmente ocorrerão conforme o seu aprendizado aumenta, poderá dar problema.

 

Não é uma das Leis de Murphy de que VAI dar problema, mas PODE dar problema.

 

Se isso não é um problema para você, pense ao menos na performance. Cada centésimo de segundo conta no final das contas.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Muito obrigado Bruno! Então, se eu usar prepared statements, não terei dor de cabeça com as aspas simples e os comandos que um usuário mal intencionado digitou no formulário?

 

Eu estava lendo aqui sobre prepared statements e vejo o pessoal sempre preparando a query após fazer a conexão com o banco de dados. Eles fazem isso porque tem que ser assim ou porque é mais lógico preparar a query somente após a conexão? Outra dúvida, se eu utlizar try/catch para gerenciar os erros e exceções na hora de fazer a conexão e for retornado um erro, o script é interrompido após o catch ou eu teria que colocar um "exit" ou "die" dentro do catch para parar o script, caso a conexão não se estabeleça?

 

Me desculpe, mas como estou iniciando em PHP OO, eu não entendi direito essa parte:

 

Segundo o manual você deve SIM utilizá-los não apenas porque interpolar os dados vindos do usuário com PDO::quote() é uma tremenda chateação que incha bastante o código mas também porque é mais rápido em termos de performance.

 

Obrigado!

 

--------------- Ponto de Mesclagem ---------------

 

A conexão aberta pela PDO é fechada automaticamente ao final do script

 

E se tiver um exit(); ou die(); no meio do código? Ele fehca a conexão automaticamente também?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Muito obrigado Bruno! Então, se eu usar prepared statements, não terei dor de cabeça com as aspas simples e os comandos que um usuário mal intencionado digitou no formulário?

Sim e não.

 

Sim porque a segurança proporcionada adiciona novas aspas logo após aquelas manualmente abertas. E como algumas das técnicas de SQL Injection envolvem as tais aspas combinadas com operadores de atribuição/comparação, que quando enviados ao banco violam aquilo que foi pré-especificado numa cláusula WHERE, tal procedimento anula a tentativa de injeção.

 

Para entender melhor, veja o Exemplo #3 de PDO::quote().

 

Agora o não é que, se checar o exemplo você terá/poderá ter várias aspas soltas no meio da string,requerendo que se preocupe limpando-as.

 

Eu estava lendo aqui sobre prepared statements e vejo o pessoal sempre preparando a query após fazer a conexão com o banco de dados. Eles fazem isso porque tem que ser assim ou porque é mais lógico preparar a query somente após a conexão?

Existem dois tipos de preparação: Aquela que você faz e aquela que você diz para a PDO fazer.

 

Um dos benefícios trazidos pelo MVC (assim como toda arquitetura n-tiers), que é a separação do código em camadas, garante que suas queries não fiquem espalhadas pelo código todo dentro de PDO::prepare() ou PDO::query()

 

Com isso você teria classes ou funções com todas as suas queries armazenadas, centralizando-as num único lugar. Vou demonstrar um dos possíveis casos, mesmo que eu não o use uma vez que meu sistema é bem mais complexo.

 

<?php

class Users {

  // Obtém usuário pelo ID

   public static function getUserById() {
       return 'SELECT * FROM `users` WHERE `id` = ?';
   }
}

Esse tipo de preparação você quem faz pois você já deixou a query prontinha num único lugar.

 

Agora quando você usa essa classe (ou função) você diz à PDO para prepará-la pra você.

 

Essa preparação faz com que, dentre outras coisas, o driver de conexão escolhido no construtor saiba qual é a query e quantos placeholders ela tem. Se estiver usando named placeholders haverá também um tipo de mapeamento para que a atribuição de valores ocorra por nome e não por posição.

 

<?php

$id = ( isset( $_GET['id'] ) && ! empty( $_GET['id'] ) ? (int) $_GET['id'] : NULL );

if( is_null( $id ) || $id == 0 ) {

   // Impede de continuar pois a chave primária começa em 1 (um)
}

$conn = new PDO( /* ... */ );

$stmt = $conn -> prepare( Users::getUserById() );

/**
* O PDOStatement() obtido, depois de preparado, sabe da
* existência de um placeholder, então precisa recebê-lo
*
* Mesmo que seja apenas um, PDOStatement::execute só aceita arrays
*/
if( $stmt -> execute( array( $id ) ) !== FALSE ) {

   $data = $stmt -> fetch();

   if( $data !== FALSE ) {

       /**
        * Se tiver um registro, mostramos
        *
        * Eu prefiro separar pois nem sempre PDOStatement::fetch()
        * vai retornar FALSE SÓ porque newnhum registro foi encontrado
        */
       if( count( $data ) == 1 ) {

           var_dump( $data );

       } else {

           printf( 'Nenhum usuário encontrado para o ID #%d', $id );
       }

   } else {

       // Algum erro ocorreu com PDOStatement::fetch()
   }

} else {

   /**
    * Algum erro ocorreu com PDOStatement::execute()
    *
    * Se uma PDOException for pêga, sempre será FALSE, logo esse condicional é desnecessário
    */
}

Outra dúvida, se eu utlizar try/catch para gerenciar os erros e exceções na hora de fazer a conexão e for retornado um erro, o script é interrompido após o catch ou eu teria que colocar um "exit" ou "die" dentro do catch para parar o script, caso a conexão não se estabeleça?

 

...

 

E se tiver um exit(); ou die(); no meio do código? Ele fehca a conexão automaticamente também?

Um bloco try...catch() serve para você tentar fazer alguma coisa (try) e se alguma coisa der errado com aquilo que estiver tentando fazer E uma Exception for disparada (throw), permitir que você faça outra coisa (catch).

 

E veja o que o manual diz na página das Exceptions:

 

When an exception is thrown, code following the statement will not be executed, and PHP will attempt to find the first matching catch block. If an exception is not caught, a PHP Fatal Error will be issued with an "Uncaught Exception ..." message, unless a handler has been defined with set_exception_handler().

Uma tradução livre seria a de que quando uma Exception é disparada todo o código que se seguir não será executado, ficando o interpretador encarregado de buscar na aplicação que usou o recurso o qual disparou a Exception por um catch e então o executar.

 

Se essa busca falhar, um Fatal Error será disparado alertando que uma Exception foi disparada e não pêga (Uncaught) a menos que um Exception Handler customizado tenha sido definido.

 

O código acima, dentro de um try...catch() ficaria:

 

<?php

$id = ( isset( $_GET['id'] ) && ! empty( $_GET['id'] ) ? (int) $_GET['id'] : NULL );

if( is_null( $id ) || $id == 0 ) {

   // Impede de continuar pois a chave primária começa em 1 (um)
}

$conn = new PDO( /* ... */ );

try {

   $stmt = $conn -> prepare( Users::getUserById() );

   $stmt -> execute( array( $id ) );

   $data = $stmt -> fetch();

   if( $data !== FALSE ) {

       if( count( $data ) == 1 ) {

           var_dump( $data );

       } else {

           printf( 'Nenhum usuário encontrado para o ID #%d', $id );
       }

   } else {

       // Algum erro ocorreu com PDOStatement::fetch()
   }
} catch( PDOException $e ) {

   /**
    * Cuidado ao confiar cegamente na mensagem de uma PDOException
    * porque na maioria das vezes que uma delas é disparada por um
    * PDO::prepare() ou PDOStatement::execute() falho
    * a mensagem é gigante e quase incompreensível
    */
   echo $e -> getMessage();
}

Vale lembrar que a PDO só vai disparar Exceptions se tiver recebido o valor PDO::ERRMODE_EXCEPTION para o atributo PDO::ATTR_ERRMODE:

 

$conn = new PDO( /* ... */ );

$conn -> setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

Quantoao fechamento, se o fluxo de execução é interrompido temos um exit/die implícito, terminando o script. E como PDO fecha a conexão automaticamente, não precisa fechá-la novamente.

 

Perceba, no entanto, que em implementações orientadas a objetos invariavelmente a instância da PDO estará armazenada numa propriedade e embora a conexão seja fechada, o objeto atribuído à essa propriedade não será excluído, daí sim você precisa "fechar", mas no contexto de destruir o objeto:

 

$this -> propriedadeComAInstanciaDaPDO = NULL;

Aos leitores, se houverem erros de sintaxe no código ou "palavrassemespaço", peço que desconsiderem. Minha irmã quebrou o teclado de novo.

Compartilhar este post


Link para o post
Compartilhar em outros sites
A conexão aberta pela PDO é fechada automaticamente ao final do script

 

ja li que o ideal é fazer

$conn = new PDO( /* ... */ );
...
$conn = null;

 

no caso de Registry, é necessário resetar o valor?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Fazendo isso você vai apenas desalocar a memória utilizada para tal variável.

 

Eu testei aqui e, para ambos os casos, isto é, destruindo ou não a variável, o consumo de memória antes de instanciar o objeto e depois dele variou em 6200 bytes, mesmo com a memória utilizada para as variáveis e invocação da função de exibição da memória consumida:

 

<?php

$before = memory_get_usage();

echo 'Before: ', $before, '<br />';

$con = new PDO( 'mysql:host=localhost;dbname=junkyard', 'root', '7v4h6q7t', array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) );

unset( $conn );

$after = memory_get_usage();

echo 'After: ', $after, '<br />';
echo 'Diff: ', $after - $before;

Claro que foi um script simples e que talvez numa aplicação de verdade isso varie, afinal seriam muitas outras variáveis a serem setadas e possivelmente destruídas.

 

Já quando a Registry, eu vejo sua aplicabilidade como sendo a de colocar todos os brinquedos na caixa antes de querer brincar com qualquer um deles, ao invés de só colocar na caixa quando se precisar.

 

Em código, isso representaria armazenar nele tudo aquilo de que múltiplos locais da aplicação precisarão, ao invés de só armazenar assim que a primeiro Action, do primeiro Controller acessado fosse invocada.

 

Ainda no exemplo da caixa de brinquedos, se ela não estivesse cheia, ao querer brincar nós procuraríamos o brinquedo e depois de tirar a mão da caixa, olharíamos para o mesmo e verificaríamos se é o brinquedo certo.

 

Em código, isso significa procurar um objeto no Registry e ver se ele é do tipo que você espera.

 

E a partir do momento que começamos a testar esse tipo de coisa, além de desperdiçar performance com verificações, perdemos a confiança na informação, ao passo que definindo lá atrás, antes mesmo de despachar o Controller devido, teremos sempre 100% de certeza de que o bjeto certo estará lá.

 

E, como um Registry é um objeto, tudo aquilo que estiver armazenado nele será automaticamente jogado fora quando o próprio objeto for destruído, manualmente por destruir a variável (o que invocaria seu destrutor), ou automaticamente, quando o fluxo se encerrar.

Compartilhar este post


Link para o post
Compartilhar em outros sites

E a partir do momento que começamos a testar esse tipo de coisa, além de desperdiçar performance com verificações, perdemos a confiança na informação, ao passo que definindo lá atrás, antes mesmo de despachar o Controller devido, teremos sempre 100% de certeza de que o bjeto certo estará lá.

é que estou trabalhando com modulos divididos, então registry veio à mente

caso contrario, teria que passar $conn = new PDO() de modulo em modulo

 

 

E, como um Registry é um objeto, tudo aquilo que estiver armazenado nele será automaticamente jogado fora quando o próprio objeto for destruído, manualmente por destruir a variável (o que invocaria seu destrutor), ou automaticamente, quando o fluxo se encerrar.

se entendi corretamente, irei discordar

um metodo registry para apagar determinado indice da storage não apagaria os outros

Compartilhar este post


Link para o post
Compartilhar em outros sites

É... Faltou um do Registry naquela frase. <_<

 

Deixa eu corrigir:

 

E, como um Registry é um objeto, tudo aquilo que estiver armazenado nele será automaticamente jogado fora quando o próprio objeto do Registry for destruído, manualmente por destruir a variável (o que invocaria seu destrutor), ou automaticamente, quando o fluxo se encerrar.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Sim e não.

 

Sim porque a segurança proporcionada adiciona novas aspas logo após aquelas manualmente abertas. E como algumas das técnicas de SQL Injection envolvem as tais aspas combinadas com operadores de atribuição/comparação, que quando enviados ao banco violam aquilo que foi pré-especificado numa cláusula WHERE, tal procedimento anula a tentativa de injeção.

 

Para entender melhor, veja o Exemplo #3 de PDO::quote().

 

Agora o não é que, se checar o exemplo você terá/poderá ter várias aspas soltas no meio da string,requerendo que se preocupe limpando-as.

 

 

Existem dois tipos de preparação: Aquela que você faz e aquela que você diz para a PDO fazer.

 

Um dos benefícios trazidos pelo MVC (assim como toda arquitetura n-tiers), que é a separação do código em camadas, garante que suas queries não fiquem espalhadas pelo código todo dentro de PDO::prepare() ou PDO::query()

 

Com isso você teria classes ou funções com todas as suas queries armazenadas, centralizando-as num único lugar. Vou demonstrar um dos possíveis casos, mesmo que eu não o use uma vez que meu sistema é bem mais complexo.

 

<?php

class Users {

  // Obtém usuário pelo ID

   public static function getUserById() {
       return 'SELECT * FROM `users` WHERE `id` = ?';
   }
}

Esse tipo de preparação você quem faz pois você já deixou a query prontinha num único lugar.

 

Agora quando você usa essa classe (ou função) você diz à PDO para prepará-la pra você.

 

Essa preparação faz com que, dentre outras coisas, o driver de conexão escolhido no construtor saiba qual é a query e quantos placeholders ela tem. Se estiver usando named placeholders haverá também um tipo de mapeamento para que a atribuição de valores ocorra por nome e não por posição.

 

<?php

$id = ( isset( $_GET['id'] ) && ! empty( $_GET['id'] ) ? (int) $_GET['id'] : NULL );

if( is_null( $id ) || $id == 0 ) {

   // Impede de continuar pois a chave primária começa em 1 (um)
}

$conn = new PDO( /* ... */ );

$stmt = $conn -> prepare( Users::getUserById() );

/**
* O PDOStatement() obtido, depois de preparado, sabe da
* existência de um placeholder, então precisa recebê-lo
*
* Mesmo que seja apenas um, PDOStatement::execute só aceita arrays
*/
if( $stmt -> execute( array( $id ) ) !== FALSE ) {

   $data = $stmt -> fetch();

   if( $data !== FALSE ) {

       /**
        * Se tiver um registro, mostramos
        *
        * Eu prefiro separar pois nem sempre PDOStatement::fetch()
        * vai retornar FALSE SÓ porque newnhum registro foi encontrado
        */
       if( count( $data ) == 1 ) {

           var_dump( $data );

       } else {

           printf( 'Nenhum usuário encontrado para o ID #%d', $id );
       }

   } else {

       // Algum erro ocorreu com PDOStatement::fetch()
   }

} else {

   /**
    * Algum erro ocorreu com PDOStatement::execute()
    *
    * Se uma PDOException for pêga, sempre será FALSE, logo esse condicional é desnecessário
    */
}

 

Um bloco try...catch() serve para você tentar fazer alguma coisa (try) e se alguma coisa der errado com aquilo que estiver tentando fazer E uma Exception for disparada (throw), permitir que você faça outra coisa (catch).

 

E veja o que o manual diz na página das Exceptions:

 

 

Uma tradução livre seria a de que quando uma Exception é disparada todo o código que se seguir não será executado, ficando o interpretador encarregado de buscar na aplicação que usou o recurso o qual disparou a Exception por um catch e então o executar.

 

Se essa busca falhar, um Fatal Error será disparado alertando que uma Exception foi disparada e não pêga (Uncaught) a menos que um Exception Handler customizado tenha sido definido.

 

O código acima, dentro de um try...catch() ficaria:

 

<?php

$id = ( isset( $_GET['id'] ) && ! empty( $_GET['id'] ) ? (int) $_GET['id'] : NULL );

if( is_null( $id ) || $id == 0 ) {

   // Impede de continuar pois a chave primária começa em 1 (um)
}

$conn = new PDO( /* ... */ );

try {

   $stmt = $conn -> prepare( Users::getUserById() );

   $stmt -> execute( array( $id ) );

   $data = $stmt -> fetch();

   if( $data !== FALSE ) {

       if( count( $data ) == 1 ) {

           var_dump( $data );

       } else {

           printf( 'Nenhum usuário encontrado para o ID #%d', $id );
       }

   } else {

       // Algum erro ocorreu com PDOStatement::fetch()
   }
} catch( PDOException $e ) {

   /**
    * Cuidado ao confiar cegamente na mensagem de uma PDOException
    * porque na maioria das vezes que uma delas é disparada por um
    * PDO::prepare() ou PDOStatement::execute() falho
    * a mensagem é gigante e quase incompreensível
    */
   echo $e -> getMessage();
}

Vale lembrar que a PDO só vai disparar Exceptions se tiver recebido o valor PDO::ERRMODE_EXCEPTION para o atributo PDO::ATTR_ERRMODE:

 

$conn = new PDO( /* ... */ );

$conn -> setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

Quantoao fechamento, se o fluxo de execução é interrompido temos um exit/die implícito, terminando o script. E como PDO fecha a conexão automaticamente, não precisa fechá-la novamente.

 

Perceba, no entanto, que em implementações orientadas a objetos invariavelmente a instância da PDO estará armazenada numa propriedade e embora a conexão seja fechada, o objeto atribuído à essa propriedade não será excluído, daí sim você precisa "fechar", mas no contexto de destruir o objeto:

 

$this -> propriedadeComAInstanciaDaPDO = NULL;

Aos leitores, se houverem erros de sintaxe no código ou "palavrassemespaço", peço que desconsiderem. Minha irmã quebrou o teclado de novo.

 

Muito obrigado mais uma vez! Mais uma vez você não explicou, mas deu uma aula sobre o assunto!

 

Então você acha melhor eu preparar a query do que usar o bindParam?

 

Obrigado!

Compartilhar este post


Link para o post
Compartilhar em outros sites

Depende da sua necessidade.

 

PDOStatement::bindParam() (e cia) têm suas vantagens sobre simplesmente informar um array à PDOStatement::execute(), para casos muito específicos, através dos argumentos length e driver_options.

 

Eu nunca precisei deles então não consigo enxergar a aplicabilidade num caso REAL, por isso prefiro via PDOStatement::execute().

Compartilhar este post


Link para o post
Compartilhar em outros sites

PDOStatement::bindParam() é útil quando um valor pode ou não ser declarado em uma SQL, como em consultas "avançadas" num sistema.

 

Por exemplo, se você possui uma busca com 3 parâmetros, consequentemente, 3 campos input, eles serão passados por post e serão tratados por execute. Uma vez que um desse campos, quando passado em branco, não deve ser utilizado na consulta, utiliza-se o bindParam para não ficar repetindo os campos já existentes.

 

exemplo:

if( isset( $param1 ) ) {
$statement->bindParam( ':param1', $param1, PDO::PARAM_INT );
}
if( isset( $param2 ) ) {
$statement->bindParam( ':param2', $param2, PDO::PARAM_STR);
}
if( isset( $param3 ) ) {
$statement->bindParam( ':param3 ', $param3, PDO::PARAM_STR );
}

$statement->execute();

 

Claro que a SQL também deverá ser tradada pelos parâmetros existentes ou não. Mas ai já não vem ao caso.

 

Assim, nesse caso, é muito mais viável utilizá-lo pelo bind ao invés do execute.

 

O que também pode ser feito através de um array, informando ou não os atributos do execute.

$parametros = array();
if( isset( $param1 ) ) {
$parametros[':param1'] = $param1;
}
if( isset( $param2 ) ) {
$parametros[':param2'] = $param2;
}
if( isset( $param3 ) ) {
$parametros[':param3'] = $param3;
}
$statement->execute($parametros);

 

Exceto que, pelo bindParam, pode-se ser definido que tipo de atributo será utilizado no placeholder através das suas constantes.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Nunca usei estes BindParams da vida, parece interessante,porém nem quero esquentar cabeça com isso no momento, não quero re-fazer meu sistema! <_<

Compartilhar este post


Link para o post
Compartilhar em outros sites
não quero re-fazer meu sistema!

Futuramente, pelo que li em um artigo, PDO irá se tornar o padrão e conexão via funções (mysql_connect, mssql_connect, pg_connect, etc..) serão opcionais, mediante ativação no php.ini. Logo, terá de refazer o seu sistema...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Futuramente, pelo que li em um artigo, PDO irá se tornar o padrão e conexão via funções (mysql_connect, mssql_connect, pg_connect, etc..) serão opcionais, mediante ativação no php.ini. Logo, terá de refazer o seu sistema...

 

eu já utilizo PDO há um 'tempo', mas nunca usei esses Binds.

Compartilhar este post


Link para o post
Compartilhar em outros sites

eu já utilizo PDO há um 'tempo', mas nunca usei esses Binds.

ahh, mas utiliza PDOStatement?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Sim, utilizo. Não sei bem a fundo sobre o PDO, acho que só o básico pra fazer minhas coisas mesmo, tô é precisando dar uma estudada melhor sobre ele.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.