mirror of
https://github.com/sigmasternchen/php-doc-en
synced 2025-03-15 16:38:54 +00:00

git-svn-id: https://svn.php.net/repository/phpdoc/en/trunk@339619 c90b9560-bf6c-de11-be94-00142212c4b1
529 lines
21 KiB
XML
529 lines
21 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
<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, <acronym>PHP</acronym> 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 <acronym>PHP</acronym> 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>
|
|
|
|
<sect1 xml: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>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="security.database.connection">
|
|
<title>Connecting to Database</title>
|
|
<simpara>
|
|
You may want to establish 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
|
|
<acronym>PHP</acronym> and database via SSL.
|
|
</simpara-->
|
|
</sect1>
|
|
|
|
<sect1 xml: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 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), 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 <acronym>PHP</acronym> scripts. <acronym>PHP</acronym>
|
|
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>
|
|
|
|
<sect2 xml:id="security.database.storage.hashing">
|
|
<title>Hashing</title>
|
|
<simpara>
|
|
In the case of truly hidden data, if its raw representation is not needed
|
|
(i.e. will not be displayed), hashing should be taken into consideration.
|
|
The well-known example for hashing is storing the cryptographic hash of a
|
|
password in a database, instead of the password itself.
|
|
</simpara>
|
|
<simpara>
|
|
In PHP 5.5 or newer <link linkend="ref.password">password</link> functions
|
|
provide a convenient way to hash sensitive data and work with these hashes.
|
|
In PHP 5.3.7+ <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="&url.password.compat;">
|
|
password_compat</link> library can also be used.
|
|
</simpara>
|
|
<simpara>
|
|
<function>password_hash</function> is used to hash a given string using the
|
|
strongest algorithm currently available and <function>password_verify</function>
|
|
checks whether the given password matches the hash stored in database.
|
|
</simpara>
|
|
<example>
|
|
<title>Hashing password field</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
// storing password hash
|
|
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
|
|
pg_escape_string($username),
|
|
password_hash($password, PASSWORD_DEFAULT));
|
|
$result = pg_query($connection, $query);
|
|
|
|
// querying if user submitted the right password
|
|
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
|
|
pg_escape_string($username));
|
|
$row = pg_fetch_assoc(pg_query($connection, $query));
|
|
|
|
if ($row && password_verify($password, $row['pwd'])) {
|
|
echo 'Welcome, ' . htmlspecialchars($username) . '!';
|
|
} else {
|
|
echo 'Authentication failed for ' . htmlspecialchars($username) . '.';
|
|
}
|
|
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
<simpara>
|
|
In older versions of PHP this can be achieved using <function>crypt</function>
|
|
function.
|
|
</simpara>
|
|
<example>
|
|
<title>Hashing password using <function>crypt</function>s</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
// storing password hash
|
|
// $random_chars retrieved e.g. using /dev/random
|
|
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
|
|
pg_escape_string($username),
|
|
pg_escape_string(crypt($password, '$2a$07$' . $random_chars . '$')));
|
|
$result = pg_query($connection, $query);
|
|
|
|
// querying if user submitted the right password
|
|
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
|
|
pg_escape_string($username));
|
|
$row = pg_fetch_assoc(pg_query($connection, $query));
|
|
|
|
if ($row && crypt($password, $row['pwd']) == $row['pwd']) {
|
|
echo 'Welcome, ' . htmlspecialchars($username) . '!';
|
|
} else {
|
|
echo 'Authentication failed for ' . htmlspecialchars($username) . '.';
|
|
}
|
|
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 xml: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 an 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)
|
|
</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
$offset = $argv[0]; // beware, no input validation!
|
|
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
|
|
$result = pg_query($conn, $query);
|
|
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
Normal users click on the 'next', 'prev' links where the <varname>$offset</varname>
|
|
is encoded into the <acronym>URL</acronym>. 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 <acronym>URL</acronym>
|
|
<informalexample>
|
|
<programlisting role="sql">
|
|
<![CDATA[
|
|
0;
|
|
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
|
|
select 'crack', usesysid, 't','t','crack'
|
|
from pg_shadow where usename='postgres';
|
|
--
|
|
]]>
|
|
</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[
|
|
<?php
|
|
|
|
$query = "SELECT id, name, inserted, size FROM products
|
|
WHERE size = '$size'";
|
|
$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 role="sql">
|
|
<![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[
|
|
<?php
|
|
$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', trusted=100, admin='yes</literal> to gain more
|
|
privileges. Then, the query will be twisted:
|
|
<informalexample>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
// $uid: ' or uid like '%admin%
|
|
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
|
|
|
|
// $pwd: hehehe', trusted=100, admin='yes
|
|
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' 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[
|
|
<?php
|
|
|
|
$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[
|
|
<?php
|
|
|
|
$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>
|
|
<para>
|
|
<mediaobject>
|
|
<alt>A worked example of the issues regarding SQL Injection</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/security/figures/xkcd-bobby-tables.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
Image courtesy of <link xlink:href="&url.xkcd;327">xkcd</link>
|
|
</para>
|
|
|
|
<sect2 xml:id="security.database.avoiding">
|
|
<title>Avoidance Techniques</title>
|
|
<simpara>
|
|
While it remains obvious that an attacker must possess at least some
|
|
knowledge of the database architecture in order to conduct a successful
|
|
attack, obtaining this information is often very simple. For example,
|
|
if the database is part of an open source or other publicly-available
|
|
software package with a default installation, this information is
|
|
completely open and available. This information may also be divulged
|
|
by closed-source code - even if it's encoded, obfuscated, or compiled -
|
|
and even by your very own code through the display of error messages.
|
|
Other methods include the user of common table and column names. For
|
|
example, a login form that uses a 'users' table with column names
|
|
'id', 'username', and 'password'.
|
|
</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>
|
|
Use prepared statements with bound variables. They are provided
|
|
<link linkend="pdo.prepared-statements">by PDO</link>,
|
|
<link linkend="mysqli.quickstart.prepared-statements">by MySQLi</link>
|
|
and by other libraries.
|
|
</simpara>
|
|
</listitem>
|
|
<listitem>
|
|
<simpara>
|
|
Check if the given input has the expected data type. <acronym>PHP</acronym> has
|
|
a wide range of input validating functions, from the simplest ones
|
|
found in <link linkend="ref.var">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>ctype_digit</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[
|
|
<?php
|
|
|
|
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>
|
|
If the database layer doesn't support binding variables then
|
|
quote each non numeric user supplied value that is passed to the
|
|
database with the database-specific string escape function (e.g.
|
|
<function>mysql_real_escape_string</function>,
|
|
<function>sqlite_escape_string</function>, etc.).
|
|
Generic functions like <function>addslashes</function> are useful only
|
|
in a very specific environment (e.g. MySQL in a single-byte character
|
|
set with disabled <varname>NO_BACKSLASH_ESCAPES</varname>) so it is
|
|
better to avoid them.
|
|
</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>
|
|
</sect2>
|
|
</sect1>
|
|
</chapter>
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-omittag:t
|
|
sgml-shorttag:t
|
|
sgml-minimize-attributes:nil
|
|
sgml-always-quote-attributes:t
|
|
sgml-indent-step:1
|
|
sgml-indent-data:t
|
|
indent-tabs-mode:nil
|
|
sgml-parent-document:nil
|
|
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
|
sgml-exposed-tags:nil
|
|
sgml-local-catalogs:nil
|
|
sgml-local-ecat-files:nil
|
|
End:
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|