├── .gitignore ├── bad.rules ├── README.md └── dumbpig.pl /.gitignore: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /bad.rules: -------------------------------------------------------------------------------- 1 | # Sample set of bad rules. 2 | # These rules are "bad", so don't copy them 3 | # Leon Ward 4 | # 5 | alert ip any any -> any 53 (msg: "DNS lookup for foo.com using IP proto with port numbers"; content:"baddomain"; sid:1; rev:1) 6 | alert tcp any any -> any 80 (msg: "fastpattern not"; content:"short1"; content: "short2"; content: "looooooong"; http_uri; sid:2; rev:1) 7 | alert tcp any any -> any 80 (msg: "fastpattern set"; content:"short1"; content: "short2"; fast_pattern; content: "looooooong"; sid:3; rev:1) 8 | alert tcp any any -> any any (msg: "Any any -> any any rule"; content: "stuff"; flow: to_server, established; sid:4; classtype: bad-unknown; rev:1) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dumbpig 2 | ======= 3 | An automated way to check for "dumb" snort rules. 4 | 5 | Requirements 6 | ------------ 7 | Perl, LWP::Simple, and Parse::Snort. 8 | 9 | Setup 10 | ------- 11 | On CentOS 6/7, setup is as follows: 12 | 13 | ``` 14 | sudo yum install perl-CPAN perl-libwww-perl perl-Class-Accessor 15 | sudo cpan -i "Parse::Snort" 16 | ``` 17 | 18 | On Mac OS with Perlbrew: 19 | Note, don't mess with your system Perl on OSX, it will bite you. Perlbrew is your friend. 20 | 21 | ``` 22 | $ cpanm Parse::Snort 23 | $ ./dumbpig.pl 24 | ``` 25 | 26 | Usage 27 | ----- 28 | ``` 29 | $ perl dumbpig.pl 30 | 31 | DumbPig version 0.3 - leon@leonward.com 32 | __,, ( Dumb-pig says ) 33 | ~( oo ---( "ur rulz r not so ) 34 | '''' ( gud akshuly" * ) 35 | 36 | DumbPig Configuration 37 | ********************************************* 38 | * Sensitivity level - 4/4 39 | * Processing File - 0 40 | * Quiet mode : Disabled 41 | ********************************************* 42 | Error : Please specify a rules file 43 | Usage dumbPig 44 | -r or --rulefile 45 | -s or --sensitivity <1-4> Sensitivity level, Higher the number, the higher the pass-grade 46 | -b or --blacklist Enable blacklist output (see Marty's Blog post for details) 47 | -p or --pause Pause for ENTER after each FAIL 48 | -w or --write Filename to wite CLEAN rules to 49 | -q or --quiet Suppress FAIL, only provide summary 50 | -d or --disabled Check rules that are disabled i.e commented out #alert # alert etc 51 | -v or --verbose Verbose output for debugging 52 | -c or --censor Censor rules in the output, in case you dont trust everyone 53 | -f or --forcefail Force good rules to FAIL. Allows output of all rules 54 | ``` 55 | 56 | Output 57 | ------ 58 | The output of dumbpig against a file with bad rules: 59 | 60 | ``` 61 | $ perl dumbpig.pl -r bad.rules 62 | 63 | DumbPig version 0.2 - leon.ward@sourcefire.com 64 | __,, ( Dumb-pig says ) 65 | ~( oo ---( "ur rulz r not so ) 66 | '''' ( gud akshuly" * ) 67 | 68 | DumbPig Configuration 69 | ********************************************* 70 | * Sensitivity level - 4/4 71 | * Processing File - bad.rules 72 | * Quiet mode : Disabled 73 | ********************************************* 74 | Issue 1 75 | 2 Problem(s) found with rule on line 5 of bad.rules 76 | 77 | alert ip any any -> any 53 ( \ 78 | msg:"DNS lookup for foo.com using IP proto with port numbers"; \ 79 | content:"baddomain"; \ 80 | sid:1; \ 81 | rev:1; \ 82 | ) 83 | - IP rule with port number (or var that could be set to a port number). This is BAD and invalid syntax. 84 | It is likely that this rule head is not functioning as you expect it to. 85 | The IP protocol doesn't have port numbers. 86 | If you want to inspect both UDP and TCP traffic on specific ports use two rules, its faster and valid syntax. 87 | - No classification specified - Please add a classtype to add correct priority rating 88 | 89 | Rule source sid: 1 90 | alert ip any any -> any 53 (msg: "DNS lookup for foo.com using IP proto with port numbers"; content:"baddomain"; sid:1; rev:1) 91 | ============================================================================= 92 | Issue 2 93 | 2 Problem(s) found with rule on line 6 of bad.rules 94 | 95 | alert tcp any any -> any 80 ( \ 96 | msg:"fastpattern not"; \ 97 | content:"short1"; \ 98 | content:"short2"; \ 99 | content:"looooooong"; \ 100 | http_uri; \ 101 | sid:2; \ 102 | rev:1; \ 103 | ) 104 | - No classification specified - Please add a classtype to add correct priority rating 105 | - TCP, without flow. Consider adding flow to provide better state tracking on this TCP based rule 106 | 107 | Rule source sid: 2 108 | alert tcp any any -> any 80 (msg: "fastpattern not"; content:"short1"; content: "short2"; content: "looooooong"; http_uri; sid:2; rev:1) 109 | ============================================================================= 110 | Issue 3 111 | 112 | ============================================================================= 113 | -------------------------------------- 114 | Total: 4 fails over 4 rules (8 lines) in bad.rules 115 | - Contact leon.ward@sourcefire.com 116 | ``` 117 | 118 | License 119 | ------- 120 | GNU General Public License (GPL) v2 -------------------------------------------------------------------------------- /dumbpig.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | ################################################### 4 | # Copyright (C) 2010 Leon Ward 5 | # 6 | # This program is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; either version 2 9 | # of the License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | # 20 | # Contact: leon@rm-rf.co.uk 21 | # 22 | # TODO - Require msg 23 | # - Add resp keyword 24 | # - Check for normalized data in content buffers where available (uri modifiers and uricontent) 25 | 26 | use strict; 27 | use warnings; 28 | use Getopt::Long; 29 | use Parse::Snort; 30 | use Data::Dumper; 31 | 32 | # Nothing to configure - Check out usage() 33 | # ---------------------------------------------- 34 | my $rulefile=0; 35 | my $blacklist=0; 36 | my $verbose=0; 37 | my $level=4; 38 | my $version=0.3; 39 | my $censor=0; 40 | my $pause=0; 41 | my $write=0; 42 | my $comment=0; 43 | my $forcefail=0; 44 | my $q=0; 45 | my $linenum=0; 46 | my $rulecount=0; 47 | my $failnum=0; 48 | my $blackCIDR=""; 49 | my @blackArray=(); 50 | my $fixnormbug=1; # I found a bug in Parse::Snort with whitespace normalization, 51 | # this is a quick fix while waiting for patch upstream 52 | 53 | sub convert_bl{ 54 | # Convert a load of snort rule header format IPs (or CIDR) to the format used by the blacklist patch 55 | # Note that the BL pacth isn't stable yet, and formats my change etc etc . Use at your own risk 56 | my $ips = shift; 57 | my $iplist; 58 | 59 | # 1) Get rid of any [] and , that may have been part of an array of CIDRs earlier 60 | $ips =~ s/[\[\]]//g; 61 | 62 | # 2) Lets convert this var into a space separated list of CIDR blocks for blacklist 63 | my @iparray = split(/,/,$ips); 64 | foreach (@iparray){ 65 | # 3) Add a /32 to each bare IP for blacklist 66 | unless ( "$_" =~ m/.*\/[0-9]/) { 67 | $iplist=$iplist . "$_/32 "; 68 | } else { # We must already have a CIDR then, dont add a /32 69 | $iplist=$iplist . "$_ "; 70 | } 71 | } 72 | return("$iplist"); 73 | } 74 | 75 | sub chk_ip{ 76 | # Check if we are processing a VAR, IP, or an ANY 77 | my $ip=shift; 78 | 79 | if ( "$ip" eq "any") { 80 | return("any"); 81 | } elsif ( "$ip" =~ m/^\$|^\!\$|^\[\$|\!\[\$/) { 82 | return("var"); 83 | } elsif ( "$ip" =~ m/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b|\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/) { 84 | return("ip"); 85 | } else { 86 | # No idea what this is then. FAIL 87 | return(0); 88 | } 89 | } 90 | 91 | sub chk_pt{ 92 | # Provide a value, and get back num,var, or any. 93 | my $port=shift; 94 | 95 | if ( "$port" eq "any") { 96 | return("any"); 97 | } elsif ( "$port" =~ m/^\$|^\!\$/) { 98 | return("var"); 99 | } elsif ( "$port" =~ m/\b\d{1,5}/) { 100 | return("num"); 101 | } else { 102 | # No idea what this is then. FAIL 103 | return(0); 104 | } 105 | } 106 | 107 | sub usage{ 108 | my $err=shift; 109 | 110 | print "Error : $err\n"; 111 | print "Usage dumbPig \n"; 112 | print " -r or --rulefile \n"; 113 | print " -s or --sensitivity <1-4> Sensitivity level, Higher the number, the higher the pass-grade\n"; 114 | print " -b or --blacklist Enable blacklist output (see Marty's Blog post for details)\n"; 115 | print " -p or --pause Pause for ENTER after each FAIL\n"; 116 | print " -w or --write Filename to wite CLEAN rules to\n"; 117 | print " -q or --quiet Suppress FAIL, only provide summary\n"; 118 | print " -d or --disabled Check rules that are disabled i.e commented out #alert # alert etc\n"; 119 | print " -v or --verbose Verbose output for debugging\n"; 120 | print " -c or --censor Censor rules in the output, in case you dont trust everyone\n"; 121 | print " -f or --forcefail Force good rules to FAIL. Allows output of all rules\n"; 122 | exit 1; 123 | } 124 | 125 | GetOptions ( 'b|blacklist=s' => \$blacklist, 126 | 's|sensitivity=s' => \$level, 127 | 'r|rulefile=s' => \$rulefile, 128 | 'w|write=s' => \$write, 129 | 'p|pause' => \$pause, 130 | 'v|verbose' => \$verbose, 131 | 'd|disabled' => \$comment, 132 | 'f|forcefail' => \$forcefail, 133 | 'c|censor' => \$censor, 134 | 'q|quiet' => \$q, 135 | ); 136 | 137 | unless ( $q ) { 138 | print "\nDumbPig version $version - leon\@leonward.com \n"; 139 | print " __,, ( Dumb-pig says ) 140 | ~( oo ---( \"ur rulz r not so ) 141 | '''' ( gud akshuly\" * ) 142 | \n"; # Hey if pulled pork can have a pig, so can I :) -> http://code.google.com/p/pulledpork/ 143 | 144 | print "DumbPig Configuration\n"; 145 | print "*********************************************\n"; 146 | print "* Sensitivity level - $level/4\n"; 147 | print "* Blacklist output : Enabled" if ($blacklist); 148 | print "* Processing File - $rulefile\n"; 149 | print "* Checking commented out rules in $rulefile\n" if ($comment); 150 | print "* Pause after each rule Enabled\n" if ($pause); 151 | print "* ForceFail : Enabled\n" if ($forcefail); 152 | print "* Censor : Enabled\n" if ($censor); 153 | print "* Writing clean rules to : $write\n" if ($write); 154 | print "* Quiet mode : Disabled \n"; 155 | print "*********************************************\n"; 156 | } 157 | 158 | 159 | unless ($rulefile) { usage("Please specify a rules file"); } 160 | open RULEFILE, "$rulefile" or die "Unable to open $rulefile\n"; 161 | open OUTPUT,">","$write" or die "Unable to open output file $write\n"; 162 | 163 | 164 | while (my $line=) { 165 | chomp $line; 166 | my $originalline = $line; 167 | $linenum++; 168 | 169 | if ($fixnormbug) { 170 | # There is a minor bug in the Parse::Snort module with whitespace, while waiting for a fix (reported) lets "fix" it here. 171 | # Thanks to Tom Dixon for spotting the problem. 172 | # Thanks to Per Kristian Johnsen for pointing out that I was breaking peoples rules by writing this output to file. 173 | 174 | $line =~ s/: *"/:"/g; # Remove extra space after : eg. msg: "foo"; 175 | $line =~ s/^\s+(alert|drop|pass|reject|activate|dynamic|activate)/$1/g; # Remove ws before action keyword e.g. ^ alert ip any 176 | $line =~ s/\s+/ /g; # Normalize All whitespace <- This is brutal and breakes the formatted output 177 | if ($line =~ m/\)\s/) { 178 | $line =~ s/\)\s/\)/; 179 | } 180 | } 181 | 182 | if ($comment) { 183 | # User wants to process commented out lines, so lets uncomment them 184 | $line =~ s/^#\s*alert/alert/g; 185 | $line =~ s/^#\s*drop/drop/g; 186 | } 187 | 188 | if ( $line =~ m/^alert|^pass|^drop|^reject|^activate|^dynamic/ ) { 189 | $rulecount++; 190 | my $rulehash=Parse::Snort->new($line); 191 | if ($verbose) { 192 | print "-V Dumping rule hash from Parse::Snort\n"; 193 | print Dumper $rulehash; 194 | } 195 | my $fail=0; # Problem found with rule 196 | my $blacklistable=0; # Can this rule be converted to a blacklist? 197 | my $sffail=0; # Problem that will stop rule from importing into Sourcefire Defense Center 198 | my $fatal=0; # Fatal problem that will prevent the rule from starting in snort. Syntax error etc. 199 | my @reason=(); # Array of reasons for fail 200 | my $sfreason=""; 201 | my $display_head=""; 202 | my $display_body=""; 203 | my $action=0; 204 | my $proto=0; 205 | my $src_addr=0; 206 | my $src_port=0; 207 | my $direction=0; 208 | my $dst_addr=0; 209 | my $dst_port=0; 210 | my $unknown=0; 211 | my @rulelines=(); 212 | 213 | ############################################################ 214 | # If any of these are 0 post processing, the keyword is not in use. 215 | my @censorKeywords=("pcre","content","uricontent","msg"); 216 | my @argless=("base64_data", 217 | "dce_stub_data", 218 | "fast_pattern", 219 | "file_data", 220 | "ftpbounce", 221 | "http_client_body", 222 | "http_cookie", 223 | "http_header", 224 | "http_method", 225 | "http_method", 226 | "http_raw_cookie", 227 | "http_raw_header", 228 | "http_raw_uri", 229 | "http_stat_code", 230 | "http_stat_msg", 231 | "http_uri", 232 | "nocase", 233 | "pkt_data", 234 | "rawbytes"); # Some keywords don't take args, these are argless. 235 | 236 | 237 | my %hkeywords =("ack" => 0, 238 | "asn1" => 0, 239 | "base64_data" => 0, 240 | "base64_decode" => 0, 241 | "byte_extract" => 0, 242 | "byte_jump" => 0, 243 | "byte_test" => 0, 244 | "classtype" => 0, 245 | "content" => 0, 246 | "dce_iface" => 0, 247 | "dce_opnum" => 0, 248 | "dce_stub_data" => 0, 249 | "depth" => 0, 250 | "detection_filter" => 0, 251 | "distance" => 0, 252 | "dsize" => 0, 253 | "fast_pattern" => 0, 254 | "file_data" => 0, 255 | "flags" => 0, 256 | "flow" => 0, 257 | "flowbits" => 0, 258 | "fragbits" => 0, 259 | "fragoffset" => 0, 260 | "ftpbounce" => 0, 261 | "fwsam" => 0, 262 | "gid" => 0, 263 | "http_client_body" => 0, 264 | "http_cookie" => 0, 265 | "http_encode" => 0, 266 | "http_header" => 0, 267 | "http_method" => 0, 268 | "http_raw_header" => 0, 269 | "http_raw_uri" => 0, 270 | "http_stat_code" => 0, 271 | "http_stat_msg" => 0, 272 | "http_uri" => 0, 273 | "icmp_id" => 0, 274 | "icmp_seq" => 0, 275 | "icode" => 0, 276 | "id" => 0, 277 | "ip_proto" => 0, 278 | "ipopts" => 0, 279 | "isdataat" => 0, 280 | "itype" => 0, 281 | "metadata" => 0, 282 | "msg" => 0, 283 | "nocase" => 0, 284 | "offset" => 0, 285 | "offset" => 0, 286 | "pcre" => 0, 287 | "pkt_data" => 0, 288 | "priority" => 0, 289 | "rawbytes" => 0, 290 | "reference" => 0, 291 | "rev" => 0, 292 | "seq" => 0, 293 | "sid" => 0, 294 | "ssl_state" => 0, 295 | "ssl_version" => 0, 296 | "stream_size" => 0, 297 | "tag" => 0, 298 | "threshold" => 0, 299 | "ttl" => 0, 300 | "uricontent" => 0, 301 | "urilen" => 0, 302 | "window" => 0, 303 | "within" => 0 304 | ); 305 | 306 | if ($verbose) { 307 | print "----Got Rule $linenum--------------------\n"; 308 | } 309 | 310 | ############################################################ 311 | # Check Rule Header 312 | # Check action 313 | 314 | unless ("$rulehash->{'action'}" =~ m/alert|drop|pass|sdrop|reject|activate|dynamic/) { 315 | print "Action is -$rulehash->{'action'}-\n"; 316 | $fail++; 317 | push(@reason, "- Only drop and alert actions are supported on rule imports\n"); 318 | } 319 | $action=$rulehash->{'action'}; 320 | 321 | $display_head .= "$action "; 322 | 323 | # Check protocol 324 | if ( $rulehash->{'proto'} =~ m/tcp|udp|icmp|ip/ ) { 325 | $proto=$rulehash->{'proto'}; 326 | } else { 327 | $fail++; 328 | push(@reason, "- Invalid Protocol $rulehash->{'proto'}\n"); 329 | } 330 | $display_head=$display_head . "$rulehash->{'proto'} "; 331 | 332 | # Source IP 333 | if ( chk_ip("$rulehash->{'src'}") ) { 334 | $src_addr=chk_ip("$rulehash->{'src'}"); 335 | } else { 336 | $fail++; 337 | push(@reason,"- Invalid src_addr $rulehash->{'src'}\n"); 338 | } 339 | 340 | if ($censor) { 341 | if ( "$src_addr" eq "ip") { 342 | $rulehash->{'src'} = "CENSORRD_IP"; 343 | } 344 | } 345 | $display_head=$display_head . "$rulehash->{'src'} "; 346 | 347 | # Source Port 348 | if ( chk_pt("$rulehash->{'src_port'}") ) { 349 | $src_port=chk_pt("$rulehash->{'src_port'}"); 350 | } else { 351 | $fail++; 352 | push(@reason, "- Invalid src_port $rulehash->{'src_port'}\n"); 353 | } 354 | $display_head=$display_head . "$rulehash->{'src_port'} "; 355 | 356 | # Direction 357 | if (("$rulehash->{'direction'}" eq "->") or ("$rulehash->{'direction'}" eq "<>")) { 358 | $direction=1; 359 | } else { 360 | $fail++; 361 | push(@reason, "- Invalid direction $rulehash->{'direction'}\n"); 362 | } 363 | $display_head=$display_head . "$rulehash->{'direction'} "; 364 | 365 | # Dest IP 366 | if ( chk_ip("$rulehash->{'dst'}") ) { 367 | $dst_addr=chk_ip("$rulehash->{'dst'}"); 368 | } else { 369 | $fail++; 370 | push(@reason,"- Invalid dst_addr $rulehash->{'dst'}\n"); 371 | } 372 | if ($censor) { 373 | if ( "$dst_addr" eq "ip") { 374 | $rulehash->{'dst'} = "CENSORRD_IP"; 375 | } 376 | } 377 | $display_head=$display_head . "$rulehash->{'dst'} "; 378 | 379 | # Dest Port 380 | if ( chk_pt("$rulehash->{'dst_port'}") ) { 381 | $dst_port=chk_pt("$rulehash->{'dst_port'}"); 382 | } else { 383 | $fail++; 384 | push(@reason, "- Invalid dst_port $rulehash->{'dst_port'}\n"); 385 | } 386 | $display_head=$display_head . "$rulehash->{'dst_port'} "; 387 | 388 | 389 | if ($verbose) { 390 | print "[v] ---- RULE Head ----\n"; 391 | print "proto $proto \n"; 392 | print "src_addr $src_addr ($rulehash->{'src'})\n"; 393 | print "src_port $src_port ($rulehash->{'src_port'})\n"; 394 | print "direction $direction ($rulehash->{'direction'})\n"; 395 | print "dst_addr $dst_addr ($rulehash->{'dst'})\n"; 396 | print "dst_port $dst_port ($rulehash->{'dst_port'})\n"; 397 | } 398 | 399 | 400 | 401 | 402 | if ($verbose) { 403 | print "[v] ---- RULE Body ----\n"; 404 | } 405 | 406 | ############################################################ 407 | # Check Rule Body 408 | # Process Rule Opts 409 | 410 | foreach ($rulehash->{'opts'}) { 411 | foreach my $keyword (@$_){ 412 | #print "Processing $keyword->[0] \n"; 413 | 414 | # Check we support this keyword 415 | if (grep {$_ eq $keyword->[0]} %hkeywords) { 416 | 417 | unless (grep {$_ eq $keyword->[0]} @argless) { # Check if this keyword is argless. If so set to 1 to show it's used 418 | $hkeywords{$keyword->[0]} = $keyword->[1] ; # If it takes args, set the value of the keyword in the hash to the arg. 419 | if ($censor) { 420 | # Censor the value of some keywords, defined in censor_keywords 421 | if (grep {$_ eq $keyword->[0]} @censorKeywords) { 422 | push (@rulelines, "$keyword->[0]: \"XXXXXXXX\";"); 423 | } else { 424 | push (@rulelines, "$keyword->[0]:$keyword->[1];"); 425 | } 426 | } else { 427 | push (@rulelines, "$keyword->[0]:$keyword->[1];"); 428 | } 429 | } else { 430 | $hkeywords{$keyword->[0]} = 1; 431 | push (@rulelines, "$keyword->[0];"); 432 | } 433 | } else { 434 | print "WARNING: $keyword->[0] not supported on line $linenum of $rulefile\n"; 435 | $fail++; 436 | push (@reason, "- Invalid keyword $keyword->[0] found. \n Does this tool support the keyword \"$keyword->[0]\" If it should contact me. \n Have you correctly escaped things that should be escaped?\n Are you using invalid content chars such as \?\"\& etc that should be represented by their hex values eg content\: \"\|VAL\|\"\;\n"); 437 | 438 | } 439 | } 440 | 441 | } 442 | if ($verbose) { 443 | print "[v] ------------------\n"; 444 | print "$display_head (\n"; 445 | print "$display_body )\n"; 446 | } 447 | 448 | 449 | #print "END New rule lines\n"; 450 | 451 | ############################################################ 452 | # Rule sanity checking. Has the writer created a valid 453 | # rule, but forgotten some important performance 454 | # tweaks, or supporting data 455 | 456 | if ($forcefail) { 457 | # Force a fail on each rule. Use this for printing the rule source, good for use with -c 458 | $fail++; 459 | } 460 | 461 | # Low sensitivity = BAD problems 462 | if ($level >= 1) { 463 | 464 | # IP rule with a port num (WTF?) 465 | if ( "$proto" eq "ip" and (("$src_port" ne "any") or ("$dst_port" ne "any"))) { 466 | $fail++; 467 | push(@reason, "- IP rule with port number (or var that could be set to a port number). This is BAD and invalid syntax. \n It is likely that this rule head is not functioning as you expect it to. \n The IP protocol doesn't have port numbers. \n If you want to inspect both UDP and TCP traffic on specific ports use two rules, its faster and valid syntax.\n"); 468 | } 469 | 470 | # No revision number 471 | #if ( !$rev ) { 472 | # $fail++; 473 | # push(@reason, "- No revision number! Please add a rev: keyword\n"); 474 | #} 475 | 476 | 477 | unless ( $hkeywords{'rev'} ) { 478 | $fail++; 479 | push(@reason, "- No revision number! Please add a rev: keyword\n"); 480 | 481 | } 482 | 483 | # No SID 484 | unless ( $hkeywords{'sid'}) { 485 | $fail++; 486 | push (@reason, "- No SID number! Please add a sid: keyword\n"); 487 | } 488 | 489 | # No classtype 490 | unless ( $hkeywords{'classtype'}) { 491 | $fail++; 492 | push (@reason, "- No classification specified - Please add a classtype to add correct priority rating\n"); 493 | } 494 | 495 | # unknown keyword 496 | if ( $unknown ) { 497 | $fail++; 498 | push (@reason, "- Unknown keyword \"$unknown\" found! Either \n A) you messed up\n B) This tool needs to add support for that keyword \n C) You are using reserved chars in your rule, HEX should be used for stuff like \" ?() etc \n Note that the decoded rule will NOT show this keyword, check the original rule line\n"); 499 | } 500 | } 501 | 502 | # Medium sensitivity level = Medium problems 503 | if ($level > 2 ) { 504 | # IP rule with flow - move to TCP/UDP 505 | if ( ("$proto" eq "ip") and $hkeywords{'flow'} ) { 506 | $fail++; 507 | push (@reason, "- IP rule with flow?, Consider moving to a TCP or UDP (with stream5) based rule\n"); 508 | } 509 | 510 | # No deep packet checks - Firewall suited check 511 | if ( ("$proto" eq "tcp" or "$proto" eq "udp") and not 512 | ($hkeywords{'content'} or $hkeywords{'uricontent'} or $hkeywords{'pcre'} or $hkeywords{'byte_test'} or $hkeywords{'dsize'} or $hkeywords{'flags'}) ) { 513 | $fail++; 514 | $blacklistable=1; 515 | push (@reason, "- TCP/UDP rule with no deep packet checks? This rule looks more suited to a firewall or blacklist\n"); 516 | } 517 | 518 | # IP rule without content, pcre or uricontent? 519 | if ( "$proto" eq "ip" and not ($hkeywords{'content'} or $hkeywords{'uricontent'} or $hkeywords{'pcre'} or $hkeywords{'ip_proto'} or ($hkeywords{'metadata'} =~ /engine shared, soid [0-9]+|[0-9]+/))) { 520 | $blacklistable=1; 521 | $fail++; 522 | push (@reason, "- IP rule without a content match. Put this in a firewall!\n"); 523 | } 524 | 525 | # PCRE w/o fast ptn match 526 | if ( $hkeywords{'pcre'} and not ($hkeywords{'content'} or $hkeywords{'uricontent'})) { 527 | $fail++; 528 | push (@reason, "- PCRE found without a fast-pattern match keyword (content || uricontent). Obvious performance hit here\n"); 529 | } 530 | 531 | } 532 | 533 | # High sensitivity = Minor problems 534 | if ($level >=3) { 535 | # TCP without flow 536 | if ( ("$proto" eq "tcp") and not $hkeywords{'flow'}) { 537 | $fail++; 538 | push (@reason, "- TCP, without flow. Consider adding flow to provide better state tracking on this TCP based rule\n"); 539 | } 540 | } 541 | 542 | 543 | if ($level >=4) { 544 | # Any any any any rule..... SLOW 545 | if (("$src_port" eq "any") and ("$dst_port" eq "any")) { 546 | $fail++; 547 | push (@reason, "- ANY -> ANY rule. \n You should really add port numbers into your rule. You are likely wasting huge chunks of processing effort on the wrong packets\n"); 548 | } 549 | } 550 | 551 | # If this is a blacklist-able rule, and blacklist o/p is enabled, lets track these for use in a snort.conf 552 | if ( $blacklist and $blacklistable ) { 553 | # Chck we have some real IP's and dont end up adding $HOME_NET :) 554 | # This isn't the smartest way to do this, but works in my tests - Leon 555 | 556 | if ( "$src_addr" eq "ip" ) { # We have an IP/CIDR for src_addr here then 557 | my $blackTarget=convert_bl("$rulehash->{'src'}") . " # From Sid $hkeywords{'sid'} : $hkeywords{'msg'} : $rulefile"; 558 | push(@blackArray,$blackTarget); 559 | if ($verbose) { 560 | print "V- Adding Source $blackTarget to blacklist\n"; 561 | } 562 | } 563 | if ( "$dst_addr" eq "ip" ) { # We have an IP/CIDR for src_addr here then 564 | my $blackTarget=convert_bl("$rulehash->{'dst'}"); 565 | push(@blackArray, $blackTarget); 566 | if ($verbose) { 567 | print "V- Adding Destination $blackTarget to blacklist\n"; 568 | } 569 | } 570 | } 571 | 572 | if ($fail) { 573 | $failnum++; 574 | # We have a problem with this rule. 575 | unless ($q) { 576 | print "Issue $failnum \n"; 577 | print "$fail Problem(s) found with rule on line $linenum of $rulefile\n"; 578 | print "\n$display_head ( \\ \n"; 579 | foreach (@rulelines) { 580 | print "\t$_ \\ \n"; 581 | } 582 | print ")\n"; 583 | 584 | foreach (@reason) { 585 | print "$_"; 586 | } 587 | unless ($censor ) { 588 | print "\nRule source sid: $hkeywords{'sid'} \n"; 589 | print "$originalline\n"; 590 | } 591 | print "=============================================================================\n"; 592 | if ($pause) { 593 | print "Press Enter for the next fail \n"; 594 | print "=============================================================================\n"; 595 | my $foobar=; 596 | } 597 | } 598 | } else { # WIN! 599 | print OUTPUT "$originalline\n"; 600 | } 601 | } 602 | } 603 | 604 | if ($write) { 605 | close(OUTPUT); 606 | } 607 | 608 | if ($blacklist) { 609 | print "============================================\n"; 610 | print " Creating blacklist $blacklist\n"; 611 | open BLACKLIST,">","$blacklist" or die "Unable to open blacklist file $blacklist"; 612 | print BLACKLIST "# Autogenerated blacklist by DumbPig from $rulefile \n# Contact leon.ward\@sourcefire.com \n# For more information about dumbPig visit http://rm-rf.co.uk\n "; 613 | foreach (@blackArray) { 614 | print BLACKLIST "$_ \n"; 615 | } 616 | print "....Done\n"; 617 | } 618 | 619 | print "--------------------------------------\n"; 620 | print "Total: $failnum fails over $rulecount rules ($linenum lines) in $rulefile\n"; 621 | print "- Contact leon.ward\@sourcefire.com\n"; 622 | --------------------------------------------------------------------------------