Projet

Général

Profil

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

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

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

    
4
# PHP Phar Stream Wrapper
5

    
6
## Abstract & History
7

    
8
Based on Sam Thomas' findings concerning
9
[insecure deserialization in combination with obfuscation strategies](https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are)
10
allowing to hide Phar files inside valid image resources, the TYPO3 project
11
decided back then to introduce a `PharStreamWrapper` to intercept invocations
12
of the `phar://` stream in PHP and only allow usage for defined locations in
13
the file system.
14

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

    
19
The mentioned security issue was reported to TYPO3 on 10th June 2018 by Sam Thomas
20
and has been addressed concerning the specific attack vector and for this generic
21
`PharStreamWrapper` in TYPO3 versions 7.6.30 LTS, 8.7.17 LTS and 9.3.1 on 12th
22
July 2018.
23

    
24
* https://typo3.org/security/advisory/typo3-core-sa-2018-002/
25
* https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are
26
* https://youtu.be/GePBmsNJw6Y
27

    
28
## License
29

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

    
35
## Credits
36

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

    
40
## Installation
41

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

    
45
### Installation for PHP v7.0
46

    
47
```
48
composer require typo3/phar-stream-wrapper ^3.0
49
```
50

    
51
### Installation for PHP v5.3
52

    
53
```
54
composer require typo3/phar-stream-wrapper ^2.0
55
```
56

    
57
## Example
58

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

    
64
```
65
$behavior = new \TYPO3\PharStreamWrapper\Behavior();
66
\TYPO3\PharStreamWrapper\Manager::initialize(
67
    $behavior->withAssertion(new PharExtensionInterceptor())
68
);
69

    
70
if (in_array('phar', stream_get_wrappers())) {
71
    stream_wrapper_unregister('phar');
72
    stream_wrapper_register('phar', 'TYPO3\\PharStreamWrapper\\PharStreamWrapper');
73
}
74
```
75

    
76
* `PharStreamWrapper` defined as class reference will be instantiated each time
77
  `phar://` streams shall be processed.
78
* `Manager` as singleton pattern being called by `PharStreamWrapper` instances
79
  in order to retrieve individual behavior and settings.
80
* `Behavior` holds reference to interceptor(s) that shall assert correct/allowed
81
  invocation of a given `$path` for a given `$command`. Interceptors implement
82
  the interface `Assertable`. Interceptors can act individually on following
83
  commands or handle all of them in case not defined specifically:  
84
  + `COMMAND_DIR_OPENDIR`
85
  + `COMMAND_MKDIR`
86
  + `COMMAND_RENAME`
87
  + `COMMAND_RMDIR`
88
  + `COMMAND_STEAM_METADATA`
89
  + `COMMAND_STREAM_OPEN`
90
  + `COMMAND_UNLINK`
91
  + `COMMAND_URL_STAT`
92

    
93
## Interceptors
94

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

    
99
```
100
class PharExtensionInterceptor implements Assertable
101
{
102
    /**
103
     * Determines whether the base file name has a ".phar" suffix.
104
     *
105
     * @param string $path
106
     * @param string $command
107
     * @return bool
108
     * @throws Exception
109
     */
110
    public function assert($path, $command)
111
    {
112
        if ($this->baseFileContainsPharExtension($path)) {
113
            return true;
114
        }
115
        throw new Exception(
116
            sprintf(
117
                'Unexpected file extension in "%s"',
118
                $path
119
            ),
120
            1535198703
121
        );
122
    }
123

    
124
    /**
125
     * @param string $path
126
     * @return bool
127
     */
128
    private function baseFileContainsPharExtension($path)
129
    {
130
        $baseFile = Helper::determineBaseFile($path);
131
        if ($baseFile === null) {
132
            return false;
133
        }
134
        $fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION);
135
        return strtolower($fileExtension) === 'phar';
136
    }
137
}
138
```
139

    
140
### ConjunctionInterceptor
141

    
142
This interceptor combines multiple interceptors implementing `Assertable`.
143
It succeeds when all nested interceptors succeed as well (logical `AND`).
144

    
145
```
146
$behavior = new \TYPO3\PharStreamWrapper\Behavior();
147
\TYPO3\PharStreamWrapper\Manager::initialize(
148
    $behavior->withAssertion(new ConjunctionInterceptor(array(
149
        new PharExtensionInterceptor(),
150
        new PharMetaDataInterceptor()
151
    )))
152
);
153
```
154

    
155
### PharExtensionInterceptor
156

    
157
This (basic) interceptor just checks whether the invoked Phar archive has
158
an according `.phar` file extension. Resolving symbolic links as well as
159
Phar internal alias resolving are considered as well.
160

    
161
```
162
$behavior = new \TYPO3\PharStreamWrapper\Behavior();
163
\TYPO3\PharStreamWrapper\Manager::initialize(
164
    $behavior->withAssertion(new PharExtensionInterceptor())
165
);
166
```
167

    
168
### PharMetaDataInterceptor
169

    
170
This interceptor is actually checking serialized Phar meta-data against
171
PHP objects and would consider a Phar archive malicious in case not only
172
scalar values are found. A custom low-level `Phar\Reader` is used in order to
173
avoid using PHP's `Phar` object which would trigger the initial vulnerability.
174

    
175
```
176
$behavior = new \TYPO3\PharStreamWrapper\Behavior();
177
\TYPO3\PharStreamWrapper\Manager::initialize(
178
    $behavior->withAssertion(new PharMetaDataInterceptor())
179
);
180
```
181

    
182
## Reader
183

    
184
* `Phar\Reader::__construct(string $fileName)`: Creates low-level reader for Phar archive
185
* `Phar\Reader::resolveContainer(): Phar\Container`: Resolves model representing Phar archive
186
* `Phar\Container::getStub(): Phar\Stub`: Resolves (plain PHP) stub section of Phar archive
187
* `Phar\Container::getManifest(): Phar\Manifest`: Resolves parsed Phar archive manifest as
188
  documented at http://php.net/manual/en/phar.fileformat.manifestfile.php
189
* `Phar\Stub::getMappedAlias(): string`: Resolves internal Phar archive alias defined in stub
190
  using `Phar::mapPhar('alias.phar')` - actually the plain PHP source is analyzed here
191
* `Phar\Manifest::getAlias(): string` - Resolves internal Phar archive alias defined in manifest
192
  using `Phar::setAlias('alias.phar')`
193
* `Phar\Manifest::getMetaData(): string`: Resolves serialized Phar archive meta-data
194
* `Phar\Manifest::deserializeMetaData(): mixed`: Resolves deserialized Phar archive meta-data
195
  containing only scalar values - in case an object is determined, an according
196
  `Phar\DeserializationException` will be thrown
197

    
198
```
199
$reader = new Phar\Reader('example.phar');
200
var_dump($reader->resolveContainer()->getManifest()->deserializeMetaData());
201
```
202

    
203
## Helper
204

    
205
* `Helper::determineBaseFile(string $path): string`: Determines base file that can be
206
  accessed using the regular file system. For instance the following path
207
  `phar:///home/user/bundle.phar/content.txt` would be resolved to
208
  `/home/user/bundle.phar`.
209
* `Helper::resetOpCache()`: Resets PHP's OPcache if enabled as work-around for
210
  issues in `include()` or `require()` calls and OPcache delivering wrong
211
  results. More details can be found in PHP's bug tracker, for instance like
212
  https://bugs.php.net/bug.php?id=66569
213

    
214
## Security Contact
215

    
216
In case of finding additional security issues in the TYPO3 project or in this
217
`PharStreamWrapper` package in particular, please get in touch with the
218
[TYPO3 Security Team](mailto:security@typo3.org).