43 Implementing a Secure Locate


#43 Implementing a Secure Locate

The locate script presented as Script #19 is useful but has a security problem: If the build process is run as root , it builds a list of all files and directories on the entire system, regardless of owner, allowing users to see directories and filenames that they wouldn't otherwise have permission to access. The build process can be run as a generic user (as Mac OS X does, running mklocatedb as user nobody ), but that's not right either, because as a user I want to be able to locate file matches anywhere in my directory tree, regardless of whether user nobody can see them.

One way to solve this dilemma is to increase the data saved in the locate database so that each entry has an owner, group , and permissions string attached, but then the mklocatedb database itself remains insecure unless the locate script is run as either a setuid or setgid script, and that's something to be avoided at all cost.

A compromise is to have a separate locatedb for each user. But it's not quite that bad, because a personal database is needed only for users who actually use the locate command. Once invoked, the system creates a .locatedb file in the user's home directory, and a cron job can update existing .locatedb files nightly to keep them in sync. The very first time someone runs the secure slocate script, it outputs a message warning them that they may see only matches for files that are publicly accessible. Starting the very next day (depending on the cron schedule) the users get their personalized results.

The Code

Two scripts are necessary for a secure locate: the database builder, mkslocatedb , and the actual locate search utility, slocate :

 #!/bin/sh # mkslocatedb - Builds the central, public locate database as user nobody, #    and simultaneously steps through each user's home directory to find those #    that contain an .slocatedb file. If found, an additional, private #    version of the locate database will be created for that user. locatedb="/var/locate.db" slocatedb=".slocatedb" if [ "$(whoami)" != "root" ] ; then   echo " 
 #!/bin/sh # mkslocatedb - Builds the central, public locate database as user nobody, # and simultaneously steps through each user's home directory to find those # that contain an .slocatedb file. If found, an additional, private # version of the locate database will be created for that user. locatedb="/var/locate.db" slocatedb=".slocatedb" if [ "$(whoami)" != "root" ] ; then echo "$0: Error: You must be root to run this command." >&2 exit 1 fi if [ "$(grep '^nobody:' /etc/passwd)" = "" ] ; then echo "$0: Error: you must have an account for user 'nobody'" >&2 echo "to create the default slocate database." >&2; exit 1 fi cd / # sidestep post-su pwd permission problems # First, create or update the public database su -fm nobody -c "find / -print" > $locatedb 2>/dev/null echo "building default slocate database (user = nobody)" echo ... result is $(wc -l < $locatedb) lines long. # Now step through the user accounts on the system to see who has # a $slocatedb file in their home directory.... for account in $(cut -d: -f1 /etc/passwd) do homedir="$(grep "^${account}:" /etc/passwd  cut -d: -f6)" if [ "$homedir" = "/" ] ; then continue # refuse to build one for root dir elif [ -e $homedir/$slocatedb ] ; then echo "building slocate database for user $account" su -fm $account -c "find / -print" > $homedir/$slocatedb \ 2>/dev/null chmod 600 $homedir/$slocatedb chown $account $homedir/$slocatedb echo ... result is $(wc -l < $homedir/$slocatedb) lines long. fi done exit 0 
: Error: You must be root to run this command." >&2 exit 1 fi if [ "$(grep '^nobody:' /etc/passwd)" = "" ] ; then echo "
 #!/bin/sh # mkslocatedb - Builds the central, public locate database as user nobody, # and simultaneously steps through each user's home directory to find those # that contain an .slocatedb file. If found, an additional, private # version of the locate database will be created for that user. locatedb="/var/locate.db" slocatedb=".slocatedb" if [ "$(whoami)" != "root" ] ; then echo "$0: Error: You must be root to run this command." >&2 exit 1 fi if [ "$(grep '^nobody:' /etc/passwd)" = "" ] ; then echo "$0: Error: you must have an account for user 'nobody'" >&2 echo "to create the default slocate database." >&2; exit 1 fi cd / # sidestep post-su pwd permission problems # First, create or update the public database su -fm nobody -c "find / -print" > $locatedb 2>/dev/null echo "building default slocate database (user = nobody)" echo ... result is $(wc -l < $locatedb) lines long. # Now step through the user accounts on the system to see who has # a $slocatedb file in their home directory.... for account in $(cut -d: -f1 /etc/passwd) do homedir="$(grep "^${account}:" /etc/passwd  cut -d: -f6)" if [ "$homedir" = "/" ] ; then continue # refuse to build one for root dir elif [ -e $homedir/$slocatedb ] ; then echo "building slocate database for user $account" su -fm $account -c "find / -print" > $homedir/$slocatedb \ 2>/dev/null chmod 600 $homedir/$slocatedb chown $account $homedir/$slocatedb echo ... result is $(wc -l < $homedir/$slocatedb) lines long. fi done exit 0 
: Error: you must have an account for user 'nobody'" >&2 echo "to create the default slocate database." >&2; exit 1 fi cd / # sidestep post-su pwd permission problems # First, create or update the public database su -fm nobody -c "find / -print" > $locatedb 2>/dev/null echo "building default slocate database (user = nobody)" echo ... result is $(wc -l < $locatedb) lines long. # Now step through the user accounts on the system to see who has # a $slocatedb file in their home directory.... for account in $(cut -d: -f1 /etc/passwd) do homedir="$(grep "^${account}:" /etc/passwd cut -d: -f6)" if [ "$homedir" = "/" ] ; then continue # refuse to build one for root dir elif [ -e $homedir/$slocatedb ] ; then echo "building slocate database for user $account" su -fm $account -c "find / -print" > $homedir/$slocatedb \ 2>/dev/null chmod 600 $homedir/$slocatedb chown $account $homedir/$slocatedb echo ... result is $(wc -l < $homedir/$slocatedb) lines long. fi done exit 0

The slocate script itself is the user interface to the slocate database:

 #!/bin/sh # slocate - Tries to search the user's own secure slocatedb database for the #    specified pattern. If no database exists, outputs a warning and creates #    one. If personal slocatedb is empty, uses system one instead. locatedb="/var/locate.db" slocatedb="$HOME/.slocatedb" if [ ! -e $slocatedb -o "" = "--explain" ] ; then   cat << "EOF" >&2 Warning: Secure locate keeps a private database for each user, and your database hasn't yet been created. Until it is (probably late tonight) I'll just use the public locate database, which will show you all publicly accessible matches, rather than those explicitly available to account ${USER:-$LOGNAME}. EOF   if [ "" = "--explain" ] ; then     exit 0   fi   # Before we go, create a .slocatedb so that cron will fill it   # the next time the mkslocatedb script is run   touch $slocatedb      # mkslocatedb will build it next time through   chmod 600 $slocatedb  # start on the right foot with permissions elif [ -s $slocatedb ] ; then   locatedb=$slocatedb else   echo "Warning: using public database. Use \" 
 #!/bin/sh # slocate - Tries to search the user's own secure slocatedb database for the # specified pattern. If no database exists, outputs a warning and creates # one. If personal slocatedb is empty, uses system one instead. locatedb="/var/locate.db" slocatedb="$HOME/.slocatedb" if [ ! -e $slocatedb -o "$1" = "--explain" ] ; then cat << "EOF" >&2 Warning: Secure locate keeps a private database for each user, and your database hasn't yet been created. Until it is (probably late tonight) I'll just use the public locate database, which will show you all publicly accessible matches, rather than those explicitly available to account ${USER:-$LOGNAME}. EOF if [ "$1" = "--explain" ] ; then exit 0 fi # Before we go, create a .slocatedb so that cron will fill it # the next time the mkslocatedb script is run touch $slocatedb # mkslocatedb will build it next time through chmod 600 $slocatedb # start on the right foot with permissions elif [ -s $slocatedb ] ; then locatedb=$slocatedb else echo "Warning: using public database. Use \"$0 --explain\" for details." >&2 fi if [ -z "$1" ] ; then echo "Usage: $0 pattern" >&2; exit 1 fi exec grep -i "$1" $locatedb 
--explain\" for details." >&2 fi if [ -z "" ] ; then echo "Usage:
 #!/bin/sh # slocate - Tries to search the user's own secure slocatedb database for the # specified pattern. If no database exists, outputs a warning and creates # one. If personal slocatedb is empty, uses system one instead. locatedb="/var/locate.db" slocatedb="$HOME/.slocatedb" if [ ! -e $slocatedb -o "$1" = "--explain" ] ; then cat << "EOF" >&2 Warning: Secure locate keeps a private database for each user, and your database hasn't yet been created. Until it is (probably late tonight) I'll just use the public locate database, which will show you all publicly accessible matches, rather than those explicitly available to account ${USER:-$LOGNAME}. EOF if [ "$1" = "--explain" ] ; then exit 0 fi # Before we go, create a .slocatedb so that cron will fill it # the next time the mkslocatedb script is run touch $slocatedb # mkslocatedb will build it next time through chmod 600 $slocatedb # start on the right foot with permissions elif [ -s $slocatedb ] ; then locatedb=$slocatedb else echo "Warning: using public database. Use \"$0 --explain\" for details." >&2 fi if [ -z "$1" ] ; then echo "Usage: $0 pattern" >&2; exit 1 fi exec grep -i "$1" $locatedb 
pattern" >&2; exit 1 fi exec grep -i "" $locatedb

How It Works

The mkslocatedb script revolves around the idea that the root user can temporarily become another user ID by using su -fm user , and so therefore can run find on the file system of each user in order to create a user-specific database of filenames. Working with the su command proves tricky within this script, though, because by default su not only wants to change the effective user ID but also wants to import the environment of the specified account. The end result is odd and confusing error messages on just about any Unix unless the -m flag is specified, which prevents the user environment from being imported. The -f flag is extra insurance, bypassing the . cshrc file for any csh or tcsh users.

The other unusual notation in mkslocatedb is 2>/dev/null , which routes all error messages directly to the proverbial bit bucket: Anything redirected to /dev/null vanishes without a trace. It's an easy way to skip the inevitable flood of permission denied error messages for each find function invoked.

Running the Scripts

The mkslocatedb script is very unusual in that not only must it be run as root , but using sudo won't cut it. You need to either log in as root or use the more powerful su command to become root before running the script. The slocate script, of course, has no such requirements.

The Results

Building the slocate database for both nobody (the public database) and user taylor on a Red Hat Linux 10.0 box produces the following output:

 #  mkslocatedb  building default slocate database (user = nobody) ... result is 99809 lines long. building slocate database for user taylor ... result is 99808 lines long. 

The same command run on a pretty full Mac OS X box, for comparison, produces the following:

 #  mkslocatedb  building default slocate database (user = nobody) ... result is 240160 lines long. building slocate database for user taylor ... result is 263862 lines long. 

To search for a particular file or set of files that match a given pattern, let's first try it as user tintin (who doesn't have an .slocatedb file):

 tintin $  slocate Taylor-Self-Assess.doc  Warning: using public database. Use "slocate --explain" for details. $ 

Now we'll enter the same command but as user taylor (who owns the file being sought):

 taylor $  slocate Taylor-Self-Assess.doc  /Users/taylor/Documents/Merrick/Taylor-Self-Assess.doc 

Hacking the Script

If you have a very large file system, it's possible that this approach will consume a nontrivial amount of space. One way to address this issue would be to make sure that the individual .slocatedb database files don't contain entries for files that also appear in the central public database. This requires a bit more processing up front ( sort both, and then use diff ), but it could pay off in terms of saved space.

Another technique aimed at saving space would be to build the individual .slocatedb files with references only to files that have been accessed since the last update. This would work better if the mkslocatedb script was run weekly rather than daily; otherwise each Monday all users would be back to ground zero because they're unlikely to have run the slocate command over the weekend .

Finally, another easy way to save space would be to keep the .slocatedb files compressed and uncompress them on the fly when they are searched with slocate . See the zgrep command in Script #37 for inspiration regarding how this technique might be utilized.




Wicked Cool Shell Scripts. 101 Scripts for Linux, Mac OS X, and Unix Systems
Wicked Cool Shell Scripts
ISBN: 1593270127
EAN: 2147483647
Year: 2004
Pages: 150
Authors: Dave Taylor

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net