How To Check for Infected Files Using Nagios Plugins

Published Date Author: , Posted January 25th, 2016 at 8:00:36am

This example shows how to look for infection patterns inside all .php files in a directory tree using find and grep called from a Nagios NRPE plugin written in PERL.

You can adjust the behavior by modifying the script, described at the bottom of this post in the Advanced section.

On the host to be checked, create the two files /usr/lib/nagios/plugins/check_for_infections and /usr/lib/nagios/plugins/infection.patterns using the information below.
Make sure the path location matches your specific OS requirements.

check_for_infections

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
cat /util/check_for_infections 
#!/usr/bin/perl -wT
 
use strict;
use Getopt::Long;
use vars qw($opt_V $opt_h $opt_w $opt_c $opt_H $opt_d $opt_p $opt_v $PROGNAME %ERRORS);
 
## common variables
$PROGNAME = "check_index_php";
%ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);
 
## utility subroutines
sub print_revision ($$) {
    my $commandName = shift;
    my $pluginRevision = shift;
    print "$commandName v$pluginRevision (nagios-plugins 1.4.15)\n";
    print "The nagios plugins come with ABSOLUTELY NO WARRANTY. You may redistribute\ncopies of the plugins under the terms of the GNU General Public License.\nFor more information about these matters, see the file named COPYING.\n";
}
 
sub support () {
    my $support='Send email to nagios-users@lists.sourceforge.net if you have questions\nregarding use of this software. To submit patches or suggest improvements,\nsend email to nagiosplug-devel@lists.sourceforge.net.\nPlease include version information with all correspondence (when possible,\nuse output from the --version option of the plugin itself).\n';
    $support =~ s/@/\@/g;
    $support =~ s/\\n/\n/g;
    print $support;
}
 
sub usage {
    my $format=shift;
    printf($format,@_);
    exit $ERRORS{'UNKNOWN'};
}
 
sub print_usage () {
    print "Usage: $PROGNAME -H <host> -w <warn> -c <crit>\n";
}
 
sub print_help () {
    print_revision($PROGNAME,'1.0');
    print "Copyright (c) 2016 Eric M. Stone, Wyzaerd Consulting, Inc.
 
This plugin reports the presence of a hacked WordPress index.php file.
 
";
    print_usage();
    print "
-H, --hostname=HOST
   Name or IP address of host to check
-w, --warning=INTEGER
   Percentage strength below which a WARNING status will result
-c, --critical=INTEGER
   Percentage strength below which a CRITICAL status will result
-d, --directory=PATH
   Where to start looking, defaults to /
-p, --patternFile=PATH
   List of infection patterns to look for in every file, one per line. defaults to /util/infection.patterns
-v, --verbose
   List the file names that match
";
    support();
}
 
$ENV{'PATH'}='';
$ENV{'BASH_ENV'}=''; 
$ENV{'ENV'}='';
 
### PROCESS THE COMMAND LINE
Getopt::Long::Configure('bundling');
GetOptions
    ("V"   => \$opt_V, "version"    => \$opt_V,
     "h"   => \$opt_h, "help"       => \$opt_h,
     "v"   => \$opt_v, "verbose"    => \$opt_v,
     "w=s" => \$opt_w, "warning=s"  => \$opt_w,
     "c=s" => \$opt_c, "critical=s" => \$opt_c,
     "H=s" => \$opt_H, "hostname=s" => \$opt_H,
     "d=s" => \$opt_d, "directory=s" => \$opt_d,
     "p=s" => \$opt_p, "patternFile=s" => \$opt_p);
 
if ($opt_V) {
    print_revision($PROGNAME,'1.4.15');
    exit $ERRORS{'OK'};
}
 
if ($opt_h) {print_help(); exit $ERRORS{'OK'};}
 
my $verbose = $opt_v ? 1 : 0;
 
($opt_H) || usage("Host name/address not specified\n");
my $host = $1 if ($opt_H =~ /([-.A-Za-z0-9]+)/);
($host) || usage("Invalid host: $opt_H\n");
 
($opt_w) || usage("Warning threshold not specified\n");
my $warning = $1 if ($opt_w =~ /([0-9]{1,2}|100)+/);
($warning) || usage("Invalid warning threshold: $opt_w\n");
 
($opt_c) || usage("Critical threshold not specified\n");
my $critical = $1 if ($opt_c =~ /([0-9]{1,2}|100)/);
($critical) || usage("Invalid critical threshold: $opt_c\n");
 
($opt_d) || ($opt_d = "/") ;
my $dir = $1 if ($opt_d =~ /^(\/.*)/);
(-d "$dir") || usage("Invalid dir: $dir\n");
 
($opt_p) || ($opt_p = "/util/infection.patterns") ;
my $patternFile = $opt_p if ($opt_p =~ /^\//);
(-r "$patternFile") || usage("Invalid pattern file: $patternFile\n");
 
### PERFORM THE SCAN - uses option lower-case ELL
my @result;
chomp(@result = `/bin/grep -RFl -f $patternFile --include "*.php" $dir/*`);
my $found   = scalar @result;
 
### PRINT RESULTS
if ($found) {
    my $plural  = $found == 1 ? '' : 's';
    print "CRITICAL: Found $found Infected File$plural";
    #print(': ', join(", ",@result)) if $found < 10;
    print ("\n", join("\n",@result)) if $verbose;
    print "\n";
} else {
    print "OK: No infected files found.\n";
}
 
## EXIT WITH PROPER CODE
exit $ERRORS{'CRITICAL'} if ($found>=$critical);
exit $ERRORS{'WARNING'} if ($found>=$warning);
exit $ERRORS{'OK'};
cat /util/check_for_infections 
#!/usr/bin/perl -wT

use strict;
use Getopt::Long;
use vars qw($opt_V $opt_h $opt_w $opt_c $opt_H $opt_d $opt_p $opt_v $PROGNAME %ERRORS);

## common variables
$PROGNAME = "check_index_php";
%ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);

## utility subroutines
sub print_revision ($$) {
	my $commandName = shift;
	my $pluginRevision = shift;
	print "$commandName v$pluginRevision (nagios-plugins 1.4.15)\n";
	print "The nagios plugins come with ABSOLUTELY NO WARRANTY. You may redistribute\ncopies of the plugins under the terms of the GNU General Public License.\nFor more information about these matters, see the file named COPYING.\n";
}

sub support () {
	my $support='Send email to nagios-users@lists.sourceforge.net if you have questions\nregarding use of this software. To submit patches or suggest improvements,\nsend email to nagiosplug-devel@lists.sourceforge.net.\nPlease include version information with all correspondence (when possible,\nuse output from the --version option of the plugin itself).\n';
	$support =~ s/@/\@/g;
	$support =~ s/\\n/\n/g;
	print $support;
}

sub usage {
	my $format=shift;
	printf($format,@_);
	exit $ERRORS{'UNKNOWN'};
}

sub print_usage () {
	print "Usage: $PROGNAME -H <host> -w <warn> -c <crit>\n";
}

sub print_help () {
	print_revision($PROGNAME,'1.0');
	print "Copyright (c) 2016 Eric M. Stone, Wyzaerd Consulting, Inc.

This plugin reports the presence of a hacked WordPress index.php file.

";
	print_usage();
	print "
-H, --hostname=HOST
   Name or IP address of host to check
-w, --warning=INTEGER
   Percentage strength below which a WARNING status will result
-c, --critical=INTEGER
   Percentage strength below which a CRITICAL status will result
-d, --directory=PATH
   Where to start looking, defaults to /
-p, --patternFile=PATH
   List of infection patterns to look for in every file, one per line. defaults to /util/infection.patterns
-v, --verbose
   List the file names that match
";
	support();
}

$ENV{'PATH'}='';
$ENV{'BASH_ENV'}=''; 
$ENV{'ENV'}='';

### PROCESS THE COMMAND LINE
Getopt::Long::Configure('bundling');
GetOptions
	("V"   => \$opt_V, "version"    => \$opt_V,
	 "h"   => \$opt_h, "help"       => \$opt_h,
	 "v"   => \$opt_v, "verbose"    => \$opt_v,
	 "w=s" => \$opt_w, "warning=s"  => \$opt_w,
	 "c=s" => \$opt_c, "critical=s" => \$opt_c,
	 "H=s" => \$opt_H, "hostname=s" => \$opt_H,
	 "d=s" => \$opt_d, "directory=s" => \$opt_d,
	 "p=s" => \$opt_p, "patternFile=s" => \$opt_p);

if ($opt_V) {
	print_revision($PROGNAME,'1.4.15');
	exit $ERRORS{'OK'};
}

if ($opt_h) {print_help(); exit $ERRORS{'OK'};}

my $verbose	= $opt_v ? 1 : 0;

($opt_H) || usage("Host name/address not specified\n");
my $host = $1 if ($opt_H =~ /([-.A-Za-z0-9]+)/);
($host) || usage("Invalid host: $opt_H\n");

($opt_w) || usage("Warning threshold not specified\n");
my $warning = $1 if ($opt_w =~ /([0-9]{1,2}|100)+/);
($warning) || usage("Invalid warning threshold: $opt_w\n");

($opt_c) || usage("Critical threshold not specified\n");
my $critical = $1 if ($opt_c =~ /([0-9]{1,2}|100)/);
($critical) || usage("Invalid critical threshold: $opt_c\n");

($opt_d) || ($opt_d = "/") ;
my $dir = $1 if ($opt_d =~ /^(\/.*)/);
(-d "$dir") || usage("Invalid dir: $dir\n");

($opt_p) || ($opt_p = "/util/infection.patterns") ;
my $patternFile = $opt_p if ($opt_p =~ /^\//);
(-r "$patternFile") || usage("Invalid pattern file: $patternFile\n");

### PERFORM THE SCAN - uses option lower-case ELL
my @result;
chomp(@result = `/bin/grep -RFl -f $patternFile --include "*.php" $dir/*`);
my $found	= scalar @result;

### PRINT RESULTS
if ($found) {
	my $plural	= $found == 1 ? '' : 's';
	print "CRITICAL: Found $found Infected File$plural";
	#print(': ', join(", ",@result)) if $found < 10;
	print ("\n", join("\n",@result)) if $verbose;
	print "\n";
} else {
	print "OK: No infected files found.\n";
}

## EXIT WITH PROPER CODE
exit $ERRORS{'CRITICAL'} if ($found>=$critical);
exit $ERRORS{'WARNING'} if ($found>=$warning);
exit $ERRORS{'OK'};

infection.patterns

1
2
3
4
5
6
nike
jersey
Chr(rand
base64_decode(str_rot13
eval(base64
eval($_POST
nike
jersey
Chr(rand
base64_decode(str_rot13
eval(base64
eval($_POST

Then, on the monitored host, edit the NRPE config file, add the command below, and restart NRPE:

vim /etc/nagios/nrpe.cfg
command[check_for_infections]=/usr/lib/nagios/plugins/check_for_infections -w 1 -c 1 -d /data
service nrpe restart

Finally, update the main Nagios server services definitions file, then restart the Nagios server daemon.

This is what I added to my services.cfg:

1
2
3
4
5
6
7
8
define service{
    service_description         Check for Infections
    servicegroups               aws_hosts
    host_name                   myWatchedHost123
    check_command               check_nrpe!check_for_infections
    contact_groups              admin
    use                         generic-service
    }
define service{
    service_description         Check for Infections
    servicegroups               aws_hosts
    host_name                   myWatchedHost123
    check_command               check_nrpe!check_for_infections
    contact_groups              admin
    use                         generic-service
    }

service nagios restart

ADVANCED: The key line of the script is:
chomp(@result = `/bin/grep -R -o -F -f $patternFile --include "*.php" $dir/*`);
Please note that the command is looking for all items listed in the pattern file. You could make several enhancements, including the ability to pass the include filespec (currently *.php) into the script via command-line arguments.

As always, YMMV!

This is a great troubleshooting document: https://assets.nagios.com/downloads/nagiosxi/docs/NRPE-Troubleshooting-and-Common-Solutions.pdf

No comments as yet.

Leave Your Comment  Leave a comment

All fields marked with "*" are required.