Seguridad del Sistema de Archivos

Tabla de contenidos

PHP está sujeto a la seguridad integrada en la mayoría de sistemas de servidores con respecto a los permisos de archivos y directorios. Esto permite controlar qué archivos en el sistema de archivos se pueden leer. Se debe tener cuidado con los archivos que son legibles para garantizar que son seguros para la lectura por todos los usuarios que tienen acceso al sistema de archivos.

Desde que PHP fue diseñado para permitir el acceso a nivel de usuarios para el sistema de archivos, es perfectamente posible escribir un script PHP que le permita leer archivos del sistema como /etc/passwd, modificar sus conexiones de red, enviar trabajos de impresión masiva, etc. Esto tiene algunas implicaciones obvias, es necesario asegurarse que los archivos que se van a leer o escribir son los apropiados.

Considere el siguiente script, donde un usuario indica que quiere borrar un archivo en su directorio home. Esto supone una situación en la que una interfaz web en PHP es usada regularmente para gestionar archivos, por lo que es necesario que el usuario Apache pueda borrar archivos en los directorios home de los usuarios.

Ejemplo #1 Un control pobre puede llevar a ....

<?php
// eliminar un archivo del directorio personal del usuario
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";

unlink("$homedir/$userfile");

echo
"El archivo ha sido eliminado!";
?>
Dado que el nombre de usuario y el nombre del archivo son enviados desde un formulario, estos pueden representar un nombre de archivo y un nombre de usuario que pertenecen a otra persona, incluso se podría borrar el archivo a pesar que se supone que no estaría permitido hacerlo. En este caso, usted desearía usar algún otro tipo de autenticación. Considere lo que podría suceder si las variables enviadas son "../etc/" y "passwd". El código entonces se ejecutaría efectivamente como:

Ejemplo #2 ... Un ataque al sistema de archivos

<?php
// elimina un archivo desde cualquier lugar en el disco duro al que
// el usuario de PHP tiene acceso. Si PHP tiene acceso de 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 "El archivo ha sido eliminado!";
?>
Hay dos medidas importantes que usted debe tomar para prevenir estas cuestiones.
  • Únicamente permisos limitados al usuario web de PHP.
  • Revise todas las variables que se envían.
Aquí está una versión mejorada del script:

Ejemplo #3 Comprobación más segura del nombre de archivo

<?php
// elimina un archivo del disco duro al que
// el usuario de PHP tiene acceso.
$username = $_SERVER['REMOTE_USER']; // usando un mecanismo de autenticación
$userfile = basename($_POST['user_submitted_filename']);
$homedir = "/home/$username";

$filepath = "$homedir/$userfile";

if (
file_exists($filepath) && unlink($filepath)) {
$logstring = "Se ha eliminado $filepath\n";
} else {
$logstring = "No se ha podido eliminar $filepath\n";
}
$fp = fopen("/home/logging/filedelete.log", "a");
fwrite($fp, $logstring);
fclose($fp);

echo
htmlentities($logstring, ENT_QUOTES);

?>
Sin embargo, incluso esto no está exento de defectos. Si la autenticación del sistema permite a los usuarios crear sus propios inicios de sesión de usuario, y un usuario eligió la entrada "../etc/", el sistema está expuesto una vez más. Por esta razón, puede que prefiera escribir un chequeo más personalizado:

Ejemplo #4 Comprobación más segura del nombre de archivo

<?php
$username
= $_SERVER['REMOTE_USER']; // usando un mecanismo de autenticación
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";

$filepath = "$homedir/$userfile";

if (!
ctype_alnum($username) || !preg_match('/^(?:[a-z0-9_-]|\.(?!\.))+$/iD', $userfile)) {
die(
"nombre de usuario o nombre de archivo incorrecto");
}

//etc...
?>

Dependiendo de sus sistema operativo, hay una gran variedad de archivos a los que debe estar atento, esto incluye las entradas de dispositivos (/dev/ o COM1), archivos de configuracion (archivos /etc/ y archivos .ini), las muy conocidas carpetas de almacenamiento (/home/, Mis documentos), etc. Por esta razón, por lo general es más fácil crear una política en donde se prohíba todo excepto lo que expresamente se permite.

add a note add a note

User Contributed Notes 7 notes

up
73
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
16
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
14
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
6
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
5
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
up
-4
1 at 234 dot cx
19 years ago
I don't think the filename validation solution from Jones at partykel is complete.  It certainly helps, but it doesn't address the case where the user is able to create a symlink pointing from his home directory to the root.  He might then ask to unlink "foo/etc/passwd" which would be in his home directory, except that foo is a symlink pointing to /.

Personally I wouldn't feel confident that any solution to this problem would keep my system secure.  Running PHP as root (or some equivalent which can unlink files in all users' home directories) is asking for trouble.

If you have a multi-user system and you are afraid that users may install scripts like this, try security-enhanced Linux.  It won't give total protection, but it at least makes sure that an insecure user script can only affect files which the web server is meant to have access to.  Whatever script someone installs, outsiders are not going to be able to read your password file---or remove it.
up
-10
eLuddite at not dot a dot real dot addressnsky dot ru
24 years ago
I think the lesson is clear:

(1) Forbit path separators in usernames.
(2) map username to a physical home directory - /home/username is fine
(3) read the home directory
(4) present only results of (3) as an option for deletion.

I have discovered a marvelous method of doing the above in php but this submission box is too small to contain it.

:-)
To Top