SoftwareSecurity2014/Group 12/Verdict

Uit Werkplaats
< SoftwareSecurity2014‎ | Group 12
Versie door Erik Poll (overleg | bijdragen) op 22 jun 2014 om 21:26 (V5.2 Not Sure)
(wijz) ← Oudere versie | Huidige versie (wijz) | Nieuwere versie → (wijz)
Ga naar: navigatie, zoeken

Verification requirements

Joomla consist of many PHP files, we have chosen a few files that are critical for our verification requirements: Input validation. We have reviewed Login.php, Registration.php, Search.php and Contact.php. (Erik:WHy are these files critical? Also, it would have been nice to get some impression of how much of the code base you covered with these files: are this 4 out of a dozen (critical) files, or 4 out of a hundred?)

V5.1 Requirement is met

Verify that the runtime environment is not susceptible to buffer overflows, or that security controls prevent buffer overflows.

Since the applications source code mainly consists of PHP, its runtime environment will not be susceptible to buffer overflows. Additionally, no external programs or vulnerable extensions are used.

V5.2 Not Sure

Verify that a positive validation pattern is defined and applied to all input. Data should be:

  • Strongly typed at all times
  • Length checked and fields length minimised
  • Range checked if a numeric
  • Unsigned unless required to be signed
  • Syntax or grammar should be checked prior to first use or inspection

OWASP Data Validation Definition

Field.php

In field.php the date is formatted in order to satisfy the syntax. As a result, no checking is required prior to first use.

if (trim(str_replace('-', '', $this->alias)) == '')
{
	$this->alias = JFactory::getDate()->format('Y-m-d-H-i-s');

}

Contact.php

The function _sendEmail retrieves the data from model and extracts the message data using a XML form. In the end, an email is sent.

private function _sendEmail($data, $contact)
{
	$app		= JFactory::getApplication();
	$params 	= JComponentHelper::getParams('com_contact');
	if ($contact->email_to == '' && $contact->user_id != 0)
	{
		$contact_user = JUser::getInstance($contact->user_id);
		$contact->email_to = $contact_user->get('email');
	}
	$mailfrom	= $app->getCfg('mailfrom');
	$fromname	= $app->getCfg('fromname');
	$sitename	= $app->getCfg('sitename');
	$copytext 	= JText::sprintf('COM_CONTACT_COPYTEXT_OF', $contact->name, $sitename);

	$name		= $data['contact_name'];
	$email		= $data['contact_email'];
	$subject	= $data['contact_subject'];
	$body		= $data['contact_message'];
        [...]

The terms contact_subject and contact_message are defined in contact.xml:

<field name="contact_subject"
type="text"
id="contact-emailmsg"
size="60"
description="COM_CONTACT_CONTACT_MESSAGE_SUBJECT_DESC"
label="COM_CONTACT_CONTACT_MESSAGE_SUBJECT_LABEL"
filter="string"
validate="contactemailsubject"
required="true"
/>
<field name="contact_message"
type="textarea"
cols="50"
rows="10"
id="contact-message"
description="COM_CONTACT_CONTACT_ENTER_MESSAGE_DESC"
label="COM_CONTACT_CONTACT_ENTER_MESSAGE_LABEL"
filter="htmlsafe"
validate="contactemailmessage"
required="true"
/>

The length of each component is defined within the XML file. However, no length checking occurs in the PHP source code. Note that a filter htmlsafe is applied on the contents of contact_message. We are not sure where the filter is defined. For validation the file ContactEmailMessage.php is evoked.

ContactMailMessage.php

ContactMailMessage.php contains only one method; scan the entire email body for banned words. (Erik: Do you have any idea of what these banned words are? Or is that something that can be configured? ) This indicates that black listing is used (negative validation pattern). banned_text is defined in form.xml.

public function test(&$element, $value, $group = null, &$input = null, &$form = null)
{
	$params = JComponentHelper::getParams('com_contact');
	$banned = $params->get('banned_text');

	foreach (explode(';', $banned) as $item) {
		if (JString::stristr($item, $value) !== false)
				return false;
	}

	return true;
}

Search.php

Joomla's search function provides the opportunity for an attacker to feed invalid input to the system. It is useful to have a look at the controller class of Joomla's search component, since in our opinion the controller should be responsible for taking care of input validation. The first part of the search function of this class is displayed below:

// slashes cause errors, <> get stripped anyway later on. # causes problems.
$badchars = array('#', '>', '<', '\\');
$searchword = trim(str_replace($badchars, '', $this->input->getString('searchword', null, 'post')));

Note that the developers used a negative validation approach here, because they specified a list of characters which should be replaced in the search word. We think the reason that positive validation is not applied is that it is infeasible to produce a whitelist representing all genuine search inputs. Thus, the search controller class does neither define nor apply a positive validation pattern to all input.

Registration.php

In registration.php there are 2 functions, activate() and register(). Activate() is the function that a user can use to activate their account and register() to register the account.
We take a look at the register() function. Before the function does anything, it will check for request forgeries and check if registration is disabled.

// Check for request forgeries.
JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));

// If registration is disabled - Redirect to login page.
if (JComponentHelper::getParams('com_users')->get('allowUserRegistration') == 0)
{
	$this->setRedirect(JRoute::_('index.php?option=com_users&view=login', false));
	return false;
}

When this succeeds we see that this function first validates the posted data before first use (Erik:Do you have any idea what this validation,by the call to model->validate below, actually validates? Or was that too hard to find in the code?) and pushes up to three warnings to the user.

$data	= $model->validate($form, $requestData);

// Check for validation errors.
if ($data === false)
{
	// Get the validation messages.
	$errors	= $model->getErrors();

	// Push up to three validation messages out to the user.
	for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
	{
		if ($errors[$i] instanceof Exception)
		{
			$app->enqueueMessage($errors[$i]->getMessage(), 'warning');
		} else {
			$app->enqueueMessage($errors[$i], 'warning');
		}
	}

	// Save the data in the session.
	$app->setUserState('com_users.registration.data', $requestData);

	// Redirect back to the registration screen.
	$this->setRedirect(JRoute::_('index.php?option=com_users&view=registration', false));
	return false;
}

If the data is valid, it wil try to save the data to the database. This function will return a user id on success or false on failure and will redirect the user back to the registration screen.

// Attempt to save the data.
$return	= $model->register($data);

When the data is saved succesfully, Joomla will clear the session data for security reasons.

// Flush the data from the session.
$app->setUserState('com_users.registration.data', null);

The register() function returns true on success and if anything goes wrong it will return false. We see that this function validates all data before using it and also checks for security vulnerabilities. If anything is not correct, the function will stop and return false.


The activate() function uses the same guidelines. It first checks if the user isn't logged in already and that registration or activation are disabled. It then checks if the token is in the valid format.

$token = $input->getAlnum('token');

// Check that the token is in a valid format.
if ($token === null || strlen($token) !== 32)
{
	JError::raiseError(403, JText::_('JINVALID_TOKEN'));
	return false;
}

If the token is in the valid format, we will call the model to try and activate the user.

// Attempt to activate the user.
$return = $model->activate($token);

The registration model will try and locate a userid based on the token, if this succeeds we can get the user information and activate the user.

// Get the user id based on the token.
$query = $db->getQuery(true);
$query->select($db->quoteName('id'))
	->from($db->quoteName('#__users'))
	->where($db->quoteName('activation') . ' = ' . $db->quote($token))
	->where($db->quoteName('block') . ' = ' . 1)
	->where($db->quoteName('lastvisitDate') . ' = ' . $db->quote($db->getNullDate()));
$db->setQuery($query);

try
{
	$userId = (int) $db->loadResult();
}
catch (RuntimeException $e)
{
	$this->setError(JText::sprintf('COM_USERS_DATABASE_ERROR', $e->getMessage()), 500);
	return false;
}

// Check for a valid user id.
if (!$userId)
{
	$this->setError(JText::_('COM_USERS_ACTIVATION_TOKEN_NOT_FOUND'));
	return false;
}

If any errors happen, Joomla informs the user by showing what happend.

// Check for errors.
if ($return === false)
{
	// Redirect back to the homepage.
	$this->setMessage(JText::sprintf('COM_USERS_REGISTRATION_SAVE_FAILED', $model->getError()), 'warning');
	$this->setRedirect('index.php');
	return false;
}

The activate() function returns true on success and false on failure. We see that this function validates the token before using it. If anything goes wrong, it will inform the user with an error message.

In the end, it is difficult to say if V5.2 is met. According to the definition of positive data validation (as described above), the code satisfies some of the points; not all points were able to be verified. However, considering the literal meaning of the requirement, we conclude that V5.2 is not met; white listing is not used for each input.

(Erik: Note that another place to observe that the code does not use a positive pattern (aka whitelisting) everywhere is to look at the code of the function clean you discuss further down: that function clearly applies a blacklist to validate usernames, as it strips some dangerous characters from usernames)

V5.3 Requirement is met

Verify that all input validation failures result in input rejection or input sanitisation.

The method test defined in ContactEmailMessage.php[1] will return false and thus fail if a banned word is encountered.
Both methods activate() and register() defined in Registration.php[2] will reject all the input if it is not in the valid form.


Login.php

Login.php is a large group of files, it took a while to find the important part of the code. The default_login.php containts the following line of code:

<form action="<?php echo JRoute::_('index.php?option=com_users&task=user.login'); ?>" method="post" class="form-horizontal">

after looking around in the user component we found the correct login function in user.php:

$data = array();
$data['return'] = base64_decode($app->input->post->get('return', '', 'BASE64'));
$data['username'] = JRequest::getVar('username', '', 'method', 'username');
$data['password'] = JRequest::getString('password', '', 'post', JREQUEST_ALLOWRAW);

// Set the return URL if empty.
if (empty($data['return']))
{
	$data['return'] = 'index.php?option=com_users&view=profile';
}

The data is checked if it is empty. then another array is filled with the information and passed to a checking function:

// Get the log in credentials.
$credentials = array();
$credentials['username'] = $data['username'];
$credentials['password'] = $data['password'];
// Perform the log in.
if (true === $app->login($credentials, $options))
{
			// Success
}
else
{
			// Login failed !
}

in application.php the data gets passed to the parent:

parent::login($credentials, $options);

It is unclear what happens next. We managed to find a file in \plugins\authentication\joomla. Which is intended to handle all authentication. We have not seen it being referred to. However this function does do the authentication correctly and we suppose this is used for every login action.

Search.php

A more thorough inspection of the JInputFilter class brings us to the following interesting line of code:

$post['areas'][] = JFilterInput::getInstance()->clean($area, 'cmd');

The JFilterInput class seems to be an important input validation class which is why we inspected this class as well. The class enables the user to define a list of permitted tags and attributes which is used to filter the unsanitized input. The class also expresses pre-defined blacklisted tags and tag attributes. We noticed that Joomla enables the developer to choose between a whitelist and blacklist input filtering approach. This means that the type of input validation (e.g., positive or negative validation) can be determined by the developer. By default, a whitelist approach is used which is to be preferred to a blacklist approach since the whitelist approach often provides a higher level of security.

The function clean attempts to avoid cross-site scripting attacks by sanitizing the input and removing specified bad code. The caller may provide a type argument indicating the return type of the variable. Depending on this specified type, input is stripped, removed or replaced. For most return types a blacklist approach is used. Additionally, for regularly used input types (e.g., HTML and strings) all unwanted tags and attributes are removed. Since the method to validate (e.g., whitelisting or blacklisting) is up to the developer, a positive validation pattern may be defined and applied to all input.

As we noted earlier, the clean function of the JFilterInput distinghuishes several return types. The case for the integer or int return type is listed below:

case 'INT':
case 'INTEGER':
        // Only use the first integer value
	preg_match('/-?[0-9]+/', (string) $source, $matches);
	$result = @ (int) $matches[0];
	break;

We consulted the PHP documentation (in particular: the preg_match function documentation) to conclude that the variable matches[0] will contain the text that matches the full pattern. The code above is thus essentially grabbing only the first occurrence of an int(eger).

In a different return type case we observe the execution of the preg_replace() function which clearly illustrates that in the case of input validation failure (e.g., the input conforms to the specified regular expression) the unexpected characters are replaced by the empty string. Therefore, input is sanitized.

V5.4 Requirement is met

Verify that a character set, such as UTF-8, is specified for all sources of input.

Joomla uses UTF-8 since version 1.5. Additionally, the encoding is defined in the XML files:

<?xml version="1.0" encoding="UTF-8"?>

The file ascii.php handles the usage of ASCII in UTF-8.

V5.5 Requirement is met

Verify that all input validation is performed on the server side.

Joomla's source code is stored and run on the server; validation is thus performed server-side. Joomla, however, allows form elements to be validated client-side before the form is submitted to the server. Running client-side validation on top of server-side is a plus, but not required.

V5.6 Requirement is met

Verify that a single input validation control is used by the application for each type of data that is accepted.

The function clean() in libraries/joomla/filter/input.php removes bad code from the input. For each data type a separate single input validation control is applied. (Erik: It does indeed seem that input validation is done in a structureed way, using this clean function. But in your discussion of registration.php you mention that a function mode->validate is used: does that function ultimately call down to clean? Or does that validate function do a different kind of input validation? )

public function clean($source, $type = 'string')
	{
		// Handle the type constraint
		switch (strtoupper($type))
		{
			case 'INT':
			case 'INTEGER':
				// Only use the first integer value
				preg_match('/-?[0-9]+/', (string) $source, $matches);
				$result = @ (int) $matches[0];
				break;

			case 'UINT':
				// Only use the first integer value
				preg_match('/-?[0-9]+/', (string) $source, $matches);
				$result = @ abs((int) $matches[0]);
				break;

			case 'FLOAT':
			case 'DOUBLE':
				// Only use the first floating point value
				preg_match('/-?[0-9]+(\.[0-9]+)?/', (string) $source, $matches);
				$result = @ (float) $matches[0];
				break;

			case 'BOOL':
			case 'BOOLEAN':
				$result = (bool) $source;
				break;

			case 'WORD':
				$result = (string) preg_replace('/[^A-Z_]/i', '', $source);
				break;

			case 'ALNUM':
				$result = (string) preg_replace('/[^A-Z0-9]/i', '', $source);
				break;

			case 'CMD':
				$result = (string) preg_replace('/[^A-Z0-9_\.-]/i', '', $source);
				$result = ltrim($result, '.');
				break;

			case 'BASE64':
				$result = (string) preg_replace('/[^A-Z0-9\/+=]/i', '', $source);
				break;

			case 'STRING':
				$result = (string) $this->_remove($this->_decode((string) $source));
				break;

			case 'HTML':
				$result = (string) $this->_remove((string) $source);
				break;

			case 'ARRAY':
				$result = (array) $source;
				break;

			case 'PATH':
				$pattern = '/^[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/';
				preg_match($pattern, (string) $source, $matches);
				$result = @ (string) $matches[0];
				break;

			case 'USERNAME':
				$result = (string) preg_replace('/[\x00-\x1F\x7F<>"\'%&]/', '', $source);
				break;

			case 'RAW':
				$result = $source;
				break;
[...]

V5.7 Requirement is not met

Verify that all input validation failures are logged.

Joomla does not log validation failures by default. No indications of logging are found in the source code. It is possible to enable logging features through third party plugins and extensions. In addition, Joomla has built-in logging functionality (e.g., JLog). However, it is unclear if logging is possible for validation errors.