1#!/usr/local/bin/perl 2# (C) Copyright 1998 Ivan S. Bishop (isb@notoryus.genmagic.com) 3# 4############### START SUBROUTINE DECLARATIONS ########### 5 6 7sub usage { 8 print "\n" x 24; 9 print "USAGE: ipfanalyze.pl -h [-p port# or all] [-g] [-s] [-v] [-o] portnum -t [target ip address] [-f] logfilename\n"; 10 print "\n arguments to -p -f -o REQUIRED\n"; 11 print "\n -h show this help\n"; 12 print "\n -p limit stats/study to this port number.(eg 25 not smtp)\n"; 13 print " -g make graphs, one per 4 hour interval called outN.gif 1<=N<=5\n"; 14 print " -s make security report only (no graphical or full port info generated) \n"; 15 print " -o lowest port number incoming traffic can talk to and be regarded as safe\n"; 16 print " -v verbose report with graphs and textual AND SECURITY REPORTS with -o 1024 set\n"; 17 print " -t the ip address of the inerface on which you collected data!\n"; 18 print " -f name ipfilter log file (compatible with V 3.2.9) [ipfilter.log]\n"; 19 print " \nExample: ./ipfanalyze.pl -p all -g -f log1\n"; 20 print "Will look at traffic to/from all ports and make graphs from file log1\n"; 21 print " \nExample2 ./ipfanalyze.pl -p 25 -g -f log2\n"; 22 print "Will look at SMTP traffic and make graphs from file log2\n"; 23 print " \nExample3 ./ipfanalyze.pl -p all -g -f log3 -o 1024\n"; 24 print "Will look at all traffic,make graphs from file log3 and log security info for anthing talking inwards below port 1024\n"; 25 print " \nExample4 ./ipfanalyze.pl -p all -f log3 -v \n"; 26 print "Report the works.....when ports below 1024 are contacted highlight (like -s -o 1024)\n"; 27} 28 29 30 31 32sub makegifs { 33local ($maxin,$maxout,$lookat,$xmax)=@_; 34$YMAX=$maxin; 35$XMAX=$xmax; 36 37if ($maxout > $maxin) 38 { $YMAX=$maxout;} 39 40($dateis,$junk)=split " " , @recs[0]; 41($dayis,$monthis,$yearis)=split "/",$dateis; 42$month=$months{$monthis}; 43$dateis="$dayis " . "$month " . "$yearis "; 44# split graphs in to 6 four hour spans for 24 hours 45$numgraphs=int($XMAX/240); 46 47$junk=0; 48$junk=$XMAX - 240*($numgraphs); 49if($junk gt 0 ) 50{ 51$numgraphs++; 52} 53 54$cnt1=0; 55$end=0; 56$loop=0; 57 58while ($cnt1++ < $numgraphs) 59{ 60 $filename1="in$cnt1.dat"; 61 $filename2="out$cnt1.dat"; 62 $filename3="graph$cnt1.conf"; 63 open(OUTDATA,"> $filename2") || die "Couldnt open $filename2 for writing \n"; 64 open(INDATA,"> $filename1") || die "Couldnt open $filename1 for writing \n"; 65 66 $loop=$end; 67 $end=($end + 240); 68 69# write all files as x time coord from 1 to 240 minutes 70# set hour in graph via conf file 71 $arraycnt=0; 72 while ($loop++ < $end ) 73 { 74 $arraycnt++; 75 $val1=""; 76 $val2=""; 77 $val1=$inwards[$loop] [1]; 78 if($val1 eq "") 79 {$val1=0}; 80 $val2=$outwards[$loop] [1]; 81 if($val2 eq "") 82 {$val2=0}; 83 print INDATA "$arraycnt:$val1\n"; 84 print OUTDATA "$arraycnt:$val2\n"; 85 } 86 close INDATA; 87 close OUTDATA; 88 $gnum=($cnt1 - 1); 89 open(INCONFIG,"> $filename3") || die "Couldnt open ./graph.conf for writing \n"; 90 print INCONFIG "NUMBERYCELLGRIDSIZE:5\n"; 91 print INCONFIG "MAXYVALUE:$YMAX\n"; 92 print INCONFIG "MINYVALUE:0\n"; 93 print INCONFIG "XCELLGRIDSIZE:1.3\n"; 94 print INCONFIG "XMAX: 240\n"; 95 print INCONFIG "Bar:0\n"; 96 print INCONFIG "Average:0\n"; 97 print INCONFIG "Graphnum:$gnum\n"; 98 print INCONFIG "Title: port $lookat packets/minute to/from gatekeep on $dateis \n"; 99 print INCONFIG "Transparent:no\n"; 100 print INCONFIG "Rbgcolour:0\n"; 101 print INCONFIG "Gbgcolour:255\n"; 102 print INCONFIG "Bbgcolour:255\n"; 103 print INCONFIG "Rfgcolour:0\n"; 104 print INCONFIG "Gfgcolour:0\n"; 105 print INCONFIG "Bfgcolour:0\n"; 106 print INCONFIG "Rcolour:0\n"; 107 print INCONFIG "Gcolour:0\n"; 108 print INCONFIG "Bcolour:255\n"; 109 print INCONFIG "Racolour:255\n"; 110 print INCONFIG "Gacolour:255\n"; 111 print INCONFIG "Bacolour:0\n"; 112 print INCONFIG "Rincolour:100\n"; 113 print INCONFIG "Gincolour:100\n"; 114 print INCONFIG "Bincolour:60\n"; 115 print INCONFIG "Routcolour:60\n"; 116 print INCONFIG "Goutcolour:100\n"; 117 print INCONFIG "Boutcolour:100\n"; 118 close INCONFIG; 119 120} 121 122 123$cnt1=0; 124while ($cnt1++ < $numgraphs) 125{ 126 $filename1="in$cnt1.dat"; 127 $out="out$cnt1.gif"; 128 $filename2="out$cnt1.dat"; 129 $filename3="graph$cnt1.conf"; 130 system( "cp ./$filename1 ./in.dat; 131 cp ./$filename2 ./out.dat; 132 cp ./$filename3 ./graph.conf"); 133 system( "./isbgraph -conf graph.conf;mv graphmaker.gif $out"); 134 system(" cp $out /isb/local/etc/httpd/htdocs/."); 135 136} 137 138} # end of subroutine make gifs 139 140 141 142 143sub packbytime { 144local ($xmax)=@_; 145$XMAX=$xmax; 146# pass in the dest port number or get graph for all packets 147# at 1 minute intervals 148# @shortrecs has form 209.24.1.217 123 192.216.16.2 123 udp len 20 76 149# @recs has form 27/07/1998 00:01:05.216596 le0 @0:2 L 192.216.21.16,2733 -> 192.216.16.2,53 PR udp len 20 62 150# 151# dont uses hashes to store how many packets per minite as they 152# return random x coordinate order 153@inwards=(); 154@outwards=(); 155$cnt=-1; 156$value5=0; 157$maxin=0; 158$maxout=0; 159$xpos=0; 160while ($cnt++ <= $#recs ) 161 { 162 ($srcip,$srcport,$destip,$destport,$pro)= split " " , @shortrecs[$cnt]; 163 $bit=substr(@recs[$cnt],11); 164 ($bit,$junkit)= split " " , $bit ; 165 ($hour,$minute,$sec,$junk) = split ":", $bit; 166# 167# covert the time to decimal minutes and bucket to nearest minute 168# 169 $xpos=($hour * 3600) + ($minute * 60) + ($sec) ; 170# xpos is number of seconds since 00:00:00 on day...... 171 $xpos=int($xpos / 60); 172# if we just want to see all packet in/out activity 173 if("$lookat" eq "all") 174 { 175 if("$destip" eq "$gatekeep") 176 { 177# TO GATEKEEP port lookat 178# print "to gatekeep at $xpos\n"; 179 $value5=$inwards[$xpos] [1]; 180 $value5++ ; 181# $maxin = $value5 if $maxin < $value5 ; 182 183 if($value5 > $maxin) 184 { 185 $maxin=$value5; 186 $timemaxin="$hour:$minute"; 187 } 188 $inwards[$xpos][1]=$value5; 189 } 190 else 191 { 192# FROM GATEKEEP to port lookat 193# print "from gatekeep at $xpos\n"; 194 $value4=$outwards[$xpos] [1]; 195 $value4++ ; 196# $maxout = $value4 if $maxout < $value4 ; 197 if($value4 > $maxout) 198 { 199 $maxout=$value4; 200 $timemaxout="$hour:$minute"; 201 } 202 203 $outwards[$xpos][1]=$value4; 204 } 205 } 206 207 208 209 210 if("$destport" eq "$lookat") 211 { 212 if("$destip" eq "$gatekeep") 213 { 214# TO GATEKEEP port lookat 215# print "to gatekeep at $xpos\n"; 216 $value5=$inwards[$xpos] [1]; 217 $value5++ ; 218 $maxin = $value5 if $maxin < $value5 ; 219 $inwards[$xpos][1]=$value5; 220 } 221 else 222 { 223# FROM GATEKEEP to port lookat 224# print "from gatekeep at $xpos\n"; 225 $value4=$outwards[$xpos] [1]; 226 $value4++ ; 227 $maxout = $value4 if $maxout < $value4 ; 228 $outwards[$xpos][1]=$value4; 229 } 230 } 231 } # end while 232 233# now call gif making stuff 234if("$opt_g" eq "1") 235{ 236 print "Making plots of in files outN.gif\n";; 237 makegifs($maxin,$maxout,$lookat,$#inwards); 238} 239if ("$timemaxin" ne "") 240{print "\nTime of peak packets/minute in was $timemaxin\n";} 241if ("$timemaxout" ne "") 242{print "\nTime of peak packets/minute OUT was $timemaxout\n";} 243 244} # end of subroutine packets by time 245 246 247 248 249 250sub posbadones { 251 252$safenam=""; 253@dummy=$saferports; 254foreach $it (split " ",$saferports) { 255if ($it eq "icmp" ) 256 { 257 $safenam = $safenam . " icmp"; 258 } 259else 260 { 261 $safenam = $safenam . " $services{$it}" ; 262 } 263 264} 265print "\n\n########################################################################\n"; 266print "well known ports are 0->1023\n"; 267print "Registered ports are 1024->49151\n"; 268print "Dynamic/Private ports are 49152->65535\n\n"; 269print "Sites that contacted gatekeep on 'less safe' ports (<$ITRUSTABOVE)\n"; 270 271print " 'safe' ports are $safenam \n"; 272print "\n variables saferports and safehosts hardwire what/who we trust\n"; 273print "########################################################################\n"; 274 275$loop=-1; 276while ($loop++ <= $#recs ) 277 { 278 ($srcip,$srcport,$destip,$destport,$pro)= split " " , @shortrecs[$loop]; 279 if ("$destip" eq "$gatekeep") 280 { 281 if ($destport < $ITRUSTABOVE ) 282 { 283# if index not found (ie < 0) then we have a low port attach to gatekeep 284# that is not to a safer port (see top of this file) 285# ie no ports 25 (smtp), 53 (dns) , 113 (ident), 123 (ntp), icmp 286 $where=index($saferports,$destport); 287 if ($where < 0) 288 { 289 $nameis=$services{$destport}; 290 if ("$nameis" eq "" ) 291 { 292 $nameis=$destport; 293 } 294 print " Warning: $srcip contacted gatekeep $nameis\n"; 295 } 296 } 297 } 298 } 299print "\n\n"; 300} # end of subroutine posbadones 301 302 303 304 305sub toobusy_site { 306$percsafe=1; 307print "\n\n########################################################################\n"; 308print "# Sites sending > $percsafe % of all packets to gatekeep MAY be attacking/probing\n"; 309print "Trusted hosts are $safehosts\n"; 310print "\nTOTAL packets were $#recs \n"; 311print "########################################################################\n"; 312while(($ipadd,$numpacketsent)=each %numpacks) 313{ 314$perc=$numpacketsent/$#recs*100; 315if ($perc > $percsafe) 316# dont believe safehosts are attacking! 317 { 318 $where=index($safehosts,$ipadd); 319# if not found (ie < 0 then the source host IP address 320# isn't in the saferhosts list, a list we trust...... 321 if ($where < 0 ) 322 { 323 printf "$ipadd sent %4.1f (\045) of all packets to gatekeep\n",$perc; 324 } 325 } 326} 327 328print "\n\n"; 329} # end of subroutine toobusy_site 330 331 332############### END SUBROUTINE DECLARATIONS ########### 333 334use Getopt::Std; 335 336getopt('pfot'); 337 338if("$opt_t" eq "0") 339 {usage;print "\n---->ERROR: You must psecify the IP address of the interface that collected the data!\n"; 340exit; 341} 342 343if("$opt_h" eq "1") 344 {usage;exit 0}; 345if("$opt_H" eq "1") 346 {usage;exit 0}; 347 348if("$opt_v" eq "1") 349{ 350$ITRUSTABOVE=1024; 351$opt_s=1; 352$opt_o=$ITRUSTABOVE; 353print "\n" x 5; 354print "NOTE: when the final section of the verbose report is generated\n"; 355print " every host IP address that contacted $gatekeep has \n"; 356print " a tally of how many times packets from a particular port on that host\n"; 357print " reached $gatekeep, and WHICH source port or source portname \n"; 358print " these packets originated from.\n"; 359print " Many non RFC obeying boxes do not use high ports and respond to requests from\n"; 360print " $gatekeep using reserved low ports... hence you'll see things like\n"; 361print " #### with 207.50.191.60 as the the source for packets ####\n"; 362print " 1 connections from topx to gatekeep\n\n\n\n"; 363 364} 365 366if("$opt_o" eq "") 367 {usage;print "\n---->ERROR: Must specify lowest safe port name for incoming trafic\n";exit 0} 368else 369{ 370$ITRUSTABOVE=$opt_o;$opt_s=1;} 371 372if("$opt_f" eq "") 373 {usage;print "\n---->ERROR: Must specify filename with -f \n";exit 0}; 374$FILENAME=$opt_f; 375 376if("$opt_p" eq "") 377 {usage;print "\n---->ERROR: Must specify port number or 'all' with -p \n";exit 0}; 378 379# -p arg must be all or AN INTEGER in range 1<=N<=64K 380if ("$opt_p" ne "all") 381 { 382 $_=$opt_p; 383 unless (/^[+-]?\d+$/) 384 { 385 usage; 386 print "\n---->ERROR: Must specify port number (1-64K) or 'all' with -p \n"; 387 exit 0; 388 } 389 } 390 391 392# if we get here then the port option is either 'all' or an integer... 393# good enough..... 394$lookat=$opt_p; 395 396# -o arg must be all or AN INTEGER in range 1<=N<=64K 397 $_=$opt_o; 398 unless (/^[+-]?\d+$/) 399 { 400 usage; 401 print "\n---->ERROR: Must specify port number (1-64K) with -o \n"; 402 exit 0; 403 } 404 405 406#--------------------------------------------------------------------- 407 408 409%danger=(); 410%numpacks=(); 411 412$saferports="25 53 113 123 icmp"; 413$gatekeep="192.216.16.2"; 414#genmagic is 192.216.25.254 415$safehosts="$gatekeep 192.216.25.254"; 416 417 418 419# load hash with service numbers versus names 420 421# hash called $services 422print "Creating hash of service names / numbers \n"; 423$SERV="./services"; 424open (INFILE, $SERV) || die "Cant open $SERV: $!n"; 425while(<INFILE>) 426{ 427 ($servnum,$servname,$junk)=split(/ /,$_); 428# chop off null trailing..... 429 $servname =~ s/\n$//; 430 $services{$servnum}=$servname; 431} 432print "Create hash of month numbers as month names\n"; 433%months=("01","January","02","February","03","March","04","April","05","May","06","June","07","July","08","August","09","September","10","October","11","November","12","December"); 434 435print "Reading log file into an array\n"; 436#$FILENAME="./ipfilter.log"; 437open (REC, $FILENAME) || die "Cant open $FILENAME: \n"; 438($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$junk)=stat REC; 439print "Log file $FILENAME is $size bytes in size\n"; 440#each record is an element of array rec[] now 441while(<REC>) 442 { 443 @recs[$numrec++]=$_; 444 } 445 446 447# get list of UNIQUE source IP addresses now, records look like 448# 192.216.25.254,62910 -> 192.216.16.2,113 PR tcp len 20 40 -R 449# this is slow on big log files, about 1minute for every 2.5M log file 450print "Making list of unique source IP addresses (1minute for every 2M log parsed)\n"; 451$loop=-1; 452$where=-1; 453while ($loop++ < $#recs ) 454 { 455# get the LHS = source IP address, need fiddle as icmp rcords are logged oddly 456 $bit=substr(@recs[$loop],39); 457 $bit =~ s/,/ /g; 458 ($sourceip,$junkit)= split " " , $bit ; 459 460# NOTE the . is the string concat command NOT + .......!!!! 461 462 $sourceip =~ split " ", $sourceip; 463 $where=index($allips,$sourceip); 464# if not found (ie < 0, add it) 465 if ($where < 0 ) 466 { 467 $allips = $allips . "$sourceip " ; 468 } 469 } 470 471print "Put all unique ip addresses into a 1D array\n"; 472@allips=split " ", $allips; 473 474#set loop back to -1 as first array element in recs is element 0 NOT 1 !! 475print "Making compact array of logged entries\n"; 476$loop=-1; 477$icmp=" icmp "; 478$ptr=" -> "; 479$lenst=" len "; 480$numpackets=0; 481 482while ($loop++ < $#recs ) 483 { 484# this prints from 39 char to EOR 485 $a=substr(@recs[$loop],39); 486 ($srcip,$dummy,$destip,$dummy2,$dummy3,$dummy4,$lenicmp)= split " " , $a ; 487# need to rewrite icmp ping records.... they dont have service numbers 488 $whereicmp=index($a,"PR icmp"); 489 if($whereicmp > 0 ) 490 { 491 $a = $srcip . $icmp . $ptr . $destip . $icmp . $icmp . $lenst . $lenicmp ; 492 } 493 494# dump the "->" and commas from logging 495 $a =~ s/->//g; 496 $a =~ s/PR//g; 497 $a =~ s/,/ /g; 498# shortrec has records that look like 499# 209.24.1.217 123 192.216.16.2 123 udp len 20 76 500 @shortrecs[$loop]= "$a"; 501 502# count number packets from each IP address into hash 503 ($srcip,$junk) = split " ","$a"; 504 $numpackets=$numpacks{"$srcip"}; 505 $numpackets++ ; 506 $numpacks{"$srcip"}=$numpackets; 507 508} 509 510 511 512# call sub to analyse packets by time 513# @shortrecs has form 209.24.1.217 123 192.216.16.2 123 udp len 20 76 514# @recs has form 27/07/1998 00:01:05.216596 le0 @0:2 L 192.216.21.16,2733 -> 192.216.16.2,53 PR udp len 20 62 515packbytime($XMAX); 516 517if("$opt_s" eq "1") 518{ 519# call subroutine to scan for connections to ports on gatekeep 520# other than those listed in saferports, connections to high 521# ports are assumed OK..... 522posbadones; 523 524# call subroutine to print out which sites had sent more than 525# a defined % of packets to gatekeep 526toobusy_site; 527} 528 529 530# verbose reporting? 531if ("$opt_v" eq "1") 532{ 533$cnt=-1; 534# loop over ALL unique IP source destinations 535while ($cnt++ < $#allips) 536{ 537 %tally=(); 538 %unknownsrcports=(); 539 $uniqip=@allips[$cnt]; 540 $loop=-1; 541 $value=0; 542 $value1=0; 543 $value2=0; 544 $value3=0; 545 $set="N"; 546 547 while ($loop++ < $#recs ) 548 { 549# get src IP num, src port number, 550# destination IP num, destnation port number,protocol 551 ($srcip,$srcport,$destip,$destport,$pro)= split " " , @shortrecs[$loop]; 552# loop over all records for the machine $uniqip 553# NOTE THE STRINGS ARE COMPARED WITH eq NOT cmp and NOT = !!!! 554 if( "$uniqip" eq "$srcip") 555 { 556# look up hash of service names to get key... IF ITS NOT THERE THEN WHAT??? 557# its more than likely a request coming back in on a high port 558# ....So... 559# find out the destination port from the unknown (high) src port 560# and tally these as they may be a port attack 561 if ("$srcport" eq "icmp") 562 { $srcportnam="icmp";} 563 else 564 { 565 $srcportnam=$services{$srcport}; 566 } 567# try and get dest portname, if not there, leave it as the 568# dest portnumber 569 if ("$destport" eq "icmp") 570 { $destportnam="icmp";} 571 else 572 { 573 $destportnam=$services{$destport}; 574 } 575 576 if ($destportnam eq "") 577 { 578 $destportnam=$destport; 579 } 580 581 if ($srcportnam eq "") 582 { 583# increment number of times a (high)/unknown port has gone to destport 584 $value1=$unknownsrcports{$destportnam}; 585 $value1++ ; 586 $unknownsrcports{$destportnam}=$value1; 587 } 588 else 589 { 590# want tally(srcport) counter to be increased by 1 591 $value3=$tally{$srcportnam}; 592 $value3++ ; 593 $tally{$srcportnam}=$value3; 594 } 595 } 596 597 598 } 599# end of loop over ALL IP's 600 601if ($set eq "N") 602{ 603$set="Y"; 604 605print "\n#### with $uniqip as the the source for packets ####\n"; 606while(($key,$value)=each %tally) 607 { 608 if (not "$uniqip" eq "$gatekeep") 609 { 610 print "$value connections from $key to gatekeep\n"; 611 } 612 else 613 { 614 print "$value connections from gatekeep to $key\n"; 615 } 616 } 617 618 619 620while(($key2,$value2)=each %unknownsrcports) 621 { 622 if (not "$uniqip" eq "$gatekeep") 623 { 624 print "$value2 high port connections to $key2 on gatekeep\n"; 625 } 626 else 627 { 628 print "$value2 high port connections to $key2 from gatekeep\n"; 629 } 630 } 631 632} 633# print if rests for UNIQIP IF flag is set to N then toggle flag 634 635} # end of all IPs loop 636} # end of if verbose option set block 637 638 639 640