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("[EMAIL PROTECTED]", "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