TIP: mtree HIDS script

Summary

I wrote a quick mtree HIDS script to supplement the concept I alluded to in this post.

As a HIDS, this is nowhere near as flexible as some of the better third-party apps (e.g. AIDE), but it does offer the following benefits:
  • It uses only tools in the FreeBSD base system (sh, mtree, etc.).
  • It allows you to check and (re-)initialize just part (i.e. a single directory) of your HIDS db, or the entire thing, as needed.
  • It's quick to set up and easy to use.
  • When the autocheck is run as a cronjob, it will not send you needless chatter. It only makes noise when a difference is discovered.

Do not treat this alone as a comprehensive HIDS solution. A proper implementation would include facilities to keep the HIDS db on removable media and/or to encrypt and/or sign the HIDS db files.

(In my case, I am using this from a host system to check integrity on certain jail directories.)

Usage

Code:
# ./mtree-hids.sh help
Usage: ./mtree-hids.sh help
       ./mtree-hids.sh (init|check) directory
       ./mtree-hids.sh (autoinit|autocheck)
----------------------
 help      : prints this message
 init      : creates mtree database for a single directory
 check     : checks a single directory against its mtree database
 autoinit  : creates mtree databases for all directories
             in /usr/local/mtreedb/dirlist.txt
 autocheck : checks all directories in /usr/local/mtreedb/dirlist.txt
             against their mtree databases

Be sure to manually create /usr/local/mtreedb/dirlist.txt. I recommend changing /usr/local/mtreedb ownership to root, and settings its mode with the octal 0700.

Example dirlist.txt (used by autoinit & autocheck)

Code:
# use absolute pathnames
/bin
/usr/bin
/usr/local/bin

/sbin
/usr/sbin
/usr/local/sbin

/lib
/usr/lib
/usr/local/lib

/libexec
/usr/libexec
/usr/local/libexec

/etc
/usr/local/etc

# add any others...

The script

Code:
#!/bin/sh

# Author: anomie
# Date  : 2009-09-15

# Please see copyright at bottom of script

PATH=/bin:/usr/bin:/sbin:/usr/sbin

# ---------------------------------------------------------------------------
# Variable assignments
# ---------------------------------------------------------------------------

_keywords='uid,gid,mode,sha1digest,flags' # see mtree(8) manpages
_dbpath='/usr/local/mtreedb'
_dirlist='/usr/local/mtreedb/dirlist.txt'

# ---------------------------------------------------------------------------
# Functions
# ---------------------------------------------------------------------------

print_usage() {

  echo "Usage: ${0} help"
  echo "       ${0} (init|check) directory"
  echo "       ${0} (autoinit|autocheck)" 

}

print_help() {

  echo "----------------------"
  echo " help      : prints this message"
  echo " init      : creates mtree database for a single directory"
  echo " check     : checks a single directory against its mtree database"
  echo " autoinit  : creates mtree databases for all directories" 
  echo "             in ${_dirlist}"
  echo " autocheck : checks all directories in ${_dirlist}" 
  echo "             against their mtree databases"

}

audit_and_name_db() {

  if [ -z "${_dir}" ] ; then 
    print_usage
    exit 1
  fi

  if [ ! -d "${_dir}" ] ; then
    echo "${0}: there is no directory ${_dir}"
    exit 1
  fi

  #
  # Give our mtree db file a name
  #
  _db="${_dbpath}/db-$(echo ${_dir} | tr '/' '_')"
  

}

audit_auto() {

  if [ ! -f "${_dirlist}" ] ; then
    echo "${0}: ${_dirlist} does not exist."                   
    echo "Auto mode can not work without it."
    exit 1
  fi

  if [ ! -s "${_dirlist}" ] ; then
    echo "${0}: ${_dirlist} is empty." 
    echo "Nothing to do here." 
    exit 1
  fi

}

create_db() {

  echo "Creating new mtree db file for ${_dir}...."

  mtree -p ${_dir} -k ${_keywords} -c > ${_db}

  if [ ${?} -ne 0 ] ; then
    echo "${0}: problem creating mtree db file for ${_dir}."
    exit 1
  fi

}

check_integrity() {
  #
  # Check directory integrity against existing mtree database
  #
  if [ ! -e "${_db}" ] ; then
    echo "${0}: no mtree db found at ${_db}."
    echo "Check your path, or init a new db file."
    exit 1
  fi

  mtree -p ${_dir} -f ${_db} > ${_db}.tmp.output

  _rc=${?}

  if [ ${_rc} -eq 2 ] || [ -s "${_db}.tmp.output" ] ; then
    echo '--------'
    echo "WARNING: mtree found differences for ${_dir}"
    echo '--------'
    cat ${_db}.tmp.output
    rm -f ${_db}.tmp.output

  elif [ ${_rc} -ne 0 ] ; then
    echo "${0}: error checking integrity of ${_dir}."
    echo "(mtree exited with: ${_rc})"
    exit 1
  fi

  #
  # Clean up the empty file
  #
  rm -f ${_db}.tmp.output

}

create_db_auto() {

  #
  # Note that blank lines and comments (#) are silently ignored
  #
  for I in $(cat ${_dirlist} | egrep -v '^$|^#') ; do

    _dir=${I}
    audit_and_name_db
    create_db

  done

}

check_integrity_auto() {

  #
  # Note that blank lines and comments (#) are silently ignored
  #
  for I in $(cat ${_dirlist} | egrep -v '^$|^#') ; do

    _dir=${I}
    audit_and_name_db
    check_integrity

  done

}

# ---------------------------------------------------------------------------
# Main logic
# ---------------------------------------------------------------------------

#
# This first decision structure handles audits and some variable 
# initialization
#
case "${1}" in

  "help") print_usage
          print_help
          exit 0
          ;;

  "init"|"check") 
          _dir="${2}"
          audit_and_name_db
          ;; 

  "autoinit"|"autocheck")
          audit_auto
          ;;

  *)      print_usage 
          exit 1
          ;;

esac

#
# This second decision structure does the actual work 
#
case "${1}" in

  "init")      create_db
               ;;

  "check")     check_integrity
               ;;

  "autoinit")  create_db_auto
               ;;

  "autocheck") check_integrity_auto
               ;;

  *)           exit 99    # how did you get here? 
               ;;

esac

exit 0

# ---------------------------------
#  Copyright (c) 2009 anomie
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
#  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
#  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
#  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
#  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
#  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#  SUCH DAMAGE.

Preliminary test cases look good. If I discover any bugs I'll post a patch here.
 
mtree hids

Hello,

A couple of years ago, I did the same thing from Dru Lavigne tips (BSD tips); ssh to the host, run mtree and retrieve the output to be stored in a safe place (with any digest that mtree supports, to be sure). Via crontab, re-run the same command, get back the output and do a diff to see what has changed. Simple solution, as you mentioned, with software from the base system of FreeBSD. ;)
 
Back
Top