Skip to content

Prepared Statements contra SQL Injection

by João Pedro Pereira on July 27th, 2011

É muito comum quando se estuda uma linguagem não se dar principal foco ao desenvolvimento em segurança, mas este, em ambientes de produção é um dos factores decisivos do sucesso de uma aplicação e da confiança por parte dos utilizadores.
Neste artigo vai ser abordada uma forma de defesa relativa a possíveis ataques de SQL Injection em PHP utilizando o SGDB MySQL, e com recurso a Prepared Statements.

Existe suporte oficial a várias bases de dados: CUBRID, MS SQL Server, Firebird/Interbase, IBM, Informix, MySQL, MS SQL Server, Oracle, ODBC e DB2, PostgreSQL, SQLite, 4D [print_r(PDO::getAvailableDrivers());]

Vantagens e Desvantagens

Vantagens:
i. Previne SQL Injection
ii. Melhora a performance (pode só ser notado quando usado em grandes plataformas)
iii. Mais simples de escrever e ler

Desvantagens e Limitações:
i. Limitado a: SELECT, INSERT, UPDATE, REPLACE, DELETE, e CREATE TABLE
ii. Placeholders apenas para valores e nunca para nomes de tabelas ou colunas
iii. Quando precisamos de utilizar o método bind_result no escopo de uma classe e não sabemos à partida quantas variáveis vão ser passadas. Problema analisado por Jeffrey Way @ NetTuts (http://net.tutsplus.com/tutorials/php/the-problem-with-phps-prepared-statements/)

addslashes vs mysql_real_escape_string vs Prepared Statements

As funções addslashes e mysql_real_escape_string não serão objecto de estudo neste artigo, será apenas faladas as vulnerabilidades que não acontecem com o uso de Prepared Statements de modo a mostrar que esta última alternativa é de facto a mais segura.

O Chris Shiflett (http://shiflett.org/archive/184) já analisou as vulnerabilidades das addslashes relativas ao abuso de caracteres multibyte.

A função mysql_real_escape_string não é muito diferente da addslashes ao nível do tipo de vulnerabilidade, na verdade a sua vantagem reside mesmo em ter em conta o character set a ser utilizado conseguindo determinar como analisar a informção passada. Contudo continuam a existir vulnerabilidades, por exemplo com a utilização da codificação GBK e da sequ?ncia 0xbf27 (¿’) que passada nesta função não terá o resultado pretendido de ¿\’ . (POC http://ilia.ws/archives/103-mysql_real_escape_string-versus-Prepared-Statements.html )

Prepared Statements em Uso

Vejamos então um exemplo bastante simples…

$login = $_GET['login']
$query = "SELECT * FROM registos WHERE login = '$login'";

em que /?login=’ OR 1′;

Assim sendo
A query ficará algo do género:

$query = "SELECT * FROM registos WHERE login = '' OR 1";

O que resultaria num retorno de todas as informações da base de dados.

Se neste caso especifico fosse utilizado o método de destaque deste artigo: Prepared Statements, teriamos de colocar um placeholder (para marcar o local onde serão inseridos os dados a introduzir / consultar na base de dados, isto faz com que nos locais onde estão colocados os placeholders não sejam feitos pedidos à base de dados ou alteração ao pedido em questão. É aqui que reside a força deste método.

Existem duas formas de usar placeholders tal como referido no Manual do PHP.net

Forma #1:

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);

Forma #2:

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
$stmt->bindParam(1, $name);
$stmt->bindParam(2, $value);

Voltando então ao exemplo anterior, a nossa expressão seria então:

$query = "SELECT * FROM registos WHERE login = ?";

Nota: Apesar da extensão MySQLi também suportar prepared statements é aconselhável a utilização da extensão PDO (PHP Data Objects)

$login = "joaoppereira";
$query = "SELECT * FROM registos WHERE login = ?";
 
$stmt = $pdo->prepare($query); 
$stmt->bindValue(1, $login, PDO::PARAM_STR);
$ok = $stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

A função bindValue dos PDOStatement permite simples validações de tipo de dados no terceiro parâmetro da função bindValue(parametro, valor, tipo_dado), com as seguintes opções:

- PDO::PARAM_STR
- PDO::PARAM_INT
- PDO::PARAM_BOOL
- PDO::PARAM_NULL

Este foi só um artigo introdutório a este tema, existe agora imenso material no Manual do PHP.net e um pouco por toda a internet relativamente a esta temática para os que quiserem, e bem, aprofundar os conhecimentos relativos a Prepared Statements e PDO.

Bibliografia:

http://php.net/manual/en/pdo.prepared-statements.php
http://www.php.net/manual/en/class.pdostatement.php
http://www.php.net/manual/en/pdo.constants.php
http://www.php.net/manual/en/pdo.drivers.php
http://net.tutsplus.com/tutorials/php/the-problem-with-phps-prepared-statements/

2 Comments
  1. Estava procurando post sobre SQL Injection, gostei da relação de links que você colocou, valeu!!!

  2. Trabalho há imenso tempo com PHP. Mas admiro muito conteúdo bem explicado e bem preparado. Abraços pra você meu amigo.

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS