PHP 5.4.36 Released

파일시스템 보안

Table of Contents

PHP 는 대부분의 서버시스템에서 파일과 디렉터리 기반의 권한에 대한 보안을 담당합니다. 이는 읽기 가능한 파일시스템에 존재하는 파일을 제어 할수 있도록 합니다. 파일시스템에 접근하는 모든 사용자에 의해 읽혀지는 파일이 안전한지 보장하기 위해 읽기 가능한 모든 파일을 신경써야 됩니다.

PHP 가 사용자 레벨의 파일시스템 접근을 허용하도록 만들어진 뒤로, /etc/passwd 파일과 같은 시스템 파일을 읽고, 이더넷을 조작하거나, 프린트 잡을 보내거나 등등의 PHP 스크립트 파일을 작성하는게 가능해 졌습니다. 이것은 다소 직접적인 영향을 미치는데, 읽으려거나 쓰려는 파일이 적절한 파일인지 확실히 할 필요가 있습니다.

다음 스크립트를 보면, 사용자의 홈디렉터리에서 파일을 지웁니다. 이때 파일관리를 위해 PHP 웹 인터페이스가 사용됨을 가정합니다. 이로 인해 Apache 사용자가 사용자 홈디렉터리 안에 있는 파일의 삭제가 가능하게 됩니다.

Example #1 공격을 유발하는 나쁜 변수 체크 방법

<?php
// 사용자의 홈디렉터리로부터 파일을 제거 합니다.
$username $_POST['user_submitted_name'];
$userfile $_POST['user_submitted_filename'];
$homedir  "/home/$username";

unlink("$homedir/$userfile");

echo 
"The file has been deleted!";
?>
사용자 이름과 파일명을 사용자 입력폼으로부터 전송받을 수 있으므로, 사용자 이름과 파일명을 다른 누군가로 바꿔 전송할 수 있고, 삭제할 수 있습니다. 이런 경우에는, 다른 형태의 인증을 필요합니다. 만약에 전송된 값들이 "../etc/" 과 "passwd" 일 경우를 생각해 보십시오. 코드는 다음과 같이 읽혀질 것입니다.

Example #2 파일시스템 공격

<?php
// PHP 유저가 하드드라이브 내에 접근권한을 가진 파일을 삭제 합니다.
// 만약에 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!";
?>
이 문제를 막기 위해서는 두가지 주요점을 알아야 합니다.
  • PHP 웹 사용자에게 제한된 권한만을 허용한다.
  • 전송된 모든 값들을 검사한다.
개선된 스크립트 :

Example #3 좀더 안전한 파일명 체크

<?php
// PHP 유저가 하드드라이브 내에 접근권한을 가진 파일을 삭제 합니다.
$username $_SERVER['REMOTE_USER']; // 인증 메카니즘을 사용합니다.
$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($logstringENT_QUOTES);

?>
하지만, 이 또한 문제가 없는것은 아닙니다. 만약에 인증 시스템이 자체적으로 만들어진 사용자 로그인을 허용하고, 사용자가 "../etc/" 를 선택하면, 시스템은 다시한번 문제에 노출됩니다. 이러한 이유로, 좀더 상황에 맞는 체크로직을 짜야 됩니다.

Example #4 좀더 안전한 파일명 체크

<?php
$username     
$_SERVER['REMOTE_USER']; // 인증 메카니즘을 사용합니다.
$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...
?>

운영체제에 따라, 장치엔트리(/dev/ or COM1) 및 설정 파일들(/etc/ files and the .ini files), 잘알려질 저장 장소들(/home/, My Documents) 등등. 신경써야할 파일은 많아 집니다. 이런 이유로, 명시적으로 허용하는것 이외에 모든것을 금지하는 정책을 만드는게 더 쉽습니다.

add a note add a note

User Contributed Notes 10 notes

up
28
anonymous
9 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
6
fmrose at ncsu dot edu
9 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
3
devik at cdi dot cz
13 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
2
Latchezar Tzvetkoff
5 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
2
cronos586(AT)caramail(DOT)com
12 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
1
eLuddite at not dot a dot real dot addressnsky dot ru
14 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.

:-)
up
-1
akul at inbox dot ru
10 years ago
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
up
-4
1 at 234 dot cx
9 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
-6
joshudson';DROP TABLE EMAILS;' gmail.com
9 years ago
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 ?>
up
-5
steelchords at yahoo dot com
7 years ago
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
To Top