annotate vendor/typo3/phar-stream-wrapper/README.md @ 19:fa3358dc1485 tip

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