About Archive Tags RSS Feed


Best practice - Don't serve writeable PHP files

2 February 2016 21:50

I deal with compromises often enough of PHP-based websites that I wish to improve hardening.

One obvious way to improve things is to not serve PHP files which are writeable by the webserver-user. This would ensure that things like wp-content/uploads didn't get served as PHP if a compromise wrote valid PHP there.

In the past using php5-suhosin would have allowd this via the suhosin.executor.include.allow_writable_files flag.

Since suhosin is no longer supported under Debian Jessie I wonder if there is a simple way to achieve this?

I've written a toy-module which allows me to call stat on every request, and return a 403 on access to writeable files/directories. But it seems like I shouldn't need to write my own code for this functionality.

Any pointers welcome; happy to post my code if that is useful but suspect not - it just shouldn't exist.



Comments on this entry

icon Steve Kemp at 19:24 on 2 February 2016

My toy/hacky solution is here:

I do not wish to have to use this code in production, for obvious reasons.

icon Jean Paul Galea at 20:38 on 2 February 2016

I usually use a combination of reverse proxy (e.g. nginx) and backend (e.g. fastcgi) to mitigate such issues.

# stuff here should never be passed to fastcgi!

location /wp-content/uploads {
    # empty

location / {
    # if file or directory do not exist:
    #       internal redirect to /index.php
    # $request_uri is not modified,
    #       and wordpress picks up that value to render dynamic URIs.
    try_files $uri $uri/ /index.php$is_args$args;

location ~ \.php$ { fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_pass$fcgiport; } }
icon Ralf at 20:41 on 2 February 2016

Of course, this breaks the Wordpress Auto-Updater...

icon Michael Herold at 22:14 on 2 February 2016

I never heard of a proper solution for this. I am running PHP over CGI with users differing from www-data. My unimplemented plan is to let the CGI-Script refuse calls to PHP if the file is writable for the CGI script.

However, this will not cover the case of script inclusions. Upstream seems to have to decided to not increase security here either https://wiki.php.net/rfc/script_only_include (even this RFC would not cover all problems).

Unfortunately, some software is giving me a hard time with my setup. For example, ownCloud 7 refused to work without having a writable config file, even if there is a config options that says that it should be okay.

icon Steven C. at 22:39 on 2 February 2016

Getting Suhosin back into Debian would be my preference. The removal was part of a mass bug filing over really tedious licensing issues: https://bugs.debian.org/752650

icon Pete Foster at 23:22 on 2 February 2016

You could use this directive in php.ini: auto_prepend_file = somescript.php

Where some script is something like: <?php if (is_writable($SERVER["SCRIPTFILENAME"])) { die(); } ?>

Yes, it still requires (a little) additional code, but it's portable. :)

icon Mensoif at 07:51 on 3 February 2016

In upload folders, i usually disable php execution (In apache config or htaccess) or put a deny from all (In apache config or htaccess)

icon Steve Kemp at 08:05 on 3 February 2016

Removing permissions on specific folders, such as wp-content/uploads is easy. But I'm looking for a more general solution.

So far Pete's approach of using a prepended file looks nicest, but I live in hope!

icon Steven C. at 11:35 on 3 February 2016

For me, the ultimate would be if from PHP code you could make sandboxing syscalls that apply to the rest of the script's execution; the design of OpenBSD pledge(2) looks ideal to me but other OSes may be able to emulate that soon.

Either from a prepended script, or later during execution of any other script, depending on the HTTP request being handled you could surrender the ability to fork/exec, use networking or open any new file descriptors.

The important thing is being able to do this at any point within PHP code, because a sandbox applied to the main /index.php before it has done any initialisation (like SElinux) probably cannot be made very restrictive without breaking a lot of things.

icon John Leach at 11:41 on 3 February 2016

I've just been using apparmor to deny php the ability to write to any file ending in .php*

A bit extreme perhaps, but it has other benefits.

I have a full apparmor profile defined for my php binary (I use php with fastcgi with apache) but you should be able to just deny php writes and allow everything else

Though now I'm unsure if Debian has apparmor.

icon Steve Kemp at 11:50 on 3 February 2016

Debian does have an apparmor packages, in both Wheezy and Jessie releases..