
Full Disclosure mailing list archives
PHP glob() filename disclosure vulnerability under safe_mode and open_basedir restriction
From: Peter Brodersen <php () ter dk>
Date: Thu, 01 Sep 2005 05:56:07 +0200
Hi, This post has been posted to bugtraq (BID 12701) earlier without disclosure of content. Anyhow: --- SUMMARY: PHP native function glob() discloses pathnames in error messages on system out of open_basedir- and safe_mode-limits. All known stable versions of PHP are vulnerable (PHP4, PHP5). Furthermore, safe_mode and open_basedir checks are only performed on first file in glob() result instead of all files matched. Information has been posted to PHP security contact and php.internals, without any response (PHP-bug #28932 - dismissed as "Bogus" without addressing the exact issue). The error messages can be utilized by a user in a shared host environment to retrieve complete directory lists of the system fairly easy. Link to code example follows. Example: http://basedir.ter.dk/globeater.php Example with debug: http://basedir.ter.dk/globeater.php?debug=1 Source code: http://basedir.ter.dk/globeater.phps For safe_mode and open_basedir, glob() only performs a single UID and path check on first file matched. One might fetch a file list specifying a file at first with same UID as the script inside the open_basedir path , e.g.: print_r(glob("{.,/tmp}/*",GLOB_BRACE)); Example: http://basedir.ter.dk/globall.php --- BASICS: The web scripting language PHP offers several solutions to limit the usual problems in a shared host environment. At a global level, methods such as safe_mode- and open_basedir-restrictions prevent users from accessing directories/files with other UID than the current script executed (safe_mode-restriction), and accessing directories/files not under the specified location (open_basedir-restriction). The manual states at http://www.php.net/manual/en/features.safe-mode.php "The PHP safe mode is an attempt to solve the shared-server security problem. It is architecturally incorrect to try to solve this problem at the PHP level, but since the alternatives at the web server and OS levels aren't very realistic, many people, especially ISP's, use safe mode for now." safe_mode isn't safe as such, but native functions should be implemented following safe_mode policies. At a per-host-level, individual session.save_path for each virtual host prevents session sharing across different virtual host. Without this setting, a user can read and manipulate data in a session. When a function access a file or directory out of it limits (i.e. restricted by safe_mode and/or open_basedir), a warning is thrown: $ php -d safe_mode=On -r 'readfile("/etc/passwd");' Warning: readfile(): SAFE MODE Restriction in effect. The script whose uid is 1000 is not allowed to access /etc/passwd owned by uid 0 in Command line code on line 1 $ php -d open_basedir=/home/someuser -r 'readfile("/etc/passwd");' Warning: readfile(): open_basedir restriction in effect. File(/etc/passwd) is not within the allowed path(s): (/home/someuser) in Command line code on line 1 --- ISSUE: The glob()-function is used to find pathnames matching a pattern. The issue here is that the pattern is expanded to find a file and then safe_mode/open_basedir-checks are performed. While this might be the only way of performing this check (as glob might match directories with different owners, e.g. glob("/home/*/public_html") ), the warning still discloses the matched, previously unknown file name: $ php -d safe_mode=On -r 'glob("/tmp/faketmp/phptest_sess_*");' Warning: glob(): SAFE MODE Restriction in effect. The script whose uid is 1000 is not allowed to access /tmp/faketmp/phptest_sess_03735f0f339412345678901234567890 owned by uid 0 in Command line code on line 1 Retrieving the error message using output buffering/capture, one can utilize these error messages and craft a fairly simple script that find every filename (available for the user php is executed as - usually the Apache user) in few attempts per file as opposed to brute force guessing filenames. --- METHOD: Exploiting error messages: - Match first file using glob("/target/directory/*") - Retrieve warning about first file matched (eg. apache) - Check matced file plus ?* (eg. apache?*) - Walk all way backwards using square brackets (eg. apache => apach[f-z], apac[i-z], apa[d-z], ap[b-z], a[q-z], [b-z]) Exploiding first-file-check-only: - Craft a glob pattern that matches a "good" file (with same UID as script and inside open_basedir path) at first and the target directory afterwards - E.g. glob("{.,/tmp}/*",GLOB_BRACE) --- IMPACT: Users at a webhosting company are able to retrieve file names out of their bounds. Only the filenames are accessible, not the actual content. This might still be a concern, as the native method for PHP storing sessions are using files with the token name as part of the filename, revealing session names, enabling users to hijack existing session (though not being able to directly manipulating it - unless session.save_path are the same for all hosts). Furthermore, this might be a tool for gathering information about the system, searching for other possible exploits. --- WORKAROUND: At a global level glob() could be disabled using the disable_function-directive in php.ini configuration file, e.g.: disable_functions glob This might not be a practical solution as many php scripts use that function for normal operation. At at local level a user might want to create his own session mechanism where filenames does not directly disclose session tokens. session_set_save_handler() could be used but is cumbersome on a large scale. Patching ext/standard/dir.c might prevent glob disclosing file names unknown to users in advance. --- OTHER ISSUE: glob() raises no warning if used on another owner's directory and there is no match. Even if glob is fixed to prevent disclosure of file name, this might still be used to finding a file name (test if /dir/[a-z]* provides warning or not, test if /dir/[a-n]* provides warning or not, etc. - file names might be found using about 8 glob() requests per letter - still better than brute force). This is quite the opposite of the method mentioned above, as we are walking forward to find the filename, not backwards. --- NOTES: As mentioned, safe_mode isn't meant to be magically safe. This is not my belief either, but as it is a centralized method that several web hosts depend on carrying users with access to no shells or other languages than PHP, it should provide security in its own world. Of course, PHP isn't able to prevent users with access to other languages or shells to retrieve entire file lists, but users in pure PHP environments shouldn't be able to (under safe_mode/open_basedir-restriction). Information about a webhost's session.save_path might be retrieved by tricking an error message - such as adding ?PHPSESSID=_ (or any other invalid session name) to the URL and looking at last line of output such as: Unknown(): Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/path/to/session/data) in Unknown on line 0 Other workarounds, solutions or concept ideas (based on general php behaviour and not at php-code-level) to solve issues with glob, shared sessions and session hijacking when session filename is known: * Most important: No disclosure of file name on safe_mode/open_basedir-error when using glob(). This could prevent file structure harvesting. * UID check for each and every glob()-entry, not just the first one. It requires more effort but it might be a small price to pay. * glob() of non-existent folders should be restricted as well - glob("/home/nonexistent/") provides no error, but glob("/home/existent/") does. * Session file names could be hashed values. In that case the filename itself can't be used for session hijacking. * Append the UID to the session file in safe_mode (just as the UID is appended to the realm in HTTP authentication) - or maybe (hash of) SERVER_NAME where available. * An option to prevent running of PHP scripts with the same UID as the Apache user (as it is easy to create a script with the UID of the Apache user, bypassing some safe_mode-limitations). Personal opinion: I can't see why setups only using PHP (and e.g. FTP restricted to homedir as ftp-root) absolutely have to be insecure based on arguments that has nothing to do with PHP-only-access. I don't believe that security comes in a box, but any issue to be solved on a global/centralized level is better than asking sysadms and developers of performing custom, individual workarounds. --- RELATED: Example of reading and manipulating data across virtual hosts with same session.save_path: http://stock.ter.dk/session.php More info at PHP bug #28242 --- -- - Peter Brodersen _______________________________________________ Full-Disclosure - We believe in it. Charter: http://lists.grok.org.uk/full-disclosure-charter.html Hosted and sponsored by Secunia - http://secunia.com/
Current thread:
- PHP glob() filename disclosure vulnerability under safe_mode and open_basedir restriction Peter Brodersen (Sep 01)