Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strengthen input validation of component names #1524

Merged
merged 5 commits into from
Jul 15, 2021

Conversation

pascal-meunier
Copy link
Contributor

The path used with require_once may be invalid or manipulated with dots due to missing or incorrect input validation, or the file may be unreadable.

The path used with require_once may be invalid or manipulated with dots due to missing or incorrect input validation,  or the file may be unreadable.
@zooley
Copy link
Contributor

zooley commented Jun 29, 2021

"... or manipulated with dots due to missing or incorrect input validation ..."

The path is derived from the model's name which is either a set property of the controller or derived by singularizing the controller's name and, thus, no external inputs are accepted or should even involved in the process.

@pascal-meunier
Copy link
Contributor Author

Hi Shawn, the API infers the component from the URL which results in an observable and demonstrated exception when the URL contains dots at the right place, as it tries to open the component "com_..".

The path is derived from the model's name which is either a set property of the controller or derived by singularizing the controller's name and, thus, no external inputs are accepted or should even involved in the process.

@pascal-meunier
Copy link
Contributor Author

In case I'm not being clear, this patch fixes an exception triggered by the API when the URL contains '..' where the component name is expected.

@zooley
Copy link
Contributor

zooley commented Jun 29, 2021

That's a problem that should be handled via the component loader. That is, the check shouldn't be needed as the system shouldn't ever allow it to reach that point. The real issue seems to be here:

$option = preg_replace('/[^A-Z0-9_\.-]/i', '', $option);

I can't think of any reason why periods would be allowed in component names. Probably not even dashes and should just be [^A-Z0-9_] but that's less an issue. Then check if the resulting option is just 'com_':

	public function canonical($option)
	{
		if (is_array($option))
		{
			$option = implode('', $option);
		}
		$option = preg_replace('/[^A-Z0-9_-]/i', '', $option);
		if (substr($option, 0, strlen('com_')) != 'com_')
		{
			$option = 'com_' . $option;
		}
		if ($option == 'com_')
		{
			$option = '';
		}
		return $option;
	}

There's a secondary issue that these lines:

$option = $this->canonical($option);

$option = $this->canonical($option);

Need to be moved up to right before the if (empty($option)) checks.

		$option = $this->canonical($option);

		if (empty($option))
		{

That should guard against invalid component names. Then it should fall through to the "is enabled" checks. There is a flag for strict checking (i.e., if it isn't in the database and enabled, throw a 404) but the component loader isn't checking with that flag purely because it was easier to develop without it (drop a component in place with no DB entry and it still loads). That may simply have to be enforced going forward.

if ($this->isEnabled($option)) should probably be if ($this->isEnabled($option, true))

Filter input before checking if the remaining input is empty, as suggested by Shawn.
Remove dots from component names;  filter input before checking if the remaining input is empty, as suggested by Shawn.
@pascal-meunier pascal-meunier changed the title Checks before require in ApiController.php Strengthen input validation of component names Jun 29, 2021
@pascal-meunier
Copy link
Contributor Author

Thank you Shawn, I made the changes you suggested instead of my initial ones. I'll keep the idea of the strict checking for when we are ready to verify it won't break anything in production hubs.

$option = preg_replace('/[^A-Z0-9_-]/i', '', $option);
// prepend com_ to the name if it doesn't start with com_
// if option became empty due to the filtering, return an empty string
if ((strlen($option) > 0) && (substr($option, 0, strlen('com_')) != 'com_'))
Copy link
Contributor

@zooley zooley Jun 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may still want to add the following after this if state:

if ($option == 'com_')
{
	$option = '';
}

If I call {host}/api/index.php?option=com_.., the periods will be filtered and the resulting $option is just "com_" which will pass the if (empty($option)) check.

@pascal-meunier
Copy link
Contributor Author

Would it be wrong to abort inside that function and so reduce complexity and save resources, as in:

if ((strlen($option) == 0) || ($option == 'com_')) $this->app->abort(404, $lang->translate('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'));
if (substr($option, 0, strlen('com_')) != 'com_')
{
	$option = 'com_' . $option;
}

@zooley
Copy link
Contributor

zooley commented Jun 30, 2021

I wouldn't. That method is used in a variety of places to sanitize $option values and there may be cases where some piece of code is doing checks or whatever and you don't want a 404 getting thrown in the middle of it. A bit of a separation of tasks issue. The canonical method's sole purpose is to sanitize and normalize a given string.

A component named "com_" (and nothing else after) is an invalid name.
@pascal-meunier
Copy link
Contributor Author

Thank you Shawn.

@erichhuebner erichhuebner merged commit 256a395 into dev Jul 15, 2021
@pascal-meunier pascal-meunier deleted the pascal-meunier-require-checks-1 branch August 16, 2021 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants