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';
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 $default_icmp_type = 8; ### echo request
387 my $ipt_print_type = 0;
388 my $ipt_check_capabilities = 0;
389 my $ipt_rule_ctr = 1;
392 my $ipt_del_chains = 0;
398 my $no_ipt_jumps = 0;
399 my $no_ipt_input = 0;
400 my $no_ipt_output = 0;
401 my $no_addr_check = 0;
402 my $no_ipt_forward = 0;
404 my $include_sids = '';
405 my $exclude_sids = '';
407 my $rules_types = '';
408 my $exclude_types = '';
410 my $ulog_nlgroup = 1;
412 my $nfqueue_mode = 0;
417 my $include_re_caseless = 0;
418 my $exclude_re_caseless = 0;
419 my $enable_ip6tables = 0;
420 my $ipt_var_str = 'IPTABLES';
421 my $no_ipt_conntrack = 0;
422 my $conntrack_state = 'ESTABLISHED';
423 my $have_conntrack = 0;
425 my $snort_conf_file = '';
426 my $ipt_restrict_intf = '';
427 my $no_ipt_comments = 0;
428 my $no_ipt_rule_nums = 0;
429 my $no_exclude_loopback = 0;
430 my $no_ipt_log_ip_opts = 0;
431 my $no_ipt_log_tcp_opts = 0;
432 my $ipt_log_tcp_seq = 0;
433 my $include_perl_triggers = 0;
434 my $duplicate_last_build = 0;
435 my $ipt_max_str_len = 1;
436 my $ipt_max_log_prefix_len = 1;
437 my $ipt_max_comment_len = 1;
438 my $no_fast_pattern_order = 0;
439 my $ipt_have_multiport_match = 0;
440 my $ipt_multiport_max = 2;
442 ### to be added to the string match extension
443 my $ipt_has_string_payload_offset_opt = 0;
445 ### default to processing these filter chains
446 my %process_chains = (
451 my $TEST_CHAIN = 'FWS_CAP_TEST';
455 ### save a copy of the command line args
458 ### see if we are running as root
461 ### handle the command line args
464 &run_last_cmdline() if $run_last;
466 ### import config, initialize various things, etc.
469 ### if we are running with $chk_ipt_policy, then cache
470 ### the current iptables policy
471 &cache_ipt_policy() if $ipt_sync;
473 ### truncate old fwsnort log
476 ### check to make sure iptables has various functionality available
477 ### such as the LOG target, --hex-strings, the comment match, etc.
478 &ipt_capabilities() unless $no_ipt_test;
480 ### cache the running iptables policy in iptables-save format
481 &cache_ipt_save_policy();
483 ### print a header at the top of the iptables ruleset
487 ### now that we have the interfaces, add the iptables
488 ### chains to the fwsnort shell script
491 ### add any WHITELIST rules to the main fwsnort chains
492 ### with the RETURN target
495 ### add any BLACKLIST rules to the main fwsnort chains
496 ### with the DROP or REJECT targets
499 ### add jump rules for established tcp connections to
500 ### the fwsnort state tracking chains
501 &ipt_add_conntrack_jumps() unless $no_ipt_conntrack;
503 ### display the config on STDOUT
504 &dump_conf() if $dump_conf;
506 ### make sure <type>.rules file exists if --type was
507 ### specified on the command line
508 &check_type() if $rules_types;
510 &logr("[+] Begin parsing cycle.");
512 ### parse snort rules (signatures)
514 print "[+] Parsing Snort rules files...\n";
517 print "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=",
518 "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n",
519 sprintf("%-30s%-10s%-10s%-10s%-10s", ' Snort Rules File',
520 'Success', 'Fail', 'Ipt_apply', 'Total'), "\n\n";
522 print "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=",
523 "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n",
524 sprintf("%-30s%-10s%-10s%-10s", ' Snort Rules File',
525 'Success', 'Fail', 'Total'), "\n\n";
529 ### main subroutine to parse snort rules and add them to the
530 ### fwsnort.sh script.
531 &parse_snort_rules();
533 ### append all translated rules to the iptables-save formatted array
534 &save_format_append_rules();
536 ### jump packets (as appropriate) from the INPUT and
537 ### FORWARD chains to our fwsnort chains
538 &ipt_jump_chain() unless $no_ipt_jumps;
540 push @ipt_script_lines, qq|\n\$ECHO "[+] Finished."|, '### EOF ###';
541 push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines];
543 print "\n[+] Logfile: $config{'LOG_FILE'}\n";
545 if ($ipt_rule_ctr > 1) {
547 ### write the iptables script out to disk
550 if ($queue_mode or $nfqueue_mode) {
551 print "[+] Snort rule set directory for rules to be queued ",
552 "to userspace:\n $config{'QUEUE_RULES_DIR'}\n";
554 print "[+] $ipt_str script (individual commands): " .
555 "$config{'FWSNORT_SCRIPT'}\n";
558 die "[-] No Snort rules could be translated, exiting\n";
563 &print_final_message();
566 #===================== end main ======================
568 sub parse_snort_rules() {
575 @rfiles = split /\,/, $rules_file;
577 for my $dir (split /\,/, $config{'RULES_DIR'}) {
578 opendir D, $dir or die "[*] Could not opendir $dir";
579 for my $file (readdir D) {
580 push @rfiles, "$dir/$file";
587 my $tot_ipt_apply = 0;
588 my $tot_unsup_ctr = 0;
589 FILE: for my $rfile (sort @rfiles) {
590 $rfile = $cwd . '/' . $rfile unless $rfile =~ m|^/|;
593 if ($rfile =~ m|.*/(\S+\.rules)$|) {
596 if ($rfile =~ m|.*/(\S+)\.rules$|) {
603 next FILE unless defined $include_types{$type};
605 if ($exclude_types) {
606 next FILE if defined $exclude_types{$type};
608 if ($rfile eq 'deleted.rules') {
609 next FILE unless $add_deleted;
611 ($snort_type) = ($rfile =~ m|.*/(\S+)\.rules|);
612 printf("%-30s", "[+] $filename") unless $include_sids;
614 &logr("[+] Parsing $rfile");
615 open R, "< $rfile" or die "[*] Could not open: $rfile";
619 ### contains Snort rules that will be used by Snort_inline
620 ### if fwsnort is building a QUEUE policy; these rules have
621 ### met the criteria that at least one "content" match is
623 my @queue_rules = ();
629 my $ipt_apply_ctr = 0;
630 my $ipt_rules_ctr = 0;
632 RULE: for my $rule (@lines) {
638 ### pass == ACCEPT, log == ULOG
639 unless ($rule =~ /^\s*alert/ or $rule =~ /^\s*pass/
640 or $rule =~ /^\s*log/) {
646 next RULE unless $rule =~ $exclude_re;
650 next RULE unless $rule =~ $include_re;
653 $rule_num++; ### keep track of the abs num of rules
656 if ($rule =~ m|^(.*?)\s+\((.*)\)|) {
658 $rule_options = " $2 "; ### allows out-of-order options
660 &logr("[-] Unrecognized rule format at line: $line_num. " .
665 ### skip all icmp "Undefined Code" rules; psad properly
666 ### handles this, but not fwsnort (see the icmp-info.rules
668 if ($filename =~ /icmp/ and $rule_options =~ /undefined\s+code/i) {
674 ### parse header portion of Snort rule
675 my $hdr_hr = &parse_rule_hdr($rule_hdr, $line_num);
676 unless (keys %$hdr_hr) {
677 &logr("[-] Unrecognized rule header: \"$rule_hdr\" at " .
678 "line: $line_num, skipping.");
684 ### parse options portion of Snort rule
685 my ($parse_rv, $opts_hr, $patterns_ar)
686 = &parse_rule_options($rule_options,
687 &get_avg_hdr_len($hdr_hr),
696 print "[+] Found sid: $opts_hr->{'sid'} in $filename\n";
699 if ($queue_mode or $nfqueue_mode) {
701 ### In general, it is not easy to modify the signatures that
702 ### snort_inline would use; one would think that an optimzation
703 ### would be to remove all "content" keywords since the kernel
704 ### itself is doing this now, but consider the following:
706 ### Suppose there are two original Snort signatures like so:
708 ### msg: "SIG1"; content: "abc"; pcre: "(d|e)";
709 ### msg: "SIG2"; content: "xyz"; pcre: "(e|f)";
711 ### Now, suppose there is a packet with the following data:
713 ### packet data: "xyz------------e------"
715 ### Then the SIG1 matches when it shouldn't because the packet
716 ### does not contain "abc" (assuming the "abc" string is
717 ### removed from the signature that is actually deployed with
718 ### snort_inline). There does not seem to be a good solution
719 ### for this problem if pcre criteria are involved because the
720 ### two pcre's would have to be interpreted to see if there is
721 ### any data that could satisfy both at the same time.
723 ### However, performing the duplicate string matching is far
724 ### less expensive than not sending a large portion of network
725 ### traffic to userspace for analysis by snort_inline in the
726 ### first place. This is the real benefit of letting fwsnort
727 ### build a smarter iptables queueing policy. This does come
728 ### with a penalty against detection, since snort_inline is
729 ### only receiving individual packets that match one of the
730 ### content keywords in a signature; it does not get the
731 ### entire stream. But, this may be worth it for large sites
732 ### where performance is the primary concern. Also, there is
733 ### some potential for removing a subset of the content
734 ### matches if done in the right way; this is the reason the
735 ### queue_get_rule() function is stubbed in below.
736 my $queue_rule = &queue_get_rule($rule_hdr, $rule_options);
738 push @queue_rules, $queue_rule if $queue_rule;
741 ### construct the equivalent iptables rule and add it
742 ### to $config{'FWSNORT_SCRIPT'}
743 my ($ipt_rv, $num_rules) = &ipt_build($hdr_hr,
744 $opts_hr, $patterns_ar, $rule);
749 ### may have the rule in several chains
750 $ipt_rules_ctr += $num_rules;
752 print " Successful translation.\n";
756 print " Unsuccessful translation.\n";
759 $parsed_ctr++; ### keep track of successfully parsed rules
763 if (($queue_mode or $nfqueue_mode) and @queue_rules) {
764 open M, "> $config{'QUEUE_RULES_DIR'}/$filename" or die "[*] Could not ",
765 "open $config{'QUEUE_RULES_DIR'}/$filename: $!";
766 print M "#\n### This file generated with: fwsnort @argv_cp\n#\n\n";
767 print M "$_\n", for @queue_rules;
768 print M "\n### EOF ###\n";
772 if ($ipt_rules_ctr) {
773 $ipt_rules_ctr *= 2 if $ipt_drop;
774 $ipt_rules_ctr *= 2 if $ipt_reject;
775 push @ipt_script_lines,
776 qq|\$ECHO " Rules added: $ipt_rules_ctr"|;
779 unless ($include_sids) {
781 printf("%-10s%-10s%-10s%-10s\n", $parsed_ctr, $unsup_ctr,
782 $ipt_apply_ctr, $rule_num);
784 printf("%-10s%-10s%-10s\n", $parsed_ctr, $unsup_ctr,
789 unless ($include_sids) {
792 print "=======================================\n";
793 printf("%30s%-10s%-10s%-10s%-10s\n", ' ',
794 $abs_num, $tot_unsup_ctr, $tot_ipt_apply, $sabs_num);
797 print "=============================\n";
798 printf("%30s%-10s%-10s%-10s\n", ' ',
799 $abs_num, $tot_unsup_ctr, $sabs_num);
802 if ($abs_num) { ### we parsed at least one rule
803 print "[+] Generated $ipt_str rules for $abs_num out of ",
804 "$sabs_num signatures: ",
805 sprintf("%.2f", $abs_num/$sabs_num*100), "%\n";
807 print "[+] No rules parsed.\n";
810 print "[+] Found $tot_ipt_apply applicable snort rules to your " .
811 "current $ipt_str\n policy.\n";
817 sub parse_rule_options() {
818 my ($rule_options, $avg_hdr_len, $line_num) = @_;
824 ### get the sid here for logging purposes
825 if ($rule_options =~ /$snort_opts{'logprefix'}{'sid'}/) {
828 return 0, \%opts, \@patterns;
832 return 0, \%opts, \@patterns if defined $exclude_sids{$sid};
835 if (defined $include_sids{$sid}) {
836 &logr("[+] matched sid:$sid: $rule_options");
838 return 0, \%opts, \@patterns;
842 unless ($queue_mode or $nfqueue_mode) {
844 ### if we're queuing packets to userspace Snort, then we don't have to
845 ### disqualify a signature based on an option that is not supported by
847 my $found_unsupported = '';
848 for my $opt (keys %{$snort_opts{'unsupported'}}) {
849 ### see if we match a regex belonging to an unsupported option
850 if ($rule_options =~ /$snort_opts{'unsupported'}{$opt}/) {
851 $found_unsupported .= "'$opt', ";
854 if ($found_unsupported) {
855 $found_unsupported =~ s/,\s+$//;
856 &logr("[-] SID: $sid Unsupported option(s): $found_unsupported " .
857 "at line: $line_num, skipping.");
858 if (%include_sids and defined $include_sids{$sid}) {
859 print "[-] SID: $sid contain the unsupported option(s): ",
860 "$found_unsupported at line: $line_num\n";
862 return 0, \%opts, \@patterns;
866 if ($rule_options =~ /ip_proto\s*:.*ip_proto\s*:/) {
867 &logr("[-] SID: $sid, unsupported multiple ip_proto fields at " .
868 "line: $line_num, skipping.");
869 return 0, \%opts, \@patterns;
872 for my $opt (keys %{$snort_opts{'filter'}}) {
873 ### see if we match the option regex
874 if ($rule_options =~ /$snort_opts{'filter'}{$opt}{'regex'}/) {
876 $opts{$opt} = $1 if defined $1; ### some keywords may not have an option
880 my $found_content = 0;
881 while ($rule_options =~ /(\w+):?\s*((?:.*?[^\x5c]?))\s*;/g) {
884 $val = $2 if defined $2; ### some keywords may not have an argument
886 if ($opt eq 'content' or $opt eq 'uricontent') {
887 return 0, \%opts, \@patterns unless $val =~ /"$/;
890 return 0, \%opts, \@patterns unless $val =~ /\S/;
892 ### convert the string into a form that is more compatible
894 my ($rv, $log_str, $ipt_pattern_hr)
895 = &convert_pattern_for_iptables($val);
899 push @patterns, $ipt_pattern_hr;
901 &logr("[-] SID: $sid, $log_str");
902 return 0, \%opts, \@patterns;
905 } elsif ($opt eq 'pcre') {
908 $val =~ s|/\w{0,3}"$||;
910 ### see if this pcre only has strings separated with ".*" or ".+"
911 ### and if so translate to multple string matches
912 my ($pcre_rv, $pcre_strings_ar) = &parse_pcre($val);
914 for my $str (@$pcre_strings_ar) {
915 push @patterns, $str;
918 unless ($queue_mode or $nfqueue_mode) {
919 &logr("[-] SID: $sid, unsupported complex pcre: $val");
920 return 0, \%opts, \@patterns;
924 } elsif ($opt eq 'fast_pattern') {
925 if ($no_fast_pattern_order) {
926 ### force it to be the first pattern so no reordering
928 $patterns[0]->{'fast_pattern'} = 1;
930 $patterns[$#patterns]->{'fast_pattern'} = 1;
932 } elsif ($opt eq 'nocase') {
933 unless (defined $snort_opts{'ignore'}{'nocase'}) {
934 $patterns[$#patterns]->{'nocase'} = 1;
937 for my $key (qw(offset depth within distance)) {
939 my ($offsets_rv, $log_str)
940 = &define_offsets(\@patterns,
941 $avg_hdr_len, $key, $val);
942 unless ($offsets_rv) {
943 &logr("[-] SID: $sid, $log_str");
944 return 0, \%opts, \@patterns;
952 if (($queue_mode or $nfqueue_mode) and not $found_content) {
953 my $queue_str = 'QUEUE';
954 $queue_str = 'NFQUEUE' if $nfqueue_mode;
955 &logr("[-] SID: $sid In --$queue_str mode signature must have " .
956 "'content' or 'uricontent' keyword " .
957 "at line: $line_num, skipping.");
958 if (%include_sids and defined $include_sids{$sid}) {
959 print "[-] SID: $sid does not contain 'content' ",
962 return 0, \%opts, \@patterns;
965 ### update offset, depth, within, and distance values for relative
967 my ($offsets_rv, $log_str) = &update_offsets_relative_matches(\@patterns);
968 unless ($offsets_rv) {
969 &logr("[-] SID: $sid, $log_str");
970 return 0, \%opts, \@patterns;
973 for my $opt (keys %{$snort_opts{'logprefix'}}) {
974 if ($rule_options =~ /$snort_opts{'logprefix'}{$opt}/) {
979 unless ($queue_mode or $nfqueue_mode) {
980 while ($rule_options =~ /(\w+):\s*.*?;/g) {
982 if (not defined $opts{$option}
983 and not defined $snort_opts{'ignore'}{$option}) {
984 &logr("[-] SID: $sid bad option: \"$option\" at line: $line_num " .
986 return 0, \%opts, \@patterns;
990 if (defined $opts{'ipopts'}
991 and $opts{'ipopts'} ne 'rr'
992 and $opts{'ipopts'} ne 'ts'
993 and $opts{'ipopts'} ne 'ssrr'
994 and $opts{'ipopts'} ne 'lsrr'
995 and $opts{'ipopts'} ne 'any') {
996 &logr("[-] SID: $sid, unsupported ipopts field at " .
997 "line: $line_num, skipping.");
998 return 0, \%opts, \@patterns;
1001 if (defined $opts{'itype'}
1002 and ($opts{'itype'} =~ m|<| or $opts{'itype'} =~ m|>|)) {
1003 &logr("[-] SID: $sid, unsupported range operator in itype field " .
1004 "line: $line_num, skipping.");
1005 return 0, \%opts, \@patterns;
1007 if (defined $opts{'icode'}
1008 and ($opts{'icode'} =~ m|<| or $opts{'icode'} =~ m|>|)) {
1009 &logr("[-] SID: $sid, unsupported range operator in icode field " .
1010 "line: $line_num, skipping.");
1011 return 0, \%opts, \@patterns;
1013 if (defined $opts{'ip_proto'}
1014 and ($opts{'ip_proto'} =~ m|<| or $opts{'ip_proto'} =~ m|>|)) {
1015 &logr("[-] SID: $sid, unsupported range operator in ip_proto field " .
1016 "line: $line_num, skipping.");
1017 return 0, \%opts, \@patterns;
1022 return 1, \%opts, \@patterns;
1025 sub parse_rule_hdr() {
1026 my ($rule_hdr, $line_num) = @_;
1028 my $action = 'alert'; ### default
1029 if ($rule_hdr =~ /^\s*pass/) {
1031 } elsif ($rule_hdr =~ /^\s*log/) {
1034 if ($rule_hdr =~ m|^\s*\w+\s+(\S+)\s+(\S+)\s+(\S+)
1035 \s+(\S+)\s+(\S+)\s+(\S+)|ix) {
1043 unless ($proto =~ /^\w+$/) {
1044 &logr("[-] Unsupported protocol: \"$proto\" at line: " .
1045 "$line_num, skipping.");
1049 ### in --ip6tables mode make sure we're not looking at IPv4 addresses
1050 if ($enable_ip6tables
1051 and ($src =~ /\b$ip_re\b/ or $dst =~ /\b$ip_re\b/)) {
1052 &logr("[-] --ip6tables mode enabled but IPv4 " .
1053 "address in rule variable at line: $line_num.");
1057 ### in --ip6tables mode exclude the icmp protocol - maybe should
1058 ### change to icmp6 in the future
1059 if ($enable_ip6tables and $proto eq 'icmp') {
1060 &logr("[-] --ip6tables mode enabled, so excluding " .
1061 "icmp (non-icmp6) siganture at line: $line_num.");
1066 $bidir_flag = 1 if $bidir eq '<>';
1069 'action' => $action,
1073 'bidir' => $bidir_flag,
1078 ### map to expanded values (e.g. $HOME -> "any" or whatever
1079 ### is defined in fwsnort.conf)
1080 for my $var (qw(src sport dst dport)) {
1081 my $val = $hsh{$var};
1082 my $negate_flag = 0;
1083 $negate_flag = 1 if $val =~ m|!|;
1084 while ($val =~ /\$(\w+)/) {
1086 if (defined $config{$val}) {
1087 $val = $config{$val};
1088 if ($enable_ip6tables and $val =~ /\b$ip_re\b/) {
1089 &logr("[-] --ip6tables mode enabled but IPv4 " .
1090 "address in rule variable at line: $line_num.");
1094 &logr("[-] Undefined variable $val in rule header " .
1095 "at line: $line_num.");
1099 if ($negate_flag and $val !~ m|!|) {
1100 $hsh{$var} = "!$val";
1106 for my $var (qw(sport dport)) {
1107 next unless $hsh{$var} =~ /,/;
1108 if ($ipt_have_multiport_match) {
1109 $hsh{$var} =~ s/\[//;
1110 $hsh{$var} =~ s/\]//;
1112 my @ports = split /\s*,\s*/, $hsh{$var};
1114 for my $port (@ports) {
1115 if ($port =~ /\d+:$/) {
1116 $ports_str .= "${port}65535,";
1118 $ports_str .= "${port},";
1121 $ctr++ if $port =~ /\:/; ### a range counts for two ports
1122 ### multiport is limited to 15 ports
1123 last if $ctr >= $ipt_multiport_max;
1125 $ports_str =~ s/,$//;
1126 $hsh{$var} = $ports_str;
1128 &logr("[-] Warning: taking the first port in the list " .
1129 "$hsh{$var} until the $ipt_str multiport match is supported " .
1130 "at line: $line_num.");
1131 $hsh{$var} =~ s/,.*//;
1132 $hsh{$var} =~ s/\[//;
1133 $hsh{$var} =~ s/\]//;
1147 if ($pcre =~ m|^\w+$|) {
1148 push @patterns, (&convert_pattern_for_iptables($pcre))[2];
1150 } elsif ($pcre =~ m|UNION\x5c\x73\x2bSELECT|) {
1151 ### a bunch of Emerging Threats rules contain "UNION\s+SELECT"
1152 ### as a PCRE. Sure, the translation below can be evaded, but
1153 ### it is better than nothing.
1154 push @patterns, (&convert_pattern_for_iptables('UNION SELECT'))[2];
1158 if ($pcre =~ m|\.\*|) {
1159 @ar = split /\.\*/, $pcre;
1161 } elsif ($pcre =~ m|\.\+|) {
1162 @ar = split /\.\+/, $pcre;
1164 } elsif ($pcre =~ m|\x5b\x5e\x5c\x6e\x5d\x2b|) { ### [^\n]+
1165 @ar = split /\x5b\x5e\x5c\x6e\x5d\x2b/, $pcre;
1169 for my $part (@ar) {
1170 next unless $part; ### some Snort pcre's begin with .* or .+
1171 ### (which seems useless)
1173 ### Replace "\(" with hex equivalent in PCRE's
1174 ### like: /.+ASCII\(.+SELECT/
1175 $part =~ s/\x5c\x28/|5c 28|/;
1177 ### Replace "\:" with hex equivalent in PCRE's
1178 ### like: /User-Agent\:[^\n]+spyaxe/
1179 $part =~ s/\x5c\x3a/|5c 3a|/;
1182 $basic =~ s/\|5c 28\|//;
1183 $basic =~ s/\|5c 3a\|//;
1185 if ($basic =~ /^[\w\x20]+$/) {
1186 push @patterns, (&convert_pattern_for_iptables($part))[2];
1187 } elsif ($basic eq 'User-Agent') {
1188 push @patterns, (&convert_pattern_for_iptables($part))[2];
1195 return $rv, \@patterns;
1198 sub queue_get_rule() {
1199 my ($rule_hdr, $rule_opts) = @_;
1201 ### FIXME: the following commented out code would need to be
1202 ### drastically improved to ensure that the remaining signatures
1203 ### are completely unique in userspace. For now, just return
1204 ### the original Snort rule
1205 ### Remove all of the following keywords since they are handled
1206 ### within the kernel directly.
1207 # for my $key qw/uricontent content offset depth within distance/ {
1208 # $rule_opts =~ s/([\s;])$key:\s*.*?\s*;\s*/$1/g;
1211 $rule_opts =~ s/^\s*//;
1212 $rule_opts =~ s/\s*$//;
1214 return "$rule_hdr ($rule_opts)";
1217 sub ipt_allow_traffic() {
1218 my ($hdr_hr, $opts_hr, $chain, $orig_snort_rule) = @_;
1223 print "\n[+] Snort rule: $orig_snort_rule"
1224 unless defined $snort_dump_cache{$orig_snort_rule};
1225 $snort_dump_cache{$orig_snort_rule} = '';
1228 ### check to see if the header is allowed through the chain,
1229 ### and if not we don't really care about matching traffic
1230 ### because iptables doesn't allow it anyway
1231 RULE: for my $rule_hr (@{$ipt_policy{$chain}}) {
1234 if ($dumper and $verbose) {
1235 print "[+] RULE: $rule_ctr:\n",
1239 print "[+] $ipt_str rule: $rule_hr->{'raw'}\n"
1240 unless defined $ipt_dump_cache{$rule_hr->{'raw'}};
1241 $ipt_dump_cache{$rule_hr->{'raw'}} = '';
1244 ### don't match on rules to/from the loopback interface
1245 unless ($no_exclude_loopback) {
1246 if ($rule_hr->{'intf_in'} eq 'lo'
1247 or $rule_hr->{'intf_out'} eq 'lo') {
1248 print "[-] Skipping $chain rule $rule_ctr: loopback rule\n"
1254 ### don't match on rules that build state
1255 if ($rule_hr->{'extended'} =~ /state/) {
1256 print "[-] Skipping $chain rule $rule_ctr: state rule\n"
1262 unless (($hdr_hr->{'proto'} eq $rule_hr->{'proto'}
1263 or $rule_hr->{'proto'} eq 'all')) {
1264 print "[-] Skipping $chain rule $rule_ctr: $hdr_hr->{'proto'} ",
1265 "!= $rule_hr->{'proto'}\n" if $debug;
1269 ### match src/dst IP/network
1270 unless (&match_addr($hdr_hr->{'src'}, $rule_hr->{'src'})) {
1271 print "[-] Skipping $chain rule $rule_ctr: src $hdr_hr->{'src'} ",
1272 "not part of $rule_hr->{'src'}\n" if $debug;
1275 unless (&match_addr($hdr_hr->{'dst'}, $rule_hr->{'dst'})) {
1276 print "[-] Skipping $chain rule $rule_ctr: dst $hdr_hr->{'dst'} ",
1277 "not part of $rule_hr->{'dst'}\n" if $debug;
1281 ### match src/dst ports
1282 if ($hdr_hr->{'proto'} ne 'icmp') {
1283 unless (&match_port($hdr_hr->{'sport'},
1284 $rule_hr->{'sport'})) {
1285 print "[-] Skipping $chain rule $rule_ctr: sport ",
1286 "$hdr_hr->{'sport'} not part of $rule_hr->{'sport'}\n"
1290 unless (&match_port($hdr_hr->{'dport'},
1291 $rule_hr->{'dport'})) {
1292 print "[-] Skipping $chain rule $rule_ctr: dport ",
1293 "$hdr_hr->{'dport'} not part of $rule_hr->{'dport'}\n"
1299 if (defined $opts_hr->{'flow'} and $rule_hr->{'state'}) {
1300 if ($opts_hr->{'flow'} eq 'established') {
1301 unless ($rule_hr->{'state'} =~ /ESTABLISHED/) {
1302 print "[-] Skipping $chain rule $rule_ctr: state ",
1303 "$opts_hr->{'flow'} not part of $rule_hr->{'state'}\n"
1310 ### if we make it here, then this rule matches the signature
1311 ### (from a header perspective)
1312 if ($rule_hr->{'target'} eq 'DROP'
1313 or $rule_hr->{'target'} eq 'REJECT') {
1315 print "[-] Matching $ipt_str rule has DROP or REJECT target; ",
1316 "$ipt_str policy does not allow this Snort rule.\n"
1319 print "\n[-] RULE $chain DROP:\n",
1326 } elsif ($rule_hr->{'target'} eq 'ACCEPT') {
1328 print "\n[+] RULE $chain ACCEPT:\n",
1334 print "[-] Matching $ipt_str rule has ACCEPT target; ",
1335 "$ipt_str policy allows this Snort rule.\n" if $debug;
1337 } ### we don't support other targets besides DROP, REJECT,
1338 ### or ACCEPT for now.
1341 ### if we make it here, then no specific ACCEPT rule matched the header,
1342 ### so return false if the chain policy is set to DROP (or there is
1343 ### a default drop rule). Otherwise there is no rule that would block
1345 if (defined $ipt_default_policy_setting{$chain}) {
1346 if ($ipt_default_policy_setting{$chain} eq 'ACCEPT') {
1347 if (defined $ipt_default_drop{$chain}) {
1348 if (defined $ipt_default_drop{$chain}{'all'}) {
1349 print "[-] Default DROP rule applies to this Snort rule.\n"
1352 } elsif (defined $ipt_default_drop{$chain}
1353 {$hdr_hr->{'proto'}}) {
1354 print "[-] Default DROP rule applies to this Snort rule.\n"
1360 print "\nACCEPT $chain, no $ipt_str matching rule\n",
1369 print "\nDROP $chain, no $ipt_str matching rule\n",
1375 ### maybe a "strict" option should be added here?
1380 my ($hdr_src, $rule_src) = @_;
1381 return 1 if $rule_src eq '0.0.0.0/0';
1382 return 1 if $hdr_src =~ /any/i;
1383 return 1 if $hdr_src eq $rule_src;
1386 my $ipt_mask = '32';
1392 $negate = 1 if $hdr_src =~ /\!/;
1394 if ($rule_src =~ /\!/) {
1396 ### if both hdr_src and rule_src are negated
1397 ### then revert to normal match.
1404 if ($rule_src =~ m|($ip_re)/($ip_re)|) {
1407 } elsif ($rule_src =~ m|($ip_re)/(\d+)|) {
1410 } elsif ($rule_src =~ m|($ip_re)|) {
1414 $ipt_obj = new NetAddr::IP($ipt_ip, $ipt_mask);
1416 for my $addr (@{&expand_addresses($hdr_src)}) {
1418 my $src_mask = '32';
1419 if ($addr =~ m|($ip_re)/($ip_re)|) {
1422 } elsif ($addr =~ m|($ip_re)/(\d+)|) {
1425 } elsif ($addr =~ m|($ip_re)|) {
1428 $s_obj = new NetAddr::IP($src_ip, $src_mask);
1430 return 1 unless $ipt_obj->within($s_obj);
1432 return 1 if $ipt_obj->within($s_obj);
1439 my ($snort_port, $ipt_port) = @_;
1440 return 1 if $ipt_port eq '0:0';
1441 return 1 if $snort_port =~ /any/i;
1442 return 1 if $ipt_port eq $snort_port;
1444 my $ipt_end = 65535;
1448 if ($ipt_port =~ /:/) {
1449 if ($ipt_port =~ /(\d+):/) {
1452 if ($ipt_port =~ /:(\d+)/) {
1455 } elsif ($ipt_port =~ /(\d+)/) {
1456 $ipt_start = $ipt_end = $1;
1459 if ($snort_port =~ /:/) {
1460 if ($snort_port =~ /(\d+):/) {
1463 if ($snort_port =~ /:(\d+)/) {
1466 } elsif ($snort_port =~ /(\d+)/) {
1467 $h_start = $h_end = $1;
1470 if ($ipt_port =~ /!/) {
1471 if ($snort_port =~ /!/) {
1474 return 1 if (($h_start < $ipt_start and $h_end < $ipt_start)
1475 or ($h_start > $ipt_end and $h_end > $ipt_end));
1478 if ($snort_port =~ /!/) {
1479 return 1 if (($ipt_start < $h_start and $ipt_end < $h_start)
1480 or ($ipt_start > $h_end and $ipt_end > $h_end));
1482 return 1 if $h_start >= $ipt_start and $h_end <= $ipt_end;
1488 sub cache_ipt_policy() {
1490 my $ipt = new IPTables::Parse 'iptables' => $cmds{'iptables'}
1491 or die "[*] Could not acquire IPTables::Parse object: $!";
1493 for my $chain (keys %process_chains) {
1494 next unless $process_chains{$chain};
1496 $ipt_policy{$chain} = $ipt->chain_rules('filter',
1499 $ipt_default_policy_setting{$chain}
1500 = $ipt->chain_policy('filter', $chain, $ipt_file);
1502 my ($def_drop_hr, $ipt_rv)
1503 = $ipt->default_drop('filter', $chain, $ipt_file);
1506 $ipt_default_drop{$chain} = $def_drop_hr;
1513 my ($snort_hdr_hr, $snort_opts_hr, $patterns_ar, $orig_snort_rule) = @_;
1518 my %process_rules = ();
1520 ### define iptables source and destination
1521 if ($snort_hdr_hr->{'dst'} =~ /any/i) {
1522 if ($snort_hdr_hr->{'src'} =~ /any/i) {
1523 push @{$process_rules{'INPUT'}}, '' if $process_chains{'INPUT'};
1524 push @{$process_rules{'FORWARD'}}, ''
1525 if $process_chains{'FORWARD'};
1527 my $addr_ar = &expand_addresses($snort_hdr_hr->{'src'});
1529 $negate = '! ' if $snort_hdr_hr->{'src'} =~ m|!|;
1531 &logr("[-] No valid source IPs/networks in Snort " .
1535 for my $src (@$addr_ar) {
1536 if (&is_local($src)) {
1537 push @{$process_rules{'OUTPUT'}},
1538 "$negate$ipt_hdr_opts{'src'} ${src}"
1539 if $process_chains{'OUTPUT'};
1541 push @{$process_rules{'INPUT'}},
1542 "$negate$ipt_hdr_opts{'src'} ${src}"
1543 if $process_chains{'INPUT'};
1545 push @{$process_rules{'FORWARD'}},
1546 "$negate$ipt_hdr_opts{'src'} ${src}"
1547 if $process_chains{'FORWARD'};
1551 my $dst_addr_ar = &expand_addresses($snort_hdr_hr->{'dst'});
1552 unless ($dst_addr_ar) {
1553 &logr("[-] No valid destination IPs/networks in Snort rule " .
1557 if ($snort_hdr_hr->{'src'} =~ /any/i) {
1559 $negate = '! ' if $snort_hdr_hr->{'dst'} =~ m|!|;
1560 for my $dst (@$dst_addr_ar) {
1561 if (&is_local($dst)) {
1562 push @{$process_rules{'INPUT'}},
1563 "$negate$ipt_hdr_opts{'dst'} ${dst}"
1564 if $process_chains{'INPUT'};
1566 push @{$process_rules{'OUTPUT'}},
1567 "$negate$ipt_hdr_opts{'dst'} ${dst}"
1568 if $process_chains{'OUTPUT'};
1570 push @{$process_rules{'FORWARD'}},
1571 "$negate$ipt_hdr_opts{'dst'} ${dst}"
1572 if $process_chains{'FORWARD'};
1575 my $src_addr_ar = &expand_addresses($snort_hdr_hr->{'src'});
1576 my $negate_src = '';
1577 $negate_src = '! ' if $snort_hdr_hr->{'src'} =~ m|!|;
1578 my $negate_dst = '';
1579 $negate_dst = '! ' if $snort_hdr_hr->{'dst'} =~ m|!|;
1580 unless ($src_addr_ar) {
1581 &logr("[-] No valid source IPs/networks in Snort rule " .
1585 for my $src (@$src_addr_ar) {
1586 for my $dst (@$dst_addr_ar) {
1587 if (&is_local($dst)) {
1588 push @{$process_rules{'INPUT'}},
1589 "$negate_src$ipt_hdr_opts{'src'} ${src}" .
1590 " $negate_dst$ipt_hdr_opts{'dst'} ${dst}"
1591 if $process_chains{'INPUT'};
1593 push @{$process_rules{'OUTPUT'}},
1594 "$negate_src$ipt_hdr_opts{'src'} ${src}" .
1595 " $negate_dst$ipt_hdr_opts{'dst'} ${dst}"
1596 if $process_chains{'OUTPUT'};
1598 push @{$process_rules{'FORWARD'}},
1599 "$negate_src$ipt_hdr_opts{'src'} ${src}" .
1600 " $negate_dst$ipt_hdr_opts{'dst'} ${dst}"
1601 if $process_chains{'FORWARD'};
1607 ### determine which chain (e.g. stateful/stateless)
1608 my $flow_established = '';
1609 unless ($no_ipt_conntrack) {
1610 if (defined $snort_hdr_hr->{'proto'}
1611 and $snort_hdr_hr->{'proto'} =~ /tcp/i
1612 and defined $snort_opts_hr->{'flow'}
1613 and $snort_opts_hr->{'flow'} =~ /established/i) {
1614 $flow_established = 'ESTABLISHED';
1618 my $add_snort_comment = 1;
1619 my $add_perl_trigger = 1;
1620 for my $chain (keys %process_chains) {
1622 next unless $process_chains{$chain} and $process_rules{$chain};
1624 for my $src_dst (@{$process_rules{$chain}}) {
1626 my $rule = "\$${ipt_var_str} -A ";
1627 my $save_rule = '-A';
1628 my $fwsnort_chain = '';
1630 ### see if we can jump to the ESTABLISHED inspection chain.
1631 if ($flow_established) {
1632 $rule .= $config{"FWSNORT_${chain}_ESTAB"};
1633 $fwsnort_chain = $config{"FWSNORT_${chain}_ESTAB"};
1635 $rule .= $config{"FWSNORT_$chain"};
1636 $fwsnort_chain = $config{"FWSNORT_$chain"};
1639 ### append interface restriction if necessary
1640 if ($src_dst =~ m|127\.0\.0\.\d/|) {
1641 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
1642 $rule .= ' ! -i lo';
1643 } elsif ($chain eq 'OUTPUT') {
1644 $rule .= ' ! -o lo';
1648 ### append source and destination criteria
1650 if ($chain eq 'FORWARD') {
1651 $rule .= " $src_dst";
1652 } elsif ($chain eq 'INPUT') {
1653 ### we always treat the INPUT chain as part of the HOME_NET;
1654 ### the system running iptables may have an interface on the
1655 ### external network and hence may not be part of the HOME_NET
1656 ### as defined in the fwsnort.conf file so we don't necessarily
1657 ### append the IP criteria
1658 if ($src_dst ne "$ipt_hdr_opts{'dst'} $config{'HOME_NET'}") {
1659 $rule .= " $src_dst";
1661 } elsif ($chain eq 'OUTPUT') {
1662 if ($src_dst ne "$ipt_hdr_opts{'src'} $config{'HOME_NET'}") {
1663 $rule .= " $src_dst";
1668 my $rv = &ipt_build_rule(
1682 $add_snort_comment = 0;
1683 $add_perl_trigger = 0;
1688 return $found_rule, $num_rules;
1694 return 1 if $no_addr_check;
1699 if ($addr =~ m|($ip_re)/($ip_re)|) {
1702 } elsif ($addr =~ m|($ip_re)/(\d+)|) {
1705 } elsif ($addr =~ m|($ip_re)|) {
1709 my $ip_obj = new NetAddr::IP($ip, $mask);
1711 for my $local_ar (@local_addrs) {
1712 my $local_ip = $local_ar->[0];
1713 my $local_mask = $local_ar->[1];
1715 my $local_obj = new NetAddr::IP($local_ip, $local_mask);
1717 return 1 if $ip_obj->within($local_obj);
1722 sub get_local_addrs() {
1723 open IFC, "$cmds{'ifconfig'} -a |" or die "[*] Could not run ",
1724 "$cmds{'ifconfig'}: $!";
1729 for my $line (@lines) {
1730 if ($line =~ /^(\w+)\s+Link/) {
1734 next if $intf_name eq 'lo';
1735 next if $intf_name =~ /dummy/i;
1736 if ($line =~ /^\s+inet.*?:($ip_re).*:($ip_re)/i) {
1737 push @local_addrs, [$1, $2];
1743 sub ipt_build_rule() {
1744 my ($chain, $fwsnort_chain, $rule, $hdr_hr, $opts_hr, $patterns_ar,
1745 $orig_snort_rule, $flow_logging_prefix, $add_snort_comment,
1746 $add_perl_trigger) = @_;
1748 ### $chain is used only to see whether or not we need to add the
1749 ### rule to the iptables script based on whether the built-in chain
1750 ### will pass the traffic in the first place.
1752 return 0 unless &ipt_allow_traffic($hdr_hr,
1753 $opts_hr, $chain, $orig_snort_rule);
1756 ### append the protocol to the rule
1757 if (defined $opts_hr->{'ip_proto'}) {
1758 return 0 unless $opts_hr->{'ip_proto'} =~ /^\w+$/;
1759 $rule .= " $snort_opts{'filter'}{'ip_proto'}{'iptopt'} " .
1760 "$opts_hr->{'ip_proto'}";
1762 return 0 unless $hdr_hr->{'proto'} =~ /^\w+$/;
1763 if ((($hdr_hr->{'sport'} !~ /any/i and $hdr_hr->{'sport'} ne '')
1764 or ($hdr_hr->{'dport'} !~ /any/i
1765 and $hdr_hr->{'dport'} ne ''))
1766 and $hdr_hr->{'proto'} !~ /tcp/i
1767 and $hdr_hr->{'proto'} !~ /udp/i) {
1768 ### force to tcp because iptables does not like src/dst
1769 ### ports with anything other than tcp or udp
1770 $hdr_hr->{'proto'} = 'tcp';
1773 if ($hdr_hr->{'proto'} =~ /ip/) {
1774 $rule .= " $ipt_hdr_opts{'proto'} $hdr_hr->{'proto'}";
1776 $rule .= " $ipt_hdr_opts{'proto'} $hdr_hr->{'proto'} " .
1777 "-m $hdr_hr->{'proto'}";
1781 ### append the source and destination ports
1782 for my $type (qw(sport dport)) {
1783 if (defined $hdr_hr->{$type} and $hdr_hr->{$type} !~ /any/i) {
1785 my $port = $hdr_hr->{$type};
1786 $negate = '! ' if $port =~ m|!|;
1787 $port =~ s/\!\s*(\d)/$1/;
1790 $rule .= " -m multiport ${negate}--${type}s $port";
1792 $rule .= " ${negate}$ipt_hdr_opts{$type} $port";
1797 my $rv = &ipt_build_opts($rule, $hdr_hr, $opts_hr, $patterns_ar,
1798 $orig_snort_rule, $flow_logging_prefix, $chain, $fwsnort_chain,
1799 $add_snort_comment, $add_perl_trigger);
1804 sub ipt_build_opts() {
1805 my ($rule, $hdr_hr, $opts_hr, $patterns_ar, $orig_snort_rule,
1806 $flow_logging_prefix, $chain, $fwsnort_chain, $add_snort_comment,
1807 $add_perl_trigger) = @_;
1809 ### append tcp flags
1810 if (defined $opts_hr->{'flags'}) {
1813 $f_str .= 'URG,' if $opts_hr->{'flags'} =~ /U/i;
1814 $f_str .= 'ACK,' if $opts_hr->{'flags'} =~ /A/i;
1815 $f_str .= 'PSH,' if $opts_hr->{'flags'} =~ /P/i;
1816 $f_str .= 'RST,' if $opts_hr->{'flags'} =~ /R/i;
1817 $f_str .= 'SYN,' if $opts_hr->{'flags'} =~ /S/i;
1818 $f_str .= 'FIN,' if $opts_hr->{'flags'} =~ /F/i;
1821 if ($opts_hr->{'flags'} =~ /\+/) {
1822 ### --tcp-flags ACK ACK
1823 $rule .= " $snort_opts{'filter'}{'flags'}{'iptopt'} " .
1826 ### --tcp-flags ALL URG,PSH,SYN,FIN
1827 $rule .= " $snort_opts{'filter'}{'flags'}{'iptopt'} " .
1832 if ($no_ipt_conntrack) {
1833 ### fall back to appending --tcp-flags ACK ACK if flow=established.
1834 ### NOTE: we can't really handle "flow" in the same way snort can,
1835 ### since there is no way to keep track of which side initiated the
1836 ### tcp session (where the SYN packet came from), but older versions
1837 ### of snort (pre 1.9) just used tcp flags "A+" to keep track of
1838 ### this... we need to do the same.
1839 if (defined $opts_hr->{'flow'} && ! defined $opts_hr->{'flags'}) {
1840 if ($opts_hr->{'flow'} =~ /established/i) {
1841 ### note that this ignores the "stateless" keyword
1843 $rule .= " $snort_opts{'filter'}{'flow'}{'iptopt'} ACK ACK";
1848 ### append icmp type
1849 if ($hdr_hr->{'proto'} =~ /icmp/i) {
1850 if (defined $opts_hr->{'itype'}) {
1851 $rule .= " $snort_opts{'filter'}{'itype'}{'iptopt'} " .
1852 "$opts_hr->{'itype'}";
1853 ### append icmp code (becomes "--icmp-type type/code")
1854 if (defined $opts_hr->{'icode'}) {
1855 $rule .= "/$opts_hr->{'icode'}";
1858 ### append the default icmp type since some recent versions of
1859 ### iptables (such as 1.4.12 on Fedora 16) require it - an error
1860 ### like the following will be thrown if it's not there:
1861 ### iptables-restore v1.4.12: icmp: option "--icmp-type" must be specified
1862 $rule .= " $snort_opts{'filter'}{'itype'}{'iptopt'} " .
1867 ### append ip options
1868 if (defined $opts_hr->{'ipopts'}) {
1869 $rule .= " $snort_opts{'filter'}{'ipopts'}{'iptopt'} " .
1870 "--$opts_hr->{'ipopts'}"
1873 ### append tos (requires CONFIG_IP_NF_MATCH_TOS)
1874 if (defined $opts_hr->{'tos'}) {
1875 $rule .= " $snort_opts{'filter'}{'tos'}{'iptopt'} " .
1880 ### append ttl (requires CONFIG_IP_NF_MATCH_TTL)
1881 if (defined $opts_hr->{'ttl'}) {
1882 if ($opts_hr->{'ttl'} =~ /\<\s*(\d+)/) {
1883 $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} --ttl-lt $1";
1884 } elsif ($opts_hr->{'ttl'} =~ /\>\s*(\d+)/) {
1885 $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} --ttl-gt $1";
1887 $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} " .
1888 "--ttl-eq $opts_hr->{'ttl'}";
1892 my $avg_hdr_len = &get_avg_hdr_len($hdr_hr);
1894 ### append dsize option (requires CONFIG_IP_NF_MATCH_LENGTH)
1895 if (defined $opts_hr->{'dsize'}) {
1896 ### get the average packet header size based on the protocol
1897 ### (the iptables length match applies to the network header
1899 if ($opts_hr->{'dsize'} =~ m|(\d+)\s*<>\s*(\d+)|) {
1900 my $iptables_len1 = $1 + $avg_hdr_len;
1901 my $iptables_len2 = $2 + $avg_hdr_len;
1902 $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " .
1903 "$iptables_len1:$iptables_len2";
1904 } elsif ($opts_hr->{'dsize'} =~ m|<\s*(\d+)|) {
1905 my $iptables_len = $1 + $avg_hdr_len;
1906 $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " .
1907 "$avg_hdr_len:$iptables_len";
1908 } elsif ($opts_hr->{'dsize'} =~ m|>\s*(\d+)|) {
1909 my $iptables_len = $1 + $avg_hdr_len;
1910 if ($iptables_len < $config{'MAX_FRAME_LEN'} + $avg_hdr_len) {
1911 $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " .
1913 ($config{'MAX_FRAME_LEN'} + $avg_hdr_len);
1915 } elsif ($opts_hr->{'dsize'} =~ m|(\d+)|) {
1916 my $iptables_len = $1 + $avg_hdr_len;
1917 $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " .
1922 ### append snort content options
1923 my $ipt_content_criteria = 0;
1924 my $perl_trigger_str = '';
1926 if ($#$patterns_ar > -1) {
1927 ($ipt_content_criteria, $perl_trigger_str)
1928 = &build_content_matches($opts_hr, $patterns_ar);
1930 return 0 unless $ipt_content_criteria;
1931 $rule .= $ipt_content_criteria;
1934 ### print the rest of the logprefix snort options in a comment
1935 ### one line above the rule
1937 my $target_str = '';
1938 for my $key (qw(sid msg classtype reference priority rev)) {
1939 if (defined $opts_hr->{$key}) {
1940 $comment .= qq|$key:$opts_hr->{$key}; |;
1943 $comment =~ s/\s*$//;
1946 ### append the fwsnort version as "FWS:$version"
1947 $comment .= " FWS:$version;";
1949 ### build up the logging prefix and comment match
1950 if (defined $opts_hr->{'sid'}) {
1951 unless ($no_ipt_comments) {
1952 ### add the Snort msg (and other) fields to the iptables rule
1953 ### with the 'comment' match (which can handle up to 255 chars
1954 ### and is set/verified by the ipt_find_max_comment_len()
1956 $comment =~ s|\"||g;
1957 $comment =~ s|/\*||g;
1958 $comment =~ s|\*/||g;
1959 if (length($comment) < $ipt_max_comment_len) {
1960 $target_str = qq| -m comment --comment "$comment"|;
1963 ### increment chain counter and add in if necessary
1964 $chain_ctr{$chain}++;
1966 if ($queue_mode or $nfqueue_mode) {
1968 $target_str .= qq| -j QUEUE|;
1970 $target_str .= qq| -j NFQUEUE|;
1972 $target_str .= " --queue-num $nfqueue_num";
1976 if ($hdr_hr->{'action'} eq 'log' or $ulog_mode) {
1977 $target_str .= qq| -j ULOG --ulog-nlgroup $ulog_nlgroup --ulog-prefix "|;
1979 $target_str .= ' -j LOG ';
1980 $target_str .= '--log-ip-options ' unless $no_ipt_log_ip_opts;
1981 if ($hdr_hr->{'proto'} eq 'tcp') {
1982 $target_str .= '--log-tcp-options ' unless $no_ipt_log_tcp_opts;
1983 $target_str .= '--log-tcp-sequence ' if $ipt_log_tcp_seq;
1985 $target_str .= qq|--log-prefix |;
1989 ### build up the LOG prefix string
1990 my $prefix_str = '';
1992 unless ($no_ipt_rule_nums) {
1993 $prefix_str .= "[$chain_ctr{$chain}] ";
1997 $prefix_str .= 'DRP ';
1998 } elsif ($ipt_reject) {
1999 $prefix_str .= 'REJ ';
2002 ### always add the sid
2003 $prefix_str .= qq|SID$opts_hr->{'sid'} |;
2004 if ($flow_logging_prefix) {
2005 $prefix_str .= 'ESTAB ';
2008 if (length($prefix_str) >= $ipt_max_log_prefix_len) {
2009 $prefix_str = qq|SID$opts_hr->{'sid'} |;
2010 if (length($prefix_str) >= $ipt_max_log_prefix_len) {
2011 return 0 unless $ipt_content_criteria;
2015 $target_str .= qq|"| . $prefix_str . qq|"|;
2019 ### print the snort rules type header to the fwsnort.sh script
2020 unless ($ipt_print_type) {
2021 &ipt_type($snort_type);
2022 $ipt_print_type = 1;
2025 ### write the rule out to the iptables script
2026 &ipt_add_rule($hdr_hr, $opts_hr, $orig_snort_rule,
2027 $rule, $target_str, "### $comment", $add_snort_comment,
2028 $perl_trigger_str, $add_perl_trigger, $chain, $fwsnort_chain);
2032 sub build_content_matches() {
2033 my ($opts_hr, $patterns_ar) = @_;
2035 my $fast_pattern_index = 0;
2036 my $fast_pattern_is_set = 0;
2037 my $ipt_content_criteria = '';
2038 my $perl_trigger_command = '';
2039 my @content_fast_pattern_order = ();
2041 $perl_trigger_command = q|perl -e 'print "| if $include_perl_triggers;
2043 if ($no_fast_pattern_order) {
2044 $patterns_ar->[0]->{'fast_pattern'} = 1;
2047 for (my $index=0; $index <= $#$patterns_ar; $index++) {
2048 if ($patterns_ar->[$index]->{'fast_pattern'}) {
2049 $fast_pattern_index = $index;
2050 $fast_pattern_is_set = 1;
2055 unless ($fast_pattern_is_set) {
2056 ### in this case, the 'fast_pattern' option was not used, so pick the
2057 ### longest pattern to match first (this should help with performance
2058 ### of signature matches on average)
2060 my $max_len_index = 0;
2061 PATTERN: for (my $index=0; $index <= $#$patterns_ar; $index++) {
2062 my $pat_ar = $patterns_ar->[$index];
2064 if ($pat_ar->{'length'} > $max_len) {
2066 ### make sure it is not a relative match
2067 next PATTERN if defined $pat_ar->{'distance'};
2068 next PATTERN if defined $pat_ar->{'within'};
2070 if ($index < $#$patterns_ar) {
2071 my $next_pat_ar = $patterns_ar->[$index+1];
2072 next PATTERN if defined $next_pat_ar->{'distance'};
2073 next PATTERN if defined $next_pat_ar->{'within'};
2076 $max_len = $pat_ar->{'length'};
2077 $max_len_index = $index;
2080 $fast_pattern_index = $max_len_index;
2083 $content_fast_pattern_order[0] = $patterns_ar->[$fast_pattern_index];
2084 for (my $i=0; $i <= $#$patterns_ar; $i++) {
2085 next if $i == $fast_pattern_index;
2086 push @content_fast_pattern_order, $patterns_ar->[$i];
2089 for (my $i=0; $i <= $#content_fast_pattern_order; $i++) {
2091 if (($queue_mode or $nfqueue_mode) and $queue_pre_match_max > 0) {
2092 ### limit the number of content matches to perform within the
2093 ### kernel before sending the packet to a userspace Snort
2095 last if $i >= $queue_pre_match_max;
2098 my $pattern_hr = $content_fast_pattern_order[$i];
2099 my $content_str = $pattern_hr->{'ipt_pattern'};
2101 if ($content_str =~ /\|.+\|/) {
2102 ### there is hex data in the content
2103 if ($pattern_hr->{'negative_match'}) {
2104 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2105 {'content'}{'iptopt'} . ' ' .
2106 qq{! --hex-string "$content_str"};
2108 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2109 {'content'}{'iptopt'} . ' ' .
2110 qq{--hex-string "$content_str"};
2113 ### there is no hex data in the content
2114 if ($pattern_hr->{'negative_match'}) {
2115 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2116 {'content'}{'iptopt'} . ' ' .
2117 qq{! --string "$content_str"};
2119 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2120 {'content'}{'iptopt'} . ' ' .
2121 qq{--string "$content_str"};
2125 if (defined $opts_hr->{'replace'}) {
2126 my $replace_str = $opts_hr->{'replace'};
2127 $replace_str =~ s/`/\\`/g;
2128 if ($replace_str =~ /\|.+\|/) { ### there is hex data in the content
2129 $ipt_content_criteria
2130 .= qq{ --replace-hex-string "$replace_str"};
2132 $ipt_content_criteria
2133 .= qq{ --replace-string "$replace_str"};
2137 if ($kernel_ver ne '2.4') {
2138 $ipt_content_criteria .= " --algo $string_match_alg";
2140 ### see if we have any offset, depth, distance, or within
2143 if (defined $pattern_hr->{'offset'}) {
2145 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2146 {'offset'}{'iptopt'} . " $pattern_hr->{'offset'}";
2147 $perl_trigger_command .= 'A'x$pattern_hr->{'offset'}
2148 if $include_perl_triggers;
2150 } elsif (defined $pattern_hr->{'distance'}) { ### offset trumps distance
2152 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2153 {'distance'}{'iptopt'} . " $pattern_hr->{'distance'}";
2155 $perl_trigger_command .= 'A'x$pattern_hr->{'distance'}
2156 if $include_perl_triggers;
2159 if (defined $pattern_hr->{'depth'}) {
2161 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2162 {'depth'}{'iptopt'} . " $pattern_hr->{'depth'}";
2164 $perl_trigger_command .= 'A'x$pattern_hr->{'depth'}
2165 if $include_perl_triggers;
2167 } elsif (defined $pattern_hr->{'within'}) { ### depth trumps within
2169 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2170 {'within'}{'iptopt'} . " $pattern_hr->{'within'}";
2172 $perl_trigger_command .= 'A'x$pattern_hr->{'within'}
2173 if $include_perl_triggers;
2176 ### see if we need to match the string match case insensitive
2177 if ($pattern_hr->{'nocase'}) {
2178 $ipt_content_criteria .= ' ' . $snort_opts{'filter'}
2179 {'nocase'}{'iptopt'};
2182 ### if the --payload option is available for
2183 ### the string match extension
2184 $ipt_content_criteria .= ' --payload'
2185 if $ipt_has_string_payload_offset_opt;
2188 if ($include_perl_triggers) {
2189 ### now append the perl trigger command bytes
2190 if ($pattern_hr->{'negative_match'}) {
2191 $perl_trigger_command .= 'A'x($pattern_hr->{'length'});
2193 $perl_trigger_command .= &translate_perl_trigger($content_str);
2198 if ($include_perl_triggers) {
2199 $perl_trigger_command .= qq|"'|;
2202 return $ipt_content_criteria, $perl_trigger_command;
2205 sub convert_pattern_for_iptables() {
2206 my $snort_str = shift;
2209 my $ipt_str = $snort_str;
2213 'orig_snort_str' => $snort_str,
2214 'ipt_pattern' => '',
2215 'negative_match' => 0,
2216 'fast_pattern' => 0,
2220 if ($ipt_str =~ /\s*\!\s*"/) {
2221 $ipt_str =~ s/\s*\!\s*"//;
2222 $pattern{'negative_match'} = 1;
2225 $ipt_str =~ s/`/|60|/g; ### ` -> |60|
2226 $ipt_str =~ s/'/|27|/g; ### ' -> |27|
2227 $ipt_str =~ s/\x2d/|2d|/g; ### - -> |2d|
2228 $ipt_str =~ s/\x24/|24|/g; ### $ -> |24|
2230 ### convert all escaped chars to their hex equivalents
2231 $ipt_str =~ s/\x5c(.)/sprintf "|%02x|", (ord($1))/eg;
2234 while ($ipt_str =~ m/\|\|/) {
2235 ### consolidate consecutive hex blocks
2236 if ($ipt_str =~ /\|.+?\|\|.+?\|/) {
2237 $ipt_str =~ s/\|(.+?)\|\|(.+?)\|/|$1 $2|/;
2243 ### remove all spaces between hex codes (they simply waste space
2244 ### on the command line, and they aren't part of the string to
2245 ### search in network traffic anyway).
2246 $ipt_str = &consolidate_hex_spaces($ipt_str);
2248 ### handles length of hex blocks
2249 my $content_len = &get_content_len($ipt_str);
2250 if ($content_len >= $ipt_max_str_len
2251 or $content_len >= $config{'MAX_STRING_LEN'}) {
2252 return 0, "pattern too long: $snort_str", \%pattern;
2255 $pattern{'ipt_pattern'} = $ipt_str;
2256 $pattern{'length'} = $content_len;
2258 return 1, $log_str, \%pattern;
2261 sub define_offsets() {
2262 my ($patterns_ar, $avg_hdr_len, $opt, $val) = @_;
2264 my $current_index = $#$patterns_ar;
2266 ### the option should not be defined - if so, there is a duplicate
2267 ### Snort keyword in the signature
2268 if (defined $patterns_ar->[$current_index]->{$opt}) {
2269 return 0, "duplicate keyword: $opt";
2272 ### store the value that was in the original Snort rule
2273 $patterns_ar->[$current_index]->{"${opt}_snort_orig"} = $val;
2275 ### store the value and account for average header length if
2277 if ($ipt_has_string_payload_offset_opt) {
2278 $patterns_ar->[$current_index]->{$opt} = $val;
2280 $patterns_ar->[$current_index]->{$opt}
2281 = $val + $avg_hdr_len + $MAC_HDR_LEN;
2287 sub update_offsets_relative_matches() {
2288 my $patterns_ar = shift;
2290 for (my $i=0; $i <= $#$patterns_ar; $i++) {
2292 my $pat_hr = $patterns_ar->[$i];
2293 my $snort_offset = -1;
2294 my $snort_depth = -1;
2295 my $snort_distance = -1;
2296 my $snort_within = -1;
2298 $snort_offset = $pat_hr->{'offset_snort_orig'}
2299 if defined $pat_hr->{'offset_snort_orig'};
2300 $snort_depth = $pat_hr->{'depth_snort_orig'}
2301 if defined $pat_hr->{'depth_snort_orig'};
2302 $snort_distance = $pat_hr->{'distance_snort_orig'}
2303 if defined $pat_hr->{'distance_snort_orig'};
2304 $snort_within = $pat_hr->{'within_snort_orig'}
2305 if defined $pat_hr->{'within_snort_orig'};
2307 if ($snort_depth > -1) {
2308 ### if there is also an offset, then the depth begins after
2309 ### the offset starts
2310 if ($snort_offset > -1) {
2311 $pat_hr->{'depth'} += $snort_offset;
2312 } elsif ($snort_distance > -1) {
2313 $pat_hr->{'depth'} += $snort_distance;
2315 if ($snort_depth < $pat_hr->{'length'}) {
2316 return 0, "depth: $snort_depth less than " .
2317 "pattern length: $pat_hr->{'length'}";
2321 if ($snort_distance > -1) {
2322 ### see if we need to increase the distance based
2323 ### on the length of the previous pattern match and offset
2325 my $prev_pat_hr = $patterns_ar->[$i-1];
2326 $pat_hr->{'distance'} += $prev_pat_hr->{'length'};
2327 if (defined $prev_pat_hr->{'offset_snort_orig'}) {
2328 $pat_hr->{'distance'} += $prev_pat_hr->{'offset_snort_orig'};
2333 if ($snort_within > -1) {
2334 if ($snort_offset > -1) {
2335 $pat_hr->{'within'} += $snort_offset;
2336 } elsif ($snort_distance > -1) {
2337 $pat_hr->{'within'} += $snort_distance;
2340 my $prev_pat_hr = $patterns_ar->[$i-1];
2341 $pat_hr->{'within'} += $prev_pat_hr->{'length'};
2342 if (defined $prev_pat_hr->{'offset_snort_orig'}) {
2343 $pat_hr->{'within'} += $prev_pat_hr->{'offset_snort_orig'};
2351 sub get_avg_hdr_len() {
2354 my $avg_hdr_len = $config{'AVG_IP_HEADER_LEN'};
2355 if (defined $hdr_hr->{'proto'}) {
2356 if ($hdr_hr->{'proto'} =~ /udp/i) {
2357 $avg_hdr_len += $UDP_HDR_LEN; ### udp header is 8 bytes
2358 } elsif ($hdr_hr->{'proto'} =~ /icmp/i) {
2359 $avg_hdr_len += $ICMP_HDR_LEN; ### icmp header is 8 bytes
2362 $avg_hdr_len += $config{'AVG_TCP_HEADER_LEN'};
2365 ### don't know what the average transport layer (if there
2366 ### is one) length will be; add 10 bytes just to be safe
2369 return $avg_hdr_len;
2372 ### handles length of hex blocks
2373 sub get_content_len() {
2377 my @chars = split //, $str;
2378 for (my $i=0; $i<=$#chars; $i++) {
2379 if ($chars[$i] eq '|') {
2380 $hex_mode == 0 ? ($hex_mode = 1) : ($hex_mode = 0);
2384 next if $chars[$i] eq ' ';
2394 sub consolidate_hex_spaces() {
2398 my @chars = split //, $str;
2399 for my $char (@chars) {
2401 $hex_mode == 0 ? ($hex_mode = 1) : ($hex_mode = 0);
2404 next if $char eq ' ';
2411 sub translate_perl_trigger() {
2413 my $trigger_str = '';
2415 my $append_hex_str = '';
2416 my $append_non_hex_str = '';
2418 my @chars = split //, $str;
2420 for my $char (@chars) {
2423 if ($append_hex_str) {
2424 while ($append_hex_str =~ /(.{2})/g) {
2425 $trigger_str .= "\\x$1";
2427 $append_hex_str = '';
2431 if ($append_non_hex_str) {
2432 $trigger_str .= qq|$append_non_hex_str|;
2433 $append_non_hex_str = '';
2441 $append_hex_str .= $char;
2443 $append_non_hex_str .= $char;
2447 if ($append_hex_str) {
2448 while ($append_hex_str =~ /(.{2})/g) {
2449 $trigger_str .= "\\x$1";
2452 $trigger_str .= qq|$append_non_hex_str| if $append_non_hex_str;
2454 return $trigger_str;
2457 sub ipt_add_rule() {
2458 my ($hdr_hr, $opts_hr, $orig_snort_rule, $rule_base,
2459 $target_str, $comment, $add_snort_comment,
2460 $perl_trigger_str, $add_perl_trigger, $chain, $fwsnort_chain) = @_;
2462 my $action_rule = '';
2463 if ($hdr_hr->{'proto'} eq 'tcp') {
2464 if ($hdr_hr->{'action'} eq 'pass') {
2465 $action_rule = "$rule_base -j ACCEPT";
2467 if (defined $opts_hr->{'resp'}
2468 and $opts_hr->{'resp'} =~ /rst/i) {
2469 ### iptables can only send tcp resets to the connection
2470 ### client, so we can't support rst_rcv, but we should
2471 ### try to tear the connection down anyway.
2472 $action_rule = "$rule_base -j REJECT " .
2473 "--reject-with tcp-reset";
2474 } elsif ($ipt_drop) {
2475 $action_rule = "$rule_base -j DROP";
2476 } elsif ($ipt_reject) {
2477 $action_rule = "$rule_base -j REJECT " .
2478 "--reject-with tcp-reset";
2481 } elsif ($hdr_hr->{'proto'} eq 'udp') {
2482 if ($hdr_hr->{'action'} eq 'pass') {
2483 $action_rule = "$rule_base -j ACCEPT";
2485 if (defined $opts_hr->{'resp'}
2486 and $opts_hr->{'resp'} =~ /icmp/i) {
2487 if ($opts_hr->{'resp'} =~ /all/i) { ### icmp_all
2488 $action_rule = "$rule_base -j REJECT " .
2489 "--reject-with icmp-port-unreachable";
2490 } elsif ($opts_hr->{'resp'} =~ /net/i) { ### icmp_net
2491 $action_rule = "$rule_base -j REJECT " .
2492 "--reject-with icmp-net-unreachable";
2493 } elsif ($opts_hr->{'resp'} =~ /host/i) { ### icmp_host
2494 $action_rule = "$rule_base -j REJECT " .
2495 "--reject-with icmp-host-unreachable";
2496 } elsif ($opts_hr->{'resp'} =~ /port/i) { ### icmp_port
2497 $action_rule = "$rule_base -j REJECT " .
2498 "--reject-with icmp-port-unreachable";
2500 } elsif ($ipt_drop) {
2501 $action_rule = "$rule_base -j DROP";
2502 } elsif ($ipt_reject) {
2503 $action_rule = "$rule_base -j REJECT " .
2504 "--reject-with icmp-port-unreachable";
2508 if ($hdr_hr->{'action'} eq 'pass') {
2509 $action_rule = "$rule_base -j ACCEPT";
2511 $action_rule = "$rule_base -j DROP";
2514 my $ipt_rule = $rule_base . $target_str;
2516 push @ipt_script_lines, "\n### $orig_snort_rule" if $add_snort_comment;
2517 if ($include_perl_triggers and $add_perl_trigger) {
2518 push @ipt_script_lines, "### $perl_trigger_str";
2521 push @ipt_script_lines, qq|\$ECHO "[+] rule $ipt_rule_ctr"|;
2524 ### save format handling
2525 my $save_format_ipt_rule = $ipt_rule . " \n";
2526 my $save_format_action_rule = $action_rule . " \n";
2528 $save_format_ipt_rule =~ s|\$${ipt_var_str}\s+\-A|-A|;
2529 $save_format_action_rule =~ s|\$${ipt_var_str}\s+\-A|-A|;
2531 if ($hdr_hr->{'action'} ne 'pass') {
2532 if ($queue_mode or $nfqueue_mode) {
2534 push @ipt_script_lines, $ipt_rule;
2535 push @{$save_format_rules{$fwsnort_chain}}, $save_format_ipt_rule;
2539 push @ipt_script_lines, $ipt_rule unless $no_ipt_log;
2540 push @{$save_format_rules{$fwsnort_chain}}, $save_format_ipt_rule
2545 if ($action_rule and ($ipt_drop or $ipt_reject or
2546 $hdr_hr->{'action'} eq 'pass' or defined $opts_hr->{'resp'})) {
2548 push @ipt_script_lines, $action_rule;
2549 push @{$save_format_rules{$fwsnort_chain}}, $save_format_action_rule;
2556 sub save_format_append_rules() {
2558 for my $chain (sort keys %ipt_save_existing_chains) {
2560 next unless &is_fwsnort_chain($chain, $MATCH_EQUIV);
2562 ### make sure that whitelist/blacklist and established jump rules
2563 ### are added at the beginning of each chain in save format
2564 &save_format_add_prereqs($chain);
2566 for my $rule (@{$save_format_rules{$chain}}) {
2568 push @fwsnort_save_lines, $rule;
2572 ### now append any last lines from the iptables-save output that
2573 ### had nothing to do with fwsnort (other custom chains, etc.)
2574 for (my $i = $ipt_save_index; $i <= $#ipt_save_lines; $i++) {
2575 next if &is_fwsnort_chain($ipt_save_lines[$i], $MATCH_SUBSTR);
2576 push @fwsnort_save_lines, $ipt_save_lines[$i];
2582 sub save_format_add_prereqs() {
2585 return if defined $save_format_prereqs{$chain};
2588 if (defined $save_format_whitelist{$chain}) {
2589 for my $whitelist_rule (@{$save_format_whitelist{$chain}}) {
2590 push @fwsnort_save_lines, "$whitelist_rule \n";
2595 if (defined $save_format_blacklist{$chain}) {
2596 for my $blacklist_rule (@{$save_format_blacklist{$chain}}) {
2597 push @fwsnort_save_lines, "$blacklist_rule \n";
2601 ### add jump rules into the connection tracking fwsnort chains
2602 if (defined $save_format_conntrack_jumps{$chain}) {
2603 for my $jump_rule (@{$save_format_conntrack_jumps{$chain}}) {
2604 push @fwsnort_save_lines, "$jump_rule \n";
2608 $save_format_prereqs{$chain} = '';
2613 sub ipt_whitelist() {
2614 my @whitelist_addrs = ();
2616 for my $whitelist_line (@{$config{'WHITELIST'}}) {
2617 for my $addr (@{&expand_addresses($whitelist_line)}) {
2618 push @whitelist_addrs, $addr;
2622 return unless $#whitelist_addrs >= 0;
2624 push @ipt_script_lines, "\n###\n############ Add IP/network " .
2625 "WHITELIST rules. ############\n###";
2627 for my $addr (@whitelist_addrs) {
2628 for my $chain (keys %process_chains) {
2629 next unless $process_chains{$chain};
2631 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
2632 my $rule_str = qq|-A $config{"FWSNORT_$chain"} | .
2633 "-s $addr -j RETURN";
2634 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2636 push @{$save_format_whitelist{$config{"FWSNORT_$chain"}}},
2639 if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') {
2640 my $rule_str = qq|-A $config{"FWSNORT_$chain"} | .
2641 "-d $addr -j RETURN";
2642 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2644 push @{$save_format_whitelist{$config{"FWSNORT_$chain"}}},
2652 sub ipt_blacklist() {
2654 my $printed_intro = 0;
2656 for my $blacklist_line (@{$config{'BLACKLIST'}}) {
2658 my @blacklist_addrs = ();
2659 my $target = 'DROP'; ### default
2661 if ($blacklist_line =~ /\s+REJECT/) {
2665 for my $addr (@{&expand_addresses($blacklist_line)}) {
2666 push @blacklist_addrs, $addr;
2669 return unless $#blacklist_addrs >= 0;
2671 unless ($printed_intro) {
2672 push @ipt_script_lines, "\n###\n############ Add IP/network " .
2673 "BLACKLIST rules. ############\n###";
2677 for my $addr (@blacklist_addrs) {
2678 for my $chain (keys %process_chains) {
2679 next unless $process_chains{$chain};
2681 if ($target eq 'DROP') {
2682 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
2684 my $rule_str = qq|-A $config{"FWSNORT_$chain"} | .
2687 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2688 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2692 if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') {
2694 my $rule_str = qq|-A $config{"FWSNORT_$chain"} | .
2696 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2697 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2701 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
2702 my $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | .
2703 "-p tcp -j REJECT --reject-with tcp-reset";
2705 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2706 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2709 $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | .
2710 "-p udp -j REJECT --reject-with icmp-port-unreachable";
2712 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2713 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2716 $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | .
2717 "-p icmp -j REJECT --reject-with icmp-host-unreachable";
2719 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2720 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2723 if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') {
2724 my $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | .
2725 "-p tcp -j REJECT --reject-with tcp-reset";
2727 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2728 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2731 $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | .
2732 "-p udp -j REJECT --reject-with icmp-port-unreachable";
2734 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2735 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2738 $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | .
2739 "-p icmp -j REJECT --reject-with icmp-host-unreachable";
2741 push @ipt_script_lines, "\$${ipt_var_str} $rule_str";
2742 push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}},
2752 sub ipt_add_chains() {
2755 my %ipt_save_built_in_chains = ();
2756 my $look_for_chains = 0;
2757 for (@ipt_save_lines) {
2758 unless ($look_for_chains) {
2759 push @fwsnort_save_lines, $_;
2762 $look_for_chains = 1;
2763 } elsif ($look_for_chains and $_ =~ /^:(\S+)/) {
2765 if ($chain eq 'INPUT'
2766 or $chain eq 'OUTPUT'
2767 or $chain eq 'FORWARD') {
2768 $ipt_save_built_in_chains{$chain} = $_;
2770 ### don't preserve any old fwsnort chains, but preserve
2771 ### any other existing custom chains
2772 unless (&is_fwsnort_chain($chain, $MATCH_EQUIV)) {
2773 $ipt_save_existing_chains{$chain} = $_;
2776 } elsif ($look_for_chains and $_ !~ /^:(\S+)/) {
2782 ### save format - add the built-in chains first
2783 for my $chain (qw(INPUT FORWARD OUTPUT)) {
2784 ### should always be defined unless we're not running as root
2785 next unless defined $ipt_save_built_in_chains{$chain};
2786 push @fwsnort_save_lines, $ipt_save_built_in_chains{$chain};
2789 ### add the fwsnort chains
2790 push @ipt_script_lines, "\n###\n############ Create " .
2791 "fwsnort $ipt_str chains. ############\n###";
2793 for my $built_in_chain (qw(INPUT FORWARD OUTPUT)) {
2795 ### see if any of the "FWSNORT_<built-in-chain>" chains need to be
2797 next unless $process_chains{$built_in_chain};
2799 for my $chain ($config{"FWSNORT_$built_in_chain"},
2800 $config{"FWSNORT_${built_in_chain}_ESTAB"}) {
2801 if ($no_ipt_conntrack) {
2803 $config{"FWSNORT_${built_in_chain}_ESTAB"};
2805 push @ipt_script_lines,
2806 "\$${ipt_var_str} -N $chain 2> /dev/null",
2807 "\$${ipt_var_str} -F $chain\n";
2810 $ipt_save_existing_chains{$chain} = ":$chain - [0:0]\n";
2814 ### save format - add the custom chains
2815 for my $chain (sort keys %ipt_save_existing_chains) {
2816 push @fwsnort_save_lines, $ipt_save_existing_chains{$chain};
2819 ### save format - add in the jump rules from the
2820 ### built-in chains here
2821 &save_format_add_jump_rules();
2823 ### add in any rules from custom chains that alphabetically come
2824 ### before the first fwsnort chain
2825 &save_format_add_early_custom_chains();
2830 sub save_format_add_jump_rules() {
2832 ### add the jump rule for each built-in chain
2833 for my $built_in_chain (qw(INPUT FORWARD OUTPUT)) {
2835 next unless defined $process_chains{$built_in_chain}
2836 and $process_chains{$built_in_chain};
2838 ### get the current $chain rules (if any), and then see where to add
2839 ### the fwsnort jump rule
2841 my @existing_chain_rules = ();
2843 for (my $i = $ipt_save_index; $i < $#ipt_save_lines; $i++) {
2845 ### delete any existing fwsnort jump rules
2846 if ($ipt_save_lines[$i] =~ /^\-A\s$built_in_chain\s/) {
2847 if ($ipt_save_lines[$i] !~ /\-j\sFWSNORT_/) {
2848 push @existing_chain_rules, $ipt_save_lines[$i];
2857 my $added_jump_rule = 0;
2858 for my $existing_rule (@existing_chain_rules) {
2859 if ($ctr == $config{"FWSNORT_${built_in_chain}_JUMP"}) {
2860 &save_format_add_chain_jump_rule($built_in_chain);
2861 $added_jump_rule = 1;
2864 push @fwsnort_save_lines, $existing_rule;
2868 ### the chain may have been empty
2869 unless ($added_jump_rule) {
2870 &save_format_add_chain_jump_rule($built_in_chain);
2877 sub save_format_add_chain_jump_rule() {
2878 my $built_in_chain = shift;
2879 my $fwsnort_chain = "FWSNORT_${built_in_chain}";
2880 if (%restrict_interfaces) {
2881 for my $intf (keys %restrict_interfaces) {
2882 if ($built_in_chain eq 'INPUT' or $built_in_chain eq 'FORWARD') {
2883 push @fwsnort_save_lines, "-A $built_in_chain -i $intf " .
2884 "-j $fwsnort_chain \n";
2885 } elsif ($built_in_chain eq 'OUTPUT') {
2886 push @fwsnort_save_lines, "-A $built_in_chain -o $intf " .
2887 "-j $fwsnort_chain \n";
2891 if ($no_exclude_loopback) {
2892 push @fwsnort_save_lines, "-A $built_in_chain " .
2893 "-j $fwsnort_chain \n";
2895 if ($built_in_chain eq 'INPUT' or $built_in_chain eq 'FORWARD') {
2896 push @fwsnort_save_lines, "-A $built_in_chain ! -i lo " .
2897 "-j $fwsnort_chain \n";
2898 } elsif ($built_in_chain eq 'OUTPUT') {
2899 push @fwsnort_save_lines, "-A $built_in_chain ! -o lo " .
2900 "-j $fwsnort_chain \n";
2908 sub save_format_add_early_custom_chains() {
2910 for my $chain (sort keys %ipt_save_existing_chains) {
2911 last if &is_fwsnort_chain($chain, $MATCH_EQUIV);
2913 for (my $i = $ipt_save_index; $i <= $#ipt_save_lines; $i++) {
2914 last if &is_fwsnort_chain($ipt_save_lines[$i], $MATCH_SUBSTR);
2915 push @fwsnort_save_lines, $ipt_save_lines[$i];
2923 sub is_fwsnort_chain() {
2924 my ($str, $match_style) = @_;
2927 for my $fwsnort_chain ($config{'FWSNORT_INPUT'},
2928 $config{'FWSNORT_INPUT_ESTAB'},
2929 $config{'FWSNORT_FORWARD'},
2930 $config{'FWSNORT_FORWARD_ESTAB'},
2931 $config{'FWSNORT_OUTPUT'},
2932 $config{'FWSNORT_OUTPUT_ESTAB'}) {
2934 if ($match_style eq $MATCH_SUBSTR) {
2935 if ($str =~ /$fwsnort_chain/) {
2939 } elsif ($match_style eq $MATCH_EQUIV) {
2940 if ($str eq $fwsnort_chain) {
2950 sub ipt_add_conntrack_jumps() {
2951 ### jump ESTABLISHED tcp traffic to each of the _ESTAB
2953 push @ipt_script_lines, "\n###\n############ Inspect $conntrack_state " .
2954 "tcp connections. ############\n###";
2956 for my $chain (keys %process_chains) {
2957 next unless $process_chains{$chain};
2961 if ($have_conntrack) {
2962 $rule_str = qq|-A $config{"FWSNORT_$chain"} -p tcp -m conntrack | .
2963 qq|--ctstate $conntrack_state -j | .
2964 qq|$config{"FWSNORT_${chain}_ESTAB"}|;
2965 } elsif ($have_state) {
2966 $rule_str = qq|-A $config{"FWSNORT_$chain"} -p tcp -m state | .
2967 qq|--state $conntrack_state -j | .
2968 qq|$config{"FWSNORT_${chain}_ESTAB"}|;
2971 push @ipt_script_lines, qq|\$${ipt_var_str} $rule_str|;
2972 push @{$save_format_conntrack_jumps{$config{"FWSNORT_$chain"}}},
2978 sub ipt_jump_chain() {
2979 push @ipt_script_lines, "\n###\n############ Jump traffic " .
2980 "to the fwsnort chains. ############\n###";
2981 if (%restrict_interfaces) {
2982 for my $intf (keys %restrict_interfaces) {
2983 for my $chain (keys %process_chains) {
2984 next unless $process_chains{$chain};
2986 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
2987 ### delete any existing jump rule so that fwsnort.sh can
2988 ### be executed many times in a row without adding several
2990 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
2991 qq|-i $intf -j $config{"FWSNORT_$chain"}| .
2994 ### now add the jump rule
2995 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
2996 qq|$config{"FWSNORT_${chain}_JUMP"} -i | .
2997 qq|$intf -j $config{"FWSNORT_$chain"}|;
2998 } elsif ($chain eq 'OUTPUT') {
3000 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
3001 qq|-o $intf -j $config{'FWSNORT_OUTPUT'}| .
3004 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
3005 qq|$config{'FWSNORT_OUTPUT_JUMP'} -o | .
3006 qq|$intf -j $config{'FWSNORT_OUTPUT'}|;
3011 for my $chain (keys %process_chains) {
3012 next unless $process_chains{$chain};
3014 if ($no_exclude_loopback) {
3016 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
3017 qq|-j $config{"FWSNORT_$chain"}| .
3020 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
3021 qq|$config{"FWSNORT_${chain}_JUMP"} | .
3022 qq|-j $config{"FWSNORT_$chain"}|;
3024 if ($chain eq 'INPUT' or $chain eq 'FORWARD') {
3026 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
3027 qq|! -i lo -j $config{"FWSNORT_$chain"}| .
3030 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
3031 qq|$config{"FWSNORT_${chain}_JUMP"} ! -i lo | .
3032 qq|-j $config{"FWSNORT_$chain"}|;
3034 } elsif ($chain eq 'OUTPUT') {
3036 push @ipt_script_lines, "\$${ipt_var_str} -D $chain " .
3037 qq|! -o lo -j $config{"FWSNORT_$chain"}| .
3040 push @ipt_script_lines, "\$${ipt_var_str} -I $chain " .
3041 qq|$config{"FWSNORT_${chain}_JUMP"} ! -o lo | .
3042 qq|-j $config{"FWSNORT_$chain"}|;
3052 "#!$cmds{'sh'}\n#", '#'x76,
3053 "#\n# File: $config{'FWSNORT_SCRIPT'}",
3054 "#\n# Purpose: This script was auto-" .
3055 "generated by fwsnort, and implements",
3056 "# an $ipt_str ruleset based upon " .
3057 "Snort rules. For more",
3058 "# information see the fwsnort man " .
3059 "page or the documentation",
3061 "http://www.cipherdyne.org/fwsnort/",
3062 "#\n# Generated with: fwsnort @argv_cp",
3063 "# Generated on host: " . hostname(),
3064 "# Time stamp: " . localtime(),
3065 "#\n# Author: Michael Rash <mbr\@cipherdyne.org>",
3066 "#\n# Version: $version",
3071 push @ipt_script_lines, &hdr_lines();
3072 push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines];
3074 ### add paths to system binaries (iptables included)
3075 &ipt_config_section();
3079 sub ipt_config_section() {
3080 ### build the config section of the iptables script
3081 push @ipt_script_lines,
3082 '#==================== config ====================',
3083 "ECHO=$cmds{'echo'}",
3084 "${ipt_var_str}=$ipt_bin",
3085 "#================== end config ==================\n";
3086 push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines];
3092 push @ipt_script_lines, "\n###\n############ ${type}.rules #######" .
3093 "#####\n###", "\$ECHO \"[+] Adding $type rules:\"";
3098 for my $type_hr (\%include_types, \%exclude_types) {
3099 for my $type (keys %$type_hr) {
3101 my @valid_types = ();
3102 for my $dir (split /\,/, $config{'RULES_DIR'}) {
3103 if (-e "$dir/${type}.rules") {
3106 opendir D, $dir or die "[*] Could not open $dir: $!";
3107 for my $file (readdir D) {
3108 if ($file =~ /(\S+)\.rules/) {
3109 push @valid_types, $1;
3115 print "[-] \"$type\" is not a valid type.\n",
3116 " Choose from the following available signature types:\n";
3117 for my $type (sort @valid_types) {
3127 sub import_config() {
3128 open C, "< $fwsnort_conf" or die "[*] Could not open $fwsnort_conf: $!";
3132 for my $line (@lines) {
3135 next if $line =~ /^\s*#/;
3136 next unless $line =~ /\S/;
3137 if ($line =~ /^\s*(\S+)Cmd\s+(\S+);/) { ### e.g. "iptablesCmd"
3139 } elsif ($line =~ /^\s*(\S+)\s+(.*?);/) {
3142 die "[*] $fwsnort_conf: Variable \"$var\" is set to\n",
3143 " _CHANGEME_ at line $l_ctr. Edit $fwsnort_conf.\n"
3144 if $val eq '_CHANGEME_';
3145 if (defined $multi_line_vars{$var}) {
3146 push @{$config{$var}}, $val;
3148 ### may have already been defined in existing snort.conf
3149 ### file if --snort-conf was given.
3150 $config{$var} = $val unless defined $config{$var};
3162 $config{'FWSNORT_INPUT'},
3163 $config{'FWSNORT_INPUT_ESTAB'},
3164 $config{'FWSNORT_OUTPUT'},
3165 $config{'FWSNORT_OUTPUT_ESTAB'},
3166 $config{'FWSNORT_FORWARD'},
3167 $config{'FWSNORT_FORWARD_ESTAB'}
3169 my $cmd = "$ipt_bin -v -n -L $chain";
3170 my $exists = (system "$cmd > /dev/null 2>&1") >> 8;
3172 print "[+] Listing $chain chain...\n";
3176 print "[-] Chain $chain does not exist...\n";
3184 $config{'FWSNORT_INPUT'},
3185 $config{'FWSNORT_INPUT_ESTAB'},
3186 $config{'FWSNORT_OUTPUT'},
3187 $config{'FWSNORT_OUTPUT_ESTAB'},
3188 $config{'FWSNORT_FORWARD'},
3189 $config{'FWSNORT_FORWARD_ESTAB'}
3191 my $exists = (system "$ipt_bin -n -L " .
3192 "$chain > /dev/null 2>&1") >> 8;
3194 print "[+] Flushing $chain chain...\n";
3195 system "$ipt_bin -F $chain";
3196 if ($ipt_del_chains) {
3197 ### must remove any jump rules from the built-in
3199 &del_jump_rule($chain);
3201 print " Deleting $chain chain...\n";
3202 system "$ipt_bin -X $chain";
3205 print "[-] Chain $chain does not exist...\n";
3211 sub cache_ipt_save_policy() {
3213 return unless $is_root;
3215 open IPT, "$save_bin -t filter |" or die "[*] Could not execute $save_bin";
3217 push @ipt_save_lines, $_;
3221 ### also write out the current iptables policy so that we can
3222 ### revert to it if necessary (iptables does a good job of not committing
3223 ### a policy via iptables-save if there is a problem with a rule though).
3224 &archive($config{'IPT_BACKUP_SAVE_FILE'});
3225 open F, "> $config{'IPT_BACKUP_SAVE_FILE'}" or die "[*] Could not " .
3226 "open $config{'IPT_BACKUP_SAVE_FILE'}: $!";
3227 print F for @ipt_save_lines;
3230 ### remove the last two lines (the 'COMMIT' and '# Completed ...' lines
3231 ### so they can be added later).
3232 $ipt_save_completed_line = $ipt_save_lines[$#ipt_save_lines];
3233 pop @ipt_save_lines;
3234 pop @ipt_save_lines;
3239 sub del_jump_rule() {
3242 my $ipt = new IPTables::Parse 'iptables' => $cmds{'iptables'}
3243 or die "[*] Could not acquire IPTables::Parse object: $!";
3245 for my $built_in_chain (qw(INPUT OUTPUT FORWARD)) {
3246 my $rules_ar = $ipt->chain_rules('filter', $built_in_chain, $ipt_file);
3248 for (my $i=0; $i <= $#$rules_ar; $i++) {
3249 my $rule_num = $i+1;
3250 if ($rules_ar->[$i]->{'target'} eq $chain) {
3251 system "$ipt_bin -D $built_in_chain $rule_num";
3260 sub fwsnort_init() {
3262 ### set umask to -rw-------
3265 ### turn off buffering
3268 &set_non_root_values() unless $is_root;
3270 ### read in configuration info from the config file
3273 ### make sure the commands are where the
3274 ### config file says they are
3277 ### make sure all of the required variables are defined
3278 ### in the config file
3281 $non_host = $NON_HOST;
3282 $ipt_bin = $cmds{'iptables'};
3283 $restore_bin = $cmds{'iptables-restore'};
3284 $save_bin = $cmds{'iptables-save'};
3286 if ($enable_ip6tables) {
3287 for my $opt (qw(itype icode ttl tos ipopts)) {
3288 $snort_opts{'unsupported'}{$opt}
3289 = $snort_opts{'filter'}{$opt};
3290 delete $snort_opts{'filter'}{$opt};
3292 $non_host = $NON_IP6_HOST;
3293 $save_str = 'ip6tables-save';
3294 $ipt_str = 'ip6tables';
3295 $ipt_bin = $cmds{'ip6tables'};
3296 $restore_bin = $cmds{'ip6tables-restore'};
3297 $save_bin = $cmds{'ip6tables-save'};
3305 die "[*] You need to be root for --ipt-apply" unless $is_root;
3306 if (-e $config{'FWSNORT_SAVE_EXEC_FILE'}) {
3307 print "[+] Executing $config{'FWSNORT_SAVE_EXEC_FILE'}\n";
3308 system $config{'FWSNORT_SAVE_EXEC_FILE'};
3311 die "[*] $config{'FWSNORT_SAVE_EXEC_FILE'} does not exist.";
3315 if ($enable_ip6tables) {
3316 ### switch to ip6tables
3317 $ipt_var_str = 'IP6TABLES';
3320 $process_chains{'INPUT'} = 0 if $no_ipt_input;
3321 $process_chains{'FORWARD'} = 0 if $no_ipt_forward;
3322 $process_chains{'OUTPUT'} = 0 if $no_ipt_output;
3324 ### import HOME_NET, etc. from existing Snort config file.
3325 &import_snort_conf() if $snort_conf_file;
3328 my @types = split /\,/, $rules_types;
3329 for my $type (@types) {
3330 $include_types{$type} = '';
3333 if ($exclude_types) {
3334 my @types = split /\,/, $exclude_types;
3335 for my $type (@types) {
3336 $exclude_types{$type} = '';
3339 if ($include_sids) {
3340 ### disable iptables policy parsing if we are translating a
3341 ### specific set of Snort sids.
3344 my @sids = split /\,/, $include_sids;
3345 for my $sid (@sids) {
3346 $include_sids{$sid} = '';
3349 if ($exclude_sids) {
3350 my @sids = split /\,/, $exclude_sids;
3351 for my $sid (@sids) {
3352 $exclude_sids{$sid} = '';
3355 if ($ipt_restrict_intf) {
3356 my @interfaces = split /\,/, $ipt_restrict_intf;
3357 for my $intf (@interfaces) {
3358 $restrict_interfaces{$intf} = '';
3363 if ($include_re_caseless) {
3364 $include_re = qr|$include_re|i;
3366 $include_re = qr|$include_re|;
3370 if ($exclude_re_caseless) {
3371 $exclude_re = qr|$exclude_re|i;
3373 $exclude_re = qr|$exclude_re|;
3377 ### flush all fwsnort chains.
3378 &ipt_flush() if $ipt_flush or $ipt_del_chains;
3380 ### list all fwsnort chains.
3381 &ipt_list() if $ipt_list;
3383 ### download latest snort rules from snort.org
3384 &update_rules() if $update_rules;
3386 ### make sure some directories exist, etc.
3389 ### get kernel version (this is mainly used to know whether
3390 ### the "--algo bm" argument is required for the string match
3391 ### extension in the 2.6.14 (and later) kernels. Also, the
3392 ### string match extension as of 2.6.14 supports the Snort
3393 ### offset and depth keywords via --from and --to
3396 ### may have been specified on the command line
3397 $home_net = $config{'HOME_NET'} unless $home_net;
3398 $ext_net = $config{'EXTERNAL_NET'} unless $ext_net;
3400 &get_local_addrs() unless $no_addr_check;
3403 ### make the snort options parser very strict
3404 for my $opt (qw(uricontent pcre
3405 distance within http_uri http_method urilen)) {
3406 $snort_opts{'unsupported'}{$opt}
3407 = $snort_opts{'filter'}{$opt};
3408 delete $snort_opts{'filter'}{$opt};
3410 my @ignore = (qw(nocase));
3412 if ($kernel_ver eq '2.4') {
3413 push @ignore, 'offset', 'depth';
3415 for my $opt (@ignore) {
3416 next unless defined $snort_opts{'ignore'}{$opt};
3417 $snort_opts{'unsupported'}{$opt}
3418 = $snort_opts{'ignore'}{$opt};
3419 delete $snort_opts{'ignore'}{$opt};
3423 ### skip trying to translate basic PCRE's
3424 $snort_opts{'unsupported'}{'pcre'}
3425 = $snort_opts{'filter'}{'pcre'};
3426 delete $snort_opts{'filter'}{'pcre'};
3429 if ($no_fast_pattern_order) {
3430 $snort_opts{'ignore'}{'fast_pattern'}
3431 = $snort_opts{'filter'}{'fast_pattern'}{'regex'};
3432 delete $snort_opts{'filter'}{'fast_pattern'};
3437 sub get_kernel_ver() {
3438 die "[*] uname command: $cmds{'uname'} is not executable."
3439 unless -x $cmds{'uname'};
3440 open U, "$cmds{'uname'} -a |" or die "[*] Could not run ",
3441 "$cmds{'uname'} -a";
3444 ### Linux orthanc 2.6.12.5 #2 Tue Sep 27 22:43:02 EDT 2005 i686 \
3445 ### Pentium III (Coppermine) GenuineIntel GNU/Linux
3446 if ($out =~ /\s2\.6/) {
3447 $kernel_ver = '2.6';
3452 sub handle_cmd_line() {
3454 ### make Getopts case sensitive
3455 Getopt::Long::Configure('no_ignore_case');
3457 die "[*] Use --help for usage information.\n" unless (GetOptions(
3458 'ipt-apply' => \$ipt_exec, # Apply the generated ruleset.
3459 'ipt-drop' => \$ipt_drop, # Add iptables DROP rules.
3460 'ipt-reject' => \$ipt_reject, # Add iptables REJECT rules.
3461 'ipt-script=s' => \$ipt_script, # Manually specify the path to the
3462 # generated iptables script.
3463 'ipt-log-tcp-seq' => \$ipt_log_tcp_seq, # Log TCP seq/ack values.
3464 'ipt-flush' => \$ipt_flush, # Flush any existing fwsnort chains.
3465 'ipt-check-capabilities' =>\$ipt_check_capabilities, # Check capabilities
3467 'Flush' => \$ipt_flush, # Synonym for --ipt-flush
3468 'ipt-list' => \$ipt_list, # List any existing fwsnort chains.
3469 'List' => \$ipt_list, # Synonym for --ipt-list
3470 'ipt-del' => \$ipt_del_chains, # Delete fwsnort chains.
3471 'ip6tables' => \$enable_ip6tables, # Turn on ip6tables mode.
3472 '6' => \$enable_ip6tables, # Synonym for --ip6tables.
3473 'X' => \$ipt_del_chains, # Synonym for --ipt-del.
3474 'ipt-file=s' => \$ipt_file, # Read iptables policy from a file.
3475 'Home-net=s' => \$home_net, # Manually specify home network.
3476 'External-net=s' => \$ext_net, # Manually specify external network.
3477 'snort-sid=s' => \$include_sids, # Parse only these particular snort rules.
3478 'snort-sids=s' => \$include_sids, # Synonum for --snort-sid
3479 'string-match-alg=s' => \$string_match_alg,
3480 'exclude-sid=s' => \$exclude_sids, # Exclude these particular snort rules.
3481 'snort-conf=s' => \$snort_conf_file, # Get HOME_NET, etc. vars from
3482 # existing Snort config file.
3483 'include-perl-triggers' => \$include_perl_triggers, # perl commands to
3484 # trigger signature matches.
3485 'include-type=s' => \$rules_types, # Process only this type of snort rule
3487 'exclude-type=s' => \$exclude_types,# Exclude specified types (e.g. "ddos").
3488 'include-regex=s' => \$include_re, # Include only those signatures that
3489 # match the specified regex.
3490 'include-re-caseless' => \$include_re_caseless, # make include regex case
3492 'exclude-regex=s' => \$exclude_re, # Exclude those signatures that
3493 # match the specified regex.
3494 'exclude-re-caseless' => \$exclude_re_caseless, # make exclude regex case
3496 'snort-rdir=s' => \$rules_dir, # Manually specify the snort rules
3498 'snort-rfile=s' => \$rules_file, # Translate a single rules file.
3499 'no-pcre' => \$no_pcre, # Make no attempt to translate PCRE's.
3500 'no-addresses' => \$no_addr_check, # Don't check local ifconfig output.
3501 'no-ipt-sync' => \$ignore_opt, # Do not sync with the iptables policy.
3502 'ipt-sync' => \$ipt_sync, # Sync fwsnort rules with the iptables
3504 'no-ipt-log' => \$no_ipt_log, # Do not generate iptables logging rules.
3505 'no-ipt-test' => \$no_ipt_test, # Don't perform any checks for
3506 # iptables capabilities.
3507 'no-ipt-jumps' => \$no_ipt_jumps, # Don't jump packets from the INPUT or
3509 'no-ipt-conntrack' => \$no_ipt_conntrack, # Don't use iptables connection
3510 # tracking (falls back to ACK flag test).
3511 'Conntrack-state=s' => \$conntrack_state, ### Specify conntrack state for
3512 ### 'flow' keyword emulation
3513 ### (default is ESTABLISHED).
3514 'no-ipt-INPUT' => \$no_ipt_input, # Disable fwsnort rules processed via
3516 'no-ipt-OUTPUT' => \$no_ipt_output, # Disable fwsnort rules processed via
3518 'no-ipt-FORWARD' => \$no_ipt_forward, # Disable fwsnort rules processed via
3519 # the FORWARD chain.
3520 'no-ipt-comments' => \$no_ipt_comments, # Don't include msg fields
3521 # with the comment match
3522 'no-ipt-rule-nums' => \$no_ipt_rule_nums, # Exclude rule numbers from
3524 'no-exclude-lo' => \$no_exclude_loopback, # include loopback interface
3525 'no-log-ip-opts' => \$no_ipt_log_ip_opts, # Don't log IP options
3526 'no-log-tcp-opts' => \$no_ipt_log_tcp_opts, # Don't log TCP options
3527 'no-fast-pattern-order' => \$no_fast_pattern_order, ### Don't alter
3528 # pattern match ordering based on
3529 # pattern length, and ignore the
3530 # explicit 'fast_pattern' keyword
3531 'restrict-intf=s' => \$ipt_restrict_intf, # Restrict iptables rules to an
3532 # individual interface (supports a
3533 # comma separate list).
3534 'update-rules' => \$update_rules, # Download latest snort rules.
3535 'rules-url=s' => \$rules_url, # Specify rules URL.
3536 'add-deleted' => \$add_deleted, # Add deleted rules.
3537 'strict' => \$strict, # Strict mode.
3538 'debug' => \$debug, # Debug mode.
3539 'dumper' => \$dumper, # Dumper mode for IPTables::Parse
3541 'Dump-conf' => \$dump_conf, # Display config variables
3542 'Dump-ipt' => \$dump_ipt, # Dump iptables rules on STDOUT.
3543 'Dump-snort' => \$dump_snort, # Dump snort rules on STDOUT.
3544 'config=s' => \$fwsnort_conf, # Manually specify the config file
3545 'Ulog' => \$ulog_mode, # Force ULOG mode.
3546 'ulog-nlgroup=i' => \$ulog_nlgroup, # Specify the ulogd nl group.
3547 'QUEUE' => \$queue_mode, # Specify QUEUE mode; this pulls out
3548 # all kernel-matchable features from
3549 # original Snort rules and creates a
3550 # a modified rule set based on this.
3551 'NFQUEUE' => \$nfqueue_mode, # Same as QUEUE mode, except use the
3552 # updated NFQUEUE target.
3553 'queue-rules-dir=s' => \$queue_rules_dir, # Change the path to the generated
3554 # rules directory in --QUEUE or
3556 'queue-num=i' => \$nfqueue_num, # Specifies the NFQUEUE number.
3557 'queue-pre-match-max=i' => \$queue_pre_match_max, ### max number of patterns
3558 ### to match within the kernel before
3559 ### queuing a packet to userspace
3561 'Home-dir=s' => \$cmdl_homedir,
3562 'Last-cmd' => \$run_last,
3563 'lib-dir=s' => \$lib_dir, # Specify path to lib directory.
3564 'verbose' => \$verbose,
3565 'logfile=s' => \$logfile, # Specify the logfile path.
3566 'stdout' => \$stdout, # Print log messages to stdout.
3567 'Version' => \$print_ver,
3575 &save_args() unless $run_last;
3577 ### Print the version number and exit if -V given on the command line.
3579 print "[+] fwsnort v$version by Michael Rash <mbr\@cipherdyne.org>\n";
3583 if (($queue_mode or $nfqueue_mode) and ($ipt_drop or $ipt_reject)) {
3585 "[*] --NFQUEUE and --QUEUE modes are not compatible with --ipt-drop or\n",
3586 " --ipt-reject; a userland process should set the verdict. If you can\n",
3587 " always use fwsnort with --ipt-drop or --ipt-reject and add an NFQUEUE\n",
3588 " or QUEUE rule manually to a built-in chain. This allows the fwsnort\n",
3589 " policy to DROP or REJECT packets that match signatures before they are\n",
3590 " communicated to userland (hence speeding up Snort_inline).\n";
3593 if ($nfqueue_num != 0) {
3594 unless ($nfqueue_num > 0 and $nfqueue_num < 65536) {
3595 die "[*] --queue-num must be between 0 and 65535 (inclusive)";
3597 unless ($nfqueue_mode) {
3598 die "[*] Must also specifiy --NFQUEUE mode if using --queue-num";
3602 if ($no_ipt_log and not ($ipt_drop or $ipt_reject)) {
3603 die "[*] --ipt-no-log option can only be used ",
3604 "with --ipt-drop or --ipt-reject";
3607 if ($ipt_drop and $ipt_reject) {
3608 die "[*] Cannot specify both --ipt-drop and --ipt-reject";
3614 sub import_snort_conf() {
3615 unless (-e $snort_conf_file) {
3616 die "[*] Snort config file $snort_conf_file does not exist.";
3618 open F, "< $snort_conf_file" or die "[*] Could not open Snort ",
3619 "config $snort_conf_file: $!";
3622 for my $line (@lines) {
3624 next if $line =~ /^\s*#/;
3625 if ($line =~ /^\s*var\s+(\w+)\s+(.*)\s*/) {
3634 my $has_sub_var = 1;
3635 my $resolve_ctr = 0;
3637 while ($has_sub_var) {
3640 if ($resolve_ctr >= 20) {
3641 die "[*] Exceeded maximum variable resolution counter.";
3643 for my $hr (\%config, \%cmds) {
3644 for my $var (keys %$hr) {
3645 my $val = $hr->{$var};
3646 if ($val =~ m|\$(\w+)|) {
3648 die "[*] sub-ver $sub_var not allowed within same ",
3649 "variable $var" if $sub_var eq $var;
3650 if (defined $config{$sub_var}) {
3651 $val =~ s|\$$sub_var|$config{$sub_var}|;
3654 die "[*] sub-var \"$sub_var\" not defined in ",
3655 "config for var: $var.";
3665 sub required_vars() {
3666 my @required_vars = (qw(
3667 HOME_NET EXTERNAL_NET HTTP_SERVERS SMTP_SERVERS DNS_SERVERS
3668 SQL_SERVERS TELNET_SERVERS AIM_SERVERS HTTP_PORTS SHELLCODE_PORTS
3669 SSH_PORTS ORACLE_PORTS WHITELIST BLACKLIST AVG_IP_HEADER_LEN
3670 AVG_TCP_HEADER_LEN MAX_FRAME_LEN FWSNORT_INPUT FWSNORT_INPUT_ESTAB
3671 FWSNORT_OUTPUT FWSNORT_OUTPUT_ESTAB FWSNORT_FORWARD
3672 FWSNORT_FORWARD_ESTAB FWSNORT_INPUT_JUMP FWSNORT_OUTPUT_JUMP
3673 FWSNORT_FORWARD_JUMP MAX_STRING_LEN CONF_DIR RULES_DIR ARCHIVE_DIR
3674 QUEUE_RULES_DIR LOG_DIR LIBS_DIR CONF_FILE FWSNORT_SCRIPT LOG_FILE
3675 FWSNORT_SAVE_FILE FWSNORT_SAVE_EXEC_FILE IPT_BACKUP_SAVE_FILE
3676 UPDATE_RULES_URL STATE_DIR
3678 for my $var (@required_vars) {
3679 die "[*] Variable $var not defined in $fwsnort_conf. Exiting.\n"
3680 unless defined $config{$var};
3685 sub ipt_capabilities() {
3687 print "[+] Testing $ipt_bin for supported capabilities...\n";
3689 my $test_rule_rv = -1;
3691 ### create test chain
3692 &create_test_chain();
3694 ### test for the LOG target.
3695 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3696 "$non_host -j LOG") == $IPT_SUCCESS) {
3697 print "[+] $ipt_str has 'LOG' target support...\n"
3698 if $verbose or $ipt_check_capabilities;
3700 ### check for the max --log-prefix string length
3701 &ipt_find_max_log_prefix_len();
3704 &delete_test_chain();
3705 die "[*] $ipt_str has not been compiled with logging support. ",
3706 "If you want to\n have fwsnort generate an $ipt_str script ",
3707 " anyway then specify the\n --no-ipt-test option. ",
3712 ### test for the comment match (where Snort msg fields are placed)
3713 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3714 qq|$non_host -m comment --comment "testing the comment match" | .
3715 qq|-j LOG|) == $IPT_SUCCESS) {
3716 print "[+] $ipt_str has 'comment' match support...\n"
3717 if $verbose or $ipt_check_capabilities;
3719 ### now find the maximum comment length that is supported by iptables
3720 &ipt_find_max_comment_len();
3723 unless ($no_ipt_comments) {
3724 print"[-] It looks like the $ipt_str 'comment' match is not ",
3725 "available, disabling.\n";
3726 $no_ipt_comments = 1;
3730 ### test for the ipv4options extension.
3731 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp -m " .
3732 "ipv4options --rr -s $non_host -j LOG") == $IPT_SUCCESS) {
3733 print "[+] $ipt_str has the 'ipv4options' extension...\n"
3734 if $verbose or $ipt_check_capabilities;
3737 &logr("[-] $ipt_str ipv4options extension not available, " .
3738 "disabling ipopts translation.");
3739 ### put ipopts in the unsupported list
3740 if (defined $snort_opts{'filter'}{'ipopts'}) {
3741 $snort_opts{'unsupported'}{'ipopts'} =
3742 $snort_opts{'filter'}{'ipopts'}{'regex'};
3743 delete $snort_opts{'filter'}{'ipopts'};
3745 $snort_opts{'unsupported'}{'ipopts'} = '[\s;]ipopts:\s*(\w+)\s*;';
3747 print "[-] $ipt_str does not have the 'ipv4options' extension, " .
3748 "disabling...\n" if $verbose or $ipt_check_capabilities;
3751 ### test for the ttl match.
3752 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " .
3753 "-s $non_host -m ttl --ttl-eq 1 -j LOG") == $IPT_SUCCESS) {
3754 print "[+] $ipt_str has the 'ttl' match...\n"
3755 if $verbose or $ipt_check_capabilities;
3757 ### put ttl in the unsupported list
3758 &logr("[-] $ipt_str TTL match not available, " .
3759 "disabling ttl translation.");
3760 if (defined $snort_opts{'filter'}{'ttl'}) {
3761 $snort_opts{'unsupported'}{'ttl'} =
3762 $snort_opts{'filter'}{'ttl'}{'regex'};
3763 delete $snort_opts{'filter'}{'ttl'};
3765 $snort_opts{'unsupported'}{'ttl'} = '[\s;]ttl:\s*(.*?)\s*;';
3767 print "[+] $ipt_str does not have the 'ttl' match, " .
3768 "disabling...\n" if $verbose or $ipt_check_capabilities;
3771 ### test for the TOS match.
3772 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " .
3773 "-s $non_host -m tos --tos 8 -j LOG") == $IPT_SUCCESS) {
3774 print "[+] $ipt_str has the 'tos' match...\n"
3775 if $verbose or $ipt_check_capabilities;
3777 ### put tos in the unsupported list
3778 &logr("[-] $ipt_str TOS match not available, " .
3779 "disabling tos translation.");
3780 if (defined $snort_opts{'filter'}{'tos'}) {
3781 $snort_opts{'unsupported'}{'tos'} =
3782 $snort_opts{'filter'}{'tos'}{'regex'};
3783 delete $snort_opts{'filter'}{'tos'};
3785 $snort_opts{'unsupported'}{'tos'} = '[\s;]tos:\s*(.*?)\s*;';
3787 print "[+] $ipt_str does not have the 'tos' match, " .
3788 "disabling...\n" if $verbose or $ipt_check_capabilities;
3791 ### test for the length match.
3792 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " .
3793 "-s $non_host -m length --length 256 -j LOG") == $IPT_SUCCESS) {
3794 print "[+] $ipt_str has the 'length' match...\n"
3795 if $verbose or $ipt_check_capabilities;
3797 ### put length in the unsupported list
3798 &logr("[-] $ipt_str length match not available, " .
3799 "disabling length translation.");
3800 if (defined $snort_opts{'filter'}{'dsize'}) {
3801 $snort_opts{'unsupported'}{'dsize'} =
3802 $snort_opts{'filter'}{'dsize'}{'regex'};
3803 delete $snort_opts{'filter'}{'dsize'};
3805 $snort_opts{'unsupported'}{'dsize'} = '[\s;]dsize:\s*(.*?)\s*;';
3807 print "[+] $ipt_str does not have the 'length' match, " .
3808 "disabling...\n" if $verbose or $ipt_check_capabilities;
3811 ### test for the multiport match.
3812 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p tcp " .
3813 "-s $non_host -m multiport --dports 53,123:500 -j LOG") == $IPT_SUCCESS) {
3814 print "[+] $ipt_str has the 'multiport' match...\n"
3815 if $verbose or $ipt_check_capabilities;
3816 $ipt_have_multiport_match = 1;
3818 ### find the maximum number of supported ports (usually 15)
3819 &ipt_find_max_multiport_supported_ports();
3822 print "[-] $ipt_str does not have the 'multiport' match...\n"
3823 if $verbose or $ipt_check_capabilities;
3824 &logr("[-] $ipt_str multiport match not available");
3827 ### test for string match support.
3828 my $ipt_str_test = my $ipt_str_test_base =
3829 "-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3830 qq|$non_host -m string --string "test" |;
3832 if ($kernel_ver ne '2.4') {
3833 ### default to include "--algo bm"
3834 $ipt_str_test .= qq|--algo $string_match_alg -j LOG|;
3836 $ipt_str_test .= qq|-j LOG|;
3839 $test_rule_rv = &ipt_rule_test($ipt_str_test);
3841 if ($test_rule_rv == $IPT_SUCCESS) {
3843 print "[+] $ipt_str has the 'string' match...\n"
3844 if $verbose or $ipt_check_capabilities;
3846 ### now find the maximum string length that is supported by iptables
3847 &ipt_find_max_string_len();
3849 ### test for case insensitive string matching
3850 $ipt_str_test = $ipt_str_test_base;
3852 if ($kernel_ver ne '2.4') {
3853 $ipt_str_test .= qq|--algo $string_match_alg --icase -j LOG|;
3855 $ipt_str_test .= qq|--icase -j LOG|;
3858 $test_rule_rv = &ipt_rule_test($ipt_str_test);
3860 unless ($test_rule_rv == $IPT_SUCCESS) {
3861 $snort_opts{'ignore'}{'nocase'}
3862 = $snort_opts{'filter'}{'nocase'}{'regex'};
3863 delete $snort_opts{'filter'}{'fast_pattern'};
3866 ### test for --replace-string support (only available for 2.4 kernels
3867 ### if the replace-string patch has been applied).
3868 if ($kernel_ver eq '2.4') {
3869 unless (&ipt_rule_test($ipt_str_test_base .
3870 qq|--replace-string "repl" -j LOG|) == $IPT_SUCCESS) {
3871 if (defined $snort_opts{'filter'}{'replace'}) {
3872 $snort_opts{'unsupported'}{'replace'} =
3873 $snort_opts{'filter'}{'replace'}{'regex'};
3874 delete $snort_opts{'filter'}{'replace'};
3876 $snort_opts{'unsupported'}{'replace'}
3877 = '[\s;]replace:\s*(.*?)\s*;';
3881 $snort_opts{'unsupported'}{'replace'}
3882 = '[\s;]replace:\s*(.*?)\s*;';
3885 ### test to see whether '--icmp-type any' is supported
3886 $ipt_str_test = $ipt_str_test_base;
3887 if ($kernel_ver ne '2.4') {
3888 $ipt_str_test .= qq|--algo $string_match_alg -p icmp -m icmp --icmp-type any -j LOG|;
3890 $ipt_str_test .= qq|-p icmp -m icmp --icmp-type any -j LOG|;
3893 $test_rule_rv = &ipt_rule_test($ipt_str_test);
3894 if ($test_rule_rv == $IPT_SUCCESS) {
3895 $default_icmp_type = 'any';
3899 &delete_test_chain();
3901 "[*] It does not appear that string match support has been compiled into\n",
3902 " the kernel. Fwsnort will not be of very much use without this.\n",
3903 " ** NOTE: If you want to have fwsnort generate an $ipt_str policy\n",
3904 " anyway, use the --no-ipt-test option. Exiting.\n";
3907 ### test for --hex-string
3908 if ($kernel_ver ne '2.4') {
3909 $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " .
3911 qq{-m string --hex-string "|0a 5d|" --algo $string_match_alg -j LOG});
3913 $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " .
3915 qq{-m string --hex-string "|0a 5d|" -j LOG});
3918 if ($test_rule_rv == $IPT_SUCCESS) {
3919 print "[+] $ipt_str has --hex-string support...\n"
3920 if $verbose or $ipt_check_capabilities;
3922 &delete_test_chain();
3924 "[*] It does not appear that the --hex-string patch has been applied.\n",
3925 " fwsnort will not be of very much use without this. ** NOTE: If you\n",
3926 " want to have fwsnort generate an $ipt_str policy anyway, then\n",
3927 " use the --no-ipt-test option. Exiting.\n";
3930 ### test for the --payload option
3931 if ($kernel_ver ne '2.4'
3932 and &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " .
3933 "-s $non_host -m string --string " .
3934 qq|"test" --algo $string_match_alg --to 50 --payload -j LOG|) == $IPT_SUCCESS) {
3935 $ipt_has_string_payload_offset_opt = 1;
3938 if ($queue_mode or $nfqueue_mode) {
3939 ### test for the QUEUE or NFQUEUE target
3940 if ($nfqueue_mode) {
3941 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3942 "$non_host -p tcp --dport 3001 -j NFQUEUE")
3944 print "[+] $ipt_str has NFQUEUE support....\n"
3945 if $verbose or $ipt_check_capabilities;
3947 die "[*] The NFQUEUE target does not appear to be available ",
3951 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3952 "$non_host -p tcp --dport 3001 -j QUEUE")
3954 print "[+] $ipt_str has QUEUE support....\n"
3955 if $verbose or $ipt_check_capabilities;
3957 die "[*] The QUEUE target does not appear to be available ",
3963 unless ($no_ipt_conntrack) {
3965 ### test for connection tracking support (conntrack
3966 ### match first then state match if not available)
3967 if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " .
3968 "$non_host -p tcp --dport 3001 -m conntrack " .
3969 "--ctstate ESTABLISHED -j LOG")
3971 print "[+] $ipt_str has conntrack state tracking support...\n"