implementando segurança em sua aplicação php
DESCRIPTION
Uma variante da palestra "PHP & Segurança - Uma união possível", procura fornecer um pouco mais de detalhes à respeito deste conteúdo.TRANSCRIPT
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Implementando Segurançaem sua aplicação
PHP
v. 1.0 Novembro/2007
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Objetivo:
Esta apresentação tem por objetivo apresentar técnicaspara o desenvolvimento de aplicações seguras utilizando a linguagem PHP, eliminando os equívocos geralmente relacionados à esta.
Conceitos:
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
PHP – Uma linguagem vulnerável?
Um dos equívocos mais comuns relacionados à linguagem PHP é o de que é difícil de se produzir sistemas e ferramentas seguras quando a utilizamos.
Este equívoco é fundamentado em 3 conceitos:
• A simplicidade da linguagem, com uma baixa curva de aprendizado
• A própria fragilidade da web como ambiente de aplicações
• Algumas features presentes na linguagem
Conceitos: Simplicidade != Vulnerabilidade
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
O Problema:
Por ser uma linguagem de fácil aprendizado, é grande o número de pessoas que começam a carreira de desenvolvimento deaplicações utilizando PHP.
Esta falta de experiência freqüentemente resulta em aplicações de baixa qualidade e com problemas relacionados, especialmente, comsegurança.
A Solução:
A comunidade, de forma geral, precisa amadurecer e possuir acesso a recursos que a tornem melhor. Eventos, artigos e a disseminação de boas práticas de desenvolvimento são apenas alguns dos caminhos que estão sendo trilhados para chegarmos a este objetivo.
Conceitos: Web: “A Terra de Ninguém”:
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
O Problema: A web é um ambiente precário em termos de segurança.
Dificuldade para identificar os usuários, ameaças que proliferam às dezenas e usuários descuidados são lugarescomuns quando tratamos de uma aplicação web.
A Solução:
Criar o hábito de pensar qualquer aplicação web, por mais simples que seja, levandose em consideração a questão de segurança.
Termos a noção de que é nossa responsabilidade como desenvolvedores criar aplicações que sejam minimamente seguras.
Conceitos: Aplicações mais maduras:
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
O Problema:
Algumas features que podem ser utilizadas na linguagem, como a ativação da configuração register_globals, facilitam o trabalho, mas inevitavelmente aumentam riscos de segurança e acostumam mal o desenvolvedor.
A Solução:
Nos acostumarmos a desempenhar nosso trabalho da melhor forma possível, tendo sempre em mente que tudo o que é fácil tem seu preço e o que dá mais trabalho hoje causará menos incômodos amanhã.
Boas Práticas: Onde tudo começa
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
register_globals desligada, sempre!
require ao invés de include!
Filtrando a entrada
Erros são para a fase de desenvolvimento!
Coloque tudo em seu devido lugar!
Criptografia: USE!
Conceitos: register_globals desligada, sempre!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
A configuração register_globals é, sem dúvida, uma das mais popularesdisponíveis na configuração do interpretador PHP. Sua utilização porém é uma péssima idéia, por dois motivos:
Tira do programador a noção de origem dos dados.
Tira do interpretador da linguagem a noção de origem dos dados.
A configuração register_globals é tão polêmica que passouà ser desabilitada por default à partir da versão 4.2.0 eserá eliminada na versão 6 da linguagem.
Prova de Caso #1: Ignorando a origem dos dados
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Considere o seguinte código:
<?php// Arquivo: sem_origem.phpif ($autenticado) { echo “Usuário autenticado, prosseguir...”;} else { echo “O usuário não está autenticado!”;}?>
Ao desprezar a origem da informação a variável $autenticado quetipicamente viria de uma sessão ou cookie, por exemplo, poderá ser completamente sobrescrita de forma absurdamente simples:
http://www.galvao.eti.br/sem_origem.php?autenticado=true
Solução: Reconhecendo a origem dos dados
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Observe a mudança no código:
<?php// Arquivo: sem_origem.phpif ($_SESSION['autenticado']) { echo “Usuário autenticado, prosseguir...”;} else { echo “O usuário não está autenticado!”;}?>
Ao explicitar a origem da informação, tornase muito mais difícil forjar a variável $autenticado, pois para isso um cracker teria que forjar toda a origem desta primeiro. Ao receber a URL:
http://www.galvao.eti.br/sem_origem.php?autenticado=true
o interpretador da linguagem jamais confundiria esta informação, pois ela não faz parte do array $_SESSION, mas do array $_GET.
http://www.galvao.eti.br/sem_trava.php?DOCUMENT_ROOT=%2Fcracker%2Fpublic_html
Prova de Caso #2: Ignorando a origem de um script
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Considere o seguinte código:
<?php// Arquivo: sem_trava.phpinclude_once(“$DOCUMENT_ROOT/trava.php”);
if ($trava) { die(“Problemas de autenticação!”);} else { echo “Tudo OK!”;}?>
Se estivermos com a diretiva register_globals ativada, nosso script pode ser facilmente desviado para outro presente em, por exemplo, /cracker/public_html:
<?php// Arquivo: sem_trava.phpinclude_once($_SERVER['DOCUMENT_ROOT'] . “/trava.php”);
if ($trava) { die(“Problemas de autenticação!”);} else { echo “Tudo OK!”;}?>
Solução: Comece fechando o caminho
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
A solução passa por várias ações, mas começa certamente pela não utilização da register_globals:
Novamente explicitamos a origem do dado, impedindo que este dado seja forjado através de um caminho alternativo.
<?phpif ($_POST) {
foreach($_POST as $nome => $valor) {$$nome = $valor;
}}?>
Mantendo a praticidade com segurança
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
O maior empecilho para abandonar o vício da utilização da register_globals é a perda de agilidade no processo de desenvolvimento. Embora a solução apresentada à seguir ainda seja um pouco problemática, é sempre preferível utilizála em detrimento da register_globals:
Ao utilizarmos o conceito de variáveis dinâmicas ou variáveis variáveis,
implementamos uma espécie de versão “light” da register_globals: explicitamos a origem dos dados e ainda assim mantemos uma boa praticidade.
Boas práticas: require ao invés de include!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
A utilização do comando include implica em uma séria questão de segurança: se o arquivo que está sendo solicitado ao servidor não existir ou se qualquer outro imprevisto cause a falha da inclusão do referido arquivo o script continuará normalmente sua execução, como se nenhum problema tivesse ocorrido.
Colocando em termos mais simples possíveis, você estará simplesmentejogando o seu script fora. Variáveis, funções e tudo o mais será completamente ignorado e o script que faria uso deste código continuará executando como se nada tivesse acontecido.
A falha de um comando include não interrompe a execução do script!
Prova de caso #3: o include falhou, o script não!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
1) Considere que o arquivo autenticar.php barra o usuário em caso de problemas de autenticação e/ou autorização.
Se por qualquer eventualidade – como um erro de digitação por exemplo – o comando include falhar ainda assim o script continuará sendo executado normalmente, como se não houvesse problemas.
<?phpinclude_once “auteticar.php”;
if (!isLoggedIn()) { die(“Problemas de autenticação!”);} else { echo “Informações sigilosas do usuário”;}?>
2) Considere o seguinte código, que confia nas rotinas presentes em autenticar.php para mostrar ou não informações sigilosas:
Solução: Se é importante não corra riscos!
<?phprequire_once “auteticar.php”;
if (!isLoggedIn()) { die(“Problemas de autenticação!”);} else { echo “Tudo OK!”;}?>
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
A simples troca do comando include pelo comando require informa aointerpretador PHP – note o sentido da palavra – que o arquivo não está sendo simplesmente incluído, mas é requerido para que a aplicação funcione corretamente.
A diferença – crucial, nesse caso – é que nosso script nem mesmo fará o teste condicional, ele será imediatamente interrompido na primeira linha, quando o arquivo não for encontrado.
Desejo continuar usando o comando include
<?phpinclude_once “auteticar.php” or die(“Falha de comando include”);
if (!isLoggedIn()) { die(“Problemas de autenticação!”);} else { echo “Tudo OK!”;}?>
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Sem problema. Existe uma forma de “emularmos” o comportamento do comando require: informamos nosso código o que ele deve fazer caso o comando include falhe.
Como no caso de uma falha a operação de inclusão não será executada o comando que segue após o operador or será executado em seu lugar: neste caso a interrupção imediata do script e a exibição de uma mensagem.
Boas práticas: Não confie em dados de estranhos
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Uma das principais técnicas de segurança envolve a filtragem de dados.A falta de filtragem é a principal causas de muitas vulnerabilidades, entre elas a famosa SQL Injection, ou injeção de SQL.
Quando trabalhamos levando em conta a filtragem de dados todo o dado é suspeito, ou contaminado, até que seja validado.
Um dos equívocos mais comuns nesse sentido é quando tratamos esta“falta de confiança” como uma premissa de que o usuário é malintencionado. Na realidade problemas de segurança podem também ser causados por erros e descuidos.
Jamais, em hipótese alguma, confie no usuário!
Prova de caso #4: Dados contaminados == SQL Injection!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
1) Considere este código que chamaremos de mostraUsuario.php:
<?php$conn = mysql_connect('servidor', usuario', 'senha');mysql_select_db('meu_banco');
$sql = 'SELECT email FROM usuarios WHERE idusuario = ' .
$_GET['userid'];
$recordSet = mysql_query($sql);?>
Seu código aceitará qualquer informação vinda da query string!
2) Observe que não há filtragem do dado de entrada: $_GET['userid']
Prova de caso #4: Dados contaminados == SQL Injection!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Desta forma é simples injetar código SQL na aplicação, pois qualquer dado passado para o script será aceito sem hesitação:
Como 'x' é sempre igual à 'x' a condição anterior terá sido invalidada pelo OR e o cracker terá em mãos todos os emails armazendos na base de dados.
http://www.galvao.eti.br/mostraUsuario.php?userid=198%20OR%20'x'='x'
Após a conversão dos caracteres codificados (%20, ou seja, espaço em branco), esta é a query que será executada em nosso ingênuo script:
SELECT email FROM usuarios WHERE idusuario = 198 OR 'x' = 'x'
Solução: Se a query aguarda um número, receba um número!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Quando nossa query espera receber um número a solução é simples: forçamos o dado à ser numérico mesmo que ele não seja, fazendo com que qualquer tentativa de injeção de SQL se transforme apenas na partenumérica da string recebida ou no número 1, dependendo do caso:
<?php$conn = mysql_connect('servidor', usuario', 'senha');mysql_select_db('meu_banco');
settype($_GET['userid'], integer);
$sql = 'SELECT email FROM usuarios WHERE idusuario = ' .
$_GET['userid'];
$recordSet = mysql_query($sql);?>
Não basta forçar a tipagem: o perigo do que não é óbvio
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Um dos maiores problemas para se desenvolver uma aplicação segura é quando existe um risco que não é óbvio. Partindo do que vimos, tornase óbvio o perigo de se aceitar dados não tratados. O que não fica claro é que qualquer número será válido, independente de quem esteja visualizando a informação.
<?phpsettype($_GET['userid'], integer);
if ($_SESSION['uid'] == $_GET['userid']) {// Código
}?>
Através de uma verificação relativamente simples garantimos que mesmo que o dado informado seja aparentemente inocente a informação esteja protegida de quem não deve ter acesso à ela.
SQL Injection com strings: o que era dor de cabeça virou enxaqueca
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Quando trabalhamos com strings, as coisas se complicam, pelo simples fato de que textos possuem personalidade, sentidos múltiplos, questões realmente difíceis de se interpretar logicamente. A chave aqui é tomarmos cuidado apenas com caracteres específicos que podem, por falta de um tratamento adequado, ser interpretados pela base de dados como caracteres SQL válidos:
' aspas simples – INSERT INTO foo ('bar's'); hífen – INSERT INTO foo ('bar' ');; pontoevírgula – INSERT INTO FOO ('bar', 1, false);');
Uma coisa que nem sempre é óbvia é que nem sempre um ataque de SQL Injection destinase à manipular dados na base, mas às vezes o objetivo é simplesmente fazer sua aplicação falhar, para tentar, dessa forma ler uma mensagem de erro na tela. É por isso que:
Funções para auxiliar na prevenção de injeção de SQL:
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Uma função muito utilizada para evitar ataques é a função addslashes.Embora esta função solucione a técnica mais comum de injeção que é ainserção de aspas simples, ela não ocasiona o escape dos demais caracteresvistos.
Para solucionar este problema, utilizamos a função addcslashes, quenos dá uma maior liberdade, permitindo que informemos cada caractereque deve ser escapado:
$dado_tratado = addcslashes($_GET['userid'], “';”);
Não reinvente a roda
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Utilizese também de funções, bibliotecas e classes que já trabalhamcom a filtragem de informação. Desta forma você contribui para ofortalecimento da comunidade, agiliza seu trabalho e conta com códigosque possuem qualidade, manutenção e suporte.
● mysql_real_escape_string, pg_escape_string, etc...
● Pear::MDB2
● ADODB
● PDO
● etc...
Boas práticas: Longe dos olhos... dos usuários
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Mensagens de erro são cruciais. É através dela que o trabalho dedesenvolvimento se torna mais ágil, e são elas que nos apontam nossos pequenos deslizes.
O problema é que, quando mantemos as mensagens de erro visíveis para qualquer usuário, corremos o risco de passar informações importantes sobre o sistema e até mesmo sobre oservidor para qualquer usuário ler.
Mensagens de erro frequentemente carregam informaçõescomprometedoras!
Prova de caso #5: Informação demais!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Considere um erro de conexão mySQL típico:
Warning: mysql_connect() [function.mysqlconnect]: Access denied for user 'foo'@'localhost' (using password: YES) in /usr/local/apache/htdocs/script.php on line 2
1) O tipo de banco utilizado: mysql2) O usuário de conexão: foo3) Que este usuário está tentando se conectar de dentro do próprio servidor: @localhost4) O path absoluto em disco do script: /usr/local/apache/htdocs5) O nome do script: script.php
Esta inocente mensagem revela 5 informações que só são úteis ao próprio desenvolvedor ou à um cracker:
Solução: Se está em produção, grave um log!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Três diretivas de configuração serão responsáveis por esconder os errose graválos em um arquivo de log:
A primeira desativa a exibição dos erros. A segunda ativa a gravação de erros em arquivo. A terceira diz que arquivo será esse que receberá as mensagens.
1) display_errors: off ou 02) log_errors: on ou 13) error_log: /caminho/para/arquivo.log
Boas práticas: Longe do browser
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
O servidor web, e consequentemente o browser, não precisa acessar todos os arquivos que você possui na sua aplicação, especialmente se este arquivo não gera saída HTML ou se serve apenas de arquivo de configuração.
Se o Browser enxerga, qualquer um enxerga!A raiz web é um local público e portanto inerentemente vulnerável!
Prova de caso #6: Entregando o ouro!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Considere o seguinte arquivo de configuração típico, que chamaremospelo enigmático nome de config.php:
<?php$dbserver = 'localhost';$dbtype= 'mysql';$dbuser = 'foo';$dbpass = 'bar';?>
Se este arquivo estiver gravado em um diretório acessível ao servidorweb, ele estará acessível à um script PHP que lerá se ele gera saída de informações, por exemplo:
<?phpfile_get_contents('http://www.galvao.eti.br/config.php');?>
Solução: Esconda o que não precisa ser visto!
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Contanto que as permissões (chmod/chown) sejam setadas corretamente, o arquivo PHP pode tranquilamente ser colocado em um diretório longe da raiz utilizada pelo servidor web.
O que não está na raiz web não pode ser acessado pelo servidor web e nem pelo browser.
Boas práticas: 97c6105c1d97d600ec16ab4abace6d4c
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Dados sigilosos são chamados assim por um motivo. Quando tratamosespecificamente de senhas é impressionante a quantidade de aplicações web que grava senhas em texto puro na base de dados.
Senhas são pessoais, intransferíveis e confidenciais! Ninguém além do próprio usuário deve ter acesso à senha!
PHP implementa criptografia das mais variadas formas, pesquise,aprenda e use!
• MD5• SHA1• mcrypt• hashetc...
Sobre o autor:
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Er Galvão Abbott trabalha há mais de dez anos com programação de websites e sistemas corporativos com interface web.
Autodidata, teve seu primeiro contato com a linguagem HTML em 1995, quando a internet estreava no Brasil.
Além de lecionar em cursos, escrever artigos e ministrar palestras, tem se dedicado ao desenvolvimento de aplicações web, tendo nas linguagens PHP, Perl e JavaScript suas principais paixões.
É o fundador e líder do UG PHP RS, Grupo de Usuários que contahoje com mais de 600 associados em todo o Brasil.
Contatos:
© 2007 Er Galvão Abbott http://www.galvao.eti.br/
Contatos com o autor:
Web: http://www.galvao.eti.br http://blog.galvao.eti.br/ http://www.phprs.com.br
Email: [email protected]
IM: [email protected] (MSN) [email protected] (GoogleTalk)