Hacking Linux Exposed

About
Authors
Contents
Reviews
Foreword
Purchase

Articles
Books
Sourcecode
Tools
Errata

Home

 


previous article
index
next article
Running programs in response to sniffed DNS packets - stealthily managing iptables rules remotely, Part 2
By Bri Hatch.

Summary: In combination to the watch_dns program which uses Net::Pcap to sniff DNS packets, we complete our stealthy remote execution setup.

Last time we set up a Perl script that would use the Net::Pcap module to sniff the network and print information about DNS requests to standard output. The output looks like this

   sourceipaddr -> destipaddr: dnshostname

The handle_remote_dns_commands program below will run watch_dns and act on the output. This program could be modified to suit any purpose. Currently it is set up to manipulate a new iptables chain called allow_in which is called before packets are DROPped. Remember, this chain must be created with the following command at a point in your iptables setup before inbound SYN packets are DROPped.[1]

  # iptables --new-chain allow_in
  # iptables -A INPUT -j allow_in

The code below is pretty well commented, so rather than breaking it up to discuss the components, just read the comments. After all, code is speech.


#!/usr/bin/perl
#
# handle_remote_dns_commands
#
# Copyright 2003, Bri Hatch, released under the GPL.
#
#  This program allows you to run commands based on DNS requests that are
#  sniffed by the 'watch_dns' command described at
#  http://www.hackinglinuxexposed.com/articles/20030730.html
#
#  It reads lines from the watch_dns DNS sniffer which are of the form
#
#         sourceipaddr -> destipaddr: dnshostname
#
#  The actual commands rules that are run are found by looking up the
#  dnshostname in the %mapping hash.  The command to be run (the
#  associated hash value) is an anonymous array.  The array values
#  are analysed, and if the string SSSSSSSS or DDDDDDDD is present
#  it is replaced by the source or destination IP address, respectively.
#
#  While the current example is for maintaining an iptables chain,
#  there's no reason you couldn't use it to run any other commands, such
#  as triggering an outbound SSH connection with remote forward back into
#  the machine itself, etc.  Use your imagination.
#
# BUGS
#
#   The script doesn't actually verify an IP address is valid - ie it should
#   reject "999.888.777.666" because it's outside the valid range.  The
#   command you call should be able to deal appropriately with invalid IP
#   addresses.
#
#   Since most host/nslookup/dig tools will retry DNS queries if no
#   response is received, you may end up calling the command multiple
#   times.  For iptables rules, this isn't terribly important.
#
#   Using SIGALRM isn't the most brilliant method to periodically call our
#   periodic commands.  As written, if a cracker knows valid commands, he
#   can spew DNS commands that will cause them to be run very quickly.  In
#   the case of iptables cleaning, this would make it difficult for a
#   legitimate connection to get in.  However if the SIGALRM were written
#   to always cause an X second timer without the fancy footwork, then
#   said cracker could cause our chain to grow infinitely long, eventually
#   maxing it out and causing a DoS, because the chain would never be
#   cleared.  The code used is probably best, since it will fail closed.
#
#   There is obviously no authentication or encryption whatsoever.  If
#   someone gleans your magical commands, they can cause the associated
#   commands to be run.

use strict ;

# If $PERIODIC_SECONDS nonzero, then every (this many) seconds we should
# run the commands stored in the @periodic_commands array.
my $PERIODIC_SECONDS = 10;

# Location of watch_dns command (will be run as our UID which must be
# root since it's sniffing.  If you are running this script as a normal
# user, you'll want to run watch_dns via sudo.)
my $WATCH_DNS="/opt/bin/watch_dns";

# Should debug and warning messages be written to STDOUT?
my $DEBUG=0;
my $WARNING=0;



####
# Modify this section to include your actual functionality.

  # Local variables we may need
  my $IPTABLES="/sbin/iptables";
  
  # Which chain should we enter the temporary access rules?
  my $CHAIN="allow_in";
  
  my @periodic_commands = (
	  [ $IPTABLES, '-F', $CHAIN ],
  );
  
  # The actual rules to run when incoming DNS hostnames match.
  #
  my %mapping = (
    openssh =>
    	[ $IPTABLES, '-A', $CHAIN,
	  qw( -p tcp --source SSSSSSSS/32 --dport 22 -j ACCEPT ) ],

    # Other examples.
    #
    #opensmtp =>
    #	[ $IPTABLES, '-A', $CHAIN,
    #     qw( -p tcp --source SSSSSSSS/32 --dport 25 -j ACCEPT ) ],
    #
    #allow-all =>
    #	[ $IPTABLES, '-A', $CHAIN, qw( -p tcp -j ACCEPT ) ],
    #
    #flush.subdomain.example.com =>
    #   [ $IPTABLES, '-A", $CHAIN, '-F' ],
    
  );

# End of purpose-specific modifications.
####


# No more changes needed hereafter.

if ( $PERIODIC_SECONDS ) {
	# Set up an alarm signal handler. When an alarm is received,
	# flush the iptables exceptions table, denying everyone again.
	$SIG{ALRM} = \&run_periodic_commands;
}

# Start our pcap sniffer, and snag it's output.
open WATCH_DNS, "$WATCH_DNS |" or die "Can't start watch_dns";

# Loop indefinitely
while (<WATCH_DNS>) {
    my $iptables_cmd;
    my @iptables_cmd;

    # Let's be very paranoid about checking our input.
    # Both the source and destination IP addresses we
    # get could be forged by an attacker.  Let's at least
    # make sure that they're in the correct form.
    my ($src, $dst, $command) =
	 /^ (\d+ \. \d+ \. \d+ \. \d+) 	# first IP address
	    \s* -> \s*			# arrow
	    (\d+ \. \d+ \. \d+ \. \d+) 	# second IP address
	    : \s* (\S+)			# command (hostname)
	/x;

    unless ( $command ) {               # ignore bad input.
	    print "ignoring $_" if $DEBUG;
	    next;
    }

    if ( @iptables_cmd = @{$mapping{$command}} ) {

    	map { s/DDDDDDDD/$dst/g } @iptables_cmd;
    	map { s/SSSSSSSS/$src/g } @iptables_cmd;

	print "Running @iptables_cmd\n" if $DEBUG;

	# System used with a list doesn't invoke a shell.
	system @iptables_cmd;
	
	if ( $PERIODIC_SECONDS ) {
		# Set the alarm 
		my $oldtimer = alarm($PERIODIC_SECONDS);
		if ( $oldtimer == 1 ) {
			&{$SIG{ALRM}};		# Call immediately
		} elsif ( $oldtimer ) {
			alarm(--$oldtimer);	# Decrement alarm
		}
	}

    } else {
    	print "ignoring $command\n" if $WARNING;
    }
}


sub run_periodic_commands {
	print "run_periodic_commands called.\n" if $DEBUG;

	# System used with a list doesn't invoke a shell.
	for my $command_arrayref ( @periodic_commands ) {
		system @$command_arrayref;
	}
}


Ok, next week I'll show you how to send the DNS query to the sniffer so we can wrap this whole thing up. The end is near...

NOTES:

[1] In the script we created two weeks ago, it could go any place before the DROP rule is called.


Bri Hatch is Chief Hacker at Onsight, Inc and author of Hacking Linux Exposed and Building Linux VPNs. He loves to create functionality from horrible bastardizations of existing protocols. Bri can be reached at bri@hackinglinuxexposed.com.


Copyright Bri Hatch, 2003


This is the August 14, 2003 issue of the Linux Security: Tips, Tricks, and Hackery newsletter. If you wish to subscribe, visit http://lists.onsight.com/ or send email to Linux_Security-request@lists.onsight.com.

previous article
index
next article