Problem/Motivation
.htccess is supposed to prevent executing php files like those in vendor
# Deny access to any other PHP files that do not match the rules above.
# Specifically, disallow autoload.php from being served directly.
RewriteRule "^(.+/.*|autoload)\.php$" - [F]
the .htaccess rule is somehow wrong or incomplete that supposed to prevent execution of php files in the vendor dir, however
/core/vendor/behat/mink/driver-testsuite/web-fixtures/headers.php/1
is executed while
/core/vendor/behat/mink/driver-testsuite/web-fixtures/headers.php
is a 403
Proposed resolution
Fix .htaccess in this issue and move vendor out of webroot in: #2508591: vendor/ is web accessible
Remaining tasks
User interface changes
n/a
API changes
n/a
Data model changes
n/a
originally reported as part of the Drupal 8 bug bounty
https://tracker.bugcrowd.com/submissions/77918e9ef18b0f36f4279efbdb39bf8...
original reporter: DongIT
Comments
Comment #1
tim.plunkettComment #2
dawehnerDoes our web.config has the same issues?
Comment #3
dokumori commentedmoved to https://security.drupal.org/node/155254 as per https://security.drupal.org/node/155252
Comment #4
benjy commentedThis rule only exists in D8 so I think this can be public. Re-publishing.
Comment #5
alexpottHere's a patch that prevents access to /core/vendor/behat/mink/driver-testsuite/web-fixtures/headers.php/1
Comment #8
alexpottAlso this has nothing to to with vendor specifically...
core/lib/Drupal.php/1is just as accessible as/core/vendor/behat/mink/driver-testsuite/web-fixtures/headers.php/1.Trying some new patches. Not sure what I did wrong.
Comment #11
alexpottComment #13
pwolanin commentedClarifying title - this is unrelated to e.g. the prevention of php execution in the files directories.
Comment #17
alexpottFixing Simpletest's http.php and https.php
Comment #19
dawehnerWhat happens with
web.configjust curious here ...Comment #20
alexpott@dawehner who as an IIS server kicking about?
Comment #21
dawehnerNo idea. Maybe some of the commerce people, as they have developed some mssql driver.
Comment #22
cilefen commentedWould query strings break the protection without this patch?
Comment #23
cilefen commented^^ no
Comment #24
benjy commentedPatch looks good, I tested this manually with as many variations as I could based on the initial issue report and couldn't reproduce after applying #17. +1 for RTBC.
Comment #25
alexpottI'm not sure that we should be using the rewrite directive to deny access to php files. This change will prevent access to a path alias like "htttp://drupal.org/what.php/5.6-is-best". mod_rewrite is not matching on files it is matching on the requested url. It does not care if the underlying file exists or not - that's what the
FileMatchdirective is for.Comment #26
benjy commentedHere's an approach with FilesMatch, see what the bot thinks.
Comment #27
alexpottI think this approach is slightly clearer and easier to grok.
I definitely agree the rewrite approach should be ditched. This basically gives us a list of possible front controller names that is simple to understand and alter.
Comment #28
alexpottA slight refinement.
Comment #29
alexpottWe can go further!
Comment #31
alexpottA change for my local env crept in.
Comment #33
alexpottMissed update.php
Comment #37
benjy commented#33 is awesome, much simpler to read and using FilesMatch makes sense. +1 from me.
Comment #38
dawehnerAt least this documentation is certainly kinda lost ...
Comment #39
alexpott@dawehner good point.
Comment #40
dawehnerCool
Comment #41
pwolanin commentedSo, I am not sure this is all working as desired. update.php is not a real file now, but removing it from the list blocks access.
Similarly, if I alias a node path to /zzz.php, I get a 403. Also /zzz.install. however, /index.php/zzz.install loads the page
I think this is why it was in the rewrite section - so that the rewrite to index.php would bypass it.
Comment #42
pwolanin commentedHere's a possible alternate fix for the problem - only act when the file exists on disk, and match php in the name.
I haven't tried all possibilities, but does seem to block use of a trailing slash to bypass the rule, while allowing a node aliased to /a/zzz.php
Comment #43
pwolanin commentedMaybe can make it simpler. We already check in the index.php rewrite the !-f condition, so we shouldn't need to check the opposite.
How about just this?
Comment #44
pwolanin commentedComment #47
alexpottAdded tests for more files and anode aliased to test.php and test.php/test.
This looks like it might actually work!
@pwolanin++
Comment #48
mlhess commentedI can confirm this works for htaccess. Can we build in a status check for people who may not have htaccess setup correctly. Something that says their site is insecure, like the update notices?
Comment #51
pwolanin commented@mlhess - reliably checking Drupal by trying to make http requests to itself has problems. e.g. see: #965078: HTTP request checking is unreliable and should be removed in favor of watchdog() calls
At this point I wonder if implementing some of these checks client site (i.e. JS) would be more sensible, since clearly the browser can access the site at the point you are viewing a report page.
Comment #52
chx commented> Can we build in a status check for people who may not have htaccess setup correctly. Something that says their site is insecure, like the update notices?
If you do, you need to do it with JS and/or PHP-masquerading-as-image but server-to-server requests have been attempted and failed. There are too many variations to make it work reliably. So please do not try it again :)
Comment #53
alexpottRerolled.
I've looked at trying to do @mlhess's suggestion in #48 in light of #51 and #52. It's going to be quite problematic to add a requirement in system_requirement that only appears when rendering through a browser. hook_requirements() is supposedly for CLI as well. I think the best way forward is to open a followup issue for discussing the best way to do this. Which I've created - #2510194: Use JS or PHP-masquerading-as-image to test .htaccess on admin/reports/status
Comment #54
pwolanin commentedSo, I think this might block access for a harmless text file named foo.php-info.txt? That was my motivation for including the
(/|$)in my last patch.Comment #55
pwolanin commentedIndeed - confirmed that problem locally.
Comment #56
alexpott@pwolanin - you're right going with ($|/) - I was trying to reduce complexity :). I've also added test coverage to ensure that a file named foo.php-info.txt is not blocked.
And I've also completed test coverage for the FilesMatch directive in .htaccess and found a bug :)
Is the current directive in D8 HEAD (and D7). This blocks
access_test.module.saveandaccess_test.module.origbut (mistakenly) permits bothaccess_test.php.saveandaccess_test.php.orig- it does however blockaccess_test.php.orig.save(lol).Care needs to be taken if this patch needs rerolls since our git is configured to ignore .swp , ~, and .orig files. You have to add them with a -f.
As @pwolanin says:
Comment #57
alexpottFYI: The bug wrt to .php.orig.save was added by #1907704: Restrict temporary files created by text editors
Comment #58
cilefen commented#2508025: .htaccess broken regex for \.orig and \.save
Comment #59
pwolanin commentedI think this code comment needs a fix - maybe remove the "If"?
Comment #60
pwolanin commentedJust fixing the comment
Comment #61
pwolanin commentedThink this is good now. We should backport some of the fixes and tests to D7 also.
Comment #62
xjmThis is, like, adding emacs backups or something to core?Edit: never mind.
Comment #63
xjmDisregard #62. That is an intended part of the test.
Comment #65
catchOne comment typo, fixed on commit.
s/in //
Committed/pushed to 8.0.x, thanks!
Moving to 7.x for backport of the relevant bits.
Comment #66
catchMajor for 7.x - we don't have .php file protection there at all, so it's mainly firming up the .orig etc. blocking which is hardening only.
Comment #67
star-szrIn that typo comment referenced above shouldn't we also s/is it/it is/? Sorry to bring it up.
Comment #68
catch@Cottser, yes, yes we should.
Also https://www.youtube.com/watch?v=YIyDXCYe7lM
Comment #69
cilefen commented#2511348: HtaccessTest::testFileAccess() has a documentation typo
Comment #70
pwolanin commentedComment #71
pwolanin commentedComment #72
DongIT commentedI found this bug
Comment #73
alexpottTicking the @DongIT checkbox in the credit list.
Comment #74
Jorgee commentedDoes this affect Nginx as well?
Comment #75
alexpott@Jorgee nginx has the same security considerations as apache does wrt to access to .php files. Drupal does not supply nginx configuration scripts. But a good place to start to see if the nginx configuration you are using has the expected security provisions is to run HtaccessTest.
Comment #81
edward.radau commentedI believe this is the same issue I ran across recently. The issue is that the regex is not escaped properly. The rule needs to be written as follows:
RewriteRule "^(.+/.*|autoload)\.php($|//)" - [F]The regex isn't escaped properly. If it isn't escaped the regex will be be far too broad and will match almost anything. It will NOT match "autoload" but many different strings. I haven't checked any of the patches yet in this thread but I am hoping it has already been addressed.
This issue still affects the latest version of D8
Comment #82
izmeez commentedRegarding backport to Drupal 7 I have looked over the patch that was committed to Drupal 8. The change to the FilesMatch line already exists in Drupal 7 and the file autoload.php is not in drupal 7. All that is left are the tests and I'm not sure it's worth the effort.
If I'm correct the backport is not needed or a new issue to specifically add tests for .htaccess in drupal 7 should be created.
Comment #83
poker10 commentedLet's handle the backport in the child issue here: #2779833: Fix Drupal 7 .htaccess to protect .orig and .save files from view. It is not possible to completely backport this fix as D7 does not have PHP files execution protection (this is being discussed here: #1587270: Forbid execution of PHP files in subfolders by default (except those needed by core)), so only fix to .orig and .save extensions can be backported + test.
So I think it will be the best to close this as fixed for D8.