Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apache shows PHP code if .php in URL is not lowercase #973

Open
sam9191 opened this issue Mar 6, 2020 · 4 comments
Open

Apache shows PHP code if .php in URL is not lowercase #973

sam9191 opened this issue Mar 6, 2020 · 4 comments

Comments

@sam9191
Copy link

sam9191 commented Mar 6, 2020

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, like example.com/index.PHP), Apache shows content of the PHP file.
This is because of this config in /etc/apache2/conf-enabled/docker-php.conf:

<FilesMatch \.php$>
	SetHandler application/x-httpd-php
</FilesMatch>

To fix it, FilesMatch's regexp should be case-insensitive:

<FilesMatch \.(?i:php)$>
	SetHandler application/x-httpd-php
</FilesMatch>

I can make a pull request but should the change be made in this file?

echo '<FilesMatch \.php$>'; \

@thaJeztah
Copy link
Contributor

Are the source files actually stored with uppercase file extensions? (or are you perhaps bind-mounting files from a case-insensitive filesystem?)

Reproducing in a "dev" situation, bind-mounting 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)); docker/for-mac#320

Reproducing a "production" situation (case-sensitive filesystem)

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>

So, should this be changed?

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

Alternatives

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 😂);

<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?

@tianon
Copy link
Member

tianon commented Mar 6, 2020

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:

  1. 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>
    

@sam9191
Copy link
Author

sam9191 commented Mar 7, 2020

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

@LaurentGoderre
Copy link
Member

I had a way to do a redirect to prevent the code from being shown

#1456 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants