How to Create Jailed Users?

My servers run CentOS: Nginx + PHP-FPM (PHP via Fast-CGI). Every site is on its own VirtualHost.

Currently both Nginx and PHP-FPM run under root. I know this is bad practice, and there is no reason for any of the sites to have access to files outside of their own directory.

How would I go about creating jailed users and instructing nginx && php-fpm to play along accordingly?


Solution 1:

Ok, you're using kind of a weird setup, but just in general:

First off, linux doesn't support true (bsd style) jails (unless you install openvz or vserver), but setting everything to run as a non priviledged user + chroots can very seriously improve security. Running things as a non-root user is essential, chroots are just an (arguably sizable) stumbling block for would-be attackers).

According to the php-fpm site, chrooting is supported via a configuration command. Of course, php-fpm has like, no documentation... If you poke through the source tarball you might find some documentation, or at least an example config. http://php-fpm.org/about/ says that setting the user, group, and chroot are possible. I've never used php-fpm, but it should be fairly commonsense.

To get nginx running as a non-root user, open the nginx configuration file, find the line that starts with "user" and change it to a non privileged user on the system. Create a new user with nologin as a shell or use the nobody user.

Then the process to chroot any daemon is basically as follows:

  • Create a directory for the specific daemon's root
  • Create a skeleton directory structure in the chroot directory (so ./etc, ./usr/lib, etc)
  • Copy over any needed binaries and configuration files (so the nginx.conf, the nginx binary, any helper programs that you'll need)
  • Copy over additional files that are needed inside the chroot. This will be at least a stub of /etc/password file (not the shadow password file, there simply needs to be a way of looking up usernames to uids), /etc/group file, and /etc/localtime (php will complain endlessly if you don't have timezone info).
  • Lastly, run ldd on the binaries that you've copied over. This will give you a list of libraries to copy. Go through this list, and copy the needed shared objects to their equivalent place in the chrooted directory. Try to preserve symlinks, or copy the original file in the place where there was previously a symlink.
  • Create any needed devices with mknod. If you don't know the device numbers, google them (ex: /dev/random is c 1 8, /dev/null is c 2 2)
  • While the paths to things in your config files should stay the same, occasionally they need tweaking. When you are manually chrooting a daemon, everything in the config file should IGNORE the part of the path that will not be visible once you're chrooted, for example /var/log/somelogfile will remain /var/log/somelogfile despite it's new path actually being /chroot/nginx/var/log/somelogfile

Keep in mind that any daemon which can be chrooted via the configuration file will not require this set of steps - the chrooting will be done by the program after the relevant library dependencies and configuration files have been loaded. Which makes life much simpler.

Once you've done this, in theory you should be ready to run nginx (or anything else) chrooted. In your /etc/init.d/ script for nginx, find where the nginx binary is actually run, and change it to use chroot, for example:

$DAEMON -c $CONFIGFILE

becomes

/usr/sbin/chroot /chroot/directory/here $DAEMON -c $CONFIGFILE

Then you can start nginx normally with your init.d script. *If you get an error from chroot like "chroot: cannot run command `/bin/that/actually/exists': No such file or directory" then you're missing some libraries, or something else essential. Anything that causes the binary to fail to run at all will result in that error (actually, one centos box of mine says Operation not permitted instead).

Since I don't have high enough of rep points to post a lot of links, check out www (dot) securityfocus (dot) com (slash) infocus/1694 (securing apache step by step) - it's a different http daemon, it's the same basic steps, at least as far as chrooting goes.

Also keep in mind that your file permissions in the chroot folder need to be attended to - basically as long as the user that nginx runs as can read/deal with your files in the chroot, everything will be fine.

Lastly, as an example of what type things are required in a chroot environment, here's a random list of files from an openwall box that's running a number of things chrooted. I'm using mysql as an example:

localhost!root:/# find /chroot/mysql 
/chroot/mysql
/chroot/mysql/var
/chroot/mysql/var/run
/chroot/mysql/var/run/mysql.sock
/chroot/mysql/var/run/mysqld.pid
/chroot/mysql/var/log
/chroot/mysql/etc
/chroot/mysql/etc/my.cnf
/chroot/mysql/etc/hosts
/chroot/mysql/etc/host.conf
/chroot/mysql/etc/resolv.conf
/chroot/mysql/etc/group
/chroot/mysql/etc/passwd
/chroot/mysql/etc/my.cnf.orig
/chroot/mysql/etc/nsswitch.conf
/chroot/mysql/tmp
/chroot/mysql/lib
/chroot/mysql/lib/libtermcap.so.2
/chroot/mysql/lib/libdl.so.2
/chroot/mysql/lib/libc.so.6
/chroot/mysql/lib/librt.so.1
/chroot/mysql/lib/libpthread.so.0
/chroot/mysql/lib/libz.so.1
/chroot/mysql/lib/libcrypt.so.1
/chroot/mysql/lib/libnsl.so.1
/chroot/mysql/lib/libstdc++.so.6
/chroot/mysql/lib/libm.so.6
/chroot/mysql/lib/libgcc_s.so.1
/chroot/mysql/lib/ld-linux.so.2
/chroot/mysql/lib/libnss_compat.so.2
/chroot/mysql/lib/libnss_files.so.2
/chroot/mysql/lib/libnss_compat-2.3.6.so
/chroot/mysql/lib/libnss_files-2.3.6.so
/chroot/mysql/data
/chroot/mysql/data/mysql
/chroot/mysql/data/mysql/db.frm
/chroot/mysql/data/mysql/db.MYI
/chroot/mysql/data/mysql/db.MYD
[further mysql tables have been omitted]
/chroot/mysql/dev
/chroot/mysql/dev/null
/chroot/mysql/usr
/chroot/mysql/usr/local
/chroot/mysql/usr/local/libexec
/chroot/mysql/usr/local/libexec/mysqld
/chroot/mysql/usr/local/charsets
/chroot/mysql/usr/local/charsets/README
/chroot/mysql/usr/local/charsets/Index.xml
/chroot/mysql/usr/local/charsets/armscii8.xml
/chroot/mysql/usr/local/charsets/ascii.xml
/chroot/mysql/usr/local/charsets/cp1250.xml
/chroot/mysql/usr/local/charsets/cp1251.xml
/chroot/mysql/usr/local/charsets/cp1256.xml
/chroot/mysql/usr/local/charsets/cp1257.xml
/chroot/mysql/usr/local/charsets/cp850.xml
/chroot/mysql/usr/local/charsets/cp852.xml
/chroot/mysql/usr/local/charsets/cp866.xml
/chroot/mysql/usr/local/charsets/dec8.xml
/chroot/mysql/usr/local/charsets/geostd8.xml
/chroot/mysql/usr/local/charsets/greek.xml
/chroot/mysql/usr/local/charsets/hebrew.xml
/chroot/mysql/usr/local/charsets/hp8.xml
/chroot/mysql/usr/local/charsets/keybcs2.xml
/chroot/mysql/usr/local/charsets/koi8r.xml
/chroot/mysql/usr/local/charsets/koi8u.xml
/chroot/mysql/usr/local/charsets/latin1.xml
/chroot/mysql/usr/local/charsets/latin2.xml
/chroot/mysql/usr/local/charsets/latin5.xml
/chroot/mysql/usr/local/charsets/latin7.xml
/chroot/mysql/usr/local/charsets/macce.xml
/chroot/mysql/usr/local/charsets/macroman.xml
/chroot/mysql/usr/local/charsets/swe7.xml
/chroot/mysql/usr/local/share
/chroot/mysql/usr/local/share/mysql
/chroot/mysql/usr/local/share/mysql/english
/chroot/mysql/usr/local/share/mysql/english/errmsg.sys
/chroot/mysql/bin
/chroot/mysql/bin/test
/chroot/mysql/bin/nohup

An example of the setup for a daemon that can be chrooted via it's configuration file is maradns:

localhost!root:/# find /chroot/maradns/
/chroot/maradns/
/chroot/maradns/logger
/chroot/maradns/db.[removed]
/chroot/maradns/db.[removed2]
/chroot/maradns/db.[removed3]

As you can see, maradns did not require much to get it chrooted (actually it just required "chroot_dir = "/chroot/maradns" in the /etc/mararc file.

Anyway, this has been a long and extremely rambling post aimed towards software slightly different from that which you are using, however I hope this information is still useful.

Solution 2:

nginx needs root to bind to port 80 as master process. Its worker processes then run at different user (based on configuration).

To make chrooted nginx and php-fpm play nice is not that difficult - just make sure nginx has a way to access php-fpm (using tcp is easiest) and make sure it passes correct path to php-fpm (relative to chrooted php-fpm, of course).

Additional security tips is to set file permissions properly. Here's how I did it:

condition:

  • say everything is located at /var/www/root
  • php files has extension of .php
  • php-fpm runs as user "php", group "php"
  • nginx runs as user "www", group "www"

permissions:

  • all files have ownership of php:www (owner "php", group "www")
  • directories' permission is 750
  • php files have permission of 600
  • everything else is 640

In short:

chown -R php:www /var/www/root
find /var/www/root -type d -exec chmod 750 {} \;
find /var/www/root -type f -exec chmod 640 {} \;
find /var/www/root -type f -name '*.php' -exec chmod 600 {} \;

Finer permission can be given but this one should be easy enough and won't break anything.