Difference between revisions of "Secure coding guidelines"

From Joomla! Documentation

(Marked this version for translation)
(Some markup, capitalization changes and a URL correction.)
 
Line 2: Line 2:
 
<translate>
 
<translate>
 
<!--T:1-->
 
<!--T:1-->
Joomla includes many features that help with the task of securing applications and extensions built on it. You should always use these features if at all possible as they have been tried and tested by the many eyes of the developer community and any updates that might conceivably be required in the future will be automatically available whenever a Joomla update is applied. What follows is a description of best practice in using the Joomla API to ensure that your extensions are as secure as possible.
+
Joomla includes many features that help with the task of securing applications and extensions built on it. You should always use these features if at all possible as they have been tried and tested by the many eyes of the developer community and any updates that might conceivably be required in the future will be automatically available whenever a Joomla update is applied. What follows is a description of best practice in using the Joomla API to ensure that your extensions are as secure as possible.
  
==Getting data from the request== <!--T:2-->
+
==Getting Data from the Request== <!--T:2-->
 
{{JVer|3.x}} Starting with Joomla version 3.0, [[Retrieving_request_data_using_JInput|JInput]] should be used instead of [https://api.joomla.org/cms-3/classes/JRequest.html JRequest].
 
{{JVer|3.x}} Starting with Joomla version 3.0, [[Retrieving_request_data_using_JInput|JInput]] should be used instead of [https://api.joomla.org/cms-3/classes/JRequest.html JRequest].
  
  
 
<!--T:3-->
 
<!--T:3-->
All input originating from a user must be considered potentially dangerous and must be cleaned before being used. You should always use the Joomla [[Retrieving_request_data_using_JInput|JInput]] class to retrieve data from the request, rather than the raw $_GET, $_POST or $_REQUEST variables as the [[Retrieving_request_data_using_JInput|JInput]] methods apply input filtering by default. JInput deals with all aspects of the user request in a way that is independent of the request method used. It can also be used to retrieve cookie data and even server and environment variables. However, it is important to use the correct [[Retrieving_request_data_using_JInput|JInput]] method to ensure maximum security. It is very easy to just use the [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] method with default parameters and ignore the fact that in many cases it is possible to apply a more stringent requirement on user input.
+
All input originating from a user must be considered potentially dangerous and must be cleaned before being used. You should always use the Joomla [[Retrieving_request_data_using_JInput|JInput]] class to retrieve data from the request, rather than the raw $_GET, $_POST or $_REQUEST variables as the [[Retrieving_request_data_using_JInput|JInput]] methods apply input filtering by default. JInput deals with all aspects of the user request in a way that is independent of the request method used. It can also be used to retrieve cookie data and even server and environment variables. However, it is important to use the correct [[Retrieving_request_data_using_JInput|JInput]] method to ensure maximum security. It is very easy to just use the [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] method with default parameters and ignore the fact that in many cases it is possible to apply a more stringent requirement on user input.
  
 
<!--T:4-->
 
<!--T:4-->
Line 15: Line 15:
  
 
<!--T:5-->
 
<!--T:5-->
Using [[Retrieving_request_data_using_JInput|JInput]] also obviates the need to pay attention to the setting of magic_quotes_gpc. [[Retrieving_request_data_using_JInput|JInput]] does the right thing, regardless of whether magic_quotes_gpc is on or off. See http://php.net/manual/en/security.magicquotes.php for further information.
+
Using [[Retrieving_request_data_using_JInput|JInput]] also obviates the need to pay attention to the setting of magic_quotes_gpc. [[Retrieving_request_data_using_JInput|JInput]] does the right thing, regardless of whether magic_quotes_gpc is on or off. See http://php.net/manual/en/security.magicquotes.php for further information.
  
 
<!--T:6-->
 
<!--T:6-->
When considering user input you should think about the data type you are expecting to retrieve and apply the most stringent form of [[Retrieving_request_data_using_JInput#Getting_Values|JInput]] that is applicable in each case. In particular, avoid the lazy approach of using [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] as this will return an array that may contain entries that you did not expect and although each of those entries will have been cleaned, it is often the case that additional filtering could have been applied to some individual arguments. For example, the get method treats all arguments as strings, whereas it may be possible to restrict some arguments to be integers.
+
When considering user input you should think about the data type you are expecting to retrieve and apply the most stringent form of [[Retrieving_request_data_using_JInput#Getting_Values|JInput]] that is applicable in each case. In particular, avoid the lazy approach of using [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] as this will return an array that may contain entries that you did not expect and although each of those entries will have been cleaned, it is often the case that additional filtering could have been applied to some individual arguments. For example, the get method treats all arguments as strings, whereas it may be possible to restrict some arguments to be integers.
  
 
<!--T:7-->
 
<!--T:7-->
The first three parameters of each of the JInput get methods are the same. Only the first parameter is mandatory. In general, the format is
+
The first three parameters of each of the JInput get methods are the same. Only the first parameter is mandatory. In general, the format is
<source lang="php">
+
<syntaxhighlight lang="php">
 
     JFactory::getApplication->input-><data-source>->get<type>( <name>, <default> )
 
     JFactory::getApplication->input-><data-source>->get<type>( <name>, <default> )
</source>
+
</syntaxhighlight>
 
where
 
where
  
Line 51: Line 51:
 
| cookie || Data submitted in cookies.
 
| cookie || Data submitted in cookies.
 
|-
 
|-
| request || All the GET, POST and COOKIE data combined. This is the default.
+
| request || All the GET, POST and COOKIE data combined. This is the default.
 
|-
 
|-
 
| files || Information about files uploaded as part of a POST request.
 
| files || Information about files uploaded as part of a POST request.
Line 67: Line 67:
  
 
===Integer=== <!--T:13-->
 
===Integer=== <!--T:13-->
The following will accept an integer. An integer can include a leading minus sign, but a plus sign is not permitted.
+
The following will accept an integer. An integer can include a leading minus sign, but a plus sign is not permitted.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$integer = JFactory::getApplication->input->getInt( 'id' );
 
$integer = JFactory::getApplication->input->getInt( 'id' );
</source>
+
</syntaxhighlight>
will return the value of the "id" argument from the request (which by default includes all GET, POST and COOKIE data). The default value is zero.
+
will return the value of the "id" argument from the request (which by default includes all GET, POST and COOKIE data). The default value is zero.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$integer = JFactory::getApplication->input->cookie->getInt( 'myId', 12 );
 
$integer = JFactory::getApplication->input->cookie->getInt( 'myId', 12 );
</source>
+
</syntaxhighlight>
 
will return the value of the "myId" variable from a cookie, with a default value of 12.
 
will return the value of the "myId" variable from a cookie, with a default value of 12.
  
===Floating point number=== <!--T:14-->
+
===Floating Point Number=== <!--T:14-->
A floating point number can include a leading minus sign, but not a plus sign. If the number includes a decimal point, then there must be at least one digit before the decimal point. For example,
+
A floating point number can include a leading minus sign, but not a plus sign. If the number includes a decimal point, then there must be at least one digit before the decimal point. For example,
<source lang="php">
+
<syntaxhighlight lang="php">
 
$float = JFactory::getApplication->input->getFloat( 'price' );
 
$float = JFactory::getApplication->input->getFloat( 'price' );
</source>
+
</syntaxhighlight>
will return the value of the 'price' argument from the request. The default is "0.0".
+
will return the value of the 'price' argument from the request. The default is "0.0".
<source lang="php">
+
<syntaxhighlight lang="php">
 
$float = JFactory::getApplication->input->post->getFloat( 'total', 100.00 );
 
$float = JFactory::getApplication->input->post->getFloat( 'total', 100.00 );
</source>
+
</syntaxhighlight>
 
will retrieve the value of the 'total' argument from a POST request (but not a GET), with a default value of 100.00.
 
will retrieve the value of the 'total' argument from a POST request (but not a GET), with a default value of 100.00.
  
===Boolean value=== <!--T:15-->
+
===Boolean Value=== <!--T:15-->
Any non-zero value is regarded as being true; zero is false.
+
Any non-zero value is regarded as true; zero is false.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$boolean = JFactory::getApplication->input->getBool( 'show' );
 
$boolean = JFactory::getApplication->input->getBool( 'show' );
</source>
+
</syntaxhighlight>
will return false if the value of the 'show' argument in the request is zero, or 1 (true) if the argument is anything else. The default is false. Note that any string argument will result in a return value of true, so calling the above with a URL containing "?show=false" will actually return true!
+
will return false if the value of the 'show' argument in the request is zero, or 1 (true) if the argument is anything else. The default is false. Note that any string argument will result in a return value of true, so calling the above with a URL containing "?show=false" will actually return true!
<source lang="php">
+
<syntaxhighlight lang="php">
 
$boolean = JFactory::getApplication->input->get->getBool( 'hide', true );
 
$boolean = JFactory::getApplication->input->get->getBool( 'hide', true );
</source>
+
</syntaxhighlight>
 
will retrieve the value of the 'hide' argument from a GET request (but not a POST), with a default value of true.
 
will retrieve the value of the 'hide' argument from a GET request (but not a POST), with a default value of true.
  
 
===Word=== <!--T:16-->
 
===Word=== <!--T:16-->
A word is defined as being a string of alphabetic characters. The underscore character is permitted as part of a word.
+
A word is defined as a string of alphabetic characters. The underscore character is permitted as part of a word.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$word = JFactory::getApplication->input->cookie->getWord( 'search-word' );
 
$word = JFactory::getApplication->input->cookie->getWord( 'search-word' );
</source>
+
</syntaxhighlight>
will retrieve the value of the 'search-word' argument from the request. The default is an empty string.
+
will retrieve the value of the 'search-word' argument from the request. The default is an empty string.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$word = JFactory::getApplication->input->cookie->getWord( 'keyword', '' );
 
$word = JFactory::getApplication->input->cookie->getWord( 'keyword', '' );
</source>
+
</syntaxhighlight>
 
will retrieve the value of the 'keyword' variable from a cookie, with the default being an empty string.
 
will retrieve the value of the 'keyword' variable from a cookie, with the default being an empty string.
  
 
===Command=== <!--T:17-->
 
===Command=== <!--T:17-->
A command is like a word but a wider range of characters is permitted. Allowed characters are: all alphanumeric characters, dot, dash (hyphen) and underscore.
+
A command is like a word but a wider range of characters is permitted. Allowed characters are: all alphanumeric characters, dot, dash (hyphen) and underscore.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$command = JFactory::getApplication->input->getCmd( 'option' );
 
$command = JFactory::getApplication->input->getCmd( 'option' );
</source>
+
</syntaxhighlight>
will retrieve the value of the "option" argument from the request. The default value is an empty string.
+
will retrieve the value of the "option" argument from the request. The default value is an empty string.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$command = JFactory::getApplication->input->post->getCmd( 'controller', 'view' );
 
$command = JFactory::getApplication->input->post->getCmd( 'controller', 'view' );
</source>
+
</syntaxhighlight>
 
will retrieve the value of the "controller" argument from a POST request (but not a GET), with a default value of 'view'.
 
will retrieve the value of the "controller" argument from a POST request (but not a GET), with a default value of 'view'.
  
 
===String=== <!--T:18-->
 
===String=== <!--T:18-->
The string type allows a much wider range of input characters. It also takes an optional fourth argument specifying some additional mask options. See [[#Filter options]] for information on the available masks.
+
The string type allows a much wider range of input characters. It also takes an optional fourth argument specifying some additional mask options. See [[#Filter options]] for information on the available masks.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$string = JFactory::getApplication->input->getString( 'description' );
 
$string = JFactory::getApplication->input->getString( 'description' );
</source>
+
</syntaxhighlight>
will retrieve the value of the "description" argument from the request. The default value is an empty string. The input will have whitespace removed from the left and right ends and any HTML tags will be removed.
+
will retrieve the value of the "description" argument from the request. The default value is an empty string. The input will have whitespace removed from the left and right ends and any HTML tags will be removed.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$string = JFactory::getApplication->input->getString( 'text', '' );
 
$string = JFactory::getApplication->input->getString( 'text', '' );
</source>
+
</syntaxhighlight>
will retrieve the value of the "text" argument from the request.The default value is an empty string.
+
will retrieve the value of the "text" argument from the request. The default value is an empty string.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$string = JFactory::getApplication->input->getString( 'template', '<html />' );
 
$string = JFactory::getApplication->input->getString( 'template', '<html />' );
</source>
+
</syntaxhighlight>
will retrieve the value of the "template" argument from the request. The default value is '<html />'.
+
will retrieve the value of the "template" argument from the request. The default value is '<html />'.
  
 
===JSON String=== <!--T:19-->
 
===JSON String=== <!--T:19-->
<source lang="php">
+
<syntaxhighlight lang="php">
 
$json = JFactory::getApplication->input->json->get( 'var_name' );
 
$json = JFactory::getApplication->input->json->get( 'var_name' );
</source>
+
</syntaxhighlight>
  
 
===Generic and other data types=== <!--T:20-->
 
===Generic and other data types=== <!--T:20-->
If the above methods do not meet your needs, there is a small number of additional filter types which you can use by calling the [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] method directly. The syntax is:
+
If the above methods do not meet your needs, there is a small number of additional filter types which you can use by calling the [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] method directly. The syntax is:
<source lang="php">
+
<syntaxhighlight lang="php">
 
JFactory::getApplication->input->get( <name>, <default>, <type> );
 
JFactory::getApplication->input->get( <name>, <default>, <type> );
</source>
+
</syntaxhighlight>
 
where:
 
where:
  
Line 152: Line 152:
 
| <name> || the name of the variable to be retrieved (for example, the name of an argument in a URL).
 
| <name> || the name of the variable to be retrieved (for example, the name of an argument in a URL).
 
|-
 
|-
| <default> || the default value. There is no default value that will be returned if no default is specified in the call the [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]].   If no default is specified and the argument is not present in the request variable then it will return undefined.
+
| <default> || the default value. There is no default value that will be returned if no default is specified in the call the [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]]. If no default is specified and the argument is not present in the request variable then it will return undefined.
 
|-
 
|-
 
| <type> || specifies the data type expected (see below).
 
| <type> || specifies the data type expected (see below).
Line 158: Line 158:
  
 
<!--T:22-->
 
<!--T:22-->
The first three arguments are the same as for the more specific methods described earlier. Only the first argument is mandatory.
+
The first three arguments are the same as for the more specific methods described earlier. Only the first argument is mandatory.
  
 
<!--T:23-->
 
<!--T:23-->
Line 187: Line 187:
 
| HTML || A sanitised string. Equivalent to [[Retrieving_request_data_using_JInput#Getting_Values|JInput->getHtml]].
 
| HTML || A sanitised string. Equivalent to [[Retrieving_request_data_using_JInput#Getting_Values|JInput->getHtml]].
 
|-
 
|-
| PATH || Valid pathname regex that filters out common attacks. For example, any path beginning with a "/" will return an empty string. Simliarly, any path containing "/./" or "/../" will return an empty string. Dots within filenames are okay though. Equivalent to [[Retrieving_request_data_using_JInput#Getting_Values|JInput->getPath]].
+
| PATH || Valid pathname regex that filters out common attacks. For example, any path beginning with a "/" will return an empty string. Simliarly, any path containing "/./" or "/../" will return an empty string. Dots within filenames are okay though. Equivalent to [[Retrieving_request_data_using_JInput#Getting_Values|JInput->getPath]].
 
|-
 
|-
 
| USERNAME || Removes control characters (0x00 - 0x1F), 0x7F, <, >, ", ', % and &. Equivalent to [[Retrieving_request_data_using_JInput#Getting_Values|JInput->getUsername]].
 
| USERNAME || Removes control characters (0x00 - 0x1F), 0x7F, <, >, ", ', % and &. Equivalent to [[Retrieving_request_data_using_JInput#Getting_Values|JInput->getUsername]].
Line 197: Line 197:
 
<!--T:26-->
 
<!--T:26-->
 
Filter an array of values.
 
Filter an array of values.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$data = JFactory::getApplication()->input->post->get('data', array(), 'array');
 
$data = JFactory::getApplication()->input->post->get('data', array(), 'array');
 
$filter = JFilterInput::getInstance();
 
$filter = JFilterInput::getInstance();
Line 206: Line 206:
 
$array[] = $filter->clean($value, 'string');
 
$array[] = $filter->clean($value, 'string');
 
}
 
}
</source>
+
</syntaxhighlight>
 
For more filter types see [https://github.com/joomla/joomla-cms/blob/master/libraries/joomla/filter/input.php#L115 JFilterInput source].
 
For more filter types see [https://github.com/joomla/joomla-cms/blob/master/libraries/joomla/filter/input.php#L115 JFilterInput source].
  
Line 217: Line 217:
 
| JREQUEST_NOTRIM || Does not remove whitespace from the start and ends of strings.
 
| JREQUEST_NOTRIM || Does not remove whitespace from the start and ends of strings.
 
|-
 
|-
| JREQUEST_ALLOWRAW || Does not do any filtering at all. Use with extreme caution.
+
| JREQUEST_ALLOWRAW || Does not do any filtering at all. Use with extreme caution.
 
|-
 
|-
 
| JREQUEST_ALLOWHTML || Does not remove HTML from string inputs.
 
| JREQUEST_ALLOWHTML || Does not remove HTML from string inputs.
Line 223: Line 223:
  
 
<!--T:30-->
 
<!--T:30-->
Masks can be combined by logically OR'ing them. If no filter options are specified, then by default, whitespace is trimmed and HTML is removed.
+
Masks can be combined by logically OR'ing them. If no filter options are specified, then by default, whitespace is trimmed and HTML is removed.
  
 
===File uploads=== <!--T:31-->
 
===File uploads=== <!--T:31-->
Web servers already have a good deal of security around handling file uploads, but it is still necessary to take additional steps to ensure that file names and paths cannot be abused. A simplified form which requests a file to be uploaded looks like this:
+
Web servers already have a good deal of security around handling file uploads, but it is still necessary to take additional steps to ensure that file names and paths cannot be abused. A simplified form which requests a file to be uploaded looks like this:
<source lang="html4strict">
+
<syntaxhighlight lang="html4strict">
<form action="index.php?option=com_mycomponent/form_handler.php" method="post" enctype="multipart/form-data">
+
<form action="index.php?option=com_mycomponent/form_handler.php" method="post" enctype="multipart/form-data">
 
         <input type="file" name="Filedata" />
 
         <input type="file" name="Filedata" />
 
     <input type="submit" />
 
     <input type="submit" />
 
</form>
 
</form>
</source>
+
</syntaxhighlight>
On clicking the submit button, the browser will upload the file in a POST request, passing control to Joomla which will call "components/com_mycomponent/form_handler.php". This will include code like the following. The variable $somepath must be set to some path where the web server has permission to create files.
+
On clicking the submit button, the browser will upload the file in a POST request, passing control to Joomla which will call "components/com_mycomponent/form_handler.php". This will include code like the following. The variable $somepath must be set to some path where the web server has permission to create files.
<source lang="php">
+
<syntaxhighlight lang="php">
 
// Check to ensure this file is included in Joomla!
 
// Check to ensure this file is included in Joomla!
 
defined('_JEXEC') or die( 'Restricted access' );
 
defined('_JEXEC') or die( 'Restricted access' );
Line 259: Line 259:
 
     JFile::upload( $file['tmp_name'], $filepath );
 
     JFile::upload( $file['tmp_name'], $filepath );
 
}
 
}
</source>
+
</syntaxhighlight>
  
 
===Saving a request variable into user state=== <!--T:37-->
 
===Saving a request variable into user state=== <!--T:37-->
Because setting a user state variable from a variable in the request is such a common operation, there is an API method to make the task easier. This is generally safe to use because it calls [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] to obtain the input from the request, but remember that none of the input filtering calls will protect against SQL injection attempts.
+
Because setting a user state variable from a variable in the request is such a common operation, there is an API method to make the task easier. This is generally safe to use because it calls [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] to obtain the input from the request, but remember that none of the input filtering calls will protect against SQL injection attempts.
<source lang="php">
+
<syntaxhighlight lang="php">
 
$app = JFactory::getApplication();
 
$app = JFactory::getApplication();
 
$app->getUserStateFromRequest( <key>, <name>, <default>, <type> );
 
$app->getUserStateFromRequest( <key>, <name>, <default>, <type> );
</source>
+
</syntaxhighlight>
 
where
 
where
 
{| class="wikitable" border="1"
 
{| class="wikitable" border="1"
Line 273: Line 273:
 
| <name> || the name of the request variable (same as the first argument of a [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] call).
 
| <name> || the name of the request variable (same as the first argument of a [[Retrieving_request_data_using_JInput#Getting_Values|JInput->get]] call).
 
|-
 
|-
| <default> || the default value to be assigned to the user state variable if the request variable is absent. The default is null.
+
| <default> || the default value to be assigned to the user state variable if the request variable is absent. The default is null.
 
|-
 
|-
 
| <type> || the type of variable expected.
 
| <type> || the type of variable expected.
Line 279: Line 279:
  
 
<!--T:38-->
 
<!--T:38-->
For example, getting an integer variable called 'id' from the request with a default value of 0, then saving it into a session variable called 'myid' can be done like this:
+
For example, getting an integer variable called ''id'' from the request with a default value of 0, then saving it into a session variable called ''myid'' can be done like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$app = JFactory::getApplication();
 
$app = JFactory::getApplication();
 
$app->getUserStateFromRequest( 'myid', 'id, 0, 'int' );
 
$app->getUserStateFromRequest( 'myid', 'id, 0, 'int' );
</source>
+
</syntaxhighlight>
 
instead of something like this:
 
instead of something like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$app = JFactory::getApplication();
 
$app = JFactory::getApplication();
 
$app->setUserState( 'myid', $app->input->getInt( 'id', 0 ) );
 
$app->setUserState( 'myid', $app->input->getInt( 'id', 0 ) );
</source>
+
</syntaxhighlight>
  
==Constructing SQL queries== <!--T:39-->
+
==Constructing SQL Queries== <!--T:39-->
One of the most common forms of attack on web applications is SQL injection, where the aim of the attacker is to change a database query by exploiting a poorly filtered input variable. Injecting modified SQL statements into the database can damage data or reveal private information. It is important to ensure that when SQL statements are constructed, they are correctly escaped and quoted so that bad input data cannot result in a bad SQL statement. You cannot rely on the [[Retrieving_request_data_using_JInput|JInput]] methods to do this as they are not SQL-aware.
+
One of the most common forms of attack on web applications is SQL injection, where the aim of the attacker is to change a database query by exploiting a poorly filtered input variable. Injecting modified SQL statements into the database can damage data or reveal private information. It is important to ensure that when SQL statements are constructed, they are correctly escaped and quoted so that bad input data cannot result in a bad SQL statement. You cannot rely on the [[Retrieving_request_data_using_JInput|JInput]] methods to do this as they are not SQL-aware.
  
 
===Secure integers and the rest of numeric values=== <!--T:40-->
 
===Secure integers and the rest of numeric values=== <!--T:40-->
With the MySQL database, numeric fields should not be quoted, so it is important that they be typecast instead. Failure to do this will leave your code vulnerable to an attacker inserting a string containing SQL data.
+
With the MySQL database, numeric fields should not be quoted, so it is important that they be typecast instead. Failure to do this will leave your code vulnerable to an attacker inserting a string containing SQL data.
  
 
<!--T:41-->
 
<!--T:41-->
 
Depending on the type, numeric types are cast like this:
 
Depending on the type, numeric types are cast like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
// For SQL data types: INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, BIGINT, YEAR
 
// For SQL data types: INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, BIGINT, YEAR
 
$query = 'SELECT * FROM #__table WHERE `id`=' . (int) $id;
 
$query = 'SELECT * FROM #__table WHERE `id`=' . (int) $id;
Line 305: Line 305:
 
// For SQL data types: FLOAT, DOUBLE
 
// For SQL data types: FLOAT, DOUBLE
 
$query = 'SELECT * FROM #__table WHERE `id`=' . (float) $id;
 
$query = 'SELECT * FROM #__table WHERE `id`=' . (float) $id;
</source>
+
</syntaxhighlight>
It's a good idea to get into the habit of always typecasting integers like this even if the variable was previously obtained using JInput->getInt. Further information on SQL injection attacks can be found here: http://php.net/manual/en/security.database.sql-injection.php and here: [[Retrieving_request_data_using_JInput#Getting_Values]].
+
It's a good idea to get into the habit of always typecasting integers like this even if the variable was previously obtained using JInput->getInt. Further information on SQL injection attacks can be found at the [https://www.php.net/manual/en/security.database.sql-injection.php PHP.NET SQL Injection page] and here: [[Retrieving_request_data_using_JInput#Getting_Values]].
  
===Secure strings=== <!--T:43-->
+
===Secure Strings=== <!--T:43-->
In the examples that follow it is assumed that $db is an instance of a Joomla database object. This can always be obtained from [https://docs.joomla.org/Using_the_JFactory_class JFactory] using
+
In the examples that follow it is assumed that $db is an instance of a Joomla database object. This can always be obtained from [https://docs.joomla.org/Using_the_JFactory_class JFactory] using
<source lang="php">
+
<syntaxhighlight lang="php">
 
$db = JFactory::getDBO();
 
$db = JFactory::getDBO();
</source>
+
</syntaxhighlight>
Strings should always be escaped before being used in an SQL statement. This is actually very simple as the [https://api.joomla.org/cms-3/classes/JDatabaseQuery.html#method_quote JDatabase->quote] method escapes everything for you. You can also use the [https://api.joomla.org/cms-3/classes/JDatabaseQuery.html#method_escape JDatabase->escape] method directly. The following statements are equivalent:
+
Strings should always be escaped before being used in an SQL statement. This is actually very simple as the [https://api.joomla.org/cms-3/classes/JDatabaseQuery.html#method_quote JDatabase->quote] method escapes everything for you. You can also use the [https://api.joomla.org/cms-3/classes/JDatabaseQuery.html#method_escape JDatabase->escape] method directly. The following statements are equivalent:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$query = 'SELECT * FROM #__table WHERE `field` = ' . $db->quote( $db->escape( $field ), false );
 
$query = 'SELECT * FROM #__table WHERE `field` = ' . $db->quote( $db->escape( $field ), false );
  
 
<!--T:44-->
 
<!--T:44-->
 
$query = 'SELECT * FROM #__table WHERE `field` = ' . $db->quote( $field );
 
$query = 'SELECT * FROM #__table WHERE `field` = ' . $db->quote( $field );
</source>
+
</syntaxhighlight>
  
 
===Secure on search=== <!--T:45-->
 
===Secure on search=== <!--T:45-->
Special attention should be paid to LIKE clauses which contain the % wildcard character as these require special escaping in order to avoid possible denial of service attacks. LIKE clauses can be handled like this:
+
Special attention should be paid to LIKE clauses which contain the % wildcard character as these require special escaping in order to avoid possible denial of service attacks. LIKE clauses can be handled like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
// Construct the search term by escaping the user-supplied string and, if required, adding the % wildcard characters manually.
 
// Construct the search term by escaping the user-supplied string and, if required, adding the % wildcard characters manually.
 
$search = '%' . $db->escape( $search, true ) . '%';
 
$search = '%' . $db->escape( $search, true ) . '%';
Line 330: Line 330:
 
// Construct the SQL query, being careful to suppress the default behaviour of Quote so as to prevent double-escaping.
 
// Construct the SQL query, being careful to suppress the default behaviour of Quote so as to prevent double-escaping.
 
$query = 'SELECT * FROM #__table WHERE `field` LIKE ' . $db->quote( $search, false );
 
$query = 'SELECT * FROM #__table WHERE `field` LIKE ' . $db->quote( $search, false );
</source>
+
</syntaxhighlight>
  
 
===Secure dates=== <!--T:47-->
 
===Secure dates=== <!--T:47-->
 
If data is to be entered into a datetime column then you can use the Joomla API to ensure a valid date format:
 
If data is to be entered into a datetime column then you can use the Joomla API to ensure a valid date format:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$date = JFactory::getDate( $mydate );
 
$date = JFactory::getDate( $mydate );
 
$query = 'UPDATE #__table SET `date` = ' . $db->quote( $date->toMySQL(), false );
 
$query = 'UPDATE #__table SET `date` = ' . $db->quote( $date->toMySQL(), false );
</source>
+
</syntaxhighlight>
 
Note that it is necessary to suppress database escaping as legitimate dates may contain characters that should not be escaped.
 
Note that it is necessary to suppress database escaping as legitimate dates may contain characters that should not be escaped.
  
 
===Secure field names=== <!--T:48-->
 
===Secure field names=== <!--T:48-->
 
In the comparatively rare case where a field name is a variable, that should also be quoted using an API call:
 
In the comparatively rare case where a field name is a variable, that should also be quoted using an API call:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$query = 'SELECT * FROM #__table WHERE ' . $db->quoteName( $field->name ) . '=' . $db->quote( $field->value );
 
$query = 'SELECT * FROM #__table WHERE ' . $db->quoteName( $field->name ) . '=' . $db->quote( $field->value );
</source>
+
</syntaxhighlight>
  
 
===Secure arrays of integers=== <!--T:49-->
 
===Secure arrays of integers=== <!--T:49-->
When you have an array of ids, typically used for IN() queries, you have to sanitise it also with JArrayHelper::toInteger($cid); before imploding:  
+
When you have an array of ids, typically used for IN() queries, you have to sanitise it also with JArrayHelper::toInteger($cid); before imploding:
  
 
<!--T:50-->
 
<!--T:50-->
<source lang="php">
+
<syntaxhighlight lang="php">
 
...
 
...
 
$catId = JArrayHelper::toInteger($catId);
 
$catId = JArrayHelper::toInteger($catId);
 
$query->where($db->quoteName('x.category_id') . ' IN (' . implode(',', $catId) . ')');
 
$query->where($db->quoteName('x.category_id') . ' IN (' . implode(',', $catId) . ')');
</source>
+
</syntaxhighlight>
  
===Short aliases of Quote and QuoteName=== <!--T:51-->
+
===Short Aliases of Quote and QuoteName=== <!--T:51-->
Shorter alternatives to the quote methods may also be used. The following statements are equivalent:
+
Shorter alternatives to the quote methods may also be used. The following statements are equivalent:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$query = 'SELECT * FROM #__table WHERE ' . $db->quoteName( $field->name ) . '=' . $db->quote( $field->value );
 
$query = 'SELECT * FROM #__table WHERE ' . $db->quoteName( $field->name ) . '=' . $db->quote( $field->value );
 
$query = 'SELECT * FROM #__table WHERE ' . $db->qn( $field->name ) . '=' . $db->q( $field->value );
 
$query = 'SELECT * FROM #__table WHERE ' . $db->qn( $field->name ) . '=' . $db->q( $field->value );
</source>
+
</syntaxhighlight>
  
 
==Securing forms== <!--T:52-->
 
==Securing forms== <!--T:52-->
Apart from cleaning input variables as described above, you can also implement a simple technique which makes it more difficult for a cross-site request forgery attack (CSRF) to succeed. This involves adding a randomly-generated unique token to the form which is checked against a copy of the token held in the user's session. By checking that the submitted token matches the one contained in the stored session, it is possible to tie a rendered form to the request variables presented.
+
Apart from cleaning input variables as described above, you can also implement a simple technique which makes it more difficult for a cross-site request forgery attack (CSRF) to succeed. This involves adding a randomly-generated unique token to the form which is checked against a copy of the token held in the user's session. By checking that the submitted token matches the one contained in the stored session, it is possible to tie a rendered form to the request variables presented.
  
 
<!--T:53-->
 
<!--T:53-->
 
In POST forms you should add a hidden token field using:
 
In POST forms you should add a hidden token field using:
<source lang="php">
+
<syntaxhighlight lang="php">
 
echo JHTML::_( 'form.token' );
 
echo JHTML::_( 'form.token' );
</source>
+
</syntaxhighlight>
 
This outputs the token as a hidden form field looking like this:
 
This outputs the token as a hidden form field looking like this:
<source lang="html4strict">
+
<syntaxhighlight lang="html4strict">
 
<input type="hidden" name="8cb24ae69ffd7828ccecbcf06056e6fc" value="1" />
 
<input type="hidden" name="8cb24ae69ffd7828ccecbcf06056e6fc" value="1" />
</source>
+
</syntaxhighlight>
 
and places a copy of the token into the user's session, for later checking.
 
and places a copy of the token into the user's session, for later checking.
  
 
<!--T:54-->
 
<!--T:54-->
 
If you need to add the token to a URL rather than a form then you can use something like this:
 
If you need to add the token to a URL rather than a form then you can use something like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
echo JRoute::_( 'index.php?option=com_mycomponent&' . JSession::getFormToken() . '=1' );
 
echo JRoute::_( 'index.php?option=com_mycomponent&' . JSession::getFormToken() . '=1' );
</source>
+
</syntaxhighlight>
  
 
<!--T:55-->
 
<!--T:55-->
In the most common scenario, you will want to check the token following a POST to the form handler. This can be done by adding this line of code to form handler:
+
In the most common scenario, you will want to check the token following a POST to the form handler. This can be done by adding this line of code to form handler:
<source lang="php">
+
<syntaxhighlight lang="php">
 
JSession::checkToken() or die( JText::_( 'Invalid Token' ) );
 
JSession::checkToken() or die( JText::_( 'Invalid Token' ) );
</source>
+
</syntaxhighlight>
 
If you need to pass the token in a GET request then you can check it like this:
 
If you need to pass the token in a GET request then you can check it like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
JSession::checkToken( 'get' ) or die( JText::_( 'Invalid Token' ) );
 
JSession::checkToken( 'get' ) or die( JText::_( 'Invalid Token' ) );
</source>
+
</syntaxhighlight>
  
 
<!--T:56-->
 
<!--T:56-->
In both cases the code will die if the token is omitted from the request, or the submitted token does not match the session token. If the token is correct but has expired, then [[JSession/checkToken|JSession::checkToken]] will automatically redirect to the site front page.
+
In both cases the code will die if the token is omitted from the request, or the submitted token does not match the session token. If the token is correct but has expired, then [[JSession/checkToken|JSession::checkToken]] will automatically redirect to the site front page.
  
==Cleaning filesystem paths== <!--T:57-->
+
==Cleaning Filesystem Paths== <!--T:57-->
If there is any possibility that a filesystem path might be constructed using data that originated from user input, then the path must be cleaned and checked before being used. This can be done quite simply like this:
+
If there is any possibility that a filesystem path might be constructed using data that originated from user input, then the path must be cleaned and checked before being used. This can be done quite simply like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
JPath::check( $path );
 
JPath::check( $path );
</source>
+
</syntaxhighlight>
This will raise an error and terminate Joomla if the path contains a ".." or leads to a location outside the Joomla root directory. If you want to deal with the error yourself without terminating the application, then you can use code like this:
+
This will raise an error and terminate Joomla if the path contains a ".." or leads to a location outside the Joomla root directory. If you want to deal with the error yourself without terminating the application, then you can use code like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
$path = JPath::clean( $path );
 
$path = JPath::clean( $path );
 
if (strpos( $path, JPath::clean( JPATH_ROOT ) ) !== 0) {
 
if (strpos( $path, JPath::clean( JPATH_ROOT ) ) !== 0) {
 
     // Handle the error here.
 
     // Handle the error here.
 
}
 
}
</source>
+
</syntaxhighlight>
The [[JPath:clean]] method can be used in your own code too. It merely removes leading and trailing whitespace and replace double slashes and backslashes with the standard directory separator.
+
The [[JPath:clean]] method can be used in your own code too. It merely removes leading and trailing whitespace and replace double slashes and backslashes with the standard directory separator.
  
==Cleaning filesystem file names== <!--T:58-->
+
==Cleaning Filesystem File Names== <!--T:58-->
As with filesystem paths, if there is any possibility that a file name might be constructed using user-originated data, then the file name must be cleaned and checked before use. This can be done like this:
+
As with filesystem paths, if there is any possibility that a file name might be constructed using user-originated data, then the file name must be cleaned and checked before use. This can be done like this:
<source lang="php">
+
<syntaxhighlight lang="php">
 
jimport('joomla.filesystem.file');
 
jimport('joomla.filesystem.file');
 
$clean = JFile::makeSafe( $unclean );
 
$clean = JFile::makeSafe( $unclean );
</source>
+
</syntaxhighlight>
This method removes sequences of two or more "." characters and any character that is not alphabetic, numeric or a dot, dash or underscore character. If there is a leading dot then that is removed too.
+
This method removes sequences of two or more "." characters and any character that is not alphabetic, numeric or a dot, dash or underscore character. If there is a leading dot then that is removed too.
 
[[Category:Development]][[Category:Security]]
 
[[Category:Development]][[Category:Security]]
 
</translate>
 
</translate>

Latest revision as of 16:36, 15 October 2022

Other languages:
English • ‎中文(台灣)‎

Joomla includes many features that help with the task of securing applications and extensions built on it. You should always use these features if at all possible as they have been tried and tested by the many eyes of the developer community and any updates that might conceivably be required in the future will be automatically available whenever a Joomla update is applied. What follows is a description of best practice in using the Joomla API to ensure that your extensions are as secure as possible.

Getting Data from the Request[edit]

Joomla 3.x Starting with Joomla version 3.0, JInput should be used instead of JRequest.


All input originating from a user must be considered potentially dangerous and must be cleaned before being used. You should always use the Joomla JInput class to retrieve data from the request, rather than the raw $_GET, $_POST or $_REQUEST variables as the JInput methods apply input filtering by default. JInput deals with all aspects of the user request in a way that is independent of the request method used. It can also be used to retrieve cookie data and even server and environment variables. However, it is important to use the correct JInput method to ensure maximum security. It is very easy to just use the JInput->get method with default parameters and ignore the fact that in many cases it is possible to apply a more stringent requirement on user input.

It very important to understand that the JInput methods are not SQL-aware and further work is required to guard against SQL injection attacks.There is no default value that will be returned if no default is specified in the call the JInput->get. If no default is specified and the argument is not present in the request variable then it will return undefined.

Using JInput also obviates the need to pay attention to the setting of magic_quotes_gpc. JInput does the right thing, regardless of whether magic_quotes_gpc is on or off. See http://php.net/manual/en/security.magicquotes.php for further information.

When considering user input you should think about the data type you are expecting to retrieve and apply the most stringent form of JInput that is applicable in each case. In particular, avoid the lazy approach of using JInput->get as this will return an array that may contain entries that you did not expect and although each of those entries will have been cleaned, it is often the case that additional filtering could have been applied to some individual arguments. For example, the get method treats all arguments as strings, whereas it may be possible to restrict some arguments to be integers.

The first three parameters of each of the JInput get methods are the same. Only the first parameter is mandatory. In general, the format is

    JFactory::getApplication->input-><data-source>->get<type>( <name>, <default> )

where

<type> the data type to be retrieved (see below for the types available).
<name> the name of the variable to be retrieved (for example, the name of an argument in a URL).
<default> the default value.
<data-source> specifies where the variable is to be retrieved from (see below).

The following values for <data-source> are supported:

get Data submitted in the query part of the URL.
post Data submitted from form fields.
method The same as either GET or POST depending on how the request was made.
cookie Data submitted in cookies.
request All the GET, POST and COOKIE data combined. This is the default.
files Information about files uploaded as part of a POST request.
env Environment variables (platform-specific).
server Web server variables (platform-specific).

Notice that the default is REQUEST, which includes cookie data.

The following sections look at each of the data types in more detail.

Integer[edit]

The following will accept an integer. An integer can include a leading minus sign, but a plus sign is not permitted.

$integer = JFactory::getApplication->input->getInt( 'id' );

will return the value of the "id" argument from the request (which by default includes all GET, POST and COOKIE data). The default value is zero.

$integer = JFactory::getApplication->input->cookie->getInt( 'myId', 12 );

will return the value of the "myId" variable from a cookie, with a default value of 12.

Floating Point Number[edit]

A floating point number can include a leading minus sign, but not a plus sign. If the number includes a decimal point, then there must be at least one digit before the decimal point. For example,

$float = JFactory::getApplication->input->getFloat( 'price' );

will return the value of the 'price' argument from the request. The default is "0.0".

$float = JFactory::getApplication->input->post->getFloat( 'total', 100.00 );

will retrieve the value of the 'total' argument from a POST request (but not a GET), with a default value of 100.00.

Boolean Value[edit]

Any non-zero value is regarded as true; zero is false.

$boolean = JFactory::getApplication->input->getBool( 'show' );

will return false if the value of the 'show' argument in the request is zero, or 1 (true) if the argument is anything else. The default is false. Note that any string argument will result in a return value of true, so calling the above with a URL containing "?show=false" will actually return true!

$boolean = JFactory::getApplication->input->get->getBool( 'hide', true );

will retrieve the value of the 'hide' argument from a GET request (but not a POST), with a default value of true.

Word[edit]

A word is defined as a string of alphabetic characters. The underscore character is permitted as part of a word.

$word = JFactory::getApplication->input->cookie->getWord( 'search-word' );

will retrieve the value of the 'search-word' argument from the request. The default is an empty string.

$word = JFactory::getApplication->input->cookie->getWord( 'keyword', '' );

will retrieve the value of the 'keyword' variable from a cookie, with the default being an empty string.

Command[edit]

A command is like a word but a wider range of characters is permitted. Allowed characters are: all alphanumeric characters, dot, dash (hyphen) and underscore.

$command = JFactory::getApplication->input->getCmd( 'option' );

will retrieve the value of the "option" argument from the request. The default value is an empty string.

$command = JFactory::getApplication->input->post->getCmd( 'controller', 'view' );

will retrieve the value of the "controller" argument from a POST request (but not a GET), with a default value of 'view'.

String[edit]

The string type allows a much wider range of input characters. It also takes an optional fourth argument specifying some additional mask options. See #Filter options for information on the available masks.

$string = JFactory::getApplication->input->getString( 'description' );

will retrieve the value of the "description" argument from the request. The default value is an empty string. The input will have whitespace removed from the left and right ends and any HTML tags will be removed.

$string = JFactory::getApplication->input->getString( 'text', '' );

will retrieve the value of the "text" argument from the request. The default value is an empty string.

$string = JFactory::getApplication->input->getString( 'template', '<html />' );

will retrieve the value of the "template" argument from the request. The default value is '<html></html>'.

JSON String[edit]

$json = JFactory::getApplication->input->json->get( 'var_name' );

Generic and other data types[edit]

If the above methods do not meet your needs, there is a small number of additional filter types which you can use by calling the JInput->get method directly. The syntax is:

JFactory::getApplication->input->get( <name>, <default>, <type> );

where:

<name> the name of the variable to be retrieved (for example, the name of an argument in a URL).
<default> the default value. There is no default value that will be returned if no default is specified in the call the JInput->get. If no default is specified and the argument is not present in the request variable then it will return undefined.
<type> specifies the data type expected (see below).

The first three arguments are the same as for the more specific methods described earlier. Only the first argument is mandatory.

Allowed values of the <type>, which is case-insensitive, are as follows:

INT, INTEGER Equivalent to JInput->getInt.
UINT Get an unsigned integer. Equivalent to JInput->getUint.
FLOAT, DOUBLE Equivalent to JInput->getFloat.
BOOL, BOOLEAN Equivalent to JInput->getBool.
WORD Equivalent to JInput->getWord.
ALNUM Allow only alphanumeric characters (a-z, A-Z, 0-9). Equivalent to JInput->getAlnum.
CMD Equivalent to JInput->getCmd.
BASE64 Allow only those characters that could be present in a base64-encoded string (ie. a-z, A-Z, 0-9, /, + and =). Equivalent to JInput->getBase64.
STRING Equivalent to JInput->getString.
ARRAY Source is not filtered but is cast to array type. When using this type you should use JFilterInput directly to filter the values in your data array.
HTML A sanitised string. Equivalent to JInput->getHtml.
PATH Valid pathname regex that filters out common attacks. For example, any path beginning with a "/" will return an empty string. Simliarly, any path containing "/./" or "/../" will return an empty string. Dots within filenames are okay though. Equivalent to JInput->getPath.
USERNAME Removes control characters (0x00 - 0x1F), 0x7F, <, >, ", ', % and &. Equivalent to JInput->getUsername.

Filter options[edit]

All input values can be filtered using JFilterInput->clean.

Filter an array of values.

$data = JFactory::getApplication()->input->post->get('data', array(), 'array');
$filter = JFilterInput::getInstance();

foreach ($data as $value)
{
	$array[] = $filter->clean($value, 'string');
}

For more filter types see JFilterInput source.

Options listed bellow available only in deprecated JRequest library. Allowed values of <options> are as follows (none of these are applied by default):

JREQUEST_NOTRIM Does not remove whitespace from the start and ends of strings.
JREQUEST_ALLOWRAW Does not do any filtering at all. Use with extreme caution.
JREQUEST_ALLOWHTML Does not remove HTML from string inputs.

Masks can be combined by logically OR'ing them. If no filter options are specified, then by default, whitespace is trimmed and HTML is removed.

File uploads[edit]

Web servers already have a good deal of security around handling file uploads, but it is still necessary to take additional steps to ensure that file names and paths cannot be abused. A simplified form which requests a file to be uploaded looks like this:

<form action="index.php?option=com_mycomponent/form_handler.php" method="post" enctype="multipart/form-data">
        <input type="file" name="Filedata" />
    <input type="submit" />
</form>

On clicking the submit button, the browser will upload the file in a POST request, passing control to Joomla which will call "components/com_mycomponent/form_handler.php". This will include code like the following. The variable $somepath must be set to some path where the web server has permission to create files.

// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die( 'Restricted access' );

// Get the file data array from the request.
$file = JFactory::getApplication->input->get( 'Filedata', '', 'files', 'array' );

// Make the file name safe.
jimport('joomla.filesystem.file');
$file['name'] = JFile::makeSafe($file['name']);

// Move the uploaded file into a permanent location.
if (isset( $file['name'] )) {

    // Make sure that the full file path is safe.
    $filepath = JPath::clean( $somepath.'/'.strtolower( $file['name'] ) );

    // Move the uploaded file.
    JFile::upload( $file['tmp_name'], $filepath );
}

Saving a request variable into user state[edit]

Because setting a user state variable from a variable in the request is such a common operation, there is an API method to make the task easier. This is generally safe to use because it calls JInput->get to obtain the input from the request, but remember that none of the input filtering calls will protect against SQL injection attempts.

$app = JFactory::getApplication();
$app->getUserStateFromRequest( <key>, <name>, <default>, <type> );

where

<key> the name of the variable in the user state.
<name> the name of the request variable (same as the first argument of a JInput->get call).
<default> the default value to be assigned to the user state variable if the request variable is absent. The default is null.
<type> the type of variable expected.

For example, getting an integer variable called id from the request with a default value of 0, then saving it into a session variable called myid can be done like this:

$app = JFactory::getApplication();
$app->getUserStateFromRequest( 'myid', 'id, 0, 'int' );

instead of something like this:

$app = JFactory::getApplication();
$app->setUserState( 'myid', $app->input->getInt( 'id', 0 ) );

Constructing SQL Queries[edit]

One of the most common forms of attack on web applications is SQL injection, where the aim of the attacker is to change a database query by exploiting a poorly filtered input variable. Injecting modified SQL statements into the database can damage data or reveal private information. It is important to ensure that when SQL statements are constructed, they are correctly escaped and quoted so that bad input data cannot result in a bad SQL statement. You cannot rely on the JInput methods to do this as they are not SQL-aware.

Secure integers and the rest of numeric values[edit]

With the MySQL database, numeric fields should not be quoted, so it is important that they be typecast instead. Failure to do this will leave your code vulnerable to an attacker inserting a string containing SQL data.

Depending on the type, numeric types are cast like this:

// For SQL data types: INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, BIGINT, YEAR
$query = 'SELECT * FROM #__table WHERE `id`=' . (int) $id;

// For SQL data types: FLOAT, DOUBLE
$query = 'SELECT * FROM #__table WHERE `id`=' . (float) $id;

It's a good idea to get into the habit of always typecasting integers like this even if the variable was previously obtained using JInput->getInt. Further information on SQL injection attacks can be found at the PHP.NET SQL Injection page and here: Retrieving_request_data_using_JInput#Getting_Values.

Secure Strings[edit]

In the examples that follow it is assumed that $db is an instance of a Joomla database object. This can always be obtained from JFactory using

$db = JFactory::getDBO();

Strings should always be escaped before being used in an SQL statement. This is actually very simple as the JDatabase->quote method escapes everything for you. You can also use the JDatabase->escape method directly. The following statements are equivalent:

$query = 'SELECT * FROM #__table WHERE `field` = ' . $db->quote( $db->escape( $field ), false );

$query = 'SELECT * FROM #__table WHERE `field` = ' . $db->quote( $field );

Secure on search[edit]

Special attention should be paid to LIKE clauses which contain the % wildcard character as these require special escaping in order to avoid possible denial of service attacks. LIKE clauses can be handled like this:

// Construct the search term by escaping the user-supplied string and, if required, adding the % wildcard characters manually.
$search = '%' . $db->escape( $search, true ) . '%';

// Construct the SQL query, being careful to suppress the default behaviour of Quote so as to prevent double-escaping.
$query = 'SELECT * FROM #__table WHERE `field` LIKE ' . $db->quote( $search, false );

Secure dates[edit]

If data is to be entered into a datetime column then you can use the Joomla API to ensure a valid date format:

$date = JFactory::getDate( $mydate );
$query = 'UPDATE #__table SET `date` = ' . $db->quote( $date->toMySQL(), false );

Note that it is necessary to suppress database escaping as legitimate dates may contain characters that should not be escaped.

Secure field names[edit]

In the comparatively rare case where a field name is a variable, that should also be quoted using an API call:

$query = 'SELECT * FROM #__table WHERE ' . $db->quoteName( $field->name ) . '=' . $db->quote( $field->value );

Secure arrays of integers[edit]

When you have an array of ids, typically used for IN() queries, you have to sanitise it also with JArrayHelper::toInteger($cid); before imploding:

...
$catId = JArrayHelper::toInteger($catId);
$query->where($db->quoteName('x.category_id') . ' IN (' . implode(',', $catId) . ')');

Short Aliases of Quote and QuoteName[edit]

Shorter alternatives to the quote methods may also be used. The following statements are equivalent:

$query = 'SELECT * FROM #__table WHERE ' . $db->quoteName( $field->name ) . '=' . $db->quote( $field->value );
$query = 'SELECT * FROM #__table WHERE ' . $db->qn( $field->name ) . '=' . $db->q( $field->value );

Securing forms[edit]

Apart from cleaning input variables as described above, you can also implement a simple technique which makes it more difficult for a cross-site request forgery attack (CSRF) to succeed. This involves adding a randomly-generated unique token to the form which is checked against a copy of the token held in the user's session. By checking that the submitted token matches the one contained in the stored session, it is possible to tie a rendered form to the request variables presented.

In POST forms you should add a hidden token field using:

echo JHTML::_( 'form.token' );

This outputs the token as a hidden form field looking like this:

<input type="hidden" name="8cb24ae69ffd7828ccecbcf06056e6fc" value="1" />

and places a copy of the token into the user's session, for later checking.

If you need to add the token to a URL rather than a form then you can use something like this:

echo JRoute::_( 'index.php?option=com_mycomponent&' . JSession::getFormToken() . '=1' );

In the most common scenario, you will want to check the token following a POST to the form handler. This can be done by adding this line of code to form handler:

JSession::checkToken() or die( JText::_( 'Invalid Token' ) );

If you need to pass the token in a GET request then you can check it like this:

JSession::checkToken( 'get' ) or die( JText::_( 'Invalid Token' ) );

In both cases the code will die if the token is omitted from the request, or the submitted token does not match the session token. If the token is correct but has expired, then JSession::checkToken will automatically redirect to the site front page.

Cleaning Filesystem Paths[edit]

If there is any possibility that a filesystem path might be constructed using data that originated from user input, then the path must be cleaned and checked before being used. This can be done quite simply like this:

JPath::check( $path );

This will raise an error and terminate Joomla if the path contains a ".." or leads to a location outside the Joomla root directory. If you want to deal with the error yourself without terminating the application, then you can use code like this:

$path = JPath::clean( $path );
if (strpos( $path, JPath::clean( JPATH_ROOT ) ) !== 0) {
    // Handle the error here.
}

The JPath:clean method can be used in your own code too. It merely removes leading and trailing whitespace and replace double slashes and backslashes with the standard directory separator.

Cleaning Filesystem File Names[edit]

As with filesystem paths, if there is any possibility that a file name might be constructed using user-originated data, then the file name must be cleaned and checked before use. This can be done like this:

jimport('joomla.filesystem.file');
$clean = JFile::makeSafe( $unclean );

This method removes sequences of two or more "." characters and any character that is not alphabetic, numeric or a dot, dash or underscore character. If there is a leading dot then that is removed too.