gerzson Wed Jan 16 05:09:37 2002 EDT Modified files: /phpdoc/en/chapters security.xml Log: new section added about database security please someone run a 'make test'
Index: phpdoc/en/chapters/security.xml diff -u phpdoc/en/chapters/security.xml:1.39 phpdoc/en/chapters/security.xml:1.40 --- phpdoc/en/chapters/security.xml:1.39 Tue Dec 18 18:18:36 2001 +++ phpdoc/en/chapters/security.xml Wed Jan 16 05:09:37 2002 @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="iso-8859-1"?> -<!-- $Revision: 1.39 $ --> +<!-- $Revision: 1.40 $ --> <chapter id="security"> <title>Security</title> @@ -484,6 +484,394 @@ 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 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. + Nowadays, the commonly used interface in the interaction with databases 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 realize, 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 my mind this simple rule: defence in depth. In the more place you + take the more action to increase the protection of your database, the less + probability of that an attacker succeeds, and exposes or abuse any stored + secret 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 + an existing third party's one. 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 one of these 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 to 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 reimplement 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 them is done, then monitoring your traffic and gaining + informations in this way will be a hard work. + </simpara> + <!--simpara> + If your database server native SSL support, consider to use <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 traveling 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 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. + </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 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 + encryption works. + </simpara> + <simpara> + 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 + <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 "Wellcome, $username!"; +} +else { + echo "Authentication failed for $username."; +} +]]> + </programlisting> + </example> + </sect2> + + <sect2 id="security.database.sql-injection"> + <title>SQL Injection</title> + <simpara> + Many web applications 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. + </simpara> + <simpara> + 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 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> + <programlisting role="php"> +<![CDATA[ +$offset = argv[0]; // beware, no input validation! +$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; +$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 + break in with appending <function>urlencode</function>'d form of the + following to the URL: + <informalexample> + <programlisting> +<![CDATA[ +0; +insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) + values ('crack', 31, 't','t','crack'); +-- +]]> + </programlisting> + </informalexample> + or + <informalexample> + <programlisting> +<![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> + 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. + </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: + <example> + <title>Listing out articles - and some passwords</title> + <programlisting role="php"> +<![CDATA[ +$query = "SELECT id, name, inserted, size FROM products WHERE size = '$size';"; +]]> + </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 were assigned to <varname>$size</varname> (prepended with + <literal>'</literal>), the query beast awakened. + </para> + <para> + SQL updates are also subject to attacking your database. + <example> + <title>From resetting a password to gaining more privileges</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>, + and the query will be twisted: + <informalexample> + <programlisting role="php"> +<![CDATA[ +$query = "UPDATE usertable SET pwd='...' WHERE uid='' or like '%admin%'; --"; +]]> + </programlisting> + </informalexample> + Now, the admin user password has been changed. Alternatively, the attacker + simply sets <varname>$pwd</varname> to + <literal>"hehehe', admin='yes', trusted=100 </literal> (with a trailing + space) to gain more privileges: + <informalexample> + <programlisting role="php"> +<![CDATA[ +$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 host's operating system</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>%' 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'--"; +$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> + + <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 on any kind of input, especially + which comes from the client side. The first example shows that such a + blameless query can cause disasters. + </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"> +<![CDATA[ +settype($order, '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 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> + </sect3> + </sect2> </sect1> <sect1 id="security.errors">