Trouble Shooting Wiki

Handling Joomla! Errors

From TroubleshootingWiki

Jump to: navigation, search
Joomla!
Official Page
Project Documentation
Download
Source Book
Mastering Joomla! 1.5 Extension and Framework Development
Mastering Joomla! 1.5 Extension and Framework Development
ISBN 978-1-847192-82-0
Publisher Packt Publishing
Author(s) James Kennard

Contents

[edit] Error Handling and Security

Security and graceful error handling is imperative to any good computer system. For systems like Joomla!, which are often available on the World Wide Web, poor security or incorrect error handling carries a high risk factor, and that risk is often higher when using third-party extensions.

This tutorial focuses on four main subjects:

  • Errors, Warnings, and Notices
  • Dealing with CGI Request Data
  • Access Control
  • Attacks

Handling errors is a common task; we will explore the different error levels, which we use to classify our errors, and ways in which we can modify the error levels and how they are handled.

Many security flaws in Joomla! extensions originate from inadequate processing of input data. We will explore how we should access CGI request data and how we can process that data to ensure that it does not pose a security risk.

We use access control to restrict, and allow, the tasks that users can perform. We will investigate the Joomla! access control mechanisms and how we can implement them in our extensions.

The final subject that we will look at is attacks. Attacks are malicious attempts to break a system. There are many ways in which an attacker can go about this; we will stick to the most common methods.

[edit] Errors, Warnings, and Notices

When we encounter errors it is important that we take some counter action. Joomla! provides a common error handling mechanism, which we access using the static JError class. JError takes advantage of the phpTemplate library, in particular the patError and patErrorManager classes. A complete description of the JError class and all of its methods is available in the Appendix.

Error Level Error Type Class Method
1 (E_ERROR) Error JError::raiseError()
2 (E_WARNING) Warning JError::raiseWarning()
8 (E_NOTICE) Notice JError::raiseNotice()

Level E_ERROR errors get an error document (JDocumentError), set the error, and render the document, sending the response and terminating the application. When we invoke any of the raise methods we pass two parameters, an error code and an error message.

The error code is a string that is used to identify the error. Error codes are rendered using one of three templates 403.php, 404.php, and 500.php. If the error code is 403 (Access Denied) or 404 (Page could not be found), we use the 403.php and 404.php templates respectively. These templates include some additional standard text that describes the normal reasons for receiving a 403 or a 404 error. All other error codes use the 500.php (Internal Server Error) template:

 JError::raiseError('403', JText::_('Access Forbidden'));
 JError::raiseError('500', JText::_('An error has occurred.'));

Level E_ERROR errors (JError::raiseError()), are for fatal errors. When a non fatal error occurs we can use the weaker, warning and notice levels. These two levels are handled in the same way, but it is still useful to make the distinction between the two; it helps aid classification of errors and the process of debugging:

JError::raiseWarning('ERROR_CODE', JText::_('Look out! There is a giant boxing kangaroo behind you!'));

This is perhaps not the most useful of messages and perhaps a little unlikely, but you get the idea. Exactly how you choose to classify your errors is up to you. Classification of errors tends to be relatively intuitive. An error that is not fatal, but should not have occurred, is a warning. An error that is not fatal, and is more or less expected to occur at some point is a notice.

The error code we used in the last example, ERROR_CODE, may seem a little odd. We can specify any error code we want; the exact intricacies of how Joomla! core error codes are going to be classified has not been fully decided. In the short-term Joomla! core errors are using scheme error codes and SOME_ERROR_CODE.

[edit] Return Values

Using the three methods we also get a return value, a JException object. The JException class contains all sorts of useful information about an error; including the error level, error code, and error message. When we raise an E_ERROR level error the object will also contain back-trace information, such as the file and line the error occurred on.

There are many methods in other classes that, if an error occurs, will return the result. We can test the return value of a method to see if it is an error using the JError::isError() method. As an example, the JController execute() method returns an error if no method is mapped to the task we try to execute:

 $result = $SomeCOntroller->execute('someTask');
 if(JError::isError($result))
 {
  // handle invalid task
 }

[edit] Customizing Error Handling

The handling of errors is not set in stone. We can modify the way each of the levels is handled and we can add new levels. We can choose any of the following modes (maximum of one mode per error level):

Mode Description
Ignore Error is ignored
Echo Prints the JException message to screen
Verbose Prints the JException message and back-trace information to screen
Die Terminates the application and prints the JException message to screen
Message Adds a message to the application queue
Log Adds a log entry to the application error log
Trigger Triggers a PHP error
Callback Calls a static method in another class

To modify the error handling of an existing error level we can use the JError::setErrorHandling() method. This example redefines the notice error to use the Ignore mode. Some modes require a third parameter, an array of options specific to the mode:

JError::setErrorHandling(E_NOTICE, 'Ignore');

To define a new error level we can use the JError::registerErrorLevel() method. If the error level is already defined the method will return false:

 define('MY_ERROR', 666);
 if( !JError::registerErrorLevel(MY_ERROR, 'My Extension Error', 'Message') )
 {
  JError::raiseError('SOME_ERROR', JText::_('Error level already defined').' ['.MY_ERROR.']');
 }

Once we have defined a new error level, to raise an error of that level we can use the JError::raise() method. The raise() method can be used with any of the defined error levels, including E_ERROR, E_WARNING, and E_NOTICE:

JError::raise(MYEXT_ERROR, 'SOME_ERROR', JText::_('Look out! It\'s those boxing kangaroos again!'));

[edit] Dealing with CGI Request Data

It is essential that we sanitize incoming data (i.e. remove any unexpected data and ensure the data is of an expected type). Joomla! provides us with the static class JRequest, which eliminates the need to directly access the request hashes $_GET, $_POST, $_FILES, $_COOKIE, and $_REQUEST. Using JRequest to its full potential we can perform useful data preprocessing.

[edit] Preprocessing CGI Data

To access a request value we must use the static JRequest::getVar() method. In this example we get the value of the input id:

$id = JRequest::getVar('id');

If we want to, we can define a default value; this is the value that will be returned if the request value is not defined. In this example we use the value 0 if the request id is not set:

$id = JRequest::getVar('id', 0);

By default JRequest::getVar() obtains data from the $_REQUEST hash. We can specify the source hash of the data as any one of the following: GET, POST, FILES, COOKIE, and DEFAULT. If we specify DEFAULT or an unknown source hash, the data will be retrieved from the $_REQUEST hash. In this example we get the data from the $_POST hash:

$id = JRequest::getVar('id', 0, 'POST');

Casting is a mechanism we can use to guarantee that a variable is of a specific type. We have a choice of the following types:

Cast Type Description Alias Method
ALNUM Alphanumeric string; can include A-Z, a-z, and 0-9. --
ARRAY Array. --
BASE64 Base64 string; can include A-Z, a-z, 0-9, forward slashes, plus signs, and equal signs. --
BOOL / BOOLEAN Boolean value. getBool()
CMD String syuitable for use as a command; can include A-Z, a-z, 0-9, underscores, fullstops, and dashes. getCmd()
FLOAT / DOUBLE Floating-point number. getFloat()
INT / INTEGER Whole number. getInt()
PATH File system path. --
STRING String; this will attempt to decode any special characters. getString()
WORD String with no spaces; can include A-Z, a-z, and underscores. getWord()

In this example we cast the value to an integer:

$id = JRequest::getVar('id', 0, 'POST', 'INT');

The trouble with the cast type parameter is that we must specify a default value and the hash before we can specify the type. To overcome this we can use the alias methods described in the table. This example retrieves someValue as a floating-point number:

$value = JRequest::getFloat('someValue');

We can use the default value and source hash parameters with the alias methods in the same way as we do with the getVar() method.

We can apply different masks to reduce the data preprocessing. There are three masks: JREQUEST_NOTRIM, JREQUEST_ALLOWHTML, and JREQUEST_ALLOWRAW. By default no mask is applied. In this example we get name from the $_POST hash and apply the JREQUEST_NOTRIM mask:

$name = JRequest::getVar('name', null, 'POST', 'STRING', JREQUEST_NOTRIM);

We can also use the mask when using the getString() alias method:

$name = JRequest::getString('name', null, 'POST', JREQUEST_NOTRIM);

To demonstrate the effects of the different masks, here is how four different inputs will be parsed:

# Input Value
1 <p>Paragraph</code> <code><a</code> <code>onClick="alert('foobar');">link</a></p>
2 CSS <link type="text/css", href="http://somewhere/nasty.css" />
3 space at front of input
4 &ltp&gtPara&lt/p&gt


# Output value (No mask)
1 Paragraph link
2 CSS
3 space at front of input
4 &ltp&gtPara&lt/p&gt


# Output value (mask JREQUEST_NOTRIM)
1 Paragraph link
2 CSS
3 space at front of input
4 &ltp&gtPara&lt/p&gt


# Output value (mask JREQUEST_ALLOWHTML)
1

Paragraph</code> <code><a>link</a>

2 CSS
3 space at front of input
4 &ltp&gtPara&lt/p&gt


# Output value (mask JREQUEST_ALLOWRAW)
1 <p>Paragraph <a onClick="alert('foobar');">link</a></p>
2 CSS <link type="text/css", href="http://somewhere/nasty.css" />
3 space at front of input
4 &ltp&gtPara&lt/p&gt

You may have noticed that using the mask JREQUEST_ALLOWHTML, the JavaScript and CSS is stripped from the data. JavaScript and CSS are removed from the data because they present a security risk. Attacks that exploit this type of security flaw are known as XSS (Cross Site Scripting) attacks; this is discussed in more detail later in the tutorial. If we want to retrieve the data in its original form, we must use the JREQUEST_ALLOWRAW mask.

[edit] Escaping and Encoding Data

Escaping is the act of prefixing special characters with an escape character. In PHP there are two configuration settings, magic_quotes_gpc and magic_quotes_runtime that, if enabled, will automatically escape data. Joomla! always disables these.

Data that we retrieve is never automatically escaped; it is the responsibility of our extensions to escape data as necessary. Joomla! provides us with some useful ways of escaping data, namely the JDatabase getEscaped() and Quote() methods and the static JOutputFilter class.

Note - Common escape syntax includes prefixing a backslash to special characters and duplicating special characters. Ensure that you use the correct escape syntax for the system with which your data interacts.

Encoding data is the act of changing data from one format to another; this is always a lossless transition. The encoding that we examine is the encoding of special XHTML characters. This is of particular use when dealing with data that we want to display in a RAW state in an XHTML page and when storing data in XML.

[edit] Escaping and Quoting Database Data

If we use un-escaped data when interacting with a database, we can inadvertently alter the meaning of a query. Imagine we have a database table #__test containing two fields, id, a numeric ID field, and content, a text field. This is how we might choose to build our update query.

 $db =& JFactory::getDBO();
 $query = false;
 if( $id = JRequest::getVar('id', 0, 'GET', 'INT') )
 {
  $data = JRequest::getVar('content', 0, 'GET', 'STRING', JREQUEST_ALLOWRAW);
  $query = " UPDATE ".$db->nameQuote('#__test'). " SET ".$db->nameQuote('content')."=". $db->Quote($data). " WHERE ". $db->nameQuote('id')."=".$id;
 }

Assuming $id=123 and $data="Foo's bar", the value of $query will be:

UPDATE `#__test` SET `content`='Foo\'s bar' WHERE `id`=123

We use nameQuote() to encapsulate a named query element, for example a field, in quotes. MySQL does not require quotes around named query elements, but it is good practice to add them because other database servers may require them.

We use Quote() to encapsulate query string values in quotes. Quote() also performs the getEscaped() method on the data, before encapsulating it; this escapes the data.

In our example we didn't bother to escape data in $id; there are three reasons why we didn't need to do this. We cast the value of $id to an integer when we retrieved it from the $_GET hash. We set the default value to 0. We checked it was a positive value.

[edit] Encode XHTML Data

When we want data to appear exactly as it was entered in an XHTML page we need to encode the data. We do this using the PHP function htmlspecialchars(), which encodes HTML special characters into HTML entities. In Joomla! when we use htmlspecialchars(), we are encouraged to specify the quote style ENT_QUOTES. This ensures that we also encode single quote characters as the HTML entity &#039:

 $value = "Foo's value is > Bar's value";
 echo htmlspecialchars($value, ENT_QUOTES);

This will produce the following:

Foo's value is > Bar's value

When we are outputting data like this, if the data is coming from an object, we can use the JOutputFilter::objectHTMLSafe() method. This method executes the htmlspecialchars() function on all of the public properties of the object:

 $o = new JObject();
 $o->set("name", "Foo's name");
 $o->set("content", "Foo is > Bar");
 JOutputFilter::objectHTMLSafe($o, ENT_QUOTES, 'content');
 print_r($o);
 JObject Object
 (
  [name] => Foo's name
  [content] => Foo is > Bar
 )

The last two parameters are optional. By default the second parameter, quote type, is ENT_QUOTES. The third parameter can be a string or an array of strings that identify properties within the object we don't want to encode.

There are other methods within JOutputFilter that we can use to encode data, including making URIs XHTML standards compliant and replacing ampersands with the HTML entity &amp.

[edit] Regular Expressions

REs (Regular Expressions) are revered by those who know how to use them, and considered a black art to those who don't. We can use Regular Expressions to sanitize data, to check the format of data, and to modify data. At the heart of REs are patterns; RE patterns are used to identify character patterns in data.

[edit] Patterns

Patterns are encapsulated with two identical characters, the pattern delimiters. Common pattern delimiters are the forward slash /, the hash #, and the tilde ~. You don't have to use the common pattern delimiters, but using them can make your code more readable for other developers.

Between the pattern delimiters is where we define what it is we are looking for. If we wanted to search for the occurrence of the term 'monkey' our pattern would look like this: /monkey/. This example will search for 'monkey' anywhere in our data; we can restrict this pattern further using the caret ^ and dollar $ characters. If we place the caret ^ character at the start of the pattern, it means that the 'data must start with' /^monkey/ (includes start of line and start of string). If we put a dollar sign at the end of the pattern it means that the 'data must end at' /monkey$/ (includes end of line and end of value).

We can, if we choose to, combine the caret character and the dollar character /^monkey$/; this is the same as asking, is the data equivalent to the string 'monkey'? In this context it is relatively useless, because we would use $data == 'monkey'.

A character class is a way of defining multiple characters that can be matched to just one actual character. If we wanted to search for 'monkey' or 'fonkey' we can define a character class that consists of the characters 'm' and 'f'. To do this we encapsulate the characters in square braces /[mf]onkey/.

There are a number of shortcuts we can use to make building character classes easier. The dash character can be used to specify a range from character to character. This example matches 'aonkey' through 'zonkey': /[a-z]onkey/.

So far we have dealt with simple consecutively matched items, but we can use quantifiers to duplicate a pattern. Quantifiers attach themselves to the pattern element directly to the left. If we wanted to match monkey, but with as many 'o's as we want we can do this: /mo+nkey/. The plus character '+' means we must have one to many 'o's.

Quantifier Description Example
+ One to many.

Matches monkey through mo...onkey.

/mo+nkey/
* Zero to many.

Matches mnkey through mo...onkey.

/mo*nkey/
 ? Optional.

Matches mnkey and monkey.

/mo?nkey/
{x} or {x,} x number.

Matches mooonkey.

/mo{3}nkey/
{x,y} x number to y number.

Matches monkey through mooonkey.

/mo{1,3}nkey/

We can add to the usefulness of quantifiers by surrounding a block in a pattern with parentheses. This way we can quantify the number of times a block occurs; this example matches 'monkeymonkeymonkey': /(monkey){3}/.

Continuing the shortcuts theme, there are certain characters that, if escaped, take on a whole new role. If we want to search for a whole word, we can use \w+. By itself \w is a character class that will match any word character. Word characters are letters, digits and underscores; sometimes locale may make a difference to what constitutes a word, for example accented characters may or may not be included.

Shortcut Description Character Class
\w Word characters Letters, digits, and underscores
\W Opposite of \w --------
\d Numbers Digits 0-9
\D Opposite of \d --------
\s Spaces Whitespace (not including new line characters)
\S Opposite of \s --------

Our pattern is case sensitive, so to allow any case we could do this /[a-zA-Z][oO][nN][kK][eE][yY]/. That's rather messy; instead we can use pattern modifiers, which are characters that can be placed after the pattern delimiters: /[a-z]onkey/i. The i modifier makes the pattern case insensitive.

Modifier Effect
i Ignore case.
s


By default the period character, '.', matches anything except newline characters.
This modifier makes the period character match newline characters as well.
m Makes the caret ^ and dollar characters match the start and end of line characters as well as string start and

end.

x Whitespace is ignored, unless it is in a character class. Allows comments in the pattern; comments are signified
by the hash character #. Do not use the pattern delimiters within comments.
u This modifier makes the pattern UTF-8 aware; this is only available with PHP 4.1.0 and above.

[edit] Matching

It's all very well knowing how to write RE patterns, but how do we use them? PHP provides us with a selection of different functions that use REs. We'll begin by looking at preg_match(); this function searches for matches in the subject and returns an the number of times the pattern was matched.

echo preg_match('/\d/','h0w many d1g1t5 ar3 th3r3');

This example will output 7. Nice and simple really; if there had been no numbers in the subject then it would have outputted 0.

Let's take another approach to preg_match(); we can return occurrences of blocks from a pattern. We define blocks by encapsulating them in parentheses. A good example of this is parsing a date.

 $matches = array();
 $pattern = '/^(\d{4})\D(\d{1,2})\D(\d{1,2})$/';
 $value = '1791-12-26';
 preg_match($pattern, $value, $matches);
 print_r($matches);

Before you run away screaming, let's break this down into its component parts. The pattern says: start of string, 4 digits, 1 non-digit, 1 or 2 digits, 1 non-digit, 1 or 2 digits, end of string. It's not all that complex, it just looks it. This will output:

 Array
 (
  [0] => 1791-12-26
  [1] => 1791
  [2] => 12
  [3] => 26
 )

The first element of the array is the text that matched the full pattern. The rest of the elements are the matching blocks.

[edit] Replacing

We can use preg_replace() to replace patterns with alternative text. This is often used for stripping out unwanted data. In this example we remove all digits.

$value = preg_replace('/\d/', , $value);

The first parameter is the pattern, in this instance, digits. The second parameter is the replacement string, in this instance, a null string. The final parameter is the subject.

We can take advantage of blocks in the same way as we did with preg_match(). Each matched block encapsulated in parentheses is assigned to a variable $1 through $n. These variables are only accessible in the replacement parameter.

 $pattern = '/^(\d{4})\D(\d{1,2})\D(\d{1,2})$/';
 $replacement = '$1/$2/$3';
 $value = '1791-12-26';
 echo preg_replace($pattern,$replacement,$value);

This example will output:

1791/12/26

[edit] Access Control

Joomla!'s access control mechanisms are not as clear cut as they could be; this is due to an ongoing development cycle that is moving away from a legacy access control system. In the future, Joomla! will use a complete GACL (Group Access Control Lists) access control mechanism.

The current access control mechanism uses an incomplete, abstracted implementation of phpGACL. There are eleven user groups; these groups are sometimes referred to as usertypes. Joomla! also maintains a set of three legacy access groups, Public, Registered, and Special.

The legacy groups are stored in the #__groups table; theoretically this makes the legacy access groups dynamic. There is no mechanism for administrators to amend the legacy access groups and even if we manually add a new legacy access group to the #__groups table, the effects are not globally reflected; we should regard the legacy access groups as static. It is advisable not to make extensions dependent on the legacy access groups because they will probably be removed from Joomla! at a later date.

We should be most interested in the phpGACL groups (simply called groups or user groups). Currently no mechanism is provided for administrators to amend these groups, we can, however, take advantage of the powerful JAuthorization

class that extends the gacl_api class. If we are careful we can add groups to Joomla! without impacting the Joomla! core. In the GACL implementation we commonly use four terms:

Name Description
ACL Access Control List Permissions list for an object
ACO Access Control Object Object to deny or allow access to
AXO Access eXtension Object Extended object to deny or allow access to
ARO Access Request Object Object requesting access

For a more complete description of GACL refer to the official phpGACL documentation phpgacl.sourceforge.net.

To demonstrate how the user groups are initially defined, this screenshot depicts the phpGACL administration interface with the Joomla! user groups defined:

Note that Joomla! does not include the phpGACL administration interface and that this screenshot is intended for demonstration purposes only.

In phpGACL, permissions are given to ARO groups and AROs, to access ACOs and AXOs. In Joomla! we only give permissions to ARO groups, and Joomla! users can only be a member of one group, whereas in phpGACL AROs can be members of multiple groups

These differences between Joomla! and phpGACL are due to one major factor. In phpGACL when we check permissions, we ask the question 'does ARO X have access to ACO Y?' In Joomla! we ask the question, 'Does ARO group X have access to ACO Y?'. The way in which we assign permissions in Joomla! will be altered in the future to use the same principals as phpGACL.

The three Access Object types, ACO, AXO, and ARO are all identified using two values, section and section value. To put this into context, the user group (ARO group) Super Administrator is identified as users > super administrator. The section name is users, and the section value is super administrator. A permission to manage contacts in the core contact component (ACO) is expressed as com_contact > manage. The section name is com_contact, and the section value is manage.

[edit] Menu Item Access Control

A misconception among some Joomla! administrators is that menu access (which uses the legacy access groups) constitutes security. Menu access is intended to define whether or not a specific menu item should be made visible to the current user.

Joomla! always attempts to transfer menu item permissions to the related menu item content; however, the solution is not infallible and must not be relied upon. The best way to deal with this is to add support for permissions in our extensions. The next section describes how to do this. We should also try to make administrators aware of the true meaning of the menu item access level.

In cases where Joomla! determines that something should not be accessible to a user, because of menu item access, Joomla! will return a 403 (Access Denied) error code.

[edit] Extension Access Control

Imagine we have a component called myExtension and we want to grant super administrator's access to 'manage'. This example gives permission to ARO group users > super administrator to ACO com_myExtension > manage.

$acl =& JFactory::getACL();
$acl->_mos_add_acl('com_myExtension', 'manage', 'users', 'super administrator');

Whenever we want to add permissions we have to use the above mechanism because currently only these ARO tables are implemented in Joomla!. The absent ARO tables are scheduled to be implemented in a later version of Joomla!.

In the short-term, when we create extensions that use Joomla!'s implementation of permissions, we should create a separate file with all the necessary calls to the ACL _mos_add_acl() method (as demonstrated in the preceding example). This way when Joomla! ultimately supports the ARO tables, we will be able to easily refactor our code to incorporate the new implementation.

Note - Calls to the _mos_add_acl() method must always be made prior to any permission checks. If they are not, the extra permissions will not have been applied in time. The best place to add the permissions is in the root extension file (this will depend upon the extension type).

Once we have added all of our permissions we will probably want to check if the current user has permissions. There are various ways of achieving this; we are encouraged to use the authorize() method in the JUser class:

 $user =& JFactory->getUser();
 if( ! $user-> authorize('com_myExtension', 'manage') )
 {
  JError::raiseError(403, JText::_('Access Forbidden'));
 }

If we are developing a component using the MVC architecture we use the JController object to automatically check permissions. The example below creates the component controller, sets the controller's ACO section, and executes the task:

 $task = JRequest->getVar('task', 'view', 'GET', 'WORD');
 $controller = new myExtensionController();
 $controller->setAccessControl('com_myExtension');
 $controller->execute($task);

When we run execute(), if the controller knows which ACO section to look at, it will check the permissions of the current user's group. The example above checks for permissions to ACO com_myExtension > $task.

We don't have to use the task as the section value; instead we can use the optional second parameter in the setAccessControl() method. This example checks for permissions to the ACO com_myExtension > manage irrespective of the task:

 $task = JRequest->getVar('task', 'view', 'GET', 'WORD');
 $controller = new myExtensionController();
 $controller->setAccessControl('com_myExtension', 'manage');
 $controller->execute($task);
 

When dealing with more complex permissions we can use AXOs to extend ACOs. Let's imagine we have a number of categories in our extension and we want to set manage permissions on each category. This example grants permissions to ACO group users > super administrator to ACO com_myExtension > manage AXO category > some category:

 $acl =& JFactory::getACL();
 $acl->_mos_add_acl('com_myExtension', 'manage', 'users', 'super administrator', 'category', 'some category');

Unlike when we were dealing with just an ACO and ARO, we cannot use this in conjunction with a JController subclass. This is because the JController class is unable to deal with AXOs. Instead we should use the JUser object to check permissions:

 $user =& JFactory->getUser();
 if( ! $user-> authorize('com_myExtension', 'manage', 'category', 'some category') )
 {
  JError::raiseError('403', JText::_('Access Forbidden'));
 }

When you define your ACOs you should always use the name of your extension as the ACO section. How you choose to define your ACO section value and your AXOs is entirely up to you. There is a great deal of emphasis put on the flexibility of Joomla!. As a third-party developer, you do not have to use the normal Joomla! access control. If you choose to use a custom access control system and the Joomla! MVC, you may want to consider overriding the authorize() method in your JController subclasses.

[edit] Attacks

Whether or not we like to think about it, there is always the potential threat of an attacker gaining access to our Joomla! websites. The most common way in which security is breached in Joomla! is through third-party extension security flaws.

Due to the number of extensions that have security defects, there is an official list of extensions that are considered insecure, available in the FAQ sections at http://help.joomla.org.

It is very important that, as third-party extension developers, we take great care in making our extensions as secure as we can. In this section we will investigate some of the more common forms of attack and how we can prevent them from affecting our extensions and we will take a look at how we can deal with users whom we believe to be attackers.

[edit] How to Avoid Common Attacks

The security flaws that we will investigate are some of the most likely to be exploited because they tend to be the easiest to initiate and there is plenty of literature explaining how to initiate them.

The attack types described here should not be considered a complete list. There are many ways in which an attacker can attempt to exploit a system. If you are concerned about attacks, you should consider hiring a security professional to help evaluate security vulnerabilities in your extensions.

[edit] Using the Session Token

A session is created for every client that makes a request. Joomla! uses its own implementation of sessions; integral to this is the JSession class. The session token, also refered to as the 'token', is a random alphanumeric string that we can use to validate requests made by a client. The token can change during a session.

Imagine that an attacker uses a utility to bombard a site with data; the data itself may not be suspicious. The attacker may just be attempting to fill your database with worthless information. If we include a hidden field in our forms with the name of the token, we can check if the user is submitting data via a form with a valid session.

We can get the token using JUtility::getToken(). In our template, where we render the form we want to secure, we add this:

<input type="hidden" name="<?php echo JUtility::getToken(); ?>" value="1" />

When we call JUtility::getToken() we can optionally provide the Boolean forceNew parameter. This will force the generation of a new token. Before doing this we must consider the context in which we are calling the method. If there are any other forms present on the page that also use the token we may inadvertently prevent these from working. Components are always rendered first so are generally safer when forcing a new token.

Now all we need to do is verify the token when we receive a request from the form that we are trying to secure. In this example we specifically get the token from the $_POST hash, guaranteeing that the token came via the correct method. The error message is not very intuitive; this is purposeful, because it makes it harder for an attacker to determine the reason why they are receiving the error.

 if(!JRequest::getVar(JUtility::getToken(), false, 'POST'))
 {
  JError::raiseError('403', JText::_('Request Forbidden'));
 }

[edit] Code Injection

Code injection occurs when code is included in input. The injected code, if not properly sanitized, may end up being executed on a server or on a client. There are a number of different ways in which injected code can compromise a Joomla! installation or a system with which we are interacting.

We will take a look at the two most common forms of code injection used to attack Joomla!: PHP and SQL code injection.

[edit] PHP Code Injection

We should use JRequest and, in some cases, REs to ensure that the input data that we are handling is valid. Most data validation is very simple and doesn't require much effort.

Even when data comes from an XHTML form control that is restricted to specific values, we must still validate the data.

There is one form of PHP code injection that we don't need to worry about. By default Joomla! always disables 'register globals'. In scripts where 'register globals' is enabled, all URI query values are automatically converted into variables, literally injecting variables into a script.

Imagine we are using an input value to determine which class to instantiate. If we do not sanitize the incoming data, we run the risk of instantiating a class that could be used to malicious effect. To overcome this we could use a predefined list of class names to ensure the data is valid:

 // define allowed classes
 $allow = array('Monkey', 'Elephant', 'Lion');
 // get the class name
 $class = JRequest::getWord('class', 'Monkey', 'GET');
 $class = ucfirst(strtolower($class));

Notice that we use the getWord() method to retrieve the value; this ensures that the value only includes letters and underscores. We also modify the case of the value so as to ensure it is in the same format as the expected value. Once we have defined the expectable class names and retrieved the value we can validate the value:

 if(!in_array($class, $allow))
 {
  // unknown class, use default
  $class = 'Monkey';
 }

Imagine we want to execute a shell command. This type of process is potentially very risky; some unwanted malicious commands such as rm or del could potentially reduce our server to a gibbering wreck. In this example we define an array of acceptable commands and use the PHP escapeshellarg() function to escape any arguments passed to the command.

 $allowCmds = array('mysqld', 'apachectl');
 $cmd = JRequest::getVar('cmd', false, 'GET', 'WORD');
 $arg = JRequest::getVar('arg', false, 'GET', 'WORD');
 if( $cmd !== false && !in_array($cmd, $allow) )
 {
  $cmd .= ' '.escapeshellarg( $arg );
  system( $cmd );
 }

Using the correct escape mechanism for the system we are accessing is imperative in preventing code injection attacks.

[edit] SQL Injection

Probably one of the most publicized vulnerabilities in PHP applications, SQL injection is potentially fatal. It is caused by inadequate processing of data before database queries are executed.

Joomla! provides us with the JDatabase methods getEscaped() and Quote() specifically for avoiding SQL injection. Consider the following value a' OR name IS NOT NULL OR name='b. If we used this value without escaping the value, we could inadvertently give an attacker access to all the records in a table:

SELECT * FROM `#__test` WHERE `name`='a' OR name IS NOT NULL OR name='b'

We can overcome this using the Quote() method:

 
 $db =& JFactory::getDBO();
 $name = $db->QuotegetEscaped(JRequest('name'));

Using the getEscaped() method escapes any special characters in the passed string. In our example the inverted comas will be escaped by prefixing them with a backslash. Our query now becomes:

SELECT * FROM `#__test` WHERE `name`='a\' OR name IS NOT NULL OR name=\'b'

The Quote() method is identical to the getEscaped() method except that it also adds quotation marks around the value. Generally we should use Quote() in preference to getEscaped(), because this method guarantees that we are using the correct quotation marks for the database server that is being used.

Something else we can verify is the number of results returned after we submit a query. For example, if we know that we should only get one record from a query, we can easily verify this.

 $db->setQuery($query);
 $row = $db->loadAssoc();
 if( $db->getNumRows() !== 1 )
 {
  // handle unexpected query result
 }

[edit] XSS (Cross Site Scripting)

XSS is the use of scripts that are executed client side that take advantage of the user's local rights. These attacks normally take the form of JavaScript. Another, slightly less common, form of XSS attack uses specially crafted images that execute code on the client; a good example of this is a Microsoft security flaw that was reported in 2004 (http://www.microsoft.com/technet/security/bulletin/MS04-028.mspx).

When we use JRequest::getVar() we automatically strip out XSS code, unless we use the JREQUEST_ALLOWRAW mask. We generally use this mask when dealing with large text fields that use are rendered using an editor; if we do not, valuable XHTML formatting data will be lost.

When we use the JREQUEST_ALLOWRAW mask we need to think carefully about how we process the data. When rendering the data remember to use the PHP htmlspecialchars() function or the static JOutput class to make the data safe for rendering in an XHTML page. When using the data with the database, remember to escape the data using the database object's Quote() method.

If you want to allow your users to submit formatted data, you may want to consider using BBCode (Bulletin Board Code). BBCode is a simple markup language that uses a similar format to XHTML. Commonly used on forums, the language allows us to give the user the power to format their data without the worry of XSS. There are all sorts of BBCode tags; exactly how they are rendered may differ.

BBCode XHTML Example
[b]Bold text[/b] <b>Bold text</b> Bold text
[i]Italic text[/i] <i>Italic text</i> Italic text
[u]Underlined text[/u] <u>Underlined text</u> Underlined text
 :) <img src="/somewhere/smile.jpg" /> Image:2820_11_05.png
[quote]Some quote[/quote] <div class="quote">Some quote</div> Some quote

Joomla! does not include any BBCode-parsing libraries. Instead we must either build our own parser or include an existing library. One such BBCode library is a class available from http://www.phpclasses.org/browse/package/951.html created by Leif K-Brooks and released under the PHP License. This class gives us lots of control; it allows us to define our own BBCode tags, use HTML entity encoded data, and import and export settings.

Note - When we use BBCode, or a similar parsing mechanism, it is important that if we intend to allow the data to be editable, we store the data in its RAW state.

[edit] File System Snooping

A common error when working with files is to allow traversal of the file system. Joomla! provides us with a number of classes for dealing with the file system. This example imports the joomla.filesystem library and builds a path based on the value of the CGI request file (the path must not be relative).

  
 jimport('joomla.filesystem');
 $path = JPATH_COMPONENT.DS.'files'.DS .JRequest('file', 'somefile.php', 'GET', 'WORD');
 JPath::check($path);

When we use the JPath::check() method, if $path is considered to be snooping, an error will be raised and the application will be terminated. Snooping paths are identified as paths that do not start with JPATH_BASE and do not attempt to traverse the tree using the parent directory indicator .. (two periods).

Other classes in the joomla.filesystem library include JFile, JFolder, and JArchive. It's important to realize that none of these classes validate path parameters to prevent snooping. This is because there are times when we expect a path to be classified as snooping.

[edit] Dealing with Attacks

Parsing input is only one part of security handling. Another part is the evasive action that an extension can automatically take if an attack is detected. Here are three good ways of dealing with detected attacks; they could be used separately or in conjunction with one another:

1. Log the user out, possibly blocking their account.

2. Maintain a log file of detected attacks.

3. Email the site administrator and inform them of the attack.

[edit] Log Out and Block

If the attack has come from a logged in user we can end the user's session and optionally block them from logging in until an administrator unblocks their account. Logging out a user and blocking them may not be appropriate. An instance appearing to be an attack could be a genuine mistake on the part of the user or a misclassification. We could use a 'three strikes and you're out' approach. This way we can reduce the chance of irritating genuine users but maintain a high level of security.

One way of implementing this would be to build a Plugin, an event handler class (extends JPlugin) registered to the application. This modular approach to dealing with attacks, would allow us to reuse the plugin throughout our extensions. The UML diagram shows one design we could use.

_params is a temporary store for the Plugin parameters (JParameter object). onAttackDetected() is the method that will be executed when an attack is detected. &_getParams() gets the Plugin parameters (uses _params). _attackCount() gets the number of detected attacks so far (stored in the session). _incrementAttacks() increments the number of attacks and returns the new number of attacks. When the user exceeds the maximum number of detected attacks _actionLogout() and _actionBlock() are run, if they are enabled in the Plugin parameters.

This is the definition of the parameters; this would be in the plugin XML file.

 <params>
  <param name="sessionValue" type="text" size="20" default="detectedAttacks" label="sessionValue" 
 description="Name of session value to store attack counter in." />
  <param name="maxAttacks" type="text" size="2" default="3" label="maxAttacks" description="Maximum number of  
  detections per session." />
  <param name="@spacer" type="spacer" default="" label="" description="" />
  <param name="logout" type="radio" default="1" label="logout" description="Logout user.">
  <option value="0">Off</option>
  <option value="1">On</option>
  </param>
  <param name="block" type="radio" default="1" label="block" description="Block user.">
  <option value="0">Off</option>
  <option value="1">On</option>
  </param>
 </params>

The example shows how we could implement the _logout() method. Notice we check if the user is logged in before attempting to log them out.

 /**
  * Logs the current user out.
  *
  * @access private
  * @return boolean true on success
  */
 function _actionLogout()
 {
  global $mainframe;
  $user =& JFactory::getUser();
  if($user->get('id') && $mainframe->logout() )
  {
  return true;
  }
  return false;
 }

The next example shows how we could implement the _block() method. Notice we check if the user is logged in before attempting to block them.

 /**
  * If they are logged in, blocks the current user's account.
  *
  * @access private
  * @return boolean true on success
  */
 function _block()
 {
  $user =& JFactory::getUser();
  print_r($user);
  if($user->get('id'))
  {
  $user->set('block', '1');
  return $user->save(true);
  }
  return false;
 }

To be able to use the DefenceHandler class we need to register the event with the application. This creates a new instance of DefenceHandler and attaches it to the application event handler.

$mainframe->registerEvent('onAttackDetected', 'DefenceHandler');

If we detected an attack we would use the handler by triggering the event onAttackDetected in the application ($mainframe):

$mainframe->triggerEvent('onAttackDetected');

[edit] Attack Logging

Detecting attacks can prevent individual attacks but, when we encounter a persistent attacker, having a history of attacks can provide us with vital information. This information can be used to determine the nature of each attack and to try to identify the perpetrator.

Building on our previous example we can use the JLog class to build up a history of attacks. Here's an example of how we might implement the _actionLog() method in our DefenceHandler class.

/**
  * Logs an Attack.
  *
  * @access private
  * @return boolean true on success
  */
 function _actionLog()
 {
  $user =& JFactory::getUser();
  $uri =& JFactory::getURI();
  $options = array('format'=>"{DATE}\t{TIME}\t{CIP} \t{USER}\t{STRIKE}\t{REQUEST}");
  $log =& JLog::getInstance($extension.'.Defences.log', $options);
  $entry = array( 'REQUEST' => $uri->toString(), 'USER' => $user->get('id'), 'STRIKE' => $this->strikeCount() );
  $log->addEntry($entry);
 }

To use this we would need to modify the plugin XML file to include the option to log attacks and we would need to update the onAttackDetected() method to deal with logging.

[edit] Notify the Site Administrator

We may also want to notify the site administrator when a user exceeds the maximum number of attacks. This time we need to add a _actionNotify() method to our DefenceHandler class and a text field for an email address in our plugin's XML file parameters.

 /**
  * Logs an Attack.
  *
  * @access private
  * @param string email address
  * @return boolean true on success
  */
 function _actionNotify( $email )
 {
  global $mainframe;
  $mailer =& $mainframe->getMailer();
  $mailer->setSender($email);
  $mailer->setRecipient($email);
  $mailer->setSubject(JText::_('Excessive Attacks Detected'));
  $mailer->setBody(JText::_"A user has exceeded the number of allowed attacks. Please consult your error log for more details."));
  $mailer->Send();
 }

This example is relatively simple. We could develop the method further by adding a more comprehensive subject line and body. If logging is enabled we could also include a copy of the log as an attachment (we would have to be careful if the log file was very large).

[edit] Summary

Although we may perhaps never receive an error message from our extensions, the JError class gives us all of the necessary tools to ensure that any errors that are encountered can be cleanly dealt with. Using the PHP die() and exit() functions can potentially 'break' the current users session; we should always exit cleanly. If JError isn't up to this task, we should use $mainframe->close().

Handling input from a URI query is very easy in Joomla! and the data type casting alone provides us with a massive form of protection against security flaws. We should remember that we can use the JRequest alias methods to easily cast an input value.

Taking input value preprocessing one step further, we can use REs to ensure that data is the expected format. Remember that we can also use REs to retrieve certain parts from a data pattern. This is especially useful if one input value contains multiple pieces of data.

When we deal with sensitive data we can restrict user access using the Joomla! GACL access control implementation. When we are creating components using the MVC architecture, we can use the controller to check for authorization.

Attackers are very resourceful and will go to great lengths to discover and exploit security flaws. Remember to always sanitize incoming data and escape outgoing data. Joomla! and PHP provide us with a plethora of utilities that, if used correctly, can ensure that our extensions are as secure as possible.

[edit] Additional References

  • For instructions on Debugging and validating Joomla Templates, click here
  • For instructions on Creating Joomla v1.0 Templates, click here
  • For instructions on Customizing Joomla v1.0 Templates, click here
  • For instructions on Creating Accessible Joomla Templates, click here
  • For instructions on Installing Joomla! 1.5, click here


[edit] Source

The source of this content is Chapter 11: Error Handling and Security of Mastering Joomla! 1.5 Extension and Framework Development by James Kennard (Packt Publishing, 2007).