Dec
22
Rogue DHCP Detection Plugin for Nagios on RedHat
Filed Under Computers & Tech, Software Development, Security, My Projects on December 22, 2006 at 7:27 pm
I’m pretty sure this is useless on most versions of Linux because the default DHCP plugin that comes with the Nagios Plugins distribution has this functionality and seems to work just fine everywhere except on RedHat-based distros like RHEL, Centos and Fedora Core. On these systems the default plugin does not seem to work and fails to detect any DHCP servers. This plugin is different to the one I gave instructions for before which tests whether a particular DHCP server is answering requests, this plugin finds rogue servers, it will not alert you if any of your actual DHCP servers are down. Hence, you should probably install both. This plugin is not very polished, it is rough and ready but I know it works on RHEL4. If you’re running a different system you may have to do some minor tweaks but this should serve as an excellent starting point none-the-less.
[tags]Nagios, DHCP, RedHat, RHEL, CentOS, Fedora, Linux[/tags]
This plugin uses the RogueDetect Perl module that I mentioned in my adendum yesterday so the first step is to download that and then copy the files DHCPDetect.pm
and OUI.pm
to some folder on your server. I created a folder called roguedetect in my Nagios plugin folder and stuck them in there.
This plugin uses the same hack that my other DHCP plugin uses to get round the issues RedHat seems to have with running SUID Perl scrips. Hence there are two files needed, the actual perl script that does the work, and a wrapper script that uses sudo
to call the main script. Again a line has to be added to /etc/sudoers
to allow Nagios run the script as root. I called the main script dhcp_rogue_check.pl
and the wrapper dhcp_rogue_check.sh
. The code for the wrapper is:
#!/bin/bash /usr/bin/sudo /usr/lib/nagios/plugins/check_rogue_dhcp.pl $1 $2 $3
The line to add in to /etc/sudoers
is:
nagios ALL=NOPASSWD: /usr/lib/nagios/plugins/check_rogue_dhcp.pl *
The code for check_rogue_dhcp.pl
is based on dhcpdetector.pl
from the RogueDetect distribution. The code uses two threads, the first one starts up a listener, sends a DHCP DISCOVER
and then waits for a specified number of seconds before killing the listener. In theory the child process should be able to write to all file handles opened by the parent but on RHEL that does not work so I had to add in a hack to get the results out of the child and into the main thread. The child writes the details of all rogues it finds to a randomly named temporary file in /tmp
which the parent then reads before deleting and formulating it’s response to Nagios. The code is show below and it should need no real editing except perhaps for changing the location that it looks for the RogueDetect Perl modules up near the top.
#!/usr/bin/perl use strict; #for roguedetect use lib qw(/usr/lib/nagios/plugins/roguedetect); use DHCPDetect; use OUI; # # Read args # unless($#ARGV >= 1){ print <<endH; Invalid arguments - at least two arguments must be supplied: 1) The interface to scan from e.g. eth0 2) A list with the MAC addresses of all authorised DHCP servers separated by :: 3) OPTIONAL - the number of seconds to wait for DHCP responses endH exit(3); } my $interface = $ARGV[0]; my @approvedservers = split('::', $ARGV[1]); my $waitFor = 15; if($ARGV[2]){ $waitFor = $ARGV[2]; } #my $interface = 'eth0'; #my @approvedservers = ('00:06:5b:04:eb:6a', '00:0d:66:2f:20:40'); my $debug = 0; my $tempFile = "/tmp/dhcpdetect.".time(); `touch $tempFile`; send_log('5',"using temp file $tempFile"); # # Get the MAC addresses of this host # my @iface = split("\n",`ifconfig | grep "HWaddr"`); my %ifaces = ''; for my $row (@iface) { $row =~ /(eth\d\.?\d*)\s+Link encap:Ethernet\s+HWaddr\s+([\d\:\A-Fa-f]+)\s*/; $ifaces{$1} = $2; } # # Create the DHCPDetect object # my $macaddr = $ifaces{$interface}; # Open a new DHCPDetect object.. my $dhcp = new DHCPDetect( macaddr => $macaddr, interface => $interface, debug => $debug, timeout => 2, approved => \@approvedservers); # # Fork the threads # defined(my $pid = fork) or die "Cannot fork: $!"; unless ($pid) { #child (listener) # Read packets in a loop until killed while(1) { # get 1 packet from scanner $dhcp->scan(); my $server = $dhcp->server_ip; my $reply = $dhcp->reply; my $hwa = $dhcp->svr_hwa; if($dhcp->server_ip eq '') { send_log('3',"Packet recieved but has no source ip"); } elsif(approvedserver($dhcp)) { send_log('3',"Packet from ". $dhcp->svr_hwa ." is identified as from an official DHCP server"); } else { # all above conditions failed, so its a rogue DHCP Server! do_alert($hwa, $server, $reply); } } die("Child is done\n"); } #parent # Catch signals so we can kill the child first $SIG{'QUIT'} = \&sighandler; $SIG{'INT'} = \&sighandler; $SIG{'HUP'} = \&sighandler; $SIG{'TERM'} = \&sighandler; # Fire off a DHCPDISCOVER sleep(1); # give the listener a chance to warm up send_log('3',"Sending a DHCPDISCOVER packet.."); $dhcp->bait(); sleep($waitFor); kill('TERM',$pid); # # read the response from the file and formulate the resonse for nagios # close(OUTFILE); open(INFILE, $tempFile) or die "Failed to open the temporary file for reading: $!\n\n"; my @results = <INFILE>; close(INFILE); #`rm $tempFile`; my $numRogues = scalar(@results); if($numRogues){ print "CRITICAL - $numRogues rogue DHCP servers detected "; foreach my $rogue (@results){ chomp $rogue; print ":: $rogue"; } print "\n"; exit(2); }else{ print "OK - no rogue DHCP servers detected in $waitFor seconds\n"; exit(0); } # # helper functions # # handel signals sent to the child sub sighandler { my $sig = shift; send_log('3',"GOT SIG$sig. Shutting down CHILD ($pid) and self"); kill('TERM', $pid); die("Deth by signal\n"); } # sighandler # check if a server is on the white-list sub approvedserver { my $self = shift; my $s_hwa = $self->svr_hwa(); my $ok = 0; foreach $a (@{$self->approved}) { if($a eq $s_hwa) { send_log('5',"DEBUG: $s_hwa is an approved server, ignoring.."); return(1); } } send_log('5',"DEBUG: $s_hwa is not on the approved server list!"); return(0); } # debug function sub send_log { my $severity = shift @_; my $message = shift @_; my $send_msg = ''; # If the severity is less then or = to current debug level if ($severity <= $debug) { $send_msg = 1; } # If the severity is 0 then we've gotta throw down if ($severity == '0') { $send_msg = 1; } if (!$message) { $message = 'ERROR: No Message Recieved, logging failure'; } # If the above conditions are met and # the send_msg is set then go ahead and # log it if ($send_msg) { print $message."\n"; } } # function to record a rogue server sub do_alert { my $hwa = shift; my $server = shift; my $reply = shift; my $timestamp = localtime(); send_log('3',"Detected a rogue DHCP server! HWA: $hwa"); my $body = "Rogue DHCP Server Detected on $interface -- "; $body .= "HWA: $hwa (" . OUI::oui($hwa). ") - IP: $server -- "; $body .= "$timestamp"; send_log('5', "Sending output to temp file:\n$body"); open(OUTFILE, ">>$tempFile") or die "failed to open temp file $tempFile for writting\n\n"; print OUTFILE $body; close(OUTFILE); return; } # do_alert
Finally you just need to add a command into your Nagios config so you can use the plugin. I have set up the script and command to take three arguments, the first is the interface to send the DHCP DISCOVER
out on, the second is a list of the MAC addresses of all authorized DHCP servers (and any switches that may be forwarding valid DHCP traffic) separated by ::, and the third is the amount of seconds that the listener should listen for responses to the DHCP DISCOVER
. The plugin definition I am using is:
define command{ command_name check_rogue_dhcp command_line /usr/lib/nagios/plugins/check_rogue_dhcp.sh $ARG1$ $ARG2$ $ARG3$ }
Below is an (anonimized) sample of this plugin in use in my DHCP configuration at work:
define service{ host_name some_server service_description rogue-dhcp-check check_command check_rogue_dhcp!eth0!00:00:00:00:00:00::11:11:11:11:11:11!15 contact_groups systems_group use tpl_service_critical }
That’s it really, as usual this comes with no promises of guarantees. It works for me and I hope it’s of some use to others too!
Hello Mr. Busschots. Exucuse me for my english. I’m Italian student.
I doing as tell you, but don’t work. You to be able to send me the plugin check_rogue_detect.pl and DHCPDetect.pm and OUI.pm and tell me where i must modify them, going to put my ip address and mac address in type file (in either file up mention), that is check_rogue_detect.pl, DHCPDetect.pm and OUI.pm.
i use eth1 and not eth0; my address is 143.225.x.x and my mac address is 00:08:a1:x:x:x
All that, i need because i doing a degree thesis on Nagios and i have to make really that thing (object). I hope thah You help me. thank’s a lot.
Meanwhile of a His neighbourly reply, i hope at You a have a nice day.
PS: my name is Oswald (Osvaldo)
Hi Osvaldo,
You are probably missing Perl libraries. If you run check_rogue_detect.pl from the command-line directly you’ll be able to see the errors and see what libraries you are missing. Then a quick trip to CPAN.org should get you everything you need.
Hope that helps,
Bart.
Hi Mr. Busschots.
If i run check_rogue_detect.pl from the command-line directly, obtein the following event:
[root@localhost plugins]# ./check_rogue_dhcp.pl
Can’t locate Net/RawIP.pm in @INC (@INC contains: /usr/lib/nagios/plugins/roguedetect /usr/lib/perl5/site_perl/5.8.8/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.7/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.6/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.5/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.8 /usr/lib/perl5/site_perl/5.8.7 /usr/lib/perl5/site_perl/5.8.6 /usr/lib/perl5/site_perl/5.8.5 /usr/lib/perl5/site_perl /usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.7/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.6/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.5/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.8 /usr/lib/perl5/vendor_perl/5.8.7 /usr/lib/perl5/vendor_perl/5.8.6 /usr/lib/perl5/vendor_perl/5.8.5 /usr/lib/perl5/vendor_perl /usr/lib/perl5/5.8.8/i386-linux-thread-multi /usr/lib/perl5/5.8.8 .) at /usr/lib/nagios/plugins/roguedetect/DHCPDetect.pm line 58.
BEGIN failed–compilation aborted at /usr/lib/nagios/plugins/roguedetect/DHCPDetect.pm line 58.
Compilation failed in require at ./check_rogue_dhcp.pl line 13.
BEGIN failed–compilation aborted at ./check_rogue_dhcp.pl line 13.
What is the error??? what i have to do???
Wait a Your fine reply. Thank’s. Osvaldo
Hi Osvaldo,
The error is that you are missing the Net::RawIP perl module. You can get it on http://www.cpan.org. When you install that you’ll probably find you’re missing a few more modules. Everything you need will be available in CPAN.
Bart.
Hi Mr Busschots.
know to say at me whitch file must downloading??
Precisely, what i have to doing??
1000 thank’s. Osvaldo
Hi Osvaldo,
I don’t mean to be rude but I am exceptionally busy at work at the moment so I really don’t have time to give a tutorial on CPAN. The best I can do is suggest you have a read of the FAQ: http://www.cpan.org/misc/cpan-faq.html
Sorry not to be able to be of more help,
Bart.
[…] Rogue DHCP Detection Plugin for Nagios on RedHat […]
Hi,
Thank you for this good solution Bart. However I have found this perl script can only work with “eth” interfaces. Here is my customization for vlan and wlan types:
John
Fantastic, thanks John!
Bart.