Please review script for search in all files

I have written a little script to scan users home directories for certain commands located inside a file. The script is setup to include a small help section and allows for passing a username argument to override scanning of all users home directories.

A lot of searching and trial and error has went into this script on my part. I would appreciate it if some of you that actually know what you're doing can take a look at it for me. If you could give me some pointers, ideas, or suggestions I would be grateful.

#!/bin/bash

# default folder we will scan
DIR=/home

# a list of commands we need to check for
# seperate commands with a pipe |
LOOKFOR='@system|@shell_exec|@include|@shell'

function help {
  # setup small help list
  echo -e "Pass an aurgument via command line or run without args."
  echo -e "   example: ./filescanner.sh -u username\n"
  echo -e "  -u\t pass a username to run against a single account."
  echo -e "  -h\t This help message.\n"
}

function scanDir {
  # check if $1 else scan all user homes
  if [ "$1" ]; then
    # do stuff on single user
    if [ -d "$DIR/$1" ]; then
      list="$DIR/$1"
    else
      echo "User not found in the \$HOME directory"
      exit
    fi
  else
    # scan all home directories where a user account exists
    list=`grep $DIR /etc/passwd | cut -d: -f6`
  fi

  echo "Checking files for $LOOKFOR."
  for i in $list; do
    path="$i/public_html"
    if [ -d "$path" ]; then
      # check if our path is a directory
      echo "Checking $i/public_html/"
      grep -H -E $LOOKFOR $i/public_html/* -R | cut -d: -f1
    else
      echo "No files to check; $path not a directory."
    fi
  done
}

while getopts  "hu:" flag
do
  case $flag in
    u )
      # echo "$flag" $OPTIND $OPTARG
      # read flag and pass to scanDir function
      scanDir $OPTARG
      exit
    ;;
    h )
      # if h call help function
      help
      exit
    ;;
    * )
      echo -e "Invalid option. Please use -h for help.\n"
      exit
    ;;
  esac
done

# no options passed scan all
scanDir

Best Regards,
Brandon

Hi Brandon,

I took a look at your script. IMO it is nicely written, but the actual content strikes me as a bit thin to justify a whole script. By and large, the work involved could be accomplished with the following command:

grep -H -E '@system|@shell_exec|@include|@shell' /home/*/public_html/* -R | cut -d: -f1

Also I do not get what the @-signs are for. Is that syntax of the files you are checking?

Yes, the @symbols are part of what is being checked. The current list is just preliminary and is not complete.

To give a little background. I run a small hosting company. One of my clients was running Zen Cart and it was exploited. While I was doing some of the forensic investigation to find out the cause and the damage, I thought a script to search for certain keywords used in the attack scripts could come in handy.

Once I make sure everything is coded the right way I plan on sharing this with other server admins in a similar situation. Which is why the added stuff to allow for checking a single users public_html directory exists.

I started off with a one liner but during testing found some issues. Checking /home/*/public_html has some drawbacks to my particular situation. I am running a Cpanel server and there are a few non-user directories located in the /home/.

Thanks for the feedback.
Brandon

P.S. Thank You. I have to admit this is a testament to the quality of help available on this site and others like it.

Just a small suggestion to that.

Instead of grep and cut - try using awk - one process without any kernel data structure.

Thanks for the reply. Regarding "one process without any kernel data structure", can you elaborate on what you said or post a link? I don't understand what you are talking about. I guess I am going a little deeper down the rabbit hole then I have been before.
edit: I Googled it Kernel Data Structures

I have seen a few posts saying cut is faster then awk and awk is more flexible. To be honest, I don't have a clue. The best I could come up with using awk, was only for part of the process. When I tried using awk in a similar fashion to my existing code I was getting directory errors since part of what was being passed was a directory. I did try something on my own using cut to get multiple fields, I couldn't figure out how to do it or if it was possible.

So this is the sample awk code I have.

grep -n -E $LOOKFOR $i/public_html/* -R | awk -F":" '{print $1, $2, $3}'

I will say awk is more flexible than cut in this instance. It took me awhile just to figure that out. Could you post an example of what you were referring to, without using grep or cut?

Currently, I am getting output similar to this using awk or cut.

Checking /home/user/public_html/
/home/user/public_html/feed1.php 467 @include("FeedForAll_rss2html_pro.php");
/home/user/public_html/feed1.php 483 @include("FeedForAll_Scripts_CachingExtension.php");
/home/user/public_html/feed1.php 485 @include_once("FeedForAll_XMLParser.inc.php");
/home/user/public_html/feed2.php 467 @include("FeedForAll_rss2html_pro.php");
/home/user/public_html/feed2.php 483 @include("FeedForAll_Scripts_CachingExtension.php");
/home/user/public_html/feed2.php 485 @include_once("FeedForAll_XMLParser.inc.php");
/home/user/public_html/feed3.php 467 @include("FeedForAll_rss2html_pro.php");
/home/user/public_html/feed3.php 483 @include("FeedForAll_Scripts_CachingExtension.php");
/home/user/public_html/feed3.php 485 @include_once("FeedForAll_XMLParser.inc.php");

Is there a way to get output more like this?

Checking /home/user/public_html/
/home/user/public_html/feed1.php
   467 @include("FeedForAll_rss2html_pro.php");
   483 @include("FeedForAll_Scripts_CachingExtension.php");
   485 @include_once("FeedForAll_XMLParser.inc.php");
/home/user/public_html/feed2.php
   467 @include("FeedForAll_rss2html_pro.php");
   483 @include("FeedForAll_Scripts_CachingExtension.php");
   485 @include_once("FeedForAll_XMLParser.inc.php");
/home/user/public_html/feed3.php
   467 @include("FeedForAll_rss2html_pro.php");
   483 @include("FeedForAll_Scripts_CachingExtension.php");
   485 @include_once("FeedForAll_XMLParser.inc.php");

Best Regards,
Brandon

---------- Post updated at 06:23 AM ---------- Previous update was at 01:23 AM ----------

I have finally came up with this and it works ok but it is getting a little unruly.
edit: Broken after pipe for less scrolling and removed a few spaces too.

grep -InE $LOOKFOR $i/public_html/* -R |
awk -F":" '!a[$1]++{gsub(/ */," ",$3); print $1,"\n Line:" $2, $3; next} {gsub(/ */," ",$3); print" Line:" $2, $3}'

How do I remove tabs and double spaces?

I think there has to be a better way then this. I just don't know what it is.
Any help is appreciated.

Regards,
Brandon

How about:

grep -InE $LOOKFOR $i/public_html/* -R |awk -F: 'NR==1{print;next} p!=$1{p=$1;print p}{print "  "$2,$3}'

Thanks for the info.

I made a small change.
Broken after pipe to reduce screen scrolling.

grep -InE $LOOKFOR $i/public_html/* -R |
awk -F: 'NR==1{print $1,"\n" $2,$3; next} p!=$1{p=$1; print p} {print "   " $2,$3}'

I get output like this

/home/openauto/public_html/wiki/lib/tpl/default/main.php
   34   <?php /*old includehook*/ @include(dirname(__FILE__).'/meta.html')?>
   38 <?php /*old includehook*/ @include(dirname(__FILE__).'/topheader.html')?>
   55     <?php /*old includehook*/ @include(dirname(__FILE__).'/header.html')?>
/home/openauto/public_html/wiki/lib/tpl/sidebar/main.php
   36   <?php /*old includehook*/ @include(dirname(__FILE__).'/meta.html')?>
   40 <?php /*old includehook*/ @include(dirname(__FILE__).'/topheader.html')?>
   60     <?php /*old includehook*/ @include(dirname(__FILE__).'/header.html')?>

Is there an easy way to get consistent column widths for fields $2 $3?

Something like this is what I am after, The $3 has a variable length of whitespace and tabs that I would like to remove if at all possible, then add something like 4 spaces back in front.

/home/openauto/public_html/wiki/lib/tpl/default/main.php
   34    <?php /*old includehook*/ @include(dirname(__FILE__).'/meta.html')?>
   38    <?php /*old includehook*/ @include(dirname(__FILE__).'/topheader.html')?>
   55    <?php /*old includehook*/ @include(dirname(__FILE__).'/header.html')?>
/home/openauto/public_html/wiki/lib/tpl/sidebar/main.php
   36    <?php /*old includehook*/ @include(dirname(__FILE__).'/meta.html')?>
   40    <?php /*old includehook*/ @include(dirname(__FILE__).'/topheader.html')?>
   123   <?php /*old includehook*/ @include(dirname(__FILE__).'/header.html')?>

Thanks again for the help.
Regards,
Brandon

This might work:

grep -InE $LOOKFOR $i/public_html/* -R |
awk -F':[ \t]*' 'p!=$1{p=$1;print p}{print "  "$2,$3}' infile