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.

Atualizando registros no MySQL

11/09/06

Se você programa em PHP, muito provavelmente já fez algum projeto utilizando banco de dados MySQL. Nem todo mundo conhece algumas funcionalidades que as versões mais novas tem, como a diretriz ON DUPLICATE KEY UPDATE.

Funciona muito bem em aplicações CRUD. Veja um exemplo:

/* MySQL Dump */
CREATE DATABASE url_counter;
CREATE TABLE urls (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20),
    hits INT DEFAULT '0',
    last_visit DATETIME
);

Agora, no PHP, poderíamos ter algo assim:

function insert_url($name)
{
    $query = sprintf("
    INSERT INTO urls 
        (name) VALUES ('%s')
    ", mysql_real_escape_string($name));
    mysql_query($query);
}
 
function update_url($id) {
    $query = sprintf("UPDATE urls SET hits=hits+1 WHERE id=%d", $id);
    mysql_query($query);
}

Agora, se você utilizar a diretriz ON DUPLICATE KEY, é possível fazer tudo isso com apenas uma função:

function url($name, $id='')
{
    $query = sprintf("
    INSERT INTO urls
        (id, name, last_visit, hits)
    VALUES
        (%d, '%s', NOW(), 1)
    ON DUPLICATE KEY UPDATE
        hits=hits+1,
        last_visit=NOW()
    ", $id, $name);
    
    mysql_query($query);
}
 
url('http://google.com/');
url('http://google.com/', 1);
url('http://google.com/', 1);
 
url('http://yahoo.com/');
url('http://yahoo.com/', 2);

Caso o registro não exista, ele adiciona. Se existir, o MySQL atualiza automaticamente os campos indicados na diretriz ON DUPLICATE KEY. Interessante, huh?