It seems to me in this particular instance that a simple check to make sure that name or partial pathname doesn't already exist would prevent this attack... if a 'passwd/etc/...' existed as the password directory, you couldn't create a username to exploit the hole in the first place. But that's only from a 'script user' perspective, it still doesn't protect your server from other sub-admin's badly written code.
Don For
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 você controle quais 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 tem acesso ao sistema de arquivos.
Já que o PHP foi desenhado para permitir acesso em nível de usuário ao sistema de aruqivos, é possível escrever um script PHP que permitirá ler arquivos do sistema com /etc/passwd, modificar suas conexões de ethernet, enviar trabalhos de impressão gigantes, etc. Isso tem algumas implicações óbvias, já que você precisa ter certeza que os arquivos que você lê e escreve são apropriados.
Considere o seguinte script, onde um usuário indica que ele 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 Checagem fraca de variáveis resulta em....
<?php
// remove a file from the user's home directory
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";
unlink("$homedir/$userfile");
echo "The file has been deleted!";
?>
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 "The file has been deleted!";
?>
- Só dar permissões limitadas ao usuário executando o binário do PHP.
- Checar todas as variáveis que são enviadas.
Exemplo #3 Checagem 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 = "Deleted $filepath\n";
} else {
$logstring = "Failed to delete $filepath\n";
}
$fp = fopen("/home/logging/filedelete.log", "a");
fwrite($fp, $logstring);
fclose($fp);
echo htmlentities($logstring, ENT_QUOTES);
?>
Exemplo #4 Checagem 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("Bad username/filename");
}
//etc...
?>
Dependendo do seu sistema operacional, existe uma variedade enorme de arquivos que você tem que se preocupar, incluindo dispositivos (/dev/ or COM1), arquivos de configuração (arquivos /etc/ e .ini), áres de armazenamento conhecidas (/home/, My Documents), etc. Por essa razão, normalmente é mais fácil criar uma política onde você proibe tudo exceto aquilo que for explicitamente permitido.
(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.");
}
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.
I keep application configuration files in the document root. I found the most effective trick to prevent access to them is to
1. Give them no code that actually runs when included (except for variable assignments),
2. Don't use register globals so nobody can do anything weird,
3. Name them *.php so PHP runs them when asked for
4. Don't have anything before <?php
5. Don't have a ?>
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.
Common and simple way to avoid path attack is separating objectname and filesystem space when possible. For example if I have users on my site and directory per user solution is not "httpdocs/users/$login", but "httpdocs/users/".md5($login) or "httpdocs/users/".$userId
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
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
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.
:-)
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!');
}
}
?>
One more thing --
whenever you connect to a database with a password hard coded into your script, make sure you put the script off of your web document tree. Put the script somewhere where apache won't serve documents, and then include/require this file in your other scripts. That way, if the server ever gets misconfigured, it won't serve your PHP scripts with passwords, etc. as plain text for all to see.
-Tim
A note on mapping for added security.
The principle with mapping is: WHAT YOU SEE MAY NOT BE.
Map each entity that can be manipulated to a token. Only the token will be displayed to the end user. For added security, map allowed functionality to each token. Changing the token will at most cause a user to manipulate some else already mapped file. This achieve the goal of control and limited access.
There is a variety to implement this but the main point here is that a physical name should never be displayed to the end user, thus restricting an evil user knowledge of the system and it architecture.
This principle is also know under names such as "black box" or "need to know" or "abstraction", etc etc depending on the context.
The point here is that one does not need to know under lying structure in order to do operation one high levels such as "add/move/remove/edit". Present a view - the view will provide the user with what the user need to know.
It is all about creating an illusion of existence at higher level of something that does not exist on a lover level.
What about:
<?php
$file_to_delete = '/home/'.$username.'/'.$userfile;
if ((!ereg('\.\.', $file_to_delete)) and (file_exists($file_to_delete))) {
unlink($file_to_delete);
}
?>
I think this should prevent every attempt to go outside the user-directory.
Additionally you should check usernames at the registration.
Another way whould be to use the user-ID as home-directory - so, this can't be changed and every registered user as an unique one (if it's a primary key in your database). But then you still have to check the given $userfile.
So the code above could be taken as a "last instance check" directly before finally deleting of the file.
you should check requested file name for back path.a person can delete your code file by going back to root directory.
for ex:- deletefile.php?file=../index.php
if no proper check for / or \\ any user can delete your index page or any usefull file.
you may use following function to check for back path.
<?php
function existBackPath($file){
$file=str_replace("\\\\","/",$file);
return preg_match('[\\/]', $file);
}
?>
if existBackPath function returns 1.It means someone is trying to delete file which does not exist in his/her directory rather it exist in your root directory.
I note that the php standard error printing includes "filename", which appears to always be the /full/folder/path/and/filename. And on all unix systems and by common practice on many windows systems, the /home/(control panel login id)/ is part of the folder path, and on most hosts the same account login id is the control panel login id. So any error message effectively exposes to the world the control panel login id. From there a hacker and brute force can break any password in time. PHP does not appear to have a way to disable displaying the actual physical directory structure from appearing in error messages, which even is no login id were there, is a security problem. One can direct the error to a log file, but that still exposes critical information to contract web programmers. Typical practice is to give web programmers limited ftp access to a web directory, but then php defeats this restriction by displaying the entire directory structure in the error message to the web developer who you tried to give restricted access to. And 9 time out of ten also gave away the control panel login in id. So all the wise script writing in the world cannot obscure the private information the php error printing is giving out. Can it?
