Open sam9191 opened 4 years ago
Are the source files actually stored with uppercase file extensions? (or are you perhaps bind-mounting files from a case-insensitive filesystem?)
First create directory to play with, and create a "hello world" index.php
mkdir -p test && cd test
echo '<?php echo "hello world\n";' > index.php
Start a container, bind-mounting the files from my Mac's filesystem (which is case-insensitive);
docker run -d --name=test-container -p 4000:80 -v $(pwd):/var/www/html php:apache
Lowercase .php
executes the php script (as expected))
curl 'http://localhost:4000/index.php'
# hello world
Any other case indeed shows the source code (:scream:)
curl 'http://localhost:4000/index.pHP'
# <?php echo "hello world\n";
This looks bad, but is due to this particular situation; URLs are case-sensitive, so this last example should produce a 404 error page (because there's no file named index.pHP
), however, because I'm bind-mounting from a case-insensitive filesystem, any "case" variation of the file is "matched", and will be shown.
This is unfortunate, as it won't allow you to (easily) replicate a production environment on Linux (which would be case sensitive). If you're on Docker Desktop for Mac; there is a feature request to allow bind-mounting case-sensitive (I'd have to check what the status of that is (and if it's technically possible)); https://github.com/docker/for-mac/issues/320
So to reproduce a "production" situation, let's run a container that doesn't bind-mount from a case-insensitive filesystem:
First, remove the test-container
docker rm -f test-container
Now build an image that contains an index.php
(lowercase);
docker build -t phptest -<<'EOF'
FROM php:apache
RUN echo '<?php echo "hello world\n";' > index.php
EOF
Run the container using that image, and this time, don't bind-mount local files (as the index.php
is in the image itself):
docker run -d --name=test-container -p 4000:80 phptest
Trying the same URL's with curl
, using the index.php
file executes the PHP script (as expected):
curl 'http://localhost:4000/index.php'
# hello world
But trying with the wrong case, will now correctly produce a 404:
curl 'http://localhost:4000/index.pHP'
# <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
# <html><head>
# <title>404 Not Found</title>
# </head><body>
# <h1>Not Found</h1>
# <p>The requested URL was not found on this server.</p>
# < hr>
# <address>Apache/2.4.38 (Debian) Server at localhost Port 4000</address>
#</body></html>
Both situations are "undesirable" (you wouldn't want your PHP source to be visible), however:
Changing the matching to be case-insensitive would be a security risk
A website using this image that allows uploading files and that disallows uploading files with a .php
(lowercase) file extension, but forgot to check other variations (.PHP
, .pHp
), would now allow those uploaded files to be executed.
Yes, this would be a pretty bad website (uploaded files should never end up in a location where PHP files are executed), but I've seen my fair share of bad examples unfortunately. Changing this matching would now make them vulnerable.
If you are developing a website, and have files with the wrong (non-lowercase) file extension, that's a bug in your project
The default configuration should not accommodate for those situations
A possible alternative would be to add another matching rule that explicitly disallows browsing files with non-lowercase .php
extensions (suggestions for a better approach / better regex welcome :joy:);
<FilesMatch \.(phP|pHp|pHP|Php|PhP|PHp|PHP)$>
Deny from all
</FilesMatch>
So with this docker-php.conf
:
<FilesMatch \.(phP|pHp|pHP|Php|PhP|PHp|PHP)$>
Deny from all
</FilesMatch>
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
DirectoryIndex disabled
DirectoryIndex index.php index.html
<Directory /var/www/>
Options -Indexes
AllowOverride All
</Directory>
After building a new phptest2
image with the configuration above, start a container with the source files mounted from a case-insensitive filesystem:L
docker run -d --name=test-container -p 4000:80 -v $(pwd):/var/www/html phptest2
Lowercase .php
executes the php script (as expected):
curl 'http://localhost:4000/index.php'
# hello world
But trying any URL with a non-lowercase .php
extension will produce a 403 (access denied):
curl -I 'http://localhost:4000/index.phP' 2>/dev/null | head -n 1
# HTTP/1.1 403 Forbidden
curl -I 'http://localhost:4000/index.pHp' 2>/dev/null | head -n 1
# HTTP/1.1 403 Forbidden
curl -I 'http://localhost:4000/index.pHP' 2>/dev/null | head -n 1
# HTTP/1.1 403 Forbidden
curl -I 'http://localhost:4000/index.Php' 2>/dev/null | head -n 1
# HTTP/1.1 403 Forbidden
curl -I 'http://localhost:4000/index.PhP' 2>/dev/null | head -n 1
# HTTP/1.1 403 Forbidden
curl -I 'http://localhost:4000/index.PHp' 2>/dev/null | head -n 1
# HTTP/1.1 403 Forbidden
curl -I 'http://localhost:4000/index.PHP' 2>/dev/null | head -n 1
# HTTP/1.1 403 Forbidden
@tianon WDYT?
I think you've made some really great points. I'm not sure if we should explicitly add anything -- do we have anywhere we can look for what "shared hosting" providers are commonly doing in these cases to give us a hint?
The only thing I could possibly add to what you've posted is that the case sensitive version we have now matches what's documented (albeit as an "example") by PHP upstream at https://www.php.net/manual/en/install.unix.apache2.php:
Tell Apache to parse certain extensions as PHP. For example, let's have Apache parse .php files as PHP. Instead of only using the Apache AddType directive, we want to avoid potentially dangerous uploads and created files such as exploit.php.jpg from being executed as PHP. Using this example, you could have any extension(s) parse as PHP by simply adding them. We'll add .php to demonstrate.
<FilesMatch \.php$> SetHandler application/x-httpd-php </FilesMatch>
Maybe just add @thaJeztah's suggestion to the docs as a "Note"?
This would append a <FilesMatch>
at the end of the .conf file to prevent access to non-lowercase .php
files:
RUN printf '<FilesMatch \.(?!php)(?i:php)$> \n\
Require all denied \n\
</FilesMatch>' >> /etc/apache2/apache2.conf
I had a way to do a redirect to prevent the code from being shown
https://github.com/docker-library/php/issues/1456#issuecomment-1796524250
If you enter a URL in browser pointing to a PHP file and
.php
at the end is not all lowercase (e.g..phP
,.pHp
,.PHP
, likeexample.com/index.PHP
), Apache shows content of the PHP file. This is because of this config in/etc/apache2/conf-enabled/docker-php.conf
:To fix it,
FilesMatch
's regexp should be case-insensitive:I can make a pull request but should the change be made in this file? https://github.com/docker-library/php/blob/a80762e229981b94c9fe6c381637350e9104096a/apache-Dockerfile-block-1#L45