PHP e SQL Injection

07/11/06

Estava explicando para um amigo meu porque não se deve interpolar uma variável direto no SQL. O grande problema é que ao fazer desta maneira, seu sistema fica suscetível a SQL Injection.

Imagine que você faça algo como abaixo.

<?php
$query = "SELECT name, email, password FROM table WHERE field='".$_GET['field']."' LIMIT 1";
?>

Se alguém passar no parâmetro algo como ' OR 1=1 #, seu banco ficaria totalmente exposto, pois a string final seria modificada com uma condição que será sempre satisfeita.

SELECT name, email, password FROM table WHERE field = '' OR 1=1 #' LIMIT 1

Parabéns! Alguém acabou de pegar todos os registros de sua tabela. Para evitar esse tipo de ataque, é aconselhável usar a função mysql_real_escape_string. Ela faz o devido escape de caracteres potencialmente inseguros. A desvantagem é que você tem que fazer isso para cada um dos parâmetros que você vai passar, o que pode se tornar um pouco massante.

Então, passei para ele uma função que eu usava há algum tempo atrás.

<?php
/**
 * @param string $query
 * @param mixed $arg1, $arg2...$argN 
 */
$QUERY = "";
function query($query)
{
    global $QUERY;
    
    $args = func_get_args();
    $query = array_shift($args);
    
    foreach ($args as $key => $arg) {
        if (is_string($arg)) {
            $args[$key] = mysql_real_escape_string($arg);
        }
    }
    
    array_unshift($args, $query);
    $query = call_user_func_array('sprintf', $args);
    $QUERY = $query;
    
    return mysql_query($query);
}
?>

Use a função acima da seguinte maneira:

<?php
$query = "SELECT name, email, password FROM table WHERE field='%s' LIMIT 1";
$resource = query($query, $_GET['field']);
 
while ($row = mysql_fetch_object($resource)) {
    printf('<strong>%s:</strong> %s<br/>', $row->name, $row->email);
}
?>

Você pode passar quantos parâmetros precisar, seguindo as regras da função sprintf.

<?php
$query = "INSERT INTO table (amount, age, name) VALUES (%.2f, %d, '%s')";
$resource = query($query, 150, 27, 'Nando Vieira');
?>

Uma outra vantagem de usar a função acima é que se você esquecer de passar algum parâmetro que estava esperando receber, um erro será exibido. E de quebra você ganha uma variável global $QUERY com a última instrução executada.

Comentários #


#1 Fernando Bittencourt disse:
07 Nov 06, 08:39PM

A idéia, em si, é boa, mas não funcionará em versões do PHP inferiores a 4.3. Em todo caso, é possível criar funções do tipo "eh_um_numero_de_verdade()" e "eh_string_mesmo()", hehe, obviamente não com esses nomes esdrúxulos. Apesar de mais simples, é uma forma de garantir que, de onde deve vir um número, não venha uma string maliciosa, por exemplo.
A propósito, belo blog! Estou esperando a parte 2 do post sobre as extensões do Firefox.

Um abraço.

#2 Gean disse:
08 Nov 06, 12:23PM

Excelente dica para os programadores menos precavidos.

Eu sempre usei uma outra rotina parecida com essa sua, mas acabei perdendo, mas já guardei essa sua na manga, sempre é bom ter essas coisas por perto qdo se está nesse mundo.

Obrigado,

Gean.

#3 Nando Vieira disse:
08 Nov 06, 01:55PM

Fernando: no caso das versões inferiores você pode usar a função mysql_escape_string, apesar de obsoleta. A segunda parte do artigo de extensões já está sendo escrito!

#4 João disse:
08 Nov 06, 02:47PM

Olá Nando, o que acha disso?

function antiSqlInje($dado) {

$dado = strip_tags($dado);
$dado = trim($dado);
$dado = get_magic_quotes_gpc() == 0 ? addslashes($dado) : $dado;
$dado = preg_replace("@(--|\#|;)@s", "", $dado);
return $dado;
}

é funcional?

#5 Nando Vieira disse:
09 Nov 06, 08:50AM

João: ela é bem genérica, visto que não necessariamente quero matar as tags ou retirar todos os espaços. No caso do GPC, você está supondo que o $dado veio através de POST, COOKIE ou GET. Na expressão regular, você pode matar algo como "-- texto --" que no markdown é substituído por &#8212; teste &#8212;. Mas se você não cai em nenhum dos casos que falei, serve muito bem.

Deixe um comentário




Este blog usa o Gravatar.


Não é aceito código HTML:
adicione-o no pastie.caboo.se ou paste.milk-it.net e poste apenas o link.

Se este é seu primeiro comentário, ele terá que ser aprovado antes de ser exibido.