--=REKLAMA=--

Zasady bezpiecznego kodowania

Z Joomla!WikiPL

Ikona przetlumacz.png
 Uwaga o zawartości

Ta strona wymaga przetłumaczenia lub jest w trakcie tłumaczenia! Pomoc jest mile widziana. Ostatnio edytowane przez Zwiastun (dyskusja. Data edycji: Sun, 24 Nov 2013 19:05:08 +0000

Szablon:Review

The Joomla Framework 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

All input originating from a user must be considered potentially dangerous and must be cleaned before being used. You should always use the Joomla Framework JRequest class to retrieve data from the request, rather than the raw $_GET, $_POST or $_REQUEST variables as the JRequest methods apply input filtering by default. JRequest 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 JRequest method to ensure maximum security. It is very easy to just use the JRequest::getVar 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 JRequest 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 JRequest::getVar. If no default is specified and the argument is not present in the request variable then it will return undefined.

Using JRequest also obviates the need to pay attention to the setting of magic_quotes_gpc. JRequest 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 JRequest that is applicable in each case. In particular, avoid the lazy approach of using JRequest::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 JRequest get methods are the same. Only the first parameter is mandatory. In general, the format is

    JRequest::get<type>( <name>, <default>, <data-source> )

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

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

$integer = JRequest::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 = JRequest::getInt( 'myId', 12, 'COOKIE' );

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

Floating point number

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 = JRequest::getFloat( 'price' );

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

$float = JRequest::getFloat( 'total', 100.00, 'POST' );

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

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

$boolean = JRequest::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 = JRequest::getBool( 'hide', true, 'GET' );

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

Word

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

$word = JRequest::getWord( 'search-word' );

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

$word = JRequest::getWord( 'keyword', '', 'COOKIE' );

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

Command

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 = JRequest::getCmd( 'option' );

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

$command = JRequest::getCmd( 'controller', 'view', 'POST' );

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

String

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 = JRequest::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 = JRequest::getString( 'text', '', 'METHOD', JREQUEST_NOTRIM );

will retrieve the value of the "text" argument from the request.. The default value is an empty string. Leading and trailing whitespace will not be removed.

$string = JRequest::getString( 'template', '<html />', 'METHOD', JREQUEST_ALLOWHTML );

will retrieve the value of the "template" argument from the request. The default value is '<html />'. Leading and trailing whitespace will be removed, but HTML will be permitted.

Generic and other data types

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 JRequest::getVar method directly. The syntax is:

JRequest::getVar( <name>, <default>, <data-source>, <type>, <options> );

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 JRequest::getVar. If no default is specified and the argument is not present in the request variable then it will return undefined.
<data-source> specifies where the variable is to be retrieved from (one of GET, POST, METHOD, COOKIE, REQUEST, ENV, SERVER; default is REQUEST).
<type> specifies the data type expected (see below).
<options> an optional bit-field used to specify options for some of the input filters (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 JRequest::getInt.
FLOAT, DOUBLE Equivalent to JRequest::getFloat.
BOOL, BOOLEAN Equivalent to JRequest::getBool.
WORD Equivalent to JRequest::getWord.
ALNUM Allow only alphanumeric characters (a-z, A-Z, 0-9).
CMD Equivalent to JRequest::getCmd.
BASE64 Allow only those characters that could be present in a base64-encoded string (ie. a-z, A-Z, 0-9, /, + and =).
STRING Equivalent to JRequest::getString.
ARRAY Source is not filtered but is cast to array type.
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.
USERNAME Removes control characters (0x00 - 0x1F), 0x7F, <, >, ", ', % and &.

Filter options

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

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 = JRequest::getVar( '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

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 [JRequest/getVar|JRequest::getVar]] 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 JRequest::getVar 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 (same as the fourth argument of a JRequest::getVar call).

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', JRequest::getInt( 'id', 0 ) );

Constructing SQL queries

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 JRequest methods to do this as they are not SQL-aware.

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 [[Further information on SQL injection attacks can be found here: http://php.net/manual/en/security.database.sql-injection.php and here: JRequest::getInt.

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->getEscaped]] method directly. The following statements are equivalent:

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

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->getEscaped( $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 );

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.

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->NameQuote( $field-name ) . '=' . $db->quote( $field-value );

Securing forms

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&' . JUtility::getToken() . '=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:

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

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

JRequest::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 JRequest::checkToken will automatically redirect to the site front page.

Cleaning filesystem paths

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

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.

Dziękujemy za wkład

» Stefan Wajda [zwiastun],