ConFoo 2025

Segurança do Sistema de Arquivos

Índice

O PHP está sujeito à segurança encontrada na maioria dos sistemas de servidor com respeito à permissões de arquivos e diretórios. Isso permite que seja controlado que arquivos no sistema podem ser lidos e por quem. É preciso ter cuidado com quaisquer arquivos que são lidos por todos para assegurar que eles podem ser lidos por todos os usuários que têm acesso ao sistema de arquivos.

Já que o PHP foi projetado para permitir acesso em nível de usuário ao sistema de arquivos, é possível escrever um script PHP que permitirá ler arquivos do sistema como o /etc/passwd, modificar suas conexões de rede, enviar inúmeros trabalhos de impressão, etc. Isso tem algumas implicações óbvias, já que é necessário garantir que os arquivos lidos e gravados são apropriados.

Considere o seguinte script, onde um usuário indica que quer apagar um arquivo no seu diretório "home". Isso presume uma situação onde uma interface web PHP é usada regularmente para controle de arquivos, então o usuário do Apache tem permissão de apagar arquivos nos diretórios "home" dos usuários.

Exemplo #1 Verificação fraca de variáveis resulta em....

<?php

// Remove um arquivo do diretório "home" do usuário
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";

unlink("$homedir/$userfile");

echo
"O arquivo foi removido!";

?>
Já que o nome do usuário e do arquivo são enviáveis pelo formulário, um usuário pode enviar um nome de usuário e de arquivo que pertença a outra pessoa, e apagá-lo, mesmo que eles não tenham permissão para fazê-lo. Nesse caso, é preciso ter alguma outra forma de autenticação. Considere o que poderia acontecer se as variáveis enviadas forem "../etc/" e "passwd". O código então leria efetivamente:

Exemplo #2 ... um ataque ao sistema de arquivos

<?php

// Remove um arquivo de qualquer lugar no disco rígido
// que o usuário do PHP tenha acesso. Se o PHP tiver acesso de administrador (root):
$username = $_POST['user_submitted_name']; // "../etc"
$userfile = $_POST['user_submitted_filename']; // "passwd"
$homedir = "/home/$username"; // "/home/../etc"

unlink("$homedir/$userfile"); // "/home/../etc/passwd"

echo "O arquivo foi removido!";

?>
Existem duas medidas importantes que você deve tomar para previnir esses problemas.
  • Dar permissões limitadas ao usuário web binário do PHP.
  • Checar todas as variáveis que são enviadas.
Aqui temos um script melhorado:

Exemplo #3 Checagem com mais segura do nome do arquivo

<?php

// Remove um arquivo do disco rígido que o
// usuário do PHP tenha acesso.
$username = $_SERVER['REMOTE_USER']; // usando um mecanismo de autenticação
$userfile = basename($_POST['user_submitted_filename']);
$homedir = "/home/$username";

$filepath = "$homedir/$userfile";

if (
file_exists($filepath) && unlink($filepath)) {
$logstring = "Removido $filepath\n";
} else {
$logstring = "Falhar ao remover $filepath\n";
}

$fp = fopen("/home/logging/filedelete.log", "a");
fwrite($fp, $logstring);
fclose($fp);

echo
htmlentities($logstring, ENT_QUOTES);

?>
No entanto, ele ainda possui falhas. Se seu sistema de autenticação permitir que os usuários criem seu próprios logins, e um usuário escolher o login "../etc/", o sistema está novamente exposto. Por essa razão, é preferível escrever uma verificação mais personalizada:

Exemplo #4 Verificação mais segura do nome do arquivo

<?php

$username
= $_SERVER['REMOTE_USER']; // usando um mecanismo de autenticação
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";

$filepath = "$homedir/$userfile";

if (!
ctype_alnum($username) || !preg_match('/^(?:[a-z0-9_-]|\.(?!\.))+$/iD', $userfile)) {
die(
"Usuário/arquivo inválido");
}

// etc.

?>

Dependendo do sistema operacional, existe uma variedade enorme de arquivos com os quais se preocupar, incluindo dispositivos (/dev/ ou COM1), arquivos de configuração (arquivos /etc/ e arquivos .ini), áreas de armazenamento de arquivo bem conhecidas (/home/, My Documents), etc. Por essa razão, normalmente é mais fácil criar uma política onde se proibe tudo exceto aquilo que for explicitamente permitido.

adicione uma nota

Notas Enviadas por Usuários (em inglês) 5 notes

up
99
anonymous
19 years ago
(A) Better not to create files or folders with user-supplied names. If you do not validate enough, you can have trouble. Instead create files and folders with randomly generated names like fg3754jk3h and store the username and this file or folder name in a table named, say, user_objects. This will ensure that whatever the user may type, the command going to the shell will contain values from a specific set only and no mischief can be done.

(B) The same applies to commands executed based on an operation that the user chooses. Better not to allow any part of the user's input to go to the command that you will execute. Instead, keep a fixed set of commands and based on what the user has input, and run those only.

For example,
(A) Keep a table named, say, user_objects with values like:
username|chosen_name |actual_name|file_or_dir
--------|--------------|-----------|-----------
jdoe |trekphotos |m5fg767h67 |D
jdoe |notes.txt |nm4b6jh756 |F
tim1997 |_imp_ folder |45jkh64j56 |D

and always use the actual_name in the filesystem operations rather than the user supplied names.

(B)
<?php
$op
= $_POST['op'];//after a lot of validations
$dir = $_POST['dirname'];//after a lot of validations or maybe you can use technique (A)
switch($op){
case
"cd":
chdir($dir);
break;
case
"rd":
rmdir($dir);
break;
.....
default:
mail("webmaster@example.com", "Mischief", $_SERVER['REMOTE_ADDR']." is probably attempting an attack.");
}
up
25
fmrose at ncsu dot edu
19 years ago
All of the fixes here assume that it is necessary to allow the user to enter system sensitive information to begin with. The proper way to handle this would be to provide something like a numbered list of files to perform an unlink action on and then the chooses the matching number. There is no way for the user to specify a clever attack circumventing whatever pattern matching filename exclusion syntax that you may have.

Anytime you have a security issue, the proper behaviour is to deny all then allow specific instances, not allow all and restrict. For the simple reason that you may not think of every possible restriction.
up
22
devik at cdi dot cz
23 years ago
Well, the fact that all users run under the same UID is a big problem. Userspace security hacks (ala safe_mode) should not be substitution for proper kernel level security checks/accounting.
Good news: Apache 2 allows you to assign UIDs for different vhosts.
devik
up
10
Latchezar Tzvetkoff
15 years ago
A basic filename/directory/symlink checking may be done (and I personally do) via realpath() ...

<?php

if (isset($_GET['file'])) {
$base = '/home/polizei/public_html/'; // it seems this one is good to be realpath too.. meaning not a symlinked path..
if (strpos($file = realpath($base.$_GET['file']), $base) === 0 && is_file($file)) {
unlink($file);
} else {
die(
'blah!');
}
}
?>
up
6
cronos586(AT)caramail(DOT)com
22 years ago
when using Apache you might consider a apache_lookup_uri on the path, to discover the real path, regardless of any directory trickery.
then, look at the prefix, and compare with a list of allowed prefixes.
for example, my source.php for my website includes:
if(isset($doc)) {
$apacheres = apache_lookup_uri($doc);
$really = realpath($apacheres->filename);
if(substr($really, 0, strlen($DOCUMENT_ROOT)) == $DOCUMENT_ROOT) {
if(is_file($really)) {
show_source($really);
}
}
}
hope this helps
regards,
KAT44
To Top