Quando se trata de consultas de banco de dados, sempre tente usar consultas parametrizadas preparadas. As bibliotecas mysqli
e PDO
suportam isso. Isso é infinitamente mais seguro do que usar funções de escape, como mysql_real_escape_string
.
Sim, mysql_real_escape_string
é efetivamente apenas uma função de escape de string. Não é uma bala mágica. Tudo o que fará é escapar caracteres perigosos para que possam ser usados com segurança em uma única string de consulta. No entanto, se você não higienizar suas entradas de antemão, ficará vulnerável a certos vetores de ataque.
Imagine o seguinte SQL:
$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);
Você deve ser capaz de ver que isso é vulnerável a exploração.
Imagine que o id
parâmetro contenha o vetor de ataque comum:
1 OR 1=1
Não há caracteres arriscados para codificar, então ele passará direto pelo filtro de escape. Deixando-nos:
SELECT fields FROM table WHERE id= 1 OR 1=1
Que é um adorável vetor de injeção de SQL e permitiria ao invasor retornar todas as linhas. Ou
1 or is_admin=1 order by id limit 1
que produz
SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1
O que permite que o invasor retorne os detalhes do primeiro administrador neste exemplo completamente fictício.
Embora essas funções sejam úteis, elas devem ser usadas com cuidado. Você precisa garantir que todas as entradas da web sejam validadas em algum grau. Neste caso, vemos que podemos ser explorados porque não verificamos se uma variável que estávamos usando como um número era realmente numérica. Em PHP, você deve usar amplamente um conjunto de funções para verificar se as entradas são inteiros, flutuantes, alfanuméricos, etc. Mas quando se trata de SQL, preste atenção ao valor da instrução preparada. O código acima teria sido seguro se fosse uma instrução preparada, pois as funções do banco de dados saberiam que 1 OR 1=1
não é um literal válido.
Quanto a htmlspecialchars()
. Esse é um campo minado por si só.
Há um problema real no PHP em que ele tem uma seleção inteira de diferentes funções de escape relacionadas ao html, e nenhuma orientação clara sobre exatamente quais funções fazem o quê.
Em primeiro lugar, se você estiver dentro de uma tag HTML, você está realmente com problemas. Olhe para
echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';
Já estamos dentro de uma tag HTML, então não precisamos <ou> fazer nada perigoso. Nosso vetor de ataque poderia ser apenasjavascript:alert(document.cookie)
Agora o HTML resultante parece
<img src= "javascript:alert(document.cookie)" />
O ataque é direto.
Fica pior. Por quê? porque htmlspecialchars
(quando chamado dessa forma) codifica apenas aspas duplas e não simples. Então, se tivéssemos
echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";
Nosso atacante malvado agora pode injetar parâmetros totalmente novos
pic.png' onclick='location.href=xxx' onmouseover='...
nos dá
<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />
Nesses casos, não há fórmula mágica, você apenas precisa santificar a entrada você mesmo. Se você tentar filtrar caracteres ruins, certamente falhará. Use uma abordagem de lista de permissões e deixe passar apenas os caracteres que são bons. Veja a folha de dicas do XSS para exemplos de como diversos vetores podem ser
Mesmo se você usar htmlspecialchars($string)
fora das tags HTML, ainda estará vulnerável a vetores de ataque de conjuntos de caracteres multibyte.
O mais eficaz que você pode ser é usar uma combinação de mb_convert_encoding e htmlentities como segue.
$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');
Mesmo isso deixa o IE6 vulnerável, devido à maneira como ele lida com UTF. No entanto, você pode recorrer a uma codificação mais limitada, como ISO-8859-1, até que o uso do IE6 diminua.
Para um estudo mais aprofundado dos problemas multibyte, consulte https://stackoverflow.com/a/12118602/1820