Re: splitting security chapter
| From: | Friedhelm Betz | Date: | Sun, 25 Jan 2004 23:49:15 +0000 |
| Subject: | Re: splitting security chapter | ||
| References: | 1 2 3 | Groups: | php.doc |
| Request: | Send a blank email to phpdoc+get-969358457@lists.php.net to get a copy of this message | ||
On Thursday 15 January 2004 13:12, Gabor Hojtsy wrote:
> > > > splitting huge files makes life for translation teams easier.
> > > > Therefore I am always +1 for splitting.
> > > >
> > > > The functions source xml-files are read for review :
> > > >
> > > > http://www.holliwell.de/security/security.tar.bz2
> > > > along with the compiled security chapter:
> > > > http://www.holliwell.de/security/
> > >
> > > As it seems there are some small changes made with this split (a title
> > > was added for the intro sectin). Since the split would be good to be
> > > done for all languages, this is not a good idea, as the change is hard
> > > to track with the split. Changes should be made IMHO after the split.
> >
> > I consider this rather a real split of the security chapter, than just a
> > "normal" change in the en version. Trans teams can pick them up as they
> > find the time.
> > Btw, as long as the old index.xml is splitted up, the new one with
> > changed content would replace the original one. So there is no version we
> > could refer to claimed by "Last change was don in Rev..."
>
> Well, technically the split just removes a lot of lines from
> security/index.xml and adds a few, so the old change history will be kept
> on the file. It might be enough to just add a comment to index.xml that
> what was the last version before the split, so translation people can pick
> up the changes between their revision and that revision before splitting.
> That also provides a good reason not to split the file in the
> translations, since the monolitic index.xml will still work, and it seems
> to be easier to let translations teams split the files themselfs after
> they picked up the changes done before the split.
>
> It is still important BTW that there should be no content change in the
> split. If a content change is needed (eg. new section title, etc), then
> that should be done in a separate commit on the splitted files. BTW the
> sections need to be elevated one level, so the section/section oddity in
> the TOC can be solved. But these should be done after the split IMHO.
>
No content changes, just split by sect1 and proper linking and adding the new
files along with the above mentioned comments, diff attached
Any objections to commit?
Friedhelm
--
http://www.jungle-world.com/
Index: index.xml =================================================================== RCS file: /repository/phpdoc/en/security/index.xml,v retrieving revision 1.66 diff -u -r1.66 index.xml --- index.xml 6 Jan 2004 09:22:21 -0000 1.66 +++ index.xml 25 Jan 2004 23:48:03 -0000 @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="iso-8859-1"?> <!-- $Revision: 1.66 $ --> +<!-- Last Revision before split: 1.66 --> <chapter id="security.index"> <title>Security</title> @@ -38,1270 +39,16 @@ coding for different levels of security. </simpara> - <sect1 id="security.general"> - <title>General considerations</title> - <simpara> - A completely secure system is a virtual impossibility, so an - approach often used in the security profession is one of balancing - risk and usability. If every variable submitted by a user required - two forms of biometric validation (such as a retinal scan and a - fingerprint), you would have an extremely high level of - accountability. It would also take half an hour to fill out a fairly - complex form, which would tend to encourage users to find ways of - bypassing the security. - </simpara> - <simpara> - The best security is often inobtrusive enough to suit the - requirements without the user being prevented from accomplishing - their work, or over-burdening the code author with excessive - complexity. Indeed, some security attacks are merely exploits of - this kind of overly built security, which tends to erode over time. - </simpara> - <simpara> - A phrase worth remembering: A system is only as good as the weakest - link in a chain. If all transactions are heavily logged based on - time, location, transaction type, etc. but the user is only - verified based on a single cookie, the validity of tying the users - to the transaction log is severely weakened. - </simpara> - <simpara> - When testing, keep in mind that you will not be able to test all - possibilities for even the simplest of pages. The input you - may expect will be completely unrelated to the input given by - a disgruntled employee, a cracker with months of time on their - hands, or a housecat walking across the keyboard. This is why it's - best to look at the code from a logical perspective, to discern - where unexpected data can be introduced, and then follow how it is - modified, reduced, or amplified. - </simpara> - <simpara> - The Internet is filled with people trying to make a name for - themselves by breaking your code, crashing your site, posting - inappropriate content, and otherwise making your day interesting. - It doesn't matter if you have a small or large site, you are - a target by simply being online, by having a server that can be - connected to. Many cracking programs do not discern by size, they - simply trawl massive IP blocks looking for victims. Try not to - become one. - </simpara> - </sect1> - - <sect1 id="security.cgi-bin"> - <title>Installed as CGI binary</title> - - <sect2 id="security.cgi-bin.attacks"> - <title>Possible attacks</title> - <simpara> - Using PHP as a <acronym>CGI</acronym> binary is an option for - setups that for some reason do not wish to integrate PHP as a - module into server software (like Apache), or will use PHP with - different kinds of CGI wrappers to create safe chroot and setuid - environments for scripts. This setup usually involves installing - executable PHP binary to the web server cgi-bin directory. CERT - advisory <ulink url="&url.cert;">CA-96.11</ulink> recommends - against placing any interpreters into cgi-bin. Even if the PHP - binary can be used as a standalone interpreter, PHP is designed - to prevent the attacks this setup makes possible: - </simpara> - <itemizedlist> - <listitem> - <simpara> - Accessing system files: <filename - role="url">http://my.host/cgi-bin/php?/etc/passwd</filename> - </simpara> - <simpara> - The query information in a url after the question mark (?) is - passed as command line arguments to the interpreter by the CGI - interface. Usually interpreters open and execute the file - specified as the first argument on the command line. - </simpara> - <simpara> - When invoked as a CGI binary, PHP refuses to interpret the - command line arguments. - </simpara> - </listitem> - <listitem> - <simpara> - Accessing any web document on server: <filename - role="url">http://my.host/cgi-bin/php/secret/doc.html</filename> - </simpara> - <simpara> - The path information part of the url after the PHP binary name, - <filename role="uri">/secret/doc.html</filename> is - conventionally used to specify the name of the file to be - opened and interpreted by the <acronym>CGI</acronym> program. - Usually some web server configuration directives (Apache: - Action) are used to redirect requests to documents like - <filename - role="url">http://my.host/secret/script.php</filename> to the - PHP interpreter. With this setup, the web server first checks - the access permissions to the directory <filename - role="uri">/secret</filename>, and after that creates the - redirected request <filename - role="url">http://my.host/cgi-bin/php/secret/script.php</filename>. - Unfortunately, if the request is originally given in this form, - no access checks are made by web server for file <filename - role="uri">/secret/script.php</filename>, but only for the - <filename role="uri">/cgi-bin/php</filename> file. This way - any user able to access <filename - role="uri">/cgi-bin/php</filename> is able to access any - protected document on the web server. - </simpara> - <simpara> - In PHP, compile-time configuration option <link - linkend="install.configure.enable-force-cgi-redirect">--enable-force-cgi-redirect</link> - and runtime configuration directives <link - linkend="ini.doc-root">doc_root</link> and <link - linkend="ini.user-dir">user_dir</link> can be used to prevent - this attack, if the server document tree has any directories - with access restrictions. See below for full the explanation - of the different combinations. - </simpara> - </listitem> - </itemizedlist> - </sect2> - - <sect2 id="security.cgi-bin.default"> - <title>Case 1: only public files served</title> - - <simpara> - If your server does not have any content that is not restricted - by password or ip based access control, there is no need for - these configuration options. If your web server does not allow - you to do redirects, or the server does not have a way to - communicate to the PHP binary that the request is a safely - redirected request, you can specify the option <link - linkend="install.configure.enable-force-cgi-redirect">--enable-force-cgi-redirect</link> - to the configure script. You still have to make sure your PHP - scripts do not rely on one or another way of calling the script, - neither by directly <filename - role="php">http://my.host/cgi-bin/php/dir/script.php</filename> - nor by redirection <filename - role="php">http://my.host/dir/script.php</filename>. - </simpara> - <simpara> - Redirection can be configured in Apache by using AddHandler and - Action directives (see below). - </simpara> - </sect2> - - <sect2 id="security.cgi-bin.force-redirect"> - <title>Case 2: using --enable-force-cgi-redirect</title> - <simpara> - This compile-time option prevents anyone from calling PHP - directly with a url like <filename - role="php">http://my.host/cgi-bin/php/secretdir/script.php</filename>. - Instead, PHP will only parse in this mode if it has gone through - a web server redirect rule. - </simpara> - <simpara> - Usually the redirection in the Apache configuration is done with - the following directives: - </simpara> - <programlisting role="apache-conf"> -<![CDATA[ -Action php-script /cgi-bin/php -AddHandler php-script .php -]]> - </programlisting> - <simpara> - This option has only been tested with the Apache web server, and - relies on Apache to set the non-standard CGI environment variable - <envar>REDIRECT_STATUS</envar> on redirected requests. If your - web server does not support any way of telling if the request is - direct or redirected, you cannot use this option and you must use - one of the other ways of running the CGI version documented - here. - </simpara> - </sect2> - - <sect2 id="security.cgi-bin.doc-root"> - <title>Case 3: setting doc_root or user_dir</title> - <simpara> - To include active content, like scripts and executables, in the - web server document directories is sometimes considered an insecure - practice. If, because of some configuration mistake, the scripts - are not executed but displayed as regular HTML documents, this - may result in leakage of intellectual property or security - information like passwords. Therefore many sysadmins will prefer - setting up another directory structure for scripts that are - accessible only through the PHP CGI, and therefore always - interpreted and not displayed as such. - </simpara> - <simpara> - Also if the method for making sure the requests are not - redirected, as described in the previous section, is not - available, it is necessary to set up a script doc_root that is - different from web document root. - </simpara> - <simpara> - You can set the PHP script document root by the configuration - directive <link linkend="ini.doc-root">doc_root</link> in the - <link linkend="configuration.file">configuration file</link>, or - you can set the environment variable - <envar>PHP_DOCUMENT_ROOT</envar>. If it is set, the CGI version - of PHP will always construct the file name to open with this - <parameter>doc_root</parameter> and the path information in the - request, so you can be sure no script is executed outside this - directory (except for <parameter>user_dir</parameter> - below). - </simpara> - <simpara> - Another option usable here is <link - linkend="ini.user-dir">user_dir</link>. When user_dir is unset, - only thing controlling the opened file name is - <parameter>doc_root</parameter>. Opening an url like <filename - role="url">http://my.host/~user/doc.php</filename> does not - result in opening a file under users home directory, but a file - called <filename role="uri">~user/doc.php</filename> under - doc_root (yes, a directory name starting with a tilde - [<literal>~</literal>]). - </simpara> - <simpara> - If user_dir is set to for example <filename - role="dir">public_php</filename>, a request like <filename - role="url">http://my.host/~user/doc.php</filename> will open a - file called <filename>doc.php</filename> under the directory - named <filename role="dir">public_php</filename> under the home - directory of the user. If the home of the user is <filename - role="dir">/home/user</filename>, the file executed is - <filename>/home/user/public_php/doc.php</filename>. - </simpara> - <simpara> - <parameter>user_dir</parameter> expansion happens regardless of - the <parameter>doc_root</parameter> setting, so you can control - the document root and user directory access - separately. - </simpara> - </sect2> - - <sect2 id="security.cgi-bin.shell"> - <title>Case 4: PHP parser outside of web tree</title> - <para> - A very secure option is to put the PHP parser binary somewhere - outside of the web tree of files. In <filename - role="dir">/usr/local/bin</filename>, for example. The only real - downside to this option is that you will now have to put a line - similar to: - <informalexample> - <programlisting> -<![CDATA[ -#!/usr/local/bin/php -]]> - </programlisting> - </informalexample> - as the first line of any file containing PHP tags. You will also - need to make the file executable. That is, treat it exactly as - you would treat any other CGI script written in Perl or sh or any - other common scripting language which uses the - <literal>#!</literal> shell-escape mechanism for launching - itself. - </para> - <para> - To get PHP to handle <envar>PATH_INFO</envar> and - <envar>PATH_TRANSLATED</envar> information correctly with this - setup, the PHP parser should be compiled with the <link - linkend="install.configure.enable-discard-path">--enable-discard-path</link> - configure option. - </para> - </sect2> - - </sect1> - - <sect1 id="security.apache"> - <title>Installed as an Apache module</title> - <simpara> - When PHP is used as an Apache module it inherits Apache's user - permissions (typically those of the "nobody" user). This has several - impacts on security and authorization. For example, if you are using - PHP to access a database, unless that database has built-in access - control, you will have to make the database accessible to the - "nobody" user. This means a malicious script could access and modify - the database, even without a username and password. It's entirely - possible that a web spider could stumble across a database - administrator's web page, and drop all of your databases. You can - protect against this with Apache authorization, or you can design - your own access model using LDAP, &htaccess; files, etc. and include - that code as part of your PHP scripts. - </simpara> - <simpara> - Often, once security is established to the point where the PHP user - (in this case, the apache user) has very little risk attached to it, - it is discovered that PHP is now prevented from writing any files - to user directories. Or perhaps it has been prevented from accessing - or changing databases. It has equally been secured from writing - good and bad files, or entering good and bad database transactions. - </simpara> - <simpara> - A frequent security mistake made at this point is to allow apache - root permissions, or to escalate apache's abilitites in some other - way. - </simpara> - <simpara> - Escalating the Apache user's permissions to root is extremely - dangerous and may compromise the entire system, so sudo'ing, - chroot'ing, or otherwise running as root should not be considered by - those who are not security professionals. - </simpara> - <simpara> - There are some simpler solutions. By using - <link linkend="ini.open-basedir">open_basedir</link> you can control and restrict what - directories are allowed to be used for PHP. You can also set up - apache-only areas, to restrict all web based activity to non-user, - or non-system, files. - </simpara> - </sect1> - - <sect1 id="security.filesystem"> - <title>Filesystem Security</title> - <simpara> - PHP is subject to the security built into most server systems with - respect to permissions on a file and directory basis. This allows - you to control which files in the filesystem may be read. Care - should be taken with any files which are world readable to ensure - that they are safe for reading by all users who have access to that - filesystem. - </simpara> - <simpara> - Since PHP was designed to allow user level access to the filesystem, - it's entirely possible to write a PHP script that will allow you - to read system files such as /etc/passwd, modify your ethernet - connections, send massive printer jobs out, etc. This has some - obvious implications, in that you need to ensure that the files - that you read from and write to are the appropriate ones. - </simpara> - <simpara> - Consider the following script, where a user indicates that they'd - like to delete a file in their home directory. This assumes a - situation where a PHP web interface is regularly used for file - management, so the Apache user is allowed to delete files in - the user home directories. - </simpara> - <para> - <example> - <title>Poor variable checking leads to....</title> - <programlisting role="php"> -<![CDATA[ -<?php -// remove a file from the user's home directory -$username = $_POST['user_submitted_name']; -$homedir = "/home/$username"; -$file_to_delete = "$userfile"; -unlink ("$homedir/$userfile"); -echo "$file_to_delete has been deleted!"; -?> -]]> - </programlisting> - </example> - Since the username is postable from a user form, they can submit - a username and file belonging to someone else, and delete files. - In this case, you'd want to use some other form of authentication. - Consider what could happen if the variables submitted were - "../etc/" and "passwd". The code would then effectively read: - <example> - <title>... A filesystem attack</title> - <programlisting role="php"> -<![CDATA[ -<?php -// removes a file from anywhere on the hard drive that -// the PHP user has access to. If PHP has root access: -$username = "../etc/"; -$homedir = "/home/../etc/"; -$file_to_delete = "passwd"; -unlink ("/home/../etc/passwd"); -echo "/home/../etc/passwd has been deleted!"; -?> -]]> - </programlisting> - </example> - There are two important measures you should take to prevent these - issues. - <itemizedlist> - <listitem> - <simpara> - Only allow limited permissions to the PHP web user binary. - </simpara> - </listitem> - <listitem> - <simpara> - Check all variables which are submitted. - </simpara> - </listitem> - </itemizedlist> - Here is an improved script: - <example> - <title>More secure file name checking</title> - <programlisting role="php"> -<![CDATA[ -<?php -// removes a file from the hard drive that -// the PHP user has access to. -$username = $_SERVER['REMOTE_USER']; // using an authentication mechanisim - -$homedir = "/home/$username"; - -$file_to_delete = basename("$userfile"); // strip paths -unlink ($homedir/$file_to_delete); - -$fp = fopen("/home/logging/filedelete.log","+a"); //log the deletion -$logstring = "$username $homedir $file_to_delete"; -fputs ($fp, $logstring); -fclose($fp); - -echo "$file_to_delete has been deleted!"; -?> -]]> - </programlisting> - </example> - However, even this is not without it's flaws. If your authentication - system allowed users to create their own user logins, and a user - chose the login "../etc/", the system is once again exposed. For - this reason, you may prefer to write a more customized check: - <example> - <title>More secure file name checking</title> - <programlisting role="php"> -<![CDATA[ -<?php -$username = $_SERVER['REMOTE_USER']; // using an authentication mechanisim -$homedir = "/home/$username"; - -if (!ereg('^[^./][^/]*$', $userfile)) - die('bad filename'); //die, do not process - -if (!ereg('^[^./][^/]*$', $username)) - die('bad username'); //die, do not process -//etc... -?> -]]> - </programlisting> - </example> - </para> - <para> - Depending on your operating system, there are a wide variety of files - which you should be concerned about, including device entries (/dev/ - or COM1), configuration files (/etc/ files and the .ini files), - well known file storage areas (/home/, My Documents), etc. For this - reason, it's usually easier to create a policy where you forbid - everything except for what you explicitly allow. - </para> - </sect1> - - <sect1 id="security.database"> - <title>Database Security</title> - - <simpara> - Nowadays, databases are cardinal components of any web based application by - enabling websites to provide varying dynamic content. Since very sensitive - or secret information can be stored in a database, you should strongly - consider protecting your databases. - </simpara> - <simpara> - To retrieve or to store any information you need to connect to the database, - send a legitimate query, fetch the result, and close the connection. - Nowadays, the commonly used query language in this interaction is the - Structured Query Language (SQL). See how an attacker can <link - linkend="security.database.sql-injection">tamper with an SQL query</link>. - </simpara> - <simpara> - As you can surmise, PHP cannot protect your database by itself. The - following sections aim to be an introduction into the very basics of how to - access and manipulate databases within PHP scripts. - </simpara> - <simpara> - Keep in mind this simple rule: defense in depth. The more places you - take action to increase the protection of your database, the less - probability of an attacker succeeding in exposing or abusing any stored - information. Good design of the database schema and the application - deals with your greatest fears. - </simpara> - - <sect2 id="security.database.design"> - <title>Designing Databases</title> - <simpara> - The first step is always to create the database, unless you want to use - one from a third party. When a database is created, it is - assigned to an owner, who executed the creation statement. Usually, only - the owner (or a superuser) can do anything with the objects in that - database, and in order to allow other users to use it, privileges must be - granted. - </simpara> - <simpara> - Applications should never connect to the database as its owner or a - superuser, because these users can execute any query at will, for - example, modifying the schema (e.g. dropping tables) or deleting its - entire content. - </simpara> - <simpara> - You may create different database users for every aspect of your - application with very limited rights to database objects. The most - required privileges should be granted only, and avoid that the same user - can interact with the database in different use cases. This means that if - intruders gain access to your database using your applications credentials, - they can only effect as many changes as your application can. - </simpara> - <simpara> - You are encouraged not to implement all the business logic in the web - application (i.e. your script), instead do it in the database schema - using views, triggers or rules. If the system evolves, new ports will be - intended to open to the database, and you have to re-implement the logic - in each separate database client. Over and above, triggers can be used - to transparently and automatically handle fields, which often provides - insight when debugging problems with your application or tracing back - transactions. - </simpara> - </sect2> - - <sect2 id="security.database.connection"> - <title>Connecting to Database</title> - <simpara> - You may want to estabilish the connections over SSL to encrypt - client/server communications for increased security, or you can use ssh - to encrypt the network connection between clients and the database server. - If either of these is used, then monitoring your traffic and gaining - information about your database will be difficult for a would-be attacker. - </simpara> - <!--simpara> - If your database server has native SSL support, consider using <link - linkend="ref.openssl">OpenSSL functions</link> in communication between - PHP and database via SSL. - </simpara--> - </sect2> - - <sect2 id="security.database.storage"> - <title>Encrypted Storage Model</title> - <simpara> - SSL/SSH protects data travelling from the client to the server, SSL/SSH - does not protect the persistent data stored in a database. SSL is an - on-the-wire protocol. - </simpara> - <simpara> - Once an attacker gains access to your database directly (bypassing the - webserver), the stored sensitive data may be exposed or misused, unless - the information is protected by the database itself. Encrypting the data - is a good way to mitigate this threat, but very few databases offer this - type of data encryption. - </simpara> - <simpara> - The easiest way to work around this problem is to first create your own - encryption package, and then use it from within your PHP scripts. PHP - can assist you in this with several extensions, such as <link - linkend="ref.mcrypt">Mcrypt</link> and <link - linkend="ref.mhash">Mhash</link>, covering a wide variety of encryption - algorithms. The script encrypts the data before inserting it into the database, and decrypts - it when retrieving. See the references for further examples of how - encryption works. - </simpara> - <simpara> - In case of truly hidden data, if its raw representation is not needed - (i.e. not be displayed), hashing may also be taken into consideration. - The well-known example for the hashing is storing the MD5 hash of a - password in a database, instead of the password itself. See also - <function>crypt</function> and <function>md5</function>. - </simpara> - <example> - <title>Using hashed password field</title> - <programlisting role="php"> -<![CDATA[ -// storing password hash -$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');", - addslashes($username), md5($password)); -$result = pg_exec($connection, $query); - -// querying if user submitted the right password -$query = sprintf("SELECT 1 FROM users WHERE name='%s' AND pwd='%s';", - addslashes($username), md5($password)); -$result = pg_exec($connection, $query); - -if (pg_numrows($result) > 0) { - echo "Welcome, $username!"; -} -else { - echo "Authentication failed for $username."; -} -]]> - </programlisting> - </example> - </sect2> - - <sect2 id="security.database.sql-injection"> - <title>SQL Injection</title> - <simpara> - Many web developers are unaware of how SQL queries can be tampered with, - and assume that an SQL query is a trusted command. It means that SQL - queries are able to circumvent access controls, thereby bypassing standard - authentication and authorization checks, and sometimes SQL queries even - may allow access to host operating system level commands. - </simpara> - <simpara> - Direct SQL Command Injection is a technique where an attacker creates or - alters existing SQL commands to expose hidden data, or to override valuable - ones, or even to execute dangerous system level commands on the database - host. This is accomplished by the application taking user input and - combining it with static parameters to build a SQL query. The following - examples are based on true stories, unfortunately. - </simpara> - <para> - Owing to the lack of input validation and connecting to the database on - behalf of a superuser or the one who can create users, the attacker - may create a superuser in your database. - <example> - <title> - Splitting the result set into pages ... and making superusers - (PostgreSQL and MySQL) - </title> - <programlisting role="php"> -<![CDATA[ -$offset = argv[0]; // beware, no input validation! -$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; -// with PostgreSQL -$result = pg_exec($conn, $query); -// with MySQL -$result = mysql_query($query); -]]> - </programlisting> - </example> - Normal users click on the 'next', 'prev' links where the <varname>$offset</varname> - is encoded into the URL. The script expects that the incoming - <varname>$offset</varname> is a decimal number. However, what if someone tries to - break in by appending a <function>urlencode</function>'d form of the - following to the URL - <informalexample> - <programlisting> -<![CDATA[ -// in case of PostgreSQL -0; -insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) - select 'crack', usesysid, 't','t','crack' - from pg_shadow where usename='postgres'; --- - -// in case of MySQL -0; -UPDATE user SET Password=PASSWORD('crack') WHERE user='root'; -FLUSH PRIVILEGES; -]]> - </programlisting> - </informalexample> - If it happened, then the script would present a superuser access to him. - Note that <literal>0;</literal> is to supply a valid offset to the - original query and to terminate it. - </para> - <note> - <para> - It is common technique to force the SQL parser to ignore the rest of the - query written by the developer with <literal>--</literal> which is the - comment sign in SQL. - </para> - </note> - <para> - A feasible way to gain passwords is to circumvent your search result pages. - The only thing the attacker needs to do is to see if there are any submitted variables - used in SQL statements which are not handled properly. These filters can be set - commonly in a preceding form to customize <literal>WHERE, ORDER BY, - LIMIT</literal> and <literal>OFFSET</literal> clauses in <literal>SELECT</literal> - statements. If your database supports the <literal>UNION</literal> construct, - the attacker may try to append an entire query to the original one to list - passwords from an arbitrary table. Using encrypted password fields is - strongly encouraged. - <example> - <title> - Listing out articles ... and some passwords (any database server) - </title> - <programlisting role="php"> -<![CDATA[ -$query = "SELECT id, name, inserted, size FROM products - WHERE size = '$size' - ORDER BY $order LIMIT $limit, $offset;"; -$result = odbc_exec($conn, $query); -]]> - </programlisting> - </example> - The static part of the query can be combined with another - <literal>SELECT</literal> statement which reveals all passwords: - <informalexample> - <programlisting> -<![CDATA[ -' -union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --- -]]> - </programlisting> - </informalexample> - If this query (playing with the <literal>'</literal> and - <literal>--</literal>) were assigned to one of the variables used in - <varname>$query</varname>, the query beast awakened. - </para> - <para> - SQL UPDATE's are also susceptible to attack. These queries are - also threatened by chopping and appending an entirely new query to it. But - the attacker might fiddle with the <literal>SET</literal> clause. In this - case some schema information must be possessed to manipulate the query - successfully. This can be acquired by examining the form variable names, or - just simply brute forcing. There are not so many naming conventions for - fields storing passwords or usernames. - <example> - <title> - From resetting a password ... to gaining more privileges (any database server) - </title> - <programlisting role="php"> -<![CDATA[ -$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';"; -]]> - </programlisting> - </example> - But a malicious user sumbits the value - <literal>' or uid like'%admin%'; --</literal> to <varname>$uid</varname> to - change the admin's password, or simply sets <varname>$pwd</varname> to - <literal>"hehehe', admin='yes', trusted=100 "</literal> (with a trailing - space) to gain more privileges. Then, the query will be twisted: - <informalexample> - <programlisting role="php"> -<![CDATA[ -// $uid == ' or uid like'%admin%'; -- -$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --"; - -// $pwd == "hehehe', admin='yes', trusted=100 " -$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;" -]]> - </programlisting> - </informalexample> - </para> - <para> - A frightening example how operating system level commands can be accessed - on some database hosts. - <example> - <title>Attacking the database hosts operating system (MSSQL Server)</title> - <programlisting role="php"> -<![CDATA[ -$query = "SELECT * FROM products WHERE id LIKE '%$prod%'"; -$result = mssql_query($query); -]]> - </programlisting> - </example> - If attacker submits the value - <literal>a%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal> - to <varname>$prod</varname>, then the <varname>$query</varname> will be: - <informalexample> - <programlisting role="php"> -<![CDATA[ -$query = "SELECT * FROM products - WHERE id LIKE '%a%' - exec master..xp_cmdshell 'net user test testpass /ADD'--"; -$result = mssql_query($query); -]]> - </programlisting> - </informalexample> - MSSQL Server executes the SQL statements in the batch including a command - to add a new user to the local accounts database. If this application - were running as <literal>sa</literal> and the MSSQLSERVER service is - running with sufficient privileges, the attacker would now have an - account with which to access this machine. - </para> - <note> - <para> - Some of the examples above is tied to a specific database server. This - does not mean that a similar attack is impossible against other products. - Your database server may be similarly vulnerable in another manner. - </para> - </note> - - <sect3 id="security.database.avoiding"> - <title>Avoiding techniques</title> - <simpara> - You may plead that the attacker must possess a piece of information - about the database schema in most examples. You are right, but you - never know when and how it can be taken out, and if it happens, - your database may be exposed. If you are using an open source, or - publicly available database handling package, which may belong to a - content management system or forum, the intruders easily produce - a copy of a piece of your code. It may be also a security risk if it - is a poorly designed one. - </simpara> - <simpara> - These attacks are mainly based on exploiting the code not being written - with security in mind. Never trust any kind of input, especially that - which comes from the client side, even though it comes from a select box, - a hidden input field or a cookie. The first example shows that such a - blameless query can cause disasters. - </simpara> - - <itemizedlist> - <listitem> - <simpara> - Never connect to the database as a superuser or as the database owner. - Use always customized users with very limited privileges. - </simpara> - </listitem> - <listitem> - <simpara> - Check if the given input has the expected data type. PHP has - a wide range of input validating functions, from the simplest ones - found in <link linkend="ref.variables">Variable Functions</link> and - in <link linkend="ref.ctype">Character Type Functions</link> - (e.g. <function>is_numeric</function>, <function>ctype_digit</function> - respectively) and onwards to the - <link linkend="ref.pcre">Perl compatible Regular Expressions</link> - support. - </simpara> - </listitem> - <listitem> - <para> - If the application waits for numerical input, consider verifying data - with <function>is_numeric</function>, or silently change its type - using <function>settype</function>, or use its numeric representation - by <function>sprintf</function>. - <example> - <title>A more secure way to compose a query for paging</title> - <programlisting role="php"> -<![CDATA[ -settype($offset, 'integer'); -$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; - -// please note %d in the format string, using %s would be meaningless -$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;", - $offset); -]]> - </programlisting> - </example> - </para> - </listitem> - <listitem> - <simpara> - Quote each non numeric user input which is passed to the database with - <function>addslashes</function> or <function>addcslashes</function>. - See <link linkend="security.database.storage">the first example</link>. - As the examples shows, quotes burnt into the static part of the query - is not enough, and can be easily cracked. - </simpara> - </listitem> - <listitem> - <simpara> - Do not print out any database specific information, especially - about the schema, by fair means or foul. See also <link - linkend="security.errors">Error Reporting</link> and <link - linkend="ref.errorfunc">Error Handling and Logging Functions</link>. - </simpara> - </listitem> - <listitem> - <simpara> - You may use stored procedures and previously defined cursors to abstract - data access so that users do not directly access tables or views, but - this solution has another impacts. - </simpara> - </listitem> - </itemizedlist> - - <simpara> - Besides these, you benefit from logging queries either within your script - or by the database itself, if it supports logging. Obviously, the logging is unable - to prevent any harmful attempt, but it can be helpful to trace back which - application has been circumvented. The log is not useful by itself, but - through the information it contains. More detail is generally better than less. - </simpara> - </sect3> - </sect2> - </sect1> - - <sect1 id="security.errors"> - <title>Error Reporting</title> - <para> - With PHP security, there are two sides to error reporting. One is - beneficial to increasing security, the other is detrimental. - </para> - <para> - A standard attack tactic involves profiling a system by feeding - it improper data, and checking for the kinds, and contexts, of the - errors which are returned. This allows the system cracker to probe - for information about the server, to determine possible weaknesses. - For example, if an attacker had gleaned information about a page - based on a prior form submission, they may attempt to override - variables, or modify them: - <example> - <title>Attacking Variables with a custom HTML page</title> - <programlisting role="php"> -<![CDATA[ -<form method="post" action="attacktarget?username=badfoo&password=badfoo"> -<input type="hidden" name="username" value="badfoo" /> -<input type="hidden" name="password" value="badfoo" /> -</form> -]]> - </programlisting> - </example> - </para> - <para> - The PHP errors which are normally returned can be quite helpful to a - developer who is trying to debug a script, indicating such things - as the function or file that failed, the PHP file it failed in, - and the line number which the failure occured in. This is all - information that can be exploited. It is not uncommon for a php - developer to use <function>show_source</function>, - <function>highlight_string</function>, or - <function>highlight_file</function> as a debugging measure, but in - a live site, this can expose hidden variables, unchecked syntax, - and other dangerous information. Especially dangerous is running - code from known sources with built-in debugging handlers, or using - common debugging techniques. If the attacker can determine what - general technique you are using, they may try to brute-force a page, - by sending various common debugging strings: - <example> - <title>Exploiting common debugging variables</title> - <programlisting role="php"> -<![CDATA[ -<form method="post" action="attacktarget?errors=Y&showerrors=1&debug=1"> -<input type="hidden" name="errors" value="Y" /> -<input type="hidden" name="showerrors" value="1" /> -<input type="hidden" name="debug" value="1" /> -</form> -]]> - </programlisting> - </example> - </para> - <para> - Regardless of the method of error handling, the ability to probe a - system for errors leads to providing an attacker with more - information. - </para> - <para> - For example, the very style of a generic PHP error indicates a system - is running PHP. If the attacker was looking at an .html page, and - wanted to probe for the back-end (to look for known weaknesses in - the system), by feeding it the wrong data they may be able to - determine that a system was built with PHP. - </para> - <para> - A function error can indicate whether a system may be running a - specific database engine, or give clues as to how a web page or - programmed or designed. This allows for deeper investigation into - open database ports, or to look for specific bugs or weaknesses - in a web page. By feeding different pieces of bad data, for example, - an attacker can determine the order of authentication in a script, - (from the line number errors) as well as probe for exploits that - may be exploited in different locations in the script. - </para> - <para> - A filesystem or general PHP error can indicate what permissions - the webserver has, as well as the structure and organization of - files on the web server. Developer written error code can aggravate - this problem, leading to easy exploitation of formerly "hidden" - information. - </para> - <para> - There are three major solutions to this issue. The first is to - scrutinize all functions, and attempt to compensate for the bulk - of the errors. The second is to disable error reporting entirely - on the running code. The third is to use PHP's custom error - handling functions to create your own error handler. Depending - on your security policy, you may find all three to be applicable - to your situation. - </para> - <para> - One way of catching this issue ahead of time is to make use of - PHP's own <function>error_reporting</function>, to help you - secure your code and find variable usage that may be dangerous. - By testing your code, prior to deployment, with E_ALL, you can - quickly find areas where your variables may be open to poisoning - or modification in other ways. Once you are ready for deployment, - by using E_NONE, you insulate your code from probing. - <example> - <title>Finding dangerous variables with E_ALL</title> - <programlisting role="php"> -<![CDATA[ -<?php -if ($username) { // Not initialized or checked before usage - $good_login = 1; -} -if ($good_login == 1) { // If above test fails, not initialized or checked before usage - readfile ("/highly/sensitive/data/index.html"); -} -?> -]]> - </programlisting> - </example> - </para> - </sect1> - - <sect1 id="security.registerglobals"> - <title>Using Register Globals</title> - <para> - Perhaps the most controversial change in PHP is when the default value - for the PHP directive <link linkend="ini.register-globals"> - register_globals</link> went from ON to OFF in PHP - <ulink url="&url.php.release4.2.0;">4.2.0</ulink>. Reliance on this - directive was quite common and many people didn't even know it existed - and assumed it's just how PHP works. This page will explain how one can - write insecure code with this directive but keep in mind that the - directive itself isn't insecure but rather it's the misuse of it. - </para> - <para> - When on, register_globals will inject (poison) your scripts will all - sorts of variables, like request variables from HTML forms. This - coupled with the fact that PHP doesn't require variable initialization - means writing insecure code is that much easier. It was a difficult - decision, but the PHP community decided to disable this directive by - default. When on, people use variables yet really don't know for sure - where they come from and can only assume. Internal variables that are - defined in the script itself get mixed up with request data sent by - users and disabling register_globals changes this. Let's demonstrate - with an example misuse of register_globals: - </para> - <para> - <example> - <title>Example misuse with register_globals = on</title> - <programlisting role="php"> -<![CDATA[ -<?php -// define $authorized = true only if user is authenticated -if (authenticated_user()) { - $authorized = true; -} - -// Because we didn't first initialize $authorized as false, this might be -// defined through register_globals, like from GET auth.php?authorized=1 -// So, anyone can be seen as authenticated! -if ($authorized) { - include "/highly/sensitive/data.php"; -} -?> -]]> - </programlisting> - </example> - </para> - <para> - When register_globals = on, our logic above may be compromised. When - off, <varname>$authorized</varname> can't be set via request so it'll - be fine, although it really is generally a good programming practice to - initialize variables first. For example, in our example above we might - have first done <literal>$authorized = false</literal>. Doing this - first means our above code would work with register_globals on or off as - users by default would be unauthorized. - </para> - <para> - Another example is that of <link linkend="ref.session">sessions</link>. - When register_globals = on, we could also use - <varname>$username</varname> in our example below but again you must - realize that <varname>$username</varname> could also come from other - means, such as GET (through the URL). - </para> - <para> - <example> - <title>Example use of sessions with register_globals on or off</title> - <programlisting role="php"> -<![CDATA[ -<?php -// We wouldn't know where $username came from but do know $_SESSION is -// for session data -if (isset($_SESSION['username'])) { - - echo "Hello <b>{$_SESSION['username']}</b>"; - -} else { - - echo "Hello <b>Guest</b><br />"; - echo "Would you like to login?"; - -} -?> -]]> - </programlisting> - </example> - </para> - <para> - It's even possible to take preventative measures to warn when forging is - being attempted. If you know ahead of time exactly where a variable - should be coming from, you can check to see if the submitted data is - coming from an inappropriate kind of submission. While it doesn't - guarantee that data has not been forged, it does require an attacker to - guess the right kind of forging. If you don't care where the request - data comes from, you can use <varname>$_REQUEST</varname> as it contains - a mix of GET, POST and COOKIE data. See also the manual section on - using <link linkend="language.variables.external">variables from outside - of PHP</link>. - </para> - <para> - <example> - <title>Detecting simple variable poisoning</title> - <programlisting role="php"> -<![CDATA[ -<?php -if (isset($_COOKIE['MAGIC_COOKIE'])) { - - // MAGIC_COOKIE comes from a cookie. - // Be sure to validate the cookie data! - -} elseif (isset($_GET['MAGIC_COOKIE']) || isset($_POST['MAGIC_COOKIE'])) { - - mail("admin@example.com", "Possible breakin attempt", $_SERVER['REMOTE_ADDR']); - echo "Security violation, admin has been alerted."; - exit; - -} else { - - // MAGIC_COOKIE isn't set through this REQUEST - -} -?> -]]> - </programlisting> - </example> - </para> - <para> - Of course, simply turning off register_globals does not mean your code - is secure. For every piece of data that is submitted, it should also be - checked in other ways. Always validate your user data and initialize - your variables! To check for unitialized variables you may turn up - <function>error_reporting</function> to show - <constant>E_NOTICE</constant> level errors. - </para> - - ¬e.superglobals; - - </sect1> - - - <sect1 id="security.variables"> - <title>User Submitted Data</title> - <para> - The greatest weakness in many PHP programs is not inherent in the - language itself, but merely an issue of code not being written with - security in mind. For this reason, you should always take the time - to consider the implications of a given piece of code, to ascertain - the possible damage if an unexpected variable is submitted to it. - <example> - <title>Dangerous Variable Usage</title> - <programlisting role="php"> -<![CDATA[ -<?php -// remove a file from the user's home directory... or maybe -// somebody else's? -unlink ($evil_var); - -// Write logging of their access... or maybe an /etc/passwd entry? -fputs ($fp, $evil_var); - -// Execute something trivial.. or rm -rf *? -system ($evil_var); -exec ($evil_var); - -?> -]]> - </programlisting> - </example> - You should always carefully examine your code to make sure that any - variables being submitted from a web browser are being properly - checked, and ask yourself the following questions: - <itemizedlist> - <listitem> - <simpara> - Will this script only affect the intended files? - </simpara> - </listitem> - <listitem> - <simpara> - Can unusual or undesirable data be acted upon? - </simpara> - </listitem> - <listitem> - <simpara> - Can this script be used in unintended ways? - </simpara> - </listitem> - <listitem> - <simpara> - Can this be used in conjunction with other scripts in a negative - manner? - </simpara> - </listitem> - <listitem> - <simpara> - Will any transactions be adequately logged? - </simpara> - </listitem> - </itemizedlist> - By adequately asking these questions while writing the script, - rather than later, you prevent an unfortunate re-write when you - need to increase your security. By starting out with this mindset, - you won't guarantee the security of your system, but you can help - improve it. - </para> - <para> - You may also want to consider turning off register_globals, - magic_quotes, or other convenience settings which may confuse - you as to the validity, source, or value of a given variable. - Working with PHP in error_reporting(E_ALL) mode can also help warn - you about variables being used before they are checked or - initialized (so you can prevent unusual data from being - operated upon). - </para> - </sect1> - - <sect1 id="security.hiding"> - <title>Hiding PHP</title> - <para> - In general, security by obscurity is one of the weakest forms of security. - But in some cases, every little bit of extra security is desirable. - </para> - <para> - A few simple techniques can help to hide PHP, possibly slowing - down an attacker who is attempting to discover weaknesses in your - system. By setting expose_php = off in your &php.ini; file, you - reduce the amount of information available to them. - </para> - <para> - Another tactic is to configure web servers such as apache to - parse different filetypes through PHP, either with an &htaccess; - directive, or in the apache configuration file itself. You can - then use misleading file extensions: - <example> - <title>Hiding PHP as another language</title> - <programlisting role="apache-conf"> -<![CDATA[ -# Make PHP code look like other code types -AddType application/x-httpd-php .asp .py .pl -]]> - </programlisting> - </example> - Or obscure it completely: - <example> - <title>Using unknown types for PHP extensions</title> - <programlisting role="apache-conf"> -<![CDATA[ -# Make PHP code look like unknown types -AddType application/x-httpd-php .bop .foo .133t -]]> - </programlisting> - </example> - Or hide it as HTML code, which has a slight performance hit because - all HTML will be parsed through the PHP engine: - <example> - <title>Using HTML types for PHP extensions</title> - <programlisting role="apache-conf"> -<![CDATA[ -# Make all PHP code look like HTML -AddType application/x-httpd-php .htm .html -]]> - </programlisting> - </example> - For this to work effectively, you must rename your PHP files with - the above extensions. While it is a form of security through - obscurity, it's a minor preventative measure with few drawbacks. - </para> - </sect1> - - <sect1 id="security.current"> - <title>Keeping Current</title> - <simpara> - PHP, like any other large system, is under constant scrutiny and - improvement. Each new version will often include both major and - minor changes to enhance and repair security flaws, configuration - mishaps, and other issues that will affect the overall security - and stability of your system. - </simpara> - <simpara> - Like other system-level scripting languages and programs, the best - approach is to update often, and maintain awareness of the latest - versions and their changes. - </simpara> - </sect1> + &security.general; + &security.cgi-bin; + &security.apache; + &security.filesystem; + &security.database; + &security.errors; + &security.globals; + &security.variables; + &security.hiding; + &security.current; </chapter> <!-- Keep this comment at the end of the file
Index: index.xml =================================================================== RCS file: /repository/phpdoc/en/security/index.xml,v retrieving revision 1.66 diff -u -r1.66 index.xml --- index.xml 6 Jan 2004 09:22:21 -0000 1.66 +++ index.xml 25 Jan 2004 23:48:03 -0000 @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="iso-8859-1"?> <!-- $Revision: 1.66 $ --> +<!-- Last Revision before split: 1.66 --> <chapter id="security.index"> <title>Security</title> @@ -38,1270 +39,16 @@ coding for different levels of security. </simpara> - <sect1 id="security.general"> - <title>General considerations</title> - <simpara> - A completely secure system is a virtual impossibility, so an - approach often used in the security profession is one of balancing - risk and usability. If every variable submitted by a user required - two forms of biometric validation (such as a retinal scan and a - fingerprint), you would have an extremely high level of - accountability. It would also take half an hour to fill out a fairly - complex form, which would tend to encourage users to find ways of - bypassing the security. - </simpara> - <simpara> - The best security is often inobtrusive enough to suit the - requirements without the user being prevented from accomplishing - their work, or over-burdening the code author with excessive - complexity. Indeed, some security attacks are merely exploits of - this kind of overly built security, which tends to erode over time. - </simpara> - <simpara> - A phrase worth remembering: A system is only as good as the weakest - link in a chain. If all transactions are heavily logged based on - time, location, transaction type, etc. but the user is only - verified based on a single cookie, the validity of tying the users - to the transaction log is severely weakened. - </simpara> - <simpara> - When testing, keep in mind that you will not be able to test all - possibilities for even the simplest of pages. The input you - may expect will be completely unrelated to the input given by - a disgruntled employee, a cracker with months of time on their - hands, or a housecat walking across the keyboard. This is why it's - best to look at the code from a logical perspective, to discern - where unexpected data can be introduced, and then follow how it is - modified, reduced, or amplified. - </simpara> - <simpara> - The Internet is filled with people trying to make a name for - themselves by breaking your code, crashing your site, posting - inappropriate content, and otherwise making your day interesting. - It doesn't matter if you have a small or large site, you are - a target by simply being online, by having a server that can be - connected to. Many cracking programs do not discern by size, they - simply trawl massive IP blocks looking for victims. Try not to - become one. - </simpara> - </sect1> - - <sect1 id="security.cgi-bin"> - <title>Installed as CGI binary</title> - - <sect2 id="security.cgi-bin.attacks"> - <title>Possible attacks</title> - <simpara> - Using PHP as a <acronym>CGI</acronym> binary is an option for - setups that for some reason do not wish to integrate PHP as a - module into server software (like Apache), or will use PHP with - different kinds of CGI wrappers to create safe chroot and setuid - environments for scripts. This setup usually involves installing - executable PHP binary to the web server cgi-bin directory. CERT - advisory <ulink url="&url.cert;">CA-96.11</ulink> recommends - against placing any interpreters into cgi-bin. Even if the PHP - binary can be used as a standalone interpreter, PHP is designed - to prevent the attacks this setup makes possible: - </simpara> - <itemizedlist> - <listitem> - <simpara> - Accessing system files: <filename - role="url">http://my.host/cgi-bin/php?/etc/passwd</filename> - </simpara> - <simpara> - The query information in a url after the question mark (?) is - passed as command line arguments to the interpreter by the CGI - interface. Usually interpreters open and execute the file - specified as the first argument on the command line. - </simpara> - <simpara> - When invoked as a CGI binary, PHP refuses to interpret the - command line arguments. - </simpara> - </listitem> - <listitem> - <simpara> - Accessing any web document on server: <filename - role="url">http://my.host/cgi-bin/php/secret/doc.html</filename> - </simpara> - <simpara> - The path information part of the url after the PHP binary name, - <filename role="uri">/secret/doc.html</filename> is - conventionally used to specify the name of the file to be - opened and interpreted by the <acronym>CGI</acronym> program. - Usually some web server configuration directives (Apache: - Action) are used to redirect requests to documents like - <filename - role="url">http://my.host/secret/script.php</filename> to the - PHP interpreter. With this setup, the web server first checks - the access permissions to the directory <filename - role="uri">/secret</filename>, and after that creates the - redirected request <filename - role="url">http://my.host/cgi-bin/php/secret/script.php</filename>. - Unfortunately, if the request is originally given in this form, - no access checks are made by web server for file <filename - role="uri">/secret/script.php</filename>, but only for the - <filename role="uri">/cgi-bin/php</filename> file. This way - any user able to access <filename - role="uri">/cgi-bin/php</filename> is able to access any - protected document on the web server. - </simpara> - <simpara> - In PHP, compile-time configuration option <link - linkend="install.configure.enable-force-cgi-redirect">--enable-force-cgi-redirect</link> - and runtime configuration directives <link - linkend="ini.doc-root">doc_root</link> and <link - linkend="ini.user-dir">user_dir</link> can be used to prevent - this attack, if the server document tree has any directories - with access restrictions. See below for full the explanation - of the different combinations. - </simpara> - </listitem> - </itemizedlist> - </sect2> - - <sect2 id="security.cgi-bin.default"> - <title>Case 1: only public files served</title> - - <simpara> - If your server does not have any content that is not restricted - by password or ip based access control, there is no need for - these configuration options. If your web server does not allow - you to do redirects, or the server does not have a way to - communicate to the PHP binary that the request is a safely - redirected request, you can specify the option <link - linkend="install.configure.enable-force-cgi-redirect">--enable-force-cgi-redirect</link> - to the configure script. You still have to make sure your PHP - scripts do not rely on one or another way of calling the script, - neither by directly <filename - role="php">http://my.host/cgi-bin/php/dir/script.php</filename> - nor by redirection <filename - role="php">http://my.host/dir/script.php</filename>. - </simpara> - <simpara> - Redirection can be configured in Apache by using AddHandler and - Action directives (see below). - </simpara> - </sect2> - - <sect2 id="security.cgi-bin.force-redirect"> - <title>Case 2: using --enable-force-cgi-redirect</title> - <simpara> - This compile-time option prevents anyone from calling PHP - directly with a url like <filename - role="php">http://my.host/cgi-bin/php/secretdir/script.php</filename>. - Instead, PHP will only parse in this mode if it has gone through - a web server redirect rule. - </simpara> - <simpara> - Usually the redirection in the Apache configuration is done with - the following directives: - </simpara> - <programlisting role="apache-conf"> -<![CDATA[ -Action php-script /cgi-bin/php -AddHandler php-script .php -]]> - </programlisting> - <simpara> - This option has only been tested with the Apache web server, and - relies on Apache to set the non-standard CGI environment variable - <envar>REDIRECT_STATUS</envar> on redirected requests. If your - web server does not support any way of telling if the request is - direct or redirected, you cannot use this option and you must use - one of the other ways of running the CGI version documented - here. - </simpara> - </sect2> - - <sect2 id="security.cgi-bin.doc-root"> - <title>Case 3: setting doc_root or user_dir</title> - <simpara> - To include active content, like scripts and executables, in the - web server document directories is sometimes considered an insecure - practice. If, because of some configuration mistake, the scripts - are not executed but displayed as regular HTML documents, this - may result in leakage of intellectual property or security - information like passwords. Therefore many sysadmins will prefer - setting up another directory structure for scripts that are - accessible only through the PHP CGI, and therefore always - interpreted and not displayed as such. - </simpara> - <simpara> - Also if the method for making sure the requests are not - redirected, as described in the previous section, is not - available, it is necessary to set up a script doc_root that is - different from web document root. - </simpara> - <simpara> - You can set the PHP script document root by the configuration - directive <link linkend="ini.doc-root">doc_root</link> in the - <link linkend="configuration.file">configuration file</link>, or - you can set the environment variable - <envar>PHP_DOCUMENT_ROOT</envar>. If it is set, the CGI version - of PHP will always construct the file name to open with this - <parameter>doc_root</parameter> and the path information in the - request, so you can be sure no script is executed outside this - directory (except for <parameter>user_dir</parameter> - below). - </simpara> - <simpara> - Another option usable here is <link - linkend="ini.user-dir">user_dir</link>. When user_dir is unset, - only thing controlling the opened file name is - <parameter>doc_root</parameter>. Opening an url like <filename - role="url">http://my.host/~user/doc.php</filename> does not - result in opening a file under users home directory, but a file - called <filename role="uri">~user/doc.php</filename> under - doc_root (yes, a directory name starting with a tilde - [<literal>~</literal>]). - </simpara> - <simpara> - If user_dir is set to for example <filename - role="dir">public_php</filename>, a request like <filename - role="url">http://my.host/~user/doc.php</filename> will open a - file called <filename>doc.php</filename> under the directory - named <filename role="dir">public_php</filename> under the home - directory of the user. If the home of the user is <filename - role="dir">/home/user</filename>, the file executed is - <filename>/home/user/public_php/doc.php</filename>. - </simpara> - <simpara> - <parameter>user_dir</parameter> expansion happens regardless of - the <parameter>doc_root</parameter> setting, so you can control - the document root and user directory access - separately. - </simpara> - </sect2> - - <sect2 id="security.cgi-bin.shell"> - <title>Case 4: PHP parser outside of web tree</title> - <para> - A very secure option is to put the PHP parser binary somewhere - outside of the web tree of files. In <filename - role="dir">/usr/local/bin</filename>, for example. The only real - downside to this option is that you will now have to put a line - similar to: - <informalexample> - <programlisting> -<![CDATA[ -#!/usr/local/bin/php -]]> - </programlisting> - </informalexample> - as the first line of any file containing PHP tags. You will also - need to make the file executable. That is, treat it exactly as - you would treat any other CGI script written in Perl or sh or any - other common scripting language which uses the - <literal>#!</literal> shell-escape mechanism for launching - itself. - </para> - <para> - To get PHP to handle <envar>PATH_INFO</envar> and - <envar>PATH_TRANSLATED</envar> information correctly with this - setup, the PHP parser should be compiled with the <link - linkend="install.configure.enable-discard-path">--enable-discard-path</link> - configure option. - </para> - </sect2> - - </sect1> - - <sect1 id="security.apache"> - <title>Installed as an Apache module</title> - <simpara> - When PHP is used as an Apache module it inherits Apache's user - permissions (typically those of the "nobody" user). This has several - impacts on security and authorization. For example, if you are using - PHP to access a database, unless that database has built-in access - control, you will have to make the database accessible to the - "nobody" user. This means a malicious script could access and modify - the database, even without a username and password. It's entirely - possible that a web spider could stumble across a database - administrator's web page, and drop all of your databases. You can - protect against this with Apache authorization, or you can design - your own access model using LDAP, &htaccess; files, etc. and include - that code as part of your PHP scripts. - </simpara> - <simpara> - Often, once security is established to the point where the PHP user - (in this case, the apache user) has very little risk attached to it, - it is discovered that PHP is now prevented from writing any files - to user directories. Or perhaps it has been prevented from accessing - or changing databases. It has equally been secured from writing - good and bad files, or entering good and bad database transactions. - </simpara> - <simpara> - A frequent security mistake made at this point is to allow apache - root permissions, or to escalate apache's abilitites in some other - way. - </simpara> - <simpara> - Escalating the Apache user's permissions to root is extremely - dangerous and may compromise the entire system, so sudo'ing, - chroot'ing, or otherwise running as root should not be considered by - those who are not security professionals. - </simpara> - <simpara> - There are some simpler solutions. By using - <link linkend="ini.open-basedir">open_basedir</link> you can control and restrict what - directories are allowed to be used for PHP. You can also set up - apache-only areas, to restrict all web based activity to non-user, - or non-system, files. - </simpara> - </sect1> - - <sect1 id="security.filesystem"> - <title>Filesystem Security</title> - <simpara> - PHP is subject to the security built into most server systems with - respect to permissions on a file and directory basis. This allows - you to control which files in the filesystem may be read. Care - should be taken with any files which are world readable to ensure - that they are safe for reading by all users who have access to that - filesystem. - </simpara> - <simpara> - Since PHP was designed to allow user level access to the filesystem, - it's entirely possible to write a PHP script that will allow you - to read system files such as /etc/passwd, modify your ethernet - connections, send massive printer jobs out, etc. This has some - obvious implications, in that you need to ensure that the files - that you read from and write to are the appropriate ones. - </simpara> - <simpara> - Consider the following script, where a user indicates that they'd - like to delete a file in their home directory. This assumes a - situation where a PHP web interface is regularly used for file - management, so the Apache user is allowed to delete files in - the user home directories. - </simpara> - <para> - <example> - <title>Poor variable checking leads to....</title> - <programlisting role="php"> -<![CDATA[ -<?php -// remove a file from the user's home directory -$username = $_POST['user_submitted_name']; -$homedir = "/home/$username"; -$file_to_delete = "$userfile"; -unlink ("$homedir/$userfile"); -echo "$file_to_delete has been deleted!"; -?> -]]> - </programlisting> - </example> - Since the username is postable from a user form, they can submit - a username and file belonging to someone else, and delete files. - In this case, you'd want to use some other form of authentication. - Consider what could happen if the variables submitted were - "../etc/" and "passwd". The code would then effectively read: - <example> - <title>... A filesystem attack</title> - <programlisting role="php"> -<![CDATA[ -<?php -// removes a file from anywhere on the hard drive that -// the PHP user has access to. If PHP has root access: -$username = "../etc/"; -$homedir = "/home/../etc/"; -$file_to_delete = "passwd"; -unlink ("/home/../etc/passwd"); -echo "/home/../etc/passwd has been deleted!"; -?> -]]> - </programlisting> - </example> - There are two important measures you should take to prevent these - issues. - <itemizedlist> - <listitem> - <simpara> - Only allow limited permissions to the PHP web user binary. - </simpara> - </listitem> - <listitem> - <simpara> - Check all variables which are submitted. - </simpara> - </listitem> - </itemizedlist> - Here is an improved script: - <example> - <title>More secure file name checking</title> - <programlisting role="php"> -<![CDATA[ -<?php -// removes a file from the hard drive that -// the PHP user has access to. -$username = $_SERVER['REMOTE_USER']; // using an authentication mechanisim - -$homedir = "/home/$username"; - -$file_to_delete = basename("$userfile"); // strip paths -unlink ($homedir/$file_to_delete); - -$fp = fopen("/home/logging/filedelete.log","+a"); //log the deletion -$logstring = "$username $homedir $file_to_delete"; -fputs ($fp, $logstring); -fclose($fp); - -echo "$file_to_delete has been deleted!"; -?> -]]> - </programlisting> - </example> - However, even this is not without it's flaws. If your authentication - system allowed users to create their own user logins, and a user - chose the login "../etc/", the system is once again exposed. For - this reason, you may prefer to write a more customized check: - <example> - <title>More secure file name checking</title> - <programlisting role="php"> -<![CDATA[ -<?php -$username = $_SERVER['REMOTE_USER']; // using an authentication mechanisim -$homedir = "/home/$username"; - -if (!ereg('^[^./][^/]*$', $userfile)) - die('bad filename'); //die, do not process - -if (!ereg('^[^./][^/]*$', $username)) - die('bad username'); //die, do not process -//etc... -?> -]]> - </programlisting> - </example> - </para> - <para> - Depending on your operating system, there are a wide variety of files - which you should be concerned about, including device entries (/dev/ - or COM1), configuration files (/etc/ files and the .ini files), - well known file storage areas (/home/, My Documents), etc. For this - reason, it's usually easier to create a policy where you forbid - everything except for what you explicitly allow. - </para> - </sect1> - - <sect1 id="security.database"> - <title>Database Security</title> - - <simpara> - Nowadays, databases are cardinal components of any web based application by - enabling websites to provide varying dynamic content. Since very sensitive - or secret information can be stored in a database, you should strongly - consider protecting your databases. - </simpara> - <simpara> - To retrieve or to store any information you need to connect to the database, - send a legitimate query, fetch the result, and close the connection. - Nowadays, the commonly used query language in this interaction is the - Structured Query Language (SQL). See how an attacker can <link - linkend="security.database.sql-injection">tamper with an SQL query</link>. - </simpara> - <simpara> - As you can surmise, PHP cannot protect your database by itself. The - following sections aim to be an introduction into the very basics of how to - access and manipulate databases within PHP scripts. - </simpara> - <simpara> - Keep in mind this simple rule: defense in depth. The more places you - take action to increase the protection of your database, the less - probability of an attacker succeeding in exposing or abusing any stored - information. Good design of the database schema and the application - deals with your greatest fears. - </simpara> - - <sect2 id="security.database.design"> - <title>Designing Databases</title> - <simpara> - The first step is always to create the database, unless you want to use - one from a third party. When a database is created, it is - assigned to an owner, who executed the creation statement. Usually, only - the owner (or a superuser) can do anything with the objects in that - database, and in order to allow other users to use it, privileges must be - granted. - </simpara> - <simpara> - Applications should never connect to the database as its owner or a - superuser, because these users can execute any query at will, for - example, modifying the schema (e.g. dropping tables) or deleting its - entire content. - </simpara> - <simpara> - You may create different database users for every aspect of your - application with very limited rights to database objects. The most - required privileges should be granted only, and avoid that the same user - can interact with the database in different use cases. This means that if - intruders gain access to your database using your applications credentials, - they can only effect as many changes as your application can. - </simpara> - <simpara> - You are encouraged not to implement all the business logic in the web - application (i.e. your script), instead do it in the database schema - using views, triggers or rules. If the system evolves, new ports will be - intended to open to the database, and you have to re-implement the logic - in each separate database client. Over and above, triggers can be used - to transparently and automatically handle fields, which often provides - insight when debugging problems with your application or tracing back - transactions. - </simpara> - </sect2> - - <sect2 id="security.database.connection"> - <title>Connecting to Database</title> - <simpara> - You may want to estabilish the connections over SSL to encrypt - client/server communications for increased security, or you can use ssh - to encrypt the network connection between clients and the database server. - If either of these is used, then monitoring your traffic and gaining - information about your database will be difficult for a would-be attacker. - </simpara> - <!--simpara> - If your database server has native SSL support, consider using <link - linkend="ref.openssl">OpenSSL functions</link> in communication between - PHP and database via SSL. - </simpara--> - </sect2> - - <sect2 id="security.database.storage"> - <title>Encrypted Storage Model</title> - <simpara> - SSL/SSH protects data travelling from the client to the server, SSL/SSH - does not protect the persistent data stored in a database. SSL is an - on-the-wire protocol. - </simpara> - <simpara> - Once an attacker gains access to your database directly (bypassing the - webserver), the stored sensitive data may be exposed or misused, unless - the information is protected by the database itself. Encrypting the data - is a good way to mitigate this threat, but very few databases offer this - type of data encryption. - </simpara> - <simpara> - The easiest way to work around this problem is to first create your own - encryption package, and then use it from within your PHP scripts. PHP - can assist you in this with several extensions, such as <link - linkend="ref.mcrypt">Mcrypt</link> and <link - linkend="ref.mhash">Mhash</link>, covering a wide variety of encryption - algorithms. The script encrypts the data before inserting it into the database, and decrypts - it when retrieving. See the references for further examples of how - encryption works. - </simpara> - <simpara> - In case of truly hidden data, if its raw representation is not needed - (i.e. not be displayed), hashing may also be taken into consideration. - The well-known example for the hashing is storing the MD5 hash of a - password in a database, instead of the password itself. See also - <function>crypt</function> and <function>md5</function>. - </simpara> - <example> - <title>Using hashed password field</title> - <programlisting role="php"> -<![CDATA[ -// storing password hash -$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');", - addslashes($username), md5($password)); -$result = pg_exec($connection, $query); - -// querying if user submitted the right password -$query = sprintf("SELECT 1 FROM users WHERE name='%s' AND pwd='%s';", - addslashes($username), md5($password)); -$result = pg_exec($connection, $query); - -if (pg_numrows($result) > 0) { - echo "Welcome, $username!"; -} -else { - echo "Authentication failed for $username."; -} -]]> - </programlisting> - </example> - </sect2> - - <sect2 id="security.database.sql-injection"> - <title>SQL Injection</title> - <simpara> - Many web developers are unaware of how SQL queries can be tampered with, - and assume that an SQL query is a trusted command. It means that SQL - queries are able to circumvent access controls, thereby bypassing standard - authentication and authorization checks, and sometimes SQL queries even - may allow access to host operating system level commands. - </simpara> - <simpara> - Direct SQL Command Injection is a technique where an attacker creates or - alters existing SQL commands to expose hidden data, or to override valuable - ones, or even to execute dangerous system level commands on the database - host. This is accomplished by the application taking user input and - combining it with static parameters to build a SQL query. The following - examples are based on true stories, unfortunately. - </simpara> - <para> - Owing to the lack of input validation and connecting to the database on - behalf of a superuser or the one who can create users, the attacker - may create a superuser in your database. - <example> - <title> - Splitting the result set into pages ... and making superusers - (PostgreSQL and MySQL) - </title> - <programlisting role="php"> -<![CDATA[ -$offset = argv[0]; // beware, no input validation! -$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; -// with PostgreSQL -$result = pg_exec($conn, $query); -// with MySQL -$result = mysql_query($query); -]]> - </programlisting> - </example> - Normal users click on the 'next', 'prev' links where the <varname>$offset</varname> - is encoded into the URL. The script expects that the incoming - <varname>$offset</varname> is a decimal number. However, what if someone tries to - break in by appending a <function>urlencode</function>'d form of the - following to the URL - <informalexample> - <programlisting> -<![CDATA[ -// in case of PostgreSQL -0; -insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) - select 'crack', usesysid, 't','t','crack' - from pg_shadow where usename='postgres'; --- - -// in case of MySQL -0; -UPDATE user SET Password=PASSWORD('crack') WHERE user='root'; -FLUSH PRIVILEGES; -]]> - </programlisting> - </informalexample> - If it happened, then the script would present a superuser access to him. - Note that <literal>0;</literal> is to supply a valid offset to the - original query and to terminate it. - </para> - <note> - <para> - It is common technique to force the SQL parser to ignore the rest of the - query written by the developer with <literal>--</literal> which is the - comment sign in SQL. - </para> - </note> - <para> - A feasible way to gain passwords is to circumvent your search result pages. - The only thing the attacker needs to do is to see if there are any submitted variables - used in SQL statements which are not handled properly. These filters can be set - commonly in a preceding form to customize <literal>WHERE, ORDER BY, - LIMIT</literal> and <literal>OFFSET</literal> clauses in <literal>SELECT</literal> - statements. If your database supports the <literal>UNION</literal> construct, - the attacker may try to append an entire query to the original one to list - passwords from an arbitrary table. Using encrypted password fields is - strongly encouraged. - <example> - <title> - Listing out articles ... and some passwords (any database server) - </title> - <programlisting role="php"> -<![CDATA[ -$query = "SELECT id, name, inserted, size FROM products - WHERE size = '$size' - ORDER BY $order LIMIT $limit, $offset;"; -$result = odbc_exec($conn, $query); -]]> - </programlisting> - </example> - The static part of the query can be combined with another - <literal>SELECT</literal> statement which reveals all passwords: - <informalexample> - <programlisting> -<![CDATA[ -' -union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --- -]]> - </programlisting> - </informalexample> - If this query (playing with the <literal>'</literal> and - <literal>--</literal>) were assigned to one of the variables used in - <varname>$query</varname>, the query beast awakened. - </para> - <para> - SQL UPDATE's are also susceptible to attack. These queries are - also threatened by chopping and appending an entirely new query to it. But - the attacker might fiddle with the <literal>SET</literal> clause. In this - case some schema information must be possessed to manipulate the query - successfully. This can be acquired by examining the form variable names, or - just simply brute forcing. There are not so many naming conventions for - fields storing passwords or usernames. - <example> - <title> - From resetting a password ... to gaining more privileges (any database server) - </title> - <programlisting role="php"> -<![CDATA[ -$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';"; -]]> - </programlisting> - </example> - But a malicious user sumbits the value - <literal>' or uid like'%admin%'; --</literal> to <varname>$uid</varname> to - change the admin's password, or simply sets <varname>$pwd</varname> to - <literal>"hehehe', admin='yes', trusted=100 "</literal> (with a trailing - space) to gain more privileges. Then, the query will be twisted: - <informalexample> - <programlisting role="php"> -<![CDATA[ -// $uid == ' or uid like'%admin%'; -- -$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --"; - -// $pwd == "hehehe', admin='yes', trusted=100 " -$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;" -]]> - </programlisting> - </informalexample> - </para> - <para> - A frightening example how operating system level commands can be accessed - on some database hosts. - <example> - <title>Attacking the database hosts operating system (MSSQL Server)</title> - <programlisting role="php"> -<![CDATA[ -$query = "SELECT * FROM products WHERE id LIKE '%$prod%'"; -$result = mssql_query($query); -]]> - </programlisting> - </example> - If attacker submits the value - <literal>a%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal> - to <varname>$prod</varname>, then the <varname>$query</varname> will be: - <informalexample> - <programlisting role="php"> -<![CDATA[ -$query = "SELECT * FROM products - WHERE id LIKE '%a%' - exec master..xp_cmdshell 'net user test testpass /ADD'--"; -$result = mssql_query($query); -]]> - </programlisting> - </informalexample> - MSSQL Server executes the SQL statements in the batch including a command - to add a new user to the local accounts database. If this application - were running as <literal>sa</literal> and the MSSQLSERVER service is - running with sufficient privileges, the attacker would now have an - account with which to access this machine. - </para> - <note> - <para> - Some of the examples above is tied to a specific database server. This - does not mean that a similar attack is impossible against other products. - Your database server may be similarly vulnerable in another manner. - </para> - </note> - - <sect3 id="security.database.avoiding"> - <title>Avoiding techniques</title> - <simpara> - You may plead that the attacker must possess a piece of information - about the database schema in most examples. You are right, but you - never know when and how it can be taken out, and if it happens, - your database may be exposed. If you are using an open source, or - publicly available database handling package, which may belong to a - content management system or forum, the intruders easily produce - a copy of a piece of your code. It may be also a security risk if it - is a poorly designed one. - </simpara> - <simpara> - These attacks are mainly based on exploiting the code not being written - with security in mind. Never trust any kind of input, especially that - which comes from the client side, even though it comes from a select box, - a hidden input field or a cookie. The first example shows that such a - blameless query can cause disasters. - </simpara> - - <itemizedlist> - <listitem> - <simpara> - Never connect to the database as a superuser or as the database owner. - Use always customized users with very limited privileges. - </simpara> - </listitem> - <listitem> - <simpara> - Check if the given input has the expected data type. PHP has - a wide range of input validating functions, from the simplest ones - found in <link linkend="ref.variables">Variable Functions</link> and - in <link linkend="ref.ctype">Character Type Functions</link> - (e.g. <function>is_numeric</function>, <function>ctype_digit</function> - respectively) and onwards to the - <link linkend="ref.pcre">Perl compatible Regular Expressions</link> - support. - </simpara> - </listitem> - <listitem> - <para> - If the application waits for numerical input, consider verifying data - with <function>is_numeric</function>, or silently change its type - using <function>settype</function>, or use its numeric representation - by <function>sprintf</function>. - <example> - <title>A more secure way to compose a query for paging</title> - <programlisting role="php"> -<![CDATA[ -settype($offset, 'integer'); -$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; - -// please note %d in the format string, using %s would be meaningless -$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;", - $offset); -]]> - </programlisting> - </example> - </para> - </listitem> - <listitem> - <simpara> - Quote each non numeric user input which is passed to the database with - <function>addslashes</function> or <function>addcslashes</function>. - See <link linkend="security.database.storage">the first example</link>. - As the examples shows, quotes burnt into the static part of the query - is not enough, and can be easily cracked. - </simpara> - </listitem> - <listitem> - <simpara> - Do not print out any database specific information, especially - about the schema, by fair means or foul. See also <link - linkend="security.errors">Error Reporting</link> and <link - linkend="ref.errorfunc">Error Handling and Logging Functions</link>. - </simpara> - </listitem> - <listitem> - <simpara> - You may use stored procedures and previously defined cursors to abstract - data access so that users do not directly access tables or views, but - this solution has another impacts. - </simpara> - </listitem> - </itemizedlist> - - <simpara> - Besides these, you benefit from logging queries either within your script - or by the database itself, if it supports logging. Obviously, the logging is unable - to prevent any harmful attempt, but it can be helpful to trace back which - application has been circumvented. The log is not useful by itself, but - through the information it contains. More detail is generally better than less. - </simpara> - </sect3> - </sect2> - </sect1> - - <sect1 id="security.errors"> - <title>Error Reporting</title> - <para> - With PHP security, there are two sides to error reporting. One is - beneficial to increasing security, the other is detrimental. - </para> - <para> - A standard attack tactic involves profiling a system by feeding - it improper data, and checking for the kinds, and contexts, of the - errors which are returned. This allows the system cracker to probe - for information about the server, to determine possible weaknesses. - For example, if an attacker had gleaned information about a page - based on a prior form submission, they may attempt to override - variables, or modify them: - <example> - <title>Attacking Variables with a custom HTML page</title> - <programlisting role="php"> -<![CDATA[ -<form method="post" action="attacktarget?username=badfoo&password=badfoo"> -<input type="hidden" name="username" value="badfoo" /> -<input type="hidden" name="password" value="badfoo" /> -</form> -]]> - </programlisting> - </example> - </para> - <para> - The PHP errors which are normally returned can be quite helpful to a - developer who is trying to debug a script, indicating such things - as the function or file that failed, the PHP file it failed in, - and the line number which the failure occured in. This is all - information that can be exploited. It is not uncommon for a php - developer to use <function>show_source</function>, - <function>highlight_string</function>, or - <function>highlight_file</function> as a debugging measure, but in - a live site, this can expose hidden variables, unchecked syntax, - and other dangerous information. Especially dangerous is running - code from known sources with built-in debugging handlers, or using - common debugging techniques. If the attacker can determine what - general technique you are using, they may try to brute-force a page, - by sending various common debugging strings: - <example> - <title>Exploiting common debugging variables</title> - <programlisting role="php"> -<![CDATA[ -<form method="post" action="attacktarget?errors=Y&showerrors=1&debug=1"> -<input type="hidden" name="errors" value="Y" /> -<input type="hidden" name="showerrors" value="1" /> -<input type="hidden" name="debug" value="1" /> -</form> -]]> - </programlisting> - </example> - </para> - <para> - Regardless of the method of error handling, the ability to probe a - system for errors leads to providing an attacker with more - information. - </para> - <para> - For example, the very style of a generic PHP error indicates a system - is running PHP. If the attacker was looking at an .html page, and - wanted to probe for the back-end (to look for known weaknesses in - the system), by feeding it the wrong data they may be able to - determine that a system was built with PHP. - </para> - <para> - A function error can indicate whether a system may be running a - specific database engine, or give clues as to how a web page or - programmed or designed. This allows for deeper investigation into - open database ports, or to look for specific bugs or weaknesses - in a web page. By feeding different pieces of bad data, for example, - an attacker can determine the order of authentication in a script, - (from the line number errors) as well as probe for exploits that - may be exploited in different locations in the script. - </para> - <para> - A filesystem or general PHP error can indicate what permissions - the webserver has, as well as the structure and organization of - files on the web server. Developer written error code can aggravate - this problem, leading to easy exploitation of formerly "hidden" - information. - </para> - <para> - There are three major solutions to this issue. The first is to - scrutinize all functions, and attempt to compensate for the bulk - of the errors. The second is to disable error reporting entirely - on the running code. The third is to use PHP's custom error - handling functions to create your own error handler. Depending - on your security policy, you may find all three to be applicable - to your situation. - </para> - <para> - One way of catching this issue ahead of time is to make use of - PHP's own <function>error_reporting</function>, to help you - secure your code and find variable usage that may be dangerous. - By testing your code, prior to deployment, with E_ALL, you can - quickly find areas where your variables may be open to poisoning - or modification in other ways. Once you are ready for deployment, - by using E_NONE, you insulate your code from probing. - <example> - <title>Finding dangerous variables with E_ALL</title> - <programlisting role="php"> -<![CDATA[ -<?php -if ($username) { // Not initialized or checked before usage - $good_login = 1; -} -if ($good_login == 1) { // If above test fails, not initialized or checked before usage - readfile ("/highly/sensitive/data/index.html"); -} -?> -]]> - </programlisting> - </example> - </para> - </sect1> - - <sect1 id="security.registerglobals"> - <title>Using Register Globals</title> - <para> - Perhaps the most controversial change in PHP is when the default value - for the PHP directive <link linkend="ini.register-globals"> - register_globals</link> went from ON to OFF in PHP - <ulink url="&url.php.release4.2.0;">4.2.0</ulink>. Reliance on this - directive was quite common and many people didn't even know it existed - and assumed it's just how PHP works. This page will explain how one can - write insecure code with this directive but keep in mind that the - directive itself isn't insecure but rather it's the misuse of it. - </para> - <para> - When on, register_globals will inject (poison) your scripts will all - sorts of variables, like request variables from HTML forms. This - coupled with the fact that PHP doesn't require variable initialization - means writing insecure code is that much easier. It was a difficult - decision, but the PHP community decided to disable this directive by - default. When on, people use variables yet really don't know for sure - where they come from and can only assume. Internal variables that are - defined in the script itself get mixed up with request data sent by - users and disabling register_globals changes this. Let's demonstrate - with an example misuse of register_globals: - </para> - <para> - <example> - <title>Example misuse with register_globals = on</title> - <programlisting role="php"> -<![CDATA[ -<?php -// define $authorized = true only if user is authenticated -if (authenticated_user()) { - $authorized = true; -} - -// Because we didn't first initialize $authorized as false, this might be -// defined through register_globals, like from GET auth.php?authorized=1 -// So, anyone can be seen as authenticated! -if ($authorized) { - include "/highly/sensitive/data.php"; -} -?> -]]> - </programlisting> - </example> - </para> - <para> - When register_globals = on, our logic above may be compromised. When - off, <varname>$authorized</varname> can't be set via request so it'll - be fine, although it really is generally a good programming practice to - initialize variables first. For example, in our example above we might - have first done <literal>$authorized = false</literal>. Doing this - first means our above code would work with register_globals on or off as - users by default would be unauthorized. - </para> - <para> - Another example is that of <link linkend="ref.session">sessions</link>. - When register_globals = on, we could also use - <varname>$username</varname> in our example below but again you must - realize that <varname>$username</varname> could also come from other - means, such as GET (through the URL). - </para> - <para> - <example> - <title>Example use of sessions with register_globals on or off</title> - <programlisting role="php"> -<![CDATA[ -<?php -// We wouldn't know where $username came from but do know $_SESSION is -// for session data -if (isset($_SESSION['username'])) { - - echo "Hello <b>{$_SESSION['username']}</b>"; - -} else { - - echo "Hello <b>Guest</b><br />"; - echo "Would you like to login?"; - -} -?> -]]> - </programlisting> - </example> - </para> - <para> - It's even possible to take preventative measures to warn when forging is - being attempted. If you know ahead of time exactly where a variable - should be coming from, you can check to see if the submitted data is - coming from an inappropriate kind of submission. While it doesn't - guarantee that data has not been forged, it does require an attacker to - guess the right kind of forging. If you don't care where the request - data comes from, you can use <varname>$_REQUEST</varname> as it contains - a mix of GET, POST and COOKIE data. See also the manual section on - using <link linkend="language.variables.external">variables from outside - of PHP</link>. - </para> - <para> - <example> - <title>Detecting simple variable poisoning</title> - <programlisting role="php"> -<![CDATA[ -<?php -if (isset($_COOKIE['MAGIC_COOKIE'])) { - - // MAGIC_COOKIE comes from a cookie. - // Be sure to validate the cookie data! - -} elseif (isset($_GET['MAGIC_COOKIE']) || isset($_POST['MAGIC_COOKIE'])) { - - mail("admin@example.com", "Possible breakin attempt", $_SERVER['REMOTE_ADDR']); - echo "Security violation, admin has been alerted."; - exit; - -} else { - - // MAGIC_COOKIE isn't set through this REQUEST - -} -?> -]]> - </programlisting> - </example> - </para> - <para> - Of course, simply turning off register_globals does not mean your code - is secure. For every piece of data that is submitted, it should also be - checked in other ways. Always validate your user data and initialize - your variables! To check for unitialized variables you may turn up - <function>error_reporting</function> to show - <constant>E_NOTICE</constant> level errors. - </para> - - ¬e.superglobals; - - </sect1> - - - <sect1 id="security.variables"> - <title>User Submitted Data</title> - <para> - The greatest weakness in many PHP programs is not inherent in the - language itself, but merely an issue of code not being written with - security in mind. For this reason, you should always take the time - to consider the implications of a given piece of code, to ascertain - the possible damage if an unexpected variable is submitted to it. - <example> - <title>Dangerous Variable Usage</title> - <programlisting role="php"> -<![CDATA[ -<?php -// remove a file from the user's home directory... or maybe -// somebody else's? -unlink ($evil_var); - -// Write logging of their access... or maybe an /etc/passwd entry? -fputs ($fp, $evil_var); - -// Execute something trivial.. or rm -rf *? -system ($evil_var); -exec ($evil_var); - -?> -]]> - </programlisting> - </example> - You should always carefully examine your code to make sure that any - variables being submitted from a web browser are being properly - checked, and ask yourself the following questions: - <itemizedlist> - <listitem> - <simpara> - Will this script only affect the intended files? - </simpara> - </listitem> - <listitem> - <simpara> - Can unusual or undesirable data be acted upon? - </simpara> - </listitem> - <listitem> - <simpara> - Can this script be used in unintended ways? - </simpara> - </listitem> - <listitem> - <simpara> - Can this be used in conjunction with other scripts in a negative - manner? - </simpara> - </listitem> - <listitem> - <simpara> - Will any transactions be adequately logged? - </simpara> - </listitem> - </itemizedlist> - By adequately asking these questions while writing the script, - rather than later, you prevent an unfortunate re-write when you - need to increase your security. By starting out with this mindset, - you won't guarantee the security of your system, but you can help - improve it. - </para> - <para> - You may also want to consider turning off register_globals, - magic_quotes, or other convenience settings which may confuse - you as to the validity, source, or value of a given variable. - Working with PHP in error_reporting(E_ALL) mode can also help warn - you about variables being used before they are checked or - initialized (so you can prevent unusual data from being - operated upon). - </para> - </sect1> - - <sect1 id="security.hiding"> - <title>Hiding PHP</title> - <para> - In general, security by obscurity is one of the weakest forms of security. - But in some cases, every little bit of extra security is desirable. - </para> - <para> - A few simple techniques can help to hide PHP, possibly slowing - down an attacker who is attempting to discover weaknesses in your - system. By setting expose_php = off in your &php.ini; file, you - reduce the amount of information available to them. - </para> - <para> - Another tactic is to configure web servers such as apache to - parse different filetypes through PHP, either with an &htaccess; - directive, or in the apache configuration file itself. You can - then use misleading file extensions: - <example> - <title>Hiding PHP as another language</title> - <programlisting role="apache-conf"> -<![CDATA[ -# Make PHP code look like other code types -AddType application/x-httpd-php .asp .py .pl -]]> - </programlisting> - </example> - Or obscure it completely: - <example> - <title>Using unknown types for PHP extensions</title> - <programlisting role="apache-conf"> -<![CDATA[ -# Make PHP code look like unknown types -AddType application/x-httpd-php .bop .foo .133t -]]> - </programlisting> - </example> - Or hide it as HTML code, which has a slight performance hit because - all HTML will be parsed through the PHP engine: - <example> - <title>Using HTML types for PHP extensions</title> - <programlisting role="apache-conf"> -<![CDATA[ -# Make all PHP code look like HTML -AddType application/x-httpd-php .htm .html -]]> - </programlisting> - </example> - For this to work effectively, you must rename your PHP files with - the above extensions. While it is a form of security through - obscurity, it's a minor preventative measure with few drawbacks. - </para> - </sect1> - - <sect1 id="security.current"> - <title>Keeping Current</title> - <simpara> - PHP, like any other large system, is under constant scrutiny and - improvement. Each new version will often include both major and - minor changes to enhance and repair security flaws, configuration - mishaps, and other issues that will affect the overall security - and stability of your system. - </simpara> - <simpara> - Like other system-level scripting languages and programs, the best - approach is to update often, and maintain awareness of the latest - versions and their changes. - </simpara> - </sect1> + &security.general; + &security.cgi-bin; + &security.apache; + &security.filesystem; + &security.database; + &security.errors; + &security.globals; + &security.variables; + &security.hiding; + &security.current; </chapter> <!-- Keep this comment at the end of the file