return $item;
}
+ /**
+ * Register an alternative phar:// stream wrapper to filter out insecure Phars
+ *
+ * PHP makes it possible to trigger Object Injection vulnerabilities by using
+ * a side-effect of the phar:// stream wrapper that unserializes Phar
+ * metadata. To mitigate this vulnerability, projects such as TYPO3 and Drupal
+ * have implemented an alternative Phar stream wrapper that disallows
+ * inclusion of phar files based on certain parameters.
+ *
+ * This code attempts to register the TYPO3 Phar stream wrapper using the
+ * interceptor defined in \Civi\Core\Security\PharExtensionInterceptor. In an
+ * environment where the stream wrapper was already registered via
+ * \TYPO3\PharStreamWrapper\Manager (i.e. Drupal), this code does not do
+ * anything. In other environments (e.g. WordPress, at the time of this
+ * writing), the TYPO3 library is used to register the interceptor to mitigate
+ * the vulnerability.
+ */
+ private static function registerPharHandler() {
+ try {
+ // try to get the existing stream wrapper, registered e.g. by Drupal
+ \TYPO3\PharStreamWrapper\Manager::instance();
+ }
+ catch (\LogicException $e) {
+ if ($e->getCode() === 1535189872) {
+ // no phar stream wrapper was registered by \TYPO3\PharStreamWrapper\Manager.
+ // This means we're probably not on Drupal and need to register our own.
+ \TYPO3\PharStreamWrapper\Manager::initialize(
+ (new \TYPO3\PharStreamWrapper\Behavior())
+ ->withAssertion(new \Civi\Core\Security\PharExtensionInterceptor())
+ );
+ if (in_array('phar', stream_get_wrappers())) {
+ stream_wrapper_unregister('phar');
+ stream_wrapper_register('phar', \TYPO3\PharStreamWrapper\PharStreamWrapper::class);
+ }
+ } else {
+ // this is not an exception we can handle
+ throw $e;
+ }
+ }
+ }
+
/**
* Given a menu item, call the appropriate controller and return the response
*
$ids = new CRM_Core_IDS();
$ids->check($item);
+ self::registerPharHandler();
+
$config = CRM_Core_Config::singleton();
if ($config->userFramework == 'Joomla' && $item) {
$config->userFrameworkURLVar = 'task';
--- /dev/null
+<?php
+
+namespace Civi\Core\Security;
+
+use TYPO3\PharStreamWrapper\Assertable;
+use TYPO3\PharStreamWrapper\Helper;
+use TYPO3\PharStreamWrapper\Exception;
+
+/**
+ * An alternate PharExtensionInterceptor to support phar-based CLI tools.
+ *
+ * This is largely based on Drupal\Core\Security\PharExtensionInterceptor,
+ * originally licensed under GPL2+
+ *
+ * @see \TYPO3\PharStreamWrapper\Interceptor\PharExtensionInterceptor
+ */
+class PharExtensionInterceptor implements Assertable {
+
+ /**
+ * Determines whether phar file is allowed to execute.
+ *
+ * The phar file is allowed to execute if:
+ * - the base file name has a ".phar" suffix.
+ * - it is the CLI tool that has invoked the interceptor.
+ *
+ * @param string $path
+ * The path of the phar file to check.
+ * @param string $command
+ * The command being carried out.
+ *
+ * @return bool
+ * TRUE if the phar file is allowed to execute.
+ *
+ * @throws Exception
+ * Thrown when the file is not allowed to execute.
+ */
+ public function assert(string $path, string $command): bool {
+ if ($this->baseFileContainsPharExtension($path)) {
+ return TRUE;
+ }
+ throw new Exception(
+ sprintf(
+ 'Unexpected file extension in "%s"',
+ $path
+ ),
+ 1535198703
+ );
+ }
+
+ /**
+ * Determines if a path has a .phar extension or invoked execution.
+ *
+ * @param string $path
+ * The path of the phar file to check.
+ *
+ * @return bool
+ * TRUE if the file has a .phar extension or if the execution has been
+ * invoked by the phar file.
+ */
+ private function baseFileContainsPharExtension($path) {
+ $baseFile = Helper::determineBaseFile($path);
+ if ($baseFile === NULL) {
+ return FALSE;
+ }
+ // If the stream wrapper is registered by invoking a phar file that does
+ // not not have .phar extension then this should be allowed. For
+ // example, some CLI tools recommend removing the extension.
+ $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ // Find the last entry in the backtrace containing a 'file' key as
+ // sometimes the last caller is executed outside the scope of a file. For
+ // example, this occurs with shutdown functions.
+ do {
+ $caller = array_pop($backtrace);
+ } while (empty($caller['file']) && !empty($backtrace));
+ if (isset($caller['file']) && $baseFile === Helper::determineBaseFile($caller['file'])) {
+ return TRUE;
+ }
+ $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION);
+ return strtolower($fileExtension) === 'phar';
+ }
+
+}
"civicrm/composer-downloads-plugin": "^2.0",
"league/csv": "^9.2",
"tplaner/when": "~3.0.0",
- "xkerman/restricted-unserialize": "~1.1"
+ "xkerman/restricted-unserialize": "~1.1",
+ "typo3/phar-stream-wrapper": "^3.0"
},
"scripts": {
"post-install-cmd": [
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "9c8e8054f45d5bdd4e18f45701527d2b",
+ "content-hash": "94187b287c901c73a271a012b55ae19a",
"packages": [
{
"name": "adrienrn/php-mimetyper",
"version": "3.0.0+php53",
"dist": {
"type": "zip",
- "url": "https://github.com/tplaner/When/archive/c1ec099f421bff354cc5c929f83b94031423fc80.zip",
- "reference": null,
- "shasum": null
+ "url": "https://github.com/tplaner/When/archive/c1ec099f421bff354cc5c929f83b94031423fc80.zip"
},
"require": {
"php": ">=5.3.0"
"time"
]
},
+ {
+ "name": "typo3/phar-stream-wrapper",
+ "version": "v3.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/TYPO3/phar-stream-wrapper.git",
+ "reference": "e0c1b495cfac064f4f5c4bcb6bf67bb7f345ed04"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/TYPO3/phar-stream-wrapper/zipball/e0c1b495cfac064f4f5c4bcb6bf67bb7f345ed04",
+ "reference": "e0c1b495cfac064f4f5c4bcb6bf67bb7f345ed04",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "ext-xdebug": "*",
+ "phpunit/phpunit": "^6.5"
+ },
+ "suggest": {
+ "ext-fileinfo": "For PHP builtin file type guessing, otherwise uses internal processing"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "v3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "TYPO3\\PharStreamWrapper\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Interceptors for PHP's native phar:// stream handling",
+ "homepage": "https://typo3.org/",
+ "keywords": [
+ "phar",
+ "php",
+ "security",
+ "stream-wrapper"
+ ],
+ "time": "2019-12-10T11:53:27+00:00"
+ },
{
"name": "xkerman/restricted-unserialize",
"version": "1.1.12",