[CentOS] Apache and web content permissions

Sun Dec 3 22:59:24 UTC 2017
Pete Travis <lists at petetravis.com>

Hi Niki,

The principle to work by here is 'least required access'.  There's two
functional types of users we care about, the one executing the PHP
code (probably apache or php-fpm) and admins like yourself with
FTP/shell access.  Upstream wordpress documents application write
requirements at
https://codex.wordpress.org/Hardening_WordPress#File_Permissions -
read it to know where the web server will expect write access, but
don't follow the instructions - especially the numbers for chmod - by
rote!

On Sat, Dec 2, 2017 at 3:30 AM, Nicolas Kovacs <info at microlinux.fr> wrote:
>
> Hi,
>
> Until a few months ago, when I had to setup a web server under CentOS, I
> assigned (I'm not sure about the correct english verb for "chown"ing)
> all the web pages to the apache user and group. To give you an example,
> let's say I have a static website under /var/www/myserver on a CentOS
> server running Apache. Then I would configure permissions for the web
> content like this:
>
> # chown -R apache:apache /var/www/myserver
> # find /var/www/myserver -type d -exec chmod 0750 {} \;
> # find /var/www/myserver -type f -exec chmod 0640 {} \;
>
> Some time ago a fellow sysadmin (Remi Collet on the fr.centos.org forum)
> pointed out that this is malpractice in terms of security, and that the
> stuff under /var/www should *not* be owned by the user/group running the
> webserver.


Right, this gives Apache write access over *everything*.  That means
that Apache could potentially change your site code.  Many attack
vectors rely on changing wordpress files or creating new files, so
this should not be possible.

> Which means that for the static website above, I could have
> something like this, for example:
>
> # chown -R microlinux:microlinux /var/www/myserver
> # find /var/www/myserver -type d -exec chmod 0755 {} \;
> # find /var/www/myserver -type f -exec chmod 0644 {} \;
>
> Or even this:
>
> # chown -R nobody:nobody /var/www/myserver
> # find /var/www/myserver -type d -exec chmod 0755 {} \;
> # find /var/www/myserver -type f -exec chmod 0644 {} \;


I don't like the convention of creating an arbitrarily named user to
own website files.  Nicolas is logging in and working on the server,
make an ie nkovacs user for yourself to do your work.  Shared hosting
companies tend to follow the "one FTP user named after website" or
"one shell user named after customer" model and expect their customers
to share a single login account, but if you have root access to the
server there's no restrict yourself this way.  It also leads to a
solution where a group of folks who need to work on the site will
share the single login account, making it impossible to answer
questions like "who changed this file" or "who is logged in right
now".  If any kind of compliance is a concern, generic/anonymous login
is a no-go.  If compliance is not a concern, there's still no real
benefit to making up usernames for yourself on a production system
that are not your own name, and sharing credentials is still bad
practice in principle.

>
> Now I'm hosting quite a few Wordpress sites on various CentOS servers.
> Some stuff in Wordpress has to be writable by Apache. If I want to keep
> stuff as secure as possible, here's the permissions I have to define.
>
> # cd /var/www
> # chown -R microlinux:microlinux wordpress-site/
> # find wordpress-site/ -type d -exec chmod 0755 {} \;
> # find wordpress-site/ -type f -exec chmod 0644 {} \;
> # cd wordpress-site/html
> # chown -R microlinux:apache wp-content/
> # find wp-content/ -type d -exec chmod 0775 {} \;
> # find wp-content/ -type f -exec chmod 0664 {} \;
>
> As far as I know, this is the most secure setup for Wordpress as far as
> permissions are concerned.


Wordpress plugins are in wp-content.  Allowing a wordpress plugin to
be compromised is functionally equivalent to allowing the core code to
be compromised, we do not want Apache to write plugin code.
`wp-content/uploads` is the only *stock* directory I'm aware of that
Wordpress *requires* write access too.  Some plugins might have
additional directories they write to, this should be documented for
each such plugin.

With an application like Wordpress, Apache only needs to create files
for things like images uploaded for posts.  It should never be allowed
to write in a directory where PHP files are.  Conversely, any
directory where it *can* write should not be used for PHP code.  You
can block that with the snippet below, again from upstream wordpress:

<Directory "/var/www/myserver/wp-content/uploads">
   # Kill PHP Execution
   <Files ~ "\.ph(?:p[345]?|t|tml)$">
      deny from all
   </Files>
</Directory>

You might notice that I used a <Directory> block where the page I
linked to does not.  The upstream example has you drop a <Files> block
into a .htaccess file; in that context, the <Directory> is implicitly
inherited from the immediate parent directory of the .htaccess file.
It's a convenient way to adjust Apache configuration if you do not
have privileged shell access, but it also means the .htaccess file
will be read and interpreted anew for *every request*.  You *do* have
privileged shell access, and can put the directives into the config
file that's read once, when the server loads, to avoid much recurring
IO expense.


> The problem is, I can't use automatic updates
> anymore. Whenever Wordpress releases a new version, I have to set
> permissions temporarily like this:
>
> # chown -R apache:apache /var/www/wordpress-site
>
> Then I can launch the update from within the Wordpress dashboard. And
> once the update is complete, I have to redefine sane permissions as
> above. Which is quite a bit tedious if you have two dozen Wordpress
> sites to manage, even if you have little scripts to define the permissions.
>
> So I'm finally coming to my question. How problematic is it really to
> have the apache user and group owning the stuff under /var/www? I admit
> I followed the users' advice out of respect for his competence. But as
> far as I know, sometimes you get security advice where the resulting
> hassle far outweighs the real benefits.
>
> Any suggestions?
>
> Cheers,
>
> Niki
>
> --

You're describing Wordpress's http direct filesystem access method.
There should be an option to chose another method.  You can install
the ssh2 php module and use the ssh access method, or the FTP access
method.  This way, when you update, you would enter your linux user
credentials into Wordpress's update form and it would work because
'nkovacs' owns those files,or is a member of the owning group.  If you
chose FTP, do not allow public access, because FTP transmits
credentials in the clear.  Admins with real FTP clients can use sftp,
which comes with the openssh server for free.  With ssh or ftp update
methods, Apache *never* gets write access to PHP files directly, it
acts as an [s]ftp client to do the update work.

When 'nkovacs' creates a new file, it will be owned by nkovacs:nkovacs
and inherit octal permissions from the parent directory and umask.
That might break some of our assumptions about the role played by
group membership, so make the group ownership 'sticky' with `chmod g+s
-R /var/www/mysite/`.  Now,  new files will inherit group membership
from the parent directory instead of the creating user.

If 'nkovacs' is the only admin who will ever update wordpress, or work
with the site in a shell or FTP session, you can put 'apache' in the
group owner slot, and set the octal permissions on directories to
appropriately restrict it - ie 2755 everywhere except
`wp-content/uploads`, which could be 2775.

If others will also perform those tasks, then you need a user group
for them, ie 'microsites_devs' or 'mysite_admins'.  We still want
sticky groups, but we can't use the group slot to give Apache
permissions anymore.  Those image uploads are coming from the public
internet, so it's not *too* much of a stretch to use the 'other' slot
to allow apache writes via a 2777 mode - but this violates the 'least
required privilege' principle because any user on the system, not just
apache, can tamper with the files.  A judicious application of facls
fits this use case, ie

setfacl -m u:apache:rwX -R /var/www/mysite/wp-content/uploads
setfacl -m d:u:apache:rwX -R /var/www/mysite/wp-content/uploads

The 'X' sets only directories for execute, in contrast to 'x' which
makes files executable too.  The first invocation applies to existing
files and the 'd:' sets the 'default' ACL inherited by new files.

Also, don't forget that /var/www by default has the SELinux context
httpd_sys_content_t, which will not allow writes regardless of octal
permissions or ACLs.  Wrap up by changing the context of directories
you've determined should be writeable:

semanage fcontext -a -t httpd_sys_rw_content_t
"/var/www/mysite/wp-content/uploads(/.*)?"
restorecon -R /var/www/mysite/

TL;DR my process is:
- Make a list of real humans that need to work on the site
- Assume the web server user should have at least read access on all
files in the site documentroot, or we'd put them somewhere else.
- Make a list of directories (uploads, cache, session files, etc) the
web server must have write access to.
- Use various permissions utilities to make sure humans and web server
can do their assigned work and nothing more.

The first three steps are basically requirements gathering; for best
results, don't skip ahead to applying permissions changes until you've
established what permissions are needed.

HTH,

-- Pete