|
|
PHP Guidelines
PHP is used extensively in the GNOME web
infrastructure. And with good reason; it's well adapted
to the task of creating web content and familiar to many
people. However, like any form of server-side scripting
there are security considerations. A poorly thought
script can pose dangers both for particular service in
question and for other services running on
gnome.org. For this reason, the current policy on
gnome.org is that any PHP must be reviewed by the
sysadmin team before it goes live. This page gives
guidelines to be used when creating PHP for use on
gnome.org and for reviewing proposed PHP scripts.
A global thing to be aware of is that all PHP scripts on
gnome.org are run as the 'apache' user. So there's no
way of isolating one PHP application against another or
against local users on the machine. Any file (such as a
password file) that can be read by the PHP script can be
read by another application or by a local user. Any file
that can be written to by the PHP script can be written
to be a local user. While the goal of the review process
and these guidelines is to prevent external attackers
from using bugs in one application to attack another
application or the system, if your application
application has security needs that are more than
nomimal, PHP is likely not the right technology. A CGI
script running as a special user with SUExec is likely
more appropriate.
Validating input
Most security vulnerabilities in web scripts come from
insufficiently validated input. Whether you are creating
a web page, a SQL command, a filename, or a shell command
to be excecuted, if you substitute in unchecked input
from the user bad things will happen.
-
Validate all external input. External input includes
values from the URL query string ($_GET[]), values from
forms ($_POST[]), cookie values ($_COOKIE[]). Any value
that comes from the user's machine must not be
trusted, even if in normal operation you set that
value yourself. The easiest way to make sure that a
script is validating all external input is to never
directly use the $_GET[], $_POST[], $_COOKIE[]
variables, but instead always assign the value (after
validation) to a local variable.
-
Specify the characters you want, not the characters
that you don't want. Trying to filter out dangerous
metacharacters like '<', '|',
';' is very error prone. The set of dangerous
metacharacters is different for shell, html, SQL, and
so forth, and it's easy to forget one. Instead,
specify the characters that you do want. For
instance, if a parameter is supposed to be an integer,
it should match the regular expression
/^[0-9]+$/. (Note that the regular expression
is anchored at both the start and the end.)
Quoting
In some cases, requiring a particular syntax isn't
possible. If the input is a human-readable text: a name,
a comment, an address, even a phone number, then
restricting the set of allowed characters is likely to
cause problems. (Remember, that just because your name
matches /^[A-Za-z ]+$/) doesn't mean that
everybody's name will.) In this case, we need to use
quoting instead.
- Quote database parameters with addslashes().
- Quote web content with htmlspecialchars().
Allowing users to enter HTML that is then inserted into
output should be avoided; writing a sanitizer that
prevents any possible malicious thing (unclosed tags,
javascript, links to images) is close to impossible.
- Don't try to quote shell command lines. While
quoting arbitrary text to insert into a command line
isn't that hard, it's quite no-obvious how to do it
and there is no PHP builtin that does the right thing.
If you need to pass arbitrary input to an external
command, use an environment variable or standard input.
External command execution
Running external commands leads to entirely new areas of
potential vulnerability. It's best avoided, but
sometimes is useful. There are a lot of PHP functions
that execute external executables, including:
exec(),
passthru(),
popen(),
proc_open(),
shell_exec(),
system(),
and
the backtick operator. All uses of these need to be checked
carefully.
-
Use fixed command lines with full pathnames to
binaries when possible.
-
Sanitize input to the external command as much as
possible. Don't assume that the external command has
been written with security in mind.
-
Sanitize input inside the external command as much as
possible. Don't assume the PHP frontend has been written
with security in mind; the external command might be
run by a local user or through a whole in someone
else's PHP script.
Filesystem access
Reading an unintended file may reveal
sensitive information. Even "harmless" information
such as program source may provide help to someone
trying to figure out how to crack the system.
Writing to or deleting an unintended file is obviously
worse.
-
Use care when constructing filenames. If your script
can be tricked into using a filename with
metacharacters like '.' and '/'
in it, then you may be accessing completely a file in
a completely different directory than you expect.
- Be careful about race conditions. If you are writing
into a directory where you don't have exclusive access
(and PHP scripts run unprivileged, so this is always
the case), then operations like checking to see if a
file exists before writing to it are never safe. Use
tmpfile()
if you need to create a temporary file. Use the x flag
to fopen()
when creating a non-temporary file.
Passwords
Hardcoding a database or other password into a script
should be avoided because web server misconfiguration
can easily allow people to see the text of the PHP
script rather than the script itself.
-
Passwords should be outside of the web server
space. Instead of specifying the password directly,
include a small php fragment outside of the
directories that the web server that sets the
password. However, see the note at the top of this
page: such files are still readable to
everybody with login access on the machine, and
could be revealed publically by someone else's buggy
PHP. (Ask the sysadmin team about creating a password
file readable only by apache if you run into this
situation. That at least helps the local user situation.)
-
Use readonly database accounts when possible. If
your script just needs to read a database, not write
it, then log into the database as a user that
doesn't have the ability to write to the database.
-
Don't check passwords into publically acessible CVS.
Obvious, but easily forgotten.
References
|