annotate vendor/typo3/phar-stream-wrapper/README.md @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents a9cd425dd02b
children
rev   line source
Chris@4 1 [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/badges/quality-score.png?b=v2)](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/?branch=v2)
Chris@4 2 [![Travis CI Build Status](https://travis-ci.org/TYPO3/phar-stream-wrapper.svg?branch=v2)](https://travis-ci.org/TYPO3/phar-stream-wrapper)
Chris@4 3
Chris@4 4 # PHP Phar Stream Wrapper
Chris@4 5
Chris@4 6 ## Abstract & History
Chris@4 7
Chris@4 8 Based on Sam Thomas' findings concerning
Chris@4 9 [insecure deserialization in combination with obfuscation strategies](https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are)
Chris@4 10 allowing to hide Phar files inside valid image resources, the TYPO3 project
Chris@4 11 decided back then to introduce a `PharStreamWrapper` to intercept invocations
Chris@4 12 of the `phar://` stream in PHP and only allow usage for defined locations in
Chris@4 13 the file system.
Chris@4 14
Chris@4 15 Since the TYPO3 mission statement is **inspiring people to share**, we thought
Chris@4 16 it would be helpful for others to release our `PharStreamWrapper` as standalone
Chris@4 17 package to the PHP community.
Chris@4 18
Chris@4 19 The mentioned security issue was reported to TYPO3 on 10th June 2018 by Sam Thomas
Chris@4 20 and has been addressed concerning the specific attack vector and for this generic
Chris@4 21 `PharStreamWrapper` in TYPO3 versions 7.6.30 LTS, 8.7.17 LTS and 9.3.1 on 12th
Chris@4 22 July 2018.
Chris@4 23
Chris@4 24 * https://typo3.org/security/advisory/typo3-core-sa-2018-002/
Chris@4 25 * https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are
Chris@4 26 * https://youtu.be/GePBmsNJw6Y
Chris@4 27
Chris@4 28 ## License
Chris@4 29
Chris@4 30 In general the TYPO3 core is released under the GNU General Public License version
Chris@4 31 2 or any later version (`GPL-2.0-or-later`). In order to avoid licensing issues and
Chris@4 32 incompatibilities this `PharStreamWrapper` is licenced under the MIT License. In case
Chris@4 33 you duplicate or modify source code, credits are not required but really appreciated.
Chris@4 34
Chris@4 35 ## Credits
Chris@4 36
Chris@4 37 Thanks to [Alex Pott](https://github.com/alexpott), Drupal for creating
Chris@4 38 back-ports of all sources in order to provide compatibility with PHP v5.3.
Chris@4 39
Chris@4 40 ## Installation
Chris@4 41
Chris@4 42 The `PharStreamWrapper` is provided as composer package `typo3/phar-stream-wrapper`
Chris@4 43 and has minimum requirements of PHP v5.3 ([`v2`](https://github.com/TYPO3/phar-stream-wrapper/tree/v2) branch) and PHP v7.0 ([`master`](https://github.com/TYPO3/phar-stream-wrapper) branch).
Chris@4 44
Chris@4 45 ### Installation for PHP v7.0
Chris@4 46
Chris@4 47 ```
Chris@4 48 composer require typo3/phar-stream-wrapper ^3.0
Chris@4 49 ```
Chris@4 50
Chris@4 51 ### Installation for PHP v5.3
Chris@4 52
Chris@4 53 ```
Chris@4 54 composer require typo3/phar-stream-wrapper ^2.0
Chris@4 55 ```
Chris@4 56
Chris@4 57 ## Example
Chris@4 58
Chris@4 59 The following example is bundled within this package, the shown
Chris@4 60 `PharExtensionInterceptor` denies all stream wrapper invocations files
Chris@4 61 not having the `.phar` suffix. Interceptor logic has to be individual and
Chris@4 62 adjusted to according requirements.
Chris@4 63
Chris@4 64 ```
Chris@4 65 $behavior = new \TYPO3\PharStreamWrapper\Behavior();
Chris@5 66 \TYPO3\PharStreamWrapper\Manager::initialize(
Chris@4 67 $behavior->withAssertion(new PharExtensionInterceptor())
Chris@4 68 );
Chris@4 69
Chris@4 70 if (in_array('phar', stream_get_wrappers())) {
Chris@4 71 stream_wrapper_unregister('phar');
Chris@4 72 stream_wrapper_register('phar', 'TYPO3\\PharStreamWrapper\\PharStreamWrapper');
Chris@4 73 }
Chris@4 74 ```
Chris@4 75
Chris@4 76 * `PharStreamWrapper` defined as class reference will be instantiated each time
Chris@4 77 `phar://` streams shall be processed.
Chris@4 78 * `Manager` as singleton pattern being called by `PharStreamWrapper` instances
Chris@4 79 in order to retrieve individual behavior and settings.
Chris@4 80 * `Behavior` holds reference to interceptor(s) that shall assert correct/allowed
Chris@4 81 invocation of a given `$path` for a given `$command`. Interceptors implement
Chris@4 82 the interface `Assertable`. Interceptors can act individually on following
Chris@4 83 commands or handle all of them in case not defined specifically:
Chris@4 84 + `COMMAND_DIR_OPENDIR`
Chris@4 85 + `COMMAND_MKDIR`
Chris@4 86 + `COMMAND_RENAME`
Chris@4 87 + `COMMAND_RMDIR`
Chris@4 88 + `COMMAND_STEAM_METADATA`
Chris@4 89 + `COMMAND_STREAM_OPEN`
Chris@4 90 + `COMMAND_UNLINK`
Chris@4 91 + `COMMAND_URL_STAT`
Chris@4 92
Chris@5 93 ## Interceptors
Chris@4 94
Chris@4 95 The following interceptor is shipped with the package and ready to use in order
Chris@4 96 to block any Phar invocation of files not having a `.phar` suffix. Besides that
Chris@4 97 individual interceptors are possible of course.
Chris@4 98
Chris@4 99 ```
Chris@4 100 class PharExtensionInterceptor implements Assertable
Chris@4 101 {
Chris@4 102 /**
Chris@4 103 * Determines whether the base file name has a ".phar" suffix.
Chris@4 104 *
Chris@4 105 * @param string $path
Chris@4 106 * @param string $command
Chris@4 107 * @return bool
Chris@4 108 * @throws Exception
Chris@4 109 */
Chris@4 110 public function assert($path, $command)
Chris@4 111 {
Chris@4 112 if ($this->baseFileContainsPharExtension($path)) {
Chris@4 113 return true;
Chris@4 114 }
Chris@4 115 throw new Exception(
Chris@4 116 sprintf(
Chris@4 117 'Unexpected file extension in "%s"',
Chris@4 118 $path
Chris@4 119 ),
Chris@4 120 1535198703
Chris@4 121 );
Chris@4 122 }
Chris@4 123
Chris@4 124 /**
Chris@4 125 * @param string $path
Chris@4 126 * @return bool
Chris@4 127 */
Chris@4 128 private function baseFileContainsPharExtension($path)
Chris@4 129 {
Chris@4 130 $baseFile = Helper::determineBaseFile($path);
Chris@4 131 if ($baseFile === null) {
Chris@4 132 return false;
Chris@4 133 }
Chris@4 134 $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION);
Chris@4 135 return strtolower($fileExtension) === 'phar';
Chris@4 136 }
Chris@4 137 }
Chris@4 138 ```
Chris@4 139
Chris@5 140 ### ConjunctionInterceptor
Chris@5 141
Chris@5 142 This interceptor combines multiple interceptors implementing `Assertable`.
Chris@5 143 It succeeds when all nested interceptors succeed as well (logical `AND`).
Chris@5 144
Chris@5 145 ```
Chris@5 146 $behavior = new \TYPO3\PharStreamWrapper\Behavior();
Chris@5 147 \TYPO3\PharStreamWrapper\Manager::initialize(
Chris@5 148 $behavior->withAssertion(new ConjunctionInterceptor(array(
Chris@5 149 new PharExtensionInterceptor(),
Chris@5 150 new PharMetaDataInterceptor()
Chris@5 151 )))
Chris@5 152 );
Chris@5 153 ```
Chris@5 154
Chris@5 155 ### PharExtensionInterceptor
Chris@5 156
Chris@5 157 This (basic) interceptor just checks whether the invoked Phar archive has
Chris@5 158 an according `.phar` file extension. Resolving symbolic links as well as
Chris@5 159 Phar internal alias resolving are considered as well.
Chris@5 160
Chris@5 161 ```
Chris@5 162 $behavior = new \TYPO3\PharStreamWrapper\Behavior();
Chris@5 163 \TYPO3\PharStreamWrapper\Manager::initialize(
Chris@5 164 $behavior->withAssertion(new PharExtensionInterceptor())
Chris@5 165 );
Chris@5 166 ```
Chris@5 167
Chris@5 168 ### PharMetaDataInterceptor
Chris@5 169
Chris@5 170 This interceptor is actually checking serialized Phar meta-data against
Chris@5 171 PHP objects and would consider a Phar archive malicious in case not only
Chris@5 172 scalar values are found. A custom low-level `Phar\Reader` is used in order to
Chris@5 173 avoid using PHP's `Phar` object which would trigger the initial vulnerability.
Chris@5 174
Chris@5 175 ```
Chris@5 176 $behavior = new \TYPO3\PharStreamWrapper\Behavior();
Chris@5 177 \TYPO3\PharStreamWrapper\Manager::initialize(
Chris@5 178 $behavior->withAssertion(new PharMetaDataInterceptor())
Chris@5 179 );
Chris@5 180 ```
Chris@5 181
Chris@5 182 ## Reader
Chris@5 183
Chris@5 184 * `Phar\Reader::__construct(string $fileName)`: Creates low-level reader for Phar archive
Chris@5 185 * `Phar\Reader::resolveContainer(): Phar\Container`: Resolves model representing Phar archive
Chris@5 186 * `Phar\Container::getStub(): Phar\Stub`: Resolves (plain PHP) stub section of Phar archive
Chris@5 187 * `Phar\Container::getManifest(): Phar\Manifest`: Resolves parsed Phar archive manifest as
Chris@5 188 documented at http://php.net/manual/en/phar.fileformat.manifestfile.php
Chris@5 189 * `Phar\Stub::getMappedAlias(): string`: Resolves internal Phar archive alias defined in stub
Chris@5 190 using `Phar::mapPhar('alias.phar')` - actually the plain PHP source is analyzed here
Chris@5 191 * `Phar\Manifest::getAlias(): string` - Resolves internal Phar archive alias defined in manifest
Chris@5 192 using `Phar::setAlias('alias.phar')`
Chris@5 193 * `Phar\Manifest::getMetaData(): string`: Resolves serialized Phar archive meta-data
Chris@5 194 * `Phar\Manifest::deserializeMetaData(): mixed`: Resolves deserialized Phar archive meta-data
Chris@5 195 containing only scalar values - in case an object is determined, an according
Chris@5 196 `Phar\DeserializationException` will be thrown
Chris@5 197
Chris@5 198 ```
Chris@5 199 $reader = new Phar\Reader('example.phar');
Chris@5 200 var_dump($reader->resolveContainer()->getManifest()->deserializeMetaData());
Chris@5 201 ```
Chris@5 202
Chris@4 203 ## Helper
Chris@4 204
Chris@5 205 * `Helper::determineBaseFile(string $path): string`: Determines base file that can be
Chris@4 206 accessed using the regular file system. For instance the following path
Chris@4 207 `phar:///home/user/bundle.phar/content.txt` would be resolved to
Chris@4 208 `/home/user/bundle.phar`.
Chris@4 209 * `Helper::resetOpCache()`: Resets PHP's OPcache if enabled as work-around for
Chris@4 210 issues in `include()` or `require()` calls and OPcache delivering wrong
Chris@4 211 results. More details can be found in PHP's bug tracker, for instance like
Chris@4 212 https://bugs.php.net/bug.php?id=66569
Chris@4 213
Chris@4 214 ## Security Contact
Chris@4 215
Chris@4 216 In case of finding additional security issues in the TYPO3 project or in this
Chris@4 217 `PharStreamWrapper` package in particular, please get in touch with the
Chris@4 218 [TYPO3 Security Team](mailto:security@typo3.org).