Phith0n · 2014/11/21 16:05

0 x00 preface


Recently, due to the development of my own Webshell, I have studied some existing vulnerabilities in PHP, and also found some security risks in PHP. In this article, I’ll share with you my own summary of how open_basedir bypasses and enumerates directories in PHP.

0x01 Introduction to Open_basedir


Open_basedir is a PHP setup to prevent PHP from reading and writing files (directories) across directories. All PHP functions related to file reading and writing are checked by Open_basedir.

Open_basedir is actually a collection of directories. After Open_basedir is defined, the files and directories that PHP can read and write are restricted to these directories.

To set open_basedir, on Linux, different directories are separated by colas, for example, /var/www/:/ TMP /. Different directories in Windows are specified by “; “. Split, as in “c:/ WWW; C: / Windows/temp.”

In this era of cloud and virtual hosting, open_Basedir is expected to act as a barrier between different users to ensure that their hosts can run independently, but it’s not as simple as people think.

This article will focus on bypassing Open_basedir directory enumeration and traversal, why we do not say the specific file read, write, because the file read and write hole is a relatively large vulnerability, in PHP5.3 after there are few ways to bypass Open_basedir read and write files.

0x02 Directly enumerates directories with DirectoryIterator + Glob


This is @ / fd script (http://zone.wooyun.org/content/11268) in the first method is given.

DirectoryIterator is a new class added to PHP5, The DirectoryIterator class provides a simple interface for viewing The contents of Filesystem Directories).

Glob: Data flow wrapper is available since PHP 5.3.0 and is used to find matching file paths.

Combining these two approaches, we can enumerate directories after PHP5.3. In practice, we learned that this method can ignore open_basedir in listing directories under Linux.

Sample code:

#! php <? php printf('<b>open_basedir : %s </b><br />', ini_get('open_basedir')); $file_list = array(); // normal files $it = new DirectoryIterator("glob:///*"); foreach($it as $f) { $file_list[] = $f->__toString(); } // special files (starting with a dot(.) ) $it = new DirectoryIterator("glob:///.*"); foreach($it as $f) { $file_list[] = $f->__toString(); } sort($file_list); foreach($file_list as $f){ echo "{$f}<br/>"; }? >Copy the code

Perform we can find that open_basedir is/usr/share/nginx/WWW / : / TMP /, but our success lists all the files/root directory:

This method is also by far the most convenient method, he does not use brute force to guess the list, but directly enumerate. However, he has certain requirements for PHP version and system version, which can be listed above 5.3 (5.5/5.6 May be fixed? To bypass open_basedir, you need to run Linux.

0x03 RealPath Lists directories


This is @ / fd script (http://zone.wooyun.org/content/11268) in the second method is given.

The Realpath function is PHP’s way of normalizing a path to an absolute path, removing unnecessary.. / or./ to convert a relative path to an absolute path.

When open_basedir is turned on, this function has a feature: it returns false if we pass a path to a file (directory) that does not exist; When we pass a File (directory) that is not in open_basedir, it will throw an error (File is not within the allowed path(s)).

So we can use this feature to guess the list. For example, we need to guess all the files in the root directory (not in open_basedir) and simply write a function err_handle() that catches PHP errors. Err_handle () is thrown when an existing file is guessed, and err_handle() is not entered when a nonexistent file is guessed.

So let’s figure out efficiency from this. If the file name is 6 characters long (config, passwd, etc.), how many times do we need to enumerate it before we can guess its existence:

26 ** 6 = 308915776 times

This is the need to run for a long time, basically every time I ran out of patience, so violent guess solution is certainly not good. So, what’s a good way to turn this “chicken” bug into a “good” bug?

Those of you familiar with Windows + PHP will remember that there are two special wildcards in Windows: <, >

Yes, we use the power of these wildcards to enumerate directories. Write a simple POC to illustrate:

#! php <? php ini_set('open_basedir', dirname(__FILE__)); printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir')); set_error_handler('isexists'); $dir = 'd:/test/'; $file = ''; $chars = 'abcdefghijklmnopqrstuvwxyz0123456789_'; for ($i=0; $i < strlen($chars); $i++) { $file = $dir . $chars[$i] . '<><'; realpath($file); } function isexists($errno, $errstr) { $regexp = '/File\((.*)\) is not within/'; preg_match($regexp, $errstr, $matches); if (isset($matches[1])) { printf("%s <br/>", $matches[1]); }}? >Copy the code

First set open_basedir to the current directory and enumerate all files in d:/test/. Pass error handling to the isEXISTS function, which matches the directory name and prints it out.

Execution can see:

Open_basedir is c:\wamp\ WWW, but we enumerate the files in d:/test/ :

Of course, this is a crude POC because I’m not considering files with the same initial letter, so this POC can only list files with different initial letters.

If the first letter is the same, we simply enumerate the second character, the third character, and so on, to list all the files in the directory.

The advantage of this method is that it can be used on all versions of PHP on Windows, but the disadvantage is that it can only be used on Windows, but on Linux it can only be guessed by force.

0x04 SplFileInfo::getRealPath Enumerates the directory


Inspired by the previous approach, I started looking for a similar approach in PHP. In case RealPath becomes unavailable, alternatives can be found.

I found a new method: WooYun: PHP design flaw caused open_basedir to bypass directory #1, using SplFileInfo::getRealPath.

The SplFileInfo class, introduced after PHP5.1.2, provides an interface for manipulating files. One method that bears a similar name to RealPath is called getRealPath.

This method is similar to realPath in that it is used to get absolute paths. We pass in the relative path of the file in the constructor of SplFileInfo and call getRealPath to get the absolute path of the file.

One feature of this method is that open_basedir is not considered at all. Returns false if the path passed is a nonexistent path; If the path passed is an existing path, the absolute path is normally returned.

Our realPath function still takes open_basedir into account, but it doesn’t take enough care to report errors so that we can determine if a file exists. But our lovely SplFileInfo::getRealPath method simply determines whether a file exists without considering open_basedir.

So, I give you a POC:

#! php <? php ini_set('open_basedir', dirname(__FILE__)); printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir')); $basedir = 'D:/test/'; $arr = array(); $chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; for ($i=0; $i < strlen($chars); $i++) { $info = new SplFileInfo($basedir . $chars[$i] . '<><'); $re = $info->getRealPath(); if ($re) { dump($re); } } function dump($s){ echo $s . '<br/>'; ob_flush(); flush(); }? >Copy the code

D:/test (); D:/test ();

The special feature of this method is that it can enumerate any directory whether open_basedir is turned on or not. The previous method (realPath) will only report an error if open_basedir is turned on and outside open_basedir to list directories. Of course, you don’t need to list directories like this if open_basedir is not enabled.

0x05 GD library ImageftbBox/Imagefttext enumeration directory


The GD library is generally one of the must-have extension libraries for PHP, so I look at these useful ones when I’m looking for Open_basedir.

This is the new method: WooYun: PHP design flaw caused the open_basedir enumeration directory 3 to be bypassed

I’ll take imageftbBox as an example. The third argument to this function is the path to the font. If this parameter is outside open_basedir, PHP will throw “File(XXXXX) is not within the allowed path(s)” if the File exists. An “Invalid font filename” error is raised when the file does not exist.

That is, we can tell if a file exists by the specifics of the error thrown. This method is similar to RealPath in that both throw open_basedir errors.

I also modified a simple POC:

#! php <? php ini_set('open_basedir', dirname(__FILE__)); printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir')); set_error_handler('isexists'); $dir = 'd:/test/'; $file = ''; $chars = 'abcdefghijklmnopqrstuvwxyz0123456789_'; for ($i=0; $i < strlen($chars); $i++) { $file = $dir . $chars[$i] . '<><'; //$m = imagecreatefrompng("zip.png"); //imagefttext($m, 100, 0, 10, 20, 0xffffff, $file, 'aaa'); imageftbbox(100, 100, $file, 'aaa'); } function isexists($errno, $errstr) { global $file; if (stripos($errstr, 'Invalid font filename') === FALSE) { printf("%s<br/>", $file); }}? >Copy the code

Also list d:/test

As shown above, we find that while the “wildcard” does the trick, our actual file name is not displayed, and the wildcard “<><” is used instead.

Therefore, this method does not expose the realpath when it returns an error, which is the biggest difference from realpath. So, we can only guess one by one, but in general, it’s possible to guess, but maybe a little more difficult than RealPath.

0x06 bindTextDomain Violent Guess directory


Here’s the new method: WooYun: A PHP design flaw caused the open_basedir enumeration directory #2 to be bypassed

Bindtextdomain is a PHP function that binds a domain to a directory. I don’t know what this domain is, but it’s a method that might be used in some L10N applications (the related functions textDomain, gettext, setlocale). http://php.net/manual/en/function.gettext.php).

The Bindtextdomain function is available only when Gettext Functions are supported, which is not available on my Windows environment and is available by default on my Linux environment.

As shown above, the function’s second argument, $directory, is a file path. It returns $directory if $directory exists and false if it does not.

Write a simple test code:

#! php <? php printf('<b>open_basedir: %s</b><br />', ini_get('open_basedir')); $re = bindtextdomain('xxx', $_GET['dir']); var_dump($re); ? >Copy the code

If /etc/passwd exists, print:

Return false if /etc/wooyun does not exist:

Open_basedir is not taken into account. So, we can also guess and enumerate a directory by returning different values.

But the big catch is, Windows doesn’t have this function by default, and Linux doesn’t have wildcards for directory guessing, so it’s a big catch.

Of course, when there is no way to retreat to the violent guess catalog, it is also a method of calculation.

0 x07 summary


Open_basedir, originally the most basic way for PHP to restrict cross-directory reading and writing of files, should be well designed. But perhaps PHP was not designed in a uniform way when it was written, resulting in tragedies like “open_basedir bypass” every time new functionality was added or some out-of-the-way function was encountered.

I wrote an article, “LNMP Virtual Host Security Configuration Research,” about a way to prevent virtual hosts from crossing directories. But it was challenged by some white hats:

The reason is that many people have too much faith in the reliability of Open_Basedir. Open_basedir is a simple way to restrict cross-directory access, but relying too heavily on one method to defend against one type of attack can kill you.

The open_Basedir bypass is version-limited, but there are plenty of people with 0day in their hands. The open_basedir bypass can be found even by a non-php novice like me. Can you really guarantee that no one will be able to bypass it?

I certainly have more faith in the permission control mechanisms that come with operating systems like Linux/Windows, and I don’t believe open_Basedir alone can really protect me from anything.

By the way, most of the methods I mentioned above haven’t been fixed in the latest version of PHP (even the “0day” I discovered myself), which means there are so many generic ways to bypass Open_basedir.

What is the use of simply bypassing the open_basedir column directory?

Of course, the list of directories compared to reading, writing specific documents, are a lot of chicken ribs. But a lot of times, it is these seemingly “chicken ribs” loophole combination skills to complete the kill.

0 x08 reference


  • Zone.wooyun.org/content/112…

  • WooYun: PHP design flaw caused the open_basedir enumeration directory #1 to be bypassed

  • WooYun: PHP design bug caused the open_basedir enumeration directory 3 to be bypassed

  • WooYun: PHP design flaw caused the open_basedir enumeration directory #2 to be bypassed

  • Php.net/manual/en/c…

  • Php.net/manual/zh/w…

  • Php.net/manual/en/f…

  • Php.net/manual/en/s…

  • Php.net/manual/en/b…

  • Php.net/manual/en/f…