• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..21-Jul-2021-

src/H21-Jul-2021-2,4211,362

LICENSEH A D21-Jul-20211.1 KiB2217

README.mdH A D21-Jul-20218.4 KiB222180

composer.jsonH A D21-Jul-2021824 3130

README.md

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