Wednesday, October 21, 2009

Hardening PHP: magic_quotes_gpc - False sense of security

Writing secure applications from the ground up requires a programmer to fully understand all the features he uses to protect his code from vulnerabilities. Today's languages provide many ways to ease coders with hardening their code. You can rely on built-in libraries to filter input variables, escape the output in HTML etc.

However, staying secure is not that simple - you need to know all the limits and caveats of your tools, because sometimes they promise you something but do not deliver - as it is in case of magic quotes functionality.

Magic quotes

PHP magic_quotes_gpc are sometimes recommended as a protection from SQL injection .When this ini setting is on, all GET/POST/COOKIE variables are automatically run through addslashes() function, basically escaping all quote characters with "\".

All is good

Let's look at an example, where that protection would work:

<?php
// some admin login functionality
$logged_in = mysql_query("SELECT 1 FROM users WHERE login='admin' AND password=sha1('" . $_POST['password'] . "')");
// process $logged_in

When magic_quotes_gpc are ON injecting the PASSWORD variable won't work. A simplest attack vector "' OR 1=1 -- " would transform to
SELECT 1 FROM users WHERE login='admin' AND password=sha1('\' OR 1=1 -- ')
That's a perfectly escaped SQL query, nothing can get injected. magic_quotes_gpc escape the dangerous single quote for us. So - goodbye SQL injection? WRONG!

Magic quotes only insert a backslash before a few characters, nothing more. That protects you from SQL injection only in some particular cases like above and only by coincidence. To prove that, let's analyze the following script.

So much for magic...

<?php
// display a single post based on id
$post_data = mysql_query("SELECT title, content FROM posts WHERE post_id={$_GET['id']}");
// display $post
The same "' OR 1=1 -- " transforms to invalid query:

SELECT title, content FROM posts WHERE post_id= \' OR 1 = 1 --

and if you have display_errors ON, you just made the attacker very happy with your database details outputted right before his eyes. Even if you don't, there still is a possiblity of blind sql injection.

But what if an attacker set id parameter to one of these:
  • 1 AND (SELECT COUNT(*) from another table_name) BETWEEN 0 AND 100
  • -10000 union select user_password from users where user_login=CHAR(97,100,109,105,110) (admin)
You won't get any protection from these vectors - they slip right under the eyes of  magic_quotes_gpc - they don't contain quotes.

In this case a single mysql query parameter that was left unprotected could lead to your whole database content dumped to the attacker (I'm not kidding - see sqlmap and try for yourself).

Know your context

The fundamental problem with magic_quotes_gpc is that they know nothing about the context. They don't know if you're using the data to insert it into MySQL, Oracle, or if you're writing to a file. Maybe you're sending it through SOAP or displaying it in HTML? Or maybe all of it. They just don't have enough information, only you know it. Escaping values depends on a context in which they are used. You should disable magic quotes now also for the following reasons:
  • they mangle your data before you get the chance to reach it and can lead to double escaping,
  • most frameworks expect them to be off, and if they're enabled, try to undo these changes at bootstrap,
  • they're deprecated in PHP 5.3 and will be removed in PHP 6.
But you need to escape your data nonetheless. Follow these simple rules:
  • to output to a HTML - use htmlspecialchars()
  • to escape a string in MySQL - use mysql_real_escape_string()
  • to escape a string in Postgresql - use pg_escape_string() 
  • When you are expecting an integer in SQL query - simply cast to int
  • ALWAYS use prepared statements when querying databases.
If you want to read more on bypassing magic quotes and SQL injection in general, I recommend:
Have any comments on the subject to share?  Please do so, this is my first security-related post and I'd like to hear your opinion.