revise database.security section

remove  bla-bla
note that attack examples are not database server specific


git-svn-id: https://svn.php.net/repository/phpdoc/en/trunk@68050 c90b9560-bf6c-de11-be94-00142212c4b1
This commit is contained in:
Gyozo Papp 2002-01-18 17:25:26 +00:00
parent 69d2467b6a
commit 56fb691746
2 changed files with 276 additions and 242 deletions

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- $Revision: 1.40 $ -->
<!-- $Revision: 1.41 $ -->
<chapter id="security">
<title>Security</title>
@ -117,7 +117,7 @@
</simpara>
<simpara>
When invoked as a CGI binary, PHP refuses to interpret the
command line arguments.
command line arguments.
</simpara>
</listitem>
<listitem>
@ -184,7 +184,7 @@
Action directives (see below).
</simpara>
</sect2>
<sect2 id="security.cgi-bin.force-redirect">
<title>Case 2: using --enable-force-cgi-redirect</title>
<simpara>
@ -214,7 +214,7 @@ AddHandler php-script .php
here.
</simpara>
</sect2>
<sect2 id="security.cgi-bin.doc-root">
<title>Case 3: setting doc_root or user_dir</title>
<simpara>
@ -256,7 +256,7 @@ AddHandler php-script .php
called <filename role="uri">~user/doc.php</filename> under
doc_root (yes, a directory name starting with a tilde
[<literal>~</literal>]).
</simpara>
</simpara>
<simpara>
If user_dir is set to for example <filename
role="dir">public_php</filename>, a request like <filename
@ -274,7 +274,7 @@ AddHandler php-script .php
separately.
</simpara>
</sect2>
<sect2 id="security.cgi-bin.shell">
<title>Case 4: PHP parser outside of web tree</title>
<para>
@ -305,7 +305,7 @@ AddHandler php-script .php
configure option.
</para>
</sect2>
</sect1>
<sect1 id="security.apache">
@ -344,7 +344,7 @@ AddHandler php-script .php
those who are not security professionals.
</simpara>
<simpara>
There are some simpler solutions. By using
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,
@ -413,7 +413,7 @@ echo "/home/../etc/passwd has been deleted!";
?>
]]>
</programlisting>
</example>
</example>
There are two important measures you should take to prevent these
issues.
<itemizedlist>
@ -435,7 +435,7 @@ echo "/home/../etc/passwd has been deleted!";
<![CDATA[
<?php
// removes a file from the hard drive that
// the PHP user has access to.
// the PHP user has access to.
$username = $HTTP_SERVER_VARS['REMOTE_USER']; // using an authentication mechanisim
$homedir = "/home/$username";
@ -467,14 +467,14 @@ $homedir = "/home/$username";
if (!ereg('^[^./][^/]*$', $userfile))
die('bad filename'); //die, do not process
if (!ereg('^[^./][^/]*$', $username))
die('bad username'); //die, do not process
die('bad username'); //die, do not process
//etc...
?>
]]>
</programlisting>
</example>
</example>
</para>
<para>
Depending on your operating system, there are a wide variety of files
@ -483,7 +483,7 @@ if (!ereg('^[^./][^/]*$', $username))
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>
</para>
</sect1>
<sect1 id="security.database">
@ -495,13 +495,6 @@ if (!ereg('^[^./][^/]*$', $username))
or secret informations can be stored in such database, you should strongly
consider to protect them somehow.
</simpara>
<simpara>
PHP can be treated as a bridge between the database and client. Your script
processes the client's request, and propagates it in such manner that the
database can provide the appropriate response. After that, the script
generates its output from the supplied data, probably based on customizeable
user preferences stored in database, too.
</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 connecion.
@ -583,10 +576,10 @@ if (!ereg('^[^./][^/]*$', $username))
</simpara>
<simpara>
Once an attacker gains access to your database directly (bypassing the
webserver), the sensitive data stored in it 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.
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
@ -594,15 +587,15 @@ if (!ereg('^[^./][^/]*$', $username))
can assist you in this case with its 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 be stored first, and decrypts
it when retrieving. See the references for further examples how
algorithms. The script encrypts the data be stored first, and decrypts
it when retrieving. See the references for further examples how
encryption works.
</simpara>
<simpara>
In case of truly hidden data, if its raw representation is not needed
In case of truly hidden data, if its raw representation is not needed
(i.e. not be displayed), hashing may be also 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
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>
@ -621,7 +614,7 @@ $result = pg_exec($connection, $query);
if (pg_numrows($result) > 0) {
echo "Wellcome, $username!";
}
}
else {
echo "Authentication failed for $username.";
}
@ -633,7 +626,7 @@ else {
<sect2 id="security.database.sql-injection">
<title>SQL Injection</title>
<simpara>
Many web applications are unaware of how SQL queries can be tampered with,
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
@ -655,7 +648,10 @@ else {
behalf of a superuser or the owner who can create users, the attacker
may create a superuser in your database.
<example>
<title>Splitting the result set into pages - and making superusers</title>
<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!
@ -664,11 +660,11 @@ $result = pg_exec($conn, $query);
]]>
</programlisting>
</example>
Normal users click on the 'next', 'prev' links where the offset is
appended to the URL. The script expects that the incoming
<varname>$offset</varname> is numeric. However, if someone tries to
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 decimal number. However, someone tries to
break in with appending <function>urlencode</function>'d form of the
following to the URL:
following to the URL (PostgreSQL):
<informalexample>
<programlisting>
<![CDATA[
@ -679,7 +675,7 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
]]>
</programlisting>
</informalexample>
or
or more precisely:
<informalexample>
<programlisting>
<![CDATA[
@ -691,9 +687,19 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
]]>
</programlisting>
</informalexample>
then the script will 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.
or in case of using MySQL:
<informalexample>
<programlisting>
<![CDATA[
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>
@ -705,7 +711,9 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
<para>
A feasible way to gain passwords:
<example>
<title>Listing out articles - and some passwords</title>
<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';";
@ -725,9 +733,11 @@ union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from use
<literal>'</literal>), the query beast awakened.
</para>
<para>
SQL updates are also subject to attacking your database.
SQL UPDATEs are also subject to attacking your database.
<example>
<title>From resetting a password to gaining more privileges</title>
<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';";
@ -760,7 +770,7 @@ $query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;
A frightening example how operating system level commands can be accessed
on some database hosts.
<example>
<title>Attacking the database host's operating system</title>
<title>Attacking the database host's operating system (MSSQL Server)</title>
<programlisting role="php">
<![CDATA[
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
@ -769,12 +779,12 @@ $result = mssql_query($query);
</programlisting>
</example>
If attacker submits the value
<literal>%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal>
<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 '%%' exec master..xp_cmdshell 'net user test testpass /ADD'--";
$query = "SELECT * FROM products WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
]]>
</programlisting>
@ -785,6 +795,13 @@ $result = mssql_query($query);
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 so vulnerable in other manner.
</para>
</note>
<sect3 id="security.database.avoiding">
<title>Avoiding techniques</title>
@ -806,26 +823,26 @@ $result = mssql_query($query);
</simpara>
<itemizedlist>
<listitem>
<simpara>
First, 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>
sections, (e.g. <function>is_numeric</function>,
<function>ctype_digit</function> respectively) onwards the
<link linkend="ref.pcre">Perl compatible Regular Expressions</link>
support.
</simpara>
</listitem>
<listitem>
<para>
If the application waits for numeric input, consider to verify data
with <function>is_numeric</function>, or silently change its type
using <function>settype</function> or <function>sprintf()</function>.
<example>
<title>A more secure way to compose a query for paging</title>
<programlisting role="php">
<listitem>
<simpara>
First, 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>
sections, (e.g. <function>is_numeric</function>,
<function>ctype_digit</function> respectively) onwards the
<link linkend="ref.pcre">Perl compatible Regular Expressions</link>
support.
</simpara>
</listitem>
<listitem>
<para>
If the application waits for numeric input, consider to verify data
with <function>is_numeric</function>, or silently change its type
using <function>settype</function> or <function>sprintf()</function>.
<example>
<title>A more secure way to compose a query for paging</title>
<programlisting role="php">
<![CDATA[
settype($order, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
@ -833,43 +850,43 @@ $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 user input which is passed to the database with
<function>addslashes</function> or <function>addcslashes</function>.
See <link linkend="security.database.storage">this example</link>.
As the examples shows, quotes burnt into the static part of the query
is not enough, and can be easily hacked.
</simpara>
</listitem>
<listitem>
<simpara>
Do not print out any database specific information, especially
about the schema, no matter what happens. 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>
</programlisting>
</example>
</para>
</listitem>
<listitem>
<simpara>
Quote user input which is passed to the database with
<function>addslashes</function> or <function>addcslashes</function>.
See <link linkend="security.database.storage">this example</link>.
As the examples shows, quotes burnt into the static part of the query
is not enough, and can be easily hacked.
</simpara>
</listitem>
<listitem>
<simpara>
Do not print out any database specific information, especially
about the schema, no matter what happens. 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. 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. The more detail is generally better.
</simpara>
<simpara>
Besides these, you benefit from logging queries either within your script
or by the database itself, if it supports. 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. The more detail is generally better.
</simpara>
</sect3>
</sect2>
</sect1>
@ -898,7 +915,7 @@ $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %
</form>
]]>
</programlisting>
</example>
</example>
</para>
<para>
The PHP errors which are normally returned can be quite helpful to a
@ -906,10 +923,10 @@ $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %
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,
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
@ -926,7 +943,7 @@ $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %
</form>
]]>
</programlisting>
</example>
</example>
</para>
<para>
Regardless of the method of error handling, the ability to probe a
@ -980,7 +997,7 @@ $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %
<![CDATA[
<?php
if ($username) { // Not initialized or checked before usage
$good_login = 1;
$good_login = 1;
}
if ($good_login == 1) { // If above test fails, not initialized or checked before usage
fpassthru ("/highly/sensitive/data/index.html");
@ -995,9 +1012,9 @@ if ($good_login == 1) { // If above test fails, not initialized or checked befor
<sect1 id="security.registerglobals">
<title>Using Register Globals</title>
<para>
One feature of PHP that can be used to enhance security is configuring PHP with
<link linkend="ini.register-globals">register_globals</link> = off.
By turning off the ability for any user-submitted variable to be injected
One feature of PHP that can be used to enhance security is configuring PHP with
<link linkend="ini.register-globals">register_globals</link> = off.
By turning off the ability for any user-submitted variable to be injected
into PHP code, you can reduce the amount of variable
poisoning a potential attacker may inflict. They will have
to take the additional time to forge submissions, and your
@ -1014,7 +1031,7 @@ if ($good_login == 1) { // If above test fails, not initialized or checked befor
<![CDATA[
<?php
if ($username) { // can be forged by a user in get/post/cookies
$good_login = 1;
$good_login = 1;
}
if ($good_login == 1) { // can be forged by a user in get/post/cookies,
@ -1029,7 +1046,7 @@ if ($good_login == 1) { // can be forged by a user in get/post/cookies,
<programlisting role="php">
<![CDATA[
<?php
if($HTTP_COOKIE_VARS['username']){
if($HTTP_COOKIE_VARS['username']){
// can only come from a cookie, forged or otherwise
$good_login = 1;
fpassthru ("/highly/sensitive/data/index.html");
@ -1052,7 +1069,7 @@ if($HTTP_COOKIE_VARS['username']){
<?php
if ($HTTP_COOKIE_VARS['username'] &&
!$HTTP_POST_VARS['username'] &&
!$HTTP_GET_VARS['username'] ) {
!$HTTP_GET_VARS['username'] ) {
// Perform other checks to validate the user name...
$good_login = 1;
fpassthru ("/highly/sensitive/data/index.html");
@ -1071,7 +1088,7 @@ if ($HTTP_COOKIE_VARS['username'] &&
</para>
</sect1>
<sect1 id="security.variables">
<title>User Submitted Data</title>
<para>
@ -1124,7 +1141,7 @@ exec ($evil_var);
Can this be used in conjunction with other scripts in a negative
manner?
</simpara>
</listitem>
</listitem>
<listitem>
<simpara>
Will any transactions be adequately logged?
@ -1147,7 +1164,7 @@ exec ($evil_var);
operated upon).
</para>
</sect1>
<sect1 id="security.hiding">
<title>Hiding PHP</title>
<para>
@ -1162,7 +1179,7 @@ exec ($evil_var);
</para>
<para>
Another tactic is to configure web servers such as apache to
parse different filetypes through PHP, either with an .htaccess
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>
@ -1200,7 +1217,7 @@ AddType application/x-httpd-php .htm .html
obscurity, it's a minor preventative measure with few drawbacks.
</para>
</sect1>
<sect1 id="security.current">
<title>Keeping Current</title>
<simpara>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- $Revision: 1.40 $ -->
<!-- $Revision: 1.41 $ -->
<chapter id="security">
<title>Security</title>
@ -117,7 +117,7 @@
</simpara>
<simpara>
When invoked as a CGI binary, PHP refuses to interpret the
command line arguments.
command line arguments.
</simpara>
</listitem>
<listitem>
@ -184,7 +184,7 @@
Action directives (see below).
</simpara>
</sect2>
<sect2 id="security.cgi-bin.force-redirect">
<title>Case 2: using --enable-force-cgi-redirect</title>
<simpara>
@ -214,7 +214,7 @@ AddHandler php-script .php
here.
</simpara>
</sect2>
<sect2 id="security.cgi-bin.doc-root">
<title>Case 3: setting doc_root or user_dir</title>
<simpara>
@ -256,7 +256,7 @@ AddHandler php-script .php
called <filename role="uri">~user/doc.php</filename> under
doc_root (yes, a directory name starting with a tilde
[<literal>~</literal>]).
</simpara>
</simpara>
<simpara>
If user_dir is set to for example <filename
role="dir">public_php</filename>, a request like <filename
@ -274,7 +274,7 @@ AddHandler php-script .php
separately.
</simpara>
</sect2>
<sect2 id="security.cgi-bin.shell">
<title>Case 4: PHP parser outside of web tree</title>
<para>
@ -305,7 +305,7 @@ AddHandler php-script .php
configure option.
</para>
</sect2>
</sect1>
<sect1 id="security.apache">
@ -344,7 +344,7 @@ AddHandler php-script .php
those who are not security professionals.
</simpara>
<simpara>
There are some simpler solutions. By using
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,
@ -413,7 +413,7 @@ echo "/home/../etc/passwd has been deleted!";
?>
]]>
</programlisting>
</example>
</example>
There are two important measures you should take to prevent these
issues.
<itemizedlist>
@ -435,7 +435,7 @@ echo "/home/../etc/passwd has been deleted!";
<![CDATA[
<?php
// removes a file from the hard drive that
// the PHP user has access to.
// the PHP user has access to.
$username = $HTTP_SERVER_VARS['REMOTE_USER']; // using an authentication mechanisim
$homedir = "/home/$username";
@ -467,14 +467,14 @@ $homedir = "/home/$username";
if (!ereg('^[^./][^/]*$', $userfile))
die('bad filename'); //die, do not process
if (!ereg('^[^./][^/]*$', $username))
die('bad username'); //die, do not process
die('bad username'); //die, do not process
//etc...
?>
]]>
</programlisting>
</example>
</example>
</para>
<para>
Depending on your operating system, there are a wide variety of files
@ -483,7 +483,7 @@ if (!ereg('^[^./][^/]*$', $username))
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>
</para>
</sect1>
<sect1 id="security.database">
@ -495,13 +495,6 @@ if (!ereg('^[^./][^/]*$', $username))
or secret informations can be stored in such database, you should strongly
consider to protect them somehow.
</simpara>
<simpara>
PHP can be treated as a bridge between the database and client. Your script
processes the client's request, and propagates it in such manner that the
database can provide the appropriate response. After that, the script
generates its output from the supplied data, probably based on customizeable
user preferences stored in database, too.
</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 connecion.
@ -583,10 +576,10 @@ if (!ereg('^[^./][^/]*$', $username))
</simpara>
<simpara>
Once an attacker gains access to your database directly (bypassing the
webserver), the sensitive data stored in it 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.
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
@ -594,15 +587,15 @@ if (!ereg('^[^./][^/]*$', $username))
can assist you in this case with its 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 be stored first, and decrypts
it when retrieving. See the references for further examples how
algorithms. The script encrypts the data be stored first, and decrypts
it when retrieving. See the references for further examples how
encryption works.
</simpara>
<simpara>
In case of truly hidden data, if its raw representation is not needed
In case of truly hidden data, if its raw representation is not needed
(i.e. not be displayed), hashing may be also 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
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>
@ -621,7 +614,7 @@ $result = pg_exec($connection, $query);
if (pg_numrows($result) > 0) {
echo "Wellcome, $username!";
}
}
else {
echo "Authentication failed for $username.";
}
@ -633,7 +626,7 @@ else {
<sect2 id="security.database.sql-injection">
<title>SQL Injection</title>
<simpara>
Many web applications are unaware of how SQL queries can be tampered with,
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
@ -655,7 +648,10 @@ else {
behalf of a superuser or the owner who can create users, the attacker
may create a superuser in your database.
<example>
<title>Splitting the result set into pages - and making superusers</title>
<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!
@ -664,11 +660,11 @@ $result = pg_exec($conn, $query);
]]>
</programlisting>
</example>
Normal users click on the 'next', 'prev' links where the offset is
appended to the URL. The script expects that the incoming
<varname>$offset</varname> is numeric. However, if someone tries to
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 decimal number. However, someone tries to
break in with appending <function>urlencode</function>'d form of the
following to the URL:
following to the URL (PostgreSQL):
<informalexample>
<programlisting>
<![CDATA[
@ -679,7 +675,7 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
]]>
</programlisting>
</informalexample>
or
or more precisely:
<informalexample>
<programlisting>
<![CDATA[
@ -691,9 +687,19 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
]]>
</programlisting>
</informalexample>
then the script will 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.
or in case of using MySQL:
<informalexample>
<programlisting>
<![CDATA[
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>
@ -705,7 +711,9 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
<para>
A feasible way to gain passwords:
<example>
<title>Listing out articles - and some passwords</title>
<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';";
@ -725,9 +733,11 @@ union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from use
<literal>'</literal>), the query beast awakened.
</para>
<para>
SQL updates are also subject to attacking your database.
SQL UPDATEs are also subject to attacking your database.
<example>
<title>From resetting a password to gaining more privileges</title>
<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';";
@ -760,7 +770,7 @@ $query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;
A frightening example how operating system level commands can be accessed
on some database hosts.
<example>
<title>Attacking the database host's operating system</title>
<title>Attacking the database host's operating system (MSSQL Server)</title>
<programlisting role="php">
<![CDATA[
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
@ -769,12 +779,12 @@ $result = mssql_query($query);
</programlisting>
</example>
If attacker submits the value
<literal>%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal>
<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 '%%' exec master..xp_cmdshell 'net user test testpass /ADD'--";
$query = "SELECT * FROM products WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
]]>
</programlisting>
@ -785,6 +795,13 @@ $result = mssql_query($query);
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 so vulnerable in other manner.
</para>
</note>
<sect3 id="security.database.avoiding">
<title>Avoiding techniques</title>
@ -806,26 +823,26 @@ $result = mssql_query($query);
</simpara>
<itemizedlist>
<listitem>
<simpara>
First, 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>
sections, (e.g. <function>is_numeric</function>,
<function>ctype_digit</function> respectively) onwards the
<link linkend="ref.pcre">Perl compatible Regular Expressions</link>
support.
</simpara>
</listitem>
<listitem>
<para>
If the application waits for numeric input, consider to verify data
with <function>is_numeric</function>, or silently change its type
using <function>settype</function> or <function>sprintf()</function>.
<example>
<title>A more secure way to compose a query for paging</title>
<programlisting role="php">
<listitem>
<simpara>
First, 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>
sections, (e.g. <function>is_numeric</function>,
<function>ctype_digit</function> respectively) onwards the
<link linkend="ref.pcre">Perl compatible Regular Expressions</link>
support.
</simpara>
</listitem>
<listitem>
<para>
If the application waits for numeric input, consider to verify data
with <function>is_numeric</function>, or silently change its type
using <function>settype</function> or <function>sprintf()</function>.
<example>
<title>A more secure way to compose a query for paging</title>
<programlisting role="php">
<![CDATA[
settype($order, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
@ -833,43 +850,43 @@ $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 user input which is passed to the database with
<function>addslashes</function> or <function>addcslashes</function>.
See <link linkend="security.database.storage">this example</link>.
As the examples shows, quotes burnt into the static part of the query
is not enough, and can be easily hacked.
</simpara>
</listitem>
<listitem>
<simpara>
Do not print out any database specific information, especially
about the schema, no matter what happens. 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>
</programlisting>
</example>
</para>
</listitem>
<listitem>
<simpara>
Quote user input which is passed to the database with
<function>addslashes</function> or <function>addcslashes</function>.
See <link linkend="security.database.storage">this example</link>.
As the examples shows, quotes burnt into the static part of the query
is not enough, and can be easily hacked.
</simpara>
</listitem>
<listitem>
<simpara>
Do not print out any database specific information, especially
about the schema, no matter what happens. 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. 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. The more detail is generally better.
</simpara>
<simpara>
Besides these, you benefit from logging queries either within your script
or by the database itself, if it supports. 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. The more detail is generally better.
</simpara>
</sect3>
</sect2>
</sect1>
@ -898,7 +915,7 @@ $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %
</form>
]]>
</programlisting>
</example>
</example>
</para>
<para>
The PHP errors which are normally returned can be quite helpful to a
@ -906,10 +923,10 @@ $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %
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,
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
@ -926,7 +943,7 @@ $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %
</form>
]]>
</programlisting>
</example>
</example>
</para>
<para>
Regardless of the method of error handling, the ability to probe a
@ -980,7 +997,7 @@ $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %
<![CDATA[
<?php
if ($username) { // Not initialized or checked before usage
$good_login = 1;
$good_login = 1;
}
if ($good_login == 1) { // If above test fails, not initialized or checked before usage
fpassthru ("/highly/sensitive/data/index.html");
@ -995,9 +1012,9 @@ if ($good_login == 1) { // If above test fails, not initialized or checked befor
<sect1 id="security.registerglobals">
<title>Using Register Globals</title>
<para>
One feature of PHP that can be used to enhance security is configuring PHP with
<link linkend="ini.register-globals">register_globals</link> = off.
By turning off the ability for any user-submitted variable to be injected
One feature of PHP that can be used to enhance security is configuring PHP with
<link linkend="ini.register-globals">register_globals</link> = off.
By turning off the ability for any user-submitted variable to be injected
into PHP code, you can reduce the amount of variable
poisoning a potential attacker may inflict. They will have
to take the additional time to forge submissions, and your
@ -1014,7 +1031,7 @@ if ($good_login == 1) { // If above test fails, not initialized or checked befor
<![CDATA[
<?php
if ($username) { // can be forged by a user in get/post/cookies
$good_login = 1;
$good_login = 1;
}
if ($good_login == 1) { // can be forged by a user in get/post/cookies,
@ -1029,7 +1046,7 @@ if ($good_login == 1) { // can be forged by a user in get/post/cookies,
<programlisting role="php">
<![CDATA[
<?php
if($HTTP_COOKIE_VARS['username']){
if($HTTP_COOKIE_VARS['username']){
// can only come from a cookie, forged or otherwise
$good_login = 1;
fpassthru ("/highly/sensitive/data/index.html");
@ -1052,7 +1069,7 @@ if($HTTP_COOKIE_VARS['username']){
<?php
if ($HTTP_COOKIE_VARS['username'] &&
!$HTTP_POST_VARS['username'] &&
!$HTTP_GET_VARS['username'] ) {
!$HTTP_GET_VARS['username'] ) {
// Perform other checks to validate the user name...
$good_login = 1;
fpassthru ("/highly/sensitive/data/index.html");
@ -1071,7 +1088,7 @@ if ($HTTP_COOKIE_VARS['username'] &&
</para>
</sect1>
<sect1 id="security.variables">
<title>User Submitted Data</title>
<para>
@ -1124,7 +1141,7 @@ exec ($evil_var);
Can this be used in conjunction with other scripts in a negative
manner?
</simpara>
</listitem>
</listitem>
<listitem>
<simpara>
Will any transactions be adequately logged?
@ -1147,7 +1164,7 @@ exec ($evil_var);
operated upon).
</para>
</sect1>
<sect1 id="security.hiding">
<title>Hiding PHP</title>
<para>
@ -1162,7 +1179,7 @@ exec ($evil_var);
</para>
<para>
Another tactic is to configure web servers such as apache to
parse different filetypes through PHP, either with an .htaccess
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>
@ -1200,7 +1217,7 @@ AddType application/x-httpd-php .htm .html
obscurity, it's a minor preventative measure with few drawbacks.
</para>
</sect1>
<sect1 id="security.current">
<title>Keeping Current</title>
<simpara>