3 ###############################################################################
7 # URL: http://www.cipherdyne.org/fwsnort/
9 # Purpose: To translate snort rules into equivalent iptables rules.
10 # fwsnort is based on the original snort2iptables shell script
11 # written by William Stearns.
13 # Author: Michael Rash <mbr@cipherdyne.org>
15 # Credits: (see the CREDITS file)
19 # Copyright (C) 2003-2012 Michael Rash (mbr@cipherdyne.org)
21 # License - GNU Public License version 2 (GPLv2):
23 # This program is distributed in the hope that it will be useful,
24 # but WITHOUT ANY WARRANTY; without even the implied warranty of
25 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 # GNU General Public License for more details.
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to the Free Software
30 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
34 # - Add the ability to remove rules from a real snort config in the same
35 # way we remove them from iptables rulesets in fwsnort (we remove rules
36 # from an iptables ruleset if the iptables policy will not allow such
37 # traffic through in the first place).
38 # - New option: --ipt-mark.
40 # Reference: Snort is a registered trademark of Sourcefire, Inc
44 # msg: Prints a message in alerts and packet logs.
45 # logto: Log the packet to a user specified filename instead of the
46 # standard output file.
47 # ttl: Test the IP header's TTL field value.
48 # tos: Test the IP header's TOS field value.
49 # id: Test the IP header's fragment ID field for a specific
51 # ipoption: Watch the IP option fields for specific codes.
52 # fragbits: Test the fragmentation bits of the IP header.
53 # dsize: Test the packet's payload size against a value.
54 # flags Test the TCP flags for certain values.
55 # seq: Test the TCP sequence number field for a specific value.
56 # ack: Test the TCP acknowledgement field for a specific value.
57 # itype: Test the ICMP type field against a specific value.
58 # icode: Test the ICMP code field against a specific value.
59 # icmp_id: Test the ICMP ECHO ID field against a specific value.
60 # icmp_seq: Test the ICMP ECHO sequence number against a specific
62 # content: Search for a pattern in the packet's payload.
63 # content-list: Search for a set of patterns in the packet's payload.
64 # offset: Modifier for the content option, sets the offset to begin
65 # attempting a pattern match.
66 # depth: Modifier for the content option, sets the maximum search
67 # depth for a pattern match attempt.
68 # nocase: Match the preceding content string with case insensitivity.
69 # session Dumps the application layer information for a given
71 # rpc: Watch RPC services for specific application/procedure
73 # resp: Active response (knock down connections, etc).
74 # react: Active response (block web sites).
75 # reference: External attack reference ids.
77 # rev: Rule revision number.
78 # classtype: Rule classification identifier.
79 # priority: Rule severity identifier.
80 # uricontent: Search for a pattern in the URI portion of a packet
82 # tag: Advanced logging actions for rules.
83 # ip_proto: IP header's protocol value.
84 # sameip: Determines if source ip equals the destination ip.
85 # stateless: Valid regardless of stream state.
86 # regex: Wildcard pattern matching.
88 ############################################################################
101 my $CONFIG_DEFAULT = '/etc/fwsnort/fwsnort.conf';
102 my $fwsnort_conf = $CONFIG_DEFAULT;
105 my $version = '1.6.2-pre1';
109 'sport' => '--sport',
111 'dport' => '--dport',
116 ### snort options that we can directly filter on
117 ### in iptables rulesets (snort options are separate
118 ### from the snort "header" which include protocol,
119 ### source, destination, etc.)
122 ### application layer
123 'uricontent' => { ### use --strict to not translate this
124 'iptopt' => '-m string',
125 'regex' => '[\s;]uricontent:\s*\"(.*?)\"\s*;'
128 'iptopt' => '-m string',
129 'regex' => '[\s;]content:\s*\"(.*?)\"\s*;'
132 'iptopt' => '', ### fast_pattern just governs ordering of
134 'regex' => '[\s;]fast_pattern(?::\s*.*?\s*)?;',
137 ### only basic PCRE's that just have strings separated
138 ### by ".*" or ".+" are supported.
139 'iptopt' => '-m string',
140 'regex' => '[\s;]pcre:\s*\"(.*?)\"\s*;'
143 'iptopt' => '--icase',
144 'regex' => '[\s;]nocase\s*;',
147 'iptopt' => '--from',
148 'regex' => '[\s;]offset:\s*(\d+)\s*;'
152 'regex' => '[\s;]depth:\s*(\d+)\s*;'
155 ### technically, the "distance" and "within" criteria
156 ### are relative to the end of the previous pattern match,
157 ### so iptables cannot emulate these directly; an approximation
158 ### is made based on the on length of the previous pattern an
159 ### any "depth" or "offset" criteria for the previous pattern.
160 ### To disable signatures with "distance" and "within", just
161 ### use the --strict option.
163 'iptopt' => '--from',
164 'regex' => '[\s;]distance:\s*(\d+)\s*;'
168 'regex' => '[\s;]within:\s*(\d+)\s*;'
170 'replace' => { ### for Snort running in inline mode
171 'iptopt' => '--replace-string',
172 'regex' => '[\s;]replace:\s*\"(.*?)\"\s*;'
175 'iptopt' => '-j REJECT',
176 'regex' => '[\s;]resp:\s*(.*?)\s*;'
181 'iptopt' => '--tcp-flags',
182 'regex' => '[\s;]flags:\s*(.*?)\s*;'
185 'iptopt' => '--tcp-flags',
186 'regex' => '[\s;]flow:\s*(.*?)\s*;'
191 'iptopt' => '--icmp-type', ### --icmp-type type/code
192 'regex' => '[\s;]itype:\s*(.*?)\s*;'
196 'regex' => '[\s;]icode:\s*(.*?)\s*;'
199 'iptopt' => '-m ttl', ### requires CONFIG_IP_NF_MATCH_TTL
200 'regex' => '[\s;]ttl:\s*(.*?)\s*;'
203 'iptopt' => '-m tos --tos', ### requires CONFIG_IP_NF_MATCH_TOS
204 'regex' => '[\s;]tos:\s*(\d+)\s*;'
207 'iptopt' => '-m ipv4options', ### requires ipv4options extension
208 'regex' => '[\s;]ipopts:\s*(\w+)\s*;'
212 'regex' => '[\s;]ip_proto:\s*(.*?)\s*;'
214 'dsize' => { ### requires CONFIG_IP_NF_MATCH_LENGTH
215 'iptopt' => '-m length --length',
216 'regex' => '[\s;]dsize:\s*(.*?)\s*;'
220 ### snort options that can be put into iptables
221 ### ruleset, but only in log messages with --log-prefix
223 'sid' => '[\s;]sid:\s*(\d+)\s*;',
224 'msg' => '[\s;]msg:\s*\"(.*?)\"\s*;', ### we create a space
225 'classtype' => '[\s;]classtype:\s*(.*?)\s*;',
226 'reference' => '[\s;]reference:\s*(.*?)\s*;',
227 'priority' => '[\s;]priority:\s*(\d+)\s*;',
228 'rev' => '[\s;]rev:\s*(\d+)\s*;',
231 ### snort options that cannot be included directly
232 ### within iptables filter statements (yet :)
234 'asn1' => '[\s;]asn1:\s*.*?\s*;',
235 'fragbits' => '[\s;]fragbits:\s*.*?\s*;',
236 'content-list' => '[\s;]content\-list:\s*\".*?\"\s*;',
237 'rpc' => '[\s;]rpc:\s*.*?\s*;',
238 'byte_test' => '[\s;]byte_test\s*.*?\s*;',
239 'byte_jump' => '[\s;]byte_jump\s*.*?\s*;',
240 'window' => '[\s;]window:\s*.*?\s*;',
241 'flowbits' => '[\s;]flowbits:\s*.*?\s*;',
242 # 'offset' => '[\s;]offset:\s*\d+\s*;',
243 # 'depth' => '[\s;]depth:\s*\d+\s*;',
245 ### the following fields get logged by iptables but
246 ### we cannot filter them directly except with the
247 ### iptables u32 module. Functionality has been built
248 ### into psad to generate alerts for most of these Snort
250 'id' => '[\s;]id:\s*(\d+)\s*;',
251 'seq' => '[\s;]seq:\s*(\d+)\s*;', ### --log-tcp-sequence
252 'ack' => '[\s;]ack:\s*.*?\s*;', ### --log-tcp-sequence
253 'icmp_seq' => '[\s;]icmp_seq:\s*(\d+)\s*;',
254 'icmp_id' => '[\s;]icmp_id:\s*(\d+)\s*;',
255 'sameip' => '[\s;]sameip\s*;',
256 'regex' => '[\s;]regex:\s*(.*?)\s*;',
257 'isdataat' => '[\s;]isdataat:\s*(.*?)\s*;',
258 'threshold' => '[\s;]threshold:\s*.*?\s*;', ### FIXME --limit
259 'detection_filter' => '[\s;]detection_filter:\s*.*?\s*;' ### FIXME --limit
262 ### snort options that fwsnort will ignore
264 'rawbytes' => '[\s;]rawbytes\s*;', ### iptables does a raw match anyway
265 'logto' => '[\s;]logto:\s*\S+\s*;',
266 'session' => '[\s;]session\s*;',
267 'tag' => '[\s;]tag:\s*.*?\s*;',
268 'react' => '[\s;]react:\s*.*?\s*;', ### FIXME -j REJECT
269 'http_uri' => '[\s;]http_uri\s*;',
270 'http_method' => '[\s;]http_method\s*;',
271 'urilen' => '[\s;]urilen:\s*.*?\s*;',
275 ### rules update link
276 my $DEFAULT_RULES_URL = 'http://rules.emergingthreats.net/open/snort-2.9.0/emerging-all.rules';
277 my $rules_url = $DEFAULT_RULES_URL;
279 ### config vars that may span multiple lines
280 my %multi_line_vars = (
281 'UPDATE_RULES_URL' => '',
286 ### array that contains the fwsnort iptables script (will be written
287 ### to $config{'FWSNORT_SCRIPT'})
288 my @ipt_script_lines = ();
290 ### array that contains the fwsnort policy in iptables-save format.
291 ### This will also contain the running iptables policy, so the fwsnort
292 ### policy is integrated in.
293 my @fwsnort_save_lines = ();
294 my @ipt_save_lines = ();
295 my $ipt_save_index = 0;
296 my @ipt_save_script_lines = ();
297 my $ipt_save_completed_line = '';
298 my $save_str = 'iptables-save';
299 my $ipt_str = 'iptables';
301 my $restore_bin = '';
304 ### contains a cache of the iptables policy
306 my %ipt_default_policy_setting = ();
307 my %ipt_default_drop = ();
308 my %ipt_save_existing_chains = ();
310 ### hashes for save format data
311 my %save_format_whitelist = ();
312 my %save_format_blacklist = ();
313 my %save_format_prereqs = ();
314 my %save_format_rules = ();
315 my %save_format_conntrack_jumps = ();
317 ### regex to match ip addresses
318 my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|;
320 my %snort_dump_cache = ();
321 my %ipt_dump_cache = ();
323 ### for iptables capabilities testing
324 my $NON_HOST = '127.0.0.2';
325 my $NON_IP6_HOST = '::2/128';
330 my $IPT_TEST_RULE_NUM = 1;
333 my $MATCH_SUBSTR = 2;
335 ### header lengths; note that IP and TCP lengths are defined
336 ### in the fwsnort.conf file since they may each contain options,
337 ### but until the --payload option is added to the string match
338 ### extension there is no way to account for them except to
339 ### define an average length.
340 my $MAC_HDR_LEN = 14;
342 my $ICMP_HDR_LEN = 8;
344 ### config and commands hashes (constructed by import_config())
348 my @local_addrs = ();
349 my %include_types = ();
350 my %exclude_types = ();
351 my %include_sids = ();
352 my %exclude_sids = ();
353 my %restrict_interfaces = ();
355 ### establish some default behavior
356 my $home_net = ''; ### normally comes from fwsnort.conf
357 my $ext_net = ''; ### normally comes from fwsnort.conf
377 my $queue_rules_dir = '';
378 my $queue_pre_match_max = 0;
380 my $kernel_ver = '2.6'; ### default
381 my $string_match_alg = 'bm';
384 my $cmdl_homedir = '';
385 my $update_rules = 0; ### used to download latest snort rules
386 my $ipt_print_type = 0;
387 my $ipt_check_capabilities = 0;
388 my $ipt_rule_ctr = 1;
391 my $ipt_del_chains = 0;
397 my $no_ipt_jumps = 0;
398 my $no_ipt_input = 0;
399 my $no_ipt_output = 0;
400 my $no_addr_check = 0;
401 my $no_ipt_forward = 0;
403 my $include_sids = '';
404 my $exclude_sids = '';
406 my $rules_types = '';
407 my $exclude_types = '';
409 my $ulog_nlgroup = 1;
411 my $nfqueue_mode = 0;
416 my $include_re_caseless = 0;
417 my $exclude_re_caseless = 0;
418 my $enable_ip6tables = 0;
419 my $ipt_var_str = 'IPTABLES';
420 my $no_ipt_conntrack = 0;
421 my $conntrack_state = 'ESTABLISHED';
422 my $have_conntrack = 0;
424 my $snort_conf_file = '';
425 my $ipt_restrict_intf = '';
426 my $no_ipt_comments = 0;
427 my $no_ipt_rule_nums = 0;
428 my $no_exclude_loopback = 0;
429 my $no_ipt_log_ip_opts = 0;
430 my $no_ipt_log_tcp_opts = 0;
431 my $ipt_log_tcp_seq = 0;
432 my $include_perl_triggers = 0;
433 my $duplicate_last_build = 0;
434 my $ipt_max_str_len = 1;
435 my $ipt_max_log_prefix_len = 1;
436 my $ipt_max_comment_len = 1;
437 my $no_fast_pattern_order = 0;
438 my $ipt_have_multiport_match = 0;
439 my $ipt_multiport_max = 2;
441 ### to be added to the string match extension
442 my $ipt_has_string_payload_offset_opt = 0;
444 ### default to processing these filter chains
445 my %process_chains = (
450 my $TEST_CHAIN = 'FWS_CAP_TEST';
454 ### save a copy of the command line args
457 ### see if we are running as root
460 ### handle the command line args
463 &run_last_cmdline() if $run_last;
465 ### import config, initialize various things, etc.
468 ### if we are running with $chk_ipt_policy, then cache
469 ### the current iptables policy
470 &cache_ipt_policy() if $ipt_sync;
472 ### truncate old fwsnort log
475 ### check to make sure iptables has various functionality available
476 ### such as the LOG target, --hex-strings, the comment match, etc.
477 &ipt_capabilities() unless $no_ipt_test;
479 ### cache the running iptables policy in iptables-save format
480 &cache_ipt_save_policy();
482 ### print a header at the top of the iptables ruleset
486 ### now that we have the interfaces, add the iptables
487 ### chains to the fwsnort shell script
490 ### add any WHITELIST rules to the main fwsnort chains
491 ### with the RETURN target
494 ### add any BLACKLIST rules to the main fwsnort chains
495 ### with the DROP or REJECT targets
498 ### add jump rules for established tcp connections to
499 ### the fwsnort state tracking chains
500 &ipt_add_conntrack_jumps() unless $no_ipt_conntrack;
502 ### display the config on STDOUT
503 &dump_conf() if $dump_conf;
505 ### make sure <type>.rules file exists if --type was
506 ### specified on the command line
507 &check_type() if $rules_types;
509 &logr("[+] Begin parsing cycle.");
511 ### parse snort rules (signatures)
513 print "[+] Parsing Snort rules files...\n";
516 print "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=",
517 "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n",
518 sprintf("%-30s%-10s%-10s%-10s%-10s", ' Snort Rules File',
519 'Success', 'Fail', 'Ipt_apply', 'Total'), "\n\n";
521 print "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=",
522 "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n",
523 sprintf("%-30s%-10s%-10s%-10s", ' Snort Rules File',
524 'Success', 'Fail', 'Total'), "\n\n";
528 ### main subroutine to parse snort rules and add them to the
529 ### fwsnort.sh script.
530 &parse_snort_rules();
532 ### append all translated rules to the iptables-save formatted array
533 &save_format_append_rules();
535 ### jump packets (as appropriate) from the INPUT and
536 ### FORWARD chains to our fwsnort chains
537 &ipt_jump_chain() unless $no_ipt_jumps;
539 push @ipt_script_lines, qq|\n\$ECHO "[+] Finished."|, '### EOF ###';
540 push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines];
542 print "\n[+] Logfile: $config{'LOG_FILE'}\n";
544 if ($ipt_rule_ctr > 1) {
546 ### write the iptables script out to disk
549 if ($queue_mode or $nfqueue_mode) {
550 print "[+] Snort rule set directory for rules to be queued ",
551 "to userspace:\n $config{'QUEUE_RULES_DIR'}\n";
553 print "[+] $ipt_str script (individual commands): " .
554 "$config{'FWSNORT_SCRIPT'}\n";
557 die "[-] No Snort rules could be translated, exiting\n";
562 &print_final_message();
565 #===================== end main ======================
567 sub parse_snort_rules() {
574 @rfiles = split /\,/, $rules_file;
576 for my $dir (split /\,/, $config{'RULES_DIR'}) {
577 opendir D, $dir or die "[*] Could not opendir $dir";
578 for my $file (readdir D) {
579 push @rfiles, "$dir/$file";
586 my $tot_ipt_apply = 0;
587 my $tot_unsup_ctr = 0;
588 FILE: for my $rfile (sort @rfiles) {
589 $rfile = $cwd . '/' . $rfile unless $rfile =~ m|^/|;
592 if ($rfile =~ m|.*/(\S+\.rules)$|) {
595 if ($rfile =~ m|.*/(\S+)\.rules$|) {
602 next FILE unless defined $include_types{$type};
604 if ($exclude_types) {
605 next FILE if defined $exclude_types{$type};
607 if ($rfile eq 'deleted.rules') {
608 next FILE unless $add_deleted;
610 ($snort_type) = ($rfile =~ m|.*/(\S+)\.rules|);
611 printf("%-30s", "[+] $filename") unless $include_sids;
613 &logr("[+] Parsing $rfile");
614 open R, "< $rfile" or die "[*] Could not open: $rfile";
618 ### contains Snort rules that will be used by Snort_inline
619 ### if fwsnort is building a QUEUE policy; these rules have
620 ### met the criteria that at least one "content" match is
622 my @queue_rules = ();
628 my $ipt_apply_ctr = 0;
629 my $ipt_rules_ctr = 0;
631 RULE: for my $rule (@lines) {
637 ### pass == ACCEPT, log == ULOG
638 unless ($rule =~ /^\s*alert/ or $rule =~ /^\s*pass/
639 or $rule =~ /^\s*log/) {
645 next RULE unless $rule =~ $exclude_re;
649 next RULE unless $rule =~ $include_re;
652 $rule_num++; ### keep track of the abs num of rules
655 if ($rule =~ m|^(.*?)\s+\((.*)\)|) {
657 $rule_options = " $2 "; ### allows out-of-order options
659 &logr("[-] Unrecognized rule format at line: $line_num. " .
664 ### skip all icmp "Undefined Code" rules; psad properly
665 ### handles this, but not fwsnort (see the icmp-info.rules
667 if ($filename =~ /icmp/ and $rule_options =~ /undefined\s+code/i) {
673 ### parse header portion of Snort rule
674 my $hdr_hr = &parse_rule_hdr($rule_hdr, $line_num);
675 unless (keys %$hdr_hr) {
676 &logr("[-] Unrecognized rule header: \"$rule_hdr\" at " .
677 "line: $line_num, skipping.");
683 ### parse options portion of Snort rule
684 my ($parse_rv, $opts_hr, $patterns_ar)
685 = &parse_rule_options($rule_options,
686 &get_avg_hdr_len($hdr_hr),
695 print "[+] Found sid: $opts_hr->{'sid'} in $filename\n";
698 if ($queue_mode or $nfqueue_mode) {
700 ### In general, it is not easy to modify the signatures that
701 ### snort_inline would use; one would think that an optimzation
702 ### would be to remove all "content" keywords since the kernel
703 ### itself is doing this now, but consider the following:
705 ### Suppose there are two original Snort signatures like so:
707 ### msg: "SIG1"; content: "abc"; pcre: "(d|e)";
708 ### msg: "SIG2"; content: "xyz"; pcre: "(e|f)";
710 ### Now, suppose there is a packet with the following data:
712 ### packet data: "xyz------------e------"
714 ### Then the SIG1 matches when it shouldn't because the packet
715 ### does not contain "abc" (assuming the "abc" string is
716 ### removed from the signature that is actually deployed with
717 ### snort_inline). There does not seem to be a good solution
718 ### for this problem if pcre criteria are involved because the
719 ### two pcre's would have to be interpreted to see if there is
720 ### any data that could satisfy both at the same time.
722 ### However, performing the duplicate string matching is far
723 ### less expensive than not sending a large portion of network
724 ### traffic to userspace for analysis by snort_inline in the
725 ### first place. This is the real benefit of letting fwsnort
726 ### build a smarter iptables queueing policy. This does come
727 ### with a penalty against detection, since snort_inline is
728 ### only receiving individual packets that match one of the
729 ### content keywords in a signature; it does not get the
730 ### entire stream. But, this may be worth it for large sites
731 ### where performance is the primary concern. Also, there is
732 ### some potential for removing a subset of the content
733 ### matches if done in the right way; this is the reason the
734 ### queue_get_rule() function is stubbed in below.
735 my $queue_rule = &queue_get_rule($rule_hdr, $rule_options);
737 push @queue_rules, $queue_rule if $queue_rule;
740 ### construct the equivalent iptables rule and add it
741 ### to $config{'FWSNORT_SCRIPT'}
742 my ($ipt_rv, $num_rules) = &ipt_build($hdr_hr,
743 $opts_hr, $patterns_ar, $rule);
748 ### may have the rule in several chains
749 $ipt_rules_ctr += $num_rules;
751 print " Successful translation.\n";
755 print " Unsuccessful translation.\n";
758 $parsed_ctr++; ### keep track of successfully parsed rules
762 if (($queue_mode or $nfqueue_mode) and @queue_rules) {
763 open M, "> $config{'QUEUE_RULES_DIR'}/$filename" or die "[*] Could not ",
764 "open $config{'QUEUE_RULES_DIR'}/$filename: $!";
765 print M "#\n### This file generated with: fwsnort @argv_cp\n#\n\n";
766 print M "$_\n", for @queue_rules;
767 print M "\n### EOF ###\n";
771 if ($ipt_rules_ctr) {
772 $ipt_rules_ctr *= 2 if $ipt_drop;
773 $ipt_rules_ctr *= 2 if $ipt_reject;
774 push @ipt_script_lines,
775 qq|\$ECHO " Rules added: $ipt_rules_ctr"|;
778 unless ($include_sids) {
780 printf("%-10s%-10s%-10s%-10s\n", $parsed_ctr, $unsup_ctr,
781 $ipt_apply_ctr, $rule_num);
783 printf("%-10s%-10s%-10s\n", $parsed_ctr, $unsup_ctr,
788 unless ($include_sids) {
791 print "=======================================\n";
792 printf("%30s%-10s%-10s%-10s%-10s\n", ' ',
793 $abs_num, $tot_unsup_ctr, $tot_ipt_apply, $sabs_num);
796 print "=============================\n";
797 printf("%30s%-10s%-10s%-10s\n", ' ',
798 $abs_num, $tot_unsup_ctr, $sabs_num);
801 if ($abs_num) { ### we parsed at least one rule
802 print "[+] Generated $ipt_str rules for $abs_num out of ",
803 "$sabs_num signatures: ",
804 sprintf("%.2f", $abs_num/$sabs_num*100), "%\n";
806 print "[+] No rules parsed.\n";
809 print "[+] Found $tot_ipt_apply applicable snort rules to your " .
810 "current $ipt_str\n policy.\n";
816 sub parse_rule_options() {
817 my ($rule_options, $avg_hdr_len, $line_num) = @_;
823 ### get the sid here for logging purposes
824 if ($rule_options =~ /$snort_opts{'logprefix'}{'sid'}/) {
827 return 0, \%opts, \@patterns;
831 return 0, \%opts, \@patterns if defined $exclude_sids{$sid};
834 if (defined $include_sids{$sid}) {
835 &logr("[+] matched sid:$sid: $rule_options");
837 return 0, \%opts, \@patterns;
841 unless ($queue_mode or $nfqueue_mode) {
843 ### if we're queuing packets to userspace Snort, then we don't have to
844 ### disqualify a signature based on an option that is not supported by
846 my $found_unsupported = '';
847 for my $opt (keys %{$snort_opts{'unsupported'}}) {
848 ### see if we match a regex belonging to an unsupported option
849 if ($rule_options =~ /$snort_opts{'unsupported'}{$opt}/) {
850 $found_unsupported .= "'$opt', ";
853 if ($found_unsupported) {
854 $found_unsupported =~ s/,\s+$//;
855 &logr("[-] SID: $sid Unsupported option(s): $found_unsupported " .
856 "at line: $line_num, skipping.");
857 if (%include_sids and defined $include_sids{$sid}) {
858 print "[-] SID: $sid contain the unsupported option(s): ",
859 "$found_unsupported at line: $line_num\n";
861 return 0, \%opts, \@patterns;
865 if ($rule_options =~ /ip_proto\s*:.*ip_proto\s*:/) {
866 &logr("[-] SID: $sid, unsupported multiple ip_proto fields at " .
867 "line: $line_num, skipping.");
868 return 0, \%opts, \@patterns;
871 for my $opt (keys %{$snort_opts{'filter'}}) {
872 ### see if we match the option regex
873 if ($rule_options =~ /$snort_opts{'filter'}{$opt}{'regex'}/) {
875 $opts{$opt} = $1 if defined $1; ### some keywords may not have an option
879 my $found_content = 0;
880 while ($rule_options =~ /(\w+):?\s*((?:.*?[^\x5c]?))\s*;/g) {
883 $val = $2 if defined $2; ### some keywords may not have an argument
885 if ($opt eq 'content' or $opt eq 'uricontent') {
886 return 0, \%opts, \@patterns unless $val =~ /"$/;
889 return 0, \%opts, \@patterns unless $val =~ /\S/;
891 ### convert the string into a form that is more compatible
893 my ($rv, $log_str, $ipt_pattern_hr)
894 = &convert_pattern_for_iptables($val);
898 push @patterns, $ipt_pattern_hr;
900 &logr("[-] SID: $sid, $log_str");
901 return 0, \%opts, \@patterns;
904 } elsif ($opt eq 'pcre') {
907 $val =~ s|/\w{0,3}"$||;
909 ### see if this pcre only has strings separated with ".*" or ".+"
910 ### and if so translate to multple string matches
911 my ($pcre_rv, $pcre_strings_ar) = &parse_pcre($val);
913 for my $str (@$pcre_strings_ar) {
914 push @patterns, $str;
917 unless ($queue_mode or $nfqueue_mode) {
918 &logr("[-] SID: $sid, unsupported complex pcre: $val");
919 return 0, \%opts, \@patterns;
923 } elsif ($opt eq 'fast_pattern') {
924 if ($no_fast_pattern_order) {
925 ### force it to be the first pattern so no reordering
927 $patterns[0]->{'fast_pattern'} = 1;
929 $patterns[$#patterns]->{'fast_pattern'} = 1;
931 } elsif ($opt eq 'nocase') {
932 unless (defined $snort_opts{'ignore'}{'nocase'}) {
933 $patterns[$#patterns]->{'nocase'} = 1;
936 for my $key (qw(offset depth within distance)) {
938 my ($offsets_rv, $log_str)
939 = &define_offsets(\@patterns,
940 $avg_hdr_len, $key, $val);
941 unless ($offsets_rv) {
942 &logr("[-] SID: $sid, $log_str");
943 return 0, \%opts, \@patterns;
951 if (($queue_mode or $nfqueue_mode) and not $found_content) {
952 my $queue_str = 'QUEUE';
953 $queue_str = 'NFQUEUE' if $nfqueue_mode;
954 &logr("[-] SID: $sid In --$queue_str mode signature must have " .
955 "'content' or 'uricontent' keyword " .
956 "at line: $line_num, skipping.");
957 if (%include_sids and defined $include_sids{$sid}) {
958 print "[-] SID: $sid does not contain 'content' ",
961 return 0, \%opts, \@patterns;
964 ### update offset, depth, within, and distance values for relative
966 my ($offsets_rv, $log_str) = &update_offsets_relative_matches(\@patterns);
967 unless ($offsets_rv) {
968 &logr("[-] SID: $sid, $log_str");
969 return 0, \%opts, \@patterns;
972 for my $opt (keys %{$snort_opts{'logprefix'}}) {
973 if ($rule_options =~ /$snort_opts{'logprefix'}{$opt}/) {
978 unless ($queue_mode or $nfqueue_mode) {
979 while ($rule_options =~ /(\w+):\s*.*?;/g) {
981 if (not defined $opts{$option}
982 and not defined $snort_opts{'ignore'}{$option}) {
983 &logr("[-] SID: $sid bad option: \"$option\" at line: $line_num " .
985 return 0, \%opts, \@patterns;
989 if (defined $opts{'ipopts'}
990 and $opts{'ipopts'} ne 'rr'
991 and $opts{'ipopts'} ne 'ts'
992 and $opts{'ipopts'} ne 'ssrr'
993 and $opts{'ipopts'} ne 'lsrr'
994 and $opts{'ipopts'} ne 'any') {
995 &logr("[-] SID: $sid, unsupported ipopts field at " .
996 "line: $line_num, skipping.");
997 return 0, \%opts, \@patterns;
1000 if (defined $opts{'itype'}
1001 and ($opts{'itype'} =~ m|<| or $opts{'itype'} =~ m|>|)) {
1002 &logr("[-] SID: $sid, unsupported range operator in itype field " .
1003 "line: $line_num, skipping.");
1004 return 0, \%opts, \@patterns;
1006 if (defined $opts{'icode'}
1007 and ($opts{'icode'} =~ m|<| or $opts{'icode'} =~ m|>|)) {
1008 &logr("[-] SID: $sid, unsupported range operator in icode field " .
1009 "line: $line_num, skipping.");
1010 return 0, \%opts, \@patterns;
1012 if (defined $opts{'ip_proto'}
1013 and ($opts{'ip_proto'} =~ m|<| or $opts{'ip_proto'} =~ m|>|)) {
1014 &logr("[-] SID: $sid, unsupported range operator in ip_proto field " .
1015 "line: $line_num, skipping.");
1016 return 0, \%opts, \@patterns;
1021 return 1, \%opts, \@patterns;
1024 sub parse_rule_hdr() {
1025 my ($rule_hdr, $line_num) = @_;
1027 my $action = 'alert'; ### default
1028 if ($rule_hdr =~ /^\s*pass/) {
1030 } elsif ($rule_hdr =~ /^\s*log/) {
1033 if ($rule_hdr =~ m|^\s*\w+\s+(\S+)\s+(\S+)\s+(\S+)
1034 \s+(\S+)\s+(\S+)\s+(\S+)|ix) {
1042 unless ($proto =~ /^\w+$/) {
1043 &logr("[-] Unsupported protocol: \"$proto\" at line: " .
1044 "$line_num, skipping.");
1048 ### in --ip6tables mode make sure we're not looking at IPv4 addresses
1049 if ($enable_ip6tables
1050 and ($src =~ /\b$ip_re\b/ or $dst =~ /\b$ip_re\b/)) {
1051 &logr("[-] --ip6tables mode enabled but IPv4 " .
1052 "address in rule variable at line: $line_num.");
1056 ### in --ip6tables mode exclude the icmp protocol - maybe should
1057 ### change to icmp6 in the future
1058 if ($enable_ip6tables and $proto eq 'icmp') {
1059 &logr("[-] --ip6tables mode enabled, so excluding " .
1060 "icmp (non-icmp6) siganture at line: $line_num.");
1065 $bidir_flag = 1 if $bidir eq '<>';
1068 'action' => $action,
1072 'bidir' => $bidir_flag,
1077 ### map to expanded values (e.g. $HOME -> "any" or whatever
1078 ### is defined in fwsnort.conf)
1079 for my $var (qw(src sport dst dport)) {
1080 my $val = $hsh{$var};
1081 my $negate_flag = 0;
1082 $negate_flag = 1 if $val =~ m|!|;
1083 while ($val =~ /\$(\w+)/) {
1085 if (defined $config{$val}) {
1086 $val = $config{$val};
1087 if ($enable_ip6tables and $val =~ /\b$ip_re\b/) {
1088 &logr("[-] --ip6tables mode enabled but IPv4 " .
1089 "address in rule variable at line: $line_num.");
1093 &logr("[-] Undefined variable $val in rule header " .
1094 "at line: $line_num.");
1098 if ($negate_flag and $val !~ m|!|) {
1099 $hsh{$var} = "!$val";
1105 for my $var (qw(sport dport)) {
1106 next unless $hsh{$var} =~ /,/;
1107 if ($ipt_have_multiport_match) {
1108 $hsh{$var} =~ s/\[//;
1109 $hsh{$var} =~ s/\]//;
1111 my @ports = split /\s*,\s*/, $hsh{$var};
1113 for my $port (@ports) {
1114 if ($port =~ /\d+:$/) {
1115 $ports_str .= "${port}65535,";
1117 $ports_str .= "${port},";
1120 $ctr++ if $port =~ /\:/; ### a range counts for two ports
1121 ### multiport is limited to 15 ports
1122 last if $ctr >= $ipt_multiport_max;
1124 $ports_str =~ s/,$//;
1125 $hsh{$var} = $ports_str;
1127 &logr("[-] Warning: taking the first port in the list " .
1128 "$hsh{$var} until the $ipt_str multiport match is supported " .
1129 "at line: $line_num.");
1130 $hsh{$var} =~ s/,.*//;
1131 $hsh{$var} =~ s/\[//;
1132 $hsh{$var} =~ s/\]//;
1146 if ($pcre =~ m|^\w+$|) {
1147 push @patterns, (&convert_pattern_for_iptables($pcre))[2];
1149 } elsif ($pcre =~ m|UNION\x5c\x73\x2bSELECT|) {
1150 ### a bunch of Emerging Threats rules contain "UNION\s+SELECT"
1151 ### as a PCRE. Sure, the translation below can be evaded, but
1152 ### it is better than nothing.
1153 push @patterns, (&convert_pattern_for_iptables('UNION SELECT'))[2];
1157 if ($pcre =~ m|\.\*|) {
1158 @ar = split /\.\*/, $pcre;
1160 } elsif ($pcre =~ m|\.\+|) {
1161 @ar = split /\.\+/, $pcre;
1163 } elsif ($pcre =~ m|\x5b\x5e\x5c\x6e\x5d\x2b|) { ### [^\n]+
1164 @ar = split /\x5b\x5e\x5c\x6e\x5d\x2b/, $pcre;
1168 for my $part (@ar) {
1169 next unless $part; ### some Snort pcre's begin with .* or .+
1170 ### (which seems useless)
1172 ### Replace "\(" with hex equivalent in PCRE's
1173 ### like: /.+ASCII\(.+SELECT/
1174 $part =~ s/\x5c\x28/|5c 28|/;
1176 ### Replace "\:" with hex equivalent in PCRE's
1177 ### like: /User-Agent\:[^\n]+spyaxe/
1178 $part =~ s/\x5c\x3a/|5c 3a|/;
1181 $basic =~ s/\|5c 28\|//;
1182 $basic =~ s/\|5c 3a\|//;
1184 if ($basic =~ /^[\w\x20]+$/) {
1185 push @patterns, (&convert_pattern_for_iptables($part))[2];
1186 } elsif ($basic eq 'User-Agent') {
1187 push @patterns, (&convert_pattern_for_iptables($part))[2];
1194 return $rv, \@patterns;
1197 sub queue_get_rule() {
1198 my ($rule_hdr, $rule_opts) = @_;
1200 ### FIXME: the following commented out code would need to be
1201 ### drastically improved to ensure that the remaining signatures
1202 ### are completely unique in userspace. For now, just return
1203 ### the original Snort rule
1204 ### Remove all of the following keywords since they are handled
1205 ### within the kernel directly.
1206 # for my $key qw/uricontent content offset depth within distance/ {
1207 # $rule_opts =~ s/([\s;])$key:\s*.*?\s*;\s*/$1/g;
1210 $rule_opts =~ s/^\s*//;
1211 $rule_opts =~ s/\s*$//;
1213 return "$rule_hdr ($rule_opts)";
1216 sub ipt_allow_traffic() {
1217 my ($hdr_hr, $opts_hr, $chain, $orig_snort_rule) = @_;
1222 print "\n[+] Snort rule: $orig_snort_rule"
1223 unless defined $snort_dump_cache{$orig_snort_rule};
1224 $snort_dump_cache{$orig_snort_rule} = '';
1227 ### check to see if the header is allowed through the chain,
1228 ### and if not we don't really care about matching traffic
1229 ### because iptables doesn't allow it anyway
1230 RULE: for my $rule_hr (@{$ipt_policy{$chain}}) {
1233 if ($dumper and $verbose) {
1234 print "[+] RULE: $rule_ctr:\n",
1238 print "[+] $ipt_str rule: $rule_hr->{'raw'}\n"
1239 unless defined $ipt_dump_cache{$rule_hr->{'raw'}};
1240 $ipt_dump_cache{$rule_hr->{'raw'}} = '';
1243 ### don't match on rules to/from the loopback interface
1244 unless ($no_exclude_loopback) {
1245 if ($rule_hr->{'intf_in'} eq 'lo'
1246 or $rule_hr->{'intf_out'} eq 'lo') {
1247 print "[-] Skipping $chain rule $rule_ctr: loopback rule\n"
1253 ### don't match on rules that build state
1254 if ($rule_hr->{'extended'} =~ /state/) {
1255 print "[-] Skipping $chain rule $rule_ctr: state rule\n"
1261 unless (($hdr_hr->{'proto'} eq $rule_hr->{'proto'}
1262 or $rule_hr->{'proto'} eq 'all')) {
1263 print "[-] Skipping $chain rule $rule_ctr: $hdr_hr->{'proto'} ",
1264 "!= $rule_hr->{'proto'}\n" if $debug;
1268 ### match src/dst IP/network
1269 unless (&match_addr($hdr_hr->{'src'}, $rule_hr->{'src'})) {
1270 print "[-] Skipping $chain rule $rule_ctr: src $hdr_hr->{'src'} ",
1271 "not part of $rule_hr->{'src'}\n" if $debug;
1274 unless (&match_addr($hdr_hr->{'dst'}, $rule_hr->{'dst'})) {
1275 print "[-] Skipping $chain rule $rule_ctr: dst $hdr_hr->{'dst'} ",
1276 "not part of $rule_hr->{'dst'}\n" if $debug;
1280 ### match src/dst ports
1281 if ($hdr_hr->{'proto'} ne 'icmp') {
1282 unless (&match_port($hdr_hr->{'sport'},
1283 $rule_hr->{'sport'})) {
1284 print "[-] Skipping $chain rule $rule_ctr: sport ",
1285 "$hdr_hr->{'sport'} not part of $rule_hr->{'sport'}\n"
1289 unless (&match_port($hdr_hr->{'dport'},
1290 $rule_hr->{'dport'})) {
1291 print "[-] Skipping $chain rule $rule_ctr: dport ",
1292 "$hdr_hr->{'dport'} not part of $rule_hr->{'dport'}\n"
1298 if (defined $opts_hr->{'flow'} and $rule_hr->{'state'}) {
1299 if ($opts_hr->{'flow'} eq 'established') {
1300 unless ($rule_hr->{'state'} =~ /ESTABLISHED/) {
1301 print "[-] Skipping $chain rule $rule_ctr: state ",
1302 "$opts_hr->{'flow'} not part of $rule_hr->{'state'}\n"
1309 ### if we make it here, then this rule matches the signature
1310 ### (from a header perspective)
1311 if ($rule_hr->{'target'} eq 'DROP'
1312 or $rule_hr->{'target'} eq 'REJECT') {
1314 print "[-] Matching $ipt_str rule has DROP or REJECT target; ",
1315 "$ipt_str policy does not allow this Snort rule.\n"
1318 print "\n[-] RULE $chain DROP:\n",
1325 } elsif ($rule_hr->{'target'} eq 'ACCEPT') {
1327 print "\n[+] RULE $chain ACCEPT:\n",
1333 print "[-] Matching $ipt_str rule has ACCEPT target; ",
1334 "$ipt_str policy allows this Snort rule.\n" if $debug;
1336 } ### we don't support other targets besides DROP, REJECT,
1337 ### or ACCEPT for now.
1340 ### if we make it here, then no specific ACCEPT rule matched the header,
1341 ### so return false if the chain policy is set to DROP (or there is
1342 ### a default drop rule). Otherwise there is no rule that would block
1344 if (defined $ipt_default_policy_setting{$chain}) {
1345 if ($ipt_default_policy_setting{$chain} eq 'ACCEPT') {
1346 if (defined $ipt_default_drop{$chain}) {
1347 if (defined $ipt_default_drop{$chain}{'all'}) {
1348 print "[-] Default DROP rule applies to this Snort rule.\n"
1351 } elsif (defined $ipt_default_drop{$chain}
1352 {$hdr_hr->{'proto'}}) {
1353 print "[-] Default DROP rule applies to this Snort rule.\n"
1359 print "\nACCEPT $chain, no $ipt_str matching rule\n",
1368 print "\nDROP $chain, no $ipt_str matching rule\n",
1374 ### maybe a "strict" option should be added here?
1379 my ($hdr_src, $rule_src) = @_;
1380 return 1 if $rule_src eq '0.0.0.0/0';
1381 return 1 if $hdr_src =~ /any/i;
1382 return 1 if $hdr_src eq $rule_src;
1385 my $ipt_mask = '32';
1391 $negate = 1 if $hdr_src =~ /\!/;
1393 if ($rule_src =~ /\!/) {
1395 ### if both hdr_src and rule_src are negated
1396 ### then revert to normal match.
1403 if ($rule_src =~ m|($ip_re)/($ip_re)|) {
1406 } elsif ($rule_src =~ m|($ip_re)/(\d+)|) {
1409 } elsif ($rule_src =~ m|($ip_re)|) {
1413 $ipt_obj = new NetAddr::IP($ipt_ip, $ipt_mask);
1415 for my $addr (@{&expand_addresses($hdr_src)}) {
1417 my $src_mask = '32';
1418 if ($addr =~ m|($ip_re)/($ip_re)|) {
1421 } elsif ($addr =~ m|($ip_re)/(\d+)|) {
1424 } elsif ($addr =~ m|($ip_re)|) {
1427 $s_obj = new NetAddr::IP($src_ip, $src_mask);
1429 return 1 unless $ipt_obj->within($s_obj);
1431 return 1 if $ipt_obj->within($s_obj);
1438 my ($snort_port, $ipt_port) = @_;
1439 return 1 if $ipt_port eq '0:0';
1440 return 1 if $snort_port =~ /any/i;
1441 return 1 if $ipt_port eq $snort_port;
1443 my $ipt_end = 65535;
1447 if ($ipt_port =~ /:/) {
1448 if ($ipt_port =~ /(\d+):/) {
1451 if ($ipt_port =~ /:(\d+)/) {
1454 } elsif ($ipt_port =~ /(\d+)/) {
1455 $ipt_start = $ipt_end = $1;
1458 if ($snort_port =~ /:/) {
1459 if ($snort_port =~ /(\d+):/) {
1462 if ($snort_port =~ /:(\d+)/) {
1465 } elsif ($snort_port =~ /(\d+)/) {
1466 $h_start = $h_end = $1;
1469 if ($ipt_port =~ /!/) {
1470 if ($snort_port =~ /!/) {
1473 return 1 if (($h_start < $ipt_start and $h_end < $ipt_start)
1474 or ($h_start > $ipt_end and $h_end > $ipt_end));
1477 if ($snort_port =~ /!/) {
1478 return 1 if (($ipt_start < $h_start and $ipt_end < $h_start)
1479 or ($ipt_start > $h_end and $ipt_end > $h_end));
1481 return 1 if $h_start >= $ipt_start and $h_end <= $ipt_end;
1487 sub cache_ipt_policy() {
1489 my $ipt = new IPTables::Parse 'iptables' => $cmds{'iptables'}
1490 or die "[*] Could not acquire IPTables::Parse object: $!";
1492 for my $chain (keys %process_chains) {
1493 next unless $process_chains{$chain};
1495 $ipt_policy{$chain} = $ipt->chain_rules('filter',
1498 $ipt_default_policy_setting{$chain}
1499 = $ipt->chain_policy('filter', $chain, $ipt_file);
1501 my ($def_drop_hr, $ipt_rv)
1502 = $ipt->default_drop('filter', $chain, $ipt_file);
1505 $ipt_default_drop{$chain} = $def_drop_hr;
1512 my ($snort_hdr_hr, $snort_opts_hr, $patterns_ar, $orig_snort_rule) = @_;
1517 my %process_rules = ();
1519 ### define iptables source and destination
1520 if ($snort_hdr_hr->{'dst'} =~ /any/i) {
1521 if ($snort_hdr_hr->{'src'} =~ /any/i) {
1522 push @{$process_rules{'INPUT'}}, '' if $process_chains{'INPUT'};
1523 push @{$process_rules{'FORWARD'}}, ''
1524 if $process_chains{'FORWARD'};
1526 my $addr_ar = &expand_addresses($snort_hdr_hr->{'src'});
1528 $negate = '! ' if $snort_hdr_hr->{'src'} =~ m|!|;
1530 &logr("[-] No valid source IPs/networks in Snort " .
1534 for my $src (@$addr_ar) {
1535 if (&is_local($src)) {
1536 push @{$process_rules{'OUTPUT'}},
1537 "$negate$ipt_hdr_opts{'src'} ${src}"
1538 if $process_chains{'OUTPUT'};
1540 push @{$process_rules{'INPUT'}},
1541 "$negate$ipt_hdr_opts{'src'} ${src}"
1542 if $process_chains{'INPUT'};
1544 push @{$process_rules{'FORWARD'}},
1545 "$negate$ipt_hdr_opts{'src'} ${src}"
1546 if $process_chains{'FORWARD'};
1550 my $dst_addr_ar = &expand_addresses($snort_hdr_hr->{'dst'});
1551 unless ($dst_addr_ar) {
1552 &logr("[-] No valid destination IPs/networks in Snort rule " .
1556 if ($snort_hdr_hr->{'src'} =~ /any/i) {
1558 $negate = '! ' if $snort_hdr_hr->{'dst'} =~ m|!|;
1559 for my $dst (@$dst_addr_ar) {
1560 if (&is_local($dst)) {
1561 push @{$process_rules{'INPUT'}},
1562 "$negate$ipt_hdr_opts{'dst'} ${dst}"
1563 if $process_chains{'INPUT'};
1565 push @{$process_rules{'OUTPUT'}},
1566 "$negate$ipt_hdr_opts{'dst'} ${dst}"
1567 if $process_chains{'OUTPUT'};
1569 push @{$process_rules{'FORWARD'}},
1570 "$negate$ipt_hdr_opts{'dst'} ${dst}"
1571 if $process_chains{'FORWARD'};
1574 my $src_addr_ar = &expand_addresses($snort_hdr_hr->{'src'});
1575 my $negate_src = '';
1576 $negate_src = '! ' if $snort_hdr_hr->{'src'} =~ m|!|;
1577 my $negate_dst = '';
1578 $negate_dst = '! ' if $snort_hdr_hr->{'dst'} =~ m|!|;
1579 unless ($src_addr_ar) {
1580 &logr("[-] No valid source IPs/networks in Snort rule " .
1584 for my $src (@$src_addr_ar) {
1585 for my $dst (@$dst_addr_ar) {
1586 if (&is_local($dst)) {
1587 push @{$process_rules{'INPUT'}},
1588 "$negate_src$ipt_hdr_opts{'src'} ${src}" .
1589 " $negate_dst$ipt_hdr_opts{'dst'} ${dst}"
1590 if $process_chains{'INPUT'};
1592 push @{$process_rules{'OUTPUT'}},
1593 "$negate_src$ipt_hdr_opts{'src'} ${src}" .
1594 " $negate_dst$ipt_hdr_opts{'dst'} ${dst}"
1595 if $process_chains{'OUTPUT'};
1597 push @{$process_rules{'FORWARD'}},
1598 "$negate_src$ipt_hdr_opts{'src'} ${src}" .
1599 " $negate_dst$ipt_hdr_opts{'dst'} ${dst}"
1600 if $process_chains{'FORWARD'};
1606 ### determine which chain (e.g. stateful/stateless)
1607 my $flow_established = '';
1608 unless ($no_ipt_conntrack) {
1609 if (defined $snort_hdr_hr->{'proto'}
1610 and $snort_hdr_hr->{'proto'} =~ /tcp/i
1611 and defined $snort_opts_hr->{'flow'}
1612 and $snort_opts_hr->{'flow'} =~ /established/i) {
1613 $flow_established = 'ESTABLISHED';
1617 my $add_snort_comment = 1;
1618 my $add_perl_trigger = 1;
1619 for my $chain (keys %process_chains) {
1621 next unless $process_chains{$chain} and $process_rules{$chain};
1623 for my $src_dst (@{$process_rules{$chain}}) {
1625 my $rule = "\$${ipt_var_str} -A ";
1626 my $save_rule = '-A';
1627 my $fwsnort_chain = '';
1629 ### see if we can jump to the ESTABLISHED inspection chain.
1630 if ($flow_established) {
1631 $rule .= $config{"FWSNORT_${chain}_ESTAB"};
1632 $fwsnort_chain = $config{"FWSNORT_${chain}_ESTAB"};
1634 $rule .= $config{"FWSNORT_$chain"};
1635 $fwsnort_chain = $config{"FWSNORT_$chain"};
1638 ### append interface restriction if necessary
1639 if ($src_dst =~ m|127\.0\.0\.\d/|) {
1640 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
1641 $rule .= ' ! -i lo';
1642 } elsif ($chain eq 'OUTPUT') {
1643 $rule .= ' ! -o lo';
1647 ### append source and destination criteria
1649 if ($chain eq 'FORWARD') {
1650 $rule .= " $src_dst";
1651 } elsif ($chain eq 'INPUT') {
1652 ### we always treat the INPUT chain as part of the HOME_NET;
1653 ### the system running iptables may have an interface on the
1654 ### external network and hence may not be part of the HOME_NET
1655 ### as defined in the fwsnort.conf file so we don't necessarily
1656 ### append the IP criteria
1657 if ($src_dst ne "$ipt_hdr_opts{'dst'} $config{'HOME_NET'}") {
1658 $rule .= " $src_dst";
1660 } elsif ($chain eq 'OUTPUT') {
1661 if ($src_dst ne "$ipt_hdr_opts{'src'} $config{'HOME_NET'}") {
1662 $rule .= " $src_dst";
1667 my $rv = &ipt_build_rule(
1681 $add_snort_comment = 0;
1682 $add_perl_trigger = 0;
1687 return $found_rule, $num_rules;
1693 return 1 if $no_addr_check;
1698 if ($addr =~ m|($ip_re)/($ip_re)|) {
1701 } elsif ($addr =~ m|($ip_re)/(\d+)|) {
1704 } elsif ($addr =~ m|($ip_re)|) {
1708 my $ip_obj = new NetAddr::IP($ip, $mask);
1710 for my $local_ar (@local_addrs) {
1711 my $local_ip = $local_ar->[0];
1712 my $local_mask = $local_ar->[1];
1714 my $local_obj = new NetAddr::IP($local_ip, $local_mask);
1716 return 1 if $ip_obj->within($local_obj);
1721 sub get_local_addrs() {
1722 open IFC, "$cmds{'ifconfig'} -a |" or die "[*] Could not run ",
1723 "$cmds{'ifconfig'}: $!";
1728 for my $line (@lines) {
1729 if ($line =~ /^(\w+)\s+Link/) {
1733 next if $intf_name eq 'lo';
1734 next if $intf_name =~ /dummy/i;
1735 if ($line =~ /^\s+inet.*?:($ip_re).*:($ip_re)/i) {
1736 push @local_addrs, [$1, $2];
1742 sub ipt_build_rule() {
1743 my ($chain, $fwsnort_chain, $rule, $hdr_hr, $opts_hr, $patterns_ar,
1744 $orig_snort_rule, $flow_logging_prefix, $add_snort_comment,
1745 $add_perl_trigger) = @_;
1747 ### $chain is used only to see whether or not we need to add the
1748 ### rule to the iptables script based on whether the built-in chain
1749 ### will pass the traffic in the first place.
1751 return 0 unless &ipt_allow_traffic($hdr_hr,
1752 $opts_hr, $chain, $orig_snort_rule);
1755 ### append the protocol to the rule
1756 if (defined $opts_hr->{'ip_proto'}) {
1757 return 0 unless $opts_hr->{'ip_proto'} =~ /^\w+$/;
1758 $rule .= " $snort_opts{'filter'}{'ip_proto'}{'iptopt'} " .
1759 "$opts_hr->{'ip_proto'}";
1761 return 0 unless $hdr_hr->{'proto'} =~ /^\w+$/;
1762 if ((($hdr_hr->{'sport'} !~ /any/i and $hdr_hr->{'sport'} ne '')
1763 or ($hdr_hr->{'dport'} !~ /any/i
1764 and $hdr_hr->{'dport'} ne ''))
1765 and $hdr_hr->{'proto'} !~ /tcp/i
1766 and $hdr_hr->{'proto'} !~ /udp/i) {
1767 ### force to tcp because iptables does not like src/dst
1768 ### ports with anything other than tcp or udp
1769 $hdr_hr->{'proto'} = 'tcp';
1772 if ($hdr_hr->{'proto'} =~ /ip/) {
1773 $rule .= " $ipt_hdr_opts{'proto'} $hdr_hr->{'proto'}";
1775 $rule .= " $ipt_hdr_opts{'proto'} $hdr_hr->{'proto'} " .
1776 "-m $hdr_hr->{'proto'}";
1780 ### append the source and destination ports
1781 for my $type (qw(sport dport)) {
1782 if (defined $hdr_hr->{$type} and $hdr_hr->{$type} !~ /any/i) {
1784 my $port = $hdr_hr->{$type};
1785 $negate = '! ' if $port =~ m|!|;
1786 $port =~ s/\!\s*(\d)/$1/;
1789 $rule .= " -m multiport ${negate}--${type}s $port";
1791 $rule .= " ${negate}$ipt_hdr_opts{$type} $port";
1796 my $rv = &ipt_build_opts($rule, $hdr_hr, $opts_hr, $patterns_ar,
1797 $orig_snort_rule, $flow_logging_prefix, $chain, $fwsnort_chain,
1798 $add_snort_comment, $add_perl_trigger);
1803 sub ipt_build_opts() {
1804 my ($rule, $hdr_hr, $opts_hr, $patterns_ar, $orig_snort_rule,
1805 $flow_logging_prefix, $chain, $fwsnort_chain, $add_snort_comment,
1806 $add_perl_trigger) = @_;
1808 ### append tcp flags
1809 if (defined $opts_hr->{'flags'}) {
1812 $f_str .= 'URG,' if $opts_hr->{'flags'} =~ /U/i;
1813 $f_str .= 'ACK,' if $opts_hr->{'flags'} =~ /A/i;
1814 $f_str .= 'PSH,' if $opts_hr->{'flags'} =~ /P/i;
1815 $f_str .= 'RST,' if $opts_hr->{'flags'} =~ /R/i;
1816 $f_str .= 'SYN,' if $opts_hr->{'flags'} =~ /S/i;
1817 $f_str .= 'FIN,' if $opts_hr->{'flags'} =~ /F/i;
1820 if ($opts_hr->{'flags'} =~ /\+/) {
1821 ### --tcp-flags ACK ACK
1822 $rule .= " $snort_opts{'filter'}{'flags'}{'iptopt'} " .
1825 ### --tcp-flags ALL URG,PSH,SYN,FIN
1826 $rule .= " $snort_opts{'filter'}{'flags'}{'iptopt'} " .
1831 if ($no_ipt_conntrack) {
1832 ### fall back to appending --tcp-flags ACK ACK if flow=established.
1833 ### NOTE: we can't really handle "flow" in the same way snort can,
1834 ### since there is no way to keep track of which side initiated the
1835 ### tcp session (where the SYN packet came from), but older versions
1836 ### of snort (pre 1.9) just used tcp flags "A+" to keep track of
1837 ### this... we need to do the same.
1838 if (defined $opts_hr->{'flow'} && ! defined $opts_hr->{'flags'}) {
1839 if ($opts_hr->{'flow'} =~ /established/i) {
1840 ### note that this ignores the "stateless" keyword
1842 $rule .= " $snort_opts{'filter'}{'flow'}{'iptopt'} ACK ACK";
1847 ### append icmp type
1848 if (defined $opts_hr->{'itype'} and $hdr_hr->{'proto'} =~ /icmp/i) {
1849 $rule .= " $snort_opts{'filter'}{'itype'}{'iptopt'} " .
1850 "$opts_hr->{'itype'}";
1851 ### append icmp code (becomes "--icmp-type type/code")
1852 if (defined $opts_hr->{'icode'}) {
1853 $rule .= "/$opts_hr->{'icode'}";
1857 ### append ip options
1858 if (defined $opts_hr->{'ipopts'}) {
1859 $rule .= " $snort_opts{'filter'}{'ipopts'}{'iptopt'} " .
1860 "--$opts_hr->{'ipopts'}"
1863 ### append tos (requires CONFIG_IP_NF_MATCH_TOS)
1864 if (defined $opts_hr->{'tos'}) {
1865 $rule .= " $snort_opts{'filter'}{'tos'}{'iptopt'} " .
1870 ### append ttl (requires CONFIG_IP_NF_MATCH_TTL)
1871 if (defined $opts_hr->{'ttl'}) {
1872 if ($opts_hr->{'ttl'} =~ /\<\s*(\d+)/) {
1873 $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} --ttl-lt $1";
1874 } elsif ($opts_hr->{'ttl'} =~ /\>\s*(\d+)/) {
1875 $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} --ttl-gt $1";
1877 $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} " .
1878 "--ttl-eq $opts_hr->{'ttl'}";
1882 my $avg_hdr_len = &get_avg_hdr_len($hdr_hr);
1884 ### append dsize option (requires CONFIG_IP_NF_MATCH_LENGTH)
1885 if (defined $opts_hr->{'dsize'}) {
1886 ### get the average packet header size based on the protocol
1887 ### (the iptables length match applies to the network header
1889 if ($opts_hr->{'dsize'} =~ m|(\d+)\s*<>\s*(\d+)|) {
1890 my $iptables_len1 = $1 + $avg_hdr_len;
1891 my $iptables_len2 = $2 + $avg_hdr_len;
1892 $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " .
1893 "$iptables_len1:$iptables_len2";
1894 } elsif ($opts_hr->{'dsize'} =~ m|<\s*(\d+)|) {
1895 my $iptables_len = $1 + $avg_hdr_len;
1896 $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " .
1897 "$avg_hdr_len:$iptables_len";
1898 } elsif ($opts_hr->{'dsize'} =~ m|>\s*(\d+)|) {
1899 my $iptables_len = $1 + $avg_hdr_len;
1900 if ($iptables_len < $config{'MAX_FRAME_LEN'} + $avg_hdr_len) {
1901 $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " .
1903 ($config{'MAX_FRAME_LEN'} + $avg_hdr_len);
1905 } elsif ($opts_hr->{'dsize'} =~ m|(\d+)|) {
1906 my $iptables_len = $1 + $avg_hdr_len;
1907 $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " .
1912 ### append snort content options
1913 my $ipt_content_criteria = 0;
1914 my $perl_trigger_str = '';
1916 if ($#$patterns_ar > -1) {
1917 ($ipt_content_criteria, $perl_trigger_str)
1918 = &build_content_matches($opts_hr, $patterns_ar);
1920 return 0 unless $ipt_content_criteria;
1921 $rule .= $ipt_content_criteria;
1924 ### print the rest of the logprefix snort options in a comment
1925 ### one line above the rule
1927 my $target_str = '';
1928 for my $key (qw(sid msg classtype reference priority rev)) {
1929 if (defined $opts_hr->{$key}) {
1930 $comment .= qq|$key:$opts_hr->{$key}; |;
1933 $comment =~ s/\s*$//;
1936 ### append the fwsnort version as "FWS:$version"
1937 $comment .= " FWS:$version;";
1939 ### build up the logging prefix and comment match
1940 if (defined $opts_hr->{'sid'}) {
1941 unless ($no_ipt_comments) {
1942 ### add the Snort msg (and other) fields to the iptables rule
1943 ### with the 'comment' match (which can handle up to 255 chars
1944 ### and is set/verified by the ipt_find_max_comment_len()
1946 $comment =~ s|\"||g;
1947 $comment =~ s|/\*||g;
1948 $comment =~ s|\*/||g;
1949 if (length($comment) < $ipt_max_comment_len) {
1950 $target_str = qq| -m comment --comment "$comment"|;
1953 ### increment chain counter and add in if necessary
1954 $chain_ctr{$chain}++;
1956 if ($queue_mode or $nfqueue_mode) {
1958 $target_str .= qq| -j QUEUE|;
1960 $target_str .= qq| -j NFQUEUE|;
1962 $target_str .= " --queue-num $nfqueue_num";
1966 if ($hdr_hr->{'action'} eq 'log' or $ulog_mode) {
1967 $target_str .= qq| -j ULOG --ulog-nlgroup $ulog_nlgroup --ulog-prefix "|;
1969 $target_str .= ' -j LOG ';
1970 $target_str .= '--log-ip-options ' unless $no_ipt_log_ip_opts;
1971 if ($hdr_hr->{'proto'} eq 'tcp') {
1972 $target_str .= '--log-tcp-options ' unless $no_ipt_log_tcp_opts;
1973 $target_str .= '--log-tcp-sequence ' if $ipt_log_tcp_seq;
1975 $target_str .= qq|--log-prefix |;
1979 ### build up the LOG prefix string
1980 my $prefix_str = '';
1982 unless ($no_ipt_rule_nums) {
1983 $prefix_str .= "[$chain_ctr{$chain}] ";
1987 $prefix_str .= 'DRP ';
1988 } elsif ($ipt_reject) {
1989 $prefix_str .= 'REJ ';
1992 ### always add the sid
1993 $prefix_str .= qq|SID$opts_hr->{'sid'} |;
1994 if ($flow_logging_prefix) {
1995 $prefix_str .= 'ESTAB ';
1998 if (length($prefix_str) >= $ipt_max_log_prefix_len) {
1999 $prefix_str = qq|SID$opts_hr->{'sid'} |;
2000 if (length($prefix_str) >= $ipt_max_log_prefix_len) {
2001 return 0 unless $ipt_content_criteria;
2005 $target_str .= qq|"| . $prefix_str . qq|"|;
2009 ### print the snort rules type header to the fwsnort.sh script
2010 unless ($ipt_print_type) {
2011 &ipt_type($snort_type);
2012 $ipt_print_type = 1;
2015 ### write the rule out to the iptables script
2016 &ipt_add_rule($hdr_hr, $opts_hr, $orig_snort_rule,
2017 $rule, $target_str, "### $comment", $add_snort_comment,
2018 $perl_trigger_str, $add_perl_trigger, $chain, $fwsnort_chain);
2022 sub build_content_matches() {
2023 my ($opts_hr, $patterns_ar) = @_;
2025 my $fast_pattern_index = 0;
2026 my $fast_pattern_is_set = 0;
2027 my $ipt_content_criteria = '';
2028 my $perl_trigger_command = '';
2029 my @content_fast_pattern_order = ();
2031 $perl_trigger_command = q|perl -e 'print "| if $include_perl_triggers;
2033 if ($no_fast_pattern_order) {
2034 $patterns_ar->[0]->{'fast_pattern'} = 1;
2037 for (my $index=0; $index <= $#$patterns_ar; $index++) {
2038 if ($patterns_ar->[$index]->{'fast_pattern'}) {
2039 $fast_pattern_index = $index;
2040 $fast_pattern_is_set = 1;
2045 unless ($fast_pattern_is_set) {
2046 ### in this case, the 'fast_pattern' option was not used, so pick the
2047 ### longest pattern to match first (this should help with performance
2048 ### of signature matches on average)
2050 my $max_len_index = 0;
2051 PATTERN: for (my $index=0; $index <= $#$patterns_ar; $index++) {
2052 my $pat_ar = $patterns_ar->[$index];
2054 if ($pat_ar->{'length'} > $max_len) {
2056 ### make sure it is not a relative match
2057 next PATTERN if defined $pat_ar->{'distance'};
2058 next PATTERN if defined $pat_ar->{'within'};
2060 if ($index < $#$patterns_ar) {
2061 my $next_pat_ar = $patterns_ar->[$index+1];
2062 next PATTERN if defined $next_pat_ar->{'distance'};
2063 next PATTERN if defined $next_pat_ar->{'within'};
2066 $max_len = $pat_ar->{'length'};
2067 $max_len_index = $index;
2070 $fast_pattern_index = $max_len_index;
2073 $content_fast_pattern_order[0] = $patterns_ar->[$fast_pattern_index];
2074 for (my $i=0; $i <= $#$patterns_ar; $i++) {
2075 next if $i == $fast_pattern_index;
2076 push @content_fast_pattern_order, $patterns_ar->[$i];
2079 for (my $i=0; $i <= $#content_fast_pattern_order; $i++) {
2081 if (($queue_mode or $nfqueue_mode) and $queue_pre_match_max > 0) {
2082 ### limit the number of content matches to perform within the
2083 ### kernel before sending the packet to a userspace Snort
2085 last if $i >= $queue_pre_match_max;
2088 my $pattern_hr = $content_fast_pattern_order[$i];
2089 my $content_str = $pattern_hr->{'ipt_pattern'};
2091 if ($content_str =~ /\|.+\|/) {
2092 ### there is hex data in the content
2093 if ($pattern_hr->{'negative_match'}) {
2094 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2095 {'content'}{'iptopt'} . ' ' .
2096 qq{! --hex-string "$content_str"};
2098 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2099 {'content'}{'iptopt'} . ' ' .
2100 qq{--hex-string "$content_str"};
2103 ### there is no hex data in the content
2104 if ($pattern_hr->{'negative_match'}) {
2105 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2106 {'content'}{'iptopt'} . ' ' .
2107 qq{! --string "$content_str"};
2109 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2110 {'content'}{'iptopt'} . ' ' .
2111 qq{--string "$content_str"};
2115 if (defined $opts_hr->{'replace'}) {
2116 my $replace_str = $opts_hr->{'replace'};
2117 $replace_str =~ s/`/\\`/g;
2118 if ($replace_str =~ /\|.+\|/) { ### there is hex data in the content
2119 $ipt_content_criteria
2120 .= qq{ --replace-hex-string "$replace_str"};
2122 $ipt_content_criteria
2123 .= qq{ --replace-string "$replace_str"};
2127 if ($kernel_ver ne '2.4') {
2128 $ipt_content_criteria .= " --algo $string_match_alg";
2130 ### see if we have any offset, depth, distance, or within
2133 if (defined $pattern_hr->{'offset'}) {
2135 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2136 {'offset'}{'iptopt'} . " $pattern_hr->{'offset'}";
2137 $perl_trigger_command .= 'A'x$pattern_hr->{'offset'}
2138 if $include_perl_triggers;
2140 } elsif (defined $pattern_hr->{'distance'}) { ### offset trumps distance
2142 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2143 {'distance'}{'iptopt'} . " $pattern_hr->{'distance'}";
2145 $perl_trigger_command .= 'A'x$pattern_hr->{'distance'}
2146 if $include_perl_triggers;
2149 if (defined $pattern_hr->{'depth'}) {
2151 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2152 {'depth'}{'iptopt'} . " $pattern_hr->{'depth'}";
2154 $perl_trigger_command .= 'A'x$pattern_hr->{'depth'}
2155 if $include_perl_triggers;
2157 } elsif (defined $pattern_hr->{'within'}) { ### depth trumps within
2159 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2160 {'within'}{'iptopt'} . " $pattern_hr->{'within'}";
2162 $perl_trigger_command .= 'A'x$pattern_hr->{'within'}
2163 if $include_perl_triggers;
2166 ### see if we need to match the string match case insensitive
2167 if ($pattern_hr->{'nocase'}) {
2168 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2169 {'nocase'}{'iptopt'};
2172 ### if the --payload option is available for
2173 ### the string match extension
2174 $ipt_content_criteria .= ' --payload'
2175 if $ipt_has_string_payload_offset_opt;
2178 if ($include_perl_triggers) {
2179 ### now append the perl trigger command bytes
2180 if ($pattern_hr->{'negative_match'}) {
2181 $perl_trigger_command .= 'A'x($pattern_hr->{'length'});
2183 $perl_trigger_command .= &translate_perl_trigger($content_str);
2188 if ($include_perl_triggers) {
2189 $perl_trigger_command .= qq|"'|;
2192 return $ipt_content_criteria, $perl_trigger_command;
2195 sub convert_pattern_for_iptables() {
2196 my $snort_str = shift;
2199 my $ipt_str = $snort_str;
2203 'orig_snort_str' => $snort_str,
2204 'ipt_pattern' => '',
2205 'negative_match' => 0,
2206 'fast_pattern' => 0,
2210 if ($ipt_str =~ /\s*\!\s*"/) {
2211 $ipt_str =~ s/\s*\!\s*"//;
2212 $pattern{'negative_match'} = 1;
2215 $ipt_str =~ s/`/|60|/g; ### ` -> |60|
2216 $ipt_str =~ s/'/|27|/g; ### ' -> |27|
2217 $ipt_str =~ s/\x2d/|2d|/g; ### - -> |2d|
2218 $ipt_str =~ s/\x24/|24|/g; ### $ -> |24|
2220 ### convert all escaped chars to their hex equivalents
2221 $ipt_str =~ s/\x5c(.)/sprintf "|%02x|", (ord($1))/eg;
2224 while ($ipt_str =~ m/\|\|/) {
2225 ### consolidate consecutive hex blocks
2226 if ($ipt_str =~ /\|.+?\|\|.+?\|/) {
2227 $ipt_str =~ s/\|(.+?)\|\|(.+?)\|/|$1 $2|/;
2233 ### remove all spaces between hex codes (they simply waste space
2234 ### on the command line, and they aren't part of the string to
2235 ### search in network traffic anyway).
2236 $ipt_str = &consolidate_hex_spaces($ipt_str);
2238 ### handles length of hex blocks
2239 my $content_len = &get_content_len($ipt_str);
2240 if ($content_len >= $ipt_max_str_len
2241 or $content_len >= $config{'MAX_STRING_LEN'}) {
2242 return 0, "pattern too long: $snort_str", \%pattern;
2245 $pattern{'ipt_pattern'} = $ipt_str;
2246 $pattern{'length'} = $content_len;
2248 return 1, $log_str, \%pattern;
2251 sub define_offsets() {
2252 my ($patterns_ar, $avg_hdr_len, $opt, $val) = @_;
2254 my $current_index = $#$patterns_ar;
2256 ### the option should not be defined - if so, there is a duplicate
2257 ### Snort keyword in the signature
2258 if (defined $patterns_ar->[$current_index]->{$opt}) {
2259 return 0, "duplicate keyword: $opt";
2262 ### store the value that was in the original Snort rule
2263 $patterns_ar->[$current_index]->{"${opt}_snort_orig"} = $val;
2265 ### store the value and account for average header length if
2267 if ($ipt_has_string_payload_offset_opt) {
2268 $patterns_ar->[$current_index]->{$opt} = $val;
2270 $patterns_ar->[$current_index]->{$opt}
2271 = $val + $avg_hdr_len + $MAC_HDR_LEN;
2277 sub update_offsets_relative_matches() {
2278 my $patterns_ar = shift;
2280 for (my $i=0; $i <= $#$patterns_ar; $i++) {
2282 my $pat_hr = $patterns_ar->[$i];
2283 my $snort_offset = -1;
2284 my $snort_depth = -1;
2285 my $snort_distance = -1;
2286 my $snort_within = -1;
2288 $snort_offset = $pat_hr->{'offset_snort_orig'}
2289 if defined $pat_hr->{'offset_snort_orig'};
2290 $snort_depth = $pat_hr->{'depth_snort_orig'}
2291 if defined $pat_hr->{'depth_snort_orig'};
2292 $snort_distance = $pat_hr->{'distance_snort_orig'}
2293 if defined $pat_hr->{'distance_snort_orig'};
2294 $snort_within = $pat_hr->{'within_snort_orig'}
2295 if defined $pat_hr->{'within_snort_orig'};
2297 if ($snort_depth > -1) {
2298 ### if there is also an offset, then the depth begins after
2299 ### the offset starts
2300 if ($snort_offset > -1) {
2301 $pat_hr->{'depth'} += $snort_offset;
2302 } elsif ($snort_distance > -1) {
2303 $pat_hr->{'depth'} += $snort_distance;
2305 if ($snort_depth < $pat_hr->{'length'}) {
2306 return 0, "depth: $snort_depth less than " .
2307 "pattern length: $pat_hr->{'length'}";
2311 if ($snort_distance > -1) {
2312 ### see if we need to increase the distance based
2313 ### on the length of the previous pattern match and offset
2315 my $prev_pat_hr = $patterns_ar->[$i-1];
2316 $pat_hr->{'distance'} += $prev_pat_hr->{'length'};
2317 if (defined $prev_pat_hr->{'offset_snort_orig'}) {
2318 $pat_hr->{'distance'} += $prev_pat_hr->{'offset_snort_orig'};
2323 if ($snort_within > -1) {
2324 if ($snort_offset > -1) {
2325 $pat_hr->{'within'} += $snort_offset;
2326 } elsif ($snort_distance > -1) {
2327 $pat_hr->{'within'} += $snort_distance;
2330 my $prev_pat_hr = $patterns_ar->[$i-1];
2331 $pat_hr->{'within'} += $prev_pat_hr->{'length'};
2332 if (defined $prev_pat_hr->{'offset_snort_orig'}) {
2333 $pat_hr->{'within'} += $prev_pat_hr->{'offset_snort_orig'};
2341 sub get_avg_hdr_len() {
2344 my $avg_hdr_len = $config{'AVG_IP_HEADER_LEN'};
2345 if (defined $hdr_hr->{'proto'}) {
2346 if ($hdr_hr->{'proto'} =~ /udp/i) {
2347 $avg_hdr_len += $UDP_HDR_LEN; ### udp header is 8 bytes
2348 } elsif ($hdr_hr->{'proto'} =~ /icmp/i) {
2349 $avg_hdr_len += $ICMP_HDR_LEN; ### icmp header is 8 bytes
2352 $avg_hdr_len += $config{'AVG_TCP_HEADER_LEN'};
2355 ### don't know what the average transport layer (if there
2356 ### is one) length will be; add 10 bytes just to be safe
2359 return $avg_hdr_len;
2362 ### handles length of hex blocks
2363 sub get_content_len() {
2367 my @chars = split //, $str;
2368 for (my $i=0; $i<=$#chars; $i++) {
2369 if ($chars[$i] eq '|') {
2370 $hex_mode == 0 ? ($hex_mode = 1) : ($hex_mode = 0);
2374 next if $chars[$i] eq ' ';
2384 sub consolidate_hex_spaces() {
2388 my @chars = split //, $str;
2389 for my $char (@chars) {
2391 $hex_mode == 0 ? ($hex_mode = 1) : ($hex_mode = 0);
2394 next if $char eq ' ';
2401 sub translate_perl_trigger() {
2403 my $trigger_str = '';
2405 my $append_hex_str = '';
2406 my $append_non_hex_str = '';
2408 my @chars = split //, $str;
2410 for my $char (@chars) {
2413 if ($append_hex_str) {
2414 while ($append_hex_str =~ /(.{2})/g) {
2415 $trigger_str .= "\\x$1";
2417 $append_hex_str = '';
2421 if ($append_non_hex_str) {
2422 $trigger_str .= qq|$append_non_hex_str|;
2423 $append_non_hex_str = '';
2431 $append_hex_str .= $char;
2433 $append_non_hex_str .= $char;
2437 if ($append_hex_str) {
2438 while ($append_hex_str =~ /(.{2})/g) {
2439 $trigger_str .= "\\x$1";
2442 $trigger_str .= qq|$append_non_hex_str| if $append_non_hex_str;
2444 return $trigger_str;
2447 sub ipt_add_rule() {
2448 my ($hdr_hr, $opts_hr, $orig_snort_rule, $rule_base,
2449 $target_str, $comment, $add_snort_comment,
2450 $perl_trigger_str, $add_perl_trigger, $chain, $fwsnort_chain) = @_;
2452 my $action_rule = '';
2453 if ($hdr_hr->{'proto'} eq 'tcp') {
2454 if ($hdr_hr->{'action'} eq 'pass') {
2455 $action_rule = "$rule_base -j ACCEPT";
2457 if (defined $opts_hr->{'resp'}
2458 and $opts_hr->{'resp'} =~ /rst/i) {
2459 ### iptables can only send tcp resets to the connection
2460 ### client, so we can't support rst_rcv, but we should
2461 ### try to tear the connection down anyway.
2462 $action_rule = "$rule_base -j REJECT " .
2463 "--reject-with tcp-reset";
2464 } elsif ($ipt_drop) {
2465 $action_rule = "$rule_base -j DROP";
2466 } elsif ($ipt_reject) {
2467 $action_rule = "$rule_base -j REJECT " .
2468 "--reject-with tcp-reset";
2471 } elsif ($hdr_hr->{'proto'} eq 'udp') {
2472 if ($hdr_hr->{'action'} eq 'pass') {
2473 $action_rule = "$rule_base -j ACCEPT";
2475 if (defined $opts_hr->{'resp'}
2476 and $opts_hr->{'resp'} =~ /icmp/i) {
2477 if ($opts_hr->{'resp'} =~ /all/i) { ### icmp_all
2478 $action_rule = "$rule_base -j REJECT " .
2479 "--reject-with icmp-port-unreachable";
2480 } elsif ($opts_hr->{'resp'} =~ /net/i) { ### icmp_net
2481 $action_rule = "$rule_base -j REJECT " .
2482 "--reject-with icmp-net-unreachable";
2483 } elsif ($opts_hr->{'resp'} =~ /host/i) { ### icmp_host
2484 $action_rule = "$rule_base -j REJECT " .
2485 "--reject-with icmp-host-unreachable";
2486 } elsif ($opts_hr->{'resp'} =~ /port/i) { ### icmp_port
2487 $action_rule = "$rule_base -j REJECT " .
2488 "--reject-with icmp-port-unreachable";
2490 } elsif ($ipt_drop) {
2491 $action_rule = "$rule_base -j DROP";
2492 } elsif ($ipt_reject) {
2493 $action_rule = "$rule_base -j REJECT " .
2494 "--reject-with icmp-port-unreachable";
2498 if ($hdr_hr->{'action'} eq 'pass') {
2499 $action_rule = "$rule_base -j ACCEPT";
2501 $action_rule = "$rule_base -j DROP";
2504 my $ipt_rule = $rule_base . $target_str;
2506 push @ipt_script_lines, "\n### $orig_snort_rule" if $add_snort_comment;
2507 if ($include_perl_triggers and $add_perl_trigger) {
2508 push @ipt_script_lines, "### $perl_trigger_str";
2511 push @ipt_script_lines, qq|\$ECHO "[+] rule $ipt_rule_ctr"|;
2514 ### save format handling
2515 my $save_format_ipt_rule = $ipt_rule . " \n";
2516 my $save_format_action_rule = $action_rule . " \n";
2518 $save_format_ipt_rule =~ s|\$${ipt_var_str}\s+\-A|-A|;
2519 $save_format_action_rule =~ s|\$${ipt_var_str}\s+\-A|-A|;
2521 if ($hdr_hr->{'action'} ne 'pass') {
2522 if ($queue_mode or $nfqueue_mode) {
2524 push @ipt_script_lines, $ipt_rule;
2525 push @{$save_format_rules{$fwsnort_chain}}, $save_format_ipt_rule;
2529 push @ipt_script_lines, $ipt_rule unless $no_ipt_log;
2530 push @{$save_format_rules{$fwsnort_chain}}, $save_format_ipt_rule
2535 if ($action_rule and ($ipt_drop or $ipt_reject or
2536 $hdr_hr->{'action'} eq 'pass' or defined $opts_hr->{'resp'})) {
2538 push @ipt_script_lines, $action_rule;
2539 push @{$save_format_rules{$fwsnort_chain}}, $save_format_action_rule;
2546 sub save_format_append_rules() {
2548 for my $chain (sort keys %ipt_save_existing_chains) {
2550 next unless &is_fwsnort_chain($chain, $MATCH_EQUIV);
2552 ### make sure that whitelist/blacklist and established jump rules
2553 ### are added at the beginning of each chain in save format
2554 &save_format_add_prereqs($chain);
2556 for my $rule (@{$save_format_rules{$chain}}) {
2558 push @fwsnort_save_lines, $rule;
2562 ### now append any last lines from the iptables-save output that
2563 ### had nothing to do with fwsnort (other custom chains, etc.)
2564 for (my $i = $ipt_save_index; $i <= $#ipt_save_lines; $i++) {
2565 next if &is_fwsnort_chain($ipt_save_lines[$i], $MATCH_SUBSTR);
2566 push @fwsnort_save_lines, $ipt_save_lines[$i];
2572 sub save_format_add_prereqs() {
2575 return if defined $save_format_prereqs{$chain};
2578 if (defined $save_format_whitelist{$chain}) {
2579 for my $whitelist_rule (@{$save_format_whitelist{$chain}}) {
2580 push @fwsnort_save_lines, "$whitelist_rule \n";
2585 if (defined $save_format_blacklist{$chain}) {
2586 for my $blacklist_rule (@{$save_format_blacklist{$chain}}) {
2587 push @fwsnort_save_lines, "$blacklist_rule \n";
2591 ### add jump rules into the connection tracking fwsnort chains
2592 if (defined $save_format_conntrack_jumps{$chain}) {
2593 for my $jump_rule (@{$save_format_conntrack_jumps{$chain}}) {
2594 push @fwsnort_save_lines, "$jump_rule \n";
2598 $save_format_prereqs{$chain} = '';
2603 sub ipt_whitelist() {
2604 my @whitelist_addrs = ();
2606 for my $whitelist_line (@{$config{'WHITELIST'}}) {
2607 for my $addr (@{&expand_addresses($whitelist_line)}) {
2608 push @whitelist_addrs, $addr;
2612 return unless $#whitelist_addrs >= 0;
2614 push @ipt_script_lines, "\n###\n############ Add IP/network " .
2615 "WHITELIST rules. ############\n###";
2617 for my $addr (@whitelist_addrs) {
2618 for my $chain (keys %process_chains) {
2619 next unless $process_chains{$chain};
2621 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
2622 my $rule_str = qq|-A $config{"FWSNORT_$chain"} | .
2623 "-s $addr -j RETURN";
2624 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2626 push @{$save_format_whitelist{$config{"FWSNORT_$chain"}}},
2629 if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') {
2630 my $rule_str = qq|-A $config{"FWSNORT_$chain"} | .
2631 "-d $addr -j RETURN";
2632 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2634 push @{$save_format_whitelist{$config{"FWSNORT_$chain"}}},
2642 sub ipt_blacklist() {
2644 my $printed_intro = 0;
2646 for my $blacklist_line (@{$config{'BLACKLIST'}}) {
2648 my @blacklist_addrs = ();
2649 my $target = 'DROP'; ### default
2651 if ($blacklist_line =~ /\s+REJECT/) {
2655 for my $addr (@{&expand_addresses($blacklist_line)}) {
2656 push @blacklist_addrs, $addr;
2659 return unless $#blacklist_addrs >= 0;
2661 unless ($printed_intro) {
2662 push @ipt_script_lines, "\n###\n############ Add IP/network " .
2663 "BLACKLIST rules. ############\n###";
2667 for my $addr (@blacklist_addrs) {
2668 for my $chain (keys %process_chains) {
2669 next unless $process_chains{$chain};
2671 if ($target eq 'DROP') {
2672 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
2674 my $rule_str = qq|-A $config{"FWSNORT_$chain"} | .
2677 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2678 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2682 if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') {
2684 my $rule_str = qq|-A $config{"FWSNORT_$chain"} | .
2686 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2687 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2691 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
2692 my $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | .
2693 "-p tcp -j REJECT --reject-with tcp-reset";
2695 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2696 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2699 $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | .
2700 "-p udp -j REJECT --reject-with icmp-port-unreachable";
2702 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2703 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2706 $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | .
2707 "-p icmp -j REJECT --reject-with icmp-host-unreachable";
2709 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2710 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2713 if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') {
2714 my $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | .
2715 "-p tcp -j REJECT --reject-with tcp-reset";
2717 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2718 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2721 $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | .
2722 "-p udp -j REJECT --reject-with icmp-port-unreachable";
2724 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2725 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2728 $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | .
2729 "-p icmp -j REJECT --reject-with icmp-host-unreachable";
2731 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2732 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2742 sub ipt_add_chains() {
2745 my %ipt_save_built_in_chains = ();
2746 my $look_for_chains = 0;
2747 for (@ipt_save_lines) {
2748 unless ($look_for_chains) {
2749 push @fwsnort_save_lines, $_;
2752 $look_for_chains = 1;
2753 } elsif ($look_for_chains and $_ =~ /^:(\S+)/) {
2755 if ($chain eq 'INPUT'
2756 or $chain eq 'OUTPUT'
2757 or $chain eq 'FORWARD') {
2758 $ipt_save_built_in_chains{$chain} = $_;
2760 ### don't preserve any old fwsnort chains, but preserve
2761 ### any other existing custom chains
2762 unless (&is_fwsnort_chain($chain, $MATCH_EQUIV)) {
2763 $ipt_save_existing_chains{$chain} = $_;
2766 } elsif ($look_for_chains and $_ !~ /^:(\S+)/) {
2772 ### save format - add the built-in chains first
2773 for my $chain (qw(INPUT FORWARD OUTPUT)) {
2774 ### should always be defined unless we're not running as root
2775 next unless defined $ipt_save_built_in_chains{$chain};
2776 push @fwsnort_save_lines, $ipt_save_built_in_chains{$chain};
2779 ### add the fwsnort chains
2780 push @ipt_script_lines, "\n###\n############ Create " .
2781 "fwsnort $ipt_str chains. ############\n###";
2783 for my $built_in_chain (qw(INPUT FORWARD OUTPUT)) {
2785 ### see if any of the "FWSNORT_<built-in-chain>" chains need to be
2787 next unless $process_chains{$built_in_chain};
2789 for my $chain ($config{"FWSNORT_$built_in_chain"},
2790 $config{"FWSNORT_${built_in_chain}_ESTAB"}) {
2791 if ($no_ipt_conntrack) {
2793 $config{"FWSNORT_${built_in_chain}_ESTAB"};
2795 push @ipt_script_lines,
2796 "\$${ipt_var_str} -N $chain 2> /dev/null",
2797 "\$${ipt_var_str} -F $chain\n";
2800 $ipt_save_existing_chains{$chain} = ":$chain - [0:0]\n";
2804 ### save format - add the custom chains
2805 for my $chain (sort keys %ipt_save_existing_chains) {
2806 push @fwsnort_save_lines, $ipt_save_existing_chains{$chain};
2809 ### save format - add in the jump rules from the
2810 ### built-in chains here
2811 &save_format_add_jump_rules();
2813 ### add in any rules from custom chains that alphabetically come
2814 ### before the first fwsnort chain
2815 &save_format_add_early_custom_chains();
2820 sub save_format_add_jump_rules() {
2822 ### add the jump rule for each built-in chain
2823 for my $built_in_chain (qw(INPUT FORWARD OUTPUT)) {
2825 next unless defined $process_chains{$built_in_chain}
2826 and $process_chains{$built_in_chain};
2828 ### get the current $chain rules (if any), and then see where to add
2829 ### the fwsnort jump rule
2831 my @existing_chain_rules = ();
2833 for (my $i = $ipt_save_index; $i < $#ipt_save_lines; $i++) {
2835 ### delete any existing fwsnort jump rules
2836 if ($ipt_save_lines[$i] =~ /^\-A\s$built_in_chain\s/) {
2837 if ($ipt_save_lines[$i] !~ /\-j\sFWSNORT_/) {
2838 push @existing_chain_rules, $ipt_save_lines[$i];
2847 my $added_jump_rule = 0;
2848 for my $existing_rule (@existing_chain_rules) {
2849 if ($ctr == $config{"FWSNORT_${built_in_chain}_JUMP"}) {
2850 &save_format_add_chain_jump_rule($built_in_chain);
2851 $added_jump_rule = 1;
2854 push @fwsnort_save_lines, $existing_rule;
2858 ### the chain may have been empty
2859 unless ($added_jump_rule) {
2860 &save_format_add_chain_jump_rule($built_in_chain);
2867 sub save_format_add_chain_jump_rule() {
2868 my $built_in_chain = shift;
2869 my $fwsnort_chain = "FWSNORT_${built_in_chain}";
2870 if (%restrict_interfaces) {
2871 for my $intf (keys %restrict_interfaces) {
2872 if ($built_in_chain eq 'INPUT' or $built_in_chain eq 'FORWARD') {
2873 push @fwsnort_save_lines, "-A $built_in_chain -i $intf " .
2874 "-j $fwsnort_chain \n";
2875 } elsif ($built_in_chain eq 'OUTPUT') {
2876 push @fwsnort_save_lines, "-A $built_in_chain -o $intf " .
2877 "-j $fwsnort_chain \n";
2881 if ($no_exclude_loopback) {
2882 push @fwsnort_save_lines, "-A $built_in_chain " .
2883 "-j $fwsnort_chain \n";
2885 if ($built_in_chain eq 'INPUT' or $built_in_chain eq 'FORWARD') {
2886 push @fwsnort_save_lines, "-A $built_in_chain ! -i lo " .
2887 "-j $fwsnort_chain \n";
2888 } elsif ($built_in_chain eq 'OUTPUT') {
2889 push @fwsnort_save_lines, "-A $built_in_chain ! -o lo " .
2890 "-j $fwsnort_chain \n";
2898 sub save_format_add_early_custom_chains() {
2900 for my $chain (sort keys %ipt_save_existing_chains) {
2901 last if &is_fwsnort_chain($chain, $MATCH_EQUIV);
2903 for (my $i = $ipt_save_index; $i <= $#ipt_save_lines; $i++) {
2904 last if &is_fwsnort_chain($ipt_save_lines[$i], $MATCH_SUBSTR);
2905 push @fwsnort_save_lines, $ipt_save_lines[$i];
2913 sub is_fwsnort_chain() {
2914 my ($str, $match_style) = @_;
2917 for my $fwsnort_chain ($config{'FWSNORT_INPUT'},
2918 $config{'FWSNORT_INPUT_ESTAB'},
2919 $config{'FWSNORT_FORWARD'},
2920 $config{'FWSNORT_FORWARD_ESTAB'},
2921 $config{'FWSNORT_OUTPUT'},
2922 $config{'FWSNORT_OUTPUT_ESTAB'}) {
2924 if ($match_style eq $MATCH_SUBSTR) {
2925 if ($str =~ /$fwsnort_chain/) {
2929 } elsif ($match_style eq $MATCH_EQUIV) {
2930 if ($str eq $fwsnort_chain) {
2940 sub ipt_add_conntrack_jumps() {
2941 ### jump ESTABLISHED tcp traffic to each of the _ESTAB
2943 push @ipt_script_lines, "\n###\n############ Inspect $conntrack_state " .
2944 "tcp connections. ############\n###";
2946 for my $chain (keys %process_chains) {
2947 next unless $process_chains{$chain};
2951 if ($have_conntrack) {
2952 $rule_str = qq|-A $config{"FWSNORT_$chain"} -p tcp -m conntrack | .
2953 qq|--ctstate $conntrack_state -j | .
2954 qq|$config{"FWSNORT_${chain}_ESTAB"}|;
2955 } elsif ($have_state) {
2956 $rule_str = qq|-A $config{"FWSNORT_$chain"} -p tcp -m state | .
2957 qq|--state $conntrack_state -j | .
2958 qq|$config{"FWSNORT_${chain}_ESTAB"}|;
2961 push @ipt_script_lines, qq|\$${ipt_var_str} $rule_str|;
2962 push @{$save_format_conntrack_jumps{$config{"FWSNORT_$chain"}}},
2968 sub ipt_jump_chain() {
2969 push @ipt_script_lines, "\n###\n############ Jump traffic " .
2970 "to the fwsnort chains. ############\n###";
2971 if (%restrict_interfaces) {
2972 for my $intf (keys %restrict_interfaces) {
2973 for my $chain (keys %process_chains) {
2974 next unless $process_chains{$chain};
2976 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
2977 ### delete any existing jump rule so that fwsnort.sh can
2978 ### be executed many times in a row without adding several
2980 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
2981 qq|-i $intf -j $config{"FWSNORT_$chain"}| .
2984 ### now add the jump rule
2985 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
2986 qq|$config{"FWSNORT_${chain}_JUMP"} -i | .
2987 qq|$intf -j $config{"FWSNORT_$chain"}|;
2988 } elsif ($chain eq 'OUTPUT') {
2990 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
2991 qq|-o $intf -j $config{'FWSNORT_OUTPUT'}| .
2994 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
2995 qq|$config{'FWSNORT_OUTPUT_JUMP'} -o | .
2996 qq|$intf -j $config{'FWSNORT_OUTPUT'}|;
3001 for my $chain (keys %process_chains) {
3002 next unless $process_chains{$chain};
3004 if ($no_exclude_loopback) {
3006 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
3007 qq|-j $config{"FWSNORT_$chain"}| .
3010 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
3011 qq|$config{"FWSNORT_${chain}_JUMP"} | .
3012 qq|-j $config{"FWSNORT_$chain"}|;
3014 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
3016 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
3017 qq|! -i lo -j $config{"FWSNORT_$chain"}| .
3020 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
3021 qq|$config{"FWSNORT_${chain}_JUMP"} ! -i lo | .
3022 qq|-j $config{"FWSNORT_$chain"}|;
3024 } elsif ($chain eq 'OUTPUT') {
3026 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
3027 qq|! -o lo -j $config{"FWSNORT_$chain"}| .
3030 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
3031 qq|$config{"FWSNORT_${chain}_JUMP"} ! -o lo | .
3032 qq|-j $config{"FWSNORT_$chain"}|;
3042 "#!$cmds{'sh'}\n#", '#'x76,
3043 "#\n# File: $config{'FWSNORT_SCRIPT'}",
3044 "#\n# Purpose: This script was auto-" .
3045 "generated by fwsnort, and implements",
3046 "# an $ipt_str ruleset based upon " .
3047 "Snort rules. For more",
3048 "# information see the fwsnort man " .
3049 "page or the documentation",
3051 "http://www.cipherdyne.org/fwsnort/",
3052 "#\n# Generated with: fwsnort @argv_cp",
3053 "# Generated on host: " . hostname(),
3054 "# Time stamp: " . localtime(),
3055 "#\n# Author: Michael Rash <mbr\@cipherdyne.org>",
3056 "#\n# Version: $version",
3061 push @ipt_script_lines, &hdr_lines();
3062 push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines];
3064 ### add paths to system binaries (iptables included)
3065 &ipt_config_section();
3069 sub ipt_config_section() {
3070 ### build the config section of the iptables script
3071 push @ipt_script_lines,
3072 '#==================== config ====================',
3073 "ECHO=$cmds{'echo'}",
3074 "${ipt_var_str}=$ipt_bin",
3075 "#================== end config ==================\n";
3076 push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines];
3082 push @ipt_script_lines, "\n###\n############ ${type}.rules #######" .
3083 "#####\n###", "\$ECHO \"[+] Adding $type rules:\"";
3088 for my $type_hr (\%include_types, \%exclude_types) {
3089 for my $type (keys %$type_hr) {
3091 my @valid_types = ();
3092 for my $dir (split /\,/, $config{'RULES_DIR'}) {
3093 if (-e "$dir/${type}.rules") {
3096 opendir D, $dir or die "[*] Could not open $dir: $!";
3097 for my $file (readdir D) {
3098 if ($file =~ /(\S+)\.rules/) {
3099 push @valid_types, $1;
3105 print "[-] \"$type\" is not a valid type.\n",
3106 " Choose from the following available signature types:\n";
3107 for my $type (sort @valid_types) {
3117 sub import_config() {
3118 open C, "< $fwsnort_conf" or die "[*] Could not open $fwsnort_conf: $!";
3122 for my $line (@lines) {
3125 next if $line =~ /^\s*#/;
3126 next unless $line =~ /\S/;
3127 if ($line =~ /^\s*(\S+)Cmd\s+(\S+);/) { ### e.g. "iptablesCmd"
3129 } elsif ($line =~ /^\s*(\S+)\s+(.*?);/) {
3132 die "[*] $fwsnort_conf: Variable \"$var\" is set to\n",
3133 " _CHANGEME_ at line $l_ctr. Edit $fwsnort_conf.\n"
3134 if $val eq '_CHANGEME_';
3135 if (defined $multi_line_vars{$var}) {
3136 push @{$config{$var}}, $val;
3138 ### may have already been defined in existing snort.conf
3139 ### file if --snort-conf was given.
3140 $config{$var} = $val unless defined $config{$var};
3152 $config{'FWSNORT_INPUT'},
3153 $config{'FWSNORT_INPUT_ESTAB'},
3154 $config{'FWSNORT_OUTPUT'},
3155 $config{'FWSNORT_OUTPUT_ESTAB'},
3156 $config{'FWSNORT_FORWARD'},
3157 $config{'FWSNORT_FORWARD_ESTAB'}
3159 my $cmd = "$ipt_bin -v -n -L $chain";
3160 my $exists = (system "$cmd > /dev/null 2>&1") >> 8;
3162 print "[+] Listing $chain chain...\n";
3166 print "[-] Chain $chain does not exist...\n";
3174 $config{'FWSNORT_INPUT'},
3175 $config{'FWSNORT_INPUT_ESTAB'},
3176 $config{'FWSNORT_OUTPUT'},
3177 $config{'FWSNORT_OUTPUT_ESTAB'},
3178 $config{'FWSNORT_FORWARD'},
3179 $config{'FWSNORT_FORWARD_ESTAB'}
3181 my $exists = (system "$ipt_bin -n -L " .
3182 "$chain > /dev/null 2>&1") >> 8;
3184 print "[+] Flushing $chain chain...\n";
3185 system "$ipt_bin -F $chain";
3186 if ($ipt_del_chains) {
3187 ### must remove any jump rules from the built-in
3189 &del_jump_rule($chain);
3191 print " Deleting $chain chain...\n";
3192 system "$ipt_bin -X $chain";
3195 print "[-] Chain $chain does not exist...\n";
3201 sub cache_ipt_save_policy() {
3203 return unless $is_root;
3205 open IPT, "$save_bin -t filter |" or die "[*] Could not execute $save_bin";
3207 push @ipt_save_lines, $_;
3211 ### also write out the current iptables policy so that we can
3212 ### revert to it if necessary (iptables does a good job of not committing
3213 ### a policy via iptables-save if there is a problem with a rule though).
3214 &archive($config{'IPT_BACKUP_SAVE_FILE'});
3215 open F, "> $config{'IPT_BACKUP_SAVE_FILE'}" or die "[*] Could not " .
3216 "open $config{'IPT_BACKUP_SAVE_FILE'}: $!";
3217 print F for @ipt_save_lines;
3220 ### remove the last two lines (the 'COMMIT' and '# Completed ...' lines
3221 ### so they can be added later).
3222 $ipt_save_completed_line = $ipt_save_lines[$#ipt_save_lines];
3223 pop @ipt_save_lines;
3224 pop @ipt_save_lines;
3229 sub del_jump_rule() {
3232 my $ipt = new IPTables::Parse 'iptables' => $cmds{'iptables'}
3233 or die "[*] Could not acquire IPTables::Parse object: $!";
3235 for my $built_in_chain (qw(INPUT OUTPUT FORWARD)) {
3236 my $rules_ar = $ipt->chain_rules('filter', $built_in_chain, $ipt_file);
3238 for (my $i=0; $i <= $#$rules_ar; $i++) {
3239 my $rule_num = $i+1;
3240 if ($rules_ar->[$i]->{'target'} eq $chain) {
3241 system "$ipt_bin -D $built_in_chain $rule_num";
3250 sub fwsnort_init() {
3252 ### set umask to -rw-------
3255 ### turn off buffering
3258 &set_non_root_values() unless $is_root;
3260 ### read in configuration info from the config file
3263 ### make sure the commands are where the
3264 ### config file says they are
3267 ### make sure all of the required variables are defined
3268 ### in the config file
3271 $non_host = $NON_HOST;
3272 $ipt_bin = $cmds{'iptables'};
3273 $restore_bin = $cmds{'iptables-restore'};
3274 $save_bin = $cmds{'iptables-save'};
3276 if ($enable_ip6tables) {
3277 for my $opt (qw(itype icode ttl tos ipopts)) {
3278 $snort_opts{'unsupported'}{$opt}
3279 = $snort_opts{'filter'}{$opt};
3280 delete $snort_opts{'filter'}{$opt};
3282 $non_host = $NON_IP6_HOST;
3283 $save_str = 'ip6tables-save';
3284 $ipt_str = 'ip6tables';
3285 $ipt_bin = $cmds{'ip6tables'};
3286 $restore_bin = $cmds{'ip6tables-restore'};
3287 $save_bin = $cmds{'ip6tables-save'};
3295 die "[*] You need to be root for --ipt-apply" unless $is_root;
3296 if (-e $config{'FWSNORT_SAVE_EXEC_FILE'}) {
3297 print "[+] Executing $config{'FWSNORT_SAVE_EXEC_FILE'}\n";
3298 system $config{'FWSNORT_SAVE_EXEC_FILE'};
3301 die "[*] $config{'FWSNORT_SAVE_EXEC_FILE'} does not exist.";
3305 if ($enable_ip6tables) {
3306 ### switch to ip6tables
3307 $ipt_var_str = 'IP6TABLES';
3310 $process_chains{'INPUT'} = 0 if $no_ipt_input;
3311 $process_chains{'FORWARD'} = 0 if $no_ipt_forward;
3312 $process_chains{'OUTPUT'} = 0 if $no_ipt_output;
3314 ### import HOME_NET, etc. from existing Snort config file.
3315 &import_snort_conf() if $snort_conf_file;
3318 my @types = split /\,/, $rules_types;
3319 for my $type (@types) {
3320 $include_types{$type} = '';
3323 if ($exclude_types) {
3324 my @types = split /\,/, $exclude_types;
3325 for my $type (@types) {
3326 $exclude_types{$type} = '';
3329 if ($include_sids) {
3330 ### disable iptables policy parsing if we are translating a
3331 ### specific set of Snort sids.
3334 my @sids = split /\,/, $include_sids;
3335 for my $sid (@sids) {
3336 $include_sids{$sid} = '';
3339 if ($exclude_sids) {
3340 my @sids = split /\,/, $exclude_sids;
3341 for my $sid (@sids) {
3342 $exclude_sids{$sid} = '';
3345 if ($ipt_restrict_intf) {
3346 my @interfaces = split /\,/, $ipt_restrict_intf;
3347 for my $intf (@interfaces) {
3348 $restrict_interfaces{$intf} = '';
3353 if ($include_re_caseless) {
3354 $include_re = qr|$include_re|i;
3356 $include_re = qr|$include_re|;
3360 if ($exclude_re_caseless) {
3361 $exclude_re = qr|$exclude_re|i;
3363 $exclude_re = qr|$exclude_re|;
3367 ### flush all fwsnort chains.
3368 &ipt_flush() if $ipt_flush or $ipt_del_chains;
3370 ### list all fwsnort chains.
3371 &ipt_list() if $ipt_list;
3373 ### download latest snort rules from snort.org
3374 &update_rules() if $update_rules;
3376 ### make sure some directories exist, etc.
3379 ### get kernel version (this is mainly used to know whether
3380 ### the "--algo bm" argument is required for the string match
3381 ### extension in the 2.6.14 (and later) kernels. Also, the
3382 ### string match extension as of 2.6.14 supports the Snort
3383 ### offset and depth keywords via --from and --to
3386 ### may have been specified on the command line
3387 $home_net = $config{'HOME_NET'} unless $home_net;
3388 $ext_net = $config{'EXTERNAL_NET'} unless $ext_net;
3390 &get_local_addrs() unless $no_addr_check;
3393 ### make the snort options parser very strict
3394 for my $opt (qw(uricontent pcre
3395 distance within http_uri http_method urilen)) {
3396 $snort_opts{'unsupported'}{$opt}
3397 = $snort_opts{'filter'}{$opt};
3398 delete $snort_opts{'filter'}{$opt};
3400 my @ignore = (qw(nocase));
3402 if ($kernel_ver eq '2.4') {
3403 push @ignore, 'offset', 'depth';
3405 for my $opt (@ignore) {
3406 next unless defined $snort_opts{'ignore'}{$opt};
3407 $snort_opts{'unsupported'}{$opt}
3408 = $snort_opts{'ignore'}{$opt};
3409 delete $snort_opts{'ignore'}{$opt};
3413 ### skip trying to translate basic PCRE's
3414 $snort_opts{'unsupported'}{'pcre'}
3415 = $snort_opts{'filter'}{'pcre'};
3416 delete $snort_opts{'filter'}{'pcre'};
3419 if ($no_fast_pattern_order) {
3420 $snort_opts{'ignore'}{'fast_pattern'}
3421 = $snort_opts{'filter'}{'fast_pattern'}{'regex'};
3422 delete $snort_opts{'filter'}{'fast_pattern'};
3427 sub get_kernel_ver() {
3428 die "[*] uname command: $cmds{'uname'} is not executable."
3429 unless -x $cmds{'uname'};
3430 open U, "$cmds{'uname'} -a |" or die "[*] Could not run ",
3431 "$cmds{'uname'} -a";
3434 ### Linux orthanc 2.6.12.5 #2 Tue Sep 27 22:43:02 EDT 2005 i686 \
3435 ### Pentium III (Coppermine) GenuineIntel GNU/Linux
3436 if ($out =~ /\s2\.6/) {
3437 $kernel_ver = '2.6';
3442 sub handle_cmd_line() {
3444 ### make Getopts case sensitive
3445 Getopt::Long::Configure('no_ignore_case');
3447 die "[*] Use --help for usage information.\n" unless (GetOptions(
3448 'ipt-apply' => \$ipt_exec, # Apply the generated ruleset.
3449 'ipt-drop' => \$ipt_drop, # Add iptables DROP rules.
3450 'ipt-reject' => \$ipt_reject, # Add iptables REJECT rules.
3451 'ipt-script=s' => \$ipt_script, # Manually specify the path to the
3452 # generated iptables script.
3453 'ipt-log-tcp-seq' => \$ipt_log_tcp_seq, # Log TCP seq/ack values.
3454 'ipt-flush' => \$ipt_flush, # Flush any existing fwsnort chains.
3455 'ipt-check-capabilities' =>\$ipt_check_capabilities, # Check capabilities
3457 'Flush' => \$ipt_flush, # Synonym for --ipt-flush
3458 'ipt-list' => \$ipt_list, # List any existing fwsnort chains.
3459 'List' => \$ipt_list, # Synonym for --ipt-list
3460 'ipt-del' => \$ipt_del_chains, # Delete fwsnort chains.
3461 'ip6tables' => \$enable_ip6tables, # Turn on ip6tables mode.
3462 '6' => \$enable_ip6tables, # Synonym for --ip6tables.
3463 'X' => \$ipt_del_chains, # Synonym for --ipt-del.
3464 'ipt-file=s' => \$ipt_file, # Read iptables policy from a file.
3465 'Home-net=s' => \$home_net, # Manually specify home network.
3466 'External-net=s' => \$ext_net, # Manually specify external network.
3467 'snort-sid=s' => \$include_sids, # Parse only these particular snort rules.
3468 'snort-sids=s' => \$include_sids, # Synonum for --snort-sid
3469 'string-match-alg=s' => \$string_match_alg,
3470 'exclude-sid=s' => \$exclude_sids, # Exclude these particular snort rules.
3471 'snort-conf=s' => \$snort_conf_file, # Get HOME_NET, etc. vars from
3472 # existing Snort config file.
3473 'include-perl-triggers' => \$include_perl_triggers, # perl commands to
3474 # trigger signature matches.
3475 'include-type=s' => \$rules_types, # Process only this type of snort rule
3477 'exclude-type=s' => \$exclude_types,# Exclude specified types (e.g. "ddos").
3478 'include-regex=s' => \$include_re, # Include only those signatures that
3479 # match the specified regex.
3480 'include-re-caseless' => \$include_re_caseless, # make include regex case
3482 'exclude-regex=s' => \$exclude_re, # Exclude those signatures that
3483 # match the specified regex.
3484 'exclude-re-caseless' => \$exclude_re_caseless, # make exclude regex case
3486 'snort-rdir=s' => \$rules_dir, # Manually specify the snort rules
3488 'snort-rfile=s' => \$rules_file, # Translate a single rules file.
3489 'no-pcre' => \$no_pcre, # Make no attempt to translate PCRE's.
3490 'no-addresses' => \$no_addr_check, # Don't check local ifconfig output.
3491 'no-ipt-sync' => \$ignore_opt, # Do not sync with the iptables policy.
3492 'ipt-sync' => \$ipt_sync, # Sync fwsnort rules with the iptables
3494 'no-ipt-log' => \$no_ipt_log, # Do not generate iptables logging rules.
3495 'no-ipt-test' => \$no_ipt_test, # Don't perform any checks for
3496 # iptables capabilities.
3497 'no-ipt-jumps' => \$no_ipt_jumps, # Don't jump packets from the INPUT or
3499 'no-ipt-conntrack' => \$no_ipt_conntrack, # Don't use iptables connection
3500 # tracking (falls back to ACK flag test).
3501 'Conntrack-state=s' => \$conntrack_state, ### Specify conntrack state for
3502 ### 'flow' keyword emulation
3503 ### (default is ESTABLISHED).
3504 'no-ipt-INPUT' => \$no_ipt_input, # Disable fwsnort rules processed via
3506 'no-ipt-OUTPUT' => \$no_ipt_output, # Disable fwsnort rules processed via
3508 'no-ipt-FORWARD' => \$no_ipt_forward, # Disable fwsnort rules processed via
3509 # the FORWARD chain.
3510 'no-ipt-comments' => \$no_ipt_comments, # Don't include msg fields
3511 # with the comment match
3512 'no-ipt-rule-nums' => \$no_ipt_rule_nums, # Exclude rule numbers from
3514 'no-exclude-lo' => \$no_exclude_loopback, # include loopback interface
3515 'no-log-ip-opts' => \$no_ipt_log_ip_opts, # Don't log IP options
3516 'no-log-tcp-opts' => \$no_ipt_log_tcp_opts, # Don't log TCP options
3517 'no-fast-pattern-order' => \$no_fast_pattern_order, ### Don't alter
3518 # pattern match ordering based on
3519 # pattern length, and ignore the
3520 # explicit 'fast_pattern' keyword
3521 'restrict-intf=s' => \$ipt_restrict_intf, # Restrict iptables rules to an
3522 # individual interface (supports a
3523 # comma separate list).
3524 'update-rules' => \$update_rules, # Download latest snort rules.
3525 'rules-url=s' => \$rules_url, # Specify rules URL.
3526 'add-deleted' => \$add_deleted, # Add deleted rules.
3527 'strict' => \$strict, # Strict mode.
3528 'debug' => \$debug, # Debug mode.
3529 'dumper' => \$dumper, # Dumper mode for IPTables::Parse
3531 'Dump-conf' => \$dump_conf, # Display config variables
3532 'Dump-ipt' => \$dump_ipt, # Dump iptables rules on STDOUT.
3533 'Dump-snort' => \$dump_snort, # Dump snort rules on STDOUT.
3534 'config=s' => \$fwsnort_conf, # Manually specify the config file
3535 'Ulog' => \$ulog_mode, # Force ULOG mode.
3536 'ulog-nlgroup=i' => \$ulog_nlgroup, # Specify the ulogd nl group.
3537 'QUEUE' => \$queue_mode, # Specify QUEUE mode; this pulls out
3538 # all kernel-matchable features from
3539 # original Snort rules and creates a
3540 # a modified rule set based on this.
3541 'NFQUEUE' => \$nfqueue_mode, # Same as QUEUE mode, except use the
3542 # updated NFQUEUE target.
3543 'queue-rules-dir=s' => \$queue_rules_dir, # Change the path to the generated
3544 # rules directory in --QUEUE or
3546 'queue-num=i' => \$nfqueue_num, # Specifies the NFQUEUE number.
3547 'queue-pre-match-max=i' => \$queue_pre_match_max, ### max number of patterns
3548 ### to match within the kernel before
3549 ### queuing a packet to userspace
3551 'Home-dir=s' => \$cmdl_homedir,
3552 'Last-cmd' => \$run_last,
3553 'lib-dir=s' => \$lib_dir, # Specify path to lib directory.
3554 'verbose' => \$verbose,
3555 'logfile=s' => \$logfile, # Specify the logfile path.
3556 'stdout' => \$stdout, # Print log messages to stdout.
3557 'Version' => \$print_ver,
3565 &save_args() unless $run_last;
3567 ### Print the version number and exit if -V given on the command line.
3569 print "[+] fwsnort v$version by Michael Rash <mbr\@cipherdyne.org>\n";
3573 if (($queue_mode or $nfqueue_mode) and ($ipt_drop or $ipt_reject)) {
3575 "[*] --NFQUEUE and --QUEUE modes are not compatible with --ipt-drop or\n",
3576 " --ipt-reject; a userland process should set the verdict. If you can\n",
3577 " always use fwsnort with --ipt-drop or --ipt-reject and add an NFQUEUE\n",
3578 " or QUEUE rule manually to a built-in chain. This allows the fwsnort\n",
3579 " policy to DROP or REJECT packets that match signatures before they are\n",
3580 " communicated to userland (hence speeding up Snort_inline).\n";
3583 if ($nfqueue_num != 0) {
3584 unless ($nfqueue_num > 0 and $nfqueue_num < 65536) {
3585 die "[*] --queue-num must be between 0 and 65535 (inclusive)";
3587 unless ($nfqueue_mode) {
3588 die "[*] Must also specifiy --NFQUEUE mode if using --queue-num";
3592 if ($no_ipt_log and not ($ipt_drop or $ipt_reject)) {
3593 die "[*] --ipt-no-log option can only be used ",
3594 "with --ipt-drop or --ipt-reject";
3597 if ($ipt_drop and $ipt_reject) {
3598 die "[*] Cannot specify both --ipt-drop and --ipt-reject";
3604 sub import_snort_conf() {
3605 unless (-e $snort_conf_file) {
3606 die "[*] Snort config file $snort_conf_file does not exist.";
3608 open F, "< $snort_conf_file" or die "[*] Could not open Snort ",
3609 "config $snort_conf_file: $!";
3612 for my $line (@lines) {
3614 next if $line =~ /^\s*#/;
3615 if ($line =~ /^\s*var\s+(\w+)\s+(.*)\s*/) {
3624 my $has_sub_var = 1;
3625 my $resolve_ctr = 0;
3627 while ($has_sub_var) {
3630 if ($resolve_ctr >= 20) {
3631 die "[*] Exceeded maximum variable resolution counter.";
3633 for my $hr (\%config, \%cmds) {
3634 for my $var (keys %$hr) {
3635 my $val = $hr->{$var};
3636 if ($val =~ m|\$(\w+)|) {
3638 die "[*] sub-ver $sub_var not allowed within same ",
3639 "variable $var" if $sub_var eq $var;
3640 if (defined $config{$sub_var}) {
3641 $val =~ s|\$$sub_var|$config{$sub_var}|;
3644 die "[*] sub-var \"$sub_var\" not defined in ",
3645 "config for var: $var.";
3655 sub required_vars() {
3656 my @required_vars = (qw(
3657 HOME_NET EXTERNAL_NET HTTP_SERVERS SMTP_SERVERS DNS_SERVERS
3658 SQL_SERVERS TELNET_SERVERS AIM_SERVERS HTTP_PORTS SHELLCODE_PORTS
3659 SSH_PORTS ORACLE_PORTS WHITELIST BLACKLIST AVG_IP_HEADER_LEN
3660 AVG_TCP_HEADER_LEN MAX_FRAME_LEN FWSNORT_INPUT FWSNORT_INPUT_ESTAB
3661 FWSNORT_OUTPUT FWSNORT_OUTPUT_ESTAB FWSNORT_FORWARD
3662 FWSNORT_FORWARD_ESTAB FWSNORT_INPUT_JUMP FWSNORT_OUTPUT_JUMP
3663 FWSNORT_FORWARD_JUMP MAX_STRING_LEN CONF_DIR RULES_DIR ARCHIVE_DIR
3664 QUEUE_RULES_DIR LOG_DIR LIBS_DIR CONF_FILE FWSNORT_SCRIPT LOG_FILE
3665 FWSNORT_SAVE_FILE FWSNORT_SAVE_EXEC_FILE IPT_BACKUP_SAVE_FILE
3666 UPDATE_RULES_URL STATE_DIR
3668 for my $var (@required_vars) {
3669 die "[*] Variable $var not defined in $fwsnort_conf. Exiting.\n"
3670 unless defined $config{$var};
3675 sub ipt_capabilities() {
3677 my $test_rule_rv = -1;
3679 ### create test chain
3680 &create_test_chain();
3682 ### test for the LOG target.
3683 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3684 "$non_host -j LOG") == $IPT_SUCCESS) {
3685 print "[+] $ipt_str has 'LOG' target support...\n"
3686 if $verbose or $ipt_check_capabilities;
3688 ### check for the max --log-prefix string length
3689 &ipt_find_max_log_prefix_len();
3692 &delete_test_chain();
3693 die "[*] $ipt_str has not been compiled with logging support. ",
3694 "If you want to\n have fwsnort generate an $ipt_str script ",
3695 " anyway then specify the\n --no-ipt-test option. ",
3700 ### test for the comment match (where Snort msg fields are placed)
3701 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3702 qq|$non_host -m comment --comment "testing the comment match" | .
3703 qq|-j LOG|) == $IPT_SUCCESS) {
3704 print "[+] $ipt_str has 'comment' match support...\n"
3705 if $verbose or $ipt_check_capabilities;
3707 ### now find the maximum comment length that is supported by iptables
3708 &ipt_find_max_comment_len();
3711 unless ($no_ipt_comments) {
3712 print"[-] It looks like the $ipt_str 'comment' match is not ",
3713 "available, disabling.\n";
3714 $no_ipt_comments = 1;
3718 ### test for the ipv4options extension.
3719 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp -m " .
3720 "ipv4options --rr -s $non_host -j LOG") == $IPT_SUCCESS) {
3721 print "[+] $ipt_str has the 'ipv4options' extension...\n"
3722 if $verbose or $ipt_check_capabilities;
3725 &logr("[-] $ipt_str ipv4options extension not available, " .
3726 "disabling ipopts translation.");
3727 ### put ipopts in the unsupported list
3728 if (defined $snort_opts{'filter'}{'ipopts'}) {
3729 $snort_opts{'unsupported'}{'ipopts'} =
3730 $snort_opts{'filter'}{'ipopts'}{'regex'};
3731 delete $snort_opts{'filter'}{'ipopts'};
3733 $snort_opts{'unsupported'}{'ipopts'} = '[\s;]ipopts:\s*(\w+)\s*;';
3735 print "[-] $ipt_str does not have the 'ipv4options' extension, " .
3736 "disabling...\n" if $verbose or $ipt_check_capabilities;
3739 ### test for the ttl match.
3740 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " .
3741 "-s $non_host -m ttl --ttl-eq 1 -j LOG") == $IPT_SUCCESS) {
3742 print "[+] $ipt_str has the 'ttl' match...\n"
3743 if $verbose or $ipt_check_capabilities;
3745 ### put ttl in the unsupported list
3746 &logr("[-] $ipt_str TTL match not available, " .
3747 "disabling ttl translation.");
3748 if (defined $snort_opts{'filter'}{'ttl'}) {
3749 $snort_opts{'unsupported'}{'ttl'} =
3750 $snort_opts{'filter'}{'ttl'}{'regex'};
3751 delete $snort_opts{'filter'}{'ttl'};
3753 $snort_opts{'unsupported'}{'ttl'} = '[\s;]ttl:\s*(.*?)\s*;';
3755 print "[+] $ipt_str does not have the 'ttl' match, " .
3756 "disabling...\n" if $verbose or $ipt_check_capabilities;
3759 ### test for the TOS match.
3760 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " .
3761 "-s $non_host -m tos --tos 8 -j LOG") == $IPT_SUCCESS) {
3762 print "[+] $ipt_str has the 'tos' match...\n"
3763 if $verbose or $ipt_check_capabilities;
3765 ### put tos in the unsupported list
3766 &logr("[-] $ipt_str TOS match not available, " .
3767 "disabling tos translation.");
3768 if (defined $snort_opts{'filter'}{'tos'}) {
3769 $snort_opts{'unsupported'}{'tos'} =
3770 $snort_opts{'filter'}{'tos'}{'regex'};
3771 delete $snort_opts{'filter'}{'tos'};
3773 $snort_opts{'unsupported'}{'tos'} = '[\s;]tos:\s*(.*?)\s*;';
3775 print "[+] $ipt_str does not have the 'tos' match, " .
3776 "disabling...\n" if $verbose or $ipt_check_capabilities;
3779 ### test for the length match.
3780 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " .
3781 "-s $non_host -m length --length 256 -j LOG") == $IPT_SUCCESS) {
3782 print "[+] $ipt_str has the 'length' match...\n"
3783 if $verbose or $ipt_check_capabilities;
3785 ### put length in the unsupported list
3786 &logr("[-] $ipt_str length match not available, " .
3787 "disabling length translation.");
3788 if (defined $snort_opts{'filter'}{'dsize'}) {
3789 $snort_opts{'unsupported'}{'dsize'} =
3790 $snort_opts{'filter'}{'dsize'}{'regex'};
3791 delete $snort_opts{'filter'}{'dsize'};
3793 $snort_opts{'unsupported'}{'dsize'} = '[\s;]dsize:\s*(.*?)\s*;';
3795 print "[+] $ipt_str does not have the 'length' match, " .
3796 "disabling...\n" if $verbose or $ipt_check_capabilities;
3799 ### test for the multiport match.
3800 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p tcp " .
3801 "-s $non_host -m multiport --dports 53,123:500 -j LOG") == $IPT_SUCCESS) {
3802 print "[+] $ipt_str has the 'multiport' match...\n"
3803 if $verbose or $ipt_check_capabilities;
3804 $ipt_have_multiport_match = 1;
3806 ### find the maximum number of supported ports (usually 15)
3807 &ipt_find_max_multiport_supported_ports();
3810 print "[-] $ipt_str does not have the 'multiport' match...\n"
3811 if $verbose or $ipt_check_capabilities;
3812 &logr("[-] $ipt_str multiport match not available");
3815 ### test for string match support.
3816 if ($kernel_ver ne '2.4') {
3818 ### default to include "--algo bm"
3819 $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3820 qq|$non_host -m string --string "test" | .
3821 qq|--algo $string_match_alg -j LOG|);
3823 $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3824 qq|$non_host -m string --string "test" -j LOG|);
3827 if ($test_rule_rv == $IPT_SUCCESS) {
3829 print "[+] $ipt_str has the 'string' match...\n"
3830 if $verbose or $ipt_check_capabilities;
3832 ### now find the maximum string length that is supported by iptables
3833 &ipt_find_max_string_len();
3835 ### test for case insensitive string matching
3836 $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3837 qq|$non_host -m string --string "test" --icase | .
3838 qq|--algo $string_match_alg -j LOG|);
3839 unless ($test_rule_rv == $IPT_SUCCESS) {
3840 $snort_opts{'ignore'}{'nocase'}
3841 = $snort_opts{'filter'}{'nocase'}{'regex'};
3842 delete $snort_opts{'filter'}{'fast_pattern'};
3845 ### test for --replace-string support (only available for 2.4 kernels
3846 ### if the replace-string patch has been applied).
3847 if ($kernel_ver eq '2.4') {
3848 unless (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3849 qq|$non_host -m string --string "test" --replace-string | .
3850 qq|"repl" -j LOG|) == $IPT_SUCCESS) {
3851 if (defined $snort_opts{'filter'}{'replace'}) {
3852 $snort_opts{'unsupported'}{'replace'} =
3853 $snort_opts{'filter'}{'replace'}{'regex'};
3854 delete $snort_opts{'filter'}{'replace'};
3856 $snort_opts{'unsupported'}{'replace'}
3857 = '[\s;]replace:\s*(.*?)\s*;';
3861 $snort_opts{'unsupported'}{'replace'}
3862 = '[\s;]replace:\s*(.*?)\s*;';
3865 &delete_test_chain();
3867 "[*] It does not appear that string match support has been compiled into\n",
3868 " the kernel. Fwsnort will not be of very much use without this.\n",
3869 " ** NOTE: If you want to have fwsnort generate an $ipt_str policy\n",
3870 " anyway, use the --no-ipt-test option. Exiting.\n";
3873 ### test for --hex-string
3874 if ($kernel_ver ne '2.4') {
3875 $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " .
3877 qq{-m string --hex-string "|0a 5d|" --algo $string_match_alg -j LOG});
3879 $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " .
3881 qq{-m string --hex-string "|0a 5d|" -j LOG});
3884 if ($test_rule_rv == $IPT_SUCCESS) {
3885 print "[+] $ipt_str has --hex-string support...\n"
3886 if $verbose or $ipt_check_capabilities;
3888 &delete_test_chain();
3890 "[*] It does not appear that the --hex-string patch has been applied.\n",
3891 " fwsnort will not be of very much use without this. ** NOTE: If you\n",
3892 " want to have fwsnort generate an $ipt_str policy anyway, then\n",
3893 " use the --no-ipt-test option. Exiting.\n";
3896 ### test for the --payload option
3897 if ($kernel_ver ne '2.4'
3898 and &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " .
3899 "-s $non_host -m string --string " .
3900 qq|"test" --algo $string_match_alg --to 50 --payload -j LOG|) == $IPT_SUCCESS) {
3901 $ipt_has_string_payload_offset_opt = 1;
3904 if ($queue_mode or $nfqueue_mode) {
3905 ### test for the QUEUE or NFQUEUE target
3906 if ($nfqueue_mode) {
3907 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3908 "$non_host -p tcp --dport 3001 -j NFQUEUE")
3910 print "[+] $ipt_str has NFQUEUE support....\n"
3911 if $verbose or $ipt_check_capabilities;
3913 die "[*] The NFQUEUE target does not appear to be available ",
3917 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3918 "$non_host -p tcp --dport 3001 -j QUEUE")
3920 print "[+] $ipt_str has QUEUE support....\n"
3921 if $verbose or $ipt_check_capabilities;
3923 die "[*] The QUEUE target does not appear to be available ",
3929 unless ($no_ipt_conntrack) {
3931 ### test for connection tracking support (conntrack
3932 ### match first then state match if not available)
3933 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3934 "$non_host -p tcp --dport 3001 -m conntrack " .
3935 "--ctstate ESTABLISHED -j LOG")
3937 print "[+] $ipt_str has conntrack state tracking support...\n"
3938 if $verbose or $ipt_check_capabilities;
3940 if ($conntrack_state ne 'ESTABLISHED') {
3942 ### check to make sure the specified state is supported
3943 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3944 "$non_host -p tcp --dport 3001 -m conntrack " .
3945 "--ctstate $conntrack_state -j LOG")
3947 die "[*] The connection state $conntrack_state does not ",
3948 "appear to be supported by iptables.\n";
3952 $have_conntrack = 1;
3954 } elsif (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3955 "$non_host -p tcp --dport 3001 -m state " .
3956 "--state ESTABLISHED -j LOG")
3958 print "[+] $ipt_str has state tracking support...\n"
3959 if $verbose or $ipt_check_capabilities;
3961 if ($conntrack_state ne 'ESTABLISHED') {
3963 ### check to make sure the specified state is supported
3964 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3965 "$non_host -p tcp --dport 3001 -m state " .