Problem/Motivation
In #3026386: Drush fatal error after upgrading to 8.6.6, 8.5.9, or 7.62: PHP Fatal error: Uncaught TYPO3\PharStreamWrapper\Exception and Slack, multiple users reported that their GeoIP installation was broken after updating Drupal to 7.62. All users were using the phar version of the GeoIP library.
From @jlockhart:
This doesn't appear to be limited to Drush. While it fixed one of my sites I'm still seeing the error on 2 others. In those cases its with the geoip module.
Warning: Uncaught TYPO3\PharStreamWrapper\Exception: Unexpected file extension in "phar://geoip2.phar/vendor/autoload.php"
I've applied the patch from #32 for a Drupal 7, PHP 7.1
Steps to Reproduce
TODO
Proposed resolution
Improve the handling of paths in typo3/phar-stream-wrapper and update core/composer.json and core/composer.lock with the next version of that library.
Remaining tasks
The issue branched into working on typo3/phar-stream-wrapper per @alexpott's comments in #48:
- Open a PR to backport changes to https://github.com/TYPO3/phar-stream-wrapper/ master to the v2 branch - we need the v2 branch for PHP5 support
- Improve PHAR alias handling: typo3/phar-stream-wrapper Pull Request #12: DONE
- Back port #12 to v2: typo3/phar-stream-wrapper Pull Request #15: DONE
- Update the issue summary with clear reproduction steps from comments and reports from people in this issue and in sub-issues
- Create a patch to test typo3/phar-stream-wrapper:dev-master in core/composer.json and composer.lock
- Update our libraries: requires typo3/phar-stream-wrapper:2.0.2.
- Review the eventual patch for security concerns.
- Update the issue summary to address API changes and Release notes snippet TBD.
User interface changes
None.
API changes
TBD.
Data model changes
None.
Release notes snippet
TBD.
| Comment | File | Size | Author |
|---|---|---|---|
| #86 | 3026443-nr-bot.txt | 146 bytes | needs-review-queue-bot |
| #77 | 3026443-3-77.patch | 39.61 KB | alexpott |
| #72 | 3026443-d7-72.patch | 1.75 KB | jstoller |
| #71 | 3026443-3-71.patch | 39.59 KB | alexpott |
| #44 | 3026443-44.patch | 3.08 KB | indigoxela |
Comments
Comment #2
jlockhartThanks for opening this. I'm updated to 7.63 on pantheon, and my local lando dev. Still getting an error.
PHP 7.1.26
Geoip 7.x-2.1
I've double checked my install even though this is a new error. see image.
Here is the full message from my local on lando.
Error message from Pantheon
This is happening on a couple of my sites with that module.
Comment #3
indigoxela commentedI can confirm this problem. Latest Drupal core update breaks geoip, if the module is used with geoip2.phar (https://github.com/maxmind/GeoIP2-php/releases).
This results in WSOD on pages, where geoip functionality is in use.
PHP Fatal error: main(): Failed opening required 'phar://geoip2.phar/vendor/autoload.php'Comment #4
alexpottLooking at the stack trace I think
\TYPO3\PharStreamWrapper\Helper::determineBaseFile()is working the wrong way around and resolvingphar://geoip2.phar/vendor/autoload.phpto be a file.I wonder if the attached patch fixes it. We'll need to push for an upstream fix but at least we can find a resolution first.
Note this patch is a Drupal 8 patch. I'll roll a Drupal 7 patch next.
Comment #5
alexpottHere's a D7 patch
Comment #6
indigoxela commented@alexpott thanks for providing a patch
I applied #5 on a D7 install, which worked without problems, but the errors still occur.
My current ugly workaround to prevent WSOD is to return TRUE in public function assert without checking anything. That's of course not a solution, it just keeps the site working.
Comment #7
alexpottPatch with tests. It turns out the phar has an aliasing system and we need to resolve them. I'm not 100% sure this will work for geoip because I'm not sure how the phar is included but hopefully @indigoxela can test again :)
The patch includes the tests from #3026414: Add tests for PharExtensionInterceptor for invocation from a CLI command and adds the phar alias case.
Comment #8
indigoxela commented@alexpott unfortunately 3026443-d7-7.patch doesn't solve the problem either, still WSOD.
I added a "drupal_set_message($path);" in public function assert before returning TRUE.
This is the full output:
Comment #9
alexpottI think this will work. At least it does for me when using geoip. I have not thought through all of the ramifications yet though. The problem is that the \Drupal\Core\Security\PharExtensionInterceptor cannot possible resolve a path like
phar://geoip2.phar/vendor/autoload.phpbecause the alias<code>phar://geoip2.pharonly works when used by code contained inside the phar.Comment #10
indigoxela commented@alexpott cool, patch #9 finally solved the problem for Drupal 7
I'm not sure, if this has any downsides for security, so I don't dare yet to set the status to RTBC.
But your comment in code properly points out why this is done.
Comment #11
alexpottHere's a test only patch for D8 and fix for D8 too showing the approach works. We just now have to determine whether this opens a security vulnerability.
Comment #14
alexpottWe;'re going to need to address this in D8 first. Those patches failed because they were tested against D7 :)
Comment #15
fabianx commentedAs discussed in chat, this change is _not secure_ as it opens up a race for placing the file after the assertion succeeded here.
What I would suggest is that aliases need to at least have a .phar extension, so I would do something like:
That should work for aliases.
Comment #16
fabianx commentedAlex is of course right that aliases do not need to end with .phar, so here is two other ideas:
- The other possibility is to allow to configure a whitelist of aliases.
But maybe we can do even better:
Maybe we could read the alias of a phar file when asserting() it the first time?
There is this for example: http://php.net/manual/en/phar.getalias.php
e.g. So maybe we can do:
We then only need to resolve the alias in case we cannot find a resolvable path.
Comment #17
alexpottSo here's a fix based on #16 but I think we should move this upstream and maybe into
\TYPO3\PharStreamWrapper\PharStreamWrapperComment #18
alexpottHere's a D7 patch based on the same logic.
Comment #19
xjmThis seems like a dangerous assumption to make. Can we verify that a real phar archive was checked?
Edit: xpost.
Comment #20
xjmWe should also check and see if people who still have Drush issues on 7.63 have this problem: #3026560: After upgrade to 7.63, 8.5.10, 8.6.7, 9.4.0 get TYPO3 phar error for drush
Based on @samuel.mortenson's previous research, I suspect there's something to do with relative paths that's causing their issues and I think this patch might fix those issues as well.
Comment #22
indigoxela commented@alexpott unfortunately the D7 patch (3026443-d7-18.patch) doesn't work yet
Still wsod.
Some findings: your strpos check won't work because $alias in my case is "/var/www/dev/html/sites/all/libraries/GeoIP2-phar/geoip2.phar". In other words $alias contains the full filesystem path. You seem to expect the basename.
instead of
strpos($path, 'phar://' . $alias) === 0try
strpos($path, 'phar://' . basename($alias)) === 0And there's an additional warning:
Comment #23
alexpott@indigoxela - the problem is that
I'm guessing this is because of how the phar is built. GeoIP2 uses box - with the following config https://github.com/maxmind/GeoIP2-php/blob/master/box.json
I think box does not call setAlias when building the phar - see https://github.com/box-project/box2/search?q=setAlias&unscoped_q=setAlias - it just uses
\Phar::mapPhar()which takes an alias but does not set the global phar alias.Not. fair.
Comment #24
alexpottWell box eventually use https://github.com/box-project/box2-lib/blob/1c5d528d0c44af9d084c2e68ed8... and that creates the phar with
new Phar(filename, , alias)so I've no idea why getAlias does not work :(Comment #25
alexpottI've tested
Add that dumps the expected alias.
Comment #26
oliver.hader commentedHi everybody,
$phar = new \Phar($compromisedFile);Basically that's already enough to trigger insecure deserialization (which was the initial aspect of dealing with Phar files in a special way). In order to retrieve the Phar alias, manifest information has to be parsed.
There is an experimental implementation available which is exactly doing this without invoking Phar internals:
https://github.com/TYPO3/phar-stream-wrapper/blob/feature/phar-reader/sr...
In case there is some minima PHP code that would show me the current drawbacks, I'd gladly jump in with giving support on how to solve that and create according functional tests for it.
Comment #27
damienmckennaComment #28
fabianx commented#26 Thanks - that is a very good catch.
I think there is no other choice than to parse the alias directly.
It seems we can easily do that manually via:
Probably we need something like:
and if we also hardcode an implementation of Reader::resolveFourByteLittleEndian() we don't need to implement all of the experimental reader.
That feels pretty good - as an approach to support the aliases.
It still remains that aliases seems to be implemented in an "interesting" way.
Comment #29
alexpott@Fabianx, @oliver.hader we're only calling \Phar on something after we consider it safe - so if that is risky then we have more problems.
Also the latest PR on https://github.com/TYPO3/phar-stream-wrapper/pull/7 moves the alias getting to a better place.
However none of this addresses the problem that the way geoip is built it doesn't have a standard phar alias - it is uses http://php.net/manual/en/phar.mapphar.php
Comment #30
indigoxela commentedIt looks like we're stuck a bit now with path mappings.
Again for clarification:
This (real) path passes the security check:
phar:///var/www/dev/html/sites/all/libraries/GeoIP2-phar/geoip2.phar/phar-stub.phpThese "paths" fail because they are just mappings (or aliases?):
See the full path list for geoip2.phar in comment #8
phar:/// vs phar://
And reading this thread, it seems impossible to verify these mappings. Is this still true?
And a simple plausibility check, for instance via regex, won't be secure enough?
A simple example:
What about a different approach: geoip2.phar is known to the libraries module. Can we create something from this? Of course, that would only work for stuff known to be installed libraries, not for drush.
Comment #31
fabianx commentedSo as stated before, as we always deem .phar files as secure, it's perfectly fine to use the original patch and only return TRUE if the proposed alias has a phar extension.
We also could with some certainty assume that aliases (made with box) have the internal alias be like the basePath -- unless the file was renamed afterwards.
Overall it's a box bug and a misunderstanding in PHP. As stated in GH upstream issue you can "patch" a phar file easily to work with the alias support in the patch here:
with phar.readonly=0 in some php.ini, e.g. a full tool could just do:
and invoke with
./patch-alias.sh geoip2.phar geoip2.pharUnfortunately box is no longer maintained, but adding a simple setAlias call after the new Phar() would solve it all for us.
Comment #32
effulgentsia commentedI think it might be. I think allowing
/^phar:\/\/[a-zA-Z0-9_]+\.phar\//makes sense as a way of whitelisting aliases that aren't available via other ways. Even if PHP's stream wrapper ends up not resolving it to an actual alias, how would that open up a vulnerability? And by allowing it, it could solve for the geoip case and other simple *.phar alias cases without requiring the burden of #31.Comment #33
fabianx commented#32That was my original suggestion, too -- but it means that we only support aliases that end in .phar -- which Alex was against.
But any support for aliases is better than none for now -- while we work on getting better support out.
Comment #34
effulgentsia commentedRight. I think we can do #32 in addition to aliases that we discover via
getAlias().Comment #35
effulgentsia commentedComment #36
alexpottI've worked a possible solution that works with GeoIP! And gives use the level of protection we have already. And doesn't read the Phar :)
It works by using the backtrace because it knows that at some point back in the chain the real Phar file must be in
$call['file'].Comment #37
generalredneckI'd be interested to see if this helps with our issue over in #3026560: After upgrade to 7.63, 8.5.10, 8.6.7, 9.4.0 get TYPO3 phar error for drush since you are using real path to do some of the checking. Might test it against that scenario too.
Comment #38
xjmPromoting to major since this is a contrib blocker.
Comment #39
effulgentsia commentedI haven't tested this, but just from looking at the patch, it looks to me like #36 would be insecure if code within a .phar file does a file operation on an untrusted phar:// URI (a different one than itself). In other words, anywhere the original vulnerability (prior to SA-CORE-2019-002) exists, then with #36, wouldn't that same vulnerability be reintroduced if the code with the original vulnerability happened to get packaged and deployed as a .phar file?
Comment #40
alexpott@effulgentsia I don't think so. You can't use a phar's internal alias from another phar file. So the first call to the phar stream wrapper is going to error because
$baseFile = Helper::determineBaseFile($path);is going to be the path to your bad phar file and then an exception is going to be thrown.I guess there might be a worry about a possible timing attack similar to #15? Ie. where you get a good phar to access a bar phar that does not exist yet and somehow is triggered to come into existence after this check and before PHP tries to actually do anything with it. I'm really not sure that is a realistic expectation because
Comment #41
alexpottHere are some performance tweaks to do less backtracing.
Comment #42
indigoxela commented@alexpott I tested your D7 patch (3026443-36-d7.patch)
It fixes the errors and wsod, but breaks geoip functionality. My geoip rule doesn't work with your patch applied.
Need to dig a little deeper, to figure out, what the problem is.
Comment #43
indigoxela commentedAh, got it. Only a part of geoip's paths pass the check now ($this->baseFileContainsPharExtension($path)).
These work:
These still fail:
It should be obvious, why (/../).
Comment #44
indigoxela commentedThis patch combines the work done by alexpott with a lightweight regex check for a limited set of aliases.
It's for Drupal 7, so of course tests fail with D8.
Comment #46
alexpottNote there is a some great stuff going on upstream to read the stub and get the mapped alias from there - see https://github.com/TYPO3/phar-stream-wrapper/pull/12/files which pulls everything together. I'm going to try out the latest changes on our tests when I have a chance.
Comment #47
indigoxela commented@alexpott are we supposed to wait for another pull request on github, or are we supposed to test the current master?
Comment #48
alexpott@indigoxela there's quite a few things we need to do:
For D7 we can do a patch to do that all in one step but it should be done with all of those things existing. They don't atm.
Comment #49
loopy1492 commentedUpgrading directly from 8.6.3 to 8.6.7 seems to have worked for me.
Comment #50
kinmen commentedSorry guys, I'm getting the following error too:
2019/01/24 12:17:33 [error] 19214#19214: *111253 FastCGI sent in stderr: "PHP message: PHP Warning: Uncaught TYPO3\PharStreamWrapper\Exception: Unexpected file extension in "phar://geoip2.phar/vendor/autoload.php" in /usr/share/nginx/html/drupal/misc/typo3/drupal-security/PharExtensionInterceptor.php:38
Stack trace:
#0 /usr/share/nginx/html/drupal/misc/typo3/phar-stream-wrapper/src/Behavior.php(72): Drupal\Core\Security\PharExtensionInterceptor->assert('phar://geoip2.p...', 'stream_open')
#1 /usr/share/nginx/html/drupal/misc/typo3/phar-stream-wrapper/src/Manager.php(83): TYPO3\PharStreamWrapper\Behavior->assert('phar://geoip2.p...', 'stream_open')
#2 /usr/share/nginx/html/drupal/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php(412): TYPO3\PharStreamWrapper\Manager->assert('phar://geoip2.p...', 'stream_open')
#3 /usr/share/nginx/html/drupal/misc/typo3/phar-stream-wrapper/src/PharStreamWrapper.php(247): TYPO3\PharStreamWrapper\PharStreamWrapper->assert('phar://geoip2.p...', 'stream_open')
#4 phar:///usr/share/nginx/html/drupal/sites/all/libraries/GeoIP2-phar/geoip2.phar/...
PHP message: PHP Fatal error: main(): Failed opening required 'phar://geoip2.phar/vendor/autoload.php' (include_path='.:/usr/share/php') in phar:///usr/share/nginx/html/drupal/sites/all/libraries/GeoIP2-phar/geoip2.phar/phar-stub.php on line 3" while reading response header from upstream...
and getting WSOD after upgrading from Drupal 7.61 to 7.63. Php 7.0, MariaDB
I didn't understand at the end if there's a solution and eventually which patch to apply...
Comment #51
indigoxela commented@kinmen work is in progress upstream (on github), but there's currently no patch yet.
As a workaround to fix your broken site, you could edit misc/typo3/drupal-security/PharExtensionInterceptor.php directly.
Open it with a text editor and in line 35 within public function assert add:
right before
if ($this->baseFileContainsPharExtension($path)), which causes the errors.This will not be the final solution, but is only a quick fix for broken sites.
Comment #52
kinmen commentedUfff... thank you so much, your solution has saved my life. I look forward to see a patch, but in the meanwhile the web site is working. Thank you thank you thank you
Comment #53
effulgentsia commented@indigoxela: could you open a child issue with a patch for just #51? I think it'll help others who are using GeoIP in the meantime. Also, if the upstream solution isn't ready in time for the next bug-fix releases of 8.6.x and 7.x, I think we should consider committing the temporary regex workaround in time for those releases, and a child issue would be a good place to discuss that.
Comment #54
indigoxela commentedChild issue has been created with a patch for Drupal 7 and geoip2.phar.
We probably should consider alexpott's patch #36, as that could fix problems for other libraries.
Comment #55
alexpotthttps://github.com/TYPO3/phar-stream-wrapper/pull/14 is PR to backport functionality we need from the master branch to v2 as that supports PHP5.3^.
Comment #56
prlw1 commentedWould just supporting PHP 5.6 and above be too radical?
Comment #57
effulgentsia commented@prlw1: How would that help with this issue?
Comment #58
mradcliffeI think, that after reading Pull Request #12 that @alexpott references in #46 and #48, that would solve #3027089: "Warning: stat failed for phar:///…/sites/all/modules/…" after upgrade to 7.63 for Drupal 7 and Drupal 8, but it's a little difficult to understand the pull request description. It mentions fixing "includes" and the upstream code refers to using realpath on an unprefixed file path. If that's the case, then we could probably resolve #3027089 as a duplicate of this issue for Drupal 8 and whatever follow-up issue is created for Drupal 7.
However I am still not clear from reading the code in Pull request #12 that it resolves that issue. I would greatly appreciate if anyone could help interpret that code to confirm it would resolve #3027089.
I added the "Needs issue summary update" tag because the issue summary mentions finding steps to replicate, but we already have a patch that Needs work and security review.
Comment #59
indigoxela commentedIn the meantime another bugfix release is out for Drupal 8.
People who needed to patch their D8 sites because of a problem with .phar files have to do it again after updating.
Is there anything we can do to speed up progress with PharExtensionInterceptor?
Comment #60
generalredneck@indigoxela
might I suggest using composer to patch your site instead of manually doing it? It's a great way to document patches as well as reapply them with each upgrade. Take a look at https://github.com/cweagans/composer-patches... Example my patches look something like this
Keep in mind you can point to a path as well... such as a "patches" folder at the root of your project. with something like
Comment #61
indigoxela commentedYou may suggest anything. ;) Yes, composer can be very handy.
But the actual intention of my comment was to warn people affected by this regression, that they shouldn't expect this issue to be fixed in current releases.
And I hoped for some progress with PharExtensionInterceptor.
Comment #62
alexpottSo https://github.com/TYPO3/phar-stream-wrapper/pull/14 is merged. We're on the plan from #48
Open a PR to backport changes to https://github.com/TYPO3/phar-stream-wrapper/ master to the v2 branch - we need the v2 branch for PHP5 supportI'll open a PR to for step 3 later today.
Comment #63
alexpott@oliver.hader opened the backport PR and did the work - yay! See https://github.com/TYPO3/phar-stream-wrapper/pull/15
Comment #64
geerlingguy commentedIt's not just .phar files that seem to be broken... I use the following code to expand an archive into a path:
And that is now hitting an error that breaks functionality:
Unexpected file extension in "phar:///var/www/html/docroot/sites/default/files/project-starter-templates/php-template.tar.gz/"It seems like the initiative to start using PharData() in core for archive handling would also be stymied by this security fix: #2891871: Rewrite the Archiver classes to use PharData. (The patch from earlier in this issue does not fix the problem either.)
Comment #65
indigoxela commentedIt seems like PR 15 has been merged into v2.
And a Joomla user has tested it successfully with geoip2.phar.
What are our next steps? What has to be done on Drupal side now?
Comment #66
alexpottWe have to bump our composer to use the dev master and try implementing with Drupal. Once we've got a successful integration we need to ask for a release of the phar wrapper and use that.
Comment #67
mradcliffeI updated the issue summary with the current work and tasks that was initially outlined in #48 and based on recent work in #62 and #65.
We use issue tags to help identify next steps, @indigoxela. On any issue, you can now look at the side bar that lists the tags and an "about tags" link. Clicking that will help describe some of the tasks associated with the tag. In this case, I think that the issue summary itself is confusing what the next steps are.
It would help to update the issue summary to include comments about how to reproduce and what libraries and contrib. modules are affected. After updating an issue summary, we remove the tag when we think we're done.
Comment #69
mradcliffeI think the next steps are done now that SA-Core-2019-007 is out. All the PRs were merged into v2 and a new release was made for both typo3-stream-wrapper and Drupal.
I think this can be closed as outdated because that was the only code change, and there wasn't anything planned for \Drupal\Core\Security\PharExtensionInterceptor.
Comment #70
alexpottWe're very nearly there! We need to update \Drupal\Core\Security\PharExtensionInterceptor - unfortunately we can't just use Typo3's \TYPO3\PharStreamWrapper\Interceptor\PharExtensionInterceptor because it break when used with a renamed Drush phar file.
So there is still something to do here - we just need to work out what.
Comment #71
alexpottHere's a patch for Drupal 8 - you can make the same changes to the PharExtensionInterceptor on Drupal 7 and it'll work.
Comment #72
jstollerHere's an updated patch for D7, based on the patch in #71.
Comment #73
indigoxela commentedConcerning patch #72 for D7 I can confirm that it fixes the problem with GeoIP2-phar. No more exceptions and GeoIP works correctly.
Comment #75
abhi shetty commentedHi all,
I am getting "TYPO3\PharStreamWrapper\Exception: Unexpected file extension in "phar://httpful.phar/Httpful/Bootstrap.php" in Drupal\Core\Security\PharExtensionInterceptor->assert() (line 38 of /Applications/MAMP/htdocs/grammy/docroot/misc/typo3/drupal-security/PharExtensionInterceptor.php)." error for using HTTPFUL phar file.
I ran drush updatedb to confirm if any updates were pending and there weren't any. For me drush cc all command is also working fine.
I just face this issue when I use "include('httpful.phar');" by following the steps provided in http://phphttpclient.com/ Option 1.
Could someone let me know if I missed anything here and how can I get this resolved.
Thanks
Comment #76
abhi shetty commentedHi,
Just an update. #72 patch worked for me. Thanks @jstoller.
Comment #77
alexpottRe-rolled.
Comment #78
Neo13 commentedWorks for me
Comment #81
tanubansal commentedPatch #77 works fine for me as well
RTBC + 1
Comment #86
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #88
akalata commented\Drupal\Core\Security\PharExtensionInterceptor was removed from D10 in https://www.drupal.org/project/drupal/issues/3210486, so this issue no longer applies to Drupal core.
Comment #89
xjmThanks @akalata! Saving credits for those who worked on this according to our issue credit guidelines, including our upstream colleagues at TYPO3.