How-To: harden Apache & PHP-FPM daemons using systemd


This How-To aims at bringing some help leveraging some systemd niceties to enforce security restrictions on daemons (or services in systemd parlance) such as Apache HTTPD and PHP-FPM. Instructions are tested on GNU/Linux Debian. They should work on any Debian-derived distribution, or with some tweaks here and there, to any GNU/Linux distribution.


Explanations

Wether you like it or not, and wether these features are relevant for a PID 1 process or not, systemd does offer some nice-to-have functionalities like overrides and parameters that can help you administer and secure your system, in a way that a traditional init system wouldn’t.

Parameters

Parameters are key-value INI-file like settings that systemd uses to define the execution environment of the processes that it will spawn.

Override

On systems with systemd, daemons are spawned and monitored using unit configuration files. These files define the execution environment of processes that get spawned as part of the unit.

Upstream projects and distributions offer configuration files along with the software, and gets updated either by upstream when software changes or distribution to better integrate it with the rest of the system.

Overrides are a way for system administrators to add even more parameters to the unit without having to modify the system-provided configuration file.

Typically, one would leave the task of bringing up the daemon to a working state to the system-provided configuration file, which will be updated when the system gets updated, and define his own parameters in an override file that are relevant for his use case.

For more information about this, refer to the systemd.exec(5) man page.

Limitations

These hardening measures will only be enforced by systemd as long as the processes are in its scope. If an Apache HTTPD process is executed and daemonized directly from a CLI, they will be ineffective. For such cases, creating and curating sandbox profiles for AppArmor or SELinux remains the best option.

Use Case

In this how-to, we’ll briefly discuss how to use those features to quickly and effectively enforce some security measures on processes that are usually reachable through firewalls : Apache HTTP and PHP-FPM.

Steps

To create an override file for a service, use:

# systemctl edit myawesomeprogram.service

systemd will then merge the system configuration file and the override file when managing the process.

Below are the overrides I personally use for Apache HTTPD and PHP-FPM. Here are some of the parameters I define and that I have curated for my use case:

  • Capabilities: leverage the capabilities(7) from the Linux kernel to lock down what the process is allowed to do, even as root;
  • NoNewPrivileges: the process cannot get new privileges ever again, like with setuid();
  • Protect[Something]: restrict what the process can read or write on the system, even if it is privileged;
  • PrivateDevices: the process will see a pseudo /dev mount that only contains Linux Kernel API devices like /dev/random;
  • PrivateTmp: the process will see distincts /tmp and /var/tmp directories mounted on a virtual filesystem, isolating the system’s temp files from it;
  • ReadOnlyPaths/ReadWritePaths: further restrict the read/write privileges of the process even if, at a filesystem level, it is allowed to read/write those paths. In my case, I only allow paths that the process has to write to or else the service crashes;
  • NoExecPaths/ExecPaths: absolute paths to executables that the process will (or will not) be able to execute. In my case, I only allow executables that the process requires or else the service crashes.

For more information about this, again, refer to the systemd.exec(5) man page.

Also, if you allow file uploads or CMS auto updates, be sure to include your webroot path to the list of read write paths.

Override for Apache HTTPD

[Service]
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_CHOWN CAP_KILL CAP_IPC_LOCK
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_CHOWN CAP_KILL CAP_IPC_LOCK

LockPersonality=yes
NoNewPrivileges=yes

MemoryDenyWriteExecute=yes
KeyringMode=private

ProtectProc=invisible
ProtectControlGroups=yes
ProtectHostname=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectClock=yes

PrivateDevices=yes
PrivateTmp=yes

RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
RestrictNamespaces=~mnt
RestrictRealtime=yes
RestrictSUIDSGID=yes

ReadOnlyPaths=/
ReadWritePaths=[webroot] /var/cache/apache2 /var/log/apache2 /var/lib/apache2 /var/lock /var/run /run
InaccessiblePaths=-/boot -/lost+found -/etc/default -/etc/php -/etc/apt -/etc/shadow -/etc/sudoers -/etc/sysctl.conf -/etc/sysctl.d -/var/backups -/var/mail -/var/spool -/var/local -/var/cache/apt -/var/opt
NoExecPaths=/
ExecPaths=/usr/bin/sh /usr/bin/chmod /usr/bin/chown /usr/bin/mv /usr/bin/mktemp /usr/bin/mkdir /usr/bin/rmdir /usr/bin/rm /usr/bin/test /usr/bin/id /usr/sbin/apachectl /usr/sbin/apache2 /usr/lib /usr/lib64

SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service

Override for PHP-FPM

[Service]
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_CHOWN CAP_KILL CAP_IPC_LOCK CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_CHOWN CAP_KILL CAP_IPC_LOCK CAP_DAC_OVERRIDE

LockPersonality=yes
NoNewPrivileges=yes

KeyringMode=private

ProtectProc=invisible
ProtectControlGroups=yes
ProtectHostname=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectClock=yes

PrivateDevices=true
PrivateTmp=true

RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
RestrictNamespaces=~mnt
RestrictRealtime=yes
RestrictSUIDSGID=yes

ReadOnlyPaths=/
ReadWritePaths=[webroot] /var/log/php[version]-fpm.log /var/run/php /var/lib/php /run/php
InaccessiblePaths=-/boot -/lost+found -/etc/default -/etc/apache2 -/etc/apt -/etc/shadow -/etc/sudoers -/etc/sysctl.conf -/etc/sysctl.d -/var/backups -/var/mail -/var/spool -/var/local -/var/cache -/var/opt
NoExecPaths=/
ExecPaths=/usr/bin/sh /usr/bin/sed /usr/bin/update-alternatives /usr/sbin/php-fpm[version] /usr/bin/php /usr/lib /usr/lib64

SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service

I’d like to hear from you!

Security is an endless job, it’s exhausting, it’s frightening, and thinking that the defenses you put on are perfect and got your ass 100% covered is a shot in the foot in the dark ages of ransomware and whatnot.

So, I’d like to hear from you! What do you think of these overrides? Do you also use them to secure your services? Do you use even more parameters? Do you prefer AppArmor or SELinux profiles? Why?

I’d like to know 🙂 Comments are down below, and my e-mail address is on the About Me page.

By Pierre Blazquez

I mess with computers, drink too much coffee and listen to music at max volume.