Fix Linux Users Home Permissions with a Cron Job

As a security nut myself, and also a Linux admin, one of my biggest pet peeves is when I've taken the time and care to segment all the users on a server into separate home directories, and then some developer comes along, logs in as root, and changes the ownership of files. Other things can cause this, like Apache, PHP, Mutt, etc.. So I've always used a cron job that executes daily (and on demand) which automatically fixes all the permissions back to what they should be.


Linux Home Directories

Essentially, every website/client has a separate username and home directory, where their site files are located.

drwx------    boxadmin:boxadmin       /web/boxadmin/
drwx------    jkjklol:jkjklol         /web/jkjklol/
drwx------    el33tbill:el33tbill     /web/el33tbill/

/etc/passwd File

In the /etc/passwd file is the list of all the users:



/etc/shells File

The /etc/shells file contains a list of all the shells that a user can use to login with. So if the shell listed in /etc/passwd is not listed here, they cannot login interactively (ftp/shell). Side Note: I restrict website users to only rssh or scponly shells.


Why Cron

This is the way to do it, well, this is how I've been doing it for about 10 years now. The below script is probably the 30th version, it works very well.

With cron I don't have to worry or constantly login to the system and run complex find commands, it also prevents my security protection tools from sending me alerts by fixing these ownership issues first.

In summary, I truly love me some cron.

Setup the Cron Script

Make sure you have the latest version of GNU find or you won't be able to use the find /web/el33tbill -mount \( ! -user el33tbill -o ! -group el33tbill \) syntax.

$ find --version
find (GNU findutils) 4.4.2

Then save the below script as /etc/cron.daily/fix_user_home_dir_owner_perms.cron or something like that. I like to give cron scripts the .cron suffix, even though they are just normal shell scripts.

Finally make sure to chown that file to be owned as root:root and also chmod it to 755

$ chown root:root /etc/cron.daily/fix_user_home_dir_owner_perms.cron
$ chmod 755 /etc/cron.daily/fix_user_home_dir_owner_perms.cron

The Script

Copy paste from below or download the file.

As you can see, the chown command (which is the only command that modifies anything) is commented out below. So you can run this first to see what it will find, and then uncomment it to make it live.

The reason for the seemingly complex operation of first searching for all the files with improper ownership and saving to the FOUND variable is one of the benefits of this being my 30th version. Basically the way cron works is it will exectue this file, and if there is any output at all it will usually send that output to the MAILTO= address in /etc/crontab, meaning if there is no output, it was a total success and found no bad perms, if there is output, it will not only fix them, it will also let you know exactly what the files were before fixing them, in a detailed stat like output.

The other thing I am really proud of with this is the first for LINE line. What that sed code does is it grabs the list of shells that are allowed for logins and uses that as a filter against /etc/passwd which means it will only list users that have shells listed. This means things like apache, sendmail, exim, mysql, bin, and all the various other daemons and services that use shells like /bin/false won't show up. The 3rd sed prevents any users with the /sbin/nologin shell from being checked.

WARNING: There is only 1 danger with this script, so if you neglect this warning blame yourself. The only danger is that this will run on a system user like exim, apache, nobody, mysql, etc., which could happen easily if that user's shell is improper. This can sometimes happen (and be a security risk) if a package was installed incorrectly, or is just really outdated. To prevent any danger, run this script as it is below first and verify that the DEBUG: lines show it is only being run for valid users, and not for daemon users. Once you verify that, you can comment the DEBUG line, and uncomment the chown line, and then you won't have to worry about user home directory ownership ever again!


function fix_user_home_dir_owner_perms ()

   for LINE in $( sed -n "\@\(`sed -n '\#^/#p' /etc/shells | sed '\#nologin#d' | sed -e :a -e 'N;s/\n/\\\\|/g;ta'`\)@p" /etc/passwd | sed 's/^\([^:]*\):[^\/]*\(\/[^:]*\):\(.*\)$/UN=\1;HD=\2;/g' );
      # set to blank
      FOUND= UN= HD=;

      # eval the LINE read from /etc/passwd - UN=username;HD=/web/userhome;
      eval $LINE;

      # search users home dir for anything they arent the user:group owner of, if any found print detailed info about it and save to the FOUND var
      FOUND=$( find $HD -mount \( ! -user $UN -o ! -group $UN \) -printf '%.5m %10M %#9u:%-9g %#5U:%-5G [%AD | %TD | %CD] [%Y] %p\n' 2>/dev/null );

      # if results, echo USER - HOME followed by all the results saved in FOUND, then recursively chown home dir of user, showing changes
      [[ ${#FOUND} -gt 1 ]] && echo -e "\n\n --- ${UN}:${UN} - ${HD}\n${FOUND}" #&& chown --preserve-root -cR $UN:$UN $HD

# this runs the function declared above

# exit with success or failure
exit $?


I ran this script manually (after turning off the DEBUG line and uncommenting the chown part) to create some output.

 --- el33tbill:el33tbill - /web/el33tbill
00755 drwxr-xr-x      root:nobody        502:500   [02/07/13 | 02/07/13 | 03/27/13] [d] /web/el33tbill/sites/
changed ownership of `/web/el33tbill/sites/' to el33tbill:el33tbill

Bonus, Extend with Permissions

You can easily modify this script, as I have, to fix the actual permissions of the home directories as well.

My personal preference is to chmod all home directories to 700, which means no-one other than a super-user and the actual linux user who owns that home directory can view the files in it. The caveat to this approach is that in order for Apache to access the site files, I chmod the site directory /web/el33tbill/sites/ to 755.

That means another user who is logged in to the machine via ssh, cannot do $ ls /web/el33tbill or $ ls /web/el33tbill/sites, but they could do $ ls /web/el33tbill/sites/ It's a nice secure way to do it.

The command to add after the eval $LINE line is:

      eval $LINE;
      chmod -c 700 $HD

If you do that you can't just hope that it will work and go off to play solitaire.. That's what a Windows user would do, hence the big joke about "el33tbill" (it's impossible for a Windows user aka bill gates to ever be considered an elite hacker, but a script kiddie el33t sure why not).

You need to verify that your web server still works, and also tail your apache error_log after restarting apache to double check.

Linux bash chmod chown cron linux permissions shell