Projet

Général

Profil

Paste
Télécharger (8,36 ko) Statistiques
| Branche: | Révision:

root / drupal7 / misc / typo3 / phar-stream-wrapper / README.md @ 6b24a280

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

    
9
Based 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)
11
allowing to hide Phar files inside valid image resources, the TYPO3 project
12
decided back then to introduce a `PharStreamWrapper` to intercept invocations
13
of the `phar://` stream in PHP and only allow usage for defined locations in
14
the file system.
15

    
16
Since the TYPO3 mission statement is **inspiring people to share**, we thought
17
it would be helpful for others to release our `PharStreamWrapper` as standalone
18
package to the PHP community.
19

    
20
The mentioned security issue was reported to TYPO3 on 10th June 2018 by Sam Thomas
21
and 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
23
July 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

    
33
In general the TYPO3 core is released under the GNU General Public License version
34
2 or any later version (`GPL-2.0-or-later`). In order to avoid licensing issues and
35
incompatibilities this `PharStreamWrapper` is licenced under the MIT License. In case
36
you duplicate or modify source code, credits are not required but really appreciated.
37

    
38
## Credits
39

    
40
Thanks to [Alex Pott](https://github.com/alexpott), Drupal for creating
41
back-ports of all sources in order to provide compatibility with PHP v5.3.
42

    
43
## Installation
44

    
45
The `PharStreamWrapper` is provided as composer package `typo3/phar-stream-wrapper`
46
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).
47

    
48
### Installation for PHP v7.0
49

    
50
```
51
composer require typo3/phar-stream-wrapper ^3.0
52
```
53

    
54
### Installation for PHP v5.3
55

    
56
```
57
composer require typo3/phar-stream-wrapper ^2.0
58
```
59

    
60
## Example
61

    
62
The following example is bundled within this package, the shown
63
`PharExtensionInterceptor` denies all stream wrapper invocations files
64
not having the `.phar` suffix. Interceptor logic has to be individual and
65
adjusted to according requirements.
66

    
67
```
68
$behavior = new \TYPO3\PharStreamWrapper\Behavior();
69
\TYPO3\PharStreamWrapper\Manager::initialize(
70
    $behavior->withAssertion(new PharExtensionInterceptor())
71
);
72

    
73
if (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

    
98
The following interceptor is shipped with the package and ready to use in order
99
to block any Phar invocation of files not having a `.phar` suffix. Besides that
100
individual interceptors are possible of course.
101

    
102
```
103
class 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

    
145
This interceptor combines multiple interceptors implementing `Assertable`.
146
It 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

    
160
This (basic) interceptor just checks whether the invoked Phar archive has
161
an according `.phar` file extension. Resolving symbolic links as well as
162
Phar 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

    
173
This interceptor is actually checking serialized Phar meta-data against
174
PHP objects and would consider a Phar archive malicious in case not only
175
scalar values are found. A custom low-level `Phar\Reader` is used in order to
176
avoid 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');
203
var_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

    
219
In 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).