KrnlPanic's Linux Notes and Tips

Working with linux since kernel version 2.0.30

Monitor Filesystem for Changed Files

When running a webserver, it is important to understand the state of the files that are stored on your system. If you run wordpress or a shopping cart system or a forum or any other software that has potential vulnerabilities, you server is subject to having files written to the filesystem without your knowledge by expoloitation of these vulnerabilities which include SQL Injection attacks, Cross-Site Scripting (XSS) or other possible attack vectors.

Previously, I have just used the find command in a cron job to get the information that I needed and I let cron report it out via root email. However, this looked a bit messy and didn’t provide a mechanism to adjust the command easily (i.e. to add exclude directories, etc). Here’s what my previous hack looked like, which runs once per day at 1600 (4PM) and searched the user’s home directory for any file that was modified within the last day, which excluding via ‘grep -v’ and file strings that I didn’t want to see reported.

00 16 * * * root find /home/username -type f -mtime -1 | grep -v wp-content/cache |grep -v "logs\/old" | grep -v temp_images | grep -v /home/user/tmp | grep -v /home/user/mail | grep -v gz

Clearly, this isn’t pretty, and when I went to migrate to a new server this past week, I was re-implementing some things and went searching for a better solution. I found the following python script at the forums, and decided to give it a run. Needless to say, I like it enough that I decided to post it up so that I won’t lose it for future needs, and hopefully it will do you some good as well.

The following python script uses cpanel user data from /var/cpanel/users and searches each user’s home directory for files that have changed within the last 180 minutes (3 hours). I have this set to run in cron every 3 hours, and it does a great job reporting on updates that have been made to the filesystem. I can add excludes as it goes along and I see trends regarding directories that are frequently updated (wordpress theme updates and cache directories, for example).

import smtplib
from email.mime.text import MIMEText

# Use the following cron entry to run this once every 3 hours:
# 0 */3 * * * root /home/user/bin/

# This is the send mail function
# it takes 3 strings: destination address, subject, and text as input
# Fill here your email account details i use the same server.
def send_mail(to_address, subject, text):
   from_address = 'user@yourdomain.tld'
   msg = MIMEText(text)
   msg['subject'] = subject
   msg['From'] = from_address
   msg['To'] = to_address

   s = smtplib.SMTP('localhost:587')
  # s.login('sender@yourdomain.tld', 'your_email_password')
   s.sendmail(from_address, to_address, msg.as_string())

import os
import subprocess
import re
import string

# this list used to exclude files that starts with this string
# keep in mind that you place here the path after /home/user/public_html
# for example if you want to exclude /home/user/public_html/wp-content/cache you just write '/wp-content/cache'
excluded_paths = ['/wp-content/cache',

# It takes the output of find command in one string,
def check_lines(output, userpath):
   new_output = ""
   # split the output string to check line by line
   lines = output.split('\n')
   for line in lines:
      exclude_it = False
      # here is where the exclusion list filter works
      for ex_path in excluded_paths:
         if (re.match(userpath + ex_path , line) != None):
            exclude_it = True
      # filter out files ends with error_log
      if (re.match(r'(.*error_log$)', line) != None):
         exclude_it = True
      if (exclude_it == False) and (line !=''):
         new_output += line + '\n'
   return new_output

user_list = os.listdir("/var/cpanel/users/")

message = ""

for user in user_list:
   userpath = "/home/" +user+ "/public_html"
   # here is the find command, -182 is how many minutes back
   # i choose to see changes every 3 hours, i run this script every 3 hours on my server thats why i have 182
   p = subprocess.Popen(["find", userpath, "-name", "*", "-mmin", "-182", "-print"], stdout = subprocess.PIPE)
   output, err = p.communicate()
   if (output != ""):
      processed_output = check_lines(output, userpath)
      if (processed_output != ''):
         message += "username: " + user + '\n'
         message += processed_output + '\n'

# where to send the email and what subject
send_mail('user@yoursite.tld', 'Filesystem changes report', message)

I hope you find this useful!