fwsnort-nodeps-1.6.7/0000775000175000017500000000000013103435666012650 5ustar mbrmbrfwsnort-nodeps-1.6.7/fwsnort.80000664000175000017500000004574213103435515014450 0ustar mbrmbr.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH FWSNORT 8 "Jan, 2011" Linux .SH NAME .B fwsnort \- Firewall Snort .SH SYNOPSIS .B fwsnort [options] .SH DESCRIPTION .B fwsnort translates SNORT rules into iptables rules on Linux systems and generates a corresponding iptables policy in iptables-save format. This ruleset allows network traffic that matches Snort signatures (i.e. attacks and other suspicious network behavior) to be logged and/or dropped by iptables directly without putting an interface into promiscuous mode or queuing packets from kernel to user space. Note that fwsnort can also build an iptables policy that combines the string match extension with the NFQUEUE or QUEUE targets to allow the kernel to perform preliminary string matches that are defined within Snort rules before queuing matching packets to a userspace snort_inline instance. Because the bulk of network communications are not generally malicious, this should provide a speedup for snort_inline since the majority of packets do not then have to be copied from kernel memory into user memory and subsequently inspected by snort_inline. There is a tradeoff here in terms of signature detection however because snort_inline when deployed in this way does not have the opportunity to see all packets associated with a session, so stream reassembly and signature comparisons against a reassembled buffer do not take place (the stream preprocessor should be disabled in the userspace snort_inline instance). As of .B fwsnort-1.5 all iptables rules built by fwsnort are written out to the .I /var/lib/fwsnort/fwsnort.save file in iptables-save format. This allows a long fwsnort policy (which may contain thousands of iptables rules translated from a large Snort signature set) to be quickly instantiated via the "iptables-restore" command. A wrapper script .I /var/lib/fwsnort/fwsnort.sh is also written out to make this easy. Hence, the typical work flow for fwsnort is to: 1) run fwsnort, 2) note the Snort rules that fwsnort was able to successfully translate (the number of such rules is printed to stdout), and then 3) execute the .I /var/lib/fwsnort/fwsnort.sh wrapper script to instantiate the policy in the running kernel. .B fwsnort (optionally) uses the IPTables::Parse CPAN module to parse the iptables ruleset on the machine to determine which Snort rules are applicable to the specific iptables policy. After all, if iptables is blocking all inbound http traffic from external addresses for example, it is probably not of much use to try detecting inbound attacks against against tcp/80. By default fwsnort generates iptables rules that log Snort sid's within a \-\-log-prefix to syslog where the messages can be analyzed with a log analyzer such as .B psad (see http://www.cipherdyne.org/psad/). .B fwsnort relies on the iptables string match module to match Snort content fields in the application portion of ip traffic. Since Snort rules can contain hex data in content fields (specified between pipe "|" characters), fwsnort implements a patch against iptables (which has been accepted by the Netfilter project as of iptables-1.2.7a) which adds a "\-\-hex-string" option. This allow iptables to accept content fields from Snort rules such as "|0d0a5b52504c5d3030320d0a|" without any modification. .B fwsnort is able to translate approximately 60% of all rules from the Snort-2.3.3 IDS into equivalent iptables rules. For more information about the translation strategy as well as advantages/disadvantages of the method used by fwsnort to obtain intrusion detection data, see the README included with the fwsnort sources or browse to: http://www.cipherdyne.org/fwsnort/ .B fwsnort is able to apply Snort rules to IPv6 traffic by building an ip6tables policy (see the "\-\-ip6tables" command line argument). .SH OPTIONS .TP .BR \-c ", " \-\^\-config\ \ By default fwsnort makes use of the configuration file .B /etc/fwsnort/fwsnort.conf for almost all configuration parameters. fwsnort can be made to override this path by specifying a different file on the command line with the \-\-config option. When fwsnort is not executed as root, then a path to a readable fwsnort.conf file is required. .TP .BR \-\^\-update-rules Download the latest Emerging Threats rules from http://www.emergingthreats.net This will overwrite the emerging-all.rules file in the /etc/fwsnort/snort_rules/ directory. Note that the automatic downloading of Snort rules from http://www.snort.org/ as of March, 2005 is only offered as a pay service. .TP .BR \-\^\-rules-url\ \ Specify the URL to use when updating the Emerging Threats rule set (or any other rule set). The default URL is: http://rules.emergingthreats.net/open/snort-2.9.0/emerging-all.rules .TP .BR \-6 ", " \-\^\-ip6tables Enable .B ip6tables mode so that the fwsnort rule set is built into an ip6tables policy instead of the iptables policy. This allows fwsnort controls to apply to IPv6 traffic. .TP .BR \-\^\-include-type\ \ Restrict to processing snort rules of . Example rule types would include "ddos", "backdoor", and "web-attacks". This option also supports a comma-separated list of types, e.g. "ddos,backdoor". .TP .BR \-\^\-exclude-type\ \ Exclude all Snort rules from of type from the translation process. For example, if you don't want any rules from the file emerging-all.rules to be translated, then use "emerging-all" as the argument to this option. A comma-separated list of types to exclude can be specified. .TP .BR \-\^\-include-regex\ \ Only translate Snort rules that match the specified regular expression. This is useful to build .B fwsnort policies for Snort rules that have a common characteristic (such as a string match on the word "Storm" for the Storm worm for example). .TP .BR \-\^\-exclude-regex\ \ Translate all Snort rules except those that match the specified regular expression. This is useful to omit Snort rules from .B fwsnort policies that have a common characteristic (such as a string match on "HTTP_PORTS" for example). .TP .BR \-\^\-include-re-caseless Make the rule matchine regular expression specified with .I \-\-include\-regex match case insensitively. .TP .BR \-\^\-exclude-re-caseless Make the rule matchine regular expression specified with .I \-\-exclude\-regex match case insensitively. .TP .BR \-\^\-snort-rdir\ Manually specify the directory where the snort rules files are located. The default is .B /etc/fwsnort/snort_rules. Multiple directories are supported as a comma-separated list. .TP .BR \-\^\-snort-rfile\ Manually specify a Snort rules file to translated into iptables rules. Multiple files are also supported as a comma-separated list. .TP .BR \-\^\-snort-sid\ \ Generate an iptables ruleset for a single snort rule specified by . A comma-separated list of sids can be specified, e.g. "2001842,1834". .TP .BR \-\^\-exclude-sid\ \ Provide a list of Snort ID's to be excluded from the translation process. .TP .BR \-\^\-include-perl-triggers Include .I 'perl -e "print ..."' commands as comments in the .I fwsnort.sh script. These commands allow payloads that are designed to trigger snort rules to easily be built, and when combined with netcat (or other software that can send bytes over the wire) it becomes possible to test whether an fwsnort policy appropriately triggers on matching traffic. .TP .BR \-\^\-ipt-script\ \ Specify the path to the iptables script generated by fwsnort. The default location is /var/lib/fwsnort/fwsnort.sh. .TP .BR \-\^\-ipt-check-capabilities Check iptables capabilities and exit. .TP .BR \-\^\-Last\-cmd Run .B fwsnort with the same command line arguments as the previous execution. This is a convenient way of rebuilding the .I /var/lib/fwsnort/fwsnort.sh script without having to remember what the last command line args were. .TP .BR \-\^\-NFQUEUE Build an .B fwsnort policy that sends packets that match Snort .B content or .B uricontent fields to userspace via the iptables NFQUEUE target for further analysis. This is a mechanism for reducing the signature inspection load placed on snort_inline. A parallel set of Snort rules that are successfully translated are placed in the /etc/fwsnort/snort_rules_queue directory. This requires CONFIG_NETFILTER_XT_TARGET_NFQUEUE support in the Linux kernel. .TP .BR \-\^\-QUEUE Same as the .B --NFQUEUE command line argument except that the older QUEUE target is used instead of the NFQUEUE target. This requires CONFIG_IP_NF_QUEUE support in the Linux kernel. .TP .BR \-\^\-queue-num\ \ Specify a queue number in \-\-NFQUEUE mode. .TP .BR \-\^\-queue-pre-match-max\ \ In \-\-QUEUE or \-\-NFQUEUE mode, limit the number of content matches that are performed within the kernel before sending a matching packet to a userspace Snort instance. This allows a level of tuning with respect to how much work the kernel does to qualify a packet based on a signature match before having Snort do the same thing. The default is to perform all specified content matches in the signature before queuing the packet to userspace because the multiple in-kernel content matches is probably less expensive than sending a packet to userspace by default. .TP .BR \-\^\-string-match-alg\ \ Specify the string matching algorithm to use with the kernel. By default, this is 'bm' for the 'Boyer-Moore' string matching algorithm, but 'kmp' may also be specified (short for the 'Knuth–Morris–Pratt' algorithm). .TP .BR \-\^\-ipt-apply Execute the iptables script generated by fwsnort. .TP .BR \-\^\-ipt-exec Synonym for \-\-ipt-apply. .TP .BR \-\^\-ipt-revert Revert to a version of the iptables policy without any .B fwsnort rules. Note that this reverts to the iptables policy as it was when .B fwsnort was originally executed. So, it is not recommended to use this option if there is a large amount of time between when fwsnort is run to translate Snort rules vs. running it with this option. For most purposes it is better to use the \-\-ipt-flush option below. .TP .BR \-\^\-ipt-flush Flush all .B fwsnort currently active iptables rules (flushes the fwsnort chains). .TP .BR \-\^\-ipt-list List all .B fwsnort currently active iptables rules (lists the fwsnort chains). .TP .BR \-\^\-ipt-drop For each logging rule generated by .B fwsnort add a corresponding DROP rule. Note that for TCP sessions using this option will cause retransmissions as packets that are part of established sessions selectively dropped. Remember that false positives are common occurrences for intrusion detection systems, and so using this or the \-\-ipt-reject option may break things on your network! You have been warned. .TP .BR \-\^\-ipt-reject For each logging rule generated by .B fwsnort add a corresponding REJECT rule. Reset packets will be generated for TCP sessions through the use of the "\-\-reject-with tcp-reset" option, and ICMP port unreachable messages will be generated for UDP packets through the use of the "\-\-reject-with icmp-port-unreachable" option. .TP .BR \-C ", " \-\^\-Conntrack-state\ \ Specify a conntrack state in place of the "established" state that commonly accompanies the Snort "flow" keyword. By default, fwsnort uses the conntrack state of "ESTABLISHED" for this. In certain corner cases, it might be useful to use "ESTABLISHED,RELATED" instead to apply application layer inspection to things like ICMP port unreachable messages that are responses to real attempted communications. .TP .BR \-\^\-no-ipt-log By default fwsnort generates an iptables script that implements a logging rule for each successfully translated snort rule. This can be disabled with the \-\-no-ipt-log option, but \-\-ipt-drop must also be specified. .TP .BR \-\^\-no-ipt-sync This is a deprecated option since the default behavior is to translate as many Snort rules into iptables rules as possible. With .B fwsnort able to produce iptables rules in iptables\-save format, it is extremely fast to instantiate a large set of translated Snort rules into an iptables policy. A new \-\-ipt-sync option has been added to reverse this behavior (not recommended). .TP .BR \-\^\-ipt-sync Consult the iptables policy currently running on the machine for applicable snort rules. .TP .BR \-\^\-no-ipt-test Do not test the iptables build for existence of support for the LOG and REJECT targets, and ascii and hex string matching. .TP .BR \-\^\-no-ipt-jumps Do not jump packets from the built-in iptables INPUT, OUTPUT, and FORWARD chains to the custom .B fwsnort chains. This options is mostly useful to make it easy to manually alter the placement of the jump rules in the iptables ruleset. .TP .BR \-\^\-no-ipt-rule-nums By default .B fwsnort includes the rule number within the logging prefix for each of the rules it adds to the fwsnort chains. E.g. the logging prefix for rule 34 would look something like "[34] SID1242 ESTAB". Use this option to not include the rule number. .TP .BR \-\^\-no-ipt-comments If the iptables "comment" match exists, then .B fwsnort puts the Snort "msg", "classtype", "reference", "priority", and "rev" fields within a comment for each iptables rule. Use this option to disable this. .TP .BR \-\^\-no-ipt-INPUT Do not jump packets from the iptables INPUT chain to the .B fwsnort chains. .TP .BR \-\^\-no-ipt-OUTPUT Do not jump packets from the iptables OUTPUT chain to the .B fwsnort chains. .TP .BR \-\^\-no-ipt-FORWARD Do not jump packets from the iptables FORWARD chain to the .B fwsnort chains. .TP .BR \-\^\-no-fast-pattern-ordering Cause .B fwsnort to not try to reorder pattern matches to process the longest pattern first. The Snort .I fast_pattern keyword is also ignored if this option is specified. .TP .BR \-H ", " \-\^\-Home-net\ \ Specify the internal network instead of having .B fwsnort derive it from the HOME_NET keyword in the fwsnort.conf configuration file. .TP .BR \-E ", " \-\^\-External-net\ \ Specify the external network instead of having .B fwsnort derive it from the EXTERNAL_NET keyword in the fwsnort.conf configuration file. .TP .BR \-\^\-no-addresses Disable all checks against the output of ifconfig for proper IP addresses. This is useful if .B fwsnort is running on a bridging firewall. .TP .BR \-\^\-Dump-conf Print the fwsnort configuration on STDOUT and exit. .TP .BR \-\^\-debug Run in debug mode. This will cause all parse errors which are normally written to the fwsnort logfile .B /var/log/fwsnort.log to be written to STDOUT instead. .TP .BR \-\^\-strict Run fwsnort in "strict" mode. This will prevent fwsnort from translating snort rules that contain the keywords "offset", "uricontent", and "depth". .TP .BR \-U ", " \-\^\-Ulog Force the usage of the ULOG target for all log messages instead of the default LOG target. .TP .BR \-\^\-ulog-nlgroup Specify the netlink group for ULOG rules. Such rules are only added for Snort rules that have an action of "log", or when .B fwsnort is run in .B --Ulog mode. .TP .BR \-l ", " \-\^\-logfile\ By default fwsnort logs all parse errors to the logfile .B /var/log/fwsnort.log. This path can be manually changed with the \-\-logfile option. .TP .BR \-v ", " \-\^\-verbose Run fwsnort in verbose mode. This will cause fwsnort to add the original snort rule as a comment to the fwsnort.sh script for each successfully translated rule. .TP .BR \-V ", " \-\^\-Version Print the fwsnort version and exit. .TP .BR \-h ", " \-\^\-help Print usage information on STDOUT and exit. .SH FILES .B /etc/fwnort/fwsnort.conf .RS The fwsnort configuration file. The path to this file can be changed on the command line with \-\-config. .RE .B /var/lib/fwnort/fwsnort.sh .RS The iptables script generated by fwsnort. The path can be manually specified on the command line with the \-\-ipt-script option. .SH FWSNORT CONFIGURATION VARIABLES This section describes what each of the more important fwsnort configuration variables do and how they can be tuned to meet your needs. These variables are located in the fwsnort configuration file .B /etc/fwsnort/fwsnort.conf .TP .BR HOME_NET .B fwsnort uses the same HOME_NET and EXTERNAL_NET variables as defined in Snort rules, and the same semantics are supported. I.e., individual IP addresses or networks in standard dotted-quad or CIDR notation can be specified, and comma separated lists are also supported. .TP .BR EXTERNAL_NET Defines the external network. See the HOME_NET variable for more information. .SH EXAMPLES The following examples illustrate the command line arguments that could be supplied to fwsnort in a few situations: .PP Script generation in logging mode, parse errors written to the fwsnort logfile, and iptables policy checking are enabled by default without having to specify any command line arguments: .PP .B # fwsnort .PP Generate ip6tables rules for attacks delivered over IPv6: .PP .B # fwsnort -6 .PP Generate iptables rules for ddos and backdoor Snort rules only: .PP .B # fwsnort --include-type ddos,backdoor .PP Generate iptables rules for Snort ID's 2008475 and 2003268 (from emerging-all.rules): .PP .B fwsnort --snort-sid 2008475,2003268 .PP Generate iptables rules for Snort ID's 1834 and 2001842 but queue them to userspace via the NFQUEUE target and restrict exclude the INPUT and OUTPUT chains: .PP .B fwsnort --snort-sid 1834,2001842 --NFQUEUE --no-ipt-INPUT --no-ipt-OUTPUT .PP Instruct .B fwsnort to only inspect traffic that traverses the eth0 and eth1 interfaces: .PP .B # fwsnort --restrict-intf eth0,eth1 .PP Generate iptables rules for Snort rules that appear to be allowed by the local iptables policy, and write original snort rules to the iptables script as a comment: .PP .B # fwsnort --ipt-sync --verbose .SH DEPENDENCIES .B fwsnort requires that the iptables string match module be compiled into the kernel (or as a loadable kernel module) in order to be able to match snort signatures that make use of the "content" keyword. Note that the \-\-no-opt-test option can be specified to have fwsnort generate an iptables script even if the string match module is not compiled in. .PP .B fwsnort also requires the IPTables::Parse CPAN module in order to parse iptables policies. This module is bundled with the fwsnort sources in the deps/ directory for convenience. .SH DIAGNOSTICS The \-\-debug option can be used to display on STDOUT any errors that are generated as fwsnort parses each snort rule. Normally these errors are written to the fwsnort logfile /var/log/fwsnort.log .SH "SEE ALSO" .BR psad (8), .BR iptables (8), .BR snort (8), .BR nmap (1) .SH AUTHOR Michael Rash .SH CONTRIBUTORS Many people who are active in the open source community have contributed to fwsnort; see the .B CREDITS file in the fwsnort sources, or visit .B http://www.cipherdyne.org/fwsnort/docs/contributors.html to view the online list of contributors. .B fwsnort is based on the original .B snort2iptables script written by William Stearns. .SH BUGS Send bug reports to mbr@cipherdyne.org. Suggestions and/or comments are always welcome as well. .SH DISTRIBUTION .B fwsnort is distributed under the GNU General Public License (GPLv2), and the latest version may be downloaded from .B http://www.cipherdyne.org/ Snort is a registered trademark of Sourcefire, Inc. fwsnort-nodeps-1.6.7/test/0000775000175000017500000000000013103435515013620 5ustar mbrmbrfwsnort-nodeps-1.6.7/test/test-fwsnort.pl0000775000175000017500000007215513103435515016651 0ustar mbrmbr#!/usr/bin/perl -w use Cwd; use File::Copy; use File::Path; use Getopt::Long 'GetOptions'; use strict; #==================== config ===================== my $logfile = 'test.log'; my $output_dir = 'output'; my $conf_dir = 'conf'; my $run_dir = 'run'; my $test_install_dir = 'fwsnort-install'; my $fwsnortCmd = "$test_install_dir/usr/sbin/fwsnort"; my $fwsnort_sh = "$test_install_dir/var/lib/fwsnort/fwsnort.sh"; my $cmd_out_tmp = 'cmd.out'; my $default_conf = "$conf_dir/default_fwsnort.conf"; ### alert tcp $EXTERNAL_NET any -> $HOME_NET 7597 (msg:"BACKDOOR QAZ Worm Client Login access"; \ ### flow:to_server,established; content:"qazwsx.hsq"; reference:MCAFEE,98775; \ ### classtype:misc-activity; sid:108; rev:6;) my $simple_sig_id = 108; #================== end config =================== my $current_test_file = "$output_dir/init"; my $YES = 1; my $NO = 0; my $IGNORE = 2; my $passed = 0; my $failed = 0; my $executed = 0; my $test_include = ''; my @tests_to_include = (); my $test_exclude = ''; my @tests_to_exclude = (); my $list_mode = 0; my $diff_mode = 0; my $fw_exec = 0; my $saved_last_results = 0; my $test_system_install = 0; my $PRINT_LEN = 68; my $REQUIRED = 1; my $OPTIONAL = 0; my $MATCH_ALL_RE = 1; my $MATCH_SINGLE_RE = 2; my $help = 0; my %test_keys = ( 'category' => $REQUIRED, 'subcategory' => $OPTIONAL, 'detail' => $REQUIRED, 'function' => $REQUIRED, 'cmdline' => $OPTIONAL, 'fatal' => $OPTIONAL, 'exec_err' => $OPTIONAL, 'match_all' => $OPTIONAL, 'fw_exec' => $OPTIONAL, 'postive_output_matches' => $OPTIONAL, 'negative_output_matches' => $OPTIONAL, ); my @args_cp = @ARGV; exit 1 unless GetOptions( 'fwsnort-path=s' => \$fwsnortCmd, 'test-include=s' => \$test_include, 'include=s' => \$test_include, ### synonym 'test-exclude=s' => \$test_exclude, 'exclude=s' => \$test_exclude, ### synonym 'test-system-install' => \$test_system_install, 'List-mode' => \$list_mode, 'diff' => \$diff_mode, 'enable-fw-exec' => \$fw_exec, 'help' => \$help ); &usage() if $help; ### define all tests my @tests = ( { 'category' => 'install', 'detail' => "test directory: $test_install_dir", 'err_msg' => 'could not install', 'function' => \&install_test_dir, 'cmdline' => "./install.pl --install-test-dir", 'exec_err' => $NO, 'fatal' => $YES }, { 'category' => 'compilation', 'detail' => 'fwsnort compiles', 'err_msg' => 'could not compile', 'function' => \&generic_exec, 'cmdline' => "perl -c $fwsnortCmd", 'exec_err' => $NO, 'fatal' => $YES }, { 'category' => 'operations', 'detail' => '--help', 'err_msg' => 'could not get --help output', 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd -h -c $default_conf", 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => '--dump-conf', 'err_msg' => 'could not get --Dump-conf output', 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --Dump-conf -c $default_conf --no-ipt-test", 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => '--ipt-list', 'err_msg' => 'could not get --ipt-list output', 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ipt-list -c $default_conf --no-ipt-test", 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--ipt-check-capabilities", 'err_msg' => "could not check iptables capabilities", 'positive_output_matches' => [ qr/iptables\shas/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ipt-check-capabilities --verbose -c $default_conf", 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--snort-sid $simple_sig_id EXTERNAL->HOME", 'err_msg' => "did not translate sid: $simple_sig_id", 'positive_output_matches' => [qr/Found\ssid\:\s$simple_sig_id/, qr/Successful\stranslation/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --snort-sid $simple_sig_id", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--snort-sid 1292 HOME->EXTERNAL", 'err_msg' => "did not translate sid: 1292", 'positive_output_matches' => [qr/Found\ssid\:\s1292/, qr/Successful\stranslation/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --snort-sid 1292", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "multiple rules --snort-sid $simple_sig_id,109,321", 'err_msg' => "did not translate sid: $simple_sig_id", 'positive_output_matches' => [qr/Found\ssid/, qr/Found\ssid\:\s109/, qr/Found\ssid\:\s321/, qr/Successful\stranslation/, ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --snort-sid $simple_sig_id,109,321", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--snort-sid badsid", 'err_msg' => 'translated badsid signature', 'positive_output_matches' => [ qr/No\sSnort\srules\scould\sbe\stranslated/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --snort-sid badsid", 'exec_err' => $YES, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--include-type backdoor", 'err_msg' => "did not translate backdoor signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/Generated\siptables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --include-type backdoor", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--strict --include-type backdoor", 'err_msg' => "did not translate backdoor signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/Generated\siptables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --strict --include-type backdoor", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--include-type emerging-all", 'err_msg' => "did not translate emerging-all signatures", 'positive_output_matches' => [ qr/emerging-all\.rules/, qr/Generated\siptables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --include-type emerging-all", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "multiple files --include-type backdoor,dns,ftp", 'err_msg' => "did not translate backdoor,dns,ftp signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/dns\.rules/, qr/ftp\.rules/, qr/Generated\siptables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --include-type backdoor,dns,ftp", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--exclude-type emerging-all", 'err_msg' => "did not translate signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/dns\.rules/, qr/ftp\.rules/, qr/Generated\siptables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --exclude-type emerging-all", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "multiple --exclude-type emerging-all,backdoor,dns,ftp", 'err_msg' => "did not translate signatures", 'positive_output_matches' => [ qr/chat\.rules/, qr/ddos\.rules/, qr/Generated\siptables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --exclude-type emerging-all,backdoor,dns,ftp", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--include-type backdoor,dns,ftp --exclude-type dns", 'err_msg' => "did not translate backdoor,ftp signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/ftp\.rules/, qr/Generated\siptables\srules\sfor/ ], 'negative_output_matches' => [ qr/dns\.rules/, ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --include-type backdoor,dns,ftp --exclude-type dns", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--snort-sid $simple_sig_id,109,321 --exclude-regex sid\:109", 'err_msg' => "did not translate sid: $simple_sig_id", 'positive_output_matches' => [qr/Found\ssid/, qr/Found\ssid\:\s321/, qr/Successful\stranslation/, ], 'negative_output_matches' => [ qr/Found\ssid\:\s109/, ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --snort-sid $simple_sig_id,109,321 --exclude-regex sid\:109", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "--snort-sid $simple_sig_id,109,321 --include-regex sid\:109", 'err_msg' => "did not translate sid: $simple_sig_id", 'positive_output_matches' => [qr/Found\ssid/, qr/Found\ssid\:\s109/, qr/Successful\stranslation/, ], 'negative_output_matches' => [ qr/Found\ssid\:\s321/, ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --no-ipt-test -c $default_conf --snort-sid $simple_sig_id,109,321 --include-regex sid\:109", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, ### ip6tables testing { 'category' => 'operations', 'detail' => "ip6tables --snort-sid $simple_sig_id", 'err_msg' => "did not translate sid: $simple_sig_id", 'positive_output_matches' => [qr/Found\ssid\:\s$simple_sig_id/, qr/Successful\stranslation/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --snort-sid $simple_sig_id", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --snort-sid $simple_sig_id,109,321", 'err_msg' => "did not translate sid: $simple_sig_id", 'positive_output_matches' => [qr/Found\ssid/, qr/Found\ssid\:\s109/, qr/Found\ssid\:\s321/, qr/Successful\stranslation/, ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --snort-sid $simple_sig_id,109,321", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --snort-sid badsid", 'err_msg' => 'translated badsid signature', 'positive_output_matches' => [ qr/No\sSnort\srules\scould\sbe\stranslated/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --snort-sid badsid", 'exec_err' => $YES, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --include-type backdoor", 'err_msg' => "did not translate backdoor signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/Generated\sip6tables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --include-type backdoor", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --strict --include-type backdoor", 'err_msg' => "did not translate backdoor signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/Generated\sip6tables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --strict --include-type backdoor", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --include-type emerging-all", 'err_msg' => "did not translate emerging-all signatures", 'positive_output_matches' => [ qr/emerging-all\.rules/, qr/Generated\sip6tables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --include-type emerging-all", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --include-type backdoor,dns,ftp", 'err_msg' => "did not translate backdoor,dns,ftp signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/dns\.rules/, qr/ftp\.rules/, qr/Generated\sip6tables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --include-type backdoor,dns,ftp", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --exclude-type emerging-all", 'err_msg' => "did not translate signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/dns\.rules/, qr/ftp\.rules/, qr/Generated\sip6tables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --exclude-type emerging-all", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --ex... emerging-all,backdoor,dns,ftp", 'err_msg' => "did not translate signatures", 'positive_output_matches' => [ qr/chat\.rules/, qr/ddos\.rules/, qr/Generated\sip6tables\srules\sfor/ ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --exclude-type emerging-all,backdoor,dns,ftp", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --in.. backdoor,dns,ftp --ex.. dns", 'err_msg' => "did not translate backdoor,ftp signatures", 'positive_output_matches' => [ qr/backdoor\.rules/, qr/ftp\.rules/, qr/Generated\sip6tables\srules\sfor/ ], 'negative_output_matches' => [ qr/dns\.rules/, ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --include-type backdoor,dns,ftp --exclude-type dns", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --sn.. $simple_sig_id,109,321 --ex.. sid\:109", 'err_msg' => "did not translate sid: $simple_sig_id", 'positive_output_matches' => [qr/Found\ssid/, qr/Found\ssid\:\s321/, qr/Successful\stranslation/, ], 'negative_output_matches' => [ qr/Found\ssid\:\s109/, ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --snort-sid $simple_sig_id,109,321 --exclude-regex sid\:109", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'operations', 'detail' => "ip6tables --sn.. $simple_sig_id,109,321 --in.. sid\:109", 'err_msg' => "did not translate sid: $simple_sig_id", 'positive_output_matches' => [qr/Found\ssid/, qr/Found\ssid\:\s109/, qr/Successful\stranslation/, ], 'negative_output_matches' => [ qr/Found\ssid\:\s321/, ], 'match_all' => $MATCH_ALL_RE, 'function' => \&generic_exec, 'cmdline' => "$fwsnortCmd --ip6tables --no-ipt-test -c $default_conf --snort-sid $simple_sig_id,109,321 --include-regex sid\:109", 'fw_exec' => $fw_exec, 'exec_err' => $NO, 'fatal' => $NO }, { 'category' => 'errors', 'detail' => 'look for perl warnings', 'err_msg' => 'found perl warnings', 'negative_output_matches' => [qr/Use\sof\suninitialized\svalue/i, qr/Missing\sargument/, qr/Argument.*isn\'t\snumeric/], 'match_all' => $MATCH_SINGLE_RE, 'function' => \&look_for_warnings, 'cmdline' => "grep -i uninit $output_dir/*.test", 'exec_err' => $IGNORE, 'fatal' => $NO }, ); ### make sure everything looks as expected before continuing &init(); &logr("\n[+] Starting the fwsnort test suite...\n\n" . " args: @args_cp\n\n" ); ### save the results from any previous test suite run ### so that we can potentially compare them with --diff if ($saved_last_results) { &logr(" Saved results from previous run " . "to: ${output_dir}.last/\n\n"); } ### main loop through all of the tests for my $test_hr (@tests) { &run_test($test_hr); } &logr("\n[+] passed/failed/executed: $passed/$failed/$executed tests\n\n"); copy $logfile, "$output_dir/$logfile" or die $!; exit 0; #===================== end main ======================= sub run_test() { my $test_hr = shift; my $msg = "[$test_hr->{'category'}]"; $msg .= " [$test_hr->{'subcategory'}]" if $test_hr->{'subcategory'}; $msg .= " $test_hr->{'detail'}"; return unless &process_include_exclude($msg); if ($list_mode) { print $msg, "\n"; return; } &dots_print($msg); $executed++; $current_test_file = "$output_dir/$executed.test"; &write_test_file("[+] TEST: $msg\n"); if (&{$test_hr->{'function'}}($test_hr)) { &logr("pass ($executed)\n"); $passed++; } else { &logr("fail ($executed)\n"); $failed++; if ($test_hr->{'fatal'} eq $YES) { die "[*] required test failed, exiting."; } } return; } sub look_for_warnings() { my $test_hr = shift; my $orig_test_file = $current_test_file; $current_test_file = "$output_dir/grep.output"; my $rv = &generic_exec($test_hr); copy $current_test_file, $orig_test_file; unlink $current_test_file; return $rv; } sub install_test_dir() { my $test_hr = shift; my $rv = 1; my $curr_pwd = cwd() or die $!; if (-d $test_install_dir) { rmtree $test_install_dir or die $!; } mkdir $test_install_dir or die $!; chdir '..' or die $!; my $exec_rv = &run_cmd($test_hr->{'cmdline'}, "test/$cmd_out_tmp", "test/$current_test_file"); if ($test_hr->{'exec_err'} eq $YES) { $rv = 0 if $exec_rv; } elsif ($test_hr->{'exec_err'} eq $NO) { $rv = 0 unless $exec_rv; } else { $rv = 1; } if ($test_hr->{'positive_output_matches'}) { $rv = 0 unless &file_find_regex( $test_hr->{'positive_output_matches'}, $test_hr->{'match_all'}, $current_test_file); } if ($test_hr->{'negative_output_matches'}) { $rv = 0 if &file_find_regex( $test_hr->{'negative_output_matches'}, $test_hr->{'match_all'}, $current_test_file); } chdir $curr_pwd or die $!; return $rv; } sub generic_exec() { my $test_hr = shift; my $rv = 1; my $exec_rv = &run_cmd($test_hr->{'cmdline'}, $cmd_out_tmp, $current_test_file); if ($test_hr->{'exec_err'} eq $YES) { $rv = 0 if $exec_rv; } elsif ($test_hr->{'exec_err'} eq $NO) { $rv = 0 unless $exec_rv; } else { $rv = 1; } if ($test_hr->{'positive_output_matches'}) { $rv = 0 unless &file_find_regex( $test_hr->{'positive_output_matches'}, $test_hr->{'match_all'}, $current_test_file); } if ($test_hr->{'negative_output_matches'}) { $rv = 0 if &file_find_regex( $test_hr->{'negative_output_matches'}, $test_hr->{'match_all'}, $current_test_file); } if ($test_hr->{'fw_exec'} eq $YES) { if (-e $fwsnort_sh) { $rv = 0 unless &run_cmd($fwsnort_sh, $cmd_out_tmp, $current_test_file); if ($test_hr->{'detail'} =~ /ip6tables/) { $rv = 0 unless &run_cmd("$fwsnortCmd --ipt-list --ip6tables", $cmd_out_tmp, $current_test_file); } else { $rv = 0 unless &run_cmd("$fwsnortCmd --ipt-list", $cmd_out_tmp, $current_test_file); } $rv = 0 unless &run_cmd("$fwsnort_sh -r", $cmd_out_tmp, $current_test_file); } else { &write_test_file("[-] $fwsnort_sh script does not exist.\n"); } } return $rv; } sub run_cmd() { my ($cmd, $cmd_out, $file) = @_; if (-e $file) { open F, ">> $file" or die "[*] Could not open $file: $!"; print F localtime() . " CMD: $cmd\n"; close F; } else { open F, "> $file" or die "[*] Could not open $file: $!"; print F localtime() . " CMD: $cmd\n"; close F; } my $rv = ((system "$cmd > $cmd_out 2>&1") >> 8); open C, "< $cmd_out" or die "[*] Could not open $cmd_out: $!"; my @cmd_lines = ; close C; open F, ">> $file" or die "[*] Could not open $file: $!"; print F $_ for @cmd_lines; close F; if ($rv == 0) { return 1; } return 0; } sub file_find_regex() { my ($re_ar, $match_all_flag, $file) = @_; my @write_lines = (); my @file_lines = (); open F, "< $file" or die "[*] Could not open $file: $!"; while () { push @file_lines, $_; } close F; my $found = 0; RE: for my $re (@$re_ar) { $found = 0; LINE: for my $line (@file_lines) { next LINE if $line =~ /file_file_regex\(\)/; if ($line =~ $re) { push @write_lines, "[.] file_find_regex() " . "Matched '$re' with line: $line (file: $file)\n"; $found = 1; last LINE; } } if ($found) { if ($match_all_flag == $MATCH_SINGLE_RE) { last RE; } } else { push @write_lines, "[.] file_find_regex() " . "did not match '$re' (file: $file)\n"; if ($match_all_flag == $MATCH_ALL_RE) { last RE; } } } for my $line (@write_lines) { &write_test_file($line); } return $found; } sub dots_print() { my $msg = shift; &logr($msg); my $dots = ''; for (my $i=length($msg); $i < $PRINT_LEN; $i++) { $dots .= '.'; } &logr($dots); return; } sub init() { $|++; ### turn off buffering $< == 0 and $> == 0 or die "[*] $0: You must be root (or equivalent ", "UID 0 account) to effectively test fwsnort"; ### validate test hashes my $hash_num = 0; for my $test_hr (@tests) { for my $key (keys %test_keys) { if ($test_keys{$key} == $REQUIRED) { die "[*] Missing '$key' element in hash: $hash_num" unless defined $test_hr->{$key}; } else { $test_hr->{$key} = '' unless defined $test_hr->{$key}; } } $hash_num++; } die "[*] $conf_dir directory does not exist." unless -d $conf_dir; die "[*] default config $default_conf does not exist" unless -e $default_conf; if (-d $output_dir) { if (-d "${output_dir}.last") { rmtree "${output_dir}.last" or die "[*] rmtree ${output_dir}.last $!"; } mkdir "${output_dir}.last" or die "[*] ${output_dir}.last: $!"; for my $file (glob("$output_dir/*.test")) { if ($file =~ m|.*/(.*)|) { copy $file, "${output_dir}.last/$1" or die $!; } } if (-e "$output_dir/init") { copy "$output_dir/init", "${output_dir}.last/init"; } if (-e $logfile) { copy $logfile, "${output_dir}.last/$logfile" or die $!; } $saved_last_results = 1; } else { mkdir $output_dir or die "[*] Could not mkdir $output_dir: $!"; } unless (-d $run_dir) { mkdir $run_dir or die "[*] Could not mkdir $run_dir: $!"; } for my $file (glob("$output_dir/*.test")) { unlink $file or die "[*] Could not unlink($file)"; } if (-e "$output_dir/init") { unlink "$output_dir/init" or die $!; } if (-e $logfile) { unlink $logfile or die $!; } if ($test_include) { @tests_to_include = split /\s*,\s*/, $test_include; } if ($test_exclude) { @tests_to_exclude = split /\s*,\s*/, $test_exclude; } return; } sub process_include_exclude() { my $msg = shift; ### inclusions/exclusions if (@tests_to_include) { my $found = 0; for my $test (@tests_to_include) { if ($msg =~ /$test/) { $found = 1; last; } } return 0 unless $found; } if (@tests_to_exclude) { my $found = 0; for my $test (@tests_to_exclude) { if ($msg =~ /$test/) { $found = 1; last; } } return 0 if $found; } return 1; } sub write_test_file() { my $msg = shift; if (-e $current_test_file) { open F, ">> $current_test_file" or die "[*] Could not open $current_test_file: $!"; print F $msg; close F; } else { open F, "> $current_test_file" or die "[*] Could not open $current_test_file: $!"; print F $msg; close F; } return; } sub logr() { my $msg = shift; print STDOUT $msg; open F, ">> $logfile" or die $!; print F $msg; close F; return; } sub usage() { ### FIXME } fwsnort-nodeps-1.6.7/test/conf/0000775000175000017500000000000013103435515014545 5ustar mbrmbrfwsnort-nodeps-1.6.7/test/conf/default_fwsnort.conf0000664000175000017500000001177313103435515020633 0ustar mbrmbr# ########################################################################### # # This is the configuration file for fwsnort. There are some similarities # between this file and the configuration file for Snort. # ########################################################################### # ### Fwsnort treats all traffic directed to / originating from the local ### machine as going to / coming from the HOME_NET in Snort rule parlance. ### If there is only one interface on the local system, then there will be ### no rules processed via the FWSNORT_FORWARD chain because no traffic ### would make it into the iptables FORWARD chain. HOME_NET any; EXTERNAL_NET any; ### List of servers. Fwsnort supports the same variable resolution as ### Snort. HTTP_SERVERS $HOME_NET; SMTP_SERVERS $HOME_NET; DNS_SERVERS $HOME_NET; SQL_SERVERS $HOME_NET; TELNET_SERVERS $HOME_NET; ### AOL AIM server nets AIM_SERVERS [64.12.24.0/24, 64.12.25.0/24, 64.12.26.14/24, 64.12.28.0/24, 64.12.29.0/24, 64.12.161.0/24, 64.12.163.0/24, 205.188.5.0/24, 205.188.9.0/24]; ### Configurable port numbers SSH_PORTS 22; HTTP_PORTS 80; SHELLCODE_PORTS !80; ORACLE_PORTS 1521; ### Default update URL for new rules. This variable can be given multiple ### times on separate lines in order to specify multiple update URL's: #UPDATE_RULES_URL #UPDATE_RULES_URL UPDATE_RULES_URL http://rules.emergingthreats.net/open/snort-2.9.0/emerging-all.rules; ### define average packet lengths and maximum frame length. This is ### used for iptables length match emulation of the Snort dsize option. AVG_IP_HEADER_LEN 20; ### IP options are not usually used. AVG_TCP_HEADER_LEN 30; ### Include 10 bytes for options MAX_FRAME_LEN 1500; ### define the max length of the content (null terminated string) that ### can be passed to either the --hex-string or --string iptables matches. ### Note that as of fwsnort-1.5, the max string length supported by the ### local iptables instance is automatically determined, so this variable ### is not really needed, and just allows a max value to be set ### independently of what iptables supports. MAX_STRING_LEN 1024; ### Use the WHITELIST variable to define a list of hosts/networks ### that should be completely ignored by fwsnort. For example, if you ### want to whitelist the IP 192.168.10.1 and the network 10.1.1.0/24, ### you would use (note that you can also specify multiple WHITELIST ### variables, one per line): #WHITELIST 192.168.10.1, 10.1.1.0/24; WHITELIST NONE; ### Use the BLACKLIST variable to define a list of hosts/networks ### that for which fwsnort should DROP or REJECT all traffic. For ### example, to DROP all traffic from the 192.168.10.0/24 network, you ### can use: ### BLACKLIST 192.168.10.0/24 DROP; ### To have fwsnort REJECT all traffic from 192.168.10.0/24, you would ### use: ### BLACKLIST 192.168.10.0/24 REJECT; BLACKLIST NONE; ### define the jump position in the built-in chains to jump to the ### fwsnort chains FWSNORT_INPUT_JUMP 1; FWSNORT_OUTPUT_JUMP 1; FWSNORT_FORWARD_JUMP 1; ### iptables chains (these do not normally need to be changed). FWSNORT_INPUT FWSNORT_INPUT; FWSNORT_INPUT_ESTAB FWSNORT_INPUT_ESTAB; FWSNORT_OUTPUT FWSNORT_OUTPUT; FWSNORT_OUTPUT_ESTAB FWSNORT_OUTPUT_ESTAB; FWSNORT_FORWARD FWSNORT_FORWARD; FWSNORT_FORWARD_ESTAB FWSNORT_FORWARD_ESTAB; ### fwsnort filesystem paths INSTALL_ROOT fwsnort-install; CONF_DIR $INSTALL_ROOT/etc/fwsnort; RULES_DIR $CONF_DIR/snort_rules; LOG_DIR $INSTALL_ROOT/var/log/fwsnort; LIBS_DIR $INSTALL_ROOT/usr/lib/fwsnort; ### for perl modules STATE_DIR $INSTALL_ROOT/var/lib/fwsnort; QUEUE_RULES_DIR $STATE_DIR/snort_rules_queue; ARCHIVE_DIR $STATE_DIR/archive; CONF_FILE $CONF_DIR/fwsnort.conf; LOG_FILE $LOG_DIR/fwsnort.log; FWSNORT_SCRIPT $STATE_DIR/fwsnort_iptcmds.sh; ### slow version FWSNORT_SAVE_EXEC_FILE $STATE_DIR/fwsnort.sh; ### main fwsnort.sh script FWSNORT_SAVE_FILE $STATE_DIR/fwsnort.save; ### main fwsnort.save file IPT_BACKUP_SAVE_FILE $STATE_DIR/iptables.save; ### iptables policy backup ### system binaries shCmd /bin/sh; catCmd /bin/cat; grepCmd /bin/grep; echoCmd /bin/echo; tarCmd /bin/tar; wgetCmd /usr/bin/wget; unameCmd /usr/bin/uname; ifconfigCmd /sbin/ifconfig; ipCmd /sbin/ip; iptablesCmd /sbin/iptables; iptables-saveCmd /sbin/iptables-save; iptables-restoreCmd /sbin/iptables-restore; ip6tablesCmd /sbin/ip6tables; ip6tables-saveCmd /sbin/ip6tables-save; ip6tables-restoreCmd /sbin/ip6tables-restore; fwsnort-nodeps-1.6.7/TODO0000664000175000017500000000176713103435515013344 0ustar mbrmbr - Allow a list of SIDs to have a manually specified target (such as -j REJECT --reject-with tcp-reset). The list should come from the command line with a new option and/or be read from a file. - Make use of the u32 module for complex match criteria. - Error checking in fwsnort.sh (at least for things like chain creation). - Print more stats information such as shortest/longest pattern length, etc. - Command line argument saving similar to fwknop. - fwsnort init scripts? - string match application layer offset bugfix (in the kernel). - Ability to execute other fwsnort scripts from within the main fwsnort.sh script. This would make it possible to have add a new fwsnort rule for a specific signature to an existing fwsnort policy without removing existing rules, or perhaps a new "--policy-add" option is in order. - Ability to download Emerging Threats rulesets as a .tar.gz so that the different classtypes can be used (e.g. within --include-type/exclude-type options, etc.) fwsnort-nodeps-1.6.7/README.RPM0000664000175000017500000000643613103435515014167 0ustar mbrmbr NOTE: Before building an RPM manually for your Linux system, it is recommended to try installing fwsnort via the standard software install mechanism for your distribution since fwsnort is available in many distribution software repositories. For example, you can try installing fwsnort with 'yum' or 'apt-get' depending on the type of Linux distribution that you are running. Building RPM files that are compatible with all possible Linux distributions is a difficult task. If a given RPM file that is downloaded from http://www.cipherdyne.org/ is not compatible with your particular Linux distro, then you can use the "cd_rpmbuilder" ("CipherDyne RPM Builder") to build an RPM file for you on your own system. The command line interface to cd_rpmbuilder is simple, and one command line argument is always required "-p " so that cd_rpmbuilder knows which CipherDyne software project you want to build: # cd_rpmbuilder -p Note that cd_rpmbuilder is normally as root because it builds RPM's within the /usr/src/redhat/ directory by default. However, if you would like to build as a normal user within a directory of your choosing you can do: # cd_rpmbuilder -p -r By default, cd_rpmbuilder builds the latest version of the specified project, but if you want to build an older version, use the -b flag: # cd_rpmbuilder -p -b If you want to see verbose output (including all output of the system rpmbuild command), then use the -v flag: # cd_rpmbuilder -p -v Finally, here is some sample output for building the psad project available at http://www.cipherdyne.org/psad/: # ./cd_rpmbuilder -p psad [+] Getting latest version file: http://www.cipherdyne.org/psad/psad-latest [+] Downloading file: http://www.cipherdyne.org/psad/download/psad-2.0.1.spec [+] Downloading file: http://www.cipherdyne.org/psad/download/psad-2.0.1.spec.md5 [+] Valid md5 sum check for psad-2.0.1.spec [+] Downloading file: http://www.cipherdyne.org/psad/download/psad-2.0.1.tar.gz [+] Downloading file: http://www.cipherdyne.org/psad/download/psad-2.0.1.tar.gz.md5 [+] Valid md5 sum check for psad-2.0.1.tar.gz [+] Building RPM, this may take a little while... [+] The following RPMS were successfully built: /usr/src/redhat/SRPMS/psad-2.0.1-1.src.rpm (source RPM) /usr/src/redhat/RPMS/i386/psad-2.0.1-1.i386.rpm You can view the usage information like so: [mbr@minastirith ~/src/psad]$ ./cd_rpmbuilder -h cd_rpmbuilder; the CipherDyne RPM builder [+] Version: 0.9 [+] By Michael Rash (mbr@cipherdyne.org, http://www.cipherdyne.org) Usage: cd_rpmbuilder -p [-b ] [-r ] [-v] [-V] [-h] Options: -p, --project - This can be one of "psad", "fwknop", "gpgdir", or "fwsnort". -b, --build-version - Build a specific project version. -r, --rpm-build-dir - Change the RPM build directory from the default of /usr/src/redhat. -v, --verbose - Run in verbose mode. -V, --Version - Print version and exit. -h, --help - Display usage information. fwsnort-nodeps-1.6.7/LICENSE0000664000175000017500000004310413103435515013650 0ustar mbrmbr GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. fwsnort-nodeps-1.6.7/snortspoof.pl0000775000175000017500000000730213103435515015417 0ustar mbrmbr#!/usr/bin/perl -w # ############################################################################### # # File: snortspoof.pl # # Purpose: To parse rules from the Snort rule set and spoof them at a target # IP from arbitrary source addresses. This is similar to the # technique employed by the Stick and Snot projects. Snortspoof.pl # is distributed with the fwsnort project # (http://www.cipherdyne.org/fwsnort/). # # Author: Michael Rash # # Copyright (C) 2003-2007 Michael Rash (mbr@cipherdyne.org) # # License (GNU Public License): # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA # ############################################################################### # require Net::RawIP; use strict; my $file = $ARGV[0] || ''; my $spoof_addr = $ARGV[1] || ''; my $dst_addr = $ARGV[2] || ''; die "$0 " unless $file and $spoof_addr and $dst_addr; # alert udp $EXTERNAL_NET any -> $HOME_NET 635 (msg:"EXPLOIT x86 Linux mountd # overflow"; content:"^|B0 02 89 06 FE C8 89|F|04 B0 06 89|F"; # reference:bugtraq,121; reference:cve,1999-0002; classtype:attempted-admin; # sid:315; rev:6;) my $sig_sent = 0; open F, "< $file" or die "[*] Could not open $file: $!"; SIG: while () { my $content = ''; my $conv_content = ''; my $hex_mode = 0; my $proto = ''; my $spt = 10000; my $dpt = 10000; ### make sure it is an inbound sig if (/^\s*alert\s+(tcp|udp)\s+\S+\s+(\S+)\s+\S+ \s+(\$HOME_NET|any)\s+(\S+)\s/x) { $proto = $1; my $spt_tmp = $2; my $dpt_tmp = $4; ### can't handle multiple content fields yet next SIG if /content:.*\s*content\:/; $content = $1 if /\s*content\:\"(.*?)\"\;/; next SIG unless $content; if ($spt_tmp =~ /(\d+)/) { $spt = $1; } elsif ($spt_tmp ne 'any') { next SIG; } if ($dpt_tmp =~ /(\d+)/) { $dpt = $1; } elsif ($dpt_tmp ne 'any') { next SIG; } my @chars = split //, $content; for (my $i=0; $i<=$#chars; $i++) { if ($chars[$i] eq '|') { $hex_mode == 0 ? ($hex_mode = 1) : ($hex_mode = 0); next; } if ($hex_mode) { next if $chars[$i] eq ' '; $conv_content .= sprintf("%c", hex($chars[$i] . $chars[$i+1])); $i++; } else { $conv_content .= $chars[$i]; } } my $rawpkt = ''; if ($proto eq 'tcp') { $rawpkt = new Net::RawIP({'ip' => { saddr => $spoof_addr, daddr => $dst_addr}, 'tcp' => { source => $spt, dest => $dpt, 'ack' => 1, data => $conv_content}}) or die "[*] Could not get Net::RawIP object: $!"; } else { $rawpkt = new Net::RawIP({'ip' => { saddr => $spoof_addr, daddr => $dst_addr}, 'udp' => { source => $spt, dest => $dpt, data => $conv_content}}) or die "[*] Could not get Net::RawIP object: $!"; } $rawpkt->send(); $sig_sent++; } } print "[+] $file, $sig_sent attacks sent.\n"; close F; exit 0; fwsnort-nodeps-1.6.7/fwsnort0000775000175000017500000052504313103435515014302 0ustar mbrmbr#!/usr/bin/perl -w # ############################################################################### # # File: fwsnort # # URL: http://www.cipherdyne.org/fwsnort/ # # Purpose: To translate snort rules into equivalent iptables rules. # fwsnort is based on the original snort2iptables shell script # written by William Stearns. # # Author: Michael Rash # # Credits: (see the CREDITS file) # # Version: 1.6.7 # # Copyright (C) 2003-2017 Michael Rash (mbr@cipherdyne.org) # # License - GNU Public License version 2 (GPLv2): # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA # # TODO: # - Add the ability to remove rules from a real snort config in the same # way we remove them from iptables rulesets in fwsnort (we remove rules # from an iptables ruleset if the iptables policy will not allow such # traffic through in the first place). # - New option: --ipt-mark. # # Reference: Snort is a registered trademark of Sourcefire, Inc # # Snort Rule Options: # # msg: Prints a message in alerts and packet logs. # logto: Log the packet to a user specified filename instead of the # standard output file. # ttl: Test the IP header's TTL field value. # tos: Test the IP header's TOS field value. # id: Test the IP header's fragment ID field for a specific # value. # ipoption: Watch the IP option fields for specific codes. # fragbits: Test the fragmentation bits of the IP header. # dsize: Test the packet's payload size against a value. # flags Test the TCP flags for certain values. # seq: Test the TCP sequence number field for a specific value. # ack: Test the TCP acknowledgement field for a specific value. # itype: Test the ICMP type field against a specific value. # icode: Test the ICMP code field against a specific value. # icmp_id: Test the ICMP ECHO ID field against a specific value. # icmp_seq: Test the ICMP ECHO sequence number against a specific # value. # content: Search for a pattern in the packet's payload. # content-list: Search for a set of patterns in the packet's payload. # offset: Modifier for the content option, sets the offset to begin # attempting a pattern match. # depth: Modifier for the content option, sets the maximum search # depth for a pattern match attempt. # nocase: Match the preceding content string with case insensitivity. # session Dumps the application layer information for a given # session. # rpc: Watch RPC services for specific application/procedure # calls. # resp: Active response (knock down connections, etc). # react: Active response (block web sites). # reference: External attack reference ids. # sid: snort rule id. # rev: Rule revision number. # classtype: Rule classification identifier. # priority: Rule severity identifier. # uricontent: Search for a pattern in the URI portion of a packet # # tag: Advanced logging actions for rules. # ip_proto: IP header's protocol value. # sameip: Determines if source ip equals the destination ip. # stateless: Valid regardless of stream state. # regex: Wildcard pattern matching. # ############################################################################ # use IO::Socket; use File::Copy; use File::Path; use Sys::Hostname; use Data::Dumper; use Cwd; use Getopt::Long; use strict; ### config file my $CONFIG_DEFAULT = '/etc/fwsnort/fwsnort.conf'; my $fwsnort_conf = $CONFIG_DEFAULT; ### version number my $version = '1.6.7'; my %ipt_hdr_opts = ( 'src' => '-s', 'sport' => '--sport', 'dst' => '-d', 'dport' => '--dport', 'proto' => '-p', ); my %snort_opts = ( ### snort options that we can directly filter on ### in iptables rulesets (snort options are separate ### from the snort "header" which include protocol, ### source, destination, etc.) 'filter' => { ### application layer 'uricontent' => { ### use --strict to not translate this 'iptopt' => '-m string', 'regex' => qr/[\s;]uricontent:\s*\"(.*?)\"\s*;/ }, 'content' => { 'iptopt' => '-m string', 'regex' => qr/[\s;]content:\s*\"(.*?)\"\s*;/ }, 'fast_pattern' => { 'iptopt' => '', ### fast_pattern just governs ordering of ### content matches 'regex' => qr/[\s;]fast_pattern(?::\s*.*?\s*)?;/, }, 'pcre' => { ### only basic PCRE's that just have strings separated ### by ".*" or ".+" are supported. 'iptopt' => '-m string', 'regex' => qr/[\s;]pcre:\s*\"(.*?)\"\s*;/ }, 'nocase' => { 'iptopt' => '--icase', 'regex' => qr/[\s;]nocase\s*;/, }, 'offset' => { 'iptopt' => '--from', 'regex' => qr/[\s;]offset:\s*(\d+)\s*;/ }, 'depth' => { 'iptopt' => '--to', 'regex' => qr/[\s;]depth:\s*(\d+)\s*;/ }, ### technically, the "distance" and "within" criteria ### are relative to the end of the previous pattern match, ### so iptables cannot emulate these directly; an approximation ### is made based on the on length of the previous pattern an ### any "depth" or "offset" criteria for the previous pattern. ### To disable signatures with "distance" and "within", just ### use the --strict option. 'distance' => { 'iptopt' => '--from', 'regex' => qr/[\s;]distance:\s*(\d+)\s*;/ }, 'within' => { 'iptopt' => '--to', 'regex' => qr/[\s;]within:\s*(\d+)\s*;/ }, 'replace' => { ### for Snort running in inline mode 'iptopt' => '--replace-string', 'regex' => qr/[\s;]replace:\s*\"(.*?)\"\s*;/ }, 'resp' => { 'iptopt' => '-j REJECT', 'regex' => qr/[\s;]resp:\s*(.*?)\s*;/ }, ### transport layer 'flags' => { 'iptopt' => '--tcp-flags', 'regex' => qr/[\s;]flags:\s*(.*?)\s*;/ }, 'flow' => { 'iptopt' => '--tcp-flags', 'regex' => qr/[\s;]flow:\s*(.*?)\s*;/ }, ### network layer 'itype' => { 'iptopt' => '--icmp-type', ### --icmp-type type/code 'regex' => qr/[\s;]itype:\s*(.*?)\s*;/ }, 'icode' => { 'iptopt' => 'NONE', 'regex' => qr/[\s;]icode:\s*(.*?)\s*;/ }, 'ttl' => { 'iptopt' => '-m ttl', ### requires CONFIG_IP_NF_MATCH_TTL 'regex' => qr/[\s;]ttl:\s*(.*?)\s*;/ }, 'tos' => { 'iptopt' => '-m tos --tos', ### requires CONFIG_IP_NF_MATCH_TOS 'regex' => qr/[\s;]tos:\s*(\d+)\s*;/ }, 'ipopts' => { 'iptopt' => '-m ipv4options', ### requires ipv4options extension 'regex' => qr/[\s;]ipopts:\s*(\w+)\s*;/ }, 'ip_proto' => { 'iptopt' => '-p', 'regex' => qr/[\s;]ip_proto:\s*(.*?)\s*;/ }, 'dsize' => { ### requires CONFIG_IP_NF_MATCH_LENGTH 'iptopt' => '-m length --length', 'regex' => qr/[\s;]dsize:\s*(.*?)\s*;/ }, }, ### snort options that can be put into iptables ### ruleset, but only in log messages with --log-prefix 'logprefix' => { 'sid' => qr/[\s;]sid:\s*(\d+)\s*;/, 'msg' => qr/[\s;]msg:\s*\"(.*?)\"\s*;/, ### we create a space 'classtype' => qr/[\s;]classtype:\s*(.*?)\s*;/, 'reference' => qr/[\s;]reference:\s*(.*?)\s*;/, 'priority' => qr/[\s;]priority:\s*(\d+)\s*;/, 'rev' => qr/[\s;]rev:\s*(\d+)\s*;/, }, ### snort options that cannot be included directly ### within iptables filter statements (yet :) 'unsupported' => { 'asn1' => qr/[\s;]asn1:\s*.*?\s*;/, 'fragbits' => qr/[\s;]fragbits:\s*.*?\s*;/, 'content-list' => qr/[\s;]content\-list:\s*\".*?\"\s*;/, 'rpc' => qr/[\s;]rpc:\s*.*?\s*;/, 'byte_test' => qr/[\s;]byte_test\s*.*?\s*;/, 'byte_jump' => qr/[\s;]byte_jump\s*.*?\s*;/, 'byte_extract' => qr/[\s;]byte_extract\s*.*?\s*;/, 'file_data' => qr/[\s;]file_data\s*;/, 'window' => qr/[\s;]window:\s*.*?\s*;/, 'flowbits' => qr/[\s;]flowbits:\s*.*?\s*;/, 'tag' => qr/[\s;]tag:\s*.*?\s*;/, 'ftpbounce' => qr/[\s;]ftpbounce\s*;/, 'base64_data' => qr/[\s;]base64_data\s*;/, 'base64_decode' => qr/[\s;]base64_decode:\s*.*?\s*;/, # 'offset' => qr/[\s;]offset:\s*\d+\s*;/, # 'depth' => qr/[\s;]depth:\s*\d+\s*;/, ### the following fields get logged by iptables but ### we cannot filter them directly except with the ### iptables u32 module. Functionality has been built ### into psad to generate alerts for most of these Snort ### options. 'id' => qr/[\s;]id:\s*(\d+)\s*;/, 'seq' => qr/[\s;]seq:\s*(\d+)\s*;/, ### --log-tcp-sequence 'ack' => qr/[\s;]ack:\s*.*?\s*;/, ### --log-tcp-sequence 'icmp_seq' => qr/[\s;]icmp_seq:\s*(\d+)\s*;/, 'icmp_id' => qr/[\s;]icmp_id:\s*(\d+)\s*;/, 'sameip' => qr/[\s;]sameip\s*;/, 'regex' => qr/[\s;]regex:\s*(.*?)\s*;/, 'isdataat' => qr/[\s;]isdataat:\s*(.*?)\s*;/, 'threshold' => qr/[\s;]threshold:\s*.*?\s*;/, ### FIXME --limit 'detection_filter' => qr/[\s;]detection_filter:\s*.*?\s*;/ ### FIXME --limit }, ### snort options that fwsnort will ignore 'ignore' => { 'rawbytes' => qr/[\s;]rawbytes\s*;/, ### iptables does a raw match anyway 'logto' => qr/[\s;]logto:\s*\S+\s*;/, 'session' => qr/[\s;]session\s*;/, 'tag' => qr/[\s;]tag:\s*.*?\s*;/, 'react' => qr/[\s;]react:\s*.*?\s*;/, ### FIXME -j REJECT 'http_uri' => qr/[\s;]http_uri\s*;/, 'http_raw_uri' => qr/[\s;]http_raw_uri\s*;/, 'http_method' => qr/[\s;]http_method\s*;/, 'http_stat_code' => qr/[\s;]http_stat_code\s*;/, 'http_stat_msg' => qr/[\s;]http_stat_msg\s*;/, 'http_client_body' => qr/[\s;]http_client_body\s*;/, 'http_cookie' => qr/[\s;]http_cookie\s*;/, 'urilen' => qr/[\s;]urilen:\s*.*?\s*;/, }, ### in --strict mode, signatures that include any of these ### options are not translated to iptables rules 'strict_list' => [ 'uricontent', 'pcre', 'distance', 'within', 'http_uri', 'http_raw_uri', 'http_method', 'http_stat_code', 'http_stat_msg', 'http_client_body', 'http_cookie', 'urilen' ] ); ### rules update link my $DEFAULT_RULES_URL = 'http://rules.emergingthreats.net/open/snort-2.9.0/emerging-all.rules'; my $rules_url = $DEFAULT_RULES_URL; ### config vars that may span multiple lines my %multi_line_vars = ( 'UPDATE_RULES_URL' => '', 'WHITELIST' => '', 'BLACKLIST' => '', ); ### array that contains the fwsnort iptables script (will be written ### to $config{'FWSNORT_SCRIPT'}) my @ipt_script_lines = (); ### array that contains the fwsnort policy in iptables-save format. ### This will also contain the running iptables policy, so the fwsnort ### policy is integrated in. my @fwsnort_save_lines = (); my @ipt_save_lines = (); my $ipt_save_index = 0; my @ipt_save_script_lines = (); my $ipt_save_completed_line = ''; my $save_str = 'iptables-save'; my $ipt_str = 'iptables'; my $save_bin = ''; my $restore_bin = ''; my $ipt_bin = ''; ### contains a cache of the iptables policy my %ipt_policy = (); my %ipt_default_policy_setting = (); my %ipt_default_drop = (); my %ipt_save_existing_chains = (); ### hashes for save format data my %save_format_whitelist = (); my %save_format_blacklist = (); my %save_format_prereqs = (); my %save_format_rules = (); my %save_format_conntrack_jumps = (); ### regex to match ip addresses my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|; my %snort_dump_cache = (); my %ipt_dump_cache = (); ### for iptables capabilities testing my $NON_HOST = '127.0.0.2'; my $NON_IP6_HOST = '::2/128'; my $non_host = ''; my $IPT_SUCCESS = 1; my $IPT_FAILURE = 0; my $IPT_TEST_RULE_NUM = 1; my $MATCH_EQUIV = 1; my $MATCH_SUBSTR = 2; ### header lengths; note that IP and TCP lengths are defined ### in the fwsnort.conf file since they may each contain options, ### but until the --payload option is added to the string match ### extension there is no way to account for them except to ### define an average length. my $MAC_HDR_LEN = 14; my $UDP_HDR_LEN = 8; my $ICMP_HDR_LEN = 8; ### config and commands hashes (constructed by import_config()) my %config = (); my %cmds = (); my @local_addrs = (); my %include_types = (); my %exclude_types = (); my %include_sids = (); my %exclude_sids = (); my %restrict_interfaces = (); ### establish some default behavior my $home_net = ''; ### normally comes from fwsnort.conf my $ext_net = ''; ### normally comes from fwsnort.conf my $ipt_exec = 0; my $ipt_revert = 0; my $ipt_drop = 0; my $ipt_reject = 0; my $ipt_max_buf_len = 1025; my $ipt_cap_search_factor = 128; my $help = 0; my $stdout = 0; my $lib_dir = ''; my $rules_file = ''; my $debug = 0; my $is_root = 0; my $dumper = 0; my $dump_ipt = 0; my $dump_snort = 0; my $strict = 0; my $ipt_script = ''; my $logfile = ''; my $rules_dir = ''; my $homedir = ''; my $abs_num = 0; my $run_last = 0; my $queue_rules_dir = ''; my $queue_pre_match_max = 0; my $dump_conf = 0; my $kernel_ver = '2.6'; ### default my $string_match_alg = 'bm'; my $verbose = 0; my $print_ver = 0; my $cmdl_homedir = ''; my $update_rules = 0; ### used to download latest snort rules my $default_icmp_type = 8; ### echo request my $ipt_print_type = 0; my $ipt_check_capabilities = 0; my $ipt_rule_ctr = 1; my $ipt_sync = 0; my $ipt_flush = 0; my $ipt_del_chains = 0; my $ipt_list = 0; my $ipt_file = ''; my $no_pcre = 0; my $no_ipt_log = 0; my $no_ipt_test = 0; my $no_ipt_jumps = 0; my $no_ipt_input = 0; my $no_ipt_output = 0; my $no_addr_check = 0; my $no_ipt_forward = 0; my $ignore_opt = 0; my $include_sids = ''; my $exclude_sids = ''; my $add_deleted = 0; my $rules_types = ''; my $exclude_types = ''; my $snort_type = ''; my $ulog_nlgroup = 1; my $queue_mode = 0; my $nfqueue_mode = 0; my $nfqueue_num = 0; my $ulog_mode = 0; my $exclude_re = ''; my $include_re = ''; my $include_re_caseless = 0; my $exclude_re_caseless = 0; my $enable_ip6tables = 0; my $ipt_var_str = 'IPTABLES'; my $no_ipt_conntrack = 0; my $conntrack_state = 'ESTABLISHED'; my $have_conntrack = 0; my $have_state = 0; my $snort_conf_file = ''; my $ipt_restrict_intf = ''; my $no_ipt_comments = 0; my $no_ipt_rule_nums = 0; my $no_exclude_loopback = 0; my $no_ipt_log_ip_opts = 0; my $no_ipt_log_tcp_opts = 0; my $ipt_log_tcp_seq = 0; my $include_perl_triggers = 0; my $duplicate_last_build = 0; my $ipt_max_str_len = 1; my $ipt_max_log_prefix_len = 1; my $ipt_max_comment_len = 1; my $no_fast_pattern_order = 0; my $ipt_have_multiport_match = 0; my $ipt_multiport_max = 2; ### to be added to the string match extension my $ipt_has_string_payload_offset_opt = 0; ### default to processing these filter chains my %process_chains = ( 'INPUT' => 1, 'FORWARD' => 1, 'OUTPUT' => 1, ); my $TEST_CHAIN = 'FWS_CAP_TEST'; my %chain_ctr = (); ### save a copy of the command line args my @argv_cp = @ARGV; ### see if we are running as root &is_root(); ### handle the command line args &handle_cmd_line(); &run_last_cmdline() if $run_last; ### import config, initialize various things, etc. &fwsnort_init(); ### if we are running with $chk_ipt_policy, then cache ### the current iptables policy &cache_ipt_policy() if $ipt_sync; ### truncate old fwsnort log &truncate_logfile(); ### check to make sure iptables has various functionality available ### such as the LOG target, --hex-strings, the comment match, etc. if ($no_ipt_test) { &set_defaults_without_ipt_test(); } else { &ipt_capabilities(); } ### cache the running iptables policy in iptables-save format &cache_ipt_save_policy(); ### print a header at the top of the iptables ruleset ### script &ipt_hdr(); ### now that we have the interfaces, add the iptables ### chains to the fwsnort shell script &ipt_add_chains(); ### add any WHITELIST rules to the main fwsnort chains ### with the RETURN target &ipt_whitelist(); ### add any BLACKLIST rules to the main fwsnort chains ### with the DROP or REJECT targets &ipt_blacklist(); ### add jump rules for established tcp connections to ### the fwsnort state tracking chains &ipt_add_conntrack_jumps() unless $no_ipt_conntrack; ### display the config on STDOUT &dump_conf() if $dump_conf; ### make sure .rules file exists if --type was ### specified on the command line &check_type() if $rules_types; &logr("[+] Begin parsing cycle."); ### parse snort rules (signatures) if ($include_sids) { print "[+] Parsing Snort rules files...\n"; } else { if ($ipt_sync) { print "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=", "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n", sprintf("%-30s%-10s%-10s%-10s%-10s", ' Snort Rules File', 'Success', 'Fail', 'Ipt_apply', 'Total'), "\n\n"; } else { print "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=", "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n", sprintf("%-30s%-10s%-10s%-10s", ' Snort Rules File', 'Success', 'Fail', 'Total'), "\n\n"; } } ### main subroutine to parse snort rules and add them to the ### fwsnort.sh script. &parse_snort_rules(); ### append all translated rules to the iptables-save formatted array &save_format_append_rules(); ### jump packets (as appropriate) from the INPUT and ### FORWARD chains to our fwsnort chains &ipt_jump_chain() unless $no_ipt_jumps; push @ipt_script_lines, qq|\n\$ECHO "[+] Finished."|, '### EOF ###'; push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines]; print "\n[+] Logfile: $config{'LOG_FILE'}\n"; if ($ipt_rule_ctr > 1) { ### write the iptables script out to disk &write_ipt_script(); if ($queue_mode or $nfqueue_mode) { print "[+] Snort rule set directory for rules to be queued ", "to userspace:\n $config{'QUEUE_RULES_DIR'}\n"; } print "[+] $ipt_str script (individual commands): " . "$config{'FWSNORT_SCRIPT'}\n"; } else { die "[-] No Snort rules could be translated, exiting\n"; } &write_save_file(); &print_final_message(); exit 0; #===================== end main ====================== sub parse_snort_rules() { my @rfiles = (); my $cwd = cwd(); if ($rules_file) { @rfiles = split /\,/, $rules_file; } else { for my $dir (split /\,/, $config{'RULES_DIR'}) { opendir D, $dir or die "[*] Could not opendir $dir"; for my $file (readdir D) { push @rfiles, "$dir/$file"; } closedir D; } } my $sabs_num = 0; my $tot_ipt_apply = 0; my $tot_unsup_ctr = 0; FILE: for my $rfile (sort @rfiles) { $rfile = $cwd . '/' . $rfile unless $rfile =~ m|^/|; my $type = ''; my $filename = ''; if ($rfile =~ m|.*/(\S+\.rules)$|) { $filename = $1; } if ($rfile =~ m|.*/(\S+)\.rules$|) { $type = $1; } else { next FILE; } $ipt_print_type = 0; if ($rules_types) { next FILE unless defined $include_types{$type}; } if ($exclude_types) { next FILE if defined $exclude_types{$type}; } if ($rfile =~ m|deleted\.rules|) { next FILE unless $add_deleted; } ($snort_type) = ($rfile =~ m|.*/(\S+)\.rules|); printf("%-30s", "[+] $filename") unless $include_sids; &logr("[+] Parsing $rfile"); open R, "< $rfile" or die "[*] Could not open: $rfile"; my @lines = ; close R; ### contains Snort rules that will be used by Snort_inline ### if fwsnort is building a QUEUE policy; these rules have ### met the criteria that at least one "content" match is ### required. my @queue_rules = (); my $line_num = 0; my $rule_num = 0; my $parsed_ctr = 0; my $unsup_ctr = 0; my $ipt_apply_ctr = 0; my $ipt_rules_ctr = 0; RULE: for my $rule (@lines) { chomp $rule; my $rule_hdr; my $rule_options; $line_num++; ### pass == ACCEPT, log == ULOG unless ($rule =~ /^\s*alert/ or $rule =~ /^\s*pass/ or $rule =~ /^\s*log/) { next RULE; } ### regex filters if ($exclude_re) { next RULE if $rule =~ $exclude_re; } if ($include_re) { next RULE unless $rule =~ $include_re; } $rule_num++; ### keep track of the abs num of rules $sabs_num++; if ($rule =~ m|^(.*?)\s+\((.*)\)|) { $rule_hdr = $1; $rule_options = " $2 "; ### allows out-of-order options } else { &logr("[-] Unrecognized rule format at line: $line_num. " . "Skipping."); next RULE; } ### skip all icmp "Undefined Code" rules; psad properly ### handles this, but not fwsnort (see the icmp-info.rules ### file). if ($filename =~ /icmp/ and $rule_options =~ /undefined\s+code/i) { $unsup_ctr++; $tot_unsup_ctr++; next RULE; } ### parse header portion of Snort rule my $hdr_hr = &parse_rule_hdr($rule_hdr, $line_num); unless (keys %$hdr_hr) { &logr("[-] Unrecognized rule header: \"$rule_hdr\" at " . "line: $line_num, skipping."); $unsup_ctr++; $tot_unsup_ctr++; next RULE; } ### parse options portion of Snort rule my ($parse_rv, $opts_hr, $patterns_ar) = &parse_rule_options( $rule_options, &get_avg_hdr_len($hdr_hr), $line_num); unless ($parse_rv) { $unsup_ctr++; $tot_unsup_ctr++; next RULE; } if ($include_sids) { print "[+] Found sid: $opts_hr->{'sid'} in $filename\n"; } if ($queue_mode or $nfqueue_mode) { ### In general, it is not easy to modify the signatures that ### snort_inline would use; one would think that an optimzation ### would be to remove all "content" keywords since the kernel ### itself is doing this now, but consider the following: ### Suppose there are two original Snort signatures like so: ### ### msg: "SIG1"; content: "abc"; pcre: "(d|e)"; ### msg: "SIG2"; content: "xyz"; pcre: "(e|f)"; ### ### Now, suppose there is a packet with the following data: ### ### packet data: "xyz------------e------" ### ### Then the SIG1 matches when it shouldn't because the packet ### does not contain "abc" (assuming the "abc" string is ### removed from the signature that is actually deployed with ### snort_inline). There does not seem to be a good solution ### for this problem if pcre criteria are involved because the ### two pcre's would have to be interpreted to see if there is ### any data that could satisfy both at the same time. ### However, performing the duplicate string matching is far ### less expensive than not sending a large portion of network ### traffic to userspace for analysis by snort_inline in the ### first place. This is the real benefit of letting fwsnort ### build a smarter iptables queueing policy. This does come ### with a penalty against detection, since snort_inline is ### only receiving individual packets that match one of the ### content keywords in a signature; it does not get the ### entire stream. But, this may be worth it for large sites ### where performance is the primary concern. Also, there is ### some potential for removing a subset of the content ### matches if done in the right way; this is the reason the ### queue_get_rule() function is stubbed in below. my $queue_rule = &queue_get_rule($rule_hdr, $rule_options); push @queue_rules, $queue_rule if $queue_rule; } ### construct the equivalent iptables rule and add it ### to $config{'FWSNORT_SCRIPT'} my ($ipt_rv, $num_rules) = &ipt_build($hdr_hr, $opts_hr, $patterns_ar, $rule); if ($ipt_rv) { $ipt_apply_ctr++; $tot_ipt_apply++; ### may have the rule in several chains $ipt_rules_ctr += $num_rules; if ($include_sids) { print " Successful translation.\n"; } } else { if ($include_sids) { print " Unsuccessful translation.\n"; } } $parsed_ctr++; ### keep track of successfully parsed rules $abs_num++;; } if (($queue_mode or $nfqueue_mode) and @queue_rules) { open M, "> $config{'QUEUE_RULES_DIR'}/$filename" or die "[*] Could not ", "open $config{'QUEUE_RULES_DIR'}/$filename: $!"; print M "#\n### This file generated with: fwsnort @argv_cp\n#\n\n"; print M "$_\n", for @queue_rules; print M "\n### EOF ###\n"; close F; } if ($ipt_rules_ctr) { $ipt_rules_ctr *= 2 if $ipt_drop; $ipt_rules_ctr *= 2 if $ipt_reject; push @ipt_script_lines, qq|\$ECHO " Rules added: $ipt_rules_ctr"|; } unless ($include_sids) { if ($ipt_sync) { printf("%-10s%-10s%-10s%-10s\n", $parsed_ctr, $unsup_ctr, $ipt_apply_ctr, $rule_num); } else { printf("%-10s%-10s%-10s\n", $parsed_ctr, $unsup_ctr, $rule_num); } } } unless ($include_sids) { if ($ipt_sync) { printf("%30s", ' '); print "=======================================\n"; printf("%30s%-10s%-10s%-10s%-10s\n", ' ', $abs_num, $tot_unsup_ctr, $tot_ipt_apply, $sabs_num); } else { printf("%30s", ' '); print "=============================\n"; printf("%30s%-10s%-10s%-10s\n", ' ', $abs_num, $tot_unsup_ctr, $sabs_num); } print "\n"; if ($abs_num) { ### we parsed at least one rule print "[+] Generated $ipt_str rules for $abs_num out of ", "$sabs_num signatures: ", sprintf("%.2f", $abs_num/$sabs_num*100), "%\n"; } else { print "[+] No rules parsed.\n"; } if ($ipt_sync) { print "[+] Found $tot_ipt_apply applicable snort rules to your " . "current $ipt_str\n policy.\n"; } } return; } sub parse_rule_options() { my ($rule_options, $avg_hdr_len, $line_num) = @_; my $sid = -1; my %opts = (); my @patterns = (); ### get the sid here for logging purposes if ($rule_options =~ $snort_opts{'logprefix'}{'sid'}) { $sid = $1; } else { return 0, \%opts, \@patterns; } if (%exclude_sids) { return 0, \%opts, \@patterns if defined $exclude_sids{$sid}; } if (%include_sids) { if (defined $include_sids{$sid}) { &logr("[+] matched sid:$sid: $rule_options"); } else { return 0, \%opts, \@patterns; } } unless ($queue_mode or $nfqueue_mode) { ### if we're queuing packets to userspace Snort, then we don't have to ### disqualify a signature based on an option that is not supported by ### iptables my $found_unsupported = ''; for my $opt (keys %{$snort_opts{'unsupported'}}) { ### see if we match a regex belonging to an unsupported option if ($rule_options =~ $snort_opts{'unsupported'}{$opt}) { $found_unsupported .= "'$opt', "; } } if ($found_unsupported) { $found_unsupported =~ s/,\s+$//; &logr("[-] SID: $sid Unsupported option(s): $found_unsupported " . "at line: $line_num, skipping."); if (%include_sids and defined $include_sids{$sid}) { print "[-] SID: $sid contain the unsupported option(s): ", "$found_unsupported at line: $line_num\n"; } return 0, \%opts, \@patterns; } } if ($rule_options =~ /ip_proto\s*:.*ip_proto\s*:/) { &logr("[-] SID: $sid, unsupported multiple ip_proto fields at " . "line: $line_num, skipping."); return 0, \%opts, \@patterns; } for my $opt (keys %{$snort_opts{'filter'}}) { ### see if we match the option regex if ($rule_options =~ $snort_opts{'filter'}{$opt}{'regex'}) { $opts{$opt} = 1; $opts{$opt} = $1 if defined $1; ### some keywords may not have an option } } my $found_content = 0; while ($rule_options =~ /(\w+):?\s*((?:.*?[^\x5c]?))\s*;/g) { my $opt = $1; my $val = 1; $val = $2 if defined $2; ### some keywords may not have an argument if ($opt eq 'content' or $opt eq 'uricontent') { return 0, \%opts, \@patterns unless $val =~ /"$/; $val =~ s/^\s*"//; $val =~ s/"\s*$//; return 0, \%opts, \@patterns unless $val =~ /\S/; ### convert the string into a form that is more compatible ### for iptables my ($rv, $log_str, $ipt_pattern_hr) = &convert_pattern_for_iptables($val); if ($rv) { $found_content = 1; push @patterns, $ipt_pattern_hr; } else { &logr("[-] SID: $sid, $log_str"); return 0, \%opts, \@patterns; } } elsif ($opt eq 'pcre') { $val =~ s|^\s*"/||; $val =~ s|/\w{0,3}"$||; ### see if this pcre only has strings separated with ".*" or ".+" ### and if so translate to multple string matches my ($pcre_rv, $pcre_strings_ar) = &parse_pcre($val); if ($pcre_rv) { for my $str (@$pcre_strings_ar) { push @patterns, $str; } } else { unless ($queue_mode or $nfqueue_mode) { &logr("[-] SID: $sid, unsupported complex pcre: $val"); return 0, \%opts, \@patterns; } } } elsif ($opt eq 'fast_pattern') { if ($no_fast_pattern_order) { ### force it to be the first pattern so no reordering ### will happen $patterns[0]->{'fast_pattern'} = 1; } else { $patterns[$#patterns]->{'fast_pattern'} = 1; } } elsif ($opt eq 'nocase') { unless (defined $snort_opts{'ignore'}{'nocase'}) { $patterns[$#patterns]->{'nocase'} = 1; } } else { for my $key (qw(offset depth within distance)) { if ($opt eq $key) { my ($offsets_rv, $log_str) = &define_offsets(\@patterns, $avg_hdr_len, $key, $val); unless ($offsets_rv) { &logr("[-] SID: $sid, $log_str"); return 0, \%opts, \@patterns; } last; } } } } if (($queue_mode or $nfqueue_mode) and not $found_content) { my $queue_str = 'QUEUE'; $queue_str = 'NFQUEUE' if $nfqueue_mode; &logr("[-] SID: $sid In --$queue_str mode signature must have " . "'content' or 'uricontent' keyword " . "at line: $line_num, skipping."); if (%include_sids and defined $include_sids{$sid}) { print "[-] SID: $sid does not contain 'content' ", "or 'uricontent'\n"; } return 0, \%opts, \@patterns; } ### update offset, depth, within, and distance values for relative ### matches my ($offsets_rv, $log_str) = &update_offsets_relative_matches(\@patterns); unless ($offsets_rv) { &logr("[-] SID: $sid, $log_str"); return 0, \%opts, \@patterns; } for my $opt (keys %{$snort_opts{'logprefix'}}) { if ($rule_options =~ $snort_opts{'logprefix'}{$opt}) { $opts{$opt} = $1; } } unless ($queue_mode or $nfqueue_mode) { while ($rule_options =~ /(\w+):\s*.*?;/g) { my $option = $1; if (not defined $opts{$option} and not defined $snort_opts{'ignore'}{$option}) { &logr("[-] SID: $sid bad option: \"$option\" at line: $line_num " . "-- $rule_options"); return 0, \%opts, \@patterns; } } if (defined $opts{'ipopts'} and $opts{'ipopts'} ne 'rr' and $opts{'ipopts'} ne 'ts' and $opts{'ipopts'} ne 'ssrr' and $opts{'ipopts'} ne 'lsrr' and $opts{'ipopts'} ne 'any') { &logr("[-] SID: $sid, unsupported ipopts field at " . "line: $line_num, skipping."); return 0, \%opts, \@patterns; } if (defined $opts{'itype'} and ($opts{'itype'} =~ m|<| or $opts{'itype'} =~ m|>|)) { &logr("[-] SID: $sid, unsupported range operator in itype field " . "line: $line_num, skipping."); return 0, \%opts, \@patterns; } if (defined $opts{'icode'} and ($opts{'icode'} =~ m|<| or $opts{'icode'} =~ m|>|)) { &logr("[-] SID: $sid, unsupported range operator in icode field " . "line: $line_num, skipping."); return 0, \%opts, \@patterns; } if (defined $opts{'ip_proto'} and ($opts{'ip_proto'} =~ m|<| or $opts{'ip_proto'} =~ m|>|)) { &logr("[-] SID: $sid, unsupported range operator in ip_proto field " . "line: $line_num, skipping."); return 0, \%opts, \@patterns; } } ### success return 1, \%opts, \@patterns; } sub parse_rule_hdr() { my ($rule_hdr, $line_num) = @_; my $bidir = 0; my $action = 'alert'; ### default if ($rule_hdr =~ /^\s*pass/) { $action = 'pass'; } elsif ($rule_hdr =~ /^\s*log/) { $action = 'log'; } if ($rule_hdr =~ m|^\s*\w+\s+(\S+)\s+(\S+)\s+(\S+) \s+(\S+)\s+(\S+)\s+(\S+)|ix) { my $proto = lc($1); my $src = $2; my $sport = $3; my $bidir = $4; my $dst = $5; my $dport = $6; unless ($proto =~ /^\w+$/) { &logr("[-] Unsupported protocol: \"$proto\" at line: " . "$line_num, skipping."); return {}; } ### in --ip6tables mode make sure we're not looking at IPv4 addresses if ($enable_ip6tables and ($src =~ /\b$ip_re\b/ or $dst =~ /\b$ip_re\b/)) { &logr("[-] --ip6tables mode enabled but IPv4 " . "address in rule variable at line: $line_num."); return {}; } ### in --ip6tables mode exclude the icmp protocol - maybe should ### change to icmp6 in the future if ($enable_ip6tables and $proto eq 'icmp') { &logr("[-] --ip6tables mode enabled, so excluding " . "icmp (non-icmp6) siganture at line: $line_num."); return {}; } my $bidir_flag = 0; $bidir_flag = 1 if $bidir eq '<>'; my %hsh = ( 'action' => $action, 'proto' => $proto, 'src' => $src, 'sport' => $sport, 'bidir' => $bidir_flag, 'dst' => $dst, 'dport' => $dport, ); ### map to expanded values (e.g. $HOME -> "any" or whatever ### is defined in fwsnort.conf) for my $var (qw(src sport dst dport)) { my $val = $hsh{$var}; my $negate_flag = 0; $negate_flag = 1 if $val =~ m|!|; while ($val =~ /\$(\w+)/) { $val = $1; if (defined $config{$val}) { $val = $config{$val}; if ($enable_ip6tables and $val =~ /\b$ip_re\b/) { &logr("[-] --ip6tables mode enabled but IPv4 " . "address in rule variable at line: $line_num."); return {}; } } else { &logr("[-] Undefined variable $val in rule header " . "at line: $line_num."); return {}; } } if ($negate_flag and $val !~ m|!|) { $hsh{$var} = "!$val"; } else { $hsh{$var} = $val; } } for my $var (qw(sport dport)) { $hsh{$var} =~ s/\[//; $hsh{$var} =~ s/\]//; next unless $hsh{$var} =~ /,/; if ($ipt_have_multiport_match) { my $ctr = 1; my @ports = split /\s*,\s*/, $hsh{$var}; my $ports_str = ''; for my $port (@ports) { if ($port =~ /\d+:$/) { $ports_str .= "${port}65535,"; } else { $ports_str .= "${port},"; } $ctr++; $ctr++ if $port =~ /\:/; ### a range counts for two ports ### multiport is limited to 15 ports last if $ctr >= $ipt_multiport_max; } $ports_str =~ s/,$//; $hsh{$var} = $ports_str; } else { &logr("[-] Warning: taking the first port in the list " . "$hsh{$var} until the $ipt_str multiport match is supported " . "at line: $line_num."); $hsh{$var} =~ s/,.*//; } } return \%hsh; } return {}; } sub parse_pcre() { my $pcre = shift; my $rv = 0; my @patterns = (); if ($pcre =~ m|^\w+$|) { push @patterns, (&convert_pattern_for_iptables($pcre))[2]; $rv = 1; } elsif ($pcre =~ m|UNION\x5c\x73\x2bSELECT|) { ### a bunch of Emerging Threats rules contain "UNION\s+SELECT" ### as a PCRE. Sure, the translation below can be evaded, but ### it is better than nothing. push @patterns, (&convert_pattern_for_iptables('UNION SELECT'))[2]; $rv = 1; } else { my @ar = (); if ($pcre =~ m|\.\*|) { @ar = split /\.\*/, $pcre; $rv = 1; } elsif ($pcre =~ m|\.\+|) { @ar = split /\.\+/, $pcre; $rv = 1; } elsif ($pcre =~ m|\x5b\x5e\x5c\x6e\x5d\x2b|) { ### [^\n]+ @ar = split /\x5b\x5e\x5c\x6e\x5d\x2b/, $pcre; $rv = 1; } if ($rv == 1) { for my $part (@ar) { next unless $part; ### some Snort pcre's begin with .* or .+ ### (which seems useless) ### Replace "\(" with hex equivalent in PCRE's ### like: /.+ASCII\(.+SELECT/ $part =~ s/\x5c\x28/|5c 28|/; ### Replace "\:" with hex equivalent in PCRE's ### like: /User-Agent\:[^\n]+spyaxe/ $part =~ s/\x5c\x3a/|5c 3a|/; my $basic = $part; $basic =~ s/\|5c 28\|//; $basic =~ s/\|5c 3a\|//; if ($basic =~ /^[\w\x20]+$/) { push @patterns, (&convert_pattern_for_iptables($part))[2]; } elsif ($basic eq 'User-Agent') { push @patterns, (&convert_pattern_for_iptables($part))[2]; } else { $rv = 0; } } } } return $rv, \@patterns; } sub queue_get_rule() { my ($rule_hdr, $rule_opts) = @_; ### FIXME: the following commented out code would need to be ### drastically improved to ensure that the remaining signatures ### are completely unique in userspace. For now, just return ### the original Snort rule ### Remove all of the following keywords since they are handled ### within the kernel directly. # for my $key qw/uricontent content offset depth within distance/ { # $rule_opts =~ s/([\s;])$key:\s*.*?\s*;\s*/$1/g; # } $rule_opts =~ s/^\s*//; $rule_opts =~ s/\s*$//; return "$rule_hdr ($rule_opts)"; } sub ipt_allow_traffic() { my ($hdr_hr, $opts_hr, $chain, $orig_snort_rule) = @_; my $rule_ctr = 0; if ($dump_snort) { print "\n[+] Snort rule: $orig_snort_rule" unless defined $snort_dump_cache{$orig_snort_rule}; $snort_dump_cache{$orig_snort_rule} = ''; } ### check to see if the header is allowed through the chain, ### and if not we don't really care about matching traffic ### because iptables doesn't allow it anyway RULE: for my $rule_hr (@{$ipt_policy{$chain}}) { $rule_ctr++; if ($dumper and $verbose) { print "[+] RULE: $rule_ctr:\n", Dumper($rule_hr); } if ($dump_ipt) { print "[+] $ipt_str rule: $rule_hr->{'raw'}\n" unless defined $ipt_dump_cache{$rule_hr->{'raw'}}; $ipt_dump_cache{$rule_hr->{'raw'}} = ''; } ### don't match on rules to/from the loopback interface unless ($no_exclude_loopback) { if ($rule_hr->{'intf_in'} eq 'lo' or $rule_hr->{'intf_out'} eq 'lo') { print "[-] Skipping $chain rule $rule_ctr: loopback rule\n" if $debug; next RULE; } } ### don't match on rules that build state if ($rule_hr->{'extended'} =~ /state/) { print "[-] Skipping $chain rule $rule_ctr: state rule\n" if $debug; next RULE; } ### match protocol unless (($hdr_hr->{'proto'} eq $rule_hr->{'proto'} or $rule_hr->{'proto'} eq 'all')) { print "[-] Skipping $chain rule $rule_ctr: $hdr_hr->{'proto'} ", "!= $rule_hr->{'proto'}\n" if $debug; next RULE; } ### match src/dst IP/network unless (&match_addr($hdr_hr->{'src'}, $rule_hr->{'src'})) { print "[-] Skipping $chain rule $rule_ctr: src $hdr_hr->{'src'} ", "not part of $rule_hr->{'src'}\n" if $debug; next RULE; } unless (&match_addr($hdr_hr->{'dst'}, $rule_hr->{'dst'})) { print "[-] Skipping $chain rule $rule_ctr: dst $hdr_hr->{'dst'} ", "not part of $rule_hr->{'dst'}\n" if $debug; next RULE; } ### match src/dst ports if ($hdr_hr->{'proto'} ne 'icmp') { unless (&match_port($hdr_hr->{'sport'}, $rule_hr->{'sport'})) { print "[-] Skipping $chain rule $rule_ctr: sport ", "$hdr_hr->{'sport'} not part of $rule_hr->{'sport'}\n" if $debug; next RULE; } unless (&match_port($hdr_hr->{'dport'}, $rule_hr->{'dport'})) { print "[-] Skipping $chain rule $rule_ctr: dport ", "$hdr_hr->{'dport'} not part of $rule_hr->{'dport'}\n" if $debug; next RULE; } } if (defined $opts_hr->{'flow'} and $rule_hr->{'state'}) { if ($opts_hr->{'flow'} eq 'established') { unless ($rule_hr->{'state'} =~ /ESTABLISHED/) { print "[-] Skipping $chain rule $rule_ctr: state ", "$opts_hr->{'flow'} not part of $rule_hr->{'state'}\n" if $debug; next RULE; } } } ### if we make it here, then this rule matches the signature ### (from a header perspective) if ($rule_hr->{'target'} eq 'DROP' or $rule_hr->{'target'} eq 'REJECT') { print "[-] Matching $ipt_str rule has DROP or REJECT target; ", "$ipt_str policy does not allow this Snort rule.\n" if $debug; if ($dumper) { print "\n[-] RULE $chain DROP:\n", Dumper($hdr_hr), Dumper($opts_hr), Dumper($rule_hr), "\n"; } return 0; } elsif ($rule_hr->{'target'} eq 'ACCEPT') { if ($dumper) { print "\n[+] RULE $chain ACCEPT:\n", Dumper($hdr_hr), Dumper($opts_hr), Dumper($rule_hr), "\n"; } print "[-] Matching $ipt_str rule has ACCEPT target; ", "$ipt_str policy allows this Snort rule.\n" if $debug; return 1; } ### we don't support other targets besides DROP, REJECT, ### or ACCEPT for now. } ### if we make it here, then no specific ACCEPT rule matched the header, ### so return false if the chain policy is set to DROP (or there is ### a default drop rule). Otherwise there is no rule that would block ### the traffic. if (defined $ipt_default_policy_setting{$chain}) { if ($ipt_default_policy_setting{$chain} eq 'ACCEPT') { if (defined $ipt_default_drop{$chain}) { if (defined $ipt_default_drop{$chain}{'all'}) { print "[-] Default DROP rule applies to this Snort rule.\n" if $debug; return 0; } elsif (defined $ipt_default_drop{$chain} {$hdr_hr->{'proto'}}) { print "[-] Default DROP rule applies to this Snort rule.\n" if $debug; return 0; } } if ($dumper) { print "\nACCEPT $chain, no $ipt_str matching rule\n", Dumper($hdr_hr), Dumper($opts_hr), "\n"; } return 1; } } if ($dumper) { print "\nDROP $chain, no $ipt_str matching rule\n", Dumper($hdr_hr), Dumper($opts_hr), "\n"; } ### maybe a "strict" option should be added here? return 0; } sub match_addr() { my ($hdr_src, $rule_src) = @_; return 1 if $rule_src eq '0.0.0.0/0'; return 1 if $hdr_src =~ /any/i; return 1 if $hdr_src eq $rule_src; my $ipt_ip = ''; my $ipt_mask = '32'; my $negate = 0; my $s_obj = ''; my $ipt_obj = ''; $negate = 1 if $hdr_src =~ /\!/; if ($rule_src =~ /\!/) { if ($negate) { ### if both hdr_src and rule_src are negated ### then revert to normal match. $negate = 0; } else { $negate = 1; } } if ($rule_src =~ m|($ip_re)/($ip_re)|) { $ipt_ip = $1; $ipt_mask = $2; } elsif ($rule_src =~ m|($ip_re)/(\d+)|) { $ipt_ip = $1; $ipt_mask = $2; } elsif ($rule_src =~ m|($ip_re)|) { $ipt_ip = $1; } $ipt_obj = new NetAddr::IP($ipt_ip, $ipt_mask); for my $addr (@{&expand_addresses($hdr_src)}) { my $src_ip = ''; my $src_mask = '32'; if ($addr =~ m|($ip_re)/($ip_re)|) { $src_ip = $1; $src_mask = $2; } elsif ($addr =~ m|($ip_re)/(\d+)|) { $src_ip = $1; $src_mask = $2; } elsif ($addr =~ m|($ip_re)|) { $src_ip = $1; } $s_obj = new NetAddr::IP($src_ip, $src_mask); if ($negate) { return 1 unless $ipt_obj->within($s_obj); } else { return 1 if $ipt_obj->within($s_obj); } } return 0; } sub match_port() { my ($snort_port, $ipt_port) = @_; return 1 if $ipt_port eq '0:0'; return 1 if $snort_port =~ /any/i; return 1 if $ipt_port eq $snort_port; my $ipt_start = 0; my $ipt_end = 65535; my $h_start = 0; my $h_end = 65535; if ($ipt_port =~ /:/) { if ($ipt_port =~ /(\d+):/) { $ipt_start = $1; } if ($ipt_port =~ /:(\d+)/) { $ipt_end = $1; } } elsif ($ipt_port =~ /(\d+)/) { $ipt_start = $ipt_end = $1; } if ($snort_port =~ /:/) { if ($snort_port =~ /(\d+):/) { $h_start = $1; } if ($snort_port =~ /:(\d+)/) { $h_end = $1; } } elsif ($snort_port =~ /(\d+)/) { $h_start = $h_end = $1; } if ($ipt_port =~ /!/) { if ($snort_port =~ /!/) { return 0; } else { return 1 if (($h_start < $ipt_start and $h_end < $ipt_start) or ($h_start > $ipt_end and $h_end > $ipt_end)); } } else { if ($snort_port =~ /!/) { return 1 if (($ipt_start < $h_start and $ipt_end < $h_start) or ($ipt_start > $h_end and $ipt_end > $h_end)); } else { return 1 if $h_start >= $ipt_start and $h_end <= $ipt_end; } } return 0; } sub cache_ipt_policy() { my $ipt = new IPTables::Parse 'iptables' => $cmds{'iptables'} or die "[*] Could not acquire IPTables::Parse object: $!"; for my $chain (keys %process_chains) { next unless $process_chains{$chain}; $ipt_policy{$chain} = $ipt->chain_rules('filter', $chain, $ipt_file); $ipt_default_policy_setting{$chain} = $ipt->chain_policy('filter', $chain, $ipt_file); my ($def_drop_hr, $ipt_rv) = $ipt->default_drop('filter', $chain, $ipt_file); if ($ipt_rv) { $ipt_default_drop{$chain} = $def_drop_hr; } } return; } sub ipt_build() { my ($snort_hdr_hr, $snort_opts_hr, $patterns_ar, $orig_snort_rule) = @_; my $found_rule = 0; my $num_rules = 0; my %process_rules = (); ### define iptables source and destination if ($snort_hdr_hr->{'dst'} =~ /any/i) { if ($snort_hdr_hr->{'src'} =~ /any/i) { if ($orig_snort_rule =~ m|\$HOME_NET.*\-\>\s+\$EXTERNAL_NET|) { push @{$process_rules{'OUTPUT'}}, '' if $process_chains{'OUTPUT'}; } else { push @{$process_rules{'INPUT'}}, '' if $process_chains{'INPUT'}; } push @{$process_rules{'FORWARD'}}, '' if $process_chains{'FORWARD'}; } else { my $addr_ar = &expand_addresses($snort_hdr_hr->{'src'}); my $negate = ''; $negate = '! ' if $snort_hdr_hr->{'src'} =~ m|!|; unless ($addr_ar) { &logr("[-] No valid source IPs/networks in Snort " . "rule header."); return 0, 0; } for my $src (@$addr_ar) { if (&is_local($src)) { push @{$process_rules{'OUTPUT'}}, "$negate$ipt_hdr_opts{'src'} ${src}" if $process_chains{'OUTPUT'}; } else { push @{$process_rules{'INPUT'}}, "$negate$ipt_hdr_opts{'src'} ${src}" if $process_chains{'INPUT'}; } push @{$process_rules{'FORWARD'}}, "$negate$ipt_hdr_opts{'src'} ${src}" if $process_chains{'FORWARD'}; } } } else { my $dst_addr_ar = &expand_addresses($snort_hdr_hr->{'dst'}); unless ($dst_addr_ar) { &logr("[-] No valid destination IPs/networks in Snort rule " . "header."); return 0, 0; } if ($snort_hdr_hr->{'src'} =~ /any/i) { my $negate = ''; $negate = '! ' if $snort_hdr_hr->{'dst'} =~ m|!|; for my $dst (@$dst_addr_ar) { if (&is_local($dst)) { push @{$process_rules{'INPUT'}}, "$negate$ipt_hdr_opts{'dst'} ${dst}" if $process_chains{'INPUT'}; } else { push @{$process_rules{'OUTPUT'}}, "$negate$ipt_hdr_opts{'dst'} ${dst}" if $process_chains{'OUTPUT'}; } push @{$process_rules{'FORWARD'}}, "$negate$ipt_hdr_opts{'dst'} ${dst}" if $process_chains{'FORWARD'}; } } else { my $src_addr_ar = &expand_addresses($snort_hdr_hr->{'src'}); my $negate_src = ''; $negate_src = '! ' if $snort_hdr_hr->{'src'} =~ m|!|; my $negate_dst = ''; $negate_dst = '! ' if $snort_hdr_hr->{'dst'} =~ m|!|; unless ($src_addr_ar) { &logr("[-] No valid source IPs/networks in Snort rule " . "header."); return 0, 0; } for my $src (@$src_addr_ar) { for my $dst (@$dst_addr_ar) { if (&is_local($dst)) { push @{$process_rules{'INPUT'}}, "$negate_src$ipt_hdr_opts{'src'} ${src}" . " $negate_dst$ipt_hdr_opts{'dst'} ${dst}" if $process_chains{'INPUT'}; } else { push @{$process_rules{'OUTPUT'}}, "$negate_src$ipt_hdr_opts{'src'} ${src}" . " $negate_dst$ipt_hdr_opts{'dst'} ${dst}" if $process_chains{'OUTPUT'}; } push @{$process_rules{'FORWARD'}}, "$negate_src$ipt_hdr_opts{'src'} ${src}" . " $negate_dst$ipt_hdr_opts{'dst'} ${dst}" if $process_chains{'FORWARD'}; } } } } ### determine which chain (e.g. stateful/stateless) my $flow_established = ''; unless ($no_ipt_conntrack) { if (defined $snort_hdr_hr->{'proto'} and $snort_hdr_hr->{'proto'} =~ /tcp/i and defined $snort_opts_hr->{'flow'} and $snort_opts_hr->{'flow'} =~ /established/i) { $flow_established = 'ESTABLISHED'; } } my $add_snort_comment = 1; my $add_perl_trigger = 1; for my $chain (keys %process_chains) { next unless $process_chains{$chain} and $process_rules{$chain}; for my $src_dst (@{$process_rules{$chain}}) { my $rule = "\$${ipt_var_str} -A "; my $save_rule = '-A'; my $fwsnort_chain = ''; ### see if we can jump to the ESTABLISHED inspection chain. if ($flow_established) { $rule .= $config{"FWSNORT_${chain}_ESTAB"}; $fwsnort_chain = $config{"FWSNORT_${chain}_ESTAB"}; } else { $rule .= $config{"FWSNORT_$chain"}; $fwsnort_chain = $config{"FWSNORT_$chain"}; } ### append interface restriction if necessary if ($src_dst =~ m|127\.0\.0\.\d/|) { if ($chain eq 'INPUT' or $chain eq 'FORWARD') { $rule .= ' ! -i lo'; } elsif ($chain eq 'OUTPUT') { $rule .= ' ! -o lo'; } } ### append source and destination criteria if ($src_dst) { if ($chain eq 'FORWARD') { $rule .= " $src_dst"; } elsif ($chain eq 'INPUT') { ### we always treat the INPUT chain as part of the HOME_NET; ### the system running iptables may have an interface on the ### external network and hence may not be part of the HOME_NET ### as defined in the fwsnort.conf file so we don't necessarily ### append the IP criteria if ($src_dst ne "$ipt_hdr_opts{'dst'} $config{'HOME_NET'}") { $rule .= " $src_dst"; } } elsif ($chain eq 'OUTPUT') { if ($src_dst ne "$ipt_hdr_opts{'src'} $config{'HOME_NET'}") { $rule .= " $src_dst"; } } } my $rv = &ipt_build_rule( $chain, $fwsnort_chain, $rule, $snort_hdr_hr, $snort_opts_hr, $patterns_ar, $orig_snort_rule, $flow_established, $add_snort_comment, $add_perl_trigger ); if ($rv) { $found_rule = 1; $add_snort_comment = 0; $add_perl_trigger = 0; $num_rules++; } } } return $found_rule, $num_rules; } sub is_local() { my $addr = shift; return 1 if $no_addr_check; my $ip = ''; my $mask = '32'; if ($addr =~ m|($ip_re)/($ip_re)|) { $ip = $1; $mask = $2; } elsif ($addr =~ m|($ip_re)/(\d+)|) { $ip = $1; $mask = $2; } elsif ($addr =~ m|($ip_re)|) { $ip = $1; } my $ip_obj = new NetAddr::IP($ip, $mask); for my $local_ar (@local_addrs) { my $local_ip = $local_ar->[0]; my $local_mask = $local_ar->[1]; my $local_obj = new NetAddr::IP($local_ip, $local_mask); return 1 if $ip_obj->within($local_obj); } return 0; } sub get_local_addrs() { my $intf_name = ''; if (-x $cmds{'ip'}) { open IP, "$cmds{'ip'} addr show |" or die "[*] Could not run ", "$cmds{'ip'}: $!"; while () { if (/^\d+\:\s+(\w)\:/) { $intf_name = $1; next; } next if $intf_name eq 'lo'; next if $intf_name =~ /dummy/i; if (m|^\s+inet\s($ip_re)/(\d+)|) { push @local_addrs, [$1, $2]; } } close IP; } else { open IFC, "$cmds{'ifconfig'} -a |" or die "[*] Could not run ", "$cmds{'ifconfig'}: $!"; my @lines = ; close IFC; for my $line (@lines) { if ($line =~ /^(\w+)\s+Link/) { $intf_name = $1; next; } next if $intf_name eq 'lo'; next if $intf_name =~ /dummy/i; if ($line =~ /^\s+inet.*?:($ip_re).*:($ip_re)/i) { push @local_addrs, [$1, $2]; } } } return; } sub ipt_build_rule() { my ($chain, $fwsnort_chain, $rule, $hdr_hr, $opts_hr, $patterns_ar, $orig_snort_rule, $flow_logging_prefix, $add_snort_comment, $add_perl_trigger) = @_; ### $chain is used only to see whether or not we need to add the ### rule to the iptables script based on whether the built-in chain ### will pass the traffic in the first place. if ($ipt_sync) { return 0 unless &ipt_allow_traffic($hdr_hr, $opts_hr, $chain, $orig_snort_rule); } ### append the protocol to the rule if (defined $opts_hr->{'ip_proto'}) { return 0 unless $opts_hr->{'ip_proto'} =~ /^\w+$/; $rule .= " $snort_opts{'filter'}{'ip_proto'}{'iptopt'} " . "$opts_hr->{'ip_proto'}"; } else { return 0 unless $hdr_hr->{'proto'} =~ /^\w+$/; if ((($hdr_hr->{'sport'} !~ /any/i and $hdr_hr->{'sport'} ne '') or ($hdr_hr->{'dport'} !~ /any/i and $hdr_hr->{'dport'} ne '')) and $hdr_hr->{'proto'} !~ /tcp/i and $hdr_hr->{'proto'} !~ /udp/i) { ### force to tcp because iptables does not like src/dst ### ports with anything other than tcp or udp $hdr_hr->{'proto'} = 'tcp'; } if ($hdr_hr->{'proto'} =~ /ip/) { ### the special iptables keyword 'all' means all IP protocols $rule .= " $ipt_hdr_opts{'proto'} all"; } else { $rule .= " $ipt_hdr_opts{'proto'} $hdr_hr->{'proto'} " . "-m $hdr_hr->{'proto'}"; } } ### append the source and destination ports for my $type (qw(sport dport)) { if (defined $hdr_hr->{$type} and $hdr_hr->{$type} !~ /any/i) { my $negate = ''; my $port = $hdr_hr->{$type}; $negate = '! ' if $port =~ m|!|; $port =~ s/\!\s*(\d)/$1/; if ($port =~ /,/) { ### multiport match $rule .= " -m multiport ${negate}--${type}s $port"; } else { $rule .= " ${negate}$ipt_hdr_opts{$type} $port"; } } } my $rv = &ipt_build_opts($rule, $hdr_hr, $opts_hr, $patterns_ar, $orig_snort_rule, $flow_logging_prefix, $chain, $fwsnort_chain, $add_snort_comment, $add_perl_trigger); return $rv; } sub ipt_build_opts() { my ($rule, $hdr_hr, $opts_hr, $patterns_ar, $orig_snort_rule, $flow_logging_prefix, $chain, $fwsnort_chain, $add_snort_comment, $add_perl_trigger) = @_; ### append tcp flags if (defined $opts_hr->{'flags'}) { my $f_str = ''; $f_str .= 'URG,' if $opts_hr->{'flags'} =~ /U/i; $f_str .= 'ACK,' if $opts_hr->{'flags'} =~ /A/i; $f_str .= 'PSH,' if $opts_hr->{'flags'} =~ /P/i; $f_str .= 'RST,' if $opts_hr->{'flags'} =~ /R/i; $f_str .= 'SYN,' if $opts_hr->{'flags'} =~ /S/i; $f_str .= 'FIN,' if $opts_hr->{'flags'} =~ /F/i; $f_str =~ s/\,$//; if ($opts_hr->{'flags'} =~ /\+/) { ### --tcp-flags ACK ACK $rule .= " $snort_opts{'filter'}{'flags'}{'iptopt'} " . "$f_str $f_str"; } else { ### --tcp-flags ALL URG,PSH,SYN,FIN $rule .= " $snort_opts{'filter'}{'flags'}{'iptopt'} " . "ALL $f_str"; } } if ($no_ipt_conntrack) { ### fall back to appending --tcp-flags ACK ACK if flow=established. ### NOTE: we can't really handle "flow" in the same way snort can, ### since there is no way to keep track of which side initiated the ### tcp session (where the SYN packet came from), but older versions ### of snort (pre 1.9) just used tcp flags "A+" to keep track of ### this... we need to do the same. if (defined $opts_hr->{'flow'} && ! defined $opts_hr->{'flags'}) { if ($opts_hr->{'flow'} =~ /established/i) { ### note that this ignores the "stateless" keyword ### as it should... $rule .= " $snort_opts{'filter'}{'flow'}{'iptopt'} ACK ACK"; } } } ### append icmp type if ($hdr_hr->{'proto'} =~ /icmp/i) { if (defined $opts_hr->{'itype'}) { $rule .= " $snort_opts{'filter'}{'itype'}{'iptopt'} " . "$opts_hr->{'itype'}"; ### append icmp code (becomes "--icmp-type type/code") if (defined $opts_hr->{'icode'}) { $rule .= "/$opts_hr->{'icode'}"; } } else { ### append the default icmp type since some recent versions of ### iptables (such as 1.4.12 on Fedora 16) require it - an error ### like the following will be thrown if it's not there: ### iptables-restore v1.4.12: icmp: option "--icmp-type" must be specified $rule .= " $snort_opts{'filter'}{'itype'}{'iptopt'} " . $default_icmp_type; } } ### append ip options if (defined $opts_hr->{'ipopts'}) { $rule .= " $snort_opts{'filter'}{'ipopts'}{'iptopt'} " . "--$opts_hr->{'ipopts'}" } ### append tos (requires CONFIG_IP_NF_MATCH_TOS) if (defined $opts_hr->{'tos'}) { $rule .= " $snort_opts{'filter'}{'tos'}{'iptopt'} " . "$opts_hr->{'tos'}" } ### append ttl (requires CONFIG_IP_NF_MATCH_TTL) if (defined $opts_hr->{'ttl'}) { if ($opts_hr->{'ttl'} =~ /\<\s*(\d+)/) { $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} --ttl-lt $1"; } elsif ($opts_hr->{'ttl'} =~ /\>\s*(\d+)/) { $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} --ttl-gt $1"; } else { $rule .= " $snort_opts{'filter'}{'ttl'}{'iptopt'} " . "--ttl-eq $opts_hr->{'ttl'}"; } } my $avg_hdr_len = &get_avg_hdr_len($hdr_hr); ### append dsize option (requires CONFIG_IP_NF_MATCH_LENGTH) if (defined $opts_hr->{'dsize'}) { ### get the average packet header size based on the protocol ### (the iptables length match applies to the network header ### and up). if ($opts_hr->{'dsize'} =~ m|(\d+)\s*<>\s*(\d+)|) { my $iptables_len1 = $1 + $avg_hdr_len; my $iptables_len2 = $2 + $avg_hdr_len; $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " . "$iptables_len1:$iptables_len2"; } elsif ($opts_hr->{'dsize'} =~ m|<\s*(\d+)|) { my $iptables_len = $1 + $avg_hdr_len; $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " . "$avg_hdr_len:$iptables_len"; } elsif ($opts_hr->{'dsize'} =~ m|>\s*(\d+)|) { my $iptables_len = $1 + $avg_hdr_len; if ($iptables_len < $config{'MAX_FRAME_LEN'} + $avg_hdr_len) { $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " . "$iptables_len:" . ($config{'MAX_FRAME_LEN'} + $avg_hdr_len); } } elsif ($opts_hr->{'dsize'} =~ m|(\d+)|) { my $iptables_len = $1 + $avg_hdr_len; $rule .= " $snort_opts{'filter'}{'dsize'}{'iptopt'} " . $iptables_len; } } ### append snort content options my $ipt_content_criteria = 0; my $perl_trigger_str = ''; if ($#$patterns_ar > -1) { ($ipt_content_criteria, $perl_trigger_str) = &build_content_matches($opts_hr, $patterns_ar); return 0 unless $ipt_content_criteria; $rule .= $ipt_content_criteria; } ### print the rest of the logprefix snort options in a comment ### one line above the rule my $comment = ''; my $target_str = ''; for my $key (qw(sid msg classtype reference priority rev)) { if (defined $opts_hr->{$key}) { $comment .= qq|$key:$opts_hr->{$key}; |; } } $comment =~ s/\s*$//; $comment =~ s/,$//; ### append the fwsnort version as "FWS:$version" $comment .= " FWS:$version;"; ### build up the logging prefix and comment match if (defined $opts_hr->{'sid'}) { unless ($no_ipt_comments) { ### add the Snort msg (and other) fields to the iptables rule ### with the 'comment' match (which can handle up to 255 chars ### and is set/verified by the ipt_find_max_comment_len() ### function). $comment =~ s|\"||g; $comment =~ s|/\*||g; $comment =~ s|\*/||g; if (length($comment) < $ipt_max_comment_len) { $target_str = qq| -m comment --comment "$comment"|; } } ### increment chain counter and add in if necessary $chain_ctr{$chain}++; if ($queue_mode or $nfqueue_mode) { if ($queue_mode) { $target_str .= qq| -j QUEUE|; } else { $target_str .= qq| -j NFQUEUE|; if ($nfqueue_num) { $target_str .= " --queue-num $nfqueue_num"; } } } else { if ($hdr_hr->{'action'} eq 'log' or $ulog_mode) { $target_str .= qq| -j ULOG --ulog-nlgroup $ulog_nlgroup --ulog-prefix |; } else { $target_str .= ' -j LOG '; $target_str .= '--log-ip-options ' unless $no_ipt_log_ip_opts; if ($hdr_hr->{'proto'} eq 'tcp') { $target_str .= '--log-tcp-options ' unless $no_ipt_log_tcp_opts; $target_str .= '--log-tcp-sequence ' if $ipt_log_tcp_seq; } $target_str .= qq|--log-prefix |; } ### build up the LOG prefix string my $prefix_str = ''; unless ($no_ipt_rule_nums) { $prefix_str .= "[$chain_ctr{$chain}] "; } if ($ipt_drop) { $prefix_str .= 'DRP '; } elsif ($ipt_reject) { $prefix_str .= 'REJ '; } ### always add the sid $prefix_str .= qq|SID$opts_hr->{'sid'} |; if ($flow_logging_prefix) { $prefix_str .= 'ESTAB '; } if (length($prefix_str) >= $ipt_max_log_prefix_len) { $prefix_str = qq|SID$opts_hr->{'sid'} |; if (length($prefix_str) >= $ipt_max_log_prefix_len) { return 0 unless $ipt_content_criteria; } } $target_str .= qq|"| . $prefix_str . qq|"|; } } ### print the snort rules type header to the fwsnort.sh script unless ($ipt_print_type) { &ipt_type($snort_type); $ipt_print_type = 1; } ### write the rule out to the iptables script &ipt_add_rule($hdr_hr, $opts_hr, $orig_snort_rule, $rule, $target_str, "### $comment", $add_snort_comment, $perl_trigger_str, $add_perl_trigger, $chain, $fwsnort_chain); return 1; } sub build_content_matches() { my ($opts_hr, $patterns_ar) = @_; my $fast_pattern_index = 0; my $fast_pattern_is_set = 0; my $ipt_content_criteria = ''; my $perl_trigger_command = ''; my @content_fast_pattern_order = (); $perl_trigger_command = q|perl -e 'print "| if $include_perl_triggers; if ($no_fast_pattern_order) { $patterns_ar->[0]->{'fast_pattern'} = 1; } for (my $index=0; $index <= $#$patterns_ar; $index++) { if ($patterns_ar->[$index]->{'fast_pattern'}) { $fast_pattern_index = $index; $fast_pattern_is_set = 1; last; } } unless ($fast_pattern_is_set) { ### in this case, the 'fast_pattern' option was not used, so pick the ### longest pattern to match first (this should help with performance ### of signature matches on average) my $max_len = 0; my $max_len_index = 0; PATTERN: for (my $index=0; $index <= $#$patterns_ar; $index++) { my $pat_ar = $patterns_ar->[$index]; if ($pat_ar->{'length'} > $max_len) { ### make sure it is not a relative match next PATTERN if defined $pat_ar->{'distance'}; next PATTERN if defined $pat_ar->{'within'}; if ($index < $#$patterns_ar) { my $next_pat_ar = $patterns_ar->[$index+1]; next PATTERN if defined $next_pat_ar->{'distance'}; next PATTERN if defined $next_pat_ar->{'within'}; } $max_len = $pat_ar->{'length'}; $max_len_index = $index; } } $fast_pattern_index = $max_len_index; } $content_fast_pattern_order[0] = $patterns_ar->[$fast_pattern_index]; for (my $i=0; $i <= $#$patterns_ar; $i++) { next if $i == $fast_pattern_index; push @content_fast_pattern_order, $patterns_ar->[$i]; } for (my $i=0; $i <= $#content_fast_pattern_order; $i++) { if (($queue_mode or $nfqueue_mode) and $queue_pre_match_max > 0) { ### limit the number of content matches to perform within the ### kernel before sending the packet to a userspace Snort ### instance last if $i >= $queue_pre_match_max; } my $pattern_hr = $content_fast_pattern_order[$i]; my $content_str = $pattern_hr->{'ipt_pattern'}; if ($content_str =~ /\|.+\|/) { ### there is hex data in the content if ($pattern_hr->{'negative_match'}) { $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'content'}{'iptopt'} . ' ' . qq{! --hex-string "$content_str"}; } else { $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'content'}{'iptopt'} . ' ' . qq{--hex-string "$content_str"}; } } else { ### there is no hex data in the content if ($pattern_hr->{'negative_match'}) { $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'content'}{'iptopt'} . ' ' . qq{! --string "$content_str"}; } else { $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'content'}{'iptopt'} . ' ' . qq{--string "$content_str"}; } } if (defined $opts_hr->{'replace'}) { my $replace_str = $opts_hr->{'replace'}; $replace_str =~ s/`/\\`/g; if ($replace_str =~ /\|.+\|/) { ### there is hex data in the content $ipt_content_criteria .= qq{ --replace-hex-string "$replace_str"}; } else { $ipt_content_criteria .= qq{ --replace-string "$replace_str"}; } } if ($kernel_ver ne '2.4') { $ipt_content_criteria .= " --algo $string_match_alg"; ### see if we have any offset, depth, distance, or within ### criteria if (defined $pattern_hr->{'offset'}) { $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'offset'}{'iptopt'} . " $pattern_hr->{'offset'}"; $perl_trigger_command .= 'A'x$pattern_hr->{'offset'} if $include_perl_triggers; } elsif (defined $pattern_hr->{'distance'}) { ### offset trumps distance $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'distance'}{'iptopt'} . " $pattern_hr->{'distance'}"; $perl_trigger_command .= 'A'x$pattern_hr->{'distance'} if $include_perl_triggers; } if (defined $pattern_hr->{'depth'}) { $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'depth'}{'iptopt'} . " $pattern_hr->{'depth'}"; $perl_trigger_command .= 'A'x$pattern_hr->{'depth'} if $include_perl_triggers; } elsif (defined $pattern_hr->{'within'}) { ### depth trumps within $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'within'}{'iptopt'} . " $pattern_hr->{'within'}"; $perl_trigger_command .= 'A'x$pattern_hr->{'within'} if $include_perl_triggers; } ### see if we need to match the string match case insensitive if ($pattern_hr->{'nocase'}) { $ipt_content_criteria .= ' ' . $snort_opts{'filter'} {'nocase'}{'iptopt'}; } ### if the --payload option is available for ### the string match extension $ipt_content_criteria .= ' --payload' if $ipt_has_string_payload_offset_opt; } if ($include_perl_triggers) { ### now append the perl trigger command bytes if ($pattern_hr->{'negative_match'}) { $perl_trigger_command .= 'A'x($pattern_hr->{'length'}); } else { $perl_trigger_command .= &translate_perl_trigger($content_str); } } } if ($include_perl_triggers) { $perl_trigger_command .= qq|"'|; } return $ipt_content_criteria, $perl_trigger_command; } sub convert_pattern_for_iptables() { my $snort_str = shift; my $rv = 0; my $ipt_str = $snort_str; my $log_str = ''; my %pattern = ( 'orig_snort_str' => $snort_str, 'ipt_pattern' => '', 'negative_match' => 0, 'fast_pattern' => 0, 'nocase' => 0, ); if ($ipt_str =~ /^\s*\!\s*"/) { $ipt_str =~ s/^\s*\!\s*"//; $pattern{'negative_match'} = 1; } ### convert all escaped chars to their hex equivalents $ipt_str =~ s/\x5c(.)/sprintf "|%02x|", (ord($1))/eg; ### consolidate any consecutive "|aa||bb|" sequences $ipt_str =~ s/\|\|//g; ### remove all spaces between hex codes (they simply waste space ### on the command line, and they aren't part of the string to ### search in network traffic anyway). $ipt_str = &consolidate_hex_spaces($ipt_str); ### if there are any existing hex blocks or if there are any chars ### that require a new hex block, then just convert the whole string ### to hex syntax for simplicity if ($ipt_str =~ /\|/ or &have_hex_char($ipt_str)) { $ipt_str = &convert_str_to_hex($ipt_str); } ### if we ever have more than one hex block then there was some ### pattern translation error my $hex_ctr = 0; while ($ipt_str =~ /\|/g) { $hex_ctr++; last if $hex_ctr >= 10; } if ($hex_ctr > 2) { return 0, "too many hex blocks, could not translate: $snort_str", \%pattern; } ### handles length of hex blocks my $content_len = &get_content_len($ipt_str); if ($content_len >= $ipt_max_str_len or $content_len >= $config{'MAX_STRING_LEN'}) { return 0, "pattern too long: $snort_str", \%pattern; } $pattern{'ipt_pattern'} = $ipt_str; $pattern{'length'} = $content_len; return 1, $log_str, \%pattern; } sub define_offsets() { my ($patterns_ar, $avg_hdr_len, $opt, $val) = @_; my $current_index = $#$patterns_ar; ### the option should not be defined - if so, there is a duplicate ### Snort keyword in the signature if (defined $patterns_ar->[$current_index]->{$opt}) { return 0, "duplicate keyword: $opt"; } ### store the value that was in the original Snort rule $patterns_ar->[$current_index]->{"${opt}_snort_orig"} = $val; ### store the value and account for average header length if ### necessary if ($ipt_has_string_payload_offset_opt) { $patterns_ar->[$current_index]->{$opt} = $val; } else { $patterns_ar->[$current_index]->{$opt} = $val + $avg_hdr_len + $MAC_HDR_LEN; } return 1, ''; } sub update_offsets_relative_matches() { my $patterns_ar = shift; for (my $i=0; $i <= $#$patterns_ar; $i++) { my $pat_hr = $patterns_ar->[$i]; my $snort_offset = -1; my $snort_depth = -1; my $snort_distance = -1; my $snort_within = -1; $snort_offset = $pat_hr->{'offset_snort_orig'} if defined $pat_hr->{'offset_snort_orig'}; $snort_depth = $pat_hr->{'depth_snort_orig'} if defined $pat_hr->{'depth_snort_orig'}; $snort_distance = $pat_hr->{'distance_snort_orig'} if defined $pat_hr->{'distance_snort_orig'}; $snort_within = $pat_hr->{'within_snort_orig'} if defined $pat_hr->{'within_snort_orig'}; if ($snort_depth > -1) { ### if there is also an offset, then the depth begins after ### the offset starts if ($snort_offset > -1) { $pat_hr->{'depth'} += $snort_offset; } elsif ($snort_distance > -1) { $pat_hr->{'depth'} += $snort_distance; } if ($snort_depth < $pat_hr->{'length'}) { return 0, "depth: $snort_depth less than " . "pattern length: $pat_hr->{'length'}"; } } if ($snort_distance > -1) { ### see if we need to increase the distance based ### on the length of the previous pattern match and offset if ($i > 0) { my $prev_pat_hr = $patterns_ar->[$i-1]; $pat_hr->{'distance'} += $prev_pat_hr->{'length'}; if (defined $prev_pat_hr->{'offset_snort_orig'}) { $pat_hr->{'distance'} += $prev_pat_hr->{'offset_snort_orig'}; } } } if ($snort_within > -1) { if ($snort_offset > -1) { $pat_hr->{'within'} += $snort_offset; } elsif ($snort_distance > -1) { $pat_hr->{'within'} += $snort_distance; } if ($i > 0) { my $prev_pat_hr = $patterns_ar->[$i-1]; $pat_hr->{'within'} += $prev_pat_hr->{'length'}; if (defined $prev_pat_hr->{'offset_snort_orig'}) { $pat_hr->{'within'} += $prev_pat_hr->{'offset_snort_orig'}; } } } } return 1, ''; } sub get_avg_hdr_len() { my $hdr_hr = shift; my $avg_hdr_len = $config{'AVG_IP_HEADER_LEN'}; if (defined $hdr_hr->{'proto'}) { if ($hdr_hr->{'proto'} =~ /udp/i) { $avg_hdr_len += $UDP_HDR_LEN; ### udp header is 8 bytes } elsif ($hdr_hr->{'proto'} =~ /icmp/i) { $avg_hdr_len += $ICMP_HDR_LEN; ### icmp header is 8 bytes } else { ### default to TCP $avg_hdr_len += $config{'AVG_TCP_HEADER_LEN'}; } } else { ### don't know what the average transport layer (if there ### is one) length will be; add 10 bytes just to be safe $avg_hdr_len += 10; } return $avg_hdr_len; } ### handles length of hex blocks sub get_content_len() { my $str = shift; my $len = 0; my $hex_mode = 0; my @chars = split //, $str; for (my $i=0; $i<=$#chars; $i++) { if ($chars[$i] eq '|') { $hex_mode == 0 ? ($hex_mode = 1) : ($hex_mode = 0); next; } if ($hex_mode) { next if $chars[$i] eq ' '; $len++; $i++; } else { $len++; } } return $len; } sub consolidate_hex_spaces() { my $str = shift; my $new_str = ''; my $hex_mode = 0; my @chars = split //, $str; for my $char (@chars) { if ($char eq '|') { $hex_mode == 0 ? ($hex_mode = 1) : ($hex_mode = 0); } if ($hex_mode) { next if $char eq ' '; } $new_str .= $char; } return $new_str; } sub have_hex_char() { my $str = shift; return 1 if $str =~ /[^A-Za-z0-9]/; return 0; } sub convert_str_to_hex() { my $str = shift; my $new_str = ''; my $hex_mode = 0; my @chars = split //, $str; CHAR: for my $char (@chars) { if ($char eq '|') { if ($hex_mode) { $new_str .= $char; $hex_mode = 0; } else { $new_str .= $char; $hex_mode = 1; } next CHAR; } if ($hex_mode) { $new_str .= $char; } else { $new_str .= sprintf "|%02x|", ord($char); } } ### consolidate any consecutive "|aa||bb|" sequences $new_str =~ s/\|\|//g; return $new_str; } sub translate_perl_trigger() { my $str = shift; my $trigger_str = ''; my $hex_mode = 0; my $append_hex_str = ''; my $append_non_hex_str = ''; my @chars = split //, $str; for my $char (@chars) { if ($char eq '|') { if ($hex_mode) { if ($append_hex_str) { while ($append_hex_str =~ /(.{2})/g) { $trigger_str .= "\\x$1"; } $append_hex_str = ''; } $hex_mode = 0; } else { if ($append_non_hex_str) { $trigger_str .= qq|$append_non_hex_str|; $append_non_hex_str = ''; } $hex_mode = 1; } next; } if ($hex_mode) { $append_hex_str .= $char; } else { $append_non_hex_str .= $char; } } if ($append_hex_str) { while ($append_hex_str =~ /(.{2})/g) { $trigger_str .= "\\x$1"; } } $trigger_str .= qq|$append_non_hex_str| if $append_non_hex_str; return $trigger_str; } sub ipt_add_rule() { my ($hdr_hr, $opts_hr, $orig_snort_rule, $rule_base, $target_str, $comment, $add_snort_comment, $perl_trigger_str, $add_perl_trigger, $chain, $fwsnort_chain) = @_; my $action_rule = ''; if ($hdr_hr->{'proto'} eq 'tcp') { if ($hdr_hr->{'action'} eq 'pass') { $action_rule = "$rule_base -j ACCEPT"; } else { if (defined $opts_hr->{'resp'} and $opts_hr->{'resp'} =~ /rst/i) { ### iptables can only send tcp resets to the connection ### client, so we can't support rst_rcv, but we should ### try to tear the connection down anyway. $action_rule = "$rule_base -j REJECT " . "--reject-with tcp-reset"; } elsif ($ipt_drop) { $action_rule = "$rule_base -j DROP"; } elsif ($ipt_reject) { $action_rule = "$rule_base -j REJECT " . "--reject-with tcp-reset"; } } } elsif ($hdr_hr->{'proto'} eq 'udp') { if ($hdr_hr->{'action'} eq 'pass') { $action_rule = "$rule_base -j ACCEPT"; } else { if (defined $opts_hr->{'resp'} and $opts_hr->{'resp'} =~ /icmp/i) { if ($opts_hr->{'resp'} =~ /all/i) { ### icmp_all $action_rule = "$rule_base -j REJECT " . "--reject-with icmp-port-unreachable"; } elsif ($opts_hr->{'resp'} =~ /net/i) { ### icmp_net $action_rule = "$rule_base -j REJECT " . "--reject-with icmp-net-unreachable"; } elsif ($opts_hr->{'resp'} =~ /host/i) { ### icmp_host $action_rule = "$rule_base -j REJECT " . "--reject-with icmp-host-unreachable"; } elsif ($opts_hr->{'resp'} =~ /port/i) { ### icmp_port $action_rule = "$rule_base -j REJECT " . "--reject-with icmp-port-unreachable"; } } elsif ($ipt_drop) { $action_rule = "$rule_base -j DROP"; } elsif ($ipt_reject) { $action_rule = "$rule_base -j REJECT " . "--reject-with icmp-port-unreachable"; } } } else { if ($hdr_hr->{'action'} eq 'pass') { $action_rule = "$rule_base -j ACCEPT"; } else { $action_rule = "$rule_base -j DROP"; } } my $ipt_rule = $rule_base . $target_str; push @ipt_script_lines, "\n### $orig_snort_rule" if $add_snort_comment; if ($include_perl_triggers and $add_perl_trigger) { push @ipt_script_lines, "### $perl_trigger_str"; } if ($verbose) { push @ipt_script_lines, qq|\$ECHO "[+] rule $ipt_rule_ctr"|; } ### save format handling my $save_format_ipt_rule = $ipt_rule . " \n"; my $save_format_action_rule = $action_rule . " \n"; $save_format_ipt_rule =~ s|\$${ipt_var_str}\s+\-A|-A|; $save_format_action_rule =~ s|\$${ipt_var_str}\s+\-A|-A|; if ($hdr_hr->{'action'} ne 'pass') { if ($queue_mode or $nfqueue_mode) { push @ipt_script_lines, $ipt_rule; push @{$save_format_rules{$fwsnort_chain}}, $save_format_ipt_rule; } else { push @ipt_script_lines, $ipt_rule unless $no_ipt_log; push @{$save_format_rules{$fwsnort_chain}}, $save_format_ipt_rule unless $no_ipt_log; } } if ($action_rule and ($ipt_drop or $ipt_reject or $hdr_hr->{'action'} eq 'pass' or defined $opts_hr->{'resp'})) { push @ipt_script_lines, $action_rule; push @{$save_format_rules{$fwsnort_chain}}, $save_format_action_rule; } $ipt_rule_ctr++; return; } sub save_format_append_rules() { for my $chain (sort keys %ipt_save_existing_chains) { next unless &is_fwsnort_chain($chain, $MATCH_EQUIV); ### make sure that whitelist/blacklist and established jump rules ### are added at the beginning of each chain in save format &save_format_add_prereqs($chain); for my $rule (@{$save_format_rules{$chain}}) { push @fwsnort_save_lines, $rule; } } ### now append any last lines from the iptables-save output that ### had nothing to do with fwsnort (other custom chains, etc.) for (my $i = $ipt_save_index; $i <= $#ipt_save_lines; $i++) { next if &is_fwsnort_chain($ipt_save_lines[$i], $MATCH_SUBSTR); push @fwsnort_save_lines, $ipt_save_lines[$i]; } return; } sub save_format_add_prereqs() { my $chain = shift; return if defined $save_format_prereqs{$chain}; ### add whitelist if (defined $save_format_whitelist{$chain}) { for my $whitelist_rule (@{$save_format_whitelist{$chain}}) { push @fwsnort_save_lines, "$whitelist_rule \n"; } } ### add blacklist if (defined $save_format_blacklist{$chain}) { for my $blacklist_rule (@{$save_format_blacklist{$chain}}) { push @fwsnort_save_lines, "$blacklist_rule \n"; } } ### add jump rules into the connection tracking fwsnort chains if (defined $save_format_conntrack_jumps{$chain}) { for my $jump_rule (@{$save_format_conntrack_jumps{$chain}}) { push @fwsnort_save_lines, "$jump_rule \n"; } } $save_format_prereqs{$chain} = ''; return; } sub ipt_whitelist() { my @whitelist_addrs = (); for my $whitelist_line (@{$config{'WHITELIST'}}) { for my $addr (@{&expand_addresses($whitelist_line)}) { push @whitelist_addrs, $addr; } } return unless $#whitelist_addrs >= 0; push @ipt_script_lines, "\n###\n############ Add IP/network " . "WHITELIST rules. ############\n###"; for my $addr (@whitelist_addrs) { for my $chain (keys %process_chains) { next unless $process_chains{$chain}; if ($chain eq 'INPUT' or $chain eq 'FORWARD') { my $rule_str = qq|-A $config{"FWSNORT_$chain"} | . "-s $addr -j RETURN"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_whitelist{$config{"FWSNORT_$chain"}}}, $rule_str; } if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') { my $rule_str = qq|-A $config{"FWSNORT_$chain"} | . "-d $addr -j RETURN"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_whitelist{$config{"FWSNORT_$chain"}}}, $rule_str; } } } return; } sub ipt_blacklist() { my $printed_intro = 0; for my $blacklist_line (@{$config{'BLACKLIST'}}) { my @blacklist_addrs = (); my $target = 'DROP'; ### default if ($blacklist_line =~ /\s+REJECT/) { $target = 'REJECT'; } for my $addr (@{&expand_addresses($blacklist_line)}) { push @blacklist_addrs, $addr; } return unless $#blacklist_addrs >= 0; unless ($printed_intro) { push @ipt_script_lines, "\n###\n############ Add IP/network " . "BLACKLIST rules. ############\n###"; $printed_intro = 1; } for my $addr (@blacklist_addrs) { for my $chain (keys %process_chains) { next unless $process_chains{$chain}; if ($target eq 'DROP') { if ($chain eq 'INPUT' or $chain eq 'FORWARD') { my $rule_str = qq|-A $config{"FWSNORT_$chain"} | . "-s $addr -j DROP"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}}, $rule_str; } if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') { my $rule_str = qq|-A $config{"FWSNORT_$chain"} | . "-d $addr -j DROP"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}}, $rule_str; } } else { if ($chain eq 'INPUT' or $chain eq 'FORWARD') { my $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | . "-p tcp -j REJECT --reject-with tcp-reset"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}}, $rule_str; $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | . "-p udp -j REJECT --reject-with icmp-port-unreachable"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}}, $rule_str; $rule_str = qq|-A $config{"FWSNORT_$chain"} -s $addr | . "-p icmp -j REJECT --reject-with icmp-host-unreachable"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}}, $rule_str; } if ($chain eq 'OUTPUT' or $chain eq 'FORWARD') { my $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | . "-p tcp -j REJECT --reject-with tcp-reset"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}}, $rule_str; $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | . "-p udp -j REJECT --reject-with icmp-port-unreachable"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}}, $rule_str; $rule_str = qq|-A $config{"FWSNORT_$chain"} -d $addr | . "-p icmp -j REJECT --reject-with icmp-host-unreachable"; push @ipt_script_lines, "\$${ipt_var_str} $rule_str"; push @{$save_format_blacklist{$config{"FWSNORT_$chain"}}}, $rule_str; } } } } } return; } sub ipt_add_chains() { ### save format my %ipt_save_built_in_chains = (); my $look_for_chains = 0; for (@ipt_save_lines) { unless ($look_for_chains) { push @fwsnort_save_lines, $_; } if (/^\*filter/) { $look_for_chains = 1; } elsif ($look_for_chains and $_ =~ /^:(\S+)/) { my $chain = $1; if ($chain eq 'INPUT' or $chain eq 'OUTPUT' or $chain eq 'FORWARD') { $ipt_save_built_in_chains{$chain} = $_; } else { ### don't preserve any old fwsnort chains, but preserve ### any other existing custom chains unless (&is_fwsnort_chain($chain, $MATCH_EQUIV)) { $ipt_save_existing_chains{$chain} = $_; } } } elsif ($look_for_chains and $_ !~ /^:(\S+)/) { last; } $ipt_save_index++; } ### save format - add the built-in chains first for my $chain (qw(INPUT FORWARD OUTPUT)) { ### should always be defined unless we're not running as root next unless defined $ipt_save_built_in_chains{$chain}; push @fwsnort_save_lines, $ipt_save_built_in_chains{$chain}; } ### add the fwsnort chains push @ipt_script_lines, "\n###\n############ Create " . "fwsnort $ipt_str chains. ############\n###"; for my $built_in_chain (qw(INPUT FORWARD OUTPUT)) { ### see if any of the "FWSNORT_" chains need to be ### excluded next unless $process_chains{$built_in_chain}; for my $chain ($config{"FWSNORT_$built_in_chain"}, $config{"FWSNORT_${built_in_chain}_ESTAB"}) { if ($no_ipt_conntrack) { next if $chain eq $config{"FWSNORT_${built_in_chain}_ESTAB"}; } push @ipt_script_lines, "\$${ipt_var_str} -N $chain 2> /dev/null", "\$${ipt_var_str} -F $chain\n"; ### save format $ipt_save_existing_chains{$chain} = ":$chain - [0:0]\n"; } } ### save format - add the custom chains for my $chain (sort keys %ipt_save_existing_chains) { push @fwsnort_save_lines, $ipt_save_existing_chains{$chain}; } ### save format - add in the jump rules from the ### built-in chains here &save_format_add_jump_rules(); ### add in any rules from custom chains that alphabetically come ### before the first fwsnort chain &save_format_add_early_custom_chains(); return; } sub save_format_add_jump_rules() { ### add the jump rule for each built-in chain for my $built_in_chain (qw(INPUT FORWARD OUTPUT)) { next unless defined $process_chains{$built_in_chain} and $process_chains{$built_in_chain}; ### get the current $chain rules (if any), and then see where to add ### the fwsnort jump rule my @existing_chain_rules = (); for (my $i = $ipt_save_index; $i < $#ipt_save_lines; $i++) { ### delete any existing fwsnort jump rules if ($ipt_save_lines[$i] =~ /^\-A\s$built_in_chain\s/) { if ($ipt_save_lines[$i] !~ /\-j\sFWSNORT_/) { push @existing_chain_rules, $ipt_save_lines[$i]; } } else { last; } $ipt_save_index++; } my $ctr = 1; my $added_jump_rule = 0; for my $existing_rule (@existing_chain_rules) { if ($ctr == $config{"FWSNORT_${built_in_chain}_JUMP"}) { &save_format_add_chain_jump_rule($built_in_chain); $added_jump_rule = 1; } push @fwsnort_save_lines, $existing_rule; $ctr++; } ### the chain may have been empty unless ($added_jump_rule) { &save_format_add_chain_jump_rule($built_in_chain); } } return; } sub save_format_add_chain_jump_rule() { my $built_in_chain = shift; my $fwsnort_chain = "FWSNORT_${built_in_chain}"; if (%restrict_interfaces) { for my $intf (keys %restrict_interfaces) { if ($built_in_chain eq 'INPUT' or $built_in_chain eq 'FORWARD') { push @fwsnort_save_lines, "-A $built_in_chain -i $intf " . "-j $fwsnort_chain \n"; } elsif ($built_in_chain eq 'OUTPUT') { push @fwsnort_save_lines, "-A $built_in_chain -o $intf " . "-j $fwsnort_chain \n"; } } } else { if ($no_exclude_loopback) { push @fwsnort_save_lines, "-A $built_in_chain " . "-j $fwsnort_chain \n"; } else { if ($built_in_chain eq 'INPUT' or $built_in_chain eq 'FORWARD') { push @fwsnort_save_lines, "-A $built_in_chain ! -i lo " . "-j $fwsnort_chain \n"; } elsif ($built_in_chain eq 'OUTPUT') { push @fwsnort_save_lines, "-A $built_in_chain ! -o lo " . "-j $fwsnort_chain \n"; } } } return; } sub save_format_add_early_custom_chains() { for my $chain (sort keys %ipt_save_existing_chains) { last if &is_fwsnort_chain($chain, $MATCH_EQUIV); for (my $i = $ipt_save_index; $i <= $#ipt_save_lines; $i++) { last if &is_fwsnort_chain($ipt_save_lines[$i], $MATCH_SUBSTR); push @fwsnort_save_lines, $ipt_save_lines[$i]; $ipt_save_index++; } } return; } sub is_fwsnort_chain() { my ($str, $match_style) = @_; my $rv = 0; for my $fwsnort_chain ($config{'FWSNORT_INPUT'}, $config{'FWSNORT_INPUT_ESTAB'}, $config{'FWSNORT_FORWARD'}, $config{'FWSNORT_FORWARD_ESTAB'}, $config{'FWSNORT_OUTPUT'}, $config{'FWSNORT_OUTPUT_ESTAB'}) { if ($match_style eq $MATCH_SUBSTR) { if ($str =~ /$fwsnort_chain/) { $rv = 1; last; } } elsif ($match_style eq $MATCH_EQUIV) { if ($str eq $fwsnort_chain) { $rv = 1; last; } } } return $rv; } sub ipt_add_conntrack_jumps() { ### jump ESTABLISHED tcp traffic to each of the _ESTAB ### chains push @ipt_script_lines, "\n###\n############ Inspect $conntrack_state " . "tcp connections. ############\n###"; for my $chain (keys %process_chains) { next unless $process_chains{$chain}; my $rule_str = ''; if ($have_conntrack) { $rule_str = qq|-A $config{"FWSNORT_$chain"} -p tcp -m conntrack | . qq|--ctstate $conntrack_state -j | . qq|$config{"FWSNORT_${chain}_ESTAB"}|; } elsif ($have_state) { $rule_str = qq|-A $config{"FWSNORT_$chain"} -p tcp -m state | . qq|--state $conntrack_state -j | . qq|$config{"FWSNORT_${chain}_ESTAB"}|; } next unless $rule_str; push @ipt_script_lines, qq|\$${ipt_var_str} $rule_str|; push @{$save_format_conntrack_jumps{$config{"FWSNORT_$chain"}}}, $rule_str; } return; } sub ipt_jump_chain() { push @ipt_script_lines, "\n###\n############ Jump traffic " . "to the fwsnort chains. ############\n###"; if (%restrict_interfaces) { for my $intf (keys %restrict_interfaces) { for my $chain (keys %process_chains) { next unless $process_chains{$chain}; if ($chain eq 'INPUT' or $chain eq 'FORWARD') { ### delete any existing jump rule so that fwsnort.sh can ### be executed many times in a row without adding several ### jump rules push @ipt_script_lines, "\$${ipt_var_str} -D $chain " . qq|-i $intf -j $config{"FWSNORT_$chain"}| . ' 2> /dev/null'; ### now add the jump rule push @ipt_script_lines, "\$${ipt_var_str} -I $chain " . qq|$config{"FWSNORT_${chain}_JUMP"} -i | . qq|$intf -j $config{"FWSNORT_$chain"}|; } elsif ($chain eq 'OUTPUT') { push @ipt_script_lines, "\$${ipt_var_str} -D $chain " . qq|-o $intf -j $config{'FWSNORT_OUTPUT'}| . ' 2> /dev/null'; push @ipt_script_lines, "\$${ipt_var_str} -I $chain " . qq|$config{'FWSNORT_OUTPUT_JUMP'} -o | . qq|$intf -j $config{'FWSNORT_OUTPUT'}|; } } } } else { for my $chain (keys %process_chains) { next unless $process_chains{$chain}; if ($no_exclude_loopback) { push @ipt_script_lines, "\$${ipt_var_str} -D $chain " . qq|-j $config{"FWSNORT_$chain"}| . ' 2> /dev/null'; push @ipt_script_lines, "\$${ipt_var_str} -I $chain " . qq|$config{"FWSNORT_${chain}_JUMP"} | . qq|-j $config{"FWSNORT_$chain"}|; } else { if ($chain eq 'INPUT' or $chain eq 'FORWARD') { push @ipt_script_lines, "\$${ipt_var_str} -D $chain " . qq|! -i lo -j $config{"FWSNORT_$chain"}| . ' 2> /dev/null'; push @ipt_script_lines, "\$${ipt_var_str} -I $chain " . qq|$config{"FWSNORT_${chain}_JUMP"} ! -i lo | . qq|-j $config{"FWSNORT_$chain"}|; } elsif ($chain eq 'OUTPUT') { push @ipt_script_lines, "\$${ipt_var_str} -D $chain " . qq|! -o lo -j $config{"FWSNORT_$chain"}| . ' 2> /dev/null'; push @ipt_script_lines, "\$${ipt_var_str} -I $chain " . qq|$config{"FWSNORT_${chain}_JUMP"} ! -o lo | . qq|-j $config{"FWSNORT_$chain"}|; } } } } return; } sub hdr_lines() { return "#!$cmds{'sh'}\n#", '#'x76, "#\n# File: $config{'FWSNORT_SCRIPT'}", "#\n# Purpose: This script was auto-" . "generated by fwsnort, and implements", "# an $ipt_str ruleset based upon " . "Snort rules. For more", "# information see the fwsnort man " . "page or the documentation", "# available at " . "http://www.cipherdyne.org/fwsnort/", "#\n# Generated with: fwsnort @argv_cp", "# Generated on host: " . hostname(), "# Time stamp: " . localtime(), "#\n# Author: Michael Rash ", "#\n# Version: $version", "#", '#'x76, "#\n"; } sub ipt_hdr() { push @ipt_script_lines, &hdr_lines(); push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines]; ### add paths to system binaries (iptables included) &ipt_config_section(); return; } sub ipt_config_section() { ### build the config section of the iptables script push @ipt_script_lines, '#==================== config ====================', "ECHO=$cmds{'echo'}", "${ipt_var_str}=$ipt_bin", "#================== end config ==================\n"; push @ipt_save_script_lines, $ipt_script_lines[$#ipt_script_lines]; return; } sub ipt_type() { my $type = shift; push @ipt_script_lines, "\n###\n############ ${type}.rules #######" . "#####\n###", "\$ECHO \"[+] Adding $type rules:\""; return; } sub check_type() { for my $type_hr (\%include_types, \%exclude_types) { for my $type (keys %$type_hr) { my $found = 0; my @valid_types = (); for my $dir (split /\,/, $config{'RULES_DIR'}) { if (-e "$dir/${type}.rules") { $found = 1; } else { opendir D, $dir or die "[*] Could not open $dir: $!"; for my $file (readdir D) { if ($file =~ /(\S+)\.rules/) { push @valid_types, $1; } } } } unless ($found) { print "[-] \"$type\" is not a valid type.\n", " Choose from the following available signature types:\n"; for my $type (sort @valid_types) { print " $type\n"; } die "[-] Exiting."; } } } return; } sub import_config() { open C, "< $fwsnort_conf" or die "[*] Could not open $fwsnort_conf: $!"; my @lines = ; close C; my $l_ctr = 0; for my $line (@lines) { $l_ctr++; chomp $line; next if $line =~ /^\s*#/; next unless $line =~ /\S/; if ($line =~ /^\s*(\S+)Cmd\s+(\S+);/) { ### e.g. "iptablesCmd" $cmds{$1} = $2; } elsif ($line =~ /^\s*(\S+)\s+(.*?);/) { my $var = $1; my $val = $2; die "[*] $fwsnort_conf: Variable \"$var\" is set to\n", " _CHANGEME_ at line $l_ctr. Edit $fwsnort_conf.\n" if $val eq '_CHANGEME_'; if (defined $multi_line_vars{$var}) { push @{$config{$var}}, $val; } else { ### may have already been defined in existing snort.conf ### file if --snort-conf was given. $config{$var} = $val unless defined $config{$var}; } } } &expand_vars(); return; } sub ipt_list() { for my $chain ( $config{'FWSNORT_INPUT'}, $config{'FWSNORT_INPUT_ESTAB'}, $config{'FWSNORT_OUTPUT'}, $config{'FWSNORT_OUTPUT_ESTAB'}, $config{'FWSNORT_FORWARD'}, $config{'FWSNORT_FORWARD_ESTAB'} ) { my $cmd = "$ipt_bin -v -n -L $chain"; my $exists = (system "$cmd > /dev/null 2>&1") >> 8; if ($exists == 0) { print "[+] Listing $chain chain...\n"; system $cmd; print "\n"; } else { print "[-] Chain $chain does not exist...\n"; } } exit 0; } sub ipt_flush() { for my $chain ( $config{'FWSNORT_INPUT'}, $config{'FWSNORT_INPUT_ESTAB'}, $config{'FWSNORT_OUTPUT'}, $config{'FWSNORT_OUTPUT_ESTAB'}, $config{'FWSNORT_FORWARD'}, $config{'FWSNORT_FORWARD_ESTAB'} ) { my $exists = (system "$ipt_bin -n -L " . "$chain > /dev/null 2>&1") >> 8; if ($exists == 0) { print "[+] Flushing $chain chain...\n"; system "$ipt_bin -F $chain"; if ($ipt_del_chains) { ### must remove any jump rules from the built-in ### chains &del_jump_rule($chain); print " Deleting $chain chain...\n"; system "$ipt_bin -X $chain"; } } else { print "[-] Chain $chain does not exist...\n"; } } exit 0; } sub cache_ipt_save_policy() { return unless $is_root; open IPT, "$save_bin -t filter |" or die "[*] Could not execute $save_bin"; while () { push @ipt_save_lines, $_; } close IPT; ### also write out the current iptables policy so that we can ### revert to it if necessary (iptables does a good job of not committing ### a policy via iptables-save if there is a problem with a rule though). &archive($config{'IPT_BACKUP_SAVE_FILE'}); open F, "> $config{'IPT_BACKUP_SAVE_FILE'}" or die "[*] Could not " . "open $config{'IPT_BACKUP_SAVE_FILE'}: $!"; print F for @ipt_save_lines; close F; ### remove the last two lines (the 'COMMIT' and '# Completed ...' lines ### so they can be added later). $ipt_save_completed_line = $ipt_save_lines[$#ipt_save_lines]; pop @ipt_save_lines; pop @ipt_save_lines; return; } sub del_jump_rule() { my $chain = shift; my $ipt = new IPTables::Parse 'iptables' => $cmds{'iptables'} or die "[*] Could not acquire IPTables::Parse object: $!"; for my $built_in_chain (qw(INPUT OUTPUT FORWARD)) { my $rules_ar = $ipt->chain_rules('filter', $built_in_chain, $ipt_file); for (my $i=0; $i <= $#$rules_ar; $i++) { my $rule_num = $i+1; if ($rules_ar->[$i]->{'target'} eq $chain) { system "$ipt_bin -D $built_in_chain $rule_num"; last; } } } return; } sub fwsnort_init() { ### set umask to -rw------- umask 0077; ### turn off buffering $| = 1; &set_non_root_values() unless $is_root; ### read in configuration info from the config file &import_config(); ### make sure the commands are where the ### config file says they are &chk_commands(); ### make sure all of the required variables are defined ### in the config file &required_vars(); $non_host = $NON_HOST; $ipt_bin = $cmds{'iptables'}; $restore_bin = $cmds{'iptables-restore'}; $save_bin = $cmds{'iptables-save'}; if ($enable_ip6tables) { for my $opt (qw(itype icode ttl tos ipopts)) { $snort_opts{'unsupported'}{$opt} = $snort_opts{'filter'}{$opt}; delete $snort_opts{'filter'}{$opt}; } $non_host = $NON_IP6_HOST; $save_str = 'ip6tables-save'; $ipt_str = 'ip6tables'; $ipt_bin = $cmds{'ip6tables'}; $restore_bin = $cmds{'ip6tables-restore'}; $save_bin = $cmds{'ip6tables-save'}; } unless ($is_root) { $no_ipt_test = 1; } if ($ipt_exec) { die "[*] You need to be root for --ipt-apply" unless $is_root; if (-e $config{'FWSNORT_SAVE_EXEC_FILE'}) { print "[+] Executing $config{'FWSNORT_SAVE_EXEC_FILE'}\n"; system $config{'FWSNORT_SAVE_EXEC_FILE'}; exit 0; } else { die "[*] $config{'FWSNORT_SAVE_EXEC_FILE'} does not exist."; } } elsif ($ipt_revert) { die "[*] You need to be root for --ipt-revert" unless $is_root; if (-e $config{'FWSNORT_SAVE_EXEC_FILE'}) { print "[+] Executing $config{'FWSNORT_SAVE_EXEC_FILE'}\n"; system "$config{'FWSNORT_SAVE_EXEC_FILE'} -r"; exit 0; } else { die "[*] $config{'FWSNORT_SAVE_EXEC_FILE'} does not exist."; } } if ($enable_ip6tables) { ### switch to ip6tables $ipt_var_str = 'IP6TABLES'; } $process_chains{'INPUT'} = 0 if $no_ipt_input; $process_chains{'FORWARD'} = 0 if $no_ipt_forward; $process_chains{'OUTPUT'} = 0 if $no_ipt_output; ### import HOME_NET, etc. from existing Snort config file. &import_snort_conf() if $snort_conf_file; if ($rules_types) { my @types = split /\,/, $rules_types; for my $type (@types) { $include_types{$type} = ''; } } if ($exclude_types) { my @types = split /\,/, $exclude_types; for my $type (@types) { $exclude_types{$type} = ''; } } if ($include_sids) { ### disable iptables policy parsing if we are translating a ### specific set of Snort sids. $ipt_sync = 0; my @sids = split /\,/, $include_sids; for my $sid (@sids) { $include_sids{$sid} = ''; } } if ($exclude_sids) { my @sids = split /\,/, $exclude_sids; for my $sid (@sids) { $exclude_sids{$sid} = ''; } } if ($ipt_restrict_intf) { my @interfaces = split /\,/, $ipt_restrict_intf; for my $intf (@interfaces) { $restrict_interfaces{$intf} = ''; } } if ($include_re) { if ($include_re_caseless) { $include_re = qr|$include_re|i; } else { $include_re = qr|$include_re|; } } if ($exclude_re) { if ($exclude_re_caseless) { $exclude_re = qr|$exclude_re|i; } else { $exclude_re = qr|$exclude_re|; } } ### flush all fwsnort chains. &ipt_flush() if $ipt_flush or $ipt_del_chains; ### list all fwsnort chains. &ipt_list() if $ipt_list; ### download latest snort rules from snort.org &update_rules() if $update_rules; ### make sure some directories exist, etc. &setup(); ### get kernel version (this is mainly used to know whether ### the "--algo bm" argument is required for the string match ### extension in the 2.6.14 (and later) kernels. Also, the ### string match extension as of 2.6.14 supports the Snort ### offset and depth keywords via --from and --to &get_kernel_ver(); ### may have been specified on the command line $home_net = $config{'HOME_NET'} unless $home_net; $ext_net = $config{'EXTERNAL_NET'} unless $ext_net; &get_local_addrs() unless $no_addr_check; if ($strict) { ### make the snort options parser very strict for my $opt (@{$snort_opts{'strict_list'}}) { if (defined $snort_opts{'filter'}{$opt}) { $snort_opts{'unsupported'}{$opt} = $snort_opts{'filter'}{$opt}; delete $snort_opts{'filter'}{$opt}; } elsif (defined $snort_opts{'ignore'}{$opt}) { $snort_opts{'unsupported'}{$opt} = $snort_opts{'ignore'}{$opt}; delete $snort_opts{'ignore'}{$opt}; } } my @ignore = (qw(nocase)); if ($kernel_ver eq '2.4') { push @ignore, 'offset', 'depth'; } for my $opt (@ignore) { next unless defined $snort_opts{'ignore'}{$opt}; $snort_opts{'unsupported'}{$opt} = $snort_opts{'ignore'}{$opt}; delete $snort_opts{'ignore'}{$opt}; } } if ($no_pcre) { ### skip trying to translate basic PCRE's $snort_opts{'unsupported'}{'pcre'} = $snort_opts{'filter'}{'pcre'}; delete $snort_opts{'filter'}{'pcre'}; } if ($no_fast_pattern_order) { $snort_opts{'ignore'}{'fast_pattern'} = $snort_opts{'filter'}{'fast_pattern'}{'regex'}; delete $snort_opts{'filter'}{'fast_pattern'}; } return; } sub get_kernel_ver() { die "[*] uname command: $cmds{'uname'} is not executable." unless -x $cmds{'uname'}; open U, "$cmds{'uname'} -a |" or die "[*] Could not run ", "$cmds{'uname'} -a"; my $out = ; close U; ### Linux orthanc 2.6.12.5 #2 Tue Sep 27 22:43:02 EDT 2005 i686 \ ### Pentium III (Coppermine) GenuineIntel GNU/Linux if ($out =~ /\s2\.6/) { $kernel_ver = '2.6'; } return; } sub handle_cmd_line() { ### make Getopts case sensitive Getopt::Long::Configure('no_ignore_case'); die "[*] Use --help for usage information.\n" unless (GetOptions( 'ipt-apply' => \$ipt_exec, # Apply the generated ruleset. 'ipt-exec' => \$ipt_exec, # Apply the generated ruleset. 'ipt-revert' => \$ipt_revert, # Apply the generated ruleset. 'ipt-drop' => \$ipt_drop, # Add iptables DROP rules. 'ipt-reject' => \$ipt_reject, # Add iptables REJECT rules. 'ipt-script=s' => \$ipt_script, # Manually specify the path to the # generated iptables script. 'ipt-log-tcp-seq' => \$ipt_log_tcp_seq, # Log TCP seq/ack values. 'ipt-flush' => \$ipt_flush, # Flush any existing fwsnort chains. 'ipt-check-capabilities' =>\$ipt_check_capabilities, # Check capabilities # and exit. 'Flush' => \$ipt_flush, # Synonym for --ipt-flush 'ipt-list' => \$ipt_list, # List any existing fwsnort chains. 'List' => \$ipt_list, # Synonym for --ipt-list 'fw-list' => \$ipt_list, # Synonym for --ipt-list 'ipt-del' => \$ipt_del_chains, # Delete fwsnort chains. 'ip6tables' => \$enable_ip6tables, # Turn on ip6tables mode. '6' => \$enable_ip6tables, # Synonym for --ip6tables. 'X' => \$ipt_del_chains, # Synonym for --ipt-del. 'ipt-file=s' => \$ipt_file, # Read iptables policy from a file. 'Home-net=s' => \$home_net, # Manually specify home network. 'External-net=s' => \$ext_net, # Manually specify external network. 'snort-sid=s' => \$include_sids, # Parse only these particular snort rules. 'snort-sids=s' => \$include_sids, # Synonum for --snort-sid 'string-match-alg=s' => \$string_match_alg, 'exclude-sid=s' => \$exclude_sids, # Exclude these particular snort rules. 'snort-conf=s' => \$snort_conf_file, # Get HOME_NET, etc. vars from # existing Snort config file. 'include-perl-triggers' => \$include_perl_triggers, # perl commands to # trigger signature matches. 'include-type=s' => \$rules_types, # Process only this type of snort rule # (e.g. "ddos") 'exclude-type=s' => \$exclude_types,# Exclude specified types (e.g. "ddos"). 'include-regex=s' => \$include_re, # Include only those signatures that # match the specified regex. 'include-re-caseless' => \$include_re_caseless, # make include regex case # insensitive 'exclude-regex=s' => \$exclude_re, # Exclude those signatures that # match the specified regex. 'exclude-re-caseless' => \$exclude_re_caseless, # make exclude regex case # insensitive 'snort-rdir=s' => \$rules_dir, # Manually specify the snort rules # directory. 'snort-rfile=s' => \$rules_file, # Translate a single rules file. 'no-pcre' => \$no_pcre, # Make no attempt to translate PCRE's. 'no-addresses' => \$no_addr_check, # Don't check local ip/ifconfig output. 'no-ipt-sync' => \$ignore_opt, # Do not sync with the iptables policy. 'ipt-sync' => \$ipt_sync, # Sync fwsnort rules with the iptables # policy. 'no-ipt-log' => \$no_ipt_log, # Do not generate iptables logging rules. 'no-ipt-test' => \$no_ipt_test, # Don't perform any checks for # iptables capabilities. 'no-ipt-jumps' => \$no_ipt_jumps, # Don't jump packets from the INPUT or # FORWARD chains. 'no-ipt-conntrack' => \$no_ipt_conntrack, # Don't use iptables connection # tracking (falls back to ACK flag test). 'Conntrack-state=s' => \$conntrack_state, ### Specify conntrack state for ### 'flow' keyword emulation ### (default is ESTABLISHED). 'no-ipt-INPUT' => \$no_ipt_input, # Disable fwsnort rules processed via # the INPUT chain. 'no-ipt-OUTPUT' => \$no_ipt_output, # Disable fwsnort rules processed via # the OUTPUT chain. 'no-ipt-FORWARD' => \$no_ipt_forward, # Disable fwsnort rules processed via # the FORWARD chain. 'no-ipt-comments' => \$no_ipt_comments, # Don't include msg fields # with the comment match 'no-ipt-rule-nums' => \$no_ipt_rule_nums, # Exclude rule numbers from # logging prefixes. 'no-exclude-lo' => \$no_exclude_loopback, # include loopback interface 'no-log-ip-opts' => \$no_ipt_log_ip_opts, # Don't log IP options 'no-log-tcp-opts' => \$no_ipt_log_tcp_opts, # Don't log TCP options 'no-fast-pattern-order' => \$no_fast_pattern_order, ### Don't alter # pattern match ordering based on # pattern length, and ignore the # explicit 'fast_pattern' keyword 'restrict-intf=s' => \$ipt_restrict_intf, # Restrict iptables rules to an # individual interface (supports a # comma separate list). 'update-rules' => \$update_rules, # Download latest snort rules. 'rules-url=s' => \$rules_url, # Specify rules URL. 'add-deleted' => \$add_deleted, # Add deleted rules. 'strict' => \$strict, # Strict mode. 'debug' => \$debug, # Debug mode. 'dumper' => \$dumper, # Dumper mode for IPTables::Parse # hashes. 'Dump-conf' => \$dump_conf, # Display config variables 'Dump-ipt' => \$dump_ipt, # Dump iptables rules on STDOUT. 'Dump-snort' => \$dump_snort, # Dump snort rules on STDOUT. 'config=s' => \$fwsnort_conf, # Manually specify the config file 'Ulog' => \$ulog_mode, # Force ULOG mode. 'ulog-nlgroup=i' => \$ulog_nlgroup, # Specify the ulogd nl group. 'QUEUE' => \$queue_mode, # Specify QUEUE mode; this pulls out # all kernel-matchable features from # original Snort rules and creates a # a modified rule set based on this. 'NFQUEUE' => \$nfqueue_mode, # Same as QUEUE mode, except use the # updated NFQUEUE target. 'queue-rules-dir=s' => \$queue_rules_dir, # Change the path to the generated # rules directory in --QUEUE or # --NFQUEUE mode. 'queue-num=i' => \$nfqueue_num, # Specifies the NFQUEUE number. 'queue-pre-match-max=i' => \$queue_pre_match_max, ### max number of patterns ### to match within the kernel before ### queuing a packet to userspace ### Snort 'Home-dir=s' => \$cmdl_homedir, 'Last-cmd' => \$run_last, 'lib-dir=s' => \$lib_dir, # Specify path to lib directory. 'verbose' => \$verbose, 'logfile=s' => \$logfile, # Specify the logfile path. 'stdout' => \$stdout, # Print log messages to stdout. 'Version' => \$print_ver, 'help' => \$help )); &get_homedir(); &usage(0) if $help; &save_args() unless $run_last; ### Print the version number and exit if -V given on the command line. if ($print_ver) { print "[+] fwsnort v$version by Michael Rash \n"; exit 0; } if (($queue_mode or $nfqueue_mode) and ($ipt_drop or $ipt_reject)) { die "[*] --NFQUEUE and --QUEUE modes are not compatible with --ipt-drop or\n", " --ipt-reject; a userland process should set the verdict. If you can\n", " always use fwsnort with --ipt-drop or --ipt-reject and add an NFQUEUE\n", " or QUEUE rule manually to a built-in chain. This allows the fwsnort\n", " policy to DROP or REJECT packets that match signatures before they are\n", " communicated to userland (hence speeding up Snort_inline).\n"; } if ($nfqueue_num != 0) { unless ($nfqueue_num > 0 and $nfqueue_num < 65536) { die "[*] --queue-num must be between 0 and 65535 (inclusive)"; } unless ($nfqueue_mode) { die "[*] Must also specifiy --NFQUEUE mode if using --queue-num"; } } if ($no_ipt_log and not ($ipt_drop or $ipt_reject)) { die "[*] --ipt-no-log option can only be used ", "with --ipt-drop or --ipt-reject"; } if ($ipt_drop and $ipt_reject) { die "[*] Cannot specify both --ipt-drop and --ipt-reject"; } return; } sub import_snort_conf() { unless (-e $snort_conf_file) { die "[*] Snort config file $snort_conf_file does not exist."; } open F, "< $snort_conf_file" or die "[*] Could not open Snort ", "config $snort_conf_file: $!"; my @lines = ; close F; for my $line (@lines) { chomp $line; next if $line =~ /^\s*#/; if ($line =~ /^\s*var\s+(\w+)\s+(.*)\s*/) { $config{$1} = $2; } } return; } sub expand_vars() { my $has_sub_var = 1; my $resolve_ctr = 0; while ($has_sub_var) { $resolve_ctr++; $has_sub_var = 0; if ($resolve_ctr >= 20) { die "[*] Exceeded maximum variable resolution counter."; } for my $hr (\%config, \%cmds) { for my $var (keys %$hr) { my $val = $hr->{$var}; if ($val =~ m|\$(\w+)|) { my $sub_var = $1; die "[*] sub-ver $sub_var not allowed within same ", "variable $var" if $sub_var eq $var; if (defined $config{$sub_var}) { if ($sub_var eq 'INSTALL_ROOT' and $config{$sub_var} eq '/') { $val =~ s|\$$sub_var||; } else { $val =~ s|\$$sub_var|$config{$sub_var}|; } $hr->{$var} = $val; } else { die "[*] sub-var \"$sub_var\" not defined in ", "config for var: $var." } $has_sub_var = 1; } } } } return; } sub required_vars() { my @required_vars = (qw( HOME_NET EXTERNAL_NET HTTP_SERVERS SMTP_SERVERS DNS_SERVERS SQL_SERVERS TELNET_SERVERS AIM_SERVERS HTTP_PORTS SHELLCODE_PORTS SSH_PORTS ORACLE_PORTS WHITELIST BLACKLIST AVG_IP_HEADER_LEN AVG_TCP_HEADER_LEN MAX_FRAME_LEN FWSNORT_INPUT FWSNORT_INPUT_ESTAB FWSNORT_OUTPUT FWSNORT_OUTPUT_ESTAB FWSNORT_FORWARD FWSNORT_FORWARD_ESTAB FWSNORT_INPUT_JUMP FWSNORT_OUTPUT_JUMP FWSNORT_FORWARD_JUMP MAX_STRING_LEN CONF_DIR RULES_DIR ARCHIVE_DIR QUEUE_RULES_DIR LOG_DIR LIBS_DIR CONF_FILE FWSNORT_SCRIPT LOG_FILE FWSNORT_SAVE_FILE FWSNORT_SAVE_EXEC_FILE IPT_BACKUP_SAVE_FILE UPDATE_RULES_URL STATE_DIR INSTALL_ROOT )); for my $var (@required_vars) { die "[*] Variable $var not defined in $fwsnort_conf. Exiting.\n" unless defined $config{$var}; } return; } sub ipt_capabilities() { print "[+] Testing $ipt_bin for supported capabilities...\n"; my $test_rule_rv = -1; ### create test chain &create_test_chain(); ### test for the LOG target. if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . "$non_host -j LOG") == $IPT_SUCCESS) { print "[+] $ipt_str has 'LOG' target support...\n" if $verbose or $ipt_check_capabilities; ### check for the max --log-prefix string length $ipt_max_log_prefix_len = &ipt_find_max_len( $ipt_max_log_prefix_len, qq|-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s | . qq|$non_host -p tcp --dport 1234 -j LOG --log-prefix "|, qq|"| ); print " Max supported LOG prefix length: $ipt_max_log_prefix_len\n" if $verbose or $ipt_check_capabilities; } else { &delete_test_chain(); die "[*] $ipt_str has not been compiled with logging support. ", "If you want to\n have fwsnort generate an $ipt_str script ", " anyway then specify the\n --no-ipt-test option. ", "Exiting.\n" unless $no_ipt_log; } ### test for the comment match (where Snort msg fields are placed) if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . qq|$non_host -m comment --comment "testing the comment match" | . qq|-j LOG|) == $IPT_SUCCESS) { print "[+] $ipt_str has 'comment' match support...\n" if $verbose or $ipt_check_capabilities; $ipt_max_comment_len = &ipt_find_max_len( $ipt_max_comment_len, qq|-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s | . qq|$non_host -p tcp --dport 1234 -m comment --comment | . qq|"|, qq|" -j LOG| ); print " Max supported comment length: $ipt_max_comment_len\n" if $verbose or $ipt_check_capabilities; } else { unless ($no_ipt_comments) { print"[-] It looks like the $ipt_str 'comment' match is not ", "available, disabling.\n"; $no_ipt_comments = 1; } } ### test for the ipv4options extension. if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp -m " . "ipv4options --rr -s $non_host -j LOG") == $IPT_SUCCESS) { print "[+] $ipt_str has the 'ipv4options' extension...\n" if $verbose or $ipt_check_capabilities; } else { &logr("[-] $ipt_str ipv4options extension not available, " . "disabling ipopts translation."); ### put ipopts in the unsupported list if (defined $snort_opts{'filter'}{'ipopts'}) { $snort_opts{'unsupported'}{'ipopts'} = $snort_opts{'filter'}{'ipopts'}{'regex'}; delete $snort_opts{'filter'}{'ipopts'}; } else { $snort_opts{'unsupported'}{'ipopts'} = qr/[\s;]ipopts:\s*(\w+)\s*;/; } print "[-] $ipt_str does not have the 'ipv4options' extension, " . "disabling...\n" if $verbose or $ipt_check_capabilities; } ### test for the ttl match. if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " . "-s $non_host -m ttl --ttl-eq 1 -j LOG") == $IPT_SUCCESS) { print "[+] $ipt_str has the 'ttl' match...\n" if $verbose or $ipt_check_capabilities; } else { ### put ttl in the unsupported list &logr("[-] $ipt_str TTL match not available, " . "disabling ttl translation."); if (defined $snort_opts{'filter'}{'ttl'}) { $snort_opts{'unsupported'}{'ttl'} = $snort_opts{'filter'}{'ttl'}{'regex'}; delete $snort_opts{'filter'}{'ttl'}; } else { $snort_opts{'unsupported'}{'ttl'} = qr/[\s;]ttl:\s*(.*?)\s*;/; } print "[+] $ipt_str does not have the 'ttl' match, " . "disabling...\n" if $verbose or $ipt_check_capabilities; } ### test for the TOS match. if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " . "-s $non_host -m tos --tos 8 -j LOG") == $IPT_SUCCESS) { print "[+] $ipt_str has the 'tos' match...\n" if $verbose or $ipt_check_capabilities; } else { ### put tos in the unsupported list &logr("[-] $ipt_str TOS match not available, " . "disabling tos translation."); if (defined $snort_opts{'filter'}{'tos'}) { $snort_opts{'unsupported'}{'tos'} = $snort_opts{'filter'}{'tos'}{'regex'}; delete $snort_opts{'filter'}{'tos'}; } else { $snort_opts{'unsupported'}{'tos'} = qr/[\s;]tos:\s*(.*?)\s*;/; } print "[+] $ipt_str does not have the 'tos' match, " . "disabling...\n" if $verbose or $ipt_check_capabilities; } ### test for the length match. if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p icmp " . "-s $non_host -m length --length 256 -j LOG") == $IPT_SUCCESS) { print "[+] $ipt_str has the 'length' match...\n" if $verbose or $ipt_check_capabilities; } else { ### put length in the unsupported list &logr("[-] $ipt_str length match not available, " . "disabling length translation."); if (defined $snort_opts{'filter'}{'dsize'}) { $snort_opts{'unsupported'}{'dsize'} = $snort_opts{'filter'}{'dsize'}{'regex'}; delete $snort_opts{'filter'}{'dsize'}; } else { $snort_opts{'unsupported'}{'dsize'} = qr/[\s;]dsize:\s*(.*?)\s*;/; } print "[+] $ipt_str does not have the 'length' match, " . "disabling...\n" if $verbose or $ipt_check_capabilities; } ### test for the multiport match. if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p tcp " . "-s $non_host -m multiport --dports 53,123:500 -j LOG") == $IPT_SUCCESS) { print "[+] $ipt_str has the 'multiport' match...\n" if $verbose or $ipt_check_capabilities; $ipt_have_multiport_match = 1; ### find the maximum number of supported ports (usually 15) &ipt_find_max_multiport_supported_ports(); } else { print "[-] $ipt_str does not have the 'multiport' match...\n" if $verbose or $ipt_check_capabilities; &logr("[-] $ipt_str multiport match not available"); } ### test for string match support. my $ipt_str_test = my $ipt_str_test_base = "-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . qq|$non_host -m string --string "test" |; if ($kernel_ver ne '2.4') { ### default to include "--algo bm" $ipt_str_test .= qq|--algo $string_match_alg -j LOG|; } else { $ipt_str_test .= qq|-j LOG|; } $test_rule_rv = &ipt_rule_test($ipt_str_test); if ($test_rule_rv == $IPT_SUCCESS) { print "[+] $ipt_str has the 'string' match...\n" if $verbose or $ipt_check_capabilities; ### now find the maximum string length that is supported by iptables if ($kernel_ver eq '2.4') { $ipt_max_str_len = &ipt_find_max_len( $ipt_max_str_len, qq|-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s | . qq|$non_host -m string --string "|, qq|" -j LOG|); } else { $ipt_max_str_len = &ipt_find_max_len( $ipt_max_str_len, qq|-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s | . qq|$non_host -m string --string "|, qq|" | . qq|--algo $string_match_alg -j LOG|); } print " Max supported string length: $ipt_max_str_len\n" if $verbose or $ipt_check_capabilities; ### test for case insensitive string matching $ipt_str_test = $ipt_str_test_base; if ($kernel_ver ne '2.4') { $ipt_str_test .= qq|--algo $string_match_alg --icase -j LOG|; } else { $ipt_str_test .= qq|--icase -j LOG|; } $test_rule_rv = &ipt_rule_test($ipt_str_test); unless ($test_rule_rv == $IPT_SUCCESS) { $snort_opts{'ignore'}{'nocase'} = $snort_opts{'filter'}{'nocase'}{'regex'}; delete $snort_opts{'filter'}{'fast_pattern'}; } ### test for --replace-string support (only available for 2.4 kernels ### if the replace-string patch has been applied). if ($kernel_ver eq '2.4') { unless (&ipt_rule_test($ipt_str_test_base . qq|--replace-string "repl" -j LOG|) == $IPT_SUCCESS) { if (defined $snort_opts{'filter'}{'replace'}) { $snort_opts{'unsupported'}{'replace'} = $snort_opts{'filter'}{'replace'}{'regex'}; delete $snort_opts{'filter'}{'replace'}; } else { $snort_opts{'unsupported'}{'replace'} = qr/[\s;]replace:\s*(.*?)\s*;/; } } } else { $snort_opts{'unsupported'}{'replace'} = qr/[\s;]replace:\s*(.*?)\s*;/; } ### test to see whether '--icmp-type any' is supported $ipt_str_test = $ipt_str_test_base; if ($kernel_ver ne '2.4') { $ipt_str_test .= qq|--algo $string_match_alg -p icmp -m icmp --icmp-type any -j LOG|; } else { $ipt_str_test .= qq|-p icmp -m icmp --icmp-type any -j LOG|; } $test_rule_rv = &ipt_rule_test($ipt_str_test); if ($test_rule_rv == $IPT_SUCCESS) { $default_icmp_type = 'any'; } } else { &delete_test_chain(); die "[*] It does not appear that string match support has been compiled into\n", " the kernel. Fwsnort will not be of very much use without this.\n", " ** NOTE: If you want to have fwsnort generate an $ipt_str policy\n", " anyway, use the --no-ipt-test option. Exiting.\n"; } ### test for --hex-string if ($kernel_ver ne '2.4') { $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " . "-s $non_host " . qq{-m string --hex-string "|0a 5d|" --algo $string_match_alg -j LOG}); } else { $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " . "-s $non_host " . qq{-m string --hex-string "|0a 5d|" -j LOG}); } if ($test_rule_rv == $IPT_SUCCESS) { print "[+] $ipt_str has --hex-string support...\n" if $verbose or $ipt_check_capabilities; } else { &delete_test_chain(); die "[*] It does not appear that the --hex-string patch has been applied.\n", " fwsnort will not be of very much use without this. ** NOTE: If you\n", " want to have fwsnort generate an $ipt_str policy anyway, then\n", " use the --no-ipt-test option. Exiting.\n"; } ### test for the --payload option if ($kernel_ver ne '2.4' and &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM " . "-s $non_host -m string --string " . qq|"test" --algo $string_match_alg --to 50 --payload -j LOG|) == $IPT_SUCCESS) { $ipt_has_string_payload_offset_opt = 1; } if ($queue_mode or $nfqueue_mode) { ### test for the QUEUE or NFQUEUE target if ($nfqueue_mode) { if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . "$non_host -p tcp --dport 3001 -j NFQUEUE") == $IPT_SUCCESS) { print "[+] $ipt_str has NFQUEUE support....\n" if $verbose or $ipt_check_capabilities; } else { die "[*] The NFQUEUE target does not appear to be available ", "in iptables."; } } else { if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . "$non_host -p tcp --dport 3001 -j QUEUE") == $IPT_SUCCESS) { print "[+] $ipt_str has QUEUE support....\n" if $verbose or $ipt_check_capabilities; } else { die "[*] The QUEUE target does not appear to be available ", "in iptables."; } } } unless ($no_ipt_conntrack) { ### test for connection tracking support (conntrack ### match first then state match if not available) if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . "$non_host -p tcp --dport 3001 -m conntrack " . "--ctstate ESTABLISHED -j LOG") == $IPT_SUCCESS) { print "[+] $ipt_str has conntrack state tracking support...\n" if $verbose or $ipt_check_capabilities; if ($conntrack_state ne 'ESTABLISHED') { ### check to make sure the specified state is supported if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . "$non_host -p tcp --dport 3001 -m conntrack " . "--ctstate $conntrack_state -j LOG") != $IPT_SUCCESS) { die "[*] The connection state $conntrack_state does not ", "appear to be supported by iptables.\n"; } } $have_conntrack = 1; } elsif (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . "$non_host -p tcp --dport 3001 -m state " . "--state ESTABLISHED -j LOG") == $IPT_SUCCESS) { print "[+] $ipt_str has state tracking support...\n" if $verbose or $ipt_check_capabilities; if ($conntrack_state ne 'ESTABLISHED') { ### check to make sure the specified state is supported if (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . "$non_host -p tcp --dport 3001 -m state " . "--state ESTABLISHED -j LOG") != $IPT_SUCCESS) { die "[*] The connection state $conntrack_state does not ", "appear to be supported by iptables.\n"; } } $have_state = 1; } else { &delete_test_chain(); die "[*] It does not appear that $ipt_str has been compiled with connection\n", " tracking support. If you want fwsnort to generate a policy anyway\n", " and just use a tcp flags check for established tcp connections, then\n", " use the --no-ipt-conntrack option. **NOTE: The resulting fwsnort\n", " $ipt_str policy will be susceptible to a stick or snot-style attack.\n", " Exiting.\n"; } } if ($ipt_reject) { ### we are going to generate a policy that drops icmp and udp ### packets, and kills tcp sessions with tcp-reset. unless (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p tcp " . "-s $non_host " . "-j REJECT --reject-with tcp-reset") == $IPT_SUCCESS) { ### in newer versions of iptables (> 1.3.5?) the "tcp-reset" ### command line arg has been changed to "tcp-rst" unless (&ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -p tcp " . "-s $non_host " . "-j REJECT --reject-with tcp-rst") == $IPT_SUCCESS) { &delete_test_chain(); die "[*] It does not appear that the REJECT target has been compiled into\n", " the kernel. The --ipt-reject option requires this option so that tcp\n", " sessions can be killed. Exiting.\n"; } } print "[+] $ipt_str has the 'REJECT' target support...\n" if $verbose or $ipt_check_capabilities; } &delete_test_chain(); exit 0 if $ipt_check_capabilities; ### more tests should be added return; } sub ipt_find_max_len() { my ($len, $ipt_pre_pattern, $ipt_post_pattern) = @_; my $test_pattern = ''; for (;;) { $test_pattern = 'A'x$len; last if &ipt_rule_test($ipt_pre_pattern . $test_pattern . $ipt_post_pattern) != $IPT_SUCCESS; $len += $ipt_cap_search_factor; last if $len >= $ipt_max_buf_len; } for (;;) { $test_pattern = 'A'x$len; last if &ipt_rule_test($ipt_pre_pattern . $test_pattern . $ipt_post_pattern) == $IPT_SUCCESS; $len--; last if $len == 1; ### minimum } return --$len; } sub ipt_find_max_multiport_supported_ports() { my $test_ports_str = $ipt_multiport_max-1; for (;;) { $test_ports_str .= ",$ipt_multiport_max"; my $test_rule_rv = 0; $test_rule_rv = &ipt_rule_test("-I $TEST_CHAIN $IPT_TEST_RULE_NUM -s " . qq|$non_host -p tcp -m multiport --dports $test_ports_str -j LOG |); last if $test_rule_rv != $IPT_SUCCESS; $ipt_multiport_max++; last if $ipt_multiport_max == $ipt_max_buf_len; ### unlikely we'll ever get here } $ipt_multiport_max--; print " Max supported multiport ports: $ipt_multiport_max\n" if $verbose or $ipt_check_capabilities; return; } sub dump_conf() { for my $var (sort keys %config) { printf "%-30s %s\n", "[+] $var", $config{$var}; } exit 0; } sub import_perl_modules() { my $mod_paths_ar = &get_mod_paths(); if ($#$mod_paths_ar > -1) { ### /usr/lib/fwsnort/ exists push @$mod_paths_ar, @INC; splice @INC, 0, $#$mod_paths_ar+1, @$mod_paths_ar; } if ($debug) { print "[+] import_perl_modules(): The \@INC array:\n"; print "$_\n" for @INC; } require IPTables::Parse; require NetAddr::IP; return; } sub get_mod_paths() { my @paths = (); $config{'LIBS_DIR'} = $lib_dir if $lib_dir; unless (-d $config{'LIBS_DIR'}) { my $dir_tmp = $config{'LIBS_DIR'}; $dir_tmp =~ s|lib/|lib64/|; if (-d $dir_tmp) { $config{'LIBS_DIR'} = $dir_tmp; } else { return []; } } opendir D, $config{'LIBS_DIR'} or die "[*] Could not open $config{'LIBS_DIR'}: $!"; my @dirs = readdir D; closedir D; push @paths, $config{'LIBS_DIR'}; for my $dir (@dirs) { ### get directories like "/usr/lib/fwsnort/x86_64-linux" next unless -d "$config{'LIBS_DIR'}/$dir"; push @paths, "$config{'LIBS_DIR'}/$dir" if $dir =~ m|linux| or $dir =~ m|thread|; } return \@paths; } sub setup() { ### these two directories must already exist for ### things to work die "[*] No fwsnort directory $config{'CONF_DIR'}" unless -d $config{'CONF_DIR'}; $config{'FWSNORT_SCRIPT'} = $ipt_script if $ipt_script; $config{'RULES_DIR'} = $rules_dir if $rules_dir; $config{'QUEUE_RULES_DIR'} = $queue_rules_dir if $queue_rules_dir; $config{'LOG_FILE'} = $logfile if $logfile; if ($rules_file) { for my $file (split /\,/, $rules_file) { die "[*] Snort rules file $file does not exist." unless -e $file; } } else { for my $dir (split /\,/, $config{'RULES_DIR'}) { die "[*] No snort rules directory $dir, use --snort-rdir" unless -d $dir; } } ### import fwsnort perl modules &import_perl_modules(); for my $dir ($config{'LOG_DIR'}, $config{'STATE_DIR'}) { unless (-d $dir) { mkdir $dir, 0755 or die "[*] Could not mkdir($dir): $!"; } } unless (-d $config{'ARCHIVE_DIR'}) { mkdir $config{'ARCHIVE_DIR'}, 0500 or die "[*] Could not mkdir($config{'ARCHIVE_DIR'}): $!"; } if (($queue_mode or $nfqueue_mode) and not -d $config{'QUEUE_RULES_DIR'}) { mkdir $config{'QUEUE_RULES_DIR'}, 0500 or die $!; } return; } sub update_rules() { print "[+] Downloading latest rules into $config{'RULES_DIR'}/"; my $dir = $config{'RULES_DIR'}; $dir =~ s/\,.*//; chdir $dir or die "[*] Could not chdir $dir: $!"; my $ctr = 0; for my $url (@{$config{'UPDATE_RULES_URL'}}) { my $file = ''; if ($url =~ m|.*/(.*)|) { $file = $1; } else { next; } if (-e $file) { move $file, "${file}.tmp" or die "[*] Could not move $file -> $file.tmp"; } system "$cmds{'wget'} $url"; if (-e $file) { ### successful download unlink "${file}.tmp"; } else { print "[-] Could not download $file file.\n"; if (-e "${file}.tmp") { ### move the original back move $file, "${file}.tmp" or die "[*] Could not move $file -> $file.tmp"; } } if ($ctr == 0 and $rules_url ne $DEFAULT_RULES_URL) { ### manual URL specified from the command line last; } } print "[+] Finished.\n"; exit 0; } sub ipt_rule_test() { my $rule = shift; my $chain = ''; print " CMD: $ipt_bin $rule\n" if $verbose; if ($rule =~ m/\-I\s+(\w+)\s/) { $chain = $1; } die qq{[*] Could not extract $ipt_str chain from: "$rule"} unless $chain; my $rv = (system "$ipt_bin $rule 2> /dev/null") >> 8; if ($rv == 0) { ### rule success, make sure to delete it. We force that ### the rule has just been inserted, so just delete the ### first rule in the chain. We could just delete the ### rule using $rule, but it is unlikely that the first ### rule in the chain isn't the one we just added system "$ipt_bin -D $chain $IPT_TEST_RULE_NUM"; return $IPT_SUCCESS; } return $IPT_FAILURE; } sub create_test_chain() { system "$ipt_bin -F $TEST_CHAIN 2> /dev/null"; system "$ipt_bin -N $TEST_CHAIN 2> /dev/null"; return; } sub delete_test_chain() { system "$ipt_bin -F $TEST_CHAIN 2> /dev/null"; system "$ipt_bin -X $TEST_CHAIN 2> /dev/null"; return; } sub chk_commands() { my @path = (qw( /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin )); CMD: for my $cmd (keys %cmds) { unless (-x $cmds{$cmd}) { my $found = 0; PATH: for my $dir (@path) { if (-x "${dir}/${cmd}") { $cmds{$cmd} = "${dir}/${cmd}"; $found = 1; last PATH; } } unless ($found) { unless ($cmd eq 'ifconfig' or $cmd eq 'ip') { die "[*] Could not find $cmd, edit $fwsnort_conf"; } } } } ### make sure either ifconfig or ip is available unless (-x $cmds{'ip'} or -x $cmds{'ifconfig'}) { die "[*] fwsnort requires either the 'ip' or 'ifconfig' command."; } return; } sub archive() { my $file = shift; return unless -e $file; return unless $file =~ m|/|; my ($filename) = ($file =~ m|.*/(.*)|); my $targetbase = "$config{'ARCHIVE_DIR'}/${filename}.old"; for (my $i = 4; $i > 1; $i--) { ### keep five copies of the old config files my $oldfile = $targetbase . $i; my $newfile = $targetbase . ($i+1); if (-e $oldfile) { move $oldfile, $newfile; } } if (-e $targetbase) { my $newfile = $targetbase . '2'; move $targetbase, $newfile; } &logr("[+] Archiving $file"); move $file, $targetbase; ### move $file into the archive directory return; } sub write_ipt_script() { ### archive any existing ipt_script file &archive($config{'FWSNORT_SCRIPT'}); ### make sure the script is writable first if (-e $config{'FWSNORT_SCRIPT'}) { chmod 0755, $config{'FWSNORT_SCRIPT'} or die "[*] Could not chmod $config{'FWSNORT_SCRIPT'}: $!"; } open F, "> $config{'FWSNORT_SCRIPT'}" or die "[*] Could not open $config{'FWSNORT_SCRIPT'}: $!"; print F "$_\n" for @ipt_script_lines; close F; chmod 0500, $config{'FWSNORT_SCRIPT'} or die "[*] Could not chmod $config{'FWSNORT_SCRIPT'}: $!"; return; } sub expand_addresses() { my $addr_string = shift; $addr_string =~ s/\]//; $addr_string =~ s/\[//; return ['0.0.0.0/0'] if $addr_string =~ /any/i; my @addrs = (); my @addrstmp = split /\s*,\s*/, $addr_string; for my $addr (@addrstmp) { if ($addr =~ m|($ip_re/$ip_re)|) { push @addrs, $1; } elsif ($addr =~ m|($ip_re/\d+)|) { push @addrs, $1; } elsif ($addr =~ m|($ip_re)|) { push @addrs, $1; } } return \@addrs; } sub run_last_cmdline() { my $save_file = "$homedir/.fwsnort.run"; open S, "< $save_file" or die "[*] Could not open $save_file: $!"; my $arg_line = ; close S; chomp $arg_line; print "[+] Running with last command line args: $arg_line\n"; @ARGV = split /\s+/, $arg_line; @argv_cp = @ARGV; ### run GetOpt() to get command line args &handle_cmd_line(); return; } sub save_args() { my $save_file = "$homedir/.fwsnort.run"; open S, "> $save_file" or die "[*] Could not open $save_file"; print S "@argv_cp\n"; close S; return; } sub get_homedir() { my $uid = $<; if ($cmdl_homedir) { $homedir = $cmdl_homedir; } else { ### prefer homedir specified in /etc/passwd (if it exists) if (-e '/etc/passwd') { open P, "< /etc/passwd" or die "[*] Could not open /etc/passwd. ", "Exiting.\n"; my @lines =

; close P; for my $line (@lines) { ### mbr:x:222:222:Michael Rash:/home/mbr:/bin/bash chomp $line; if ($line =~ /^(?:.*:){2}$uid:(?:.*:){2}(\S+):/) { $homedir = $1; last; } } } unless ($homedir and -d $homedir) { $homedir = $ENV{'HOME'} if defined $ENV{'HOME'}; } } die '[*] Could not determine homedir, use --Home option.' unless ($homedir and -d $homedir); return; } sub truncate_logfile() { open L, "> $config{'LOG_FILE'}" or die "[*] Could not open $config{'LOG_FILE'}: $!"; close L; return; } sub write_save_file() { ### append the final 'COMMIT' and 'Completed' lines push @fwsnort_save_lines, "COMMIT\n"; push @fwsnort_save_lines, $ipt_save_completed_line; ### write out the iptables-save formatted fwsnort rules &archive($config{'FWSNORT_SAVE_FILE'}); ### make sure the save file is writable first if (-e $config{'FWSNORT_SCRIPT'}) { chmod 0755, $config{'FWSNORT_SCRIPT'} or die $!; } open T, "> $config{'FWSNORT_SAVE_FILE'}" or die "[*] Could not write to: $config{'FWSNORT_SAVE_FILE'}"; print T for @fwsnort_save_lines; close T; chmod 0600, $config{'FWSNORT_SAVE_FILE'} or die "[*] Could not chmod 0600 $config{'FWSNORT_SAVE_FILE'}"; ### write out the script that will exec iptables-restore against ### save file - this is what splices the fwsnort policy into the ### running iptables policy &archive($config{'FWSNORT_SAVE_EXEC_FILE'}); my @fws_exec_lines = (); push @fws_exec_lines, &hdr_lines(); push @fws_exec_lines, <<_FWSNORT_SH_; DO_REVERT=0 while getopts r f do case \$f in r) DO_REVERT=1 esac done if [ "\$DO_REVERT" = 1 ]; then echo " " echo "[+] Reverting to original iptables policy..." $cmds{'grep'} -v FWSNORT $config{'FWSNORT_SAVE_FILE'} | exec $restore_bin else echo " " echo "[+] Splicing fwsnort $abs_num rules into the iptables policy..." $cmds{'cat'} $config{'FWSNORT_SAVE_FILE'} | exec $restore_bin fi exit _FWSNORT_SH_ ### make sure the file is writable first if (-e $config{'FWSNORT_SAVE_EXEC_FILE'}) { chmod 0755, $config{'FWSNORT_SAVE_EXEC_FILE'} or die $!; } open T, "> $config{'FWSNORT_SAVE_EXEC_FILE'}" or die "[*] Could not write to: $config{'FWSNORT_SAVE_EXEC_FILE'}"; print T "$_\n" for @fws_exec_lines; close T; chmod 0500, $config{'FWSNORT_SAVE_EXEC_FILE'} or die "[*] Could not chmod 0500 $config{'FWSNORT_SAVE_EXEC_FILE'}"; return; } sub is_root() { if ($< == 0 and $> == 0) { $is_root = 1; } return; } sub set_non_root_values() { if ($fwsnort_conf eq $CONFIG_DEFAULT) { die "[*] Must specify a path to readable ", "fwsnort.conf file when not running as root."; } &set_defaults_without_ipt_test(); return; } sub set_defaults_without_ipt_test() { $have_conntrack = 1; $ipt_max_str_len = 128; $ipt_max_comment_len = 255; $ipt_max_log_prefix_len = 29; $ipt_have_multiport_match = 1; $ipt_multiport_max = 15; ### put ipopts in the unsupported list if (defined $snort_opts{'filter'}{'ipopts'}) { $snort_opts{'unsupported'}{'ipopts'} = $snort_opts{'filter'}{'ipopts'}{'regex'}; delete $snort_opts{'filter'}{'ipopts'}; } else { $snort_opts{'unsupported'}{'ipopts'} = qr/[\s;]ipopts:\s*(\w+)\s*;/; } return; } sub print_final_message() { if ($is_root) { print <<_MSG_; Main fwsnort $save_str file: $config{'FWSNORT_SAVE_FILE'} You can instantiate the fwsnort policy with the following command: $restore_bin < $config{'FWSNORT_SAVE_FILE'} Or just execute: $config{'FWSNORT_SAVE_EXEC_FILE'} _MSG_ } else { print <<_MSG_; Main fwsnort $save_str file: $config{'FWSNORT_SAVE_FILE'} It does not appear as though you are running as root, so it is NOT recommended that you run the fwsnort.sh script without first re-running fwsnort as root first. The reason is that non-root users cannot execute iptables, and therefore fwsnort had no way to check for iptables capabilities or to parse any existing iptables policy for proper splicing of fwsnort rules. Exiting. _MSG_ } return; } sub logr() { my $msg = shift; if ($stdout) { print STDOUT "$msg\n"; } else { open F, ">> $config{'LOG_FILE'}" or die "[*] Could not open $config{'LOG_FILE'}: $!"; print F "$msg\n"; close F; } return; } sub usage() { my $exit = shift; print <<_USAGE_; fwsnort v$version [+] By Michael Rash , http://www.cipherdyne.org/ Usage: fwsnort [options] Options: --strict - Make snort parser very strict about which options it will translate into iptables rules. --ipt-script=