psad-2.2.1/0000775000175000017500000000000012071204605010564 5ustar mbrmbrpsad-2.2.1/strlcat.c0000664000175000017500000000477512071203757012430 0ustar mbrmbr/* $OpenBSD: strlcat.c,v 1.9 2003/03/14 14:35:29 millert Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if defined(LIBC_SCCS) && !defined(lint) static char *rcsid = "$OpenBSD: strlcat.c,v 1.9 2003/03/14 14:35:29 millert Exp $"; #endif /* LIBC_SCCS and not lint */ #include #include /* * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(src) + MIN(siz, strlen(initial dst)). * If retval >= siz, truncation occurred. */ size_t strlcat(char *dst, const char *src, size_t siz) { register char *d = dst; register const char *s = src; register size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return(dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } psad-2.2.1/todo.org0000664000175000017500000001152312071203757012253 0ustar mbrmbr* COMPLETE This bucket is for completed tasks. ** Nmap protocol scan detection :CLOSED: <2012-12-15 Sat> * TODO ** Automated tests to verify correct behavior for command line options (and potentially other things such as correctness of psad alerts). ** Take into account whether a destination port is in /etc/services for the danger level calculaion. A SYN packet to tcp/22 is worse than a stray SYN packet to an arbitrary high port (as long as there isn't a backdoor, etc.). There are (probably) historically more vulnerabilities in sshd than for some service that isn't even listed in /etc/services. ** Idle scan detection through seeing combination of SYN/ACK and RST packets (i.e. the iptables box was used as a zombie host). ** XML logging format. - HTML output mode, and ability to create IP directories/pages under a web root directory. - Add the ability to install.pl to restore the "latest" syslog config backup file (fwknop may have been installed for example) at uninstall time. ** Infer NMAP scan if OPT does not exist in the iptables log (because tcp options are missing)? - Play with SHOW_ALL_SIGNATURES = "Y" since this may not really cause hugely long email alerts. This trick would be to perhaps associate a "last seen" timestamp with each old signature. ** MRTG scripts. :<2012-12-01 Sat> ** Add a DNS_TIMEOUT config keyword. :<2012-12-01 Sat> ** Add a threshold danger level for ENABLE_EXT_SCRIPT_EXEC functionality. ** Ability to remove email block for a specific ip from a running psad process. ** Summary report emails like dshield does. ** Ability to elevate scan danger level based on specific iptables prefixes. ** Replace full ascii signature listings in _signatures with sid numbers and packet counts. ** Rework IGNORE_CONNTRACK_BUG_PKTS strategy to maximze signature detection. ** More syslog messages from psad, psadwatchd, and kmsgsd. ** Put a "psad signature strategy" link in all alert emails. ** Extract default behavior into psad.conf. ** Custom logging line upon auto-blocking an ip. ** Add difference notification (via syslog) for changed variables after receiving a HUP signal. ** Include the ability to specify a network in CIDR notation with --Status output. ** Drop root privileges if not running in auto-blocking mode. ** Extend install.pl to provide an option to dowload the latest perl modules (Date::Calc, Unix::Syslog, etc.) from CPAN. ** Extend passive OS fingerprinting to make use of more types of packets than just tcp/syn packets. ** Extend passive OS fingerprinting to include signatures from Xprobe from http://www.sys-security.com. - Add a density calculation for a range of scanned ports, and also add a "verbose" mode that will display which of the scanned ports actually resolve to something in the IANA spec. ** Packet grapher mode with annotated scan alerts. ** Mysql database support? ** psad.conf option to disable signature detection; useful if fwsnort is already deployed for this. ** Include a verbose message in the body of certain emails that as of psad-1.0.0-pre2 only contain a subject line. - Deal with the possibility that psad could eat lots of memory over time if $ENABLE_PERSISTENCE="Y". This should involve periodically deleting entries in %scan (or maybe the entire hash), but this should be done in a way that allows some scan data to persist. ** ipfw/pf/ipfilter support on *BSD platforms. ** Take into account syslog message summarization; i.e. "last message repeated n times". - Possibly add a daemon to take into account ACK PSH, ACK FIN, RST etc. packets that the client may generate after the ip_conntrack module is reloaded. Without anticipating such packets psad will interpret them as a belonging to a port scan. NOTE: This problem is mostly corrected by the conntrack patch to the kernel. Also, the IGNORE_CONNTRACK_BUG_PKTS variable was added to mitigate this problem. - Improve check_firewall_rules() to check for a state rule (iptables) since having such a rule greatly improves the quality of the data stream provided to psad by kmsgsd since more packet types will be denied without requiring overly complicated firewall rules to detect odd tcp flag combinations. ** Configurable iptables prerequisite checks. - Handle "pass" action on Snort rules in the signatures file. This will allow ignore rules to be written in the Snort rules language itself (this will far more powerful than any of the IGNORE_* keywords). ** Allow auto-response blocking based on either src or dst of a signature match. :<2012-11-21 Wed> ** Include IP options decode information in email alert if a signature matched against IP options. :<2012-11-21 Wed> ** Include input/output interfaces, as well as physin and physout interfaces. :<2012-11-21 Wed> ** IPCop integration. :<2012-11-21 Wed> ** Script to turn pcap files into equivalent iptables log messages. :<2012-11-21 Wed> psad-2.2.1/FW_EXAMPLE_RULES0000664000175000017500000000322712071203757013163 0ustar mbrmbr The following firewall rulesets are examples of rulesets that are compatible with psad. Basically, the only criteria is have the firewall log and drop packets that should not be allowed through. Then a port scan will manifest itself within /var/log/messages as packets are dropped and logged, at which time these messages will be written to the /var/lib/psad/psadfifo named pipe and analyzed by psad. ### iptables: Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED ACCEPT tcp -- 129.xx.xx.xx 64.44.21.15 tcp dpt:22 flags:SYN,RST,ACK/SYN ACCEPT tcp -- 208.xx.xx.xx 64.44.21.15 tcp dpt:22 flags:SYN,RST,ACK/SYN ACCEPT tcp -- 24.xx.xx.xx 64.44.21.15 tcp dpt:22 flags:SYN,RST,ACK/SYN ACCEPT tcp -- 208.xx.xx.xx 64.44.21.15 tcp dpt:22 flags:SYN,RST,ACK/SYN ACCEPT tcp -- 0.0.0.0/0 64.44.21.15 tcp dpt:25 flags:SYN,RST,ACK/SYN ACCEPT tcp -- 0.0.0.0/0 64.44.21.15 tcp dpt:80 flags:SYN,RST,ACK/SYN LOG all -- 0.0.0.0/0 0.0.0.0/0 LOG level warning prefix `DROP ' DROP all -- 0.0.0.0/0 0.0.0.0/0 Chain FORWARD (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED LOG all -- 0.0.0.0/0 0.0.0.0/0 LOG level warning prefix `DROP ' DROP all -- 0.0.0.0/0 0.0.0.0/0 Chain OUTPUT (policy ACCEPT) target prot opt source destination psad-2.2.1/pf.os0000664000175000017500000006744412071203757011562 0ustar mbrmbr# $OpenBSD: pf.os,v 1.25 2010/10/18 15:55:27 deraadt Exp $ # passive OS fingerprinting # ------------------------- # # SYN signatures. Those signatures work for SYN packets only (duh!). # # (C) Copyright 2000-2003 by Michal Zalewski # (C) Copyright 2003 by Mike Frantzen # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # # This fingerprint database is adapted from Michal Zalewski's p0f passive # operating system package. The last database sync was from a Nov 3 2003 # p0f.fp. # # # Each line in this file specifies a single fingerprint. Please read the # information below carefully before attempting to append any signatures # reported as UNKNOWN to this file to avoid mistakes. # # We use the following set metrics for fingerprinting: # # - Window size (WSS) - a highly OS dependent setting used for TCP/IP # performance control (max. amount of data to be sent without ACK). # Some systems use a fixed value for initial packets. On other # systems, it is a multiple of MSS or MTU (MSS+40). In some rare # cases, the value is just arbitrary. # # NEW SIGNATURE: if p0f reported a special value of 'Snn', the number # appears to be a multiple of MSS (MSS*nn); a special value of 'Tnn' # means it is a multiple of MTU ((MSS+40)*nn). Unless you notice the # value of nn is not fixed (unlikely), just copy the Snn or Tnn token # literally. If you know this device has a simple stack and a fixed # MTU, you can however multiply S value by MSS, or T value by MSS+40, # and put it instead of Snn or Tnn. # # If WSS otherwise looks like a fixed value (for example a multiple # of two), or if you can confirm the value is fixed, please quote # it literally. If there's no apparent pattern in WSS chosen, you # should consider wildcarding this value. # # - Overall packet size - a function of all IP and TCP options and bugs. # # NEW SIGNATURE: Copy this value literally. # # - Initial TTL - We check the actual TTL of a received packet. It can't # be higher than the initial TTL, and also shouldn't be dramatically # lower (maximum distance is defined as 40 hops). # # NEW SIGNATURE: *Never* copy TTL from a p0f-reported signature literally. # You need to determine the initial TTL. The best way to do it is to # check the documentation for a remote system, or check its settings. # A fairly good method is to simply round the observed TTL up to # 32, 64, 128, or 255, but it should be noted that some obscure devices # might not use round TTLs (in particular, some shoddy appliances use # "original" initial TTL settings). If not sure, you can see how many # hops you're away from the remote party with traceroute or mtr. # # - Don't fragment flag (DF) - some modern OSes set this to implement PMTU # discovery. Others do not bother. # # NEW SIGNATURE: Copy this value literally. # # - Maximum segment size (MSS) - this setting is usually link-dependent. P0f # uses it to determine link type of the remote host. # # NEW SIGNATURE: Always wildcard this value, except for rare cases when # you have an appliance with a fixed value, know the system supports only # a very limited number of network interface types, or know the system # is using a value it pulled out of nowhere. Specific unique MSS # can be used to tell Google crawlbots from the rest of the population. # # - Window scaling (WSCALE) - this feature is used to scale WSS. # It extends the size of a TCP/IP window to 32 bits. Some modern # systems implement this feature. # # NEW SIGNATURE: Observe several signatures. Initial WSCALE is often set # to zero or other low value. There's usually no need to wildcard this # parameter. # # - Timestamp - some systems that implement timestamps set them to # zero in the initial SYN. This case is detected and handled appropriately. # # - Selective ACK permitted - a flag set by systems that implement # selective ACK functionality. # # - The sequence of TCP all options (MSS, window scaling, selective ACK # permitted, timestamp, NOP). Other than the options previously # discussed, p0f also checks for timestamp option (a silly # extension to broadcast your uptime ;-), NOP options (used for # header padding) and sackOK option (selective ACK feature). # # NEW SIGNATURE: Copy the sequence literally. # # To wildcard any value (except for initial TTL or TCP options), replace # it with '*'. You can also use a modulo operator to match any values # that divide by nnn - '%nnn'. # # Fingerprint entry format: # # wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details # # wwww - window size (can be *, %nnn, Snn or Tnn). The special values # "S" and "T" which are a multiple of MSS or a multiple of MTU # respectively. # ttt - initial TTL # D - don't fragment bit (0 - not set, 1 - set) # ss - overall SYN packet size # OOO - option value and order specification (see below) # OS - OS genre (Linux, Solaris, Windows) # Version - OS Version (2.0.27 on x86, etc) # Subtype - OS subtype or patchlevel (SP3, lo0) # details - Generic OS details # # If OS genre starts with '*', p0f will not show distance, link type # and timestamp data. It is useful for userland TCP/IP stacks of # network scanners and so on, where many settings are randomized or # bogus. # # If OS genre starts with @, it denotes an approximate hit for a group # of operating systems (signature reporting still enabled in this case). # Use this feature at the end of this file to catch cases for which # you don't have a precise match, but can tell it's Windows or FreeBSD # or whatnot by looking at, say, flag layout alone. # # Option block description is a list of comma or space separated # options in the order they appear in the packet: # # N - NOP option # Wnnn - window scaling option, value nnn (or * or %nnn) # Mnnn - maximum segment size option, value nnn (or * or %nnn) # S - selective ACK OK # T - timestamp # T0 - timestamp with a zero value # # To denote no TCP options, use a single '.'. # # Please report any additions to this file, or any inaccuracies or # problems spotted, to the maintainers: lcamtuf@coredump.cx, # frantzen@openbsd.org and bugs@openbsd.org with a tcpdump packet # capture of the relevant SYN packet(s) # # A test and submission page is available at # http://lcamtuf.coredump.cx/p0f-help/ # # # WARNING WARNING WARNING # ----------------------- # # Do not add a system X as OS Y just because NMAP says so. It is often # the case that X is a NAT firewall. While nmap is talking to the # device itself, p0f is fingerprinting the guy behind the firewall # instead. # # When in doubt, use common sense, don't add something that looks like # a completely different system as Linux or FreeBSD or LinkSys router. # Check DNS name, establish a connection to the remote host and look # at SYN+ACK - does it look similar? # # Some users tweak their TCP/IP settings - enable or disable RFC1323 # functionality, enable or disable timestamps or selective ACK, # disable PMTU discovery, change MTU and so on. Always compare a new rule # to other fingerprints for this system, and verify the system isn't # "customized" before adding it. It is OK to add signature variants # caused by a commonly used software (personal firewalls, security # packages, etc), but it makes no sense to try to add every single # possible /proc/sys/net/ipv4 tweak on Linux or so. # # KEEP IN MIND: Some packet firewalls configured to normalize outgoing # traffic (OpenBSD pf with "scrub" enabled, for example) will, well, # normalize packets. Signatures will not correspond to the originating # system (and probably not quite to the firewall either). # # NOTE: Try to keep this file in some reasonable order, from most to # least likely systems. This will speed up operation. Also keep most # generic and broad rules near the end. # ########################## # Standard OS signatures # ########################## # ----------------- AIX --------------------- # AIX is first because its signatures are close to NetBSD, MacOS X and # Linux 2.0, but it uses a fairly rare MSSes, at least sometimes... # This is a shoddy hack, though. 45046:64:0:44:M*: AIX:4.3::AIX 4.3 16384:64:0:44:M512: AIX:4.3:2-3:AIX 4.3.2 and earlier 16384:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2 16384:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2 32768:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2 32768:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2 65535:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2 65535:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2 65535:64:0:64:M*,N,W1,N,N,T,N,N,S: AIX:5.3:ML1:AIX 5.3 ML1 # ----------------- Linux ------------------- # S1:64:0:44:M*:A: Linux:1.2::Linux 1.2.x (XXX quirks support) 512:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x 16384:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x # Endian snafu! Nelson says "ha-ha": 2:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac 64:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac S4:64:1:60:M1360,S,T,N,W0: Linux:google::Linux (Google crawlbot) S2:64:1:60:M*,S,T,N,W0: Linux:2.4::Linux 2.4 (big boy) S3:64:1:60:M*,S,T,N,W0: Linux:2.4:.18-21:Linux 2.4.18 and newer S4:64:1:60:M*,S,T,N,W0: Linux:2.4::Linux 2.4/2.6 <= 2.6.7 S4:64:1:60:M*,S,T,N,W0: Linux:2.6:.1-7:Linux 2.4/2.6 <= 2.6.7 S4:64:1:60:M*,S,T,N,W7: Linux:2.6:8:Linux 2.6.8 and newer (?) S3:64:1:60:M*,S,T,N,W1: Linux:2.5::Linux 2.5 (sometimes 2.4) S4:64:1:60:M*,S,T,N,W1: Linux:2.5-2.6::Linux 2.5/2.6 S3:64:1:60:M*,S,T,N,W2: Linux:2.5::Linux 2.5 (sometimes 2.4) S4:64:1:60:M*,S,T,N,W2: Linux:2.5::Linux 2.5 (sometimes 2.4) S20:64:1:60:M*,S,T,N,W0: Linux:2.2:20-25:Linux 2.2.20 and newer S22:64:1:60:M*,S,T,N,W0: Linux:2.2::Linux 2.2 S11:64:1:60:M*,S,T,N,W0: Linux:2.2::Linux 2.2 # Popular cluster config scripts disable timestamps and # selective ACK: S4:64:1:48:M1460,N,W0: Linux:2.4:cluster:Linux 2.4 in cluster # This needs to be investigated. On some systems, WSS # is selected as a multiple of MTU instead of MSS. I got # many submissions for this for many late versions of 2.4: T4:64:1:60:M1412,S,T,N,W0: Linux:2.4::Linux 2.4 (late, uncommon) # This happens only over loopback, but let's make folks happy: 32767:64:1:60:M16396,S,T,N,W0: Linux:2.4:lo0:Linux 2.4 (local) S8:64:1:60:M3884,S,T,N,W0: Linux:2.2:lo0:Linux 2.2 (local) # Opera visitors: 16384:64:1:60:M*,S,T,N,W0: Linux:2.2:Opera:Linux 2.2 (Opera?) 32767:64:1:60:M*,S,T,N,W0: Linux:2.4:Opera:Linux 2.4 (Opera?) # Some fairly common mods: S4:64:1:52:M*,N,N,S,N,W0: Linux:2.4:ts:Linux 2.4 w/o timestamps S22:64:1:52:M*,N,N,S,N,W0: Linux:2.2:ts:Linux 2.2 w/o timestamps # ----------------- FreeBSD ----------------- 16384:64:1:44:M*: FreeBSD:2.0-2.2::FreeBSD 2.0-4.2 16384:64:1:44:M*: FreeBSD:3.0-3.5::FreeBSD 2.0-4.2 16384:64:1:44:M*: FreeBSD:4.0-4.2::FreeBSD 2.0-4.2 16384:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.4::FreeBSD 4.4 1024:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.4::FreeBSD 4.4 57344:64:1:44:M*: FreeBSD:4.6-4.8:noRFC1323:FreeBSD 4.6-4.8 (no RFC1323) 57344:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.6-4.9::FreeBSD 4.6-4.9 32768:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.8-4.11::FreeBSD 4.8-5.1 (or MacOS X) 32768:64:1:60:M*,N,W0,N,N,T: FreeBSD:5.0-5.1::FreeBSD 4.8-5.1 (or MacOS X) 65535:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.8-4.11::FreeBSD 4.8-5.2 (or MacOS X) 65535:64:1:60:M*,N,W0,N,N,T: FreeBSD:5.0-5.2::FreeBSD 4.8-5.2 (or MacOS X) 65535:64:1:60:M*,N,W1,N,N,T: FreeBSD:4.7-4.11::FreeBSD 4.7-5.2 65535:64:1:60:M*,N,W1,N,N,T: FreeBSD:5.0-5.2::FreeBSD 4.7-5.2 # XXX need quirks support # 65535:64:1:60:M*,N,W0,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (1) # 65535:64:1:60:M*,N,W1,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (2) # 65535:64:1:60:M*,N,W2,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (3) # 65535:64:1:44:M*:Z:FreeBSD:5.2::FreeBSD 5.2 (no RFC1323) # 16384:64:1:60:M*,N,N,N,N,N,N,T:FreeBSD:4.4:noTS:FreeBSD 4.4 (w/o timestamps) # ----------------- NetBSD ------------------ 16384:64:0:60:M*,N,W0,N,N,T: NetBSD:1.3::NetBSD 1.3 65535:64:0:60:M*,N,W0,N,N,T0: NetBSD:1.6:opera:NetBSD 1.6 (Opera) 16384:64:0:60:M*,N,W0,N,N,T0: NetBSD:1.6::NetBSD 1.6 16384:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6:df:NetBSD 1.6 (DF) 65535:64:1:60:M*,N,W1,N,N,T0: NetBSD:1.6::NetBSD 1.6W-current (DF) 65535:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6::NetBSD 1.6X (DF) 32768:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6:randomization:NetBSD 1.6ZH-current (w/ ip_id randomization) # ----------------- OpenBSD ----------------- 16384:64:0:60:M*,N,W0,N,N,T: OpenBSD:2.6::NetBSD 1.3 (or OpenBSD 2.6) 16384:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.8::OpenBSD 3.0-4.8 16384:64:0:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.8:no-df:OpenBSD 3.0-4.8 (scrub no-df) 57344:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.3-4.0::OpenBSD 3.3-4.0 57344:64:0:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.3-4.0:no-df:OpenBSD 3.3-4.0 (scrub no-df) 65535:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.0:opera:OpenBSD 3.0-4.0 (Opera) 16384:64:1:64:M*,N,N,S,N,W3,N,N,T: OpenBSD:4.9::OpenBSD 4.9 16384:64:0:64:M*,N,N,S,N,W3,N,N,T: OpenBSD:4.9:no-df:OpenBSD 4.9 (scrub no-df) # ----------------- Solaris ----------------- S17:64:1:64:N,W3,N,N,T0,N,N,S,M*: Solaris:8:RFC1323:Solaris 8 RFC1323 S17:64:1:48:N,N,S,M*: Solaris:8::Solaris 8 S17:255:1:44:M*: Solaris:2.5-2.7::Solaris 2.5 to 7 S6:255:1:44:M*: Solaris:2.6-2.7::Solaris 2.6 to 7 S23:255:1:44:M*: Solaris:2.5:1:Solaris 2.5.1 S34:64:1:48:M*,N,N,S: Solaris:2.9::Solaris 9 S44:255:1:44:M*: Solaris:2.7::Solaris 7 4096:64:0:44:M1460: SunOS:4.1::SunOS 4.1.x S34:64:1:52:M*,N,W0,N,N,S: Solaris:10:beta:Solaris 10 (beta) 32850:64:1:64:M*,N,N,T,N,W1,N,N,S: Solaris:10::Solaris 10 1203 # ----------------- IRIX -------------------- 49152:64:0:44:M*: IRIX:6.4::IRIX 6.4 61440:64:0:44:M*: IRIX:6.2-6.5::IRIX 6.2-6.5 49152:64:0:52:M*,N,W2,N,N,S: IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323) 49152:64:0:52:M*,N,W3,N,N,S: IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323) 61440:64:0:48:M*,N,N,S: IRIX:6.5:12-21:IRIX 6.5.12 - 6.5.21 49152:64:0:48:M*,N,N,S: IRIX:6.5:15-21:IRIX 6.5.15 - 6.5.21 49152:60:0:64:M*,N,W2,N,N,T,N,N,S: IRIX:6.5:IP27:IRIX 6.5 IP27 # ----------------- Tru64 ------------------- 32768:64:1:48:M*,N,W0: Tru64:4.0::Tru64 4.0 (or OS/2 Warp 4) 32768:64:0:48:M*,N,W0: Tru64:5.0::Tru64 5.0 8192:64:0:44:M1460: Tru64:5.1:noRFC1323:Tru64 6.1 (no RFC1323) (or QNX 6) 61440:64:0:48:M*,N,W0: Tru64:5.1a:JP4:Tru64 v5.1a JP4 (or OpenVMS 7.x on Compaq 5.x stack) # ----------------- OpenVMS ----------------- 6144:64:1:60:M*,N,W0,N,N,T: OpenVMS:7.2::OpenVMS 7.2 (Multinet 4.4 stack) # ----------------- MacOS ------------------- # XXX Need EOL tcp opt support # S2:255:1:48:M*,W0,E:.:MacOS:8.6 classic # XXX some of these use EOL too 16616:255:1:48:M*,W0: MacOS:7.3-7.6:OTTCP:MacOS 7.3-8.6 (OTTCP) 16616:255:1:48:M*,W0: MacOS:8.0-8.6:OTTCP:MacOS 7.3-8.6 (OTTCP) 16616:255:1:48:M*,N,N,N: MacOS:8.1-8.6:OTTCP:MacOS 8.1-8.6 (OTTCP) 32768:255:1:48:M*,W0,N: MacOS:9.0-9.2::MacOS 9.0-9.2 65535:255:1:48:M*,N,N,N,N: MacOS:9.1::MacOS 9.1 (OT 2.7.4) # ----------------- Windows ----------------- # Windows TCP/IP stack is a mess. For most recent XP, 2000 and # even 98, the patchlevel, not the actual OS version, is more # relevant to the signature. They share the same code, so it would # seem. Luckily for us, almost all Windows 9x boxes have an # awkward MSS of 536, which I use to tell one from another # in most difficult cases. 8192:32:1:44:M*: Windows:3.11::Windows 3.11 (Tucows) S44:64:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:95::Windows 95 8192:128:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:95:b:Windows 95b # There were so many tweaking tools and so many stack versions for # Windows 98 it is no longer possible to tell them from each other # without some very serious research. Until then, there's an insane # number of signatures, for your amusement: S44:32:1:48:M*,N,N,S: Windows:98:lowTTL:Windows 98 (low TTL) 8192:32:1:48:M*,N,N,S: Windows:98:lowTTL:Windows 98 (low TTL) %8192:64:1:48:M536,N,N,S: Windows:98::Windows 98 %8192:128:1:48:M536,N,N,S: Windows:98::Windows 98 S4:64:1:48:M*,N,N,S: Windows:98::Windows 98 S6:64:1:48:M*,N,N,S: Windows:98::Windows 98 S12:64:1:48:M*,N,N,S: Windows:98::Windows 98 T30:64:1:64:M1460,N,W0,N,N,T0,N,N,S: Windows:98::Windows 98 32767:64:1:48:M*,N,N,S: Windows:98::Windows 98 37300:64:1:48:M*,N,N,S: Windows:98::Windows 98 46080:64:1:52:M*,N,W3,N,N,S: Windows:98:RFC1323:Windows 98 (RFC1323) 65535:64:1:44:M*: Windows:98:noSack:Windows 98 (no sack) S16:128:1:48:M*,N,N,S: Windows:98::Windows 98 S16:128:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:98::Windows 98 S26:128:1:48:M*,N,N,S: Windows:98::Windows 98 T30:128:1:48:M*,N,N,S: Windows:98::Windows 98 32767:128:1:52:M*,N,W0,N,N,S: Windows:98::Windows 98 60352:128:1:48:M*,N,N,S: Windows:98::Windows 98 60352:128:1:64:M*,N,W2,N,N,T0,N,N,S: Windows:98::Windows 98 # What's with 1414 on NT? T31:128:1:44:M1414: Windows:NT:4.0:Windows NT 4.0 SP6a 64512:128:1:44:M1414: Windows:NT:4.0:Windows NT 4.0 SP6a 8192:128:1:44:M*: Windows:NT:4.0:Windows NT 4.0 (older) # Windows XP and 2000. Most of the signatures that were # either dubious or non-specific (no service pack data) # were deleted and replaced with generics at the end. 65535:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4, XP SP1 65535:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP4, XP SP1 %8192:128:1:48:M*,N,N,S: Windows:2000:SP2+:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222) %8192:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222) S20:128:1:48:M*,N,N,S: Windows:2000::Windows 2000/XP SP3 S20:128:1:48:M*,N,N,S: Windows:XP:SP3:Windows 2000/XP SP3 S45:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4, XP SP 1 S45:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP4, XP SP 1 40320:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4 S6:128:1:48:M*,N,N,S: Windows:2000:SP2:Windows XP, 2000 SP2+ S6:128:1:48:M*,N,N,S: Windows:XP::Windows XP, 2000 SP2+ S12:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows XP SP1 S44:128:1:48:M*,N,N,S: Windows:2000:SP3:Windows Pro SP1, 2000 SP3 S44:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows Pro SP1, 2000 SP3 64512:128:1:48:M*,N,N,S: Windows:2000:SP3:Windows SP1, 2000 SP3 64512:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows SP1, 2000 SP3 32767:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows SP1, 2000 SP4 32767:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows SP1, 2000 SP4 # Odds, ends, mods: S52:128:1:48:M1260,N,N,S: Windows:2000:cisco:Windows XP/2000 via Cisco S52:128:1:48:M1260,N,N,S: Windows:XP:cisco:Windows XP/2000 via Cisco 65520:128:1:48:M*,N,N,S: Windows:XP::Windows XP bare-bone 16384:128:1:52:M536,N,W0,N,N,S: Windows:2000:ZoneAlarm:Windows 2000 w/ZoneAlarm? 2048:255:0:40:.: Windows:.NET::Windows .NET Enterprise Server 44620:64:0:48:M*,N,N,S: Windows:ME::Windows ME no SP (?) S6:255:1:48:M536,N,N,S: Windows:95:winsock2:Windows 95 winsock 2 32768:32:1:52:M1460,N,W0,N,N,S: Windows:2003:AS:Windows 2003 AS # No need to be more specific, it passes: # *:128:1:48:M*,N,N,S:U:-Windows:XP/2000 while downloading (leak!) XXX quirk # there is an equiv similar generic sig w/o the quirk # ----------------- HP/UX ------------------- 32768:64:1:44:M*: HP-UX:B.10.20::HP-UX B.10.20 32768:64:0:48:M*,W0,N: HP-UX:11.0::HP-UX 11.0 32768:64:1:48:M*,W0,N: HP-UX:11.10::HP-UX 11.0 or 11.11 32768:64:1:48:M*,W0,N: HP-UX:11.11::HP-UX 11.0 or 11.11 # Whoa. Hardcore WSS. 0:64:0:48:M*,W0,N: HP-UX:B.11.00:A:HP-UX B.11.00 A (RFC1323) # ----------------- RiscOS ------------------ # We don't yet support the ?12 TCP option #16384:64:1:68:M1460,N,W0,N,N,T,N,N,?12: RISCOS:3.70-4.36::RISC OS 3.70-4.36 12288:32:0:44:M536: RISC OS:3.70:4.10:RISC OS 3.70 inet 4.10 # XXX quirk # 4096:64:1:56:M1460,N,N,T:T: RISC OS:3.70:freenet:RISC OS 3.70 freenet 2.00 # ----------------- BSD/OS ------------------ # Once again, power of two WSS is also shared by MacOS X with DF set 8192:64:1:60:M1460,N,W0,N,N,T: BSD/OS:3.1::BSD/OS 3.1-4.3 (or MacOS X 10.2 w/DF) 8192:64:1:60:M1460,N,W0,N,N,T: BSD/OS:4.0-4.3::BSD/OS 3.1-4.3 (or MacOS X 10.2) # ---------------- NewtonOS ----------------- 4096:64:0:44:M1420: NewtonOS:2.1::NewtonOS 2.1 # ---------------- NeXTSTEP ----------------- S4:64:0:44:M1024: NeXTSTEP:3.3::NeXTSTEP 3.3 S8:64:0:44:M512: NeXTSTEP:3.3::NeXTSTEP 3.3 # ------------------ BeOS ------------------- 1024:255:0:48:M*,N,W0: BeOS:5.0-5.1::BeOS 5.0-5.1 12288:255:0:44:M1402: BeOS:5.0::BeOS 5.0.x # ------------------ OS/400 ----------------- 8192:64:1:60:M1440,N,W0,N,N,T: OS/400:VR4::OS/400 VR4/R5 8192:64:1:60:M1440,N,W0,N,N,T: OS/400:VR5::OS/400 VR4/R5 4096:64:1:60:M1440,N,W0,N,N,T: OS/400:V4R5:CF67032:OS/400 V4R5 + CF67032 # XXX quirk # 28672:64:0:44:M1460:A:OS/390:? # ------------------ ULTRIX ----------------- 16384:64:0:40:.: ULTRIX:4.5::ULTRIX 4.5 # ------------------- QNX ------------------- S16:64:0:44:M512: QNX:::QNX demodisk # ------------------ Novell ----------------- 16384:128:1:44:M1460: Novell:NetWare:5.0:Novel Netware 5.0 6144:128:1:44:M1460: Novell:IntranetWare:4.11:Novell IntranetWare 4.11 6144:128:1:44:M1368: Novell:BorderManager::Novell BorderManager ? 6144:128:1:52:M*,W0,N,S,N,N: Novell:Netware:6:Novell Netware 6 SP3 # ----------------- SCO ------------------ S3:64:1:60:M1460,N,W0,N,N,T: SCO:UnixWare:7.1:SCO UnixWare 7.1 S17:64:1:60:M1380,N,W0,N,N,T: SCO:UnixWare:7.1:SCO UnixWare 7.1.3 MP3 S23:64:1:44:M1380: SCO:OpenServer:5.0:SCO OpenServer 5.0 # ------------------- DOS ------------------- 2048:255:0:44:M536: DOS:WATTCP:1.05:DOS Arachne via WATTCP/1.05 T2:255:0:44:M984: DOS:WATTCP:1.05Arachne:Arachne via WATTCP/1.05 (eepro) # ------------------ OS/2 ------------------- S56:64:0:44:M512: OS/2:4::OS/2 4 28672:64:0:44:M1460: OS/2:4::OS/2 Warp 4.0 # ----------------- TOPS-20 ----------------- # Another hardcore MSS, one of the ACK leakers hunted down. # XXX QUIRK 0:64:0:44:M1460:A:TOPS-20:version 7 0:64:0:44:M1460: TOPS-20:7::TOPS-20 version 7 # ----------------- FreeMiNT ---------------- S44:255:0:44:M536: FreeMiNT:1:16A:FreeMiNT 1 patch 16A (Atari) # ------------------ AMIGA ------------------ # XXX TCP option 12 # S32:64:1:56:M*,N,N,S,N,N,?12:.:AMIGA:3.9 BB2 with Miami stack # ------------------ Plan9 ------------------ 65535:255:0:48:M1460,W0,N: Plan9:4::Plan9 edition 4 # ----------------- AMIGAOS ----------------- 16384:64:1:48:M1560,N,N,S: AMIGAOS:3.9::AMIGAOS 3.9 BB2 MiamiDX ########################################### # Appliance / embedded / other signatures # ########################################### # ---------- Firewalls / routers ------------ S12:64:1:44:M1460: @Checkpoint:::Checkpoint (unknown 1) S12:64:1:48:N,N,S,M1460: @Checkpoint:::Checkpoint (unknown 2) 4096:32:0:44:M1460: ExtremeWare:4.x::ExtremeWare 4.x # XXX TCP option 12 # S32:64:0:68:M512,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO w/Checkpoint NG FP3 # S16:64:0:68:M1024,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO 3.7 build 026 S4:64:1:60:W0,N,S,T,M1460: FortiNet:FortiGate:50:FortiNet FortiGate 50 8192:64:1:44:M1460: Eagle:::Eagle Secure Gateway S52:128:1:48:M1260,N,N,N,N: LinkSys:WRV54G::LinkSys WRV54G VPN router # ------- Switches and other stuff ---------- 4128:255:0:44:M*: Cisco:::Cisco Catalyst 3500, 7500 etc S8:255:0:44:M*: Cisco:12008::Cisco 12008 60352:128:1:64:M1460,N,W2,N,N,T,N,N,S: Alteon:ACEswitch::Alteon ACEswitch 64512:128:1:44:M1370: Nortel:Contivity Client::Nortel Conectivity Client # ---------- Caches and whatnots ------------ S4:64:1:52:M1460,N,N,S,N,W0: AOL:web cache::AOL web cache 32850:64:1:64:N,W1,N,N,T,N,N,S,M*: NetApp:5.x::NetApp Data OnTap 5.x 16384:64:1:64:M1460,N,N,S,N,W0,N: NetApp:5.3:1:NetApp 5.3.1 65535:64:0:64:M1460,N,N,S,N,W*,N,N,T: NetApp:5.3-5.5::NetApp 5.3-5.5 65535:64:0:60:M1460,N,W0,N,N,T: NetApp:CacheFlow::NetApp CacheFlow 8192:64:1:64:M1460,N,N,S,N,W0,N,N,T: NetApp:5.2:1:NetApp NetCache 5.2.1 20480:64:1:64:M1460,N,N,S,N,W0,N,N,T: NetApp:4.1::NetApp NetCache4.1 65535:64:0:60:M1460,N,W0,N,N,T: CacheFlow:4.1::CacheFlow CacheOS 4.1 8192:64:0:60:M1380,N,N,N,N,N,N,T: CacheFlow:1.1::CacheFlow CacheOS 1.1 S4:64:0:48:M1460,N,N,S: Cisco:Content Engine::Cisco Content Engine 27085:128:0:40:.: Dell:PowerApp cache::Dell PowerApp (Linux-based) 65535:255:1:48:N,W1,M1460: Inktomi:crawler::Inktomi crawler S1:255:1:60:M1460,S,T,N,W0: LookSmart:ZyBorg::LookSmart ZyBorg 16384:255:0:40:.: Proxyblocker:::Proxyblocker (what's this?) 65535:255:0:48:M*,N,N,S: Redline:::Redline T|X 2200 32696:128:0:40:M1460: Spirent:Avalanche::Spirent Web Avalanche HTTP benchmarking engine # ----------- Embedded systems -------------- S9:255:0:44:M536: PalmOS:Tungsten:C:PalmOS Tungsten C S5:255:0:44:M536: PalmOS:3::PalmOS 3/4 S5:255:0:44:M536: PalmOS:4::PalmOS 3/4 S4:255:0:44:M536: PalmOS:3:5:PalmOS 3.5 2948:255:0:44:M536: PalmOS:3:5:PalmOS 3.5.3 (Handera) S29:255:0:44:M536: PalmOS:5::PalmOS 5.0 16384:255:0:44:M1398: PalmOS:5.2:Clie:PalmOS 5.2 (Clie) S14:255:0:44:M1350: PalmOS:5.2:Treo:PalmOS 5.2.1 (Treo) S23:64:1:64:N,W1,N,N,T,N,N,S,M1460: SymbianOS:7::SymbianOS 7 8192:255:0:44:M1460: SymbianOS:6048::Symbian OS 6048 (Nokia 7650?) 8192:255:0:44:M536: SymbianOS:9210::Symbian OS (Nokia 9210?) S22:64:1:56:M1460,T,S: SymbianOS:P800::Symbian OS ? (SE P800?) S36:64:1:56:M1360,T,S: SymbianOS:6600::Symbian OS 60xx (Nokia 6600?) # Perhaps S4? 5840:64:1:60:M1452,S,T,N,W1: Zaurus:3.10::Zaurus 3.10 32768:128:1:64:M1460,N,W0,N,N,T0,N,N,S: PocketPC:2002::PocketPC 2002 S1:255:0:44:M346: Contiki:1.1:rc0:Contiki 1.1-rc0 4096:128:0:44:M1460: Sega:Dreamcast:3.0:Sega Dreamcast Dreamkey 3.0 T5:64:0:44:M536: Sega:Dreamcast:HKT-3020:Sega Dreamcast HKT-3020 (browser disc 51027) S22:64:1:44:M1460: Sony:PS2::Sony Playstation 2 (SOCOM?) S12:64:0:44:M1452: AXIS:5600:v5.64:AXIS Printer Server 5600 v5.64 3100:32:1:44:M1460: Windows:CE:2.0:Windows CE 2.0 #################### # Fancy signatures # #################### 1024:64:0:40:.: *NMAP:syn scan:1:NMAP syn scan (1) 2048:64:0:40:.: *NMAP:syn scan:2:NMAP syn scan (2) 3072:64:0:40:.: *NMAP:syn scan:3:NMAP syn scan (3) 4096:64:0:40:.: *NMAP:syn scan:4:NMAP syn scan (4) # Requires quirks support # 1024:64:0:40:.:A:*NMAP:TCP sweep probe (1) # 2048:64:0:40:.:A:*NMAP:TCP sweep probe (2) # 3072:64:0:40:.:A:*NMAP:TCP sweep probe (3) # 4096:64:0:40:.:A:*NMAP:TCP sweep probe (4) 1024:64:0:60:W10,N,M265,T: *NMAP:OS:1:NMAP OS detection probe (1) 2048:64:0:60:W10,N,M265,T: *NMAP:OS:2:NMAP OS detection probe (2) 3072:64:0:60:W10,N,M265,T: *NMAP:OS:3:NMAP OS detection probe (3) 4096:64:0:60:W10,N,M265,T: *NMAP:OS:4:NMAP OS detection probe (4) 32767:64:0:40:.: *NAST:::NASTsyn scan # Requires quirks support # 12345:255:0:40:.:A:-p0f:sendsyn utility ##################################### # Generic signatures - just in case # ##################################### #*:64:1:60:M*,N,W*,N,N,T: @FreeBSD:4.0-4.9::FreeBSD 4.x/5.x #*:64:1:60:M*,N,W*,N,N,T: @FreeBSD:5.0-5.1::FreeBSD 4.x/5.x *:128:1:52:M*,N,W0,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp) *:128:1:52:M*,N,W0,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp) *:128:1:52:M*,N,W*,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp) *:128:1:52:M*,N,W*,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp) *:128:1:64:M*,N,W0,N,N,T0,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323) *:128:1:64:M*,N,W0,N,N,T0,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323) *:128:1:64:M*,N,W*,N,N,T0,N,N,S: @Windows:XP:RFC1323:Windows XP (RFC1323, w+) *:128:1:48:M536,N,N,S: @Windows:98::Windows 98 *:128:1:48:M*,N,N,S: @Windows:XP::Windows XP/2000 *:128:1:48:M*,N,N,S: @Windows:2000::Windows XP/2000 psad-2.2.1/bump_version.pl0000775000175000017500000000256012071203757013646 0ustar mbrmbr#!/usr/bin/perl -w # ############################################################################# # # File: bump_version.pl # # Purpose: Minor script to enforce consistency in psad version tags. # ############################################################################# # use strict; my @files = qw( psad nf2csv ); my $new_version = $ARGV[0] or die "[*] $0 "; open F, '< VERSION' or die "[*] Could not open VERSION file: $!"; my $old_version = ; close F; chomp $old_version; print "[+] Updating software versions...\n"; for my $file (@files) { if ($file =~ /\.c/) { ###* Version: 1.8.4-pre2 my $search_re = qr/^\*\s+Version:\s+$old_version/; my $replace_str = '* Version: ' . $new_version; system qq{perl -p -i -e 's|$search_re|} . qq{$replace_str|' $file}; } else { ### Version: 1.8.4 my $search_re = qr/#\s+Version:\s+$old_version/; my $replace_str = '# Version: ' . $new_version; system qq{perl -p -i -e 's|$search_re|$replace_str|' $file}; ### my $version = '1.8.4'; $search_re = qr/^my\s+\x24version\s+=\s+'$old_version';/; $replace_str = q|my \x24version = '| . $new_version . q|';|; system qq{perl -p -i -e "s|$search_re|$replace_str|" $file}; } } system qq{perl -p -i -e 's|$old_version|$new_version|' VERSION}; exit 0; psad-2.2.1/install.pl0000775000175000017500000024006612071203757012611 0ustar mbrmbr#!/usr/bin/perl -w # ######################################################################### # # File: install.pl # # Purpose: install.pl is the installation script for psad. It is safe # to execute install.pl even if psad has already been installed # on a system since install.pl will preserve the existing # config section. # # Credits: (see the CREDITS file) # # Copyright (C) 1999-2012 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # TODO: # ######################################################################### # use Cwd; use File::Path; use File::Copy; use Sys::Hostname; use IO::Socket; use Getopt::Long; use strict; #============== config =============== my $USRSBIN_DIR = '/usr/sbin'; ### consistent with FHS (Filesystem ### Hierarchy Standard) my $USRBIN_DIR = '/usr/bin'; ### consistent with FHS my $psad_conf_file = 'psad.conf'; ### system binaries ### my $chkconfigCmd = '/sbin/chkconfig'; my $rcupdateCmd = '/sbin/rc-update'; ### Gentoo my $updatercdCmd = '/usr/sbin/update-rc.d'; ### Ubuntu my $makeCmd = '/usr/bin/make'; my $perlCmd = '/usr/bin/perl'; my $wgetCmd = '/usr/bin/wget'; my $runlevelCmd = '/sbin/runlevel'; my $install_root = '/'; my $answers_file = 'install.answers'; #============ end config ============ my %file_vars = ( 'signatures' => 'SIGS_FILE', 'auto_dl' => 'AUTO_DL_FILE', 'icmp_types' => 'ICMP_TYPES_FILE', 'icmp6_types' => 'ICMP6_TYPES_FILE', 'posf' => 'POSF_FILE', 'pf.os' => 'P0F_FILE', 'snort_rule_dl' => 'SNORT_RULE_DL_FILE', 'ip_options' => 'IP_OPTS_FILE', 'protocols' => 'PROTOCOLS_FILE' ); my %exclude_cmds = ( 'wget' => '', 'mail' => '', 'sendmail' => '', 'uname' => '', 'df' => '', 'psadwatchd' => '', 'kmsgsd' => '', 'psad' => '', 'whois' => '', 'fwcheck_psad' => '' ); ### map perl modules to versions my %required_perl_modules = ( 'Unix::Syslog' => { 'force-install' => 0, 'mod-dir' => 'Unix-Syslog' }, 'Bit::Vector' => { 'force-install' => 0, 'mod-dir' => 'Bit-Vector' }, 'Storable', => { 'force-install' => 0, 'mod-dir' => 'Storable' }, 'Date::Calc', => { 'force-install' => 0, 'mod-dir' => 'Date-Calc' }, 'NetAddr::IP' => { 'force-install' => 0, 'mod-dir' => 'NetAddr-IP' }, 'IPTables::Parse' => { 'force-install' => 1, 'mod-dir' => 'IPTables-Parse' }, 'IPTables::ChainMgr' => { 'force-install' => 1, 'mod-dir' => 'IPTables-ChainMgr' }, ); my @ordered_modules = (qw/ Unix::Syslog Bit::Vector Storable Date::Calc NetAddr::IP IPTables::Parse IPTables::ChainMgr /); my %config = (); my %cmds = (); my @cmd_search_paths = qw( /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin ); ### IP regex my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|; ### get the hostname of the system my $HOSTNAME = hostname(); my $src_dir = getcwd() or die "[*] Could not get current working directory."; ### for user answers my $ACCEPT_YES_DEFAULT = 1; my $ACCEPT_NO_DEFAULT = 2; my $NO_ANS_DEFAULT = 0; ### set the default execution flags and command line args my $noarchive = 0; my $uninstall = 0; my $help = 0; my $archived_old = 0; my $skip_syslog_test = 0; my $use_answers = 0; my $no_write_answers = 0; my %answers = (); my $skip_module_install = 0; my $cmdline_force_install = 0; my $force_path_update = 0; my $force_mod_re = ''; my $exclude_mod_re = ''; my $install_test_dir = 0; my $no_rm_old_lib_dir = 0; my $syslog_conf = ''; my $locale = 'C'; ### default LC_ALL env variable my $no_locale = 0; my $deps_dir = 'deps'; my $init_dir = '/etc/init.d'; my $init_name = 'psad'; my $install_syslog_fifo = 0; my $runlevel = -1; my @installation_lines = (); ### make Getopts case sensitive Getopt::Long::Configure('no_ignore_case'); &usage(1) unless (GetOptions( 'config=s' => \$psad_conf_file, ### specify path to psad.conf 'force-mod-install' => \$cmdline_force_install, ### force install of all modules 'Force-mod-regex=s' => \$force_mod_re, ### force specific mod install with regex 'Exclude-mod-regex=s' => \$exclude_mod_re, ### exclude a particular perl module 'path-update' => \$force_path_update, ### update command paths 'Skip-mod-install' => \$skip_module_install, 'Use-answers' => \$use_answers, 'answers-file=s' => \$answers_file, 'no-write-answers' => \$no_write_answers, 'no-rm-lib-dir' => \$no_rm_old_lib_dir, ### remove any old /usr/lib/psad dir 'no-preserve' => \$noarchive, ### Don't preserve existing configs. 'syslog-conf=s' => \$syslog_conf, ### specify path to syslog config file. 'no-syslog-test' => \$skip_syslog_test, 'uninstall' => \$uninstall, ### Uninstall psad. 'init-dir=s' => \$init_dir, 'init-name=s' => \$init_name, 'install-syslog-fifo' => \$install_syslog_fifo, 'install-root=s' => \$install_root, 'install-test-dir' => \$install_test_dir, 'runlevel=i' => \$runlevel, 'LC_ALL=s' => \$locale, 'no-LC_ALL' => \$no_locale, 'help' => \$help ### Display help. )); &usage(0) if $help; ### set LC_ALL env variable $ENV{'LC_ALL'} = $locale unless $no_locale; ### make a copy of the original psad.conf file and restore at the end copy $psad_conf_file, "${psad_conf_file}.orig" or die "[*] Could not ", "copy $psad_conf_file -> $psad_conf_file.orig"; if ($install_test_dir) { $install_root = getcwd() . '/test/psad-install'; $init_dir = $install_root . '/etc/init.d'; } &import_answers() if $use_answers; ### import paths from default psad.conf &import_config(); $force_mod_re = qr|$force_mod_re| if $force_mod_re; $exclude_mod_re = qr|$exclude_mod_re| if $exclude_mod_re; ### see if the deps/ directory exists, and if not then we are installing ### from the -nodeps sources so don't install any perl modules $skip_module_install = 1 unless -d $deps_dir; $cmds{'make'} = $makeCmd; $cmds{'perl'} = $perlCmd; $cmds{'runlevel'} = $runlevelCmd; my $distro = &get_distro(); if ($distro eq 'redhat' or $distro eq 'fedora') { ### add chkconfig only if we are runing on a redhat distro $cmds{'chkconfig'} = $chkconfigCmd; } elsif ($distro eq 'gentoo') { ### add rc-update if we are running on a gentoo distro $cmds{'rc-update'} = $rcupdateCmd; } elsif ($distro eq 'ubuntu') { ### add update-rc.d if we are running on an ubuntu distro $cmds{'update-rc.d'} = $updatercdCmd; } unless (-d $init_dir) { if (-d '/etc/rc.d/init.d') { $init_dir = '/etc/rc.d/init.d'; } elsif (-d '/etc/rc.d') { ### for Slackware $init_dir = '/etc/rc.d'; } else { die "[*] Cannot find the init script directory, use ", "--init-dir " unless $install_test_dir; } } ### need to make sure this exists before attempting to ### write anything to the install log. &full_mkdir($config{'PSAD_DIR'}, 0700); ### make sure the system binaries are where we expect ### them to be. &check_commands(); ### occasionally things from old psad installations need to be ### dealt with separately. &check_old_psad_installation(); if ($uninstall) { &uninstall(); } else { &install(); } open F, "> $config{'INSTALL_LOG_FILE'}" or die $!; print F for @installation_lines; close F; ### restore the original psad.conf file (this is just the local one in the ### sources directory). if (-e "${psad_conf_file}.orig") { unlink $psad_conf_file if -e $psad_conf_file; move "${psad_conf_file}.orig", $psad_conf_file; } exit 0; #================= end main ================= sub install() { ### make sure install.pl is being called from the source directory unless (-e 'psad') { die "[*] install.pl can only be executed from the directory\n", " that contains the psad sources! Exiting."; } &logr('[+] ' . localtime() . " Installing psad on hostname: $HOSTNAME\n"); ### make sure another psad process is not running if (&ask_to_stop_psad()) { &stop_psad(); } unless (-d $config{'PSAD_RUN_DIR'}) { &logr("[+] Creating $config{'PSAD_RUN_DIR'}\n"); &full_mkdir($config{'PSAD_RUN_DIR'}, 0700); } unless (-d $config{'PSAD_FIFO_DIR'}) { &logr("[+] Creating $config{'PSAD_FIFO_DIR'}\n"); &full_mkdir($config{'PSAD_FIFO_DIR'}, 0700); } ### change any existing psad module directory to allow anyone to import unless ($skip_module_install) { my $dir_tmp = $config{'PSAD_LIBS_DIR'}; $dir_tmp =~ s|lib/|lib64/|; for my $dir ($config{'PSAD_LIBS_DIR'}, $dir_tmp) { if (-d $dir) { chmod 0755, $dir; unless ($no_rm_old_lib_dir) { &logr("[+] Removing $dir/ directory from previous " . "psad installation.\n"); rmtree $dir; } } } } unless (-d $config{'PSAD_CONF_DIR'}) { &logr("[+] Creating $config{'PSAD_CONF_DIR'}\n"); &full_mkdir($config{'PSAD_CONF_DIR'}, 0700); } unless (-d $config{'CONF_ARCHIVE_DIR'}) { &logr("[+] Creating $config{'CONF_ARCHIVE_DIR'}\n"); &full_mkdir($config{'CONF_ARCHIVE_DIR'}, 0700); } if ($install_syslog_fifo) { unless (-e $config{'PSAD_FIFO_FILE'}) { &logr("[+] Creating named pipe $config{'PSAD_FIFO_FILE'}\n"); unless (((system "$cmds{'mknod'} -m 600 " . "$config{'PSAD_FIFO_FILE'} p")>>8) == 0) { &logr("[*] Could not create the named pipe " . "\"$config{'PSAD_FIFO_FILE'}\"!\n" . "[*] psad requires this file to exist! Aborting install.\n"); die; } unless (-p $config{'PSAD_FIFO_FILE'}) { &logr("[*] Could not create the named pipe " . "\"$config{'PSAD_FIFO_FILE'}\"!\n" . "[*] psad requires this file to exist! Aborting " . "install.\n"); die; } } } unless (-d $config{'PSAD_DIR'}) { &logr("[+] Creating $config{'PSAD_DIR'}\n"); &full_mkdir($config{'PSAD_DIR'}, 0700); } unless (-d $config{'PSAD_LIBS_DIR'}) { &logr("[+] Creating $config{'PSAD_LIBS_DIR'}\n"); &full_mkdir($config{'PSAD_LIBS_DIR'}, 0755); } unless (-e $config{'FW_DATA_FILE'}) { &logr("[+] Creating $config{'FW_DATA_FILE'} file\n"); open F, "> $config{'FW_DATA_FILE'}" or die "[*] Could not open ", "$config{'FW_DATA_FILE'}: $!"; close F; chmod 0600, "$config{'FW_DATA_FILE'}"; &perms_ownership("$config{'FW_DATA_FILE'}", 0600); } unless (-d $USRSBIN_DIR) { &logr("[+] Creating $USRSBIN_DIR\n"); &full_mkdir($USRSBIN_DIR, 0755); } if (-d 'deps' and -d 'deps/whois') { &logr("[+] Compiling Marco d'Itri's whois client\n"); system "$cmds{'make'} -C deps/whois"; if (-e 'deps/whois/whois') { ### if an old whois process is still around ("text file ### busy" error), then it is ok to not be able to copy ### the new whois binary into place; the old one should ### work fine. &logr("[+] Copying whois binary to $cmds{'whois'}\n"); copy 'deps/whois/whois', $cmds{'whois'}; die "[*] Could not copy deps/whois/whois -> $cmds{'whois'}: $!" unless -e $cmds{'whois'}; } else { die "[*] Could not compile whois"; } } &perms_ownership($cmds{'whois'}, 0755); print "\n\n"; ### install perl modules unless ($skip_module_install) { for my $module (@ordered_modules) { &install_perl_module($module); } } if (-d 'deps' and -d 'deps/snort_rules') { &logr("[+] Installing Snort-2.3.3 signatures in " . "$config{'SNORT_RULES_DIR'}\n"); unless (-d $config{'SNORT_RULES_DIR'}) { &full_mkdir($config{'SNORT_RULES_DIR'}, 0700); } opendir D, 'deps/snort_rules' or die "[*] Could not open ", "the deps/snort_rules directory: $!"; my @files = readdir D; closedir D; for my $file (@files) { next unless $file =~ /\.rules$/ or $file =~ /\.config$/; &logr("[+] Installing deps/snort_rules/${file}\n"); copy "deps/snort_rules/${file}", "$config{'SNORT_RULES_DIR'}/${file}" or die "[*] Could not copy deps/snort_rules/${file} -> ", "$config{'SNORT_RULES_DIR'}/${file}: $!"; &perms_ownership("$config{'SNORT_RULES_DIR'}/${file}", 0600); } } print "\n\n"; &logr("[+] Compiling kmsgsd, and psadwatchd:\n"); ### remove any previously compiled kmsgsd unlink 'kmsgsd' if -e 'kmsgsd'; ### remove any previously compiled psadwatchd unlink 'psadwatchd' if -e 'psadwatchd'; ### compile the C psad daemons system $cmds{'make'}; &logr("[-] Could not compile kmsgsd.c.\n") unless (-e 'kmsgsd'); &logr("[-] Could not compile psadwatchd.c.\n") unless (-e 'psadwatchd'); ### install fwcheck_psad.pl print "\n\n"; &logr("[+] Verifying compilation of fwcheck_psad.pl script:\n"); unless (((system "$cmds{'perl'} -c fwcheck_psad.pl")>>8) == 0) { die "[*] fwcheck_psad.pl does not compile with \"perl -c\". Download ", "the latest sources from:\n\nhttp://www.cipherdyne.org/" unless $skip_module_install; } ### make sure the psad (perl) daemon compiles. The other three ### daemons have all been re-written in C. &logr("[+] Verifying compilation of psad perl daemon:\n"); unless (((system "$cmds{'perl'} -c psad")>>8) == 0) { die "[*] psad does not compile with \"perl -c\". Download the", " latest sources from:\n\nhttp://www.cipherdyne.org/" unless $skip_module_install; } ### install nf2csv &logr("[+] Verifying compilation of nf2csv script:\n"); unless (((system "$cmds{'perl'} -c nf2csv")>>8) == 0) { die "[*] nf2csv does not compile with \"perl -c\". Download ", "the latest sources from:\n\nhttp://www.cipherdyne.org/" unless $skip_module_install; } ### put the nf2csv script in place unlink '/usr/sbin/nf2csv' if -e '/usr/sbin/nf2csv'; ### old path &logr("[+] Copying nf2csv -> ${USRBIN_DIR}/nf2csv\n"); unlink "${USRBIN_DIR}/nf2csv" if -e "${USRBIN_DIR}/nf2csv"; copy 'nf2csv', "${USRBIN_DIR}/nf2csv" or die "[*] Could ", "not copy nf2csv -> ${USRBIN_DIR}/nf2csv: $!"; &perms_ownership("${USRBIN_DIR}/nf2csv", 0755); ### put the fwcheck_psad.pl script in place &logr("[+] Copying fwcheck_psad.pl -> $cmds{'fwcheck_psad'}\n"); unlink $cmds{'fwcheck_psad'} if -e $cmds{'fwcheck_psad'}; copy 'fwcheck_psad.pl', $cmds{'fwcheck_psad'} or die "[*] Could ", "not copy fwcheck_psad.pl -> $cmds{'fwcheck_psad'}: $!"; &perms_ownership($cmds{'fwcheck_psad'}, 0500); ### put the psad daemons in place &logr("[+] Copying psad -> ${USRSBIN_DIR}/psad\n"); unlink "${USRSBIN_DIR}/psad" if -e "${USRSBIN_DIR}/psad"; copy 'psad', "${USRSBIN_DIR}/psad" or die "[*] Could not copy ", "psad -> ${USRSBIN_DIR}/psad: $!"; &perms_ownership("${USRSBIN_DIR}/psad", 0500); &logr("[+] Copying psadwatchd -> ${USRSBIN_DIR}/psadwatchd\n"); unlink "${USRSBIN_DIR}/psadwatchd" if -e "${USRSBIN_DIR}/psadwatchd"; copy 'psadwatchd', "${USRSBIN_DIR}/psadwatchd" or die "[*] Could not ", "copy psadwatchd -> ${USRSBIN_DIR}/psadwatchd: $!"; &perms_ownership("${USRSBIN_DIR}/psadwatchd", 0500); &logr("[+] Copying kmsgsd -> ${USRSBIN_DIR}/kmsgsd\n"); unlink "${USRSBIN_DIR}/kmsgsd" if -e "${USRSBIN_DIR}/kmsgsd"; copy 'kmsgsd', "${USRSBIN_DIR}/kmsgsd" or die "[*] Could not copy ", "kmsgsd -> ${USRSBIN_DIR}/kmsgsd: $!"; &perms_ownership("${USRSBIN_DIR}/kmsgsd", 0500); unless (-d $config{'PSAD_CONF_DIR'}) { &logr("[+] Creating $config{'PSAD_CONF_DIR'}\n"); &full_mkdir($config{'PSAD_CONF_DIR'}, 0700); } my $syslog_str = ''; if ($install_syslog_fifo) { ### get syslog daemon (e.g. syslog, rsyslog syslog-ng, or metalog) $syslog_str = &query_syslog(); } else { print "[+] psad by default parses iptables log messages from the /var/log/messages\n", " file, but you can alter this with the IPT_SYSLOG_FILE variable in the\n", " /etc/psad/psad.conf file.\n"; } my $preserve_rv = 0; if (-e "$config{'PSAD_CONF_DIR'}/psad.conf") { $preserve_rv = &query_preserve_config(); } ### the order of the config files is important (legacy FW_MSG_SEARCH ### vars in psad.conf). my $prod_file = "$config{'PSAD_CONF_DIR'}/psad.conf"; if (-e $prod_file) { &archive($prod_file) unless $noarchive; if ($preserve_rv) { &preserve_config('psad.conf', $prod_file); } else { &logr("[+] Copying psad.conf -> $prod_file\n"); copy 'psad.conf', $prod_file or die "[*] Could not ", "copy psad.conf -> $prod_file: $!"; } } else { &logr("[+] Copying psad.conf -> $prod_file\n"); copy 'psad.conf', $prod_file or die "[*] Could not copy ", "psad.conf -> $prod_file: $!"; } if ($force_path_update or not $preserve_rv) { &update_command_paths($prod_file); } &perms_ownership($prod_file, 0600); ### install auto_dl, signatures, icmp_types, posf, and pf.os files for my $filename (keys %file_vars) { my $file = $config{$file_vars{$filename}}; if (-e $file) { &archive($file) unless $noarchive; ### FIXME, need a real config preservation routine for these files. unless (&query_preserve_sigs_autodl($file)) { &logr("[+] Copying $filename -> $file\n"); copy $filename, $file or die "[*] Could not ", "copy $filename -> $file: $!"; &perms_ownership($file, 0600); } } else { &logr("[+] Copying $filename -> $file\n"); copy $filename, $file or die "[*] Could not ", "copy $filename -> $file: $!"; &perms_ownership($file, 0600); } } ### archive and remove legacy config files for my $filename (qw(kmsgsd.conf psadwatchd.conf alert.conf fw_search.conf)) { my $path = "$config{'PSAD_CONF_DIR'}/$filename"; if (-e $path) { &archive($path); unlink $path; } } &logr("\n"); unless ($preserve_rv) { ### we want to preserve the existing config ### get email address(es) my $email_str = &query_email(); if ($email_str) { &put_var('EMAIL_ADDRESSES', $email_str, "$config{'PSAD_CONF_DIR'}/psad.conf"); } ### Give the admin the opportunity to set the strings that are ### parsed out of iptables messages. This is useful since the ### admin may have configured the firewall to use a logging prefix ### of "Audit" or something else other than the default string ### "DROP". my $fw_search_ar = &get_fw_search_strings(); if ($fw_search_ar) { open F, "< $config{'PSAD_CONF_DIR'}/psad.conf" or die "[*] Could not open ", "$config{'PSAD_CONF_DIR'}/psad.conf: $!"; my @lines = ; close F; open T, "> $config{'PSAD_CONF_DIR'}/psad.conf.tmp" or die "[*] Could not open ", "$config{'PSAD_CONF_DIR'}/psad.conf.tmp: $!"; for my $line (@lines) { if ($line =~ /^\s*FW_MSG_SEARCH\s/) { for my $fw_str (@$fw_search_ar) { &logr(qq{[+] Setting FW_MSG_SEARCH to "$fw_str" } . "in $config{'PSAD_CONF_DIR'}/psad.conf\n"); printf T "%-28s%s;\n", 'FW_MSG_SEARCH', $fw_str; } } else { print T $line unless $line =~ /^\s*FW_MSG_SEARCH\s/; } } close T; move "$config{'PSAD_CONF_DIR'}/psad.conf.tmp", "$config{'PSAD_CONF_DIR'}/psad.conf" or die "[*] Could not move ", "$config{'PSAD_CONF_DIR'}/psad.conf.tmp -> ", "$config{'PSAD_CONF_DIR'}/psad.conf: $!"; } ### Give the admin the opportunity to set the HOME_NET variable. &set_home_net("$config{'PSAD_CONF_DIR'}/psad.conf"); ### see if the admin would like to have psad send info to ### DShield if (&query_dshield()) { &put_var('ENABLE_DSHIELD_ALERTS', 'Y', "$config{'PSAD_CONF_DIR'}/psad.conf"); } ### Set the hostname my $file = "$config{'PSAD_CONF_DIR'}/psad.conf"; &logr("[+] Setting hostname to \"$HOSTNAME\" in $file\n"); &set_hostname($file); } if ($install_syslog_fifo) { &put_var('SYSLOG_DAEMON', $syslog_str, "$config{'PSAD_CONF_DIR'}/psad.conf"); if ($syslog_str ne 'ulogd') { my $restarted_syslog = 0; if ($syslog_str eq 'syslogd') { if (-e $syslog_conf) { &append_fifo_syslog($syslog_conf); if (((system "$cmds{'killall'} -HUP syslogd 2> /dev/null")>>8) == 0) { &logr("[+] HUP signal sent to syslogd.\n"); $restarted_syslog = 1; } } } elsif ($syslog_str eq 'rsyslogd') { if (-e $syslog_conf) { &append_fifo_syslog($syslog_conf); if (((system "$cmds{'killall'} -HUP rsyslogd 2> /dev/null")>>8) == 0) { &logr("[+] HUP signal sent to rsyslogd.\n"); $restarted_syslog = 1; } } } elsif ($syslog_str eq 'syslog-ng') { if (-e $syslog_conf) { &append_fifo_syslog_ng($syslog_conf); if (((system "$cmds{'killall'} -HUP syslog-ng 2> /dev/null")>>8) == 0) { &logr("[+] HUP signal sent to syslog-ng.\n"); $restarted_syslog = 1; } } } elsif ($syslog_str eq 'metalog') { if (-e $syslog_conf) { &config_metalog($syslog_conf); &logr("[-] Metalog support is shaky in psad. " . "Use at your own risk.\n"); ### don't send warning about not restarting metalog daemon $restarted_syslog = 1; } } unless ($restarted_syslog) { &logr("[-] Could not restart any syslog daemons.\n"); } } if (-x $cmds{'iptables'} and not $skip_syslog_test) { &logr("[+] Found iptables. Testing syslog configuration:\n"); ### make sure we actually see packets being logged by ### the firewall. if ($syslog_str ne 'ulogd') { if (&test_syslog_config($syslog_str)) { &logr("[+] Successful $syslog_str reconfiguration.\n\n"); } else { if (&query_init_script_restart_syslog()) { my $restarted = 0; if ($syslog_str eq 'syslog-ng') { if (-e "$init_dir/syslog-ng") { system "$init_dir/syslog-ng restart"; $restarted = 1; } } elsif ($syslog_str eq 'rsyslogd') { if (-e "$init_dir/sysklogd") { system "$init_dir/sysklogd restart"; $restarted = 1; } elsif (-e "$init_dir/syslog") { system "$init_dir/syslog restart"; $restarted = 1; } } else { if (-e "$init_dir/rsyslog") { system "$init_dir/rsyslog restart"; $restarted = 1; } } ### test syslog config again now that we ### have restarted syslog via the init script ### instead of relying on a HUP signal to ### syslog if ($restarted) { if (&test_syslog_config($syslog_str)) { &logr("[+] Successful $syslog_str reconfiguration.\n\n"); } else { &logr("[-] Unsuccessful $syslog_str reconfiguration.\n"); &logr(" Consult the psad man page for the basic " . "$syslog_str requirement to get psad to work.\n\n"); } } } else { &logr("[-] Ok, hoping that psad can get packet data anyway.\n"); } } } } } ### download signatures? &download_signatures() if &query_signatures(); &install_manpage('psad.8'); &install_manpage('psadwatchd.8'); &install_manpage('kmsgsd.8'); &install_manpage('nf2csv.1'); my $init_file = ''; my $installed_init_script = 0; if ($distro eq 'redhat') { $init_file = 'init-scripts/psad-init.redhat'; } elsif ($distro eq 'fedora') { $init_file = 'init-scripts/psad-init.fedora'; } elsif ($distro eq 'gentoo') { $init_file = 'init-scripts/psad-init.gentoo'; } else { $init_file = 'init-scripts/psad-init.generic'; } if ($init_dir and &is_root()) { &logr("[+] Copying $init_file -> ${init_dir}/$init_name\n"); copy $init_file, "${init_dir}/$init_name" or die "[*] Could not copy ", "$init_file -> ${init_dir}/$init_name: $!"; &perms_ownership("${init_dir}/$init_name", 0744); &enable_psad_at_boot($distro); $installed_init_script = 1; } &logr("\n========================================================\n"); if ($archived_old) { &logr("[+] Copies of your original configs have been made " . "in: $config{'CONF_ARCHIVE_DIR'}\n"); } if ($preserve_rv) { &logr("\n[+] psad has been installed (with your original config merged).\n"); } else { &logr("\n[+] psad has been installed.\n"); } if ($installed_init_script) { if ($init_dir) { &logr("\n[+] To start psad, run \"${init_dir}/psad start\"\n"); } else { &logr("\n[+] To start psad, run ${USRSBIN_DIR}/psad\"\n"); } } return; } sub import_config() { open C, "< $psad_conf_file" or die "[*] Could not open $psad_conf_file: $!"; while () { next if /^\s*#/; if (/^\s*(\S+)\s+(.*?)\;/) { my $varname = $1; my $val = $2; if ($val =~ m|/.+| and $varname =~ /^\s*(\S+)Cmd$/) { ### found a command $cmds{$1} = $val; } else { $config{$varname} = $val; } } } close C; ### see if the install root is the same as the default in psad.conf and ### update if not if ($install_root ne '/') { $install_root = getcwd() . "/$install_root" unless $install_root =~ m|^/|; $config{'INSTALL_ROOT'} = $install_root; $USRSBIN_DIR = $config{'INSTALL_ROOT'} . $USRSBIN_DIR; $USRBIN_DIR = $config{'INSTALL_ROOT'} . $USRBIN_DIR; &put_var('INSTALL_ROOT', $install_root, $psad_conf_file); } for my $dir ($install_root, $USRSBIN_DIR, $USRBIN_DIR) { &full_mkdir($dir, 0755) unless -d $dir; } if ($install_test_dir) { &full_mkdir($init_dir, 0755) unless -d $init_dir; } ### resolve internal vars within variable values &expand_vars(); &required_vars(); 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 @vars = (qw( INSTALL_LOG_FILE PSAD_DIR PSAD_RUN_DIR PSAD_LIBS_DIR SIG_UPDATE_URL PSAD_FIFO_DIR PSAD_FIFO_FILE SNORT_RULES_DIR IP_OPTS_FILE SIGS_FILE AUTO_DL_FILE SNORT_RULE_DL_FILE POSF_FILE P0F_FILE IP_OPTS_FILE FW_DATA_FILE )); for my $var (@vars) { die "[*] Missing required var: $var in $psad_conf_file" unless defined $config{$var}; } return; } sub uninstall() { &logr("\n[+] Uninstalling psad from $HOSTNAME: " . localtime() . "\n"); unless (&query_yes_no('[+] This will completely remove psad ' . "from your system.\n Are you sure (y/n)? ", $NO_ANS_DEFAULT)) { &logr("[*] User aborted uninstall by answering \"n\" to the remove " . "question! Exiting.\n"); exit 0; } ### after this point, psad will really be uninstalled so stop writing stuff ### to the install.log file. Just print everything to STDOUT if (-e $config{'PSAD_PID_FILE'}) { open PID, "$config{'PSAD_PID_FILE'}" or die "[*] Could not open ", "$config{'PSAD_PID_FILE'}: $!"; my $pid = ; close PID; chomp $pid; if (kill 0, $pid) { print "[+] Stopping psad daemons!\n"; if (-e "${init_dir}/psad") { ### prefer this for old versions system "${init_dir}/psad stop"; } else { system "${USRSBIN_DIR}/psad --Kill"; } } } if (-e $cmds{'fwcheck_psad'}) { print "[+] Removing $cmds{'fwcheck_psad'}\n"; unlink $cmds{'fwcheck_psad'}; } if (-e "${USRBIN_DIR}/nf2csv") { print "[+] Removing ${USRBIN_DIR}/nf2csv\n"; unlink "${USRBIN_DIR}/nf2csv"; } if (-e "${USRSBIN_DIR}/psad") { print "[+] Removing psad daemons: ${USRSBIN_DIR}/", "(psad, psadwatchd, kmsgsd)\n"; unlink "${USRSBIN_DIR}/psad" or warn "[*] Could not remove ${USRSBIN_DIR}/psad!!!\n"; unlink "${USRSBIN_DIR}/psadwatchd" or warn "[*] Could not remove ${USRSBIN_DIR}/psadwatchd!!!\n"; unlink "${USRSBIN_DIR}/kmsgsd" or warn "[*] Could not remove ${USRSBIN_DIR}/kmsgsd!!!\n"; } if (-e "${init_dir}/psad") { print "[+] Removing ${init_dir}/psad\n"; unlink "${init_dir}/psad"; } if (-d $config{'PSAD_CONF_DIR'}) { print "[+] Removing configuration directory: $config{'PSAD_CONF_DIR'}"; rmtree($config{'PSAD_CONF_DIR'}, 1, 0); } if (-d $config{'PSAD_DIR'}) { print "[+] Removing logging directory: $config{'PSAD_DIR'}\n"; rmtree($config{'PSAD_DIR'}, 1, 0); } if (-e $config{'PSAD_FIFO_FILE'}) { print "[+] Removing named pipe: $config{'PSAD_FIFO_FILE'}\n"; unlink $config{'PSAD_FIFO_FILE'}; } if (-e $cmds{'whois'}) { print "[+] Removing $cmds{'whois'}\n"; unlink $cmds{'whois'}; } if (-d $config{'PSAD_FIFO_DIR'}) { print "[+] Removing $config{'PSAD_FIFO_DIR'}\n"; rmtree $config{'PSAD_FIFO_DIR'}; } if (-d $config{'PSAD_RUN_DIR'}) { print "[+] Removing $config{'PSAD_RUN_DIR'}\n"; rmtree $config{'PSAD_RUN_DIR'}; } my $dir_tmp = $config{'PSAD_LIBS_DIR'}; $dir_tmp =~ s|lib/|lib64/|; for my $dir ($config{'PSAD_LIBS_DIR'}, $dir_tmp) { if (-d $dir) { print "[+] Removing $dir\n"; rmtree $dir; } } print "[+] You will need to remove psad-specific config lines ", "from your syslog config.\n", " Your original syslog config at psad install time is preserved here:\n", " $config{'CONF_ARCHIVE_DIR'}\n"; print "\n[+] psad has been uninstalled!\n"; return; } sub ask_to_stop_psad() { if (-e $config{'PSAD_PID_FILE'}) { open P, "< $config{'PSAD_PID_FILE'}" or die "[*] Could not open ", "$config{'PSAD_PID_FILE'}: $!"; my $pid =

; close P; chomp $pid; if (kill 0, $pid) { print "[+] An existing psad daemon is running.\n"; if (&query_yes_no(" Can I stop the existing psad " . "daemon ([y]/n)? ", $ACCEPT_YES_DEFAULT)) { return 1; } else { die "[*] Aborting install."; } } } return 0; } sub stop_psad() { if (-e $config{'PSAD_PID_FILE'}) { open P, "< $config{'PSAD_PID_FILE'}" or die "[*] Could not open ", "$config{'PSAD_PID_FILE'}: $!"; my $pid =

; close P; chomp $pid; if (kill 0, $pid) { print "[+] Stopping running psad daemons.\n"; if (-x "$init_dir/psad") { system "$init_dir/psad stop"; ### if psad is still running then use -K if (kill 0, $pid) { system "$USRSBIN_DIR/psad -K"; } } else { ### psad may have been started from the command line ### without using the init script, so stop with -K system "$USRSBIN_DIR/psad -K"; } } } return; } sub install_perl_module() { my $mod_name = shift; chdir $src_dir or die "[*] Could not chdir $src_dir: $!"; chdir $deps_dir or die "[*] Could not chdir($deps_dir): $!"; die '[*] Missing force-install key in required_perl_modules hash.' unless defined $required_perl_modules{$mod_name}{'force-install'}; die '[*] Missing mod-dir key in required_perl_modules hash.' unless defined $required_perl_modules{$mod_name}{'mod-dir'}; if ($exclude_mod_re and $exclude_mod_re =~ /$mod_name/) { print "[+] Excluding installation of $mod_name module.\n"; return; } my $version = '(NA)'; my $mod_dir = $required_perl_modules{$mod_name}{'mod-dir'}; if (-e "$mod_dir/VERSION") { open F, "< $mod_dir/VERSION" or die "[*] Could not open $mod_dir/VERSION: $!"; $version = ; close F; chomp $version; } else { print "[-] Warning: VERSION file does not exist in $mod_dir\n"; } my $install_module = 0; if ($required_perl_modules{$mod_name}{'force-install'} or $cmdline_force_install) { ### install regardless of whether the module may already be ### installed $install_module = 1; } elsif ($force_mod_re and $force_mod_re =~ /$mod_name/) { print "[+] Forcing installation of $mod_name module.\n"; $install_module = 1; } else { if (has_perl_module($mod_name)) { print "[+] Module $mod_name is already installed in the ", "system perl tree, skipping.\n"; } else { ### install the module in the /usr/lib/psad directory because ### it is not already installed. $install_module = 1; } } if ($install_module) { &logr("[+] Installing the $mod_name $version perl " . "module in $config{'PSAD_LIBS_DIR'}/\n"); my $mod_dir = $required_perl_modules{$mod_name}{'mod-dir'}; chdir $mod_dir or die "[*] Could not chdir to ", "$mod_dir: $!"; unless (-e 'Makefile.PL') { die "[*] Your $mod_name source directory appears to be incomplete!\n", " Download the latest sources from ", "http://www.cipherdyne.org/"; } $ENV{'PERL5LIB'} = $config{'PSAD_LIBS_DIR'}; ### for module dependencies system "$cmds{'make'} clean" if -e 'Makefile' or -e 'makefile' or -e 'GNUmakefile'; system "$cmds{'perl'} Makefile.PL PREFIX=$config{'PSAD_LIBS_DIR'} " . "LIB=$config{'PSAD_LIBS_DIR'}"; system "$cmds{'make'}"; # system "$cmds{'make'} test"; system "$cmds{'make'} install"; chdir $src_dir or die "[*] Could not chdir $src_dir: $!"; print "\n\n"; } return; } sub set_home_net() { my $file = shift; ### first see if the admin will accept the default 'any' value return if &query_use_home_net_default(); ### FIXME future # my $str = #"\n Ok, would you like psad to automatically get the local subnets by\n" . #" parsing ifconfig output? (This is probably best for most situations).\n"; # &logr($str); # if (&query_yes_no(' ([y]/n)? ', $ACCEPT_YES_DEFAULT)) { # return; # } ### if we make it here, then the admin wants to completely enumerate the ### HOME_NET var, so we have to disable ENABLE_INTF_LOCAL_NETS # &put_var('ENABLE_INTF_LOCAL_NETS', 'N', # "$config{'PSAD_CONF_DIR'}/psad.conf"); ### get all interfaces; even those that are down since they may ### brought up any time. my @ifconfig_out = `$cmds{'ifconfig'} -a`; my $home_net_str = ''; my $intf_name = ''; my $net_ctr = 0; my %connected_subnets; for my $line (@ifconfig_out) { if ($line =~ /^\s*lo\s+Link/) { $intf_name = ''; next; } if ($line =~ /^\s*dummy.*\s+Link/) { $intf_name = ''; next; } if ($line =~ /^(\S+)\s+Link/) { $intf_name = $1; next; } if ($intf_name and $line =~ /^\s+inet\s+.*?:($ip_re).*:($ip_re)/) { my $ip = $1; my $mask = $2; my @ipbytes = split /\./, $ip; my @mbytes = split /\./, $mask; my $netaddr = ''; for (my $i=0; $i < 4; $i++) { my $byte1 = $mbytes[$i]+0; my $byte2 = $ipbytes[$i]+0; my $netaddr_byte = $byte1 & $byte2; if ($i != 0) { $netaddr = $netaddr . '.' . $netaddr_byte; } else { $netaddr = $netaddr_byte; } } $connected_subnets{$intf_name} = "$netaddr/$mask"; $net_ctr++; } } ### found two or more subnets, so forwarding traffic becomes ### possible through the box. &logr("\n It appears your machine is connected to " . "$net_ctr subnets:\n"); for my $intf (keys %connected_subnets) { &logr(" $intf -> $connected_subnets{$intf}\n"); } my $ans_file_str = 'Specify HOME_NET subnets:'; my $str = "\n Specify which subnets are part of your internal network. Note that\n" . " you can correct anything you enter here by editing the \"HOME_NET\"\n" . " variable in: $file.\n\n" . " Enter each of the subnets (except for the external subnet) on a line by\n" . " itself. Each of the subnets should be in the form /. E.g.\n" . " in CIDR notation: 192.168.10.0/24 (preferrable), or regular notation:\n" . " 192.168.10.0/255.255.255.0\n\n End with a \".\" on a line by itself.\n\n"; &logr($str); if ($use_answers and defined $answers{$ans_file_str}) { $home_net_str = $answers{$ans_file_str}; print "$answers{$ans_file_str}\n"; } else { my $ans = ''; while ($ans !~ /^\s*\.\s*$/) { &logr(" Subnet: "); $ans = ; chomp $ans; if ($ans =~ m|^\s*($ip_re/\d+)\s*$|) { ### hard to test this directly without ipv4_network() ### and this module may not be installed, so just use it. $home_net_str .= "$1, "; } elsif ($ans =~ m|^\s*($ip_re/$ip_re)\s*$|) { $home_net_str .= "$1, "; } elsif ($ans !~ /^\s*\.\s*$/) { &logr("[-] Invalid subnet \"$ans\"\n"); } } $home_net_str =~ s/\,\s*$//; &put_answer_file_value($ans_file_str, $home_net_str); } &put_var('HOME_NET', $home_net_str, $file); return; } sub query_signatures() { &logr("[+] The latest psad signatures can be installed with " . qq|"psad --sig-update"\n or installed now with install.pl.\n\n|); &logr(" If you decide to answer 'y' to the next question, install.pl\n" . " will require DNS and network access now.\n\n"); return &query_yes_no(" Would you like to install the latest " . "signatures from\n $config{'SIG_UPDATE_URL'} " . "(y/n)? ", $NO_ANS_DEFAULT); } sub download_signatures() { my $curr_pwd = cwd() or die $!; chdir '/tmp' or die $!; print "[+] Downloading latest signatures from:\n", " $config{'SIG_UPDATE_URL'}\n"; unlink 'signatures' if -e 'signatures'; ### download the file unless (-x $wgetCmd) { &logr("[-] The $wgetCmd var is not a valid path for wget, " . "skipping sig install.\n"); } system "$wgetCmd $config{'SIG_UPDATE_URL'}"; unless (-e 'signatures') { &logr("[-] Could not download signatures, continuing with install.\n"); } unlink "$config{'PSAD_CONF_DIR'}/signatures" if -e "$config{'PSAD_CONF_DIR'}/signatures"; move 'signatures', "$config{'PSAD_CONF_DIR'}/signatures"; chdir $curr_pwd or die $!; return; } sub query_use_home_net_default() { my $str = "\n[+] By default, psad matches Snort rules against any IP addresses, but\n" . " psad offers the ability to restrict signature matches to specific\n" . " networks with the HOME_NET variable (similar to Snort). However, psad\n" . " also offers the ability to acquire all local subnets on the local system\n" . " by parsing the output of \"ifconfig\", or the subnets can be restricted\n" . " to a limited set of networks.\n\n"; &logr($str); return &query_yes_no( " First, is it ok to leave the HOME_NET setting as \"any\" ([y]/n)? ", $ACCEPT_YES_DEFAULT); } sub set_hostname() { my $file = shift; if (-e $file) { open P, "< $file" or die "[*] Could not open $file: $!"; my @lines =

; close P; ### replace the "HOSTNAME _CHANGEME_" line open PH, "> $file" or die "[*] Could not open $file: $!"; for my $line (@lines) { chomp $line; if ($line =~ /^\s*HOSTNAME(\s+)_?CHANGE.?ME_?/) { print PH "HOSTNAME${1}$HOSTNAME;\n"; } else { print PH "$line\n"; } } close PH; &check_hostname($file); } else { die "[*] Your source directory appears to be incomplete! $file ", "is missing.\n Download the latest sources from ", "http://www.cipherdyne.org/"; } return; } ### see if there are any "_CHANGEME_" strings left and give the ### admin a chance to correct (this can happen if a new config ### variable is introduced in a new version of psad but the ### admin chose to preserve the old config). sub check_hostname() { my $file = shift; open F, "< $file" or die "[*] Could not open $file: $!"; my @lines = ; close F; for my $line (@lines) { next if $line =~ /^\s*#/; if ($line =~ /^\s*(\S+)\s+_?CHANGE.?ME_?\;/) { my $var = $1; ### only the HOSTNAME variable is set to _CHANGEME_ by ### default as of psad-1.6.0 if ($var eq 'HOSTNAME') { &logr("[-] set_hostname() failed. Edit the HOSTNAME " . " variable in $file\n"); } else { &logr("[-] Var $var is set to _CHANGEME_ in " . "$file, edit manually.\n"); } } } return; } sub append_fifo_syslog_ng() { my $syslog_conf = shift; open RS, "< $syslog_conf" or die "[*] Unable to open $syslog_conf: $!"; my @slines = ; close RS; my $found_fifo = 0; for my $line (@slines) { next if $line =~ m/^\s*#/; $found_fifo = 1 if ($line =~ /psadfifo/); } unless ($found_fifo) { &logr("[+] Modifying $syslog_conf to write kern.info " . "messages to\n $config{'PSAD_FIFO_FILE'}\n"); unless (-e "$syslog_conf.orig") { copy $syslog_conf, "$syslog_conf.orig" or die "[*] Could not ", "copy $syslog_conf -> $syslog_conf.orig: $!"; } &archive($syslog_conf); open SYSLOGNG, ">> $syslog_conf" or die "[*] Unable to open $syslog_conf: $!"; print SYSLOGNG "\n", qq|source psadsrc { unix-stream("/dev/log"); |, qq|internal(); pipe("/proc/kmsg"); };\n|, qq|filter f_psad { facility(kern) and match("IN=") |, qq|and match("OUT="); };\n|, 'destination psadpipe { ', "pipe(\"$config{'PSAD_FIFO_FILE'}\"); };\n", 'log { source(psadsrc); filter(f_psad); ', "destination(psadpipe); };\n"; close SYSLOGNG; } return; } sub import_answers() { die "[*] $answers_file does not exist" unless -e $answers_file; open F, "< $answers_file" or die "[*] Could not open $answers_file: $!"; while () { if (/^(.*?:)\s+(.*);/) { $answers{$1} = $2; } } close F; return; } sub config_metalog() { my $syslog_conf = shift; open RS, "< $syslog_conf" or die "[*] Unable to open $syslog_conf: $!"; my @lines = ; close RS; my $found = 0; for my $line (@lines) { if ($line =~ m/psadpipe\.sh/) { $found = 1; last; } } unless ($found) { &logr("[+] Modifying $syslog_conf to write kern.info messages " . "to\n $config{'PSAD_FIFO_FILE'} " . "(with script /usr/sbin/psadpipe.sh)"); unless (-e "$syslog_conf.orig") { copy $syslog_conf, "$syslog_conf.orig" or die "[*] Could not copy ", "$syslog_conf -> $syslog_conf.orig: $!"; } open METALOG, "> $syslog_conf" or die "[*] Unable to open $syslog_conf: $!"; print METALOG "\n", "\nPSAD :\n", " facility = \"kern\"\n", ' command = ', "\"/usr/sbin/psadpipe.sh\"\n"; close METALOG; open PIPESCRIPT, '> /usr/sbin/psadpipe.sh' or die "[*] Unable to open /usr/sbin/psadpipe.sh: $!"; print PIPESCRIPT "#!/bin/sh\n\n", "echo \"\$3\" >> $config{'PSAD_FIFO_FILE'}\n"; close PIPESCRIPT; chmod 0700, '/usr/sbin/psadpipe.sh'; &logr('[+] Generated /usr/sbin/psadpipe.sh ' . "which writes to $config{'PSAD_FIFO_FILE'}"); ### (Dennis Freise ) ### Metalog seems to simply die on SIGHUP and SIGALRM, and I ### found no signal or option to reload it's config... :-( &logr('[-] All files written. You have to manually restart metalog! ', 'When done, start psad again.'); } return; } sub query_yes_no() { my ($msg, $style) = @_; my $ans_file_str = $msg; $ans_file_str =~ s|\n| |g; $ans_file_str =~ s|\W| |g; $ans_file_str =~ s|\s+| |g; $ans_file_str =~ s|^\s+||; $ans_file_str =~ s|\s+$||; $ans_file_str =~ s|\sy\sn||; $ans_file_str .= ':' unless $ans_file_str =~ m|\:$|; if ($use_answers and defined $answers{$ans_file_str}) { &logr($msg); print "$answers{$ans_file_str}\n"; if (lc($answers{$ans_file_str}) eq 'y') { return 1; } else { return 0; } } else { my $ans = ''; while ($ans ne 'y' and $ans ne 'n') { &logr($msg); $ans = lc(); if ($style == $ACCEPT_YES_DEFAULT) { if ($ans eq "\n") { &put_answer_file_value($ans_file_str, 'y'); return 1; } } elsif ($style == $ACCEPT_NO_DEFAULT) { if ($ans eq "\n") { &put_answer_file_value($ans_file_str, 'n'); return 0; } } chomp $ans; } if ($ans eq 'y') { &put_answer_file_value($ans_file_str, 'y'); return 1; } else { &put_answer_file_value($ans_file_str, 'n'); return 0; } } return 0; } sub query_preserve_config() { return &query_yes_no("[+] Would you like to merge the " . "config from the existing\n psad installation ([y]/n)? ", $ACCEPT_YES_DEFAULT); } sub query_init_script_restart_syslog() { return &query_yes_no("[+] Is it ok to restart the syslog " . "daemon ([y]/n)? ", $ACCEPT_YES_DEFAULT); } sub query_preserve_sigs_autodl() { my $file = shift; return &query_yes_no("[+] Preserve any user modfications " . "in $file ([y]/n)? ", $ACCEPT_YES_DEFAULT); } sub preserve_config() { my ($local_file, $prod_file) = @_; open C, "< $local_file" or die "[*] Could not open $local_file: $!"; my @new_lines = ; close C; open CO, "< $prod_file" or die "[*] Could not open $prod_file: $!"; my @orig_lines = ; close CO; &logr("[+] Preserving existing config: $prod_file\n"); ### write to a tmp file and then move so any running psad daemon will ### re-import a full config file if a HUP signal is received during ### the install. open CONF, "> $prod_file.new" or die "[*] Could not open ", "$prod_file.new: $!"; for my $new_line (@new_lines) { if ($new_line =~ /^\s*#/) { print CONF $new_line; ### take comments from new file. } elsif ($new_line =~ /^\s*(\S+)/) { my $var = $1; my $found = 0; for my $orig_line (@orig_lines) { if ($orig_line =~ /^\s*$var\s/ and $var ne 'PSAD_AUTO_DL_FILE' ### special case paths and $var ne 'PSAD_ICMP_TYPES_FILE' and $var ne 'PSAD_SIGS_FILE' and $var ne 'PSAD_POSF_FILE') { print CONF $orig_line; $found = 1; last; } } unless ($found) { print CONF $new_line; } } else { print CONF $new_line; } } close CONF; move "$prod_file.new", $prod_file or die "[*] ", "Could not move $prod_file.new -> $prod_file: $!"; return; } sub append_fifo_syslog() { my $syslog_conf = shift; open RS, "< $syslog_conf" or die "[*] Unable to open $syslog_conf: $!\n"; my @slines = ; close RS; my $found_fifo = 0; for my $line (@slines) { $found_fifo = 1 if $line =~ /psadfifo/; } unless ($found_fifo) { &logr("[+] Modifying $syslog_conf to write kern.info " . "messages to\n"); &logr(" $config{'PSAD_FIFO_FILE'}\n"); unless (-e "$syslog_conf.orig") { copy $syslog_conf, "$syslog_conf.orig" or die "[*] Could ", "not copy $syslog_conf -> $syslog_conf.orig: $!"; } &archive($syslog_conf); open SYSLOG, "> $syslog_conf" or die "[*] Unable to open $syslog_conf: $!"; for my $line (@slines) { unless ($line =~ /psadfifo/) { print SYSLOG $line; } } print SYSLOG "\n### Send kern.info messages to psadfifo for ", "analysis by kmsgsd\n"; ### reinstate kernel logging to our named pipe print SYSLOG "kern.info\t\t|$config{'PSAD_FIFO_FILE'}\n"; close SYSLOG; } return; } sub test_syslog_config() { my $syslog_str = shift; my %used_ports; ### first find an unused high tcp port to use for testing my @netstat_out = `$cmds{'netstat'} -an`; for my $line (@netstat_out) { chomp $line; if ($line =~ m/^\s*tcp\s+\d+\s+\d+\s+\S+:(\d+)\s/) { ### $1 == protocol (tcp/udp), $2 == port number $used_ports{$1} = ''; } } ### get the first unused high tcp port greater than 5000 my $test_port = 5000; $test_port++ while defined $used_ports{$test_port}; ### make sure the interface is actually up my $uprv = (system "$cmds{'ifconfig'} lo up") >> 8; if ($uprv) { &logr("[-] Could not bring up the loopback interface.\n" . " Hoping the syslog reconfig will work anyway.\n"); return 0; } ### make sure we can see the loopback interface with ### ifconfig my @if_out = `$cmds{'ifconfig'} lo`; unless (@if_out) { &logr("[-] Could not see the loopback interface with ifconfig. " . "Hoping\n syslog reconfig will work anyway.\n"); return 0; } my $lo_ip = '127.0.0.1'; my $found_ip = 0; for my $line (@if_out) { if ($line =~ /inet\s+addr:($ip_re)\s/) { $lo_ip = $1; ### this should always be 127.0.0.1 &logr("[-] loopback interface IP is not 127.0.0.1. Continuing ". "anyway.\n") unless $lo_ip eq '127.0.0.1'; $found_ip = 1; } } unless ($found_ip) { &logr("[-] The loopback interface does not have an IP.\n" . " Hoping the syslog reconfig will work anyway.\n"); return 0; } ### remove any "test_DROP" lines from fwdata file and ipt_prefix_ctr ### before seeing if new ones can be written &scrub_prefix_ctr(); my $start_kmsgsd = 1; if (-e $config{'KMSGSD_PID_FILE'}) { open PID, "< $config{'KMSGSD_PID_FILE'}" or die "[*] Could not open ", "$config{'KMSGSD_PID_FILE'}: $!"; my $pid = ; close PID; chomp $pid; if (kill 0, $pid) { ### kmsgsd is already running $start_kmsgsd = 0; } } if ($start_kmsgsd) { ### briefly start kmsgsd just long enough to test syslog ### with a packet to port 5000 (or higher). unless (((system "${USRSBIN_DIR}/kmsgsd")>>8) == 0) { &logr("[-] Could not start kmsgsd to test syslog.\n" . " Send email to Michael Rash (mbr\@cipherdyne.org)\n"); return 0; } } ### insert a rule to deny traffic to the loopback ### interface on $test_port system "$cmds{'iptables'} -I INPUT 1 -i lo -p tcp --dport " . "$test_port -j LOG --log-prefix \"test_DROP \""; open FWDATA, "$config{'FW_DATA_FILE'}" or die "[*] Could not open $config{'FW_DATA_FILE'}: $!"; seek FWDATA,0,2; ### seek to the end of the file ### try to connect to $test_port to generate an iptables ### drop message. Note that since nothing is listening on ### the port we will immediately receive a tcp reset. my $sock = new IO::Socket::INET( 'PeerAddr' => $lo_ip, 'PeerPort' => $test_port, 'Proto' => 'tcp', 'Timeout' => 1 ); undef $sock if defined $sock; ### sleep to give kmsgsd a chance to pick up the packet ### log message from syslog sleep 2; my $found = 0; my @pkts = ; close FWDATA; for my $pkt (@pkts) { $found = 1 if $pkt =~ /test_DROP/; } ### remove the testing firewall rule system "$cmds{'iptables'} -D INPUT 1"; ### remove the any new test_DROP lines we just created ### (this probably is not necessary because psad is not ### running). &scrub_prefix_ctr(); if ($found) { } else { } if ($start_kmsgsd && -e $config{'KMSGSD_PID_FILE'}) { open PID, "$config{'KMSGSD_PID_FILE'}" or return 0; my $pid = ; close PID; chomp $pid; kill 9, $pid if kill 0, $pid; } return $found; } sub scrub_prefix_ctr() { if (-e $config{'IPT_PREFIX_COUNTER_FILE'}) { open SCRUB, "< $config{'IPT_PREFIX_COUNTER_FILE'}" or die "[*] Could not open $config{'IPT_PREFIX_COUNTER_FILE'}: $!"; my @lines = ; close SCRUB; open SCRUB, "> $config{'IPT_PREFIX_COUNTER_FILE'}" or die "[*] Could not open $config{'IPT_PREFIX_COUNTER_FILE'}: $!"; for my $line (@lines) { print SCRUB $line unless $line =~ /test_DROP/; } close SCRUB; } return; } sub check_old_psad_installation() { return unless &is_root(); my $old_install_dir = '/usr/local/bin'; if (-e "${old_install_dir}/psad") { move "${old_install_dir}/psad", "${USRSBIN_DIR}/psad" or die "[*] ", "Could not move ${old_install_dir}/psad -> ", "${USRSBIN_DIR}/psad: $!"; } if (-e "${old_install_dir}/psadwatchd") { move "${old_install_dir}/psadwatchd", "${USRSBIN_DIR}/psadwatchd" or die "[*] Could not move ${old_install_dir}/psadwatchd -> ", "${USRSBIN_DIR}/psadwatchd: $!"; } if (-e "${old_install_dir}/kmsgsd") { move "${old_install_dir}/kmsgsd", "${USRSBIN_DIR}/kmsgsd" or die "[*] Could not move ${old_install_dir}/kmsgsd -> ", "${USRSBIN_DIR}/kmsgsd: $!"; } if (-e "$config{'PSAD_CONF_DIR'}/psad_signatures.old") { unlink "$config{'PSAD_CONF_DIR'}/psad_signatures.old"; } if (-e "$config{'PSAD_CONF_DIR'}/psad_auto_ips.old") { unlink "$config{'PSAD_CONF_DIR'}/psad_auto_ips.old"; } if (-e "$config{'PSAD_CONF_DIR'}/psad.conf.old") { unlink "$config{'PSAD_CONF_DIR'}/psad.conf.old"; } if (-e '/var/log/psadfifo') { ### this is the old psadfifo location unlink '/var/log/psadfifo'; } return; } sub get_distro() { return 'gentoo' if -e '/etc/gentoo-release'; if (-e '/etc/issue') { ### Red Hat Linux release 6.2 (Zoot) open ISSUE, '< /etc/issue' or die "[*] Could not open /etc/issue: $!"; my @lines = ; close ISSUE; for my $line (@lines) { chomp $line; return 'redhat' if $line =~ /red\s*hat/i; return 'fedora' if $line =~ /fedora/i; return 'ubuntu' if $line =~ /ubuntu/i; } } return 'NA'; } sub perms_ownership() { my ($file, $perm_value) = @_; chmod $perm_value, $file or die "[*] Could not ", "chmod($perm_value, $file): $!"; return unless &is_root(); ### root (maybe should take the group assignment out) chown 0, 0, $file or die "[*] Could not chown 0,0,$file: $!"; return; } sub get_fw_search_strings() { my @fw_search_strings = (); print "\n[+] By default, psad parses all iptables log messages for scan activity.\n", " However, psad can be configured to only parse those iptables messages\n", " that match particular strings (that are specified in your iptables\n", " ruleset with the --log-prefix option).\n"; if (&query_yes_no("\n Would you like psad to only parse " . "specific strings in iptables\n messages (y/[n])? ", $ACCEPT_NO_DEFAULT)) { ### we are only searching for specific iptables log prefixes &put_var('FW_SEARCH_ALL', 'N', "$config{'PSAD_CONF_DIR'}/psad.conf"); my $str = "\n psad checks the firewall configuration on the underlying machine\n" . " to see if packets will be logged and dropped that have not\n" . " explicitly allowed through. By default, psad looks for the string\n" . " \"DROP\". However, if your particular firewall configuration logs\n" . " blocked packets with the string \"Audit\" for example, psad can be\n" . " configured here to look for this string. In addition, psad can also\n" . " be configured here to look for multiple strings if needed. Remember,\n" . " whatever string you configure psad to look for must be logged via the\n" . " --log-prefix option in iptables.\n\n"; &logr($str); &logr(" Add as many search strings as you like; " . "each on its own line.\n\n"); &logr(" End with a \".\" on a line by itself.\n\n"); my $ans_file_str = 'FW search strings:'; if ($use_answers and defined $answers{$ans_file_str}) { @fw_search_strings = split /\s*,\s*/, $answers{$ans_file_str}; print "$answers{$ans_file_str}\n"; } else { my $ans = ''; my $str = ''; while ($ans !~ /^\s*\.\s*$/) { &logr(" Enter string (i.e. \"Audit\"): "); $ans = ; chomp $ans; if ($ans =~ /\"/) { &logr("[-] Quotes will be removed from FW search string: $ans\n"); $ans =~ s/\"//g; } if ($ans =~ /\S/) { if ($ans !~ /^\s*\.\s*$/) { $str .= "$ans, "; push @fw_search_strings, $ans; } } else { &logr("[-] Invalid string\n"); } } $str =~ s/\,\s*$//; &put_answer_file_value($ans_file_str, $str); } &logr("\n All firewall search strings used by psad are located " . "in the psad config file:\n $config{'PSAD_CONF_DIR'}/psad.conf\n"); } return \@fw_search_strings; } sub query_dshield() { my $str = "\n[+] psad has the capability of sending scan data via email alerts to the\n" . " DShield distributed intrusion detection system (www.dshield.org). By\n" . " default this feature is not enabled since firewall log data is sensitive,\n" . " but submitting logs to DShield provides a valuable service and assists\n" . " in generally enhancing internet security. As an optional step, if you\n" . " have a DShield user id you can edit the \"DSHIELD_USER_ID\" variable\n" . " in $config{'PSAD_CONF_DIR'}/psad.conf\n\n"; &logr($str); return &query_yes_no(' Would you like to enable DShield alerts (y/[n])? ', $ACCEPT_NO_DEFAULT); } sub query_email() { my $email_str = ''; open F, "< $config{'PSAD_CONF_DIR'}/psad.conf" or die "[*] Could not open ", "$config{'PSAD_CONF_DIR'}/psad.conf: $!"; my @clines = ; close F; my $email_addresses; for my $line (@clines) { chomp $line; if ($line =~ /^\s*EMAIL_ADDRESSES\s+(.+);/) { $email_addresses = $1; last; } } unless ($email_addresses) { return ''; } &logr("[+] psad alerts will be sent to:\n\n"); &logr(" $email_addresses\n\n"); if (&query_yes_no("[+] Would you like alerts sent to a different " . "address ([y]/n)? ", $ACCEPT_YES_DEFAULT)) { print "\n"; &logr("[+] To which email address(es) would you like " . "psad alerts to be sent?\n"); &logr(" You can enter as many email addresses as you like; " . "each on its own line.\n\n"); &logr(" End with a \".\" on a line by itself.\n\n"); my $ans_file_str = 'Email addresses:'; if ($use_answers and defined $answers{$ans_file_str}) { $email_str = $answers{$ans_file_str}; print "$answers{$ans_file_str}\n"; } else { my $ans = ''; while ($ans !~ /^\s*\.\s*$/) { &logr(" Email Address: "); $ans = ; chomp $ans; if ($ans =~ m|^\s*(\S+\@\S+)$|) { $email_str .= "$1, "; } elsif ($ans !~ /^\s*\.\s*$/) { &logr("[-] Invalid email address \"$ans\"\n"); } } $email_str =~ s/\,\s*$//; &put_answer_file_value($ans_file_str, $email_str); } } return $email_str; } sub query_syslog() { &logr("\n[+] psad supports the syslogd, rsyslogd, syslog-ng, ulogd, and\n" . " metalog logging daemons. Which system logger is running?\n\n"); my $ans = ''; my $ans_file_str = 'System logger:'; if ($use_answers and defined $answers{$ans_file_str}) { $ans = $answers{$ans_file_str}; print "$answers{$ans_file_str}\n"; } else { while ($ans ne 'syslogd' and $ans ne 'rsyslogd' and $ans ne 'syslog-ng' and $ans ne 'ulogd' and $ans ne 'metalog') { &logr(" syslogd / rsyslogd / syslog-ng / ulogd / metalog? [syslogd] "); $ans = ; if ($ans eq "\n") { ### allow default to take over $ans = 'syslogd'; } $ans =~ s/\s*//g; if ($ans eq 'syslogd') { ### allow command line --syslog-conf arg to take over $syslog_conf = '/etc/syslog.conf' unless $syslog_conf; } elsif ($ans eq 'rsyslogd') { ### allow command line --syslog-conf arg to take over $syslog_conf = '/etc/rsyslog.conf' unless $syslog_conf; } elsif ($ans eq 'syslog-ng') { ### allow command line --syslog-conf arg to take over $syslog_conf = '/etc/syslog-ng/syslog-ng.conf' unless $syslog_conf; } elsif ($ans eq 'metalog') { ### allow command line --syslog-conf arg to take over $syslog_conf = '/etc/metalog/metalog.conf' unless $syslog_conf; } if ($ans ne 'ulogd' and $syslog_conf and not -e $syslog_conf) { if (-e '/etc/rsyslog.conf') { warn "[-] It looks like /etc/rsyslog.conf exists, ", "did you mean rsyslog?\n"; } die "[*] The config file $syslog_conf does not exist. Re-run install.pl\n", " with the --syslog-conf argument to specify the path to the syslog\n", " daemon config file."; } } die "[-] Invalid syslog daemon \"$ans\"" unless ($ans and ($ans eq 'syslogd' or $ans eq 'rsyslogd' or $ans eq 'syslog-ng' or $ans eq 'ulogd' or $ans eq 'metalog')); print "\n"; &put_answer_file_value($ans_file_str, $ans); } return $ans; } sub put_answer_file_value() { my ($answer_str, $value) = @_; return if $no_write_answers; my @lines = (); if (-e $answers_file) { open F, "< $answers_file" or die "[*] Could not open $answers_file: $!"; @lines = ; close F; } my $found_str = 0; open F, "> $answers_file" or die "[*] Could not open $answers_file: $!"; for my $line (@lines) { if ($line =~ /^$answer_str\s+.*;/) { print F "$answer_str $value;\n"; $found_str = 1; } else { print F $line; } } unless ($found_str) { print F "$answer_str $value;\n"; } close F; return; } sub put_var() { my ($var, $value, $file) = @_; open RF, "< $file" or die "[*] Could not open $file: $!"; my @lines = ; close RF; open F, "> $file" or die "[*] Could not open $file: $!"; for my $line (@lines) { if ($line =~ /^\s*$var\s+.*;/) { printf F "%-28s%s;\n", $var, $value; } else { print F $line; } } close F; return; } sub archive() { my $file = shift; my $curr_pwd = cwd() or die $!; chdir $config{'CONF_ARCHIVE_DIR'} or die $!; my ($filename) = ($file =~ m|.*/(.*)|); my $base = "${filename}.old"; for (my $i = 5; $i > 1; $i--) { ### keep five copies of old config files my $j = $i - 1; unlink "${base}${i}.gz" if -e "${base}${i}.gz"; if (-e "${base}${j}.gz") { move "${base}${j}.gz", "${base}${i}.gz" or die "[*] Could not ", "move ${base}${j}.gz -> ${base}${i}.gz: $!"; } } &logr("[+] Archiving $file -> $config{'CONF_ARCHIVE_DIR'}/${base}1.gz\n"); unlink "${base}1.gz" if -e "${base}1.gz"; ### move $file into the archive directory copy $file, "${base}1" or die "[*] Could not copy ", "$file -> ${base}1: $!"; system "$cmds{'gzip'} ${base}1"; chdir $curr_pwd or die $!; $archived_old = 1; return; } sub enable_psad_at_boot() { my $distro = shift; return unless &is_root(); if (&query_yes_no("[+] Enable psad at boot time ([y]/n)? ", $ACCEPT_YES_DEFAULT)) { if ($distro eq 'redhat' or $distro eq 'fedora') { system "$cmds{'chkconfig'} --add $init_name"; } elsif ($distro eq 'gentoo') { system "$cmds{'rc-update'} add $init_name default"; } elsif ($distro eq 'ubuntu') { system "$cmds{'update-rc.d'} $init_name defaults"; } else { ### get the current run level &get_runlevel(); if ($runlevel) { if (-d '/etc/rc.d' and -d "/etc/rc.d/rc${runlevel}.d") { unless (-e "/etc/rc.d/rc${runlevel}.d/S99$init_name") { symlink "$init_dir/$init_name", "/etc/rc.d/rc${runlevel}.d/S99$init_name"; } } else { print "[-] The /etc/rc.d/rc${runlevel}.d directory does ", "exist, not sure how to enable psad at boot time."; } } } } return; } ### check paths to commands and attempt to correct if any are wrong. sub check_commands() { CMD: for my $cmd (keys %cmds) { next CMD if defined $exclude_cmds{$cmd}; unless (-x $cmds{$cmd}) { my $found = 0; PATH: for my $dir (@cmd_search_paths) { if (-x "${dir}/${cmd}") { $cmds{$cmd} = "${dir}/${cmd}"; $found = 1; last PATH; } } unless ($found) { if ($cmd eq 'runlevel') { if ($runlevel > 0) { next CMD; } else { die "[*] Could not find the $cmd command, ", "use --runlevel "; } } die "\n[*] Could not find $cmd anywhere!!! ", "Please edit the config section to include the path to ", "$cmd."; } } unless (-x $cmds{$cmd}) { return unless &is_root(); die "\n[*] $cmd is located at ", "$cmds{$cmd} but is not executable by uid: $<"; } } return; } sub is_root() { return 1 if $< == 0 and $> == 0; return 0; } sub install_manpage() { my $manpage = shift; my $name = ''; my $section = ''; if ($manpage =~ m|(\w+)\.(\d)|) { $name = $1; $section = $2; } else { die "[*] Improper man page name, should be \"pagename.section\""; } ### remove old man page if (&is_root()) { unlink "/usr/local/man/man$section/${manpage}" if -e "/usr/local/man/man$section/${manpage}"; } ### default location to put the psad man page, but check with ### /etc/man.config my $mpath = "/usr/share/man/man$section"; if (-e '/etc/man.config') { ### prefer to install $manpage in /usr/local/man/man8 if ### this directory is configured in /etc/man.config open M, '< /etc/man.config' or die "[*] Could not open /etc/man.config: $!"; my @lines = ; close M; ### prefer the path "/usr/share/man" my $found = 0; for my $line (@lines) { chomp $line; if ($line =~ m|^MANPATH\s+/usr/share/man|) { $found = 1; last; } } ### try to find "/usr/local/man" if we didn't find /usr/share/man unless ($found) { for my $line (@lines) { chomp $line; if ($line =~ m|^MANPATH\s+/usr/local/man|) { $mpath = "/usr/local/man/man$section"; $found = 1; last; } } } ### if we still have not found one of the above man paths, ### just select the first one out of /etc/man.config unless ($found) { for my $line (@lines) { chomp $line; if ($line =~ m|^MANPATH\s+(\S+)|) { $mpath = $1; last; } } } } if ($install_root ne '/') { $mpath = $config{'INSTALL_ROOT'} . $mpath; } &full_mkdir($mpath, 0755); my $mfile = "${mpath}/${manpage}"; &logr("[+] Installing $manpage man page at $mfile\n"); copy $manpage, $mfile or die "[*] Could not copy $manpage to ", "$mfile: $!"; &perms_ownership($mfile, 0644); &logr("[+] Compressing manpage $mfile\n"); ### remove the old one so gzip doesn't prompt us unlink "${mfile}.gz" if -e "${mfile}.gz"; system "$cmds{'gzip'} $mfile"; return; } sub full_mkdir() { my ($dir, $perms) = @_; my @dirs = split /\//, $dir; my $path = $dirs[0]; shift @dirs; for my $d (@dirs) { next unless $d and $d =~ /\S/; $path .= "/$d"; unless (-d $path) { printf "[+] mkdir $path, %o\n", $perms; mkdir $path, $perms or die "[*] Could not mkdir($path): $!"; } } return; } sub has_perl_module() { my $module = shift; # 5.8.0 has a bug with require Foo::Bar alone in an eval, so an # extra statement is a workaround. my $file = "$module.pm"; $file =~ s{::}{/}g; eval { require $file }; return $@ ? 0 : 1; } sub update_command_paths() { my $file = shift; open F, "< $file" or die "[*] Could not open file: $!"; my @lines = ; close F; my @newlines = (); my $new_cmd = 0; for my $line (@lines) { my $found = 0; if ($line =~ /^\s*(\w+)Cmd(\s+)(\S+);/) { my $cmd = $1; my $spaces = $2; my $path = $3; if ($path =~ /\$INSTALL_ROOT/) { $path =~ s|\$INSTALL_ROOT|$install_root|; } unless (-e $path and -x $path) { ### the command is not at this path, try to find it my $cmd_minor_name = $cmd; if ($path =~ m|.*/(\S+)|) { $cmd_minor_name = $cmd if $cmd ne $1; } DIR: for my $dir (@cmd_search_paths) { if (-e "$dir/$cmd_minor_name" and -x "$dir/$cmd_minor_name") { ### found the command push @newlines, "${cmd}Cmd${spaces}${dir}/${cmd_minor_name};\n"; $found = 1; $new_cmd = 1; last DIR; } } unless ($found) { &logr("[-] Could not find the path to the $cmd command, " . "you will need to manually\n edit the path for " . "the ${cmd}Cmd variable in $file\n"); } } } unless ($found) { push @newlines, $line; } } if ($new_cmd) { open C, "> $file" or die "[*] Could not open file: $!"; print C for @newlines; close C; } return; } sub get_runlevel() { die "[*] The runlevel cannot be greater than 6" if $runlevel > 6; return if $runlevel > 0; open RUN, "$cmds{'runlevel'} |" or die "[*] Could not execute the runlevel ", "command, use --runlevel "; while () { if (/^\s*\S+\s+(\d+)/) { $runlevel = $1; last; } } close RUN; return; } ### logging subroutine that handles multiple filehandles sub logr() { my $msg = shift; print STDOUT $msg; push @installation_lines, $msg; return; } sub usage() { my $exitcode = shift; print <<_HELP_; Usage: install.pl [options] -u, --uninstall - Uninstall psad. -f, --force-mod-install - Force all perl modules to be installed even if some already exist in the system /usr/lib/perl5 tree. -F, --Force-mod-regex - Specify a regex to match a module name and force the installation of such modules. -E, --Exclude-mod-regex - Exclude a perl module that matches this regular expression. -p, --path-update - Run path update code regardless of whether a previous config is being merged. -S, --Skip-mod-install - Do not install any perl modules. -s, --syslog-conf - Specify path to syslog.conf file. -c, --config - Specify alternate path to psad.conf from which default installation paths are derived. --init-dir - Specify path to the init directory (the default is $init_dir). --init-name - Specify the name for the psad init script (the default is $init_name). --install-syslog-fifo - Add the installation of the psadfifo (this is not usually necessary since the default is to enable ENABLE_SYSLOG_FILE). --install-root

- Install psad at a custom path (analogous to './configure --prefix=/dir'). --install-test-dir - Install psad in test/psad-install for test suite. -U, --Use-answers - Apply answers to installation queries from the file $answers_file. -a, --answers-file - Specify path to the answers file. --no-write-answers - By default the install.pl script records installation query answers to the file $answers_file, but this option disables this behavior. -r, --runlevel - Specify the current system runlevel. --no-rm-lib-dir - Do not remove the /usr/lib/psad/ directory before installing psad. --no-syslog-test - Skip syslog reconfiguration test. --no-preserve - Disable preservation of old configs. -L, --LANG - Specify LANG env variable (actually the LC_ALL variable). -n, --no-LANG - Do not export the LANG env variable. -h --help - Prints this help message. _HELP_ exit $exitcode; } psad-2.2.1/patches/0000775000175000017500000000000012071203757012222 5ustar mbrmbrpsad-2.2.1/patches/linux-2.4.4_conntrack.patch0000664000175000017500000000075712071203757017120 0ustar mbrmbr--- linux-2.4.4_orig/net/ipv4/netfilter/ip_conntrack_proto_tcp.c Sun Oct 28 18:53:52 2001 +++ linux/net/ipv4/netfilter/ip_conntrack_proto_tcp.c Wed Oct 24 15:27:12 2001 @@ -55,7 +55,7 @@ 2 MINS, /* TCP_CONNTRACK_FIN_WAIT, */ 2 MINS, /* TCP_CONNTRACK_TIME_WAIT, */ 10 SECS, /* TCP_CONNTRACK_CLOSE, */ - 60 SECS, /* TCP_CONNTRACK_CLOSE_WAIT, */ + 2 MINS, /* TCP_CONNTRACK_CLOSE_WAIT, */ 30 SECS, /* TCP_CONNTRACK_LAST_ACK, */ 2 MINS, /* TCP_CONNTRACK_LISTEN, */ }; psad-2.2.1/patches/linux-2.6.7_conntrack.patch0000664000175000017500000000120712071203757017114 0ustar mbrmbr--- net/ipv4/netfilter/ip_conntrack_proto_tcp.c.orig 2005-07-01 10:13:50.000000000 -0400 +++ net/ipv4/netfilter/ip_conntrack_proto_tcp.c 2005-07-01 10:14:39.000000000 -0400 @@ -61,7 +61,7 @@ unsigned long ip_ct_tcp_timeout_syn_recv = 60 SECS; unsigned long ip_ct_tcp_timeout_established = 5 DAYS; unsigned long ip_ct_tcp_timeout_fin_wait = 2 MINS; -unsigned long ip_ct_tcp_timeout_close_wait = 60 SECS; +unsigned long ip_ct_tcp_timeout_close_wait = 2 MINS; unsigned long ip_ct_tcp_timeout_last_ack = 30 SECS; unsigned long ip_ct_tcp_timeout_time_wait = 2 MINS; unsigned long ip_ct_tcp_timeout_close = 10 SECS; psad-2.2.1/patches/README0000664000175000017500000000233012071203757013100 0ustar mbrmbr This README applies to the patch files contained within the "patches" directory in the psad (http://www.cipherdyne.org/psad) sources. The patches in this directory are organized by kernel version or iptables version, so "linux-2.4.27_conntrack.patch" applies to the linux-2.4.27 kernel, and "iptables-1.3.8_LOG_prefix_space.patch" applies to iptables-1.3.8. The "iptables-1.3.8_LOG_prefix_space.patch" adds a trailing space to any iptables log prefix that does not already include a space. This means that an iptables log prefix cannot break the separator tokens (specifically the IN= token) in an iptables log message. More information about this can be found here: http://www.cipherdyne.org/blog/2007/08/trailing-spaces-and-iptables-log-prefixes.html Many of the patches in this directory apply to the conntrack module. Specifically, each patch extends the close wait timeout for TCP connections from 60 seconds to 2 minutes. If you are seeing iptables log messages for TCP ACK packets associated with legitimate TCP connections (i.e. packets are not being correctly identified as such by the conntrack module), you may want to apply the appropriate conntrack patch. See the BUGS section of the psad man page for more information. psad-2.2.1/patches/iptables-1.3.8_LOG_prefix_space.patch0000664000175000017500000000265112071203757020750 0ustar mbrmbr--- extensions/libipt_LOG.c.orig 2007-01-23 07:50:00.000000000 -0500 +++ extensions/libipt_LOG.c 2007-08-28 23:32:05.000000000 -0400 @@ -114,6 +114,7 @@ struct ipt_entry_target **target) { struct ipt_log_info *loginfo = (struct ipt_log_info *)(*target)->data; + int add_prefix_space = 0, prefix_len = 0, space_adjust = 1; switch (c) { case '!': @@ -138,20 +139,34 @@ exit_error(PARAMETER_PROBLEM, "Unexpected `!' after --log-prefix"); - if (strlen(optarg) > sizeof(loginfo->prefix) - 1) + prefix_len = strlen(optarg); + + if (optarg[prefix_len-1] != ' ') { + /* add a space to the end of the prefix */ + add_prefix_space = 1; + space_adjust = 0; + } + + if (prefix_len > sizeof(loginfo->prefix) - 1) exit_error(PARAMETER_PROBLEM, "Maximum prefix length %u for --log-prefix", - (unsigned int)sizeof(loginfo->prefix) - 1); + (unsigned int)sizeof(loginfo->prefix) - space_adjust); - if (strlen(optarg) == 0) + if (prefix_len == 0) exit_error(PARAMETER_PROBLEM, "No prefix specified for --log-prefix"); - if (strlen(optarg) != strlen(strtok(optarg, "\n"))) + if (prefix_len != strlen(strtok(optarg, "\n"))) exit_error(PARAMETER_PROBLEM, "Newlines not allowed in --log-prefix"); strcpy(loginfo->prefix, optarg); + + if (add_prefix_space) { + prefix_len++; + loginfo->prefix[prefix_len-1] = ' '; + } + *flags |= IPT_LOG_OPT_PREFIX; break; psad-2.2.1/patches/linux-2.4.27_conntrack.patch0000664000175000017500000000120712071203757017174 0ustar mbrmbr--- net/ipv4/netfilter/ip_conntrack_proto_tcp.c.orig 2005-07-01 09:57:22.000000000 -0400 +++ net/ipv4/netfilter/ip_conntrack_proto_tcp.c 2005-07-01 09:57:49.000000000 -0400 @@ -53,7 +53,7 @@ unsigned long ip_ct_tcp_timeout_syn_recv = 60 SECS; unsigned long ip_ct_tcp_timeout_established = 5 DAYS; unsigned long ip_ct_tcp_timeout_fin_wait = 2 MINS; -unsigned long ip_ct_tcp_timeout_close_wait = 60 SECS; +unsigned long ip_ct_tcp_timeout_close_wait = 2 MINS; unsigned long ip_ct_tcp_timeout_last_ack = 30 SECS; unsigned long ip_ct_tcp_timeout_time_wait = 2 MINS; unsigned long ip_ct_tcp_timeout_close = 10 SECS; psad-2.2.1/README.SYSLOG0000664000175000017500000000516312071203757012477 0ustar mbrmbr This information is documented in the psad.conf file as well: By default, psad acquires iptables log data from the /var/log/messages file which the local syslog daemon (usually) writes iptables log messages to. If the ENABLE_SYSLOG_FILE variable is set to "N", then psad reconfigures syslog to write iptables log data to the /var/lib/psad/psadfifo fifo file where the messages are picked up by kmsgsd written to the file /var/log/psad/fwdata for analysis by psad. On some systems, having syslog communicate log data to kmsgsd can be problematic (syslog configs and external factors such as Apparmor and SELinux can play a role here), so leaving the ENABLE_SYSLOG_FILE variable set to "Y" is usually recommended. *** Pre psad-2.1.3 information below *** TESTING YOUR INSTALLATION: The psad installer does its best to reconfigure your syslog daemon to write all kern.info messages (or higher) to the /var/lib/psad/psadfifo named pipe for analysis. However, in order to test whether your installation is working or not, you can do the following as root: $ iptables -I INPUT -i lo -p tcp --dport 3003 -j LOG --log-prefix "Inbound " $ telnet localhost 3003 Assuming that psad is running, this should generate in /var/log/psad/fwdata something similar to: Jun 15 23:37:33 kernel: Inbound IN=lo OUT= MAC= SRC=127.0.0.1 DST=127.0.0.1 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=47312 DF PROTO=TCP SPT=40945 DPT=3003 WINDOW=32767 RES=0x00 SYN URGP=0 Also, executing "psad --Status" should display (among other things) something like: Iptables prefix counters: "Inbound": 1 If the /var/log/psad/fwdata file is empty but you are getting messages in the system log (for example when you type "dmesg" or in /var/log/messages), then you should make sure that psad has the fifo open: $ lsof | grep psadfifo You should get something along the lines of: syslogd 942 root 20u FIFO 3,5 544097 /var/lib/psad/psadfifo kmsgsd 25457 root 0u FIFO 3,5 544097 /var/lib/psad/psadfifo The main requirement is that iptables logs are getting logged via kern.info (or at a higher priority such as "warn") by syslog. The default for the iptables LOG target is log iptables messages at the "warn" priority, but this can be changed with the --log-level option. For example, to have iptables generate logs at the "info" priority in the INPUT chain, the following command could be used: # iptables -A INPUT -j LOG --log-level info This may help cut down on iptables logs being sent to the console if your syslog.conf instructs syslog to log kernel messages at a "warn" level or higher to the console device. psad-2.2.1/INSTALL0000664000175000017500000000646012071203757011632 0ustar mbrmbrInstallation notes: QUICK AND EASY INSTALLATION INSTRUCTIONS: Just run the psad installation script "install.pl" from the psad sources directory: # ./install.pl This will result in a functional installation of psad on your system. It is safe to run the install.pl script even if you already have psad installed on your system. The configuration can (optionally) be preserved from the previous installation (you will be prompted for this if an existing psad installation is detected). For more information, read on: ============================================================================== IMPORTANT: psad makes use of log messages that are generated by iptables as it logs (and drops) packets. Hence if your firewall is not configured to log packets, then psad will NOT detect port scans or anything else. Usually the best and most secure way to configure your firewall is to first put the minimal rules needed to allow only necessary traffic to and from your machine, and then have default LOG and DROP rules toward the end of the firewall ruleset. Some example firewall rulesets that are compatible with psad are contained within the file FW_EXAMPLE_RULES, and the "iptables.sh" script available at the following link contains a script to build a compatible iptables policy: http://www.cipherdyne.org/LinuxFirewalls/ch01/iptables.sh.tar.gz Note that psad is only compatible (as of version 2.1.3) with iptables firewalls, but support for other firewall logging formats (such as logs generated by ipfw and pf) is coming soon. DEPENDENCIES: psad requires several perl modules that may or may not already be installed on your Linux system. These modules are included in the deps/ directory in the psad sources, and the list of modules is: Bit::Vector Date::Calc IPTables::ChainMgr IPTables::Parse NetAddr::IP Storable Unix::Syslog psad also includes a whois client written by Marco d'Itri (see the deps/whois directory). This client does better than others at collecting the correct whois information for a given IP address. CONNECTION TRACKING: As of kernel version 2.4.13, there is a bug in the connection tracking code that can drop packets that are part of legitimate TCP connections that have entered into the CLOSE_WAIT state depending on how late they arrive. Since these packets are drop whenever the iptables policy is configured in a default drop stance, psad interprets them as potentially belonging to a scan. The source of the problem is an inappropriately low timeout value, and fortunately this problem is mostly fixed (or at least minimized) by the trivial kernel patch "conntrack_patch" included with the psad source code. If you start noticing lots of ACK/FIN, ACK, and even RST packets being denied by iptables from legtimate sessions, then you may want to apply this patch. This will of course require the kernel to be recompiled. For more information on how to do this, see the Kernel-HOWTO available at: http://www.digitalhermit.com/linux/Kernel-Build-HOWTO.html UPGRADES: You can install a new version of psad over an existing one; just run install.pl. The installation script will preserve any old configuration parameters when installing the new versions of psad, psadwatchd, and kmsgsd. UN-INSTALLING: psad can be completely removed from the system by executing install.pl with the --uninstall option. psad-2.2.1/psad0000775000175000017500000143476212071203757011471 0ustar mbrmbr#!/usr/bin/perl -w # ################################################################################ # # File: psad (/usr/sbin/psad) # # URL: http://www.cipherdyne.org/psad/ # # Purpose: psad makes use of iptables logs to detect port scans, # probes for backdoors and DDoS tools, and other suspect traffic # (many signatures were adapted from the Snort intrusion # detection system). Data is provided by parsing syslog # firewall messages out of /var/log/messages (or wherever syslog # is configured to write iptables logs to). # # For more information read the psad man page or view the # documentation provided at: http://www.cipherdyne.org/psad/ # # Author: Michael Rash (mbr@cipherdyne.org) # # Credits: (see the CREDITS file bundled with the psad sources.) # # Version: 2.2.1 # # Copyright (C) 1999-2012 Michael Rash (mbr@cipherdyne.org) # # Reference: Snort is a registered trademark of Sourcefire, Inc. # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # TODO: (see the TODO file bundled with the psad sources.) # # Default behavior is as follows. Each of these features can be disabled # with command line arguments: # # - passive OS fingerprinting = yes # - snort rule matching = yes # - write fw errors to error log = yes # - daemon mode = yes # - reverse dns lookups = yes # - validate firewall rules = yes # - whois lookups of scanning IPs = yes # - parse netstat output for local server ports = yes # # Coding Style: All configuration variables from psad.conf are stored in # the %config hash by keys that are in capital letters. This is # the only place in the code where capital letters will be used in # variables names. There are several variables with file-scope, and # these variables are clearly commented near the top of each of the # psad daemons. Lines are generally limited to 80 characters for easy # reading. # # Scan hash key explanation: # absnum - Total number of packets from $src to $dst # tot_protocols - Total number of IP protocols scanned by $src against $dst # chain - iptables chain under which the scan packets appear in the # logs. # s_time - Start time for the first packet seen from src to dst. # alerted - An alert has been sent. # pkts - Number of packets (used for signatures and a packet counter # for the current interval. # flags - Keeps track of tcp flags. # sid - Signature tracking # abs_sp - Absolute starting port. # abs_ep - Absolute ending port. # strtp - Starting port. # endp - Ending port. # # Sample iptables log messages: # # Sample tcp packet (rejected by iptables... --log-prefix = "DROP ") # # Mar 11 13:15:52 orthanc kernel: DROP IN=lo OUT= MAC=00:00:00:00:00:00:00:00: # 00:00:00:00:08:00 SRC=127.0.0.1 DST=127.0.0.1 LEN=60 TOS=0x00 PREC=0x00 # TTL=64 ID=0 DF PROTO=TCP SPT=44847 DPT=35 WINDOW=32304 RES=0x00 SYN URGP=0 # # Sample icmp packet rejected by iptables INPUT chain: # # Nov 27 15:45:51 orthanc kernel: DROP IN=eth1 OUT= MAC=00:a0:cc:e2:1f:f2:00: # 20:78:10:70:e7:08:00 SRC=192.168.10.20 DST=192.168.10.1 LEN=84 TOS=0x00 # PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=61055 SEQ=256 # # Sample icmp packet logged through FORWARD chain: # # Aug 20 21:23:32 orthanc kernel: SID365 IN=eth2 OUT=eth1 SRC=192.168.20.25 # DST=192.168.10.15 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=ICMP TYPE=8 # CODE=0 ID=19467 SEQ=256 # # Occasionally the kernel klogd ring buffer must become full since log # entries are sometimes generated by a long port scan like this (note # there is no 'DPT' field): # # Mar 16 23:50:25 orthanc kernel: DROP IN=lo OUT= MAC=00:00:00:00:00:00:00: # 00:00:00:00:00:08:00 SRC=127.0.0.1 DST=127.0.0.1 LEN=60 TOS=0x00 PREC=0x00 # TTL=64 ID=0 DF PROTO=TCP SPT=39935 DINDOW=32304 RES=0x00 SYN URGP=0 # # Note on iptables tcp log messages: # # iptables reports tcp flags in the following order: # # URG ACK PSH RST SYN FIN # # Files specification for /var/log/psad/ directories: # # psad creates a new directory "/var/log/psad/" for each new # from which a scan is detected. Under this directory several files are # created: # # danger_level - Overall danger level aggregated for all scans. # p0f_guess - Passive OS fingerprint guess for . # _whois - Whois information for (or if src # is a local IP). # _email_ctr - Total email alerts sent for . # _email_alert - The most recent email alert for . # _packet_ctr - Packet counters for . # _signatures - Signatures detected against . # # Note that some of the files above contain the destination address since a # single source address may scan several destination addresses. # ############################################################################### # ### modules used by psad use File::Copy; use File::Path; use IO::Socket; use Socket; use POSIX; use IO::Handle; use Data::Dumper; use Getopt::Long 'GetOptions'; use strict; ### ========================== main ================================= ### set the current version my $version = '2.2.1'; ### default config file for psad (can be changed with ### --config switch) my $config_file = '/etc/psad/psad.conf'; ### this will be set to either FW_DATA_FILE, ULOG_DATA_FILE ### or IPT_SYSLOG_FILE my $fw_data_file = ''; ### disable debugging by default my $debug = 0; my $debug_sid = 0; ### debug a specific signature my $flush_fw = 0; ### build the iptables blocking configuration out of the ### IPT_AUTO_CHAIN variable my @ipt_config = (); ### main configuration hash my %config = (); my $override_config_str = ''; ### local subnets my @local_nets = (); ### fw search string array my @fw_search = (); ### socket for --fw-block my $ipt_sock = ''; ### commands hash my %cmds = (); ### main psad data structure; contains ips, port ranges, ### protocol info, tcp flags, etc. my %scan = (); ### cache scan danger levels my %scan_dl = (); ### cache scan email counters my %scan_email_ctrs = (); ### cache executions of external script (only used if ### ENABLE_EXT_SCRIPT_EXEC is set to 'Y'); my %scan_ext_exec = (); ### cache p0f-based passive os fingerprinting information my %p0f = (); ### cache p0f-based passive os fingerprinting signature information my %p0f_ipv4_sigs = (); my %p0f_ipv6_sigs = (); ### cache TOS-based passive os fingerprinting information my %posf = (); ### cache TOS-based passive os fingerprinting signature information my %posf_sigs = (); ### cache valid icmp types and corresponding codes my %valid_icmp_types = (); my %valid_icmp6_types = (); ### Cache snort rule messages unless --no-snort-sids switch was ### given. This is only useful if iptables includes rules ### that log things like "SID123". "fwsnort" ### (http://www.cipherdyne.org/fwsnort/) will automatically ### build such a ruleset from snort signatures. my %fwsnort_sigs = (); ### Cache snort classification.config file for class priorities my %snort_class_dl = (); ### Cache any individual Snort rule priority definitions from ### the snort_rule_dl file my %snort_rule_dl = (); ### Cache Snort rule reference configuration my %snort_ref_baseurl = (); ### cache all scan signatures from /etc/psad/signatures file my %sigs = (); my %sig_search = (); my %sig_ip_objs = (); ### cache iptables prefixes my %ipt_prefixes = (); ### ignore ports my %ignore_ports = (); ### ignore protocols my %ignore_protocols = (); ### ignore interfaces my %ignore_interfaces = (); ### data array used for dshield.org logs my @dshield_data = (); ### track the last time we sent an alert to dshield.org my $last_dshield_alert = ''; ### calculate how often a dshield alert will be sent my $dshield_alert_interval = ''; ### dshield stats counters my $dshield_email_ctr = 0; my $dshield_lines_ctr = 0; ### get the current timezone for dshield (this is calculated ### and re-calculated since the timezone may change). my $timezone = ''; ### get the current year for dshield my $year = ''; ### keep track of how many CHECK_INTERVALS have elapsed; this is ### useful for TOP_SCANS_CTR_THRESHOLD my $check_interval_ctr = 0; ### track the number of scan IP pairs for MAX_SCAN_IP_PAIRS thresholding my $scan_ip_pairs = 0; ### %auto_dl holds all ip addresses that should automatically ### be assigned a danger level (or ignored). my %auto_dl = (); my %auto_dl_ip_objs = (); my %auto_assigned_msg = (); my $PERMANENT = 0; ### cache the source ips that we have automatically blocked ### (if ENABLE_AUTO_IDS == 'Y') my %auto_blocked_ips = (); ### counter to check psad iptables chains and jump rules my $iptables_prereq_check = 0; ### cache the addresses we have issued dns lookups against. my %dns_cache = (); ### cache the addresses we have executed whois lookups against. my %whois_cache = (); ### cache ports the local machine is listening on (periodically ### updated by get_listening_ports()). my %local_ports = (); ### cache the ip addresses associated with each interface on the ### local machine. my %local_ips = (); ### Top attacking statistics my %top_tcp_ports = (); my %top_udp_ports = (); my %top_udplite_ports = (); my %top_sigs = (); my %sig_sources = (); my %top_sig_counts = (); my %top_packet_counts = (); my %local_src = (); ### regex to match IP addresses my $ipv4_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|; ### IPv4 ### IPv6 - full version in ip6tables logs my $ipv6_re = qr|(?:[a-f0-9]{4}:){7}(?:[a-f0-9]{4})|i; ### ttl values are decremented depending on the number of hops ### the packet has taken before it hits the firewall. We will ### assume packets will not jump through more than 20 hops on ### average. my $max_hops = 20; ### initial set of protocol packet counters (may get expanded through ### things like protocol scan detection) my %protocols = ( 'tcp' => '', 'udp' => '', 'udplite' => '', 'icmp' => '', 'icmp6' => '' ); my %proto_ctrs = (); my %protocol_strings = (); ### pid file hash my %pidfiles = (); ### initialize and scope some default variables (command ### line args can override some default values) my $sigs_file = ''; my $posf_file = ''; my $auto_dl_file = ''; my $snort_rules_dir = ''; my $srules_type = ''; my $cmdline_file = ''; my $analyze_mode = 0; my $analysis_fields = ''; my $analysis_tokens_ar = []; my $analysis_match_criteria_ar = []; my $analyze_mode_auto_block = 0; my $get_next_rule_id = 0; my $test_mode = 0; my $syslog_server = 0; my $kill = 0; my $restart = 0; my $restrict_ip = ''; my $restrict_ip_cmdline = ''; my $status_mode = 0; my $status_ip = ''; my $status_min_dl = 0; my $status_summary = 0; my $fw_list_auto = 0; my $fw_block_ip = ''; my $fw_rm_block_ip = ''; my $fw_del_chains = 0; my $gnuplot_mode = 0; my $gnuplot_year = 0; my $gnuplot_prev_mon = 0; my $gnuplot_title = ''; my $gnuplot_legend_title = ''; my $gnuplot_grayscale = 0; my $gnuplot_x_label = ''; my $gnuplot_x_range = ''; my $gnuplot_y_label = ''; my $gnuplot_y_range = ''; my $gnuplot_z_label = ''; my $gnuplot_z_range = ''; my $gnuplot_3d = 0; my $gnuplot_view = ''; my $gnuplot_sort_style = 'value'; my $gnuplot_graph_style = ''; my $gnuplot_count_type = ''; my $gnuplot_count_element = -1; my %gnuplot_cache_uniq = (); my @gnuplot_data = (); my $gnuplot_data_file = 'psad_iptables.dat'; my $gnuplot_plot_file = 'psad_iptables.gnu'; my $gnuplot_png_file = 'psad_iptables.png'; my $gnuplot_file_prefix = ''; my $gnuplot_template_file = ''; my $store_file = ''; my $gnuplot_interactive = 0; my $plot_separator = ', '; ### default to CSV format for plot data my $csv_mode = 0; my $csv_stdin = 0; my $csv_fields = ''; my $csv_print_uniq = 0; my $csv_line_limit = 0; my $csv_start_line = 0; my $csv_end_line = 0; my $csv_regex = ''; my $csv_neg_regex = ''; my $csv_have_timestamp = 0; my $pkts_from_stdin = 0; my $dump_ipt_policy = 0; my $fw_include_ips = 0; my $benchmark = 0; my $num_packets = 0; my $usr1 = 0; my $hup = 0; my $usr1_flag = 0; my $hup_flag = 0; my $verbose = 0; my $print_ver = 0; my $help = 0; my $dump_conf = 0; my $download_sigs = 0; my $chk_interval = 0; my $log_len = 23; ### used in scan_logr() my $fw_analyze = 0; my $fw_file = ''; my $lib_dir = ''; my $rm_data_ctr = 0; my $analysis_emails = 0; my $analysis_whois = 0; my $enable_analysis_dns = 0; my $netstat_lkup_ctr = 0; my $kmsgsd_started = 0; my $warn_msg = ''; my $die_msg = ''; my $skip_first_loop = 1; my $cmdl_interface = ''; my $analyze_write_data = 0; my $local_ips_lkup_ctr = 0; my $num_hash_marks = 76; ### for gnuplot output my $imported_syslog_module = 0; ### these flags are used to disable several features ### in psad if specified from the command line my $no_snort_sids = 0; my $no_signatures = 0; my $no_icmp_types = 0; my $no_icmp6_types = 0; my $no_auto_dl = 0; my $no_posf = 0; my $no_daemon = 0; my $no_ipt_errors = 0; my $no_rdns = 0; my $no_whois = 0; my $no_netstat = 0; my $no_fwcheck = 0; my $no_kmsgsd = 0; my $no_email_alerts = 0; my $no_syslog_alerts = 0; ### tcp option types my $tcp_nop_type = 1; my $tcp_mss_type = 2; my $tcp_win_scale_type = 3; my $tcp_sack_type = 4; my $tcp_timestamp_type = 8; my %tcp_p0f_opt_types = ( 'N' => $tcp_nop_type, 'M' => $tcp_mss_type, 'W' => $tcp_win_scale_type, 'S' => $tcp_sack_type, 'T' => $tcp_timestamp_type ); my %ip_options = (); ### for ICMP my $ICMP_ECHO_REQUEST = 8; my $ICMP_ECHO_REPLY = 0; my $ICMP6_ECHO_REQUEST = 128; my $ICMP6_ECHO_REPLY = 129; ### These are not directly support by psad because they ### do not appear in iptables logs; however, several of ### these options are supported if fwsnort is also running. my @unsupported_snort_opts = qw( pcre fragbits content-list rpc byte_test byte_jump distance within flowbits rawbytes regex isdataat uricontent content offset replace resp flowbits ip_proto ); ### the ip_proto keyword could be supported, but would require ### refactoring parse_NF_pkt_str(). ### for Snort signature sp/dp matching my @port_types = ( {'sp' => 'norm', 'dp' => 'norm'}, {'sp' => 'norm', 'dp' => 'neg'}, {'sp' => 'neg', 'dp' => 'norm'}, {'sp' => 'neg', 'dp' => 'neg'}, ); ### main packet data structure my %pkt_NF_init = ( ### data link layer 'src_mac' => '', 'dst_mac' => '', 'intf' => '', ### FIXME in and out interfaces? ### network layer 'src' => '', 's_obj' => '', 'dst' => '', 'd_obj' => '', 'proto' => '', 'ip_len' => -1, 'ip_opts' => '', ### v4 or v6 ### IPv4 'ip_id' => -1, 'ttl' => -1, 'tos' => '', 'frag_bit' => 0, ### IPv6 'is_ipv6' => 0, 'tc' => -1, 'hop_limit' => -1, 'flow_label' => -1, ### ICMP 'itype' => -1, 'icode' => -1, 'icmp_seq' => -1, 'icmp_id' => -1, ### transport layer 'sp' => -1, 'dp' => -1, 'win' => -1, 'flags' => -1, 'tcp_seq' => -1, 'tcp_ack' => -1, 'tcp_opts' => '', 'udp_len' => -1, ### extra fields for psad internals (DShield reporting, fwsnort ### sid matching, iptables logging prefixes and chains, etc.) 'fwsnort_sid' => 0, 'fwsnort_rnum' => 0, 'fwsnort_estab' => 0, 'is_topera' => 0, ### Topera IPv6 scanner detection, requires --log-ip-options 'chain' => '', 'log_prefix' => '', 'dshield_str' => '', 'syslog_host' => '', 'timestamp' => '' ); my %gnuplot_non_digit_packet_fields = ( ### 'hashentry' - maps the field to an integer based on whether ### it has been seen before ### 'intf2int' - converts interface number to an integer (e.g. eth0 -> 0) ### 'ip2int' - converts IP address to integer representation ### data link layer 'src_mac' => 'hashentry', 'dst_mac' => 'hashentry', 'intf' => 'intf2int', ### network layer 'src' => 'ip2int', 'dst' => 'ip2int', 'proto' => 'proto2int', 'tos' => 'hashentry', 'ip_opts' => 'hashentry', 'frag_bit' => 'hashentry', ### transport layer 'flags' => 'hashentry', 'tcp_opts' => 'hashentry', ### extra fields for psad internals (DShield reporting, fwsnort ### sid matching, iptables logging prefixes and chains, etc.) 'chain' => 'hashentry', 'log_prefix' => 'hashentry', 'dshield_str' => 'hashentry', 'syslog_host' => 'hashentry', ); my %gnuplot_non_digit_map = (); my %ip2int_cache = (); my %gnuplot_ip2int = (); ### packet parsing return values my $PKT_ERROR = 0; my $PKT_SUCCESS = 1; my $PKT_IGNORE = 2; ### icmp header validation my $BAD_ICMP_TYPE = 1; my $BAD_ICMP_CODE = 2; my $SIG_MATCH = 1; my $NO_SIG_MATCH = 0; ### header lengths my $TCP_HEADER_LEN = 20; ### excludes options my $TCP_MAX_OPTS_LEN = 44; my $UDP_HEADER_LEN = 8; my $ICMP_HEADER_LEN = 4; my $IP_HEADER_LEN = 20; ### excludes options ### save a copy of the command line arguments my @args_cp = @ARGV; ### handle command line args &getopt_wrapper(); ### Everthing after this point must be executed as root (psad ### only needs root if run in auto-blocking mode; should take ### this into account and drop privileges). &is_root(); ### Import all psad configuration and signatures files ### (psad.conf, posf, signatures, psad_icmp_types, ### and auto_dl), and call setup(). &psad_init(); ### check to make sure another psad process is not already running. &unique_pid($config{'PSAD_PID_FILE'}); ### get the ip addresses that are local to this machine &get_local_ips(); ### get the current services running on this machine &get_listening_ports() unless $no_netstat; ### daemonize psad unless running with --no-daemon or an ### analysis mode unless ($no_daemon or $debug) { my $pid = fork(); exit 0 if $pid; die "[*] $0: Couldn't fork: $!" unless defined $pid; POSIX::setsid() or die "[*] $0: Can't start a new session: $!"; } ### write the current pid associated with psad to the psad pid file &write_pid($config{'PSAD_PID_FILE'}); ### write the command line args used to start psad to $cmdline_file &write_cmd_line(\@args_cp, $cmdline_file) unless $debug; ### psad requires that kmsgsd is running to receive any data (unless ### SYSLOG_DAEMON is set to ulogd or psad is configured to acquire data ### from a normal file via IPT_SYSLOG_FILE), so let's start it here for good ### measure (as of 0.9.2 it makes use of the pid files and unique_pid(), ### so we don't have to worry about starting a duplicate copy). While ### we're at it, start psadwatchd as well. Note that this is the best ### place to start the other daemons since we just wrote the psad pid ### to PID_FILE above. my $cmd; unless ($config{'ENABLE_SYSLOG_FILE'} eq 'Y' or $no_kmsgsd or $config{'SYSLOG_DAEMON'} =~ /ulog/i or $kmsgsd_started) { $cmd = $cmds{'kmsgsd'}; $cmd .= " -c $config_file"; $cmd .= " -O $override_config_str" if $override_config_str; open KMSGSD, "| $cmd" or die "[*] Could not execute $cmds{'kmsgsd'}"; close KMSGSD; $kmsgsd_started = 1; } unless ($kmsgsd_started) { my $pid = &is_running($pidfiles{'kmsgsd'}); if ($pid) { kill 9, $pid unless kill 15, $pid; } unlink $pidfiles{'kmsgsd'} if -e $pidfiles{'kmsgsd'}; } unless ($debug or $no_daemon) { $cmd = $cmds{'psadwatchd'}; $cmd .= " -c $config_file"; $cmd .= " -O $override_config_str" if $override_config_str; open PSADWATCHD, "| $cmd" or die "[*] Could not ", "execute $cmds{'psadwatchd'}"; close PSADWATCHD; } if ($config{'ENABLE_AUTO_IDS'} eq 'Y') { ### always flush old rules (the subsequent renew_auto_blocked_ips() ### will re-instantiate any that should not have been expired). &flush_auto_blocked_ips() if $config{'FLUSH_IPT_AT_INIT'} eq 'Y'; ### Check to see if psad automatically blocked some IPs from ### a previous run. This feature is most useful for preserving ### auto-block rules for IPs after a reboot or after restarting ### psad. (Note that ENABLE_AUTO_IDS is disabled by psad_init() ### if we are running on a syslog server or if we are running ### in -A mode). &renew_auto_blocked_ips(); } ### Install signal handlers for debugging %scan with Data::Dumper, ### and for reaping zombie whois processes. $SIG{'__WARN__'} = \&warn_handler; $SIG{'__DIE__'} = \&die_handler; $SIG{'CHLD'} = \&REAPER; $SIG{'USR1'} = \&usr1_handler; $SIG{'HUP'} = \&hup_handler; if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y') { $last_dshield_alert = time() unless $last_dshield_alert; } ### Initialize current time for disk space checking. my $last_disk_check = time(); if ($config{'IMPORT_OLD_SCANS'} eq 'Y') { ### import old scans and counters from /var/log/psad/ &import_old_scans(); } elsif ($config{'ENABLE_SCAN_ARCHIVE'} eq 'Y') { &archive_data(); } else { &remove_old_scans(); } ### zero out the packet counter file (the counters ### are all zero at this point anyway unless we ### imported old scans). &write_global_packet_counters(); ### zero out prefix counters &write_prefix_counters(); ### zero out dshield alert stats (note we do this here regardless of ### whether DShield alerting is enabled since if it isn't we will ### just zero out the counters). &write_dshield_stats(); my $fw_data_file_size = -s $fw_data_file; my $fw_data_file_inode = (stat($fw_data_file))[1]; my $fw_data_file_check_ctr = 0; ### Get an open filehandle for the main firewall data file FW_DATA_FILE. ### All firewall drop/reject log messages are written to FW_DATA_FILE ### by kmsgsd (or by ulogd directly). print STDERR "[+] Opening iptables data file: $fw_data_file\n" if $debug; open FWDATA, $fw_data_file or die '[*] Could not open ', "$fw_data_file: $!"; &get_auto_response_domain_sock() if $config{'ENABLE_AUTO_IDS'} eq 'Y'; ###=========================================================### ###### MAIN LOOP ###### ###=========================================================### MAIN: for (;;) { ### scope and clear the firewall data array my @fw_packets = (); ### for --fw-block my @add_ipt_addrs = (); if ($hup_flag) { &sys_log('received HUP signal, ' . 're-importing psad.conf'); print STDERR "[+] Received HUP signal, re-importing config...\n" if $debug; my $orig_fwdata = $fw_data_file; my $orig_ipt_sockfile = ''; $orig_ipt_sockfile = $config{'AUTO_IPT_SOCK'} if $config{'ENABLE_AUTO_IDS'} eq 'Y'; ### Re-import all used config files (psad.conf, auto_dl, ### posf, signatures) if we received a HUP signal. &psad_init(); if ($orig_fwdata ne $fw_data_file) { close FWDATA; ### re-open the fwdata file open FWDATA, $fw_data_file or die "[*] Could not open $fw_data_file: $!"; $skip_first_loop = 1; } if ($config{'ENABLE_AUTO_IDS'} eq 'Y') { if ($orig_ipt_sockfile ne $config{'AUTO_IPT_SOCK'}) { close $ipt_sock; &get_auto_response_domain_sock(); $skip_first_loop = 1; } } $hup_flag = 0; ### clear the HUP flag } ### See if we need to print out the %scan datastructure ### (we received a USR1 signal) if ($usr1_flag) { $usr1_flag = 0; ### clear the USR1 flag &sys_log('received USR1 signal, printing scan ' . "hashes to $config{'PSAD_DIR'}/scan_hash.$$"); ### dump scan hash to filesystem &print_scan(); } ### allow the contents of the fwdata file to be processed only after ### the first loop has been executed. if ($skip_first_loop) { $skip_first_loop = 0; seek FWDATA,0,2; ### seek to the end of the file next MAIN; } else { ### Get any new packets have been written to ### FW_DATA_FILE by kmsgsd for psad analysis. @fw_packets = ; if ($config{'ENABLE_AUTO_IDS'} eq 'Y') { ### get IP from the domain socket my $ipt_add_connection = $ipt_sock->accept(); if ($ipt_add_connection) { @add_ipt_addrs = <$ipt_add_connection>; } } } if ($fw_data_file_check_ctr == 10) { if (-e $fw_data_file) { my $size_tmp = -s $fw_data_file; my $inode_tmp = (stat($fw_data_file))[1]; if ($inode_tmp != $fw_data_file_inode or $size_tmp < $fw_data_file_size) { close FWDATA; &sys_log('[+]', "iptables syslog file $fw_data_file " . "shrank or was rotated, so re-opening"); ### re-open the fwdata file open FWDATA, $fw_data_file or die "[*] Could not open $fw_data_file: $!"; $skip_first_loop = 1; ### set file size and inode $fw_data_file_size = $size_tmp; $fw_data_file_inode = $inode_tmp; } } $fw_data_file_check_ctr = 0; } if (@fw_packets) { if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y') { ### calculate the timezone offset $timezone = sprintf("%.2d", (Timezone())[3]) . ':00'; $year = This_Year(); } unless ($no_netstat) { ### we don't expect the list of ports the machine is listening ### on to change very often. if ($netstat_lkup_ctr == 10) { &get_listening_ports(); $netstat_lkup_ctr = 0; } $netstat_lkup_ctr++; } ### the local machine ip addresses could change (dhcp, etc.) ### but not that often. if ($local_ips_lkup_ctr == 30) { &get_local_ips(); $local_ips_lkup_ctr = 0; } $local_ips_lkup_ctr++; ### Extract data and summarize scan packets, assign danger level, ### send email/syslog alerts. &check_scan(\@fw_packets); } ### log top scans data my $do_log = 0; if ($config{'TOP_SCANS_CTR_THRESHOLD'} == 0) { $do_log = 1; } elsif ($check_interval_ctr % $config{'TOP_SCANS_CTR_THRESHOLD'} == 0) { $do_log = 1; } if ($do_log) { ### log the top port and signature matches &log_top_scans(); } ### Write the number of tcp/udp/icmp packets out ### to the global packet counters file &write_global_packet_counters(); ### Write out log prefix counters &write_prefix_counters(); if ($config{'ENABLE_AUTO_IDS'} eq 'Y') { ### Timeout any auto-blocked IPs that are past due (need to ### check the timeouts against existing IPs in the scan hash ### even if new packets are not found). &timeout_auto_blocked_ips(); ### see if we need to add any IP address from the domain ### socket &check_ipt_cmd(\@add_ipt_addrs) if @add_ipt_addrs; } ### Send logs to dshield in dshield format if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y') { &dshield_email_log(); } ### Allow disk space utilization checks to be disabled by ### setting DISK_MAX_PERCENTAGE = 0. if ($config{'DISK_MAX_PERCENTAGE'} > 0 and (time() - $last_disk_check) > $config{'DISK_CHECK_INTERVAL'}) { ### See how we are doing on disk space, and remove data ### if necessary. if (&disk_space_exceeded() and $config{'ENABLE_SYSLOG_FILE'} ne 'Y') { close FWDATA; ### truncate fwdata file &truncate_file($fw_data_file); ### re-open the fwdata file open FWDATA, $fw_data_file or die "[*] Could not open $fw_data_file: $!"; } $last_disk_check = time(); } &check_auto_response_sock() if $config{'ENABLE_AUTO_IDS'} eq 'Y'; ### Print the number of new packets we saw in FW_DATA_FILE if we are ### running in debug mode if ($debug) { print STDERR "[+] MAIN: number of new packets: " . ($#fw_packets+1) . "\n"; } if ($die_msg) { &print_sys_msg($die_msg, "$config{'PSAD_ERR_DIR'}/psad.die"); $die_msg = ''; } if ($warn_msg) { &print_sys_msg($warn_msg, "$config{'PSAD_ERR_DIR'}/psad.warn"); $warn_msg = ''; } ### see if we need to timeout any old scans if ($config{'ENABLE_PERSISTENCE'} eq 'N') { my $do_timeout = 0; if ($config{'PERSISTENCE_CTR_THRESHOLD'} == 0) { $do_timeout = 1; } elsif ($check_interval_ctr % $config{'PERSISTENCE_CTR_THRESHOLD'} == 0) { $do_timeout = 1; } &delete_old_scans() if $do_timeout; } $check_interval_ctr++; $fw_data_file_check_ctr++; ### clearerr() on the FWDATA filehandle to be ready for new packets FWDATA->clearerr(); ### sleep for the check interval seconds sleep $config{'CHECK_INTERVAL'}; } ### for completeness close FWDATA; exit 0; ###=========================================================### ###### END MAIN ###### ###=========================================================### #=================== BEGIN SUBROUTINES ======================== ### Keeps track of scanning ip's, increments packet counters, ### keeps track of tcp flags for each scan, test for snort sid ### values in iptables packets (if fwsnort is being used). sub check_scan() { my $fw_packets_ar = shift; my %curr_scan = (); my %curr_sigs_dl = (); my %curr_sids_dl = (); my @err_pkts = (); my %auto_block_regex_match = (); my $pkt_ctr = 0; my $log_scan_ip_pair_max = 0; my $print_scale_factor = &get_scale_factor($#$fw_packets_ar); ### loop through all of the packet log messages we have just acquired ### from iptables PKT: for my $pkt_str (@$fw_packets_ar) { ### main packet data structure my %pkt = %pkt_NF_init; if ($analyze_mode) { $pkt_ctr++; if ($pkt_ctr % $print_scale_factor == 0) { print "[+] Processed $pkt_ctr packets...\n"; } } ### main parsing routine for the iptables packet logging message my $pkt_parse_rv = &parse_NF_pkt_str(\%pkt, $pkt_str); print STDERR Dumper \%pkt if $debug and $verbose; if ($pkt_parse_rv == $PKT_ERROR) { push @err_pkts, $pkt_str unless $no_ipt_errors; next PKT; } elsif ($pkt_parse_rv == $PKT_IGNORE) { next PKT; } if ($analyze_mode and $analysis_fields) { my ($matched_fields_ar, $gnuplot_comment_str) = &ipt_match_criteria(\%pkt, $analysis_tokens_ar, $analysis_match_criteria_ar); next PKT unless $#$matched_fields_ar > -1; } $proto_ctrs{$pkt{'proto'}}++; $protocols{$pkt{'proto'}} = ''; if ($pkt{'proto'} eq 'tcp') { $top_tcp_ports{$pkt{'dp'}}++; } elsif ($pkt{'proto'} eq 'udp') { $top_udp_ports{$pkt{'dp'}}++; } elsif ($pkt{'proto'} eq 'udplite') { $top_udplite_ports{$pkt{'dp'}}++; } ### If we made it here then we correctly matched packets ### that the firewall logged. print STDERR "[+] valid packet: $pkt{'src'} ($pkt{'sp'}) -> ", "$pkt{'dst'} ($pkt{'dp'}) $pkt{'proto'}\n" if $debug; ### see if we have hit the MAX_SCAN_IP_PAIRS threshold if ($config{'MAX_SCAN_IP_PAIRS'} > 0) { if ($scan_ip_pairs >= $config{'MAX_SCAN_IP_PAIRS'}) { unless (defined $scan{$pkt{'src'}} and defined $scan{$pkt{'src'}}{$pkt{'dst'}}) { print STDERR "[-] excluding $pkt{'src'} -> $pkt{'dst'}, ", "scan IP pairs too high: $scan_ip_pairs\n" if $debug; $log_scan_ip_pair_max = 1; next PKT; } } if (not defined $scan{$pkt{'src'}}) { $scan_ip_pairs++; } elsif (not defined $scan{$pkt{'src'}}{$pkt{'dst'}}) { $scan_ip_pairs++; } } ### track packet counts for this source $top_packet_counts{$pkt{'src'}}++; if ($config{'HOME_NET'} ne 'any') { if ($pkt{'chain'} eq 'INPUT') { $local_src{$pkt{'dst'}} = ''; } elsif ($pkt{'chain'} eq 'OUTPUT') { $local_src{$pkt{'src'}} = ''; } elsif ($pkt{'chain'} eq 'FORWARD') { $local_src{$pkt{'src'}} = '' if &is_local($pkt{'src'}, $pkt{'s_obj'}); } } ### initialize the danger level to 0 if it is not already defined ### (note the same source address might have already scanned a ### different destination IP, so the danger level represents the ### aggregate danger level). unless (defined $scan_dl{$pkt{'src'}}) { $scan_dl{$pkt{'src'}} = 0; $scan{$pkt{'src'}}{$pkt{'dst'}}{'alerted'} = 0 if $config{'ALERT_ALL'} eq 'N'; } ### see if we need to assign a danger level according to the auto_dl ### file. The return value is the auto-assigned danger level (or ### -1 if there is no auto-assigned danger level. unless ($no_auto_dl) { my $rv = &assign_auto_danger_level(\%pkt); print STDERR "[+] assign_auto_danger_level() returned: $rv\n" if $debug; if ($rv == 0) { print STDERR "[+] ignoring $pkt{'src'} $pkt{'proto'} ", "$pkt{'dp'} scan.\n" if $debug; next PKT; } } if ($pkt{'proto'} eq 'icmp') { ### validate icmp type and code fields against the official values ### in RFC 792. See %inval_type_code for corresponding signature ### message text and danger levels. my $type_code_rv = &check_icmp_type( 'icmp', \%valid_icmp_types, $pkt{'itype'}, $pkt{'icode'}); my $update_dl = 0; if ($type_code_rv == $BAD_ICMP_TYPE) { $scan{$pkt{'src'}}{$pkt{'dst'}}{'icmp'} {'invalid_type'}{$pkt{'itype'}} {$pkt{'chain'}}{'pkts'}++; $update_dl = 1; } elsif ($type_code_rv == $BAD_ICMP_CODE) { $scan{$pkt{'src'}}{$pkt{'dst'}}{'icmp'} {'invalid_code'}{$pkt{'itype'}}{$pkt{'icode'}} {$pkt{'chain'}}{'pkts'}++; $update_dl = 1; } if ($update_dl) { if (defined $scan_dl{$pkt{'src'}}) { if ($scan_dl{$pkt{'src'}} < 2) { $scan_dl{$pkt{'src'}} = 2; } } else { $scan_dl{$pkt{'src'}} = 2; } } } elsif ($pkt{'proto'} eq 'icmp6') { ### validate icmp6 type and code fields against the official values ### defined by IANA. See %inval_type_code for corresponding signature ### message text and danger levels. my $type_code_rv = &check_icmp_type( 'icmp6', \%valid_icmp6_types, $pkt{'itype'}, $pkt{'icode'}); my $update_dl = 0; if ($type_code_rv == $BAD_ICMP_TYPE) { $scan{$pkt{'src'}}{$pkt{'dst'}}{'icmp6'} {'invalid_type'}{$pkt{'itype'}} {$pkt{'chain'}}{'pkts'}++; $update_dl = 1; } elsif ($type_code_rv == $BAD_ICMP_CODE) { $scan{$pkt{'src'}}{$pkt{'dst'}}{'icmp6'} {'invalid_code'}{$pkt{'itype'}}{$pkt{'icode'}} {$pkt{'chain'}}{'pkts'}++; $update_dl = 1; } if ($update_dl) { if (defined $scan_dl{$pkt{'src'}}) { if ($scan_dl{$pkt{'src'}} < 2) { $scan_dl{$pkt{'src'}} = 2; } } else { $scan_dl{$pkt{'src'}} = 2; } } } unless ($no_snort_sids) { if ($pkt{'fwsnort_sid'}) { ### found a fwsnort sid in the packet log message my ($dl, $is_sig_match) = &add_fwsnort_sid(\%pkt); if ($dl) { $curr_sids_dl{$pkt{'src'}} = $dl; } else { ### a signature matched but is supposed ### to be ignored next PKT if $is_sig_match == $SIG_MATCH; } } else { ### attempt to match any tcp/udp/icmp signatures in the ### main signatures hash unless ($no_signatures) { my ($dl, $is_sig_match) = &match_sigs(\%pkt); print STDERR " match_sigs() returned DL: $dl\n" if $debug and $verbose; if ($dl) { $curr_sigs_dl{$pkt{'src'}} = $dl; } else { ### a signature matched but is supposed ### to be ignored next PKT if $is_sig_match == $SIG_MATCH; } } } } ### note that we send this packet data off to DShield regardless ### of whether psad decides that it is associated with a scan so ### that DShield can make its own determination if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y' and not $benchmark and not $analyze_mode and $pkt{'dshield_str'}) { if ($pkt{'timestamp'} =~ /^\s*(\w+)\s+(\d+)\s+(\S+)/) { my $m_tmp = $1; ### kludge for Decode_Month() call my $month = Decode_Month($m_tmp); my $day = sprintf("%.2d", $2); my $time_24 = $3; push @dshield_data, "$year-$month-$day $time_24 " . "$timezone\t$config{'DSHIELD_USER_ID'}\t1" . "\t$pkt{'dshield_str'}\n"; } } ### record the absolute starting time of the scan unless (defined $scan{$pkt{'src'}}{$pkt{'dst'}}{'s_time'}) { if ($analyze_mode) { if ($pkt_str =~ /^(.*?)\s+\S+\s+kernel:/) { $scan{$pkt{'src'}}{$pkt{'dst'}}{'s_time'} = &date_time($1); } elsif ($pkt_str =~ /^\s*(\S+\s+\S+\s+\S+)/) { $scan{$pkt{'src'}}{$pkt{'dst'}}{'s_time'} = &date_time($1); } else { die "[*] Could not extract time from packet: $pkt_str\n", " Please send a bug report to: ", "mbr\@cipherdyne.org\n"; } } else { $scan{$pkt{'src'}}{$pkt{'dst'}}{'s_time'} = time(); } } ### increment hash values $scan{$pkt{'src'}}{$pkt{'dst'}}{'absnum'}++; $scan{$pkt{'src'}}{$pkt{'dst'}}{'tot_protocols'} = 0 unless defined $scan{$pkt{'src'}}{$pkt{'dst'}}{'tot_protocols'}; $scan{$pkt{'src'}}{$pkt{'dst'}}{'tot_protocols'}++ unless defined $scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}; $scan{$pkt{'src'}}{$pkt{'dst'}}{'chain'} {$pkt{'chain'}}{$pkt{'intf'}}{$pkt{'proto'}}++; ### keep track of MAC addresses $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{'s_mac'} = $pkt{'src_mac'}; $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{'d_mac'} = $pkt{'dst_mac'}; $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{'tot_protocols'} = 0 unless defined $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{'tot_protocols'}; $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{'tot_protocols'}++ unless defined $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}; $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'pkts'}++; $curr_scan{$pkt{'src'}}{$pkt{'dst'}} {$pkt{'proto'}}{'flags'}{$pkt{'flags'}}++ if $pkt{'flags'}; ### keep track of which syslog daemon reported the message. $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{'syslog_host'} {$pkt{'syslog_host'}} = '' if $pkt{'syslog_host'}; $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{'is_topera'} = $pkt{'is_topera'}; if ($pkt{'log_prefix'}) { ### see if the logging prefix matches the blocking ### regex, and if not the IP will not be blocked if ($config{'ENABLE_AUTO_IDS'} eq 'Y' and $config{'ENABLE_AUTO_IDS_REGEX'} eq 'Y' and $config{'AUTO_BLOCK_REGEX'} ne 'NONE') { ### we require a match if (not defined $auto_block_regex_match{$pkt{'src'}} and $pkt{'log_prefix'} =~ /$config{'AUTO_BLOCK_REGEX'}/) { $auto_block_regex_match{$pkt{'src'}} = ''; } } } else { $pkt{'log_prefix'} = '*noprfx*'; } ### keep track of iptables chain and logging prefix $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'chain'} {$pkt{'chain'}}{$pkt{'log_prefix'}}++; if ($pkt{'proto'} eq 'tcp' or $pkt{'proto'} eq 'udp' or $pkt{'proto'} eq 'udplite') { ### initialize the start and end port for the scanned port range if (not defined $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'strtp'}) { ### make sure the initial start port is not too low $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'strtp'} = 65535; ### make sure the initial end port is not too high $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'endp'} = 0; } unless (defined $scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}} and defined $scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'abs_sp'}) { ### This is the absolute starting port since the ### first packet was detected. Make sure the initial ### start port is not too low $scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'abs_sp'} = 65535; ### make sure the initial end port is not too high $scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'abs_ep'} = 0; } ### see if the destination port lies outside our current range ### and change if needed ($curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'strtp'}, $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'endp'}) = &check_range($pkt{'dp'}, $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'strtp'}, $curr_scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'endp'}); ($scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'abs_sp'}, $scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'abs_ep'}) = &check_range($pkt{'dp'}, $scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'abs_sp'}, $scan{$pkt{'src'}}{$pkt{'dst'}}{$pkt{'proto'}}{'abs_ep'}); } if ($debug and $verbose) { print STDERR " print Dumper scan hash $pkt{'src'} -> $pkt{'dst'}\n"; print STDERR Dumper $scan{$pkt{'src'}}{$pkt{'dst'}}; } ### attempt to passively guess the remote operating ### system based on the ttl, id, len, window, and tos ### fields in tcp syn packets (this technique is based ### on the paper "Passive OS Fingerprinting: Details ### and Techniques" by Toby Miller). Also attempt to ### fingerprint with a re-implementation of Michal Zalewski's ### p0f that only requires iptables log messages unless ($no_posf) { ### make sure we have not already guessed the OS, ### and if we have been unsuccessful in guessing ### the OS after 100 packets don't keep trying. if ($pkt{'proto'} eq 'tcp' and $pkt{'flags'} =~ /SYN/) { if ($pkt{'tcp_opts'}) { ### got the tcp options portion of the header ### p0f based fingerprinting &p0f(\%pkt); } elsif (not defined $posf{$pkt{'src'}}{'guess'} and $scan{$pkt{'src'}}{$pkt{'dst'}}{'absnum'} < 100) { &posf(\%pkt); } } } %pkt = (); } ### write bogus packets to the error log. if ($benchmark) { print scalar localtime(), ' [+] Err packets: ' . ($#err_pkts+1) . ".\n"; } else { &collect_errors(\@err_pkts) unless $no_ipt_errors; } ### Assign a danger level to the scan print "[+] Assigning scan danger levels...\n" if $analyze_mode; &assign_danger_level(\%curr_scan, \%curr_sigs_dl, \%curr_sids_dl); my $tot_scan_ips = 0; if ($analyze_mode) { for (my $dl=1; $dl <= 5; $dl++) { my $num_ips = 0; for my $src (keys %curr_scan) { $num_ips++ if $scan_dl{$src} == $dl; } $tot_scan_ips += $num_ips; print " Level $dl: $num_ips IP addresses\n"; } print "\n Tracking $tot_scan_ips total IP addresses\n"; } ### display the scan analysis &print_scan_status() if $analyze_mode; ### log scan data to the filesystem &scan_logr(\%curr_scan); ### remember that ENABLE_AUTO_IDS may have been set to 'N' if we ### are running on a syslog server, of if we are running in -A mode. &auto_psad_response(\%curr_scan, \%auto_block_regex_match) if $config{'ENABLE_AUTO_IDS'} eq 'Y' and (not $analyze_mode or $analyze_mode_auto_block); if ($config{'ENABLE_AUTO_IDS'} eq 'Y' and $analyze_mode_auto_block) { sleep $config{'AUTO_BLOCK_TIMEOUT'} + 1; &timeout_auto_blocked_ips(); } if ($log_scan_ip_pair_max) { &sys_log("scan IP pairs threshold reached"); } return; } sub parse_NF_pkt_str() { my ($pkt_hr, $pkt_str) = @_; my $is_ipv6 = 0; my $is_tcp = 0; my $is_udp = 0; my $is_udplite = 0; my $is_icmp = 0; my $is_icmp6 = 0; my $is_proto_num = 0; my $proto_num = -1; ### with ENABLE_SYSLOG_FILE enabled, psad sees all sorts of syslog ### messages that aren't just from iptables (kmsgsd is not running ### to filter them), so require a preliminary match if ($config{'ENABLE_SYSLOG_FILE'} eq 'Y') { return $PKT_IGNORE unless $pkt_str =~ /IN=.*OUT=/; } print STDERR "\n", $pkt_str if $debug; $pkt_hr->{'raw'} = $pkt_str if $csv_mode or $gnuplot_mode; ### see if there is a logging prefix (used for scan email alert even ### if we are running with FW_SEARCH_ALL = Y). Note that sometimes ### there is a buffering issue in the kernel ring buffer that is used ### to hold the iptables log message, so we want to get only the ### very last possible candidate for the log prefix (this is why the ### "kernel:" string is preceded by .*). if ($pkt_str =~ /.*kernel:\s+(.*?)\s*IN=/) { $pkt_hr->{'log_prefix'} = $1; $pkt_hr->{'log_prefix'} =~ s|\[\s*\d+\.\d+\s*\]\s*|| if ($config{'IGNORE_KERNEL_TIMESTAMP'} eq 'Y'); if ($pkt_hr->{'log_prefix'} =~ /\S/) { if ($config{'IGNORE_LOG_PREFIXES'} ne 'NONE') { return $PKT_IGNORE if $pkt_hr->{'log_prefix'} =~ m|$config{'IGNORE_LOG_PREFIXES'}|; } $ipt_prefixes{$pkt_hr->{'log_prefix'}}++; } } ### get the in/out interface and iptables chain (the code below ### allows the iptables log message to contain the PHYSDEV stuff): ### Feb 25 12:13:27 bridge kernel: INBOUND TCP: IN=br0 PHYSIN=eth0 OUT=br0 ### PHYSOUT=eth1 SRC=63.147.183.21 DST=11.11.79.100 LEN=48 TOS=0x00 ### PREC=0x00 TTL=113 ID=19664 DF PROTO=TCP SPT=4918 DPT=135 WINDOW=64240 ### RES=0x00 SYN URGP=0 ### Note the lack of whitespace requirement before the IN= interface ### because the logging prefix might not have contained it. if ($pkt_str =~ /IN=(\S+)\s+PHYSIN=.*?\sOUT=\s/ or $pkt_str =~ /IN=(\S+).*?\sOUT=\s/) { $pkt_hr->{'intf'} = $1; $pkt_hr->{'chain'} = 'INPUT'; } elsif ($pkt_str =~ /IN=(\S+)\s+PHYSIN=.*?\sOUT=\S/ or $pkt_str =~ /IN=(\S+).*?\sOUT=\S/) { $pkt_hr->{'intf'} = $1; $pkt_hr->{'chain'} = 'FORWARD'; } elsif ($pkt_str =~ /IN=\s+PHYSIN=.*?\sOUT=(\S+)/ or $pkt_str =~ /IN=\s+OUT=(\S+)/) { $pkt_hr->{'intf'} = $1; $pkt_hr->{'chain'} = 'OUTPUT'; } ### -I was used on the command line to require a specific interface if ($cmdl_interface) { return $PKT_IGNORE unless $pkt_hr->{'intf'} eq $cmdl_interface; } if ($pkt_str =~ /\sMAC=(\S+)/) { my $mac_str = $1; if ($mac_str =~ /^((?:\w{2}\:){6})((?:\w{2}\:){6})/) { $pkt_hr->{'dst_mac'} = $1; $pkt_hr->{'src_mac'} = $2; } } if ($pkt_hr->{'src_mac'}) { $pkt_hr->{'src_mac'} =~ s/:$//; print STDERR "[+] src mac addr: $pkt_hr->{'src_mac'}\n" if $debug; } if ($pkt_hr->{'dst_mac'}) { $pkt_hr->{'dst_mac'} =~ s/:$//; print STDERR "[+] dst mac addr: $pkt_hr->{'dst_mac'}\n" if $debug; } unless ($pkt_hr->{'intf'} and $pkt_hr->{'chain'}) { print STDERR "[-] err packet: could not determine ", "interface and chain.\n" if $debug; return $PKT_ERROR; } if (%ignore_interfaces) { for my $ignore_intf (keys %ignore_interfaces) { return $PKT_IGNORE if $pkt_hr->{'intf'} eq $ignore_intf; } } ### get the syslog logging host and timestamp for this packet if ($pkt_str =~ /^\s*((?:\S+\s+){2}\S+)\s+(\S+)\s+kernel:/) { $pkt_hr->{'timestamp'} = $1; $pkt_hr->{'syslog_host'} = $2; } else { $pkt_hr->{'timestamp'} = localtime(); $pkt_hr->{'syslog_host'} = 'unknown'; } ### try to extract a snort sid (generated by fwsnort) from ### the packet unless ($no_snort_sids) { if ($pkt_hr->{'log_prefix'}) { if ($pkt_hr->{'log_prefix'} =~ /$config{'SNORT_SID_STR'}(\d+)/) { $pkt_hr->{'fwsnort_sid'} = $1; ### try to extract the fwsnort rule number (must be ### fwsnort-0.9.0 or greater) if ($pkt_hr->{'log_prefix'} =~ /\[(\d+)\]/) { $pkt_hr->{'fwsnort_rnum'} = $1; } if ($pkt_hr->{'log_prefix'} =~ /ESTAB/) { $pkt_hr->{'fwsnort_estab'} = 1; } } } } unless ($pkt_hr->{'fwsnort_sid'} or $config{'FW_SEARCH_ALL'} eq 'Y') { ### note that this is not _too_ strict since people ### have different ways of writing --log-prefix strings my $matched = 0; for my $fw_search_str (@fw_search) { $matched = 1 if $pkt_str =~ /$fw_search_str/; } return $PKT_IGNORE unless $matched; } ### test for IPv6 if ($pkt_str =~ /HOPLIMIT=\d+\s+FLOWLBL=\d+/) { return $PKT_IGNORE unless $config{'ENABLE_IPV6_DETECTION'} eq 'Y'; $is_ipv6 = 1; $pkt_hr->{'is_ipv6'} = 1; } ### test for IPv4 "don't fragment" bit unless ($is_ipv6) { $pkt_hr->{'frag_bit'} = 1 if $pkt_str =~ /\sDF\s+PROTO/; } ### get IP options if --log-ip-options is used - they appear before the ### PROTO= field for either IPv4 or IPv6 packets if ($pkt_str =~ /OPT\s+\((\S+)\)\s+PROTO=/) { $pkt_hr->{'ip_opts'} = $1; } if ($is_ipv6 and $pkt_str =~ /PROTO=ICMPv6\s/) { ### test for ICMP before TCP or UDP because ICMP destination ### unreachable messages can contain embedded TCP/UDP specifics like ### so: ### Jul 21 19:07:39 minastirith kernel: [1912155.755921] IPv6 Packet ### IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:86:dd ### SRC=0000:0000:0000:0000:0000:0000:0000:0001 ### DST=0000:0000:0000:0000:0000:0000:0000:0001 LEN=107 TC=0 ### HOPLIMIT=64 FLOWLBL=0 PROTO=ICMPv6 TYPE=1 CODE=4 ### [SRC=0000:0000:0000:0000:0000:0000:0000:0001 ### DST=0000:0000:0000:0000:0000:0000:0000:0001 LEN=59 TC=0 ### HOPLIMIT=64 FLOWLBL=0 PROTO=UDP SPT=35186 DPT=12345 LEN=19 ] $is_icmp6 = 1; } elsif ($pkt_str =~ /PROTO=ICMP\s/) { ### test for ICMP before TCP or UDP because ICMP destination ### unreachable messages can contain embedded TCP/UDP specifics like ### so: ### Sep 8 18:04:26 minastirith kernel: [28241.572876] IN_DROP ### IN=wlan0 OUT= MAC=00:1a:9f:91:df:ae:00:12:27:12:0a:a0:12:00 ### SRC=10.0.0.138 DST=192.168.1.103 LEN=96 TOS=0x00 PREC=0xC0 ### TTL=254 ID=63642 PROTO=ICMP TYPE=3 CODE=3 [SRC=192.168.1.103 ### DST=10.0.0.138 LEN=68 TOS=0x00 PREC=0x00 TTL=0 ID=22458 PROTO=UDP ### SPT=35080 DPT=33434 LEN=48 ] $is_icmp = 1; } elsif ($pkt_str =~ /PROTO=TCP\s/) { $is_tcp = 1; } elsif ($pkt_str =~ /PROTO=UDPLITE\s/) { $is_udplite = 1; } elsif ($pkt_str =~ /PROTO=UDP\s/) { $is_udp = 1; } elsif ($pkt_str =~ /PROTO=(\d+)\s/) { $proto_num = $1; $is_proto_num = 1; } else { print STDERR "[-] err packet: unrecognized protocol\n" if $debug; return $PKT_ERROR; } if ($is_tcp) { if ($is_ipv6) { ### Jul 18 19:19:08 lorien kernel: [ 1835.131574] IPV6 packet IN=lo ### OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:86:dd ### SRC=0000:0000:0000:0000:0000:0000:0000:0001 ### DST=0000:0000:0000:0000:0000:0000:0000:0001 LEN=72 TC=0 ### HOPLIMIT=255 FLOWLBL=0 PROTO=TCP SPT=22 DPT=57005 WINDOW=512 ### RES=0x00 ACK FIN URGP=0 ### Topera SYN scan with --log-ip-options turned on: ### Dec 20 20:10:51 rohan kernel: [ 499.178765] DROP IN=eth0 OUT= ### MAC=00:1b:f9:76:9c:e4:0a:13:33:3a:33:36:86:ed ### SRC=2012:1234:1234:0000:0000:0000:0000:0001 ### DST=2012:1234:1234:0000:0000:0000:0000:0002 LEN=132 TC=0 HOPLIMIT=64 ### FLOWLBL=0 OPT ( ) OPT ( ) OPT ( ) OPT ( ) OPT ( ) OPT ( ) OPT ( ) ### OPT ( ) OPT ( ) PROTO=TCP SPT=26732 DPT=78 WINDOW=8192 RES=0x00 SYN URGP=0 if ($pkt_str =~ /SRC=($ipv6_re)\s+DST=($ipv6_re)\s+LEN=(\d+)\s+ TC=(\d+)\s+HOPLIMIT=(\d+)\s+FLOWLBL=(\d+)\s+.*PROTO=TCP\s+ SPT=(\d+)\s+DPT=(\d+)\s+WINDOW=(\d+)\s+ (.*)\s+URGP=/x) { ($pkt_hr->{'src'}, $pkt_hr->{'dst'}, $pkt_hr->{'ip_len'}, $pkt_hr->{'tc'}, $pkt_hr->{'hop_limit'}, $pkt_hr->{'flow_label'}, $pkt_hr->{'sp'}, $pkt_hr->{'dp'}, $pkt_hr->{'win'}, $pkt_hr->{'flags'}) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10); $pkt_hr->{'s_obj'} = new6 NetAddr::IP($pkt_hr->{'src'}) or return $PKT_ERROR; $pkt_hr->{'d_obj'} = new6 NetAddr::IP($pkt_hr->{'dst'}) or return $PKT_ERROR; if ($pkt_str =~ /(?:OPT\s+\(\s+\)\s+){7,}/) { $pkt_hr->{'is_topera'} = 1; print STDERR " Topera IPv6 scan\n" if $debug; } } else { print STDERR "[-] err packet: strange IPv6 TCP format\n" if $debug; return $PKT_ERROR; } } else { ### May 18 22:21:26 orthanc kernel: DROP IN=eth2 OUT= ### MAC=00:60:1d:23:d0:01:00:60:1d:23:d3:0e:08:00 SRC=192.168.20.25 ### DST=192.168.20.1 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=47300 DF ### PROTO=TCP SPT=34111 DPT=6345 WINDOW=5840 RES=0x00 SYN URGP=0 if ($pkt_str =~ /SRC=($ipv4_re)\s+DST=($ipv4_re)\s+LEN=(\d+)\s+TOS=(\S+) \s*.*\s+TTL=(\d+)\s+ID=(\d+)\s*.*\s+PROTO=TCP\s+ SPT=(\d+)\s+DPT=(\d+)\s.*\s*WINDOW=(\d+)\s+ (.*)\s+URGP=/x) { ($pkt_hr->{'src'}, $pkt_hr->{'dst'}, $pkt_hr->{'ip_len'}, $pkt_hr->{'tos'}, $pkt_hr->{'ttl'}, $pkt_hr->{'ip_id'}, $pkt_hr->{'sp'}, $pkt_hr->{'dp'}, $pkt_hr->{'win'}, $pkt_hr->{'flags'}) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10); $pkt_hr->{'s_obj'} = new NetAddr::IP($pkt_hr->{'src'}) or return $PKT_ERROR; $pkt_hr->{'d_obj'} = new NetAddr::IP($pkt_hr->{'dst'}) or return $PKT_ERROR; } else { print STDERR "[-] err packet: strange IPv4 TCP format\n" if $debug; return $PKT_ERROR; } } $pkt_hr->{'proto'} = 'tcp'; ### the reserve bits are not reported by ulogd, but normal ### iptables syslog messages contain them. $pkt_hr->{'flags'} =~ s/\s*RES=\S+\s*//; ### default to NULL $pkt_hr->{'flags'} = 'NULL' unless $pkt_hr->{'flags'}; if (not $pkt_hr->{'fwsnort_sid'} and $config{'IGNORE_CONNTRACK_BUG_PKTS'} eq 'Y' && ($pkt_hr->{'flags'} =~ /ACK/ || $pkt_hr->{'flags'} =~ /RST/)) { ### $dp > 1024 && ($pkt_hr->{'flags'} =~ /ACK/ || ### FIXME: ignore TCP packets that have the ACK or RST ### bits set (unless we matched a snort sid) since ### _usually_ we see these packets as a result of the ### iptables connection tracking bug. Also, note that ### no signatures make use of the RST flag. print STDERR "[-] err packet: matched ACK or RST flag.\n" if $debug; return $PKT_IGNORE; } ### per page 595 of the Camel book, "if /blah1|blah2/" ### can be slower than "if /blah1/ || /blah2/ unless ($pkt_hr->{'flags'} !~ /WIN/ && $pkt_hr->{'flags'} =~ /ACK/ || $pkt_hr->{'flags'} =~ /SYN/ || $pkt_hr->{'flags'} =~ /RST/ || $pkt_hr->{'flags'} =~ /URG/ || $pkt_hr->{'flags'} =~ /PSH/ || $pkt_hr->{'flags'} =~ /FIN/ || $pkt_hr->{'flags'} eq 'NULL') { print STDERR "[-] err packet: bad tcp flags.\n" if $debug; return $PKT_ERROR; } ### don't pickup IP options if --log-ip-options is used ### (they appear before the PROTO= field). if ($pkt_str =~ /URGP=\S+\s+OPT\s+\((\S+)\)/) { $pkt_hr->{'tcp_opts'} = $1; } if ($pkt_str =~ /\sSEQ=(\d+)\s+ACK=(\d+)/) { $pkt_hr->{'tcp_seq'} = $1; $pkt_hr->{'tcp_ack'} = $2; } ### see if we need to ignore this packet based on the ### IGNORE_PROTOCOLS config keyword. return $PKT_IGNORE if &check_ignore_proto($pkt_hr->{'proto'}); ### see if we need to ignore this packet based on the ### IGNORE_PORTS config keyword return $PKT_IGNORE if &check_ignore_port($pkt_hr->{'dp'}, $pkt_hr->{'proto'}); if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y' and not $benchmark and not $analyze_mode) { my $dflags = $pkt_hr->{'flags'}; $dflags =~ s/\s/,/g; $pkt_hr->{'dshield_str'} = "$pkt_hr->{'src'}\t$pkt_hr->{'sp'}\t" . "$pkt_hr->{'dst'}\t$pkt_hr->{'dp'}\t$pkt_hr->{'proto'}\t" . "$dflags"; } } elsif ($is_udp or $is_udplite) { if ($is_udplite) { $pkt_hr->{'proto'} = 'udplite'; } else { $pkt_hr->{'proto'} = 'udp'; } if ($is_ipv6) { ### Jul 21 21:07:39 minastirith kernel: [1912155.755862] IPv6 Packet IN=lo ### OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:86:dd ### SRC=0000:0000:0000:0000:0000:0000:0000:0001 ### DST=0000:0000:0000:0000:0000:0000:0000:0001 LEN=59 TC=0 HOPLIMIT=64 ### FLOWLBL=0 PROTO=UDP SPT=35186 DPT=12345 LEN=19 if ($pkt_str =~ /SRC=($ipv6_re)\s+DST=($ipv6_re)\s+LEN=(\d+)\s+ TC=(\d+)\s+HOPLIMIT=(\d+)\s+FLOWLBL=(\d+)\s+ PROTO=UDP(?:LITE)?\s+SPT=(\d+)\s+DPT=(\d+)\s+LEN=(\d+)/x) { ($pkt_hr->{'src'}, $pkt_hr->{'dst'}, $pkt_hr->{'ip_len'}, $pkt_hr->{'tc'}, $pkt_hr->{'hop_limit'}, $pkt_hr->{'flow_label'}, $pkt_hr->{'sp'}, $pkt_hr->{'dp'}, $pkt_hr->{'udp_len'}) = ($1,$2,$3,$4,$5,$6,$7,$8,$9); $pkt_hr->{'s_obj'} = new6 NetAddr::IP($pkt_hr->{'src'}) or return $PKT_ERROR; $pkt_hr->{'d_obj'} = new6 NetAddr::IP($pkt_hr->{'dst'}) or return $PKT_ERROR; } else { print STDERR "[-] err packet: strange IPv6 UDP format\n" if $debug; return $PKT_ERROR; } } else { ### May 18 22:21:26 orthanc kernel: DROP IN=eth2 OUT= ### MAC=00:60:1d:23:d0:01:00:60:1d:23:d3:0e:08:00 ### SRC=192.168.20.25 DST=192.168.20.1 LEN=28 TOS=0x00 PREC=0x00 ### TTL=40 ID=47523 PROTO=UDP SPT=57339 DPT=305 LEN=8 if ($pkt_str =~ /SRC=($ipv4_re)\s+DST=($ipv4_re)\s+LEN=(\d+)\s+TOS=(\S+) \s.*TTL=(\d+)\s+ID=(\d+)\s*.*\s+PROTO=UDP\s+ SPT=(\d+)\s+DPT=(\d+)\s+LEN=(\d+)/x) { ($pkt_hr->{'src'}, $pkt_hr->{'dst'}, $pkt_hr->{'ip_len'}, $pkt_hr->{'tos'}, $pkt_hr->{'ttl'}, $pkt_hr->{'ip_id'}, $pkt_hr->{'sp'}, $pkt_hr->{'dp'}, $pkt_hr->{'udp_len'}) = ($1,$2,$3,$4,$5,$6,$7,$8,$9); $pkt_hr->{'s_obj'} = new NetAddr::IP($pkt_hr->{'src'}) or return $PKT_ERROR; $pkt_hr->{'d_obj'} = new NetAddr::IP($pkt_hr->{'dst'}) or return $PKT_ERROR; } else { print STDERR "[-] err packet: strange IPv4 UDP format\n" if $debug; return $PKT_ERROR; } } ### see if we need to ignore this packet based on the ### IGNORE_PROTOCOLS config keyword. return $PKT_IGNORE if &check_ignore_proto($pkt_hr->{'proto'}); ### see if we need to ignore this packet based on the ### IGNORE_PORTS config keyword return $PKT_IGNORE if &check_ignore_port($pkt_hr->{'dp'}, $pkt_hr->{'proto'}); if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y' and not $benchmark and not $analyze_mode) { $pkt_hr->{'dshield_str'} = "$pkt_hr->{'src'}\t$pkt_hr->{'sp'}\t" . "$pkt_hr->{'dst'}\t$pkt_hr->{'dp'}\t$pkt_hr->{'proto'}"; } } elsif ($is_icmp6 or $is_icmp) { $pkt_hr->{'sp'} = $pkt_hr->{'dp'} = 0; if ($is_ipv6) { ### Jul 18 17:18:19 lorien kernel: [ 1786.520508] IPV6 packet IN=lo ### OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:86:dd ### SRC=0000:0000:0000:0000:0000:0000:0000:0001 ### DST=0000:0000:0000:0000:0000:0000:0000:0001 LEN=104 TC=0 ### HOPLIMIT=255 FLOWLBL=0 PROTO=ICMPv6 TYPE=129 CODE=0 ID=14997 SEQ=1 if ($pkt_str =~ /SRC=($ipv6_re)\s+DST=($ipv6_re)\s+LEN=(\d+)\s+TC=(\d+)\s+ HOPLIMIT=(\d+)\s+FLOWLBL=(\d+)\s+PROTO=ICMPv6\s+TYPE=(\d+)\s+ CODE=(\d+)/x) { ($pkt_hr->{'src'}, $pkt_hr->{'dst'}, $pkt_hr->{'ip_len'}, $pkt_hr->{'tc'}, $pkt_hr->{'hop_limit'}, $pkt_hr->{'flow_label'}, $pkt_hr->{'itype'}, $pkt_hr->{'icode'}) = ($1,$2,$3,$4,$5,$6,$7,$8); $pkt_hr->{'s_obj'} = new6 NetAddr::IP($pkt_hr->{'src'}) or return $PKT_ERROR; $pkt_hr->{'d_obj'} = new6 NetAddr::IP($pkt_hr->{'dst'}) or return $PKT_ERROR; } else { print STDERR "[-] err packet: strange IPv6 ICMP format\n" if $debug; return $PKT_ERROR; } $pkt_hr->{'proto'} = 'icmp6'; } else { ### Nov 27 15:45:51 orthanc kernel: DROP IN=eth1 OUT= MAC=00:a0:cc:e2:1f:f2:00: ### 20:78:10:70:e7:08:00 SRC=192.168.10.20 DST=192.168.10.1 LEN=84 TOS=0x00 ### PREC=0x00 TTL=64 ID=0 DF PROTO=ICMP TYPE=8 CODE=0 ID=61055 SEQ=256 if ($pkt_str =~ /SRC=($ipv4_re)\s+DST=($ipv4_re)\s+LEN=(\d+).* TTL=(\d+)\s+ID=(\d+).*PROTO=ICMP\s+TYPE=(\d+)\s+ CODE=(\d+)/x) { ($pkt_hr->{'src'}, $pkt_hr->{'dst'}, $pkt_hr->{'ip_len'}, $pkt_hr->{'ttl'}, $pkt_hr->{'ip_id'}, $pkt_hr->{'itype'}, $pkt_hr->{'icode'}) = ($1,$2,$3,$4,$5,$6,$7); $pkt_hr->{'s_obj'} = new NetAddr::IP($pkt_hr->{'src'}) or return $PKT_ERROR; $pkt_hr->{'d_obj'} = new NetAddr::IP($pkt_hr->{'dst'}) or return $PKT_ERROR; } else { print STDERR "[-] err packet: strange IPv4 ICMP format\n" if $debug; return $PKT_ERROR; } $pkt_hr->{'proto'} = 'icmp'; if ($pkt_hr->{'itype'} == $ICMP_ECHO_REQUEST or $pkt_hr->{'itype'} == $ICMP_ECHO_REPLY) { ### we expect the ICMP ID and SEQ fields to be populated if ($pkt_str =~ /CODE=(\d+)\s+ID=(\d+)\s+SEQ=(\d+)/) { $pkt_hr->{'icmp_id'} = $1; $pkt_hr->{'icmp_seq'} = $2; } else { return $PKT_ERROR; } } } ### see if we need to ignore this packet based on the ### IGNORE_PROTOCOLS config keyword. return $PKT_IGNORE if &check_ignore_proto($pkt_hr->{'proto'}); if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y' and not $benchmark and not $analyze_mode) { $pkt_hr->{'dshield_str'} = "$pkt_hr->{'src'}\t$pkt_hr->{'itype'}" . "\t$pkt_hr->{'dst'}\t$pkt_hr->{'icode'}\t$pkt_hr->{'proto'}"; } } elsif ($is_proto_num) { if ($is_ipv6) { ### FIXME future } else { ### Mar 15 20:59:04 linux kernel: [810523.812773] DROP IN=eth0 OUT= ### MAC=23:87:fc:c6:24:58:00:21:3f:98:99:78:09:00 SRC=192.168.10.55 ### DST=192.168.10.1 LEN=20 TOS=0x00 PREC=0x00 TTL=59 ID=3052 PROTO=29 if ($pkt_str =~ /SRC=($ipv4_re)\s+DST=($ipv4_re)\s+LEN=(\d+).* TTL=(\d+)\s+ID=(\d+).*PROTO=/x) { ($pkt_hr->{'src'}, $pkt_hr->{'dst'}, $pkt_hr->{'ip_len'}, $pkt_hr->{'ttl'}, $pkt_hr->{'ip_id'}) = ($1,$2,$3,$4,$5); $pkt_hr->{'s_obj'} = new NetAddr::IP($pkt_hr->{'src'}) or return $PKT_ERROR; $pkt_hr->{'d_obj'} = new NetAddr::IP($pkt_hr->{'dst'}) or return $PKT_ERROR; } $pkt_hr->{'proto'} = $proto_num; } } else { ### Sometimes the iptables log entry gets messed up due to ### buffering issues so we write it to the error log. print STDERR "[-] err packet: no regex match.\n" if $debug; return $PKT_ERROR; } if ($restrict_ip) { ### we are looking to analyze packets from a specific IP/subnet if ($pkt_hr->{'is_ipv6'}) { if ($restrict_ip->version() == 6) { return $PKT_IGNORE unless $pkt_hr->{'s_obj'}->within($restrict_ip) or $pkt_hr->{'d_obj'}->within($restrict_ip); } } else { if ($restrict_ip->version() == 4) { return $PKT_IGNORE unless $pkt_hr->{'s_obj'}->within($restrict_ip) or $pkt_hr->{'d_obj'}->within($restrict_ip); } } } return $PKT_SUCCESS; } sub check_ignore_proto() { my $pkt_proto = shift; return 0 unless %ignore_protocols; return 1 if defined $ignore_protocols{$pkt_proto}; return 0; } sub match_sigs() { my $pkt_hr = shift; my $dl = 0; my $is_sig_match = $NO_SIG_MATCH; print STDERR "[+] match_sigs()\n" if $debug and $verbose; ### always run the IP protocol sigs PROTO: for my $proto ($pkt_hr->{'proto'}, 'ip') { next PROTO unless defined $sig_search{$proto}; SRC: for my $src (keys %{$sig_search{$proto}}) { print STDERR "[+] match_sigs() pkt src: $pkt_hr->{'src'} within sig src: $src ?..." if $debug and $verbose; if ($pkt_hr->{'s_obj'}->within($sig_ip_objs{$src})) { print STDERR "yes\n" if $debug and $verbose; } else { print STDERR "no\n" if $debug and $verbose; next SRC; } DST: for my $dst (keys %{$sig_search{$proto}{$src}}) { print STDERR "[+] match_sigs() pkt dst: $pkt_hr->{'dst'} within sig dst: $dst ?..." if $debug and $verbose; if ($pkt_hr->{'d_obj'}->within($sig_ip_objs{$dst})) { print STDERR "yes\n" if $debug and $verbose; } else { print STDERR "no\n" if $debug and $verbose; next DST; } print STDERR " Matched sig IP criteria.\n" if $debug and $verbose; if ($proto eq 'tcp' or $proto eq 'udp' or $proto eq 'udplite') { TYPE: for my $hr (@port_types) { my $sp_type = $hr->{'sp'}; my $dp_type = $hr->{'dp'}; next TYPE unless defined $sig_search{$proto}{$src}{$dst}{$sp_type}; my $sp_hr = $sig_search{$proto}{$src}{$dst}{$sp_type}; SP_S: for my $sp_s (keys %{$sp_hr}) { if ($sp_type eq 'norm') { ### normal match on the starting port value next SP_S unless $pkt_hr->{'sp'} >= $sp_s; } SP_E: for my $sp_e (keys %{$sp_hr->{$sp_s}}) { if ($sp_type eq 'norm') { ### normal match on the ending port value next SP_E unless $pkt_hr->{'sp'} <= $sp_e; } else { ### negative match on the ending port value ### (note the "or" condition) next SP_E unless ($pkt_hr->{'sp'} > $sp_e or $pkt_hr->{'sp'} < $sp_s); } next TYPE unless defined $sp_hr->{$sp_s}->{$sp_e}->{$dp_type}; my $dp_hr = $sp_hr->{$sp_s}->{$sp_e}->{$dp_type}; DP_S: for my $dp_s (keys %$dp_hr) { if ($dp_type eq 'norm') { next DP_S unless $pkt_hr->{'dp'} >= $dp_s; } DP_E: for my $dp_e (keys %{$dp_hr->{$dp_s}}) { if ($dp_type eq 'norm') { next DP_E unless $pkt_hr->{'dp'} <= $dp_e; } else { ### negative match on the ending port value ### (note the "or" condition) next DP_E unless ($pkt_hr->{'dp'} > $dp_e or $pkt_hr->{'dp'} < $dp_s); } ### now we have the set of applicable ### signatures that match the sip/dip ### and sp/dp, so match any Snort ### keywords my ($dl_tmp, $sig_match_tmp) = &match_snort_keywords($pkt_hr, $dp_hr->{$dp_s}->{$dp_e}); print STDERR " match_snort_keywords() ", " return DL: $dl_tmp\n" if $debug; ### return maximal danger level from all ### signature matches $dl = $dl_tmp if $dl_tmp > $dl; $is_sig_match = $SIG_MATCH if $sig_match_tmp == $SIG_MATCH; } } } } } } else { ### now we have the set of applicable icmp ### signatures that match the sip/dip my ($dl_tmp, $sig_match_tmp) = &match_snort_keywords( $pkt_hr, $sig_search{$proto}{$src}{$dst}); print STDERR " match_snort_keywords() ", " return DL: $dl_tmp\n" if $debug; ### return maximal danger level from all signature matches $dl = $dl_tmp if $dl_tmp > $dl; $is_sig_match = $SIG_MATCH if $sig_match_tmp == $SIG_MATCH; } } } } return $dl, $is_sig_match; } sub match_snort_keywords() { my ($pkt_hr, $sigs_ids_hr) = @_; print STDERR "[+] match_snort_keywords()\n" if $debug; my $dl = 0; my $matched_sig = $NO_SIG_MATCH; ### see if all Snort keywords match the packet - the sigs hash has ### been restricted to the appropriate protocol SIG: for my $sid (keys %$sigs_ids_hr) { next SIG unless defined $sigs{$sid}; ### should never happen my $sig_hr = $sigs{$sid}; ### iptables logging messages always include TTL and IP ID ### values (at least for ipv4, see ### linux/net/ipv4/netfilter/ipt_LOG.c) my $dl_tmp = 0; my ($rv, $sig_match_rv) = &match_snort_ip_keywords($pkt_hr, $sig_hr); if ($sig_match_rv == $SIG_MATCH) { $matched_sig = $SIG_MATCH; $dl_tmp = $rv; if ($rv == 0) { next SIG; ### ignore signature } } elsif ($sig_match_rv == $NO_SIG_MATCH) { ### there were network-layer keywords that did not match next SIG unless $rv; ### else there were no network-layer keywords so continue on } $dl = $dl_tmp if $dl_tmp > $dl; if ($debug and $debug_sid == $sid) { print STDERR "[+] SID: $sid, passed match_snort_ip_keywords() ", "tests.\n"; } if ($sig_hr->{'proto'} eq 'tcp') { ($rv, $sig_match_rv) = &match_snort_tcp_keywords($pkt_hr, $sig_hr); if ($sig_match_rv == $SIG_MATCH) { $matched_sig = $SIG_MATCH; next SIG if $rv == 0; $dl = $rv if $rv > $dl; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'} = 0 unless defined $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'}++ unless defined $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tcp'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tcp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'dp'} = $pkt_hr->{'dp'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tcp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'flags'} = $pkt_hr->{'flags'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tcp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'pkts'}++; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tcp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'is_fwsnort'} = 0; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tcp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'time'} = time(); $sig_sources{$sid}{$pkt_hr->{'src'}} = ''; $top_sigs{$sid}++; $top_sig_counts{$pkt_hr->{'src'}}++; } } elsif ($sig_hr->{'proto'} eq 'udp') { ($rv, $sig_match_rv) = &match_snort_udp_keywords($pkt_hr, $sig_hr); if ($sig_match_rv == $SIG_MATCH) { $matched_sig = $SIG_MATCH; next SIG if $rv == 0; $dl = $rv if $rv > $dl; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'} = 0 unless defined $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'}++ unless defined $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'udp'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'udp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'dp'} = $pkt_hr->{'dp'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'udp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'pkts'}++; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'udp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'is_fwsnort'} = 0; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'udp'} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'time'} = time(); $sig_sources{$sid}{$pkt_hr->{'src'}} = 0; ### not fwsnort $top_sigs{$sid}++; $top_sig_counts{$pkt_hr->{'src'}}++; } } elsif ($sig_hr->{'proto'} eq 'icmp') { ($rv, $sig_match_rv) = &match_snort_icmp_keywords($pkt_hr, $sig_hr); if ($sig_match_rv == $SIG_MATCH) { $matched_sig = $SIG_MATCH; next SIG if $rv == 0; $dl = $rv if $rv > $dl; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'} = 0 unless defined $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'}++ unless defined $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'icmp'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'icmp'}{'sid'} {$sid}{$pkt_hr->{'chain'}}{'pkts'}++; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'icmp'}{'sid'} {$sid}{$pkt_hr->{'chain'}}{'is_fwsnort'} = 0; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'icmp'}{'sid'} {$sid}{$pkt_hr->{'chain'}}{'time'} = time(); $sig_sources{$sid}{$pkt_hr->{'src'}} = 0; ### not fwsnort $top_sigs{$sid}++; $top_sig_counts{$pkt_hr->{'src'}}++; } } elsif ($sig_hr->{'proto'} eq 'icmp6') { ### FIXME icmp6 specifc Snort matches? } } return $dl, $matched_sig; } sub match_snort_tcp_keywords() { my ($pkt_hr, $sig_hr) = @_; if ($debug and $debug_sid == $sig_hr->{'sid'}) { print STDERR "[+] SID: $sig_hr->{'sid'} match_snort_tcp_keywords()\n"; } if (defined $sig_hr->{'flags'}) { unless ($pkt_hr->{'flags'} eq $sig_hr->{'flags'}) { if ($debug and $debug_sid == $sig_hr->{'sid'}) { print STDERR "[-] SID: $sig_hr->{'sid'} ", "$pkt_hr->{'flags'} != $sig_hr->{'flags'}\n"; } return 0, $NO_SIG_MATCH; } } my $header_len = $IP_HEADER_LEN + $TCP_HEADER_LEN; if ($pkt_hr->{'flags'} =~ m|SYN|) { ### extend the header length to compensate for TCP options $header_len += $TCP_MAX_OPTS_LEN; } if (defined $sig_hr->{'dsize'} and defined $sig_hr->{'dsize_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range( ($pkt_hr->{'ip_len'}-$header_len), 'dsize', $sig_hr); } if (defined $sig_hr->{'psad_dsize'} and defined $sig_hr->{'psad_dsize_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range( ($pkt_hr->{'ip_len'}-$header_len), 'psad_dsize', $sig_hr); } if (defined $sig_hr->{'window'} and defined $sig_hr->{'window_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'win'}, 'window', $sig_hr); } if (defined $sig_hr->{'seq'} and defined $sig_hr->{'seq_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'tcp_seq'}, 'seq', $sig_hr); } if (defined $sig_hr->{'ack'} and defined $sig_hr->{'ack_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'tcp_ack'}, 'ack', $sig_hr); } ### matched the signature if ($debug) { print STDERR "[+] packet matched tcp keywords for sid: ", "$sig_hr->{'sid'} (psad_id: $sig_hr->{'psad_id'})\n", qq| "$sig_hr->{'msg'}"\n|; } return &assign_sid_dl($sig_hr->{'sid'}, $sig_hr->{'dl'}), $SIG_MATCH; } sub match_snort_udp_keywords() { my ($pkt_hr, $sig_hr) = @_; if (defined $sig_hr->{'dsize'} and defined $sig_hr->{'dsize_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range( ($pkt_hr->{'udp_len'}-$UDP_HEADER_LEN), 'dsize', $sig_hr); } if (defined $sig_hr->{'psad_dsize'} and defined $sig_hr->{'psad_dsize_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range( ($pkt_hr->{'udp_len'}-$UDP_HEADER_LEN), 'psad_dsize', $sig_hr); } ### matched the signature if ($debug) { print STDERR "[+] packet matched udp keywords for sid: ", "$sig_hr->{'sid'} (psad_id: $sig_hr->{'psad_id'})\n", qq| "$sig_hr->{'msg'}"\n|; } return &assign_sid_dl($sig_hr->{'sid'}, $sig_hr->{'dl'}), $SIG_MATCH; } sub match_snort_icmp_keywords() { my ($pkt_hr, $sig_hr) = @_; if (defined $sig_hr->{'dsize'} and defined $sig_hr->{'dsize_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range( ($pkt_hr->{'ip_len'}-$IP_HEADER_LEN-$ICMP_HEADER_LEN), 'dsize', $sig_hr); } if (defined $sig_hr->{'psad_dsize'} and defined $sig_hr->{'psad_dsize_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range( ($pkt_hr->{'ip_len'}-$IP_HEADER_LEN-$ICMP_HEADER_LEN), 'psad_dsize', $sig_hr); } if (defined $sig_hr->{'itype'} and defined $sig_hr->{'itype_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'itype'}, 'itype', $sig_hr); } if (defined $sig_hr->{'icode'} and defined $sig_hr->{'icode_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'icode'}, 'icode', $sig_hr); } if (defined $sig_hr->{'icmp_seq'} and defined $sig_hr->{'icmp_seq_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'icmp_seq'}, 'icmp_seq', $sig_hr); } if (defined $sig_hr->{'icmp_id'} and defined $sig_hr->{'icmp_id_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'icmp_id'}, 'icmp_id', $sig_hr); } ### matched the signature if ($debug) { print STDERR "[+] packet matched icmp keywords for sid: ", "$sig_hr->{'sid'} (psad_id: $sig_hr->{'psad_id'})\n", qq| "$sig_hr->{'msg'}"\n|; } return &assign_sid_dl($sig_hr->{'sid'}, $sig_hr->{'dl'}), $SIG_MATCH; } sub match_snort_ip_keywords() { my ($pkt_hr, $sig_hr) = @_; if ($pkt_hr->{'is_ipv6'}) { ### we need to build IPv6 signature keywords return 1, $NO_SIG_MATCH; } if (defined $sig_hr->{'ttl'} and defined $sig_hr->{'ttl_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'ttl'}, 'ttl', $sig_hr); } if (defined $sig_hr->{'id'} and defined $sig_hr->{'id_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'ip_id'}, 'id', $sig_hr); } if (defined $sig_hr->{'psad_ip_len'} and defined $sig_hr->{'psad_ip_len_s'}) { return 0, $NO_SIG_MATCH unless &check_sig_int_range($pkt_hr->{'ip_len'}, 'psad_ip_len', $sig_hr); } ### to handle the ip_proto keyword parse_NF_pkt_str() would have to be ### modified to handle packets besides TCP, UDP, and ICMP. return 0, $NO_SIG_MATCH if defined $sig_hr->{'ip_proto'}; ### handle the sameip keyword if (defined $sig_hr->{'sameip'} and $sig_hr->{'sameip'}) { return 0, $NO_SIG_MATCH if $pkt_hr->{'intf'} eq 'lo'; return 0, $NO_SIG_MATCH unless $pkt_hr->{'src'} eq $pkt_hr->{'dst'}; } return 0, $NO_SIG_MATCH unless &check_sig_ipopts($pkt_hr->{'ip_opts'}, 'ipopts', $sig_hr); if ($sig_hr->{'proto'} eq 'ip') { ### signature match if ($debug) { print STDERR "[+] packet matched ip keywords for sid: ", "$sig_hr->{'sid'} (psad_id: $sig_hr->{'psad_id'})\n", qq| "$sig_hr->{'msg'}"\n|; } $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'ip'}{'sid'} {$sig_hr->{'sid'}}{$pkt_hr->{'chain'}}{'pkts'}++; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'ip'}{'sid'} {$sig_hr->{'sid'}}{$pkt_hr->{'chain'}}{'is_fwsnort'} = 0; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'ip'}{'sid'} {$sig_hr->{'sid'}}{$pkt_hr->{'chain'}}{'time'} = time(); $sig_sources{$sig_hr->{'sid'}}{$pkt_hr->{'src'}} = 0; ### not fwsnort $top_sigs{$sig_hr->{'sid'}}++; $top_sig_counts{$pkt_hr->{'src'}}++; return &assign_sid_dl($sig_hr->{'sid'}, $sig_hr->{'dl'}), $SIG_MATCH; } return 1, $NO_SIG_MATCH; } sub check_sig_int_range() { my ($pkt_val, $keyword, $sig_hr) = @_; $pkt_val = 0 if $pkt_val < 0; if ($sig_hr->{"${keyword}_neg"}) { if ($pkt_val <= $sig_hr->{"${keyword}_e"} and $pkt_val >= $sig_hr->{"${keyword}_s"}) { if ($debug) { if ($verbose or $debug_sid == $sig_hr->{'sid'}) { print STDERR "[-] SID: $sig_hr->{'sid'} failed ", "$keyword test, $pkt_val <= ", qq|$sig_hr->{"${keyword}_e"} and $pkt_val |, qq|>= $sig_hr->{"${keyword}_s"}\n|; } } return 0; } } else { ### normal match if ($pkt_val < $sig_hr->{"${keyword}_s"}) { if ($debug) { if ($verbose or $debug_sid == $sig_hr->{'sid'}) { print STDERR "[-] SID: $sig_hr->{'sid'} failed ", "$keyword test, $pkt_val < ", qq|$sig_hr->{"${keyword}_s"} (range start)\n|; } } return 0; } if ($pkt_val > $sig_hr->{"${keyword}_e"}) { if ($debug) { if ($verbose or $debug_sid == $sig_hr->{'sid'}) { print STDERR "[-] SID: $sig_hr->{'sid'} failed ", "$keyword test, $pkt_val > ", qq|$sig_hr->{"${keyword}_e"} (range end)\n|; } } return 0; } } return 1; } sub check_sig_ipopts() { my ($pkt_val, $keyword, $sig_hr) = @_; return 1 unless defined $sig_hr->{$keyword}; return 0 unless $pkt_val; return 1 if $sig_hr->{$keyword} eq 'any'; my $pkt_opts_hr = &parse_ip_options($pkt_val); return 0 unless defined $pkt_opts_hr->{$sig_hr->{$keyword}}; return 1; } sub check_ignore_port() { my ($port, $proto) = @_; return 0 unless defined $ignore_ports{$proto}; return &match_port(\%{$ignore_ports{$proto}}, $port); } sub match_port() { my ($hr, $port) = @_; if (defined $hr->{'port'}) { return 1 if defined $hr->{'port'}->{$port}; } if (defined $hr->{'range'}) { for my $low_port (keys %{$hr->{'range'}}) { my $high_port = $hr->{'range'}->{$low_port}; return 1 if ($port >= $low_port and $port <= $high_port); } } return 0; } sub p0f() { my $pkt_hr = shift; if ($pkt_hr->{'is_ipv6'}) { &p0f_ipv6($pkt_hr); } else { &p0f_ipv4($pkt_hr); } return; } sub p0f_ipv6() { my $pkt_hr = shift; return; } sub p0f_ipv4() { my $pkt_hr = shift; print STDERR "[+] p0f_ipv4(): $pkt_hr->{'src'} len: $pkt_hr->{'ip_len'}, ", "frag_bit: $pkt_hr->{'frag_bit'}, ttl: $pkt_hr->{'ttl'}, ", "win: $pkt_hr->{'win'}\n" if $debug; # p0f Fingerprint entry format: # # wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details # # wwww - window size (can be *, %nnn, Snn or Tnn). The special values # "S" and "T" which are a multiple of MSS or a multiple of MTU # respectively. # ttt - initial TTL # D - don't fragment bit (0 - not set, 1 - set) # ss - overall SYN packet size # OOO - option value and order specification (see below) # OS - OS genre (Linux, Solaris, Windows) # Version - OS Version (2.0.27 on x86, etc) # Subtype - OS subtype or patchlevel (SP3, lo0) # details - Generic OS details # ### S4:64:1:60:M*,S,T,N,W7: Linux:2.6:8:Linux 2.6.8 and newer (?) my $options_ar = &parse_tcp_options($pkt_hr->{'src'}, $pkt_hr->{'tcp_opts'}); unless ($options_ar) { print STDERR "[-] Could not fingerprint remote OS.\n" if $debug; return; } my $matched_os = 0; ### try to match SYN packet length LEN: for my $sig_len (keys %p0f_ipv4_sigs) { my $matched_len = 0; if ($sig_len eq '*') { ### len can be wildcarded in pf.os $matched_len = 1; } elsif ($sig_len =~ /^\%(\d+)/) { if (($pkt_hr->{'ip_len'} % $1) == 0) { $matched_len = 1; } } elsif ($pkt_hr->{'ip_len'} == $sig_len) { $matched_len = 1; } next LEN unless $matched_len; ### try to match fragmentation bit FRAG: for my $test_frag_bit ($pkt_hr->{'frag_bit'}, '*') { ### don't need "%nnn" check next FRAG unless defined $p0f_ipv4_sigs{$sig_len}{$test_frag_bit}; ### find out for which p0f sigs the TTL is within range TTL: for my $sig_ttl (keys %{$p0f_ipv4_sigs{$sig_len}{$test_frag_bit}}) { unless ($pkt_hr->{'ttl'} > $sig_ttl - $config{'MAX_HOPS'} and $pkt_hr->{'ttl'} <= $sig_ttl) { next TTL; } ### match tcp window size WIN: for my $sig_win_size (keys %{$p0f_ipv4_sigs{$sig_len}{$test_frag_bit}{$sig_ttl}}) { my $matched_win_size = 0; if ($sig_win_size eq '*') { $matched_win_size = 1; } elsif ($sig_win_size =~ /^\%(\d+)/) { if (($pkt_hr->{'win'} % $1) == 0) { $matched_win_size = 1; } } elsif ($sig_win_size =~ /^S(\d+)/) { ### window size must be a multiple of maximum ### seqment size my $multiple = $1; for my $opt_hr (@$options_ar) { if (defined $opt_hr->{$tcp_p0f_opt_types{'M'}}) { my $mss_val = $opt_hr->{$tcp_p0f_opt_types{'M'}}; if ($pkt_hr->{'win'} == $mss_val * $multiple) { $matched_win_size = 1; } } last; } } elsif ($sig_win_size == $pkt_hr->{'win'}) { $matched_win_size = 1; } next WIN unless $matched_win_size; TCPOPTS: for my $sig_opts (keys %{$p0f_ipv4_sigs{$sig_len} {$test_frag_bit}{$sig_ttl}{$sig_win_size}}) { my @sig_opts = split /\,/, $sig_opts; for (my $i=0; $i<=$#sig_opts; $i++) { ### tcp option order is important. Check to see if ### the option order in the packet matches the order we ### expect to see in the signature if ($sig_opts[$i] =~ /^([NMWST])/) { my $sig_letter = $1; unless (defined $options_ar->[$i]-> {$tcp_p0f_opt_types{$sig_letter}}) { next TCPOPTS; ### could not match tcp option order } ### MSS, window scale, and timestamp have ### specific signatures requirements on values if ($sig_letter eq 'M') { if ($sig_opts[$i] =~ /M(\d+)/) { my $sig_mss_val = $1; next TCPOPTS unless $options_ar->[$i]-> {$tcp_p0f_opt_types{$sig_letter}} == $sig_mss_val; } elsif ($sig_opts[$i] =~ /M\%(\d+)/) { my $sig_mss_mod_val = $1; next TCPOPTS unless (($options_ar->[$i]-> {$tcp_p0f_opt_types{$sig_letter}} % $sig_mss_mod_val) == 0); } ### else it is "M*" which always matches } elsif ($sig_letter eq 'W') { if ($sig_opts[$i] =~ /W(\d+)/) { my $sig_win_val = $1; next TCPOPTS unless $options_ar->[$i]-> {$tcp_p0f_opt_types{$sig_letter}} == $sig_win_val; } elsif ($sig_opts[$i] =~ /W\%(\d+)/) { my $sig_win_mod_val = $1; next TCPOPTS unless (($options_ar->[$i]-> {$tcp_p0f_opt_types{$sig_letter}} % $sig_win_mod_val) == 0); } ### else it is "W*" which always matches } elsif ($sig_letter eq 'T') { if ($sig_opts[$i] =~ /T0/) { next TCPOPTS unless $options_ar->[$i]-> {$tcp_p0f_opt_types{$sig_letter}} == 0; } ### else it is just "T" which matches } } } OS: for my $os (keys %{$p0f_ipv4_sigs{$sig_len} {$test_frag_bit}{$sig_ttl}{$sig_win_size} {$sig_opts}}) { my $sig = $p0f_ipv4_sigs{$sig_len} {$test_frag_bit}{$sig_ttl}{$sig_win_size} {$sig_opts}{$os}; print STDERR "[+] os: $os, $sig\n" if $debug; $matched_os = 1; $p0f{$pkt_hr->{'src'}}{$os} = ''; } } } } } } if ($debug and not $matched_os) { print STDERR " Could not match $pkt_hr->{'src'} against any p0f signature.\n"; } return; } sub parse_tcp_options() { my ($src, $tcp_options) = @_; my @opts = (); my @hex_nums = (); my $debug_str = ''; unless ($tcp_options) { print STDERR 'no tcp options' if $debug; return []; } if (length($tcp_options) % 2 != 0) { ### make sure length is a multiple of two print STDERR 'tcp options length not a multiple of two.' if $debug; return []; } ### $tcp_options is a hex string like "020405B401010402" from the iptables ### log message my @chars = split //, $tcp_options; for (my $i=0; $i <= $#chars; $i += 2) { my $str = $chars[$i] . $chars[$i+1]; push @hex_nums, $str; } my $max_parse_attempts = $#chars; my $parse_ctr = 0; OPT: for (my $opt_kind=0; $opt_kind <= $#hex_nums;) { $parse_ctr++; if ($parse_ctr > $max_parse_attempts) { print STDERR " p0f() parse_ctr $parse_ctr > ", "max_parse_attempts $max_parse_attempts\n" if $debug; return []; } last OPT unless defined $hex_nums[$opt_kind+1]; my $is_nop = 0; my $len = hex($hex_nums[$opt_kind+1]); if (hex($hex_nums[$opt_kind]) == $tcp_nop_type) { $debug_str .= 'NOP, ' if $debug; push @opts, {$tcp_nop_type => ''}; $is_nop = 1; } elsif (hex($hex_nums[$opt_kind]) == $tcp_mss_type) { ### MSS my $mss_hex = ''; for (my $i=$opt_kind+2; $i < ($opt_kind+$len); $i++) { $mss_hex .= $hex_nums[$i]; } my $mss = hex($mss_hex); push @opts, {$tcp_mss_type => $mss}; $debug_str .= 'MSS: ' . hex($mss_hex) . ', ' if $debug; } elsif (hex($hex_nums[$opt_kind]) == $tcp_win_scale_type) { my $window_scale_hex = ''; for (my $i=$opt_kind+2; $i < ($opt_kind+$len); $i++) { $window_scale_hex .= $hex_nums[$i]; } my $win_scale = hex($window_scale_hex); push @opts, {$tcp_win_scale_type => $win_scale}; $debug_str .= 'Win Scale: ' . hex($window_scale_hex) . ', ' if $debug; } elsif (hex($hex_nums[$opt_kind]) == $tcp_sack_type) { push @opts, {$tcp_sack_type => ''}; $debug_str .= 'SACK, ' if $debug; } elsif (hex($hex_nums[$opt_kind]) == $tcp_timestamp_type) { my $timestamp_hex = ''; for (my $i=$opt_kind+2; $i < ($opt_kind+$len) - 4; $i++) { $timestamp_hex .= $hex_nums[$i]; } my $timestamp = hex($timestamp_hex); push @opts, {$tcp_timestamp_type => $timestamp}; $debug_str .= 'Timestamp: ' . hex($timestamp_hex) . ', ' if $debug; } elsif (hex($hex_nums[$opt_kind]) == 0) { ### End of option list last OPT; } if ($is_nop) { $opt_kind += 1; } else { if ($len == 0 or $len == 1) { ### this should never happen; it indicates a broken TCP stack ### or maliciously constructed options since the len field is ### not large enough to accomodate the TLV encoding my $msg = "broken $len-byte len field within TCP options " . "string: $tcp_options from source IP: $src"; print STDERR " $msg\n" if $debug; &sys_log($msg); return []; } ### get to the next option-kind field $opt_kind += $len; } } if ($debug) { $debug_str =~ s/\,$//; print STDERR "[+] $debug_str\n" if $debug; } return \@opts; } sub parse_ip_options() { my $ip_opts_str = shift; my %ip_opts = (); my @hex_nums = (); if (length($ip_opts_str) % 2 != 0) { ### make sure length is a multiple of two print STDERR 'IP options length not a multiple of two.' if $debug; return ''; } print STDERR "[+] parse_ip_options(): matched " if $debug; push @hex_nums, $1 while $ip_opts_str =~ m|(.{2})|g; OPT: for (my $i=0; $i <= $#hex_nums; $i++) { my $val = hex($hex_nums[$i]); for my $rfc_opt_val (keys %ip_options) { next unless $val == $rfc_opt_val; if ($ip_options{$rfc_opt_val}{'len'} ne '-1') { $i += $ip_options{$rfc_opt_val}{'len'} unless $ip_options{$rfc_opt_val}{'len'} == 1; } else { return \%ip_opts if ($i+1 > $#hex_nums); ### subtract out the option and length fields my $pkt_opt_len = hex($hex_nums[$i+1]) - 2; if ($i + $pkt_opt_len > $#hex_nums) { ### this should not happen unless the IP packet ### was truncated (i.e. the length argument for ### this option is past the IP options portion ### of the header). return \%ip_opts; } $i += $pkt_opt_len; } if ($debug) { printf STDERR ("$ip_options{$rfc_opt_val}{'sig_keyword'} " . "(0x%x) ", $val) unless defined $ip_opts{$ip_options {$rfc_opt_val}{'sig_keyword'}}; } $ip_opts{$ip_options{$rfc_opt_val}{'sig_keyword'}} = ''; } } print STDERR "\n" if $debug; return \%ip_opts; } sub posf() { my $pkt_hr = shift; if ($pkt_hr->{'is_ipv6'}) { &posf_ipv6($pkt_hr); } else { &posf_ipv4($pkt_hr); } return; } sub posf_ipv6() { my $pkt_hr = shift; return; } sub posf_ipv4() { my $pkt_hr = shift; my $src = $pkt_hr->{'src'}; my $len = $pkt_hr->{'ip_len'}; my $tos = $pkt_hr->{'tos'}; my $ttl = $pkt_hr->{'ttl'}; my $id = $pkt_hr->{'ip_id'}; my $win = $pkt_hr->{'win'}; my $min_ttl; my $max_ttl; my $id_str; $posf{$src}{'len'}{$len}++; $posf{$src}{'tos'}{$tos}++; $posf{$src}{'ttl'}{$ttl}++; $posf{$src}{'win'}{$win}++; $posf{$src}{'ctr'}++; push @{$posf{$src}{'id'}}, $id; ### need to maintain ordering print STDERR "[+] posf(): $src LEN: $len, TOS: $tos, TTL: $ttl, ", "ID: $id, WIN: $win\n" if $debug; $id_str = &id_incr(\@{$posf{$src}{'id'}}); for my $os (keys %posf_sigs) { if ($posf{$src}{'ctr'} >= $posf_sigs{$os}{'numpkts'}) { ($min_ttl, $max_ttl) = &ttl_range($posf{$src}{'ttl'}); if (defined $posf{$src}{'win'}{$posf_sigs{$os}{'win'}} # and defined $posf{$src}{'tos'}{$posf_sigs{$os}{'tos'}} and defined $posf{$src}{'len'}{$posf_sigs{$os}{'len'}} ### ttl's only decrease and ($min_ttl > ($posf_sigs{$os}{'ttl'}-$max_hops)) and ($max_ttl <= $posf_sigs{$os}{'ttl'}) and $id_str eq $posf_sigs{$os}{'id'}) { $posf{$src}{'guess'} = $os; print STDERR "[+] posf(): matched OS: $os\n" if $debug; return; } } } return; } sub id_incr() { my $aref = shift; for (my $i=0; $i<$#$aref; $i++) { return 'RANDOM' unless ($aref->[$i] < $aref->[$i+1] and ($aref->[$i+1] - $aref->[$i]) < 1000); } return 'SMALLINCR'; } sub ttl_range() { my $hr = shift; my $min_ttl = 256; my $max_ttl = 0; for my $ttl (keys %$hr) { $min_ttl = $ttl if $ttl < $min_ttl; $max_ttl = $ttl if $ttl > $max_ttl; } return $min_ttl, $max_ttl; } sub assign_sid_dl() { my ($sid, $dl) = @_; ### see if /etc/psad/snort_rule_dl assigns a DL (may be ### zero). if (defined $snort_rule_dl{$sid}) { $dl = $snort_rule_dl{$sid}; } print STDERR "[+] assign_sid_dl(): snort_rule_dl ", "assigning SID $sid a danger level of ", "$dl\n" if $debug; return $dl; } sub add_fwsnort_sid() { my $pkt_hr = shift; my $sid = $pkt_hr->{'fwsnort_sid'}; if (defined $fwsnort_sigs{$sid}) { ### see if we need to ignore this signature match my $dl = &assign_sid_dl($sid, 2); unless ($dl) { print "[+] add_fwsnort_sid(): ignoring fwsnort signature ", "match for SID: $sid (DL=0)\n" if $debug; return 0, $SIG_MATCH; } $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'} = 0 unless defined $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{'tot_protocols'}++ unless defined $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{$pkt_hr->{'proto'}}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{$pkt_hr->{'proto'}} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'pkts'}++; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{$pkt_hr->{'proto'}} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'is_fwsnort'} = 1; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{$pkt_hr->{'proto'}} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'time'} = time(); $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{$pkt_hr->{'proto'}} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'fwsnort_rnum'} = $pkt_hr->{'fwsnort_rnum'}; $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{$pkt_hr->{'proto'}} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'fwsnort_estab'} = $pkt_hr->{'fwsnort_estab'}; $sig_sources{$sid}{$pkt_hr->{'src'}} = 1; ### is an fwsnort sid $top_sigs{$sid}++; $top_sig_counts{$pkt_hr->{'src'}}++; if ($pkt_hr->{'proto'} eq 'tcp' or $pkt_hr->{'proto'} eq 'udp') { $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{$pkt_hr->{'proto'}} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'dp'} = $pkt_hr->{'dp'}; if ($pkt_hr->{'proto'} eq 'tcp') { $scan{$pkt_hr->{'src'}}{$pkt_hr->{'dst'}}{$pkt_hr->{'proto'}} {'sid'}{$sid}{$pkt_hr->{'chain'}}{'flags'} = $pkt_hr->{'flags'}; } } return $dl, $SIG_MATCH; } else { print "[-] Found sid: $sid in packet, but no ", "corresponding fwsnort rule.\n" if $debug; } return 0, $NO_SIG_MATCH; } sub delete_old_scans() { my $current_time = time(); print STDERR "[+] delete_old_scans()\n" if $debug; ### see if we need to timeout any old scans for my $src (keys %scan) { for my $dst (keys %{$scan{$src}}) { next unless defined $scan{$src}{$dst}{'s_time'}; if (($current_time - $scan{$src}{$dst}{'s_time'}) >= $config{'SCAN_TIMEOUT'}) { print STDERR " delete old scan $src -> $dst\n" if $debug; delete $scan{$src}{$dst}; if ($config{'MAX_SCAN_IP_PAIRS'} > 0) { $scan_ip_pairs-- if $scan_ip_pairs > 0; } } } } return; } sub dshield_email_log() { ### dshield alert interval is in hours. Check to see if there are more ### than 10,000 lines of log data (and if the last alert was sent more than ### two hours later than the previous alert), and if yes send the alert ### email. if (@dshield_data and ((time() - $last_dshield_alert) >= $dshield_alert_interval) or (($#dshield_data > 10000) and ((time() - $last_dshield_alert) >= 2*3600))) { my $dshield_version = $version; $dshield_version =~ s/^(\d+\.\d+)\.\d+/$1/; $dshield_version =~ s/-pre\d+//; my $subject = "FORMAT DSHIELD USERID $config{'DSHIELD_USER_ID'} " . "TZ $timezone psad Version $dshield_version"; if ($config{'DSHIELD_USER_EMAIL'} eq 'NONE') { open MAIL, qq(| $cmds{'mail'} -s "$subject" ) . $config{'DSHIELD_ALERT_EMAIL'} or die '[*] Could not send ', 'dshield alert email.'; ### save this email to disk also open DSSAVE, "> $config{'DSHIELD_EMAIL_FILE'}" or die '[*] ', "Could not open $config{'DSHIELD_EMAIL_FILE'}: $!"; if ($config{'DSHIELD_DL_THRESHOLD'} > 0) { for my $line (@dshield_data) { if ($line =~ /^.*?($ipv4_re)/) { my $src = $1; if (defined $scan_dl{$src} and ($scan_dl{$src} >= $config{'DSHIELD_DL_THRESHOLD'})) { print MAIL $line; print DSSAVE $line; } } } } else { print MAIL for @dshield_data; print DSSAVE for @dshield_data; } close MAIL; close DSSAVE; } else { open MAIL, "| $cmds{'sendmail'} -oi -t" or die '[*] Could not ', 'send dshield alert email.'; ### save this email to disk also open DSSAVE, "> $config{'DSHIELD_EMAIL_FILE'}" or die '[*] ', "Could not open $config{'DSHIELD_EMAIL_FILE'}: $!"; print MAIL "From: $config{'DSHIELD_USER_EMAIL'}\n", "To: $config{'DSHIELD_ALERT_EMAIL'}\n", "Subject: $subject\n"; print DSSAVE "From: $config{'DSHIELD_USER_EMAIL'}\n", "To: $config{'DSHIELD_ALERT_EMAIL'}\n", "Subject: $subject\n"; if ($config{'DSHIELD_DL_THRESHOLD'} > 0) { for my $line (@dshield_data) { if ($line =~ /^.*?($ipv4_re)/) { my $src = $1; if (defined $scan_dl{$src} and ($scan_dl{$src} >= $config{'DSHIELD_DL_THRESHOLD'})) { print MAIL $line; print DSSAVE $line; } } } } else { print MAIL for @dshield_data; print DSSAVE for @dshield_data; } close MAIL; close DSSAVE; } &sys_log("sent $#dshield_data lines of log data to " . $config{'DSHIELD_ALERT_EMAIL'}); ### store the current time $last_dshield_alert = time(); ### increment stats counters $dshield_email_ctr++; $dshield_lines_ctr += $#dshield_data; ### clear the dshield data array so we don't re-send ### any old data. @dshield_data = (); ### Write Dshield stats to disk &write_dshield_stats(); } return; } sub check_icmp_type() { my ($proto, $valid_types_hr, $type, $code) = @_; print STDERR " check_icmp_type(type: $type, code: $code)\n" if $debug; if (not defined $valid_types_hr->{$type}) { print STDERR " bad $proto type\n" if $debug; return $BAD_ICMP_TYPE; } elsif (not defined $valid_types_hr->{$type}->{'codes'}->{$code}) { print STDERR " bad $proto code\n" if $debug; return $BAD_ICMP_CODE; } print STDERR " valid $proto type/code\n" if $debug; return 0; } sub import_perl_modules() { my $mod_paths_ar = &get_mod_paths(); if ($#$mod_paths_ar > -1) { ### /usr/lib/psad/ exists push @$mod_paths_ar, @INC; splice @INC, 0, $#$mod_paths_ar+1, @$mod_paths_ar; } if ($debug) { print STDERR "[+] import_perl_modules(): The \@INC array:\n"; print STDERR "$_\n" for @INC; } require IPTables::ChainMgr; require NetAddr::IP; require Date::Calc; require Unix::Syslog; require Cwd; require Storable if $store_file; Date::Calc->import(qw(Timezone This_Year Decode_Month Today Date_to_Time This_Year Mktime Localtime)); Unix::Syslog->import(qw(:subs :macros)); Storable->import(qw(retrieve store)) if $store_file; $imported_syslog_module = 1; return; } sub get_mod_paths() { my @paths = (); $config{'PSAD_LIBS_DIR'} = $lib_dir if $lib_dir; unless (-d $config{'PSAD_LIBS_DIR'}) { my $dir_tmp = $config{'PSAD_LIBS_DIR'}; $dir_tmp =~ s|lib/|lib64/|; if (-d $dir_tmp) { $config{'PSAD_LIBS_DIR'} = $dir_tmp; } else { return []; } } opendir D, $config{'PSAD_LIBS_DIR'} or die "[*] Could not open $config{'PSAD_LIBS_DIR'}: $!"; my @dirs = readdir D; closedir D; push @paths, $config{'PSAD_LIBS_DIR'}; for my $dir (@dirs) { ### get directories like "/usr/lib/psad/x86_64-linux" next unless -d "$config{'PSAD_LIBS_DIR'}/$dir"; push @paths, "$config{'PSAD_LIBS_DIR'}/$dir" if $dir =~ m|linux| or $dir =~ m|thread| or (-d "$config{'PSAD_LIBS_DIR'}/$dir/auto"); } return \@paths; } sub psad_init() { %config = (); %cmds = (); ### set umask to -rw------- umask 0077; ### turn off buffering $| = 1; $no_syslog_alerts = 1 if $analyze_mode or $status_mode or $test_mode; $no_email_alerts = 1 if $test_mode; $debug = 1 if $test_mode; ### import any override config files first &import_override_configs() if $override_config_str; ### import psad.conf &import_config($config_file); ### import FW_MSG_SEARCH strings &import_fw_search($config_file); ### expand any embedded vars within config values &expand_vars(); ### pid file hash %pidfiles = ( 'psadwatchd' => $config{'PSADWATCHD_PID_FILE'}, 'psad' => $config{'PSAD_PID_FILE'}, 'kmsgsd' => $config{'KMSGSD_PID_FILE'}, ); ### dump configuration to STDOUT if ($dump_conf or $dump_ipt_policy) { my $rv = 0; my $rv_tmp = 0; $rv = &dump_conf() if $dump_conf; $rv_tmp = &dump_ipt_policy() if $dump_ipt_policy; $rv += $rv_tmp if $rv_tmp != 0; exit $rv; } &dump_conf() if $test_mode; ### make sure all necessary configuration variables ### are defined &required_vars(); ### store the psad command line. $cmdline_file = $config{'PSAD_CMDLINE_FILE'}; ### make sure the values in the config file make sense &validate_config(); ### setup the appropriate iptables data file depending on whether ### SYSLOG_DAEMON is set to ulogd, or if ENABLE_SYSLOG_FILE is set ### to 'Y' unless ($fw_data_file) { if ($config{'SYSLOG_DAEMON'} =~ /ulog/i) { $fw_data_file = $config{'ULOG_DATA_FILE'}; } elsif ($config{'ENABLE_SYSLOG_FILE'} eq 'Y') { $fw_data_file = $config{'IPT_SYSLOG_FILE'}; } else { $fw_data_file = $config{'FW_DATA_FILE'}; } } ### check to make sure the commands specified in the config section ### are in the right place, and attempt to correct automatically if not. ### (wget is only needed in --sig-update mode) &check_commands({'wget' => ''}); ### import psad perl modules &import_perl_modules(); ### download latest signatures from ### http://www.cipherdyne.org/psad/signatures exit &download_signatures() if $download_sigs; ### set some config variables based on command line input &handle_cmdline(); ### build iptables block config hash out of IPT_AUTO_CHAIN keywords ### (we don't check ENABLE_AUTO_IDS here since someone may have turned ### it off but still want to run --Status checks or use --Flush). &build_ipt_config() unless $syslog_server; ### The --Kill command line switch was given. exit &stop_psad() if $kill; ### The --HUP command line switch was given. exit &hup() if $hup; ### The --USR1 command line switch was given. exit &usr1() if $usr1; ### The --Flush command line switch was given. exit &sockwrite_flush_auto_rules() if $flush_fw; ### the --Restart command line switch was given exit &restart() if $restart; ### list any existing iptables IPT_AUTO_CHAIN chains exit &ipt_list_auto_chains() if $fw_list_auto; ### add an IP/network to the psad auto blocking chains via the ### domain socket (note that &sockwrite_add_ipt_block_ip() calls ### &import_auto_dl() to make sure we don't add an IP that should ### be ignored). exit &sockwrite_add_ipt_block_ip() if $fw_block_ip; ### delete IP/network from psad auto blocking chains exit &sockwrite_rm_ipt_block_ip() if $fw_rm_block_ip; ### send a warning via syslog if the HOME_NET variable definition ### appears to include a subnet that is not directly connected to ### the local system. &validate_home_net(); ### import icmp types and codes from psad_icmp_types; icmp "type" ### and "code" fields will be validated against the values in this ### file. &import_icmp_types('icmp', \%valid_icmp_types, $config{'ICMP_TYPES_FILE'}) unless $no_icmp_types; &import_icmp_types('icmp6', \%valid_icmp6_types, $config{'ICMP6_TYPES_FILE'}) unless $no_icmp6_types; ### import p0f-based passive OS fingerprinting signatures &import_p0f_ipv4_sigs() unless $no_posf; ### import TOS-based passive OS fingerprinting signatures &import_posf_sigs() unless $no_posf; ### import IP protocols detail (derived from /etc/protocols) &import_protocol_strings(); ### import auto_dl file for automatic ip/network danger ### level assignment &import_auto_dl() unless $no_auto_dl; ### parse snort rules if we enable psad to match on iptables log ### messages that include snort SID's (see "fwsnort": ### http://www.cipherdyne.org/fwsnort). &import_snort_rules() unless $no_snort_sids; ### import psad signatures (note that these signatures have been ### adapted from the Snort IDS and contain several keywords that ### were added by the psad project). &import_signatures() unless $no_signatures; ### the --Status command line switch was given exit &status() if $status_mode; ### there is a set of ports that should be ignored &parse_ignore_ports(); ### there is a set of protocols that should be ignored &parse_ignore_protocols(); ### there is a set of interfaces that should be ignored &parse_ignore_interfaces(); ### enter iptables analysis mode. exit &analysis_mode() if $analyze_mode; ### enter CSV output mode. exit &csv_mode() if $csv_mode or $gnuplot_mode; ### enter benchmarking mode exit &benchmark_mode() if $benchmark; ### analyze the iptables policy and exit my $rv = &fw_analyze_mode(); exit $rv if $fw_analyze; ### make sure we are setup to run &setup(); ### dump config &dump_conf() if $debug; if ($restrict_ip_cmdline) { $restrict_ip = new NetAddr::IP $restrict_ip_cmdline or die "[*] Could not acquire NetAddr::IP object for $restrict_ip"; } return; } sub validate_config() { &check_enable_vars_value(); &is_digit_range('PORT_RANGE_SCAN_THRESHOLD', 0, 65535); &is_digit_range('TOP_SCANS_CTR_THRESHOLD', 0, 500); &is_digit_range('DSHIELD_ALERT_INTERVAL', 1, 24); &is_digit_range('TOP_PORTS_LOG_THRESHOLD', 0, 65535); &is_digit_range('STATUS_PORTS_THRESHOLD', 0, 65535); &is_digit_range('TOP_IP_LOG_THRESHOLD', 0, 10000); &is_digit_range('STATUS_IP_THRESHOLD', 0, 10000); ### it will be a long time before there are 100000 signatures &is_digit_range('TOP_SIGS_LOG_THRESHOLD', 0, 100000); &is_digit_range('STATUS_SIGS_THRESHOLD', 0, 10000); die qq([*] Invalid EMAIL_ADDRESSES value: "$config{'EMAIL_ADDRESSES'}") unless $config{'EMAIL_ADDRESSES'} =~ /\S+\@\S+/; ### translate commas into spaces $config{'EMAIL_ADDRESSES'} =~ s/\s*\,\s/ /g; if ($config{'ENABLE_AUTO_IDS'} eq 'Y' and $config{'IPTABLES_BLOCK_METHOD'} eq 'N' and $config{'TCPWRAPPERS_BLOCK_METHOD'} eq 'N') { die 'config warning, ENABLE_AUTO_IDS=Y, but ' . 'both IPTABLES_BLOCK_METHOD and TCPWRAPPERS_BLOCK_METHOD are ' . 'set to N.'; } if ($status_min_dl and $status_min_dl > 5) { die '[*] The --status-dl must be between 1 and 5.'; } if ($no_kmsgsd and not $debug) { die '[*] The --no-kmsgsd option can only be used with --debug.'; } if ($fw_del_chains and not $flush_fw) { die '[*] The --fw-del-chains option can only be used with --Flush.'; } if ($fw_block_ip) { unless ($fw_block_ip =~ m|^\s*$ipv4_re\s*$| or $fw_block_ip =~ m|^\s*$ipv4_re/\d+\s*$| or $fw_block_ip =~ m|^\s*$ipv4_re/$ipv4_re\s*$|) { die '[*] The --fw-block-ip argument accepts ' . 'an IP address or network.'; } } if ($fw_rm_block_ip) { unless ($fw_rm_block_ip =~ m|^\s*$ipv4_re\s*$| or $fw_rm_block_ip =~ m|^\s*$ipv4_re/\d+\s*$| or $fw_rm_block_ip =~ m|^\s*$ipv4_re/$ipv4_re\s*$|) { die '[*] The --fw-rm-block-ip argument accepts ' . 'an IP address or network.'; } } if ($config{'ENABLE_SYSLOG_FILE'} eq 'Y') { die "[*] Cannot set IPT_SYSLOG_FILE and FW_DATA_FILE to point ", "at the same file." if $config{'IPT_SYSLOG_FILE'} eq $config{'FW_DATA_FILE'}; } unless ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL7/i or $config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL6/i or $config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL5/i or $config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL4/i or $config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL3/i or $config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL2/i or $config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL1/i or $config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL0/i) { die "[*] Unrecognized SYSLOG_FACILITY, see psad.conf"; } unless ($config{'SYSLOG_PRIORITY'} =~ /LOG_INFO/i or $config{'SYSLOG_PRIORITY'} =~ /LOG_DEBUG/i or $config{'SYSLOG_PRIORITY'} =~ /LOG_NOTICE/i or $config{'SYSLOG_PRIORITY'} =~ /LOG_WARNING/i or $config{'SYSLOG_PRIORITY'} =~ /LOG_ERR/i or $config{'SYSLOG_PRIORITY'} =~ /LOG_CRIT/i or $config{'SYSLOG_PRIORITY'} =~ /LOG_ALERT/i or $config{'SYSLOG_PRIORITY'} =~ /LOG_EMERG/i) { die "[*] Unrecognized SYSLOG_PRIORITY, see psad.conf"; } if ($analyze_mode or $gnuplot_mode or $csv_mode) { $fw_data_file = $config{'IPT_SYSLOG_FILE'} unless $fw_data_file; } if ($gnuplot_mode and not $csv_fields) { die "[*] Must specify which iptables fields to plot with the ", "--CSV-fields argument." } return; } sub check_enable_vars_value() { for my $var (keys %config) { next unless $var =~ /^ENABLE_/; ### make sure that all of the "ENABLE_" vars have a value of ### 'Y' or 'N' $config{$var} = uc($config{$var}); unless ($config{$var} eq 'Y' or $config{$var} eq 'N' or $config{$var} eq 'YES' or $config{$var} eq 'NO') { die "[*] $var variable must be 'Y' or 'N'"; } } return; } sub is_digit_range() { my ($key, $min, $max) = @_; die "[*] $key must be an integer >= $min and <= $max" unless $config{$key} =~ m|^\d+$| and $config{$key} >= $min and $config{$key} <= $max; return; } sub get_connected_subnets() { my @connected_subnets = (); if ($config{'IFCFGTYPE'} =~ /iproute2/i) { my @ip_out = @{&run_command($cmds{'ip'}, 'addr')}; my $intf_name = ''; my $home_net_str = ''; for my $line (@ip_out) { if ($line =~ /^\d+:\s+(\S+): network()->cidr() . ", "; } $connected_str =~ s|,\s*$||; $config{'HOME_NET'} = $connected_str; $config{'HOME_NET'} = 'any' unless $connected_str; } else { if ($config{'HOME_NET'} =~ /CHANGEME/) { &sys_log('config warning: the HOME_NET ' . 'variable has not been set, defaulting to "any"'); $config{'HOME_NET'} = 'any'; return; } my @home_nets = split /\s*\,\s*/, $config{'HOME_NET'}; my $found_one_net = 0; for my $net (@home_nets) { my $home_net = ''; if ($net =~ m|($ipv4_re/$ipv4_re)|) { $home_net = new NetAddr::IP $1; } elsif ($net =~ m|($ipv4_re/\d+)|) { $home_net = new NetAddr::IP $1; } elsif ($net =~ m|($ipv4_re)|) { $home_net = new NetAddr::IP $1; } else { next; } push @local_nets, new NetAddr::IP $net; my $found = 0; for my $net (@$connected_subnets_ar) { $found = $found_one_net = 1 if $home_net->within($net); } unless ($found) { ### note that this might be ok if psad is running on a syslog ### server, but the most likely explanation is that there was a ### typo in the HOME_NET variable defintion. &sys_log('config warning: HOME_NET definition ' . qq|in psad.conf contains "$home_net" which does not appear | . "to be directly connected to the local system."); } } $config{'HOME_NET'} = 'any' unless $found_one_net; } return; } sub is_local() { my ($ip, $ip_obj) = @_; print STDERR "[+] is_local(): $ip..." if $debug; if ($test_mode) { print STDERR "(test mode enabled) no\n"; return 0; } my $found = 0; for my $net (@local_nets) { if ($ip_obj->within($net)) { $found = 1; last; } } if ($debug) { $found == 0 ? print STDERR "no\n" : print STDERR "yes\n"; } return $found; } sub import_ip_options() { %ip_options = (); open O, "< $config{'IP_OPTS_FILE'}" or die "[*] Couild not open IP options file $config{'IP_OPTS_FILE'}: $!"; while () { next unless /\S/; next if /^\s*#/; ### 136 4 satid Stream Identifier ### 145 -1 extproto Extended Internet Proto if (/^\s*(\d+)\s+(\S+)\s+(\w+)\s+(.*)\s*/) { $ip_options{$1}{'len'} = $2; $ip_options{$1}{'sig_keyword'} = $3; $ip_options{$1}{'desc'} = $4; } else { } } close O; print STDERR "[+] IP options:\n", Dumper(\%ip_options) if $debug and $verbose; return; } sub import_fw_search() { my $config_file = shift; @fw_search = (); open F, "< $config_file" or die "[*] Could not open config ", "string file $config_file: $!"; my @lines = ; close F; my $found_fw_search = 0; for my $line (@lines) { next unless $line =~ /\S/; next if $line =~ /^\s*#/; if ($line =~ /^\s*FW_MSG_SEARCH\s+(.*?);/) { push @fw_search, $1; $found_fw_search = 1; } elsif ($line =~ /^\s*FW_SEARCH_ALL\s+(\w+);/) { my $strategy = $1; if ($strategy eq 'Y' or $strategy eq 'N') { $config{'FW_SEARCH_ALL'} = $strategy; } } } unless (defined $config{'FW_SEARCH_ALL'}) { $config{'FW_SEARCH_ALL'} = 'Y'; } unless ($config{'FW_SEARCH_ALL'} eq 'Y' or $config{'FW_SEARCH_ALL'} eq 'N') { $config{'FW_SEARCH_ALL'} = 'Y'; } if ($config{'FW_SEARCH_ALL'} eq 'N' and not $found_fw_search) { push @fw_search, 'DROP'; } return; } sub parse_ignore_ports() { ### zero out the hash since a HUP signal may have been received %ignore_ports = (); return if $config{'IGNORE_PORTS'} eq 'NONE'; &parse_port_range(\%ignore_ports, $config{'IGNORE_PORTS'}); return; } sub parse_port_range() { my ($hr, $line) = @_; my @fields = split /\s*,\s*/, $line; for my $field (@fields) { if ($field =~ m/(tcp|udp)\/(\d+)\s*-\s*(\d+)/i) { my $proto = lc($1); my $low = $2; my $high = $3; if ($low < $high) { my $existing_high = 0; if (defined $hr->{$proto} and defined $hr->{$proto}->{'range'} and defined $hr->{$proto}->{'range'}->{$low}) { $existing_high = $hr->{$proto}->{'range'}->{$low}; } if ($existing_high) { if ($high > $existing_high) { $hr->{$proto}->{'range'}->{$low} = $high; } } else { $hr->{$proto}->{'range'}->{$low} = $high; } } } elsif ($field =~ m/(tcp|udp)\/(\d+)/i) { my $proto = lc($1); my $port = $2; $hr->{$proto}->{'port'}->{$port} = ''; } } return; } sub parse_ignore_protocols() { ### zero out the hash since a HUP signal may have been received %ignore_protocols = (); return if $config{'IGNORE_PROTOCOLS'} eq 'NONE'; my @protos = split /\s*,\s*/, $config{'IGNORE_PROTOCOLS'}; for my $proto (@protos) { if ($proto =~ /\W/) { &sys_log('invalid protocol in IGNORE_PROTOCOLS var'); } else { if ($proto =~ /^\d+$/) { ### IP protocol number $ignore_protocols{$proto} = ''; } else { $ignore_protocols{lc($proto)} = ''; } } } return; } sub parse_ignore_interfaces() { ### zero out the hash since a HUP signal may have been received %ignore_interfaces = (); return if $config{'IGNORE_INTERFACES'} eq 'NONE'; my @interfaces = split /\s*,\s*/, $config{'IGNORE_INTERFACES'}; for my $intf (@interfaces) { if ($intf =~ /\W/) { &sys_log('invalid interface in IGNORE_INTERFACES var'); } else { $ignore_interfaces{$intf} = ''; } } return; } sub import_snort_rules() { %fwsnort_sigs = (); for my $dir ($config{'SNORT_RULES_DIR'}, $config{'FWSNORT_RULES_DIR'}) { next unless -d $dir; opendir D, $dir or die "[*] Could not open $dir: $!"; my @rfiles = readdir D; closedir D; FILE: for my $rfile (@rfiles) { next FILE unless $rfile =~ /\.rules$/; if ($srules_type) { next FILE unless $rfile =~ /^${srules_type}\.rules$/; } my ($type) = ($rfile =~ /(\w+)\.rules/); open R, "< $dir/${rfile}" or die "[*] Could not open: ${srules_type}/${rfile}"; while () { next unless /^\s*alert/; my $sid; ### snort rule id if (/[\s;]sid:\s*(\d+)\s*;/) { $sid = $1; } else { next; } $fwsnort_sigs{$sid}{'msg'} = $1 if /msg:\s*\"(.*?)\"\s*;/; $fwsnort_sigs{$sid}{'is_psad_id'} = 0; if (/^\s*alert\s+(\w+)/) { $fwsnort_sigs{$sid}{'proto'} = lc($1); } if (/[\s;]classtype:\s*(.*?)\s*;/) { $fwsnort_sigs{$sid}{'classtype'} = $1; } else { $fwsnort_sigs{$sid}{'classtype'} = ''; } $fwsnort_sigs{$sid}{'priority'} = &convert_snort_priority($1) if /[\s;]priority:\s*(\d+)\s*;/; ### import multiple content fields; someone could have built ### a series of custom iptables chains in order to detect ### multiple content strings. while (/[\s;](?:uri)?content:\s*\"(.*?)\"\s*;/g) { push @{$fwsnort_sigs{$sid}{'content'}}, $1; } while (/[\s;]reference:\s*(.*?)\s*;/g) { my $ref = $1; if ($ref =~ /^(\w+),(\S+)/) { ### reference:bugtraq,9732; push @{$fwsnort_sigs{$sid}{'reference'}{lc($1)}}, $2; } } next unless defined $fwsnort_sigs{$sid}{'msg'} and defined $fwsnort_sigs{$sid}{'classtype'} and defined $fwsnort_sigs{$sid}{'content'}; } close R; } } ### import the Snort classification.config file &import_snort_class_priorities(); ### import the reference.config file &import_snort_reference_config(); ### import any specific SID -> DL mappings from the ### snort_rule_dl file &import_snort_rule_dl(); print STDERR Dumper %fwsnort_sigs if $debug and $verbose; &sys_log("imported original Snort rules in " . "$config{'SNORT_RULES_DIR'}/ for reference info"); return; } sub import_snort_class_priorities() { my $snort_class_file = "$config{'SNORT_RULES_DIR'}/classification.config"; return unless -e $snort_class_file; open F, "< $snort_class_file" or die $!; while () { ### config classification: rpc-portmap-decode,Decode of an RPC Query,2 if (/config\s+classification:\s+(\S+),.*(\d+)/) { ### the snort priority value can go from 1 to 10, with 1 being the ### worst offense and 10 being the least. Most priorities are ### from 1 to 4. We need to map these into the psad danger levels ### (reversed). NOTE: the Snort engine does not enforce the 1-10 ### range. $snort_class_dl{$1} = &convert_snort_priority($2); } } close F; &sys_log('imported Snort classification.config'); return; } sub convert_snort_priority() { my $snort_priority = shift; my $psad_dl = 1; if ($snort_priority == 1) { $psad_dl = 5; } elsif ($snort_priority == 2) { $psad_dl = 4; } elsif ($snort_priority == 3) { $psad_dl = 3; } elsif ($snort_priority == 4) { $psad_dl = 2; } return $psad_dl; } sub import_snort_reference_config() { my $ref_file = "$config{'SNORT_RULES_DIR'}/reference.config"; return unless -e $ref_file; open F, "< $ref_file" or die $!; while () { if (/^\s*config\s+reference:\s+(\w+)\s+(\S+)/) { ### config reference: bugtraq http://www.securityfocus.com/bid/ $snort_ref_baseurl{lc($1)} = $2; } } close F; return; } sub import_snort_rule_dl() { %snort_rule_dl = (); ### parse the snort_rule_dl file return unless -e $config{'SNORT_RULE_DL_FILE'}; open F, "< $config{'SNORT_RULE_DL_FILE'}" or die $!; while () { next unless /^\s*\d/; if (/^\s*(\d+)\s+(\d+)/) { ###
my $sid = $1; my $dl = $2; unless ($dl >= 0 and $dl < 6) { next; } $snort_rule_dl{$sid} = $dl; } } close F; return; } ### for signatures that psad is able to detect with iptables logs that do ### not contain "SIDnnn" messages generated by fwsnort (and hence have no ### application layer matching criteria) sub import_signatures() { ### import the ip_options file so that psad can make use of the ### ipopts keyword in Snort rules (requires --log-ip-options to ### iptables) &import_ip_options(); ### undef so we don't leave old signatures around if ### we execute this code after receiving a HUP signal. %sigs = (); %sig_search = (); %sig_ip_objs = (); ### make sure no duplicate psad_id and sid fields exist my %psad_ids = (); my %sids = (); open SIGS, "< $config{'SIGS_FILE'}" or die "[*] Could not open the signatures file $config{'SIGS_FILE'}: $!"; my $line_num = 0; my $sig_ctr = 0; my $next_available_sid = 1; SIG: while () { $line_num++; next SIG unless /\S/; next SIG if /^\s*#/; my %sig = (); ### alert tcp $HOME_NET 12345:12346 -> $EXTERNAL_NET any ### (msg:"BACKDOOR netbus active"; flow:from_server,established; ### content:"NetBus"; reference:arachnids,401; classtype:misc-activity; ### sid:109; rev:4; psad_dlevel:2) my $rule_hdr = ''; my $rule_options = ''; if (m|^(.*?)\s+\((.*)\)|) { $rule_hdr = $1; $rule_options = $2; } else { die "[*] import_signatures(): bad signature on line: ", "$line_num"; } ### parse rule header (routine taken from fwsnort). if ($rule_hdr =~ m|^\s*alert\s+(\S+)\s+(\S+)\s+(\S+) \s+(\S+)\s+(\S+)\s+(\S+)|x) { my $direction = $4; if ($direction eq '<>') { $sig{'bidir'} = 1; } else { $sig{'bidir'} = 0; } my $src = ''; my $dst = ''; if ($direction eq '<-') { $sig{'proto'} = lc($1); $src = $5; ### switch src and dst $dst = $2; $sig{'sp'} = $3; $sig{'dp'} = $6; } else { $sig{'proto'} = lc($1); $src = $2; ### normal src -> dst $dst = $5; $sig{'sp'} = $3; $sig{'dp'} = $6; } $sig{'src'} = &expand_sig_ips($src, $line_num); $sig{'dst'} = &expand_sig_ips($dst, $line_num); ### assign the source and destination port ranges &build_sig_int_range(\%sig, 'sp', 1, 65535, $line_num); &build_sig_int_range(\%sig, 'dp', 1, 65535, $line_num); } else { die "[*] import_signatures(): bad rule ", "header on line: $line_num"; } ### make sure the signature does not contain any unsupported ### Snort rule options unless (&check_supported_options($rule_options, $line_num)) { next SIG; } ### parse rule options if ($rule_options =~ /[\s;]psad_id:\s*(\d+)\s*;/) { my $psad_id = $1; if (defined $psad_ids{$psad_id}) { die "[*] import_signatures(): Duplicate psad_id: $psad_id ", qq|on line: $line_num|; } elsif (defined $sids{$psad_id}) { die "[*] import_signatures(): Duplicate psad_id: $psad_id ", qq|to sid on line: $line_num|; } $psad_ids{$psad_id} = ''; $sig{'psad_id'} = $psad_id; $next_available_sid = $psad_id if $psad_id > $next_available_sid; } else { my $msg = "[*] import_signatures(): could not find signature" . qq| "psad_id" on line: $line_num|; if ($config{'ENABLE_SNORT_SIG_STRICT'} eq 'Y') { die $msg; } else { &sys_log($msg); next SIG; } } ### original Snort sid if ($rule_options =~ /[\s;]sid:\s*(\d+)\s*;/) { my $sid = $1; if (defined $sids{$sid}) { die "[*] import_signatures(): Duplicate sid: $sid ", qq|on line: $line_num|; } elsif (defined $psad_ids{$sid}) { die "[*] import_signatures(): Duplicate sid: $sid ", qq|to psad_id on line: $line_num|; } $sids{$sid} = ''; $sig{'sid'} = $sid; $sig{'is_psad_id'} = 0; $next_available_sid = $sid if $sid > $next_available_sid; } else { ### the signature was derived from several Snort rules $sig{'sid'} = $sig{'psad_id'}; $sig{'is_psad_id'} = 1; } ### msg field if ($rule_options =~ /msg:\s*\"(.+?)\"\s*;/) { $sig{'msg'} = $1; } else { die "[*] import_signatures(): could not find ", qq|"msg" keyword on line: $line_num|; } ### classtype field if ($rule_options =~ /[\s;]classtype:\s*(.+?)\s*;/) { $sig{'classtype'} = $1; } else { $sig{'classtype'} = ''; } ### reference field while ($rule_options =~ /[\s;]reference:\s*(.*?)\s*;/g) { my $ref = $1; if ($ref =~ /^(\w+),(\S+)/) { ### reference:bugtraq,9732; push @{$sig{'reference'}{lc($1)}}, $2; } } ### psad danger level $sig{'dl'} = 2; ### default danger level if ($rule_options =~ /[\s;]psad_dl:\s*(\d+)/) { $sig{'dl'} = $1; } elsif ($sig{'classtype'}) { ### assign the danger level from the classification.config ### file if the psad_dl field does not exist if (defined $snort_class_dl{$sig{'classtype'}}) { $sig{'dl'} = $snort_class_dl{$sig{'classtype'}}; } } ### see the signature was derived from a set of Snort rules if ($rule_options =~ /[\s;]psad_derived_sids:\s*(.+?)\s*;/) { $sig{'psad_derived_sids'} = [split /\s*,\s*/, $1]; } ### sameip keyword if ($rule_options =~ /[\s;]sameip;/) { $sig{'sameip'} = 1; } ### psad_dsize keyword if ($rule_options =~ /[\s;]psad_dsize:\s*(.+?)\s*;/i) { $sig{'psad_dsize'} = $1; &build_sig_int_range(\%sig, 'psad_dsize', 1, 1514, $line_num); } ### psad_ip_len keyword if ($rule_options =~ /[\s;]psad_ip_len:\s*(.+?)\s*;/i) { $sig{'psad_ip_len'} = $1; ### technically, the minimum length must be $IP_HEADER_LEN &build_sig_int_range(\%sig, 'psad_ip_len', 1, 65536, $line_num); } ### dsize keyword if ($rule_options =~ /[\s;]dsize:\s*(.+?)\s*;/i) { $sig{'dsize'} = $1; &build_sig_int_range(\%sig, 'dsize', 1, 1514, $line_num); } ### ttl keyword if ($rule_options =~ /[\s;]ttl:\s*(.+?)\s*;/i) { $sig{'ttl'} = $1; &build_sig_int_range(\%sig, 'ttl', 1, 255, $line_num); } ### id keyword (for IP ID value) if ($rule_options =~ /[\s;]id:\s*(.+?)\s*;/i) { $sig{'id'} = $1; &build_sig_int_range(\%sig, 'id', 1, 65535, $line_num); } ### ipopts keyword (for IP options) if ($rule_options =~ /[\s;]ipopts:\s*(.+?)\s*;/i) { $sig{'ipopts'} = lc($1); ### make sure the IP option is defined in the ip_options ### file my $found = 0; for my $opt_val (keys %ip_options) { $found = 1 if $sig{'ipopts'} eq $ip_options{$opt_val}{'sig_keyword'}; } unless ($found) { print STDERR qq|[-] Invalid argument "$sig{'ipopts'}" to |, "ipopts keyword\n" if $debug; } } ### TCP keywords if ($sig{'proto'} eq 'tcp') { my $require_ack = 0; if ($rule_options =~ /[\s;]flow:\s*established\s*\;/i) { $require_ack = 1; } ### TCP flags if ($rule_options =~ /[\s;]flags:\s*(.+?)\s*;/) { my $sig_flags = $1; my $flags = ''; ### make flags identical to what iptables log messages ### would report (check in iptables flag reporting order). if ($sig_flags =~ /U/) { if ($flags) { $flags = 'URG ' . $flags; } else { $flags .= 'URG '; } } if ($sig_flags =~ /A/ or $require_ack) { $flags .= 'ACK '; } $flags .= 'PSH ' if $sig_flags =~ /P/; $flags .= 'RST ' if $sig_flags =~ /R/; $flags .= 'SYN ' if $sig_flags =~ /S/; $flags .= 'FIN ' if $sig_flags =~ /F/; ### if no flags are set iptables simply reports no flags ### at all instead of reporting "NULL". $flags = 'NULL' if $sig_flags =~ /N/; $flags =~ s/\s*$// if $flags; $sig{'flags'} = $flags; } ### seq keyword (TCP sequence number) if ($rule_options =~ /[\s;]seq:\s*(.+?)\s*;/i) { $sig{'seq'} = $1; &build_sig_int_range(\%sig, 'seq', 1, 4294967296, $line_num); } ### ack keyword (TCP acknowledgement number) if ($rule_options =~ /[\s;]ack:\s*(.+?)\s*;/i) { $sig{'ack'} = $1; &build_sig_int_range(\%sig, 'ack', 1, 4294967296, $line_num); } ### window keyword (TCP window size) if ($rule_options =~ /[\s;]window:\s*(.+?)\s*;/i) { $sig{'window'} = $1; &build_sig_int_range(\%sig, 'window', 1, 65535, $line_num); } } elsif ($sig{'proto'} eq 'udp') { ### no specific UDP header tests } elsif ($sig{'proto'} eq 'icmp') { ### ICMP keywords if ($rule_options =~ /[\s;]itype:\s*(.+?)\s*;/i) { $sig{'itype'} = $1; &build_sig_int_range(\%sig, 'itype', 1, 255, $line_num); } ### itype keyword (ICMP type) if ($rule_options =~ /[\s;]icode:\s*(.+?)\s*;/i) { $sig{'icode'} = $1; &build_sig_int_range(\%sig, 'icode', 1, 255, $line_num); } ### icmp_seq keyword (ICMP sequence value) if ($rule_options =~ /[\s;]icmp_seq:\s*(.+?)\s*;/i) { $sig{'icmp_seq'} = $1; &build_sig_int_range(\%sig, 'icmp_seq', 1, 255, $line_num); } ### icmp_id keyword (ICMP ID value) if ($rule_options =~ /[\s;]icmp_id:\s*(.+?)\s*;/i) { $sig{'icmp_id'} = $1; &build_sig_int_range(\%sig, 'icmp_id', 'icmp_id_s', 'icmp_id_e', 'icmp_id_neg', 1, 255, $line_num); } } elsif ($sig{'proto'} eq 'ip') { ### ip_proto keyword (IP protocol value) if ($rule_options =~ /[\s;]ip_proto:\s*(.+?)\s*;/i) { $sig{'ip_proto'} = $1; &build_sig_int_range(\%sig, 'ip_proto', 1, 255, $line_num); } } else { die "[*] import_signatures(): unsupported protocol: ", "$sig{'proto'} at line: $line_num"; } ### add this signature into attributes cache $sigs{$sig{'sid'}} = \%sig; ### add this signature into the fast lookup cache for my $src (@{$sig{'src'}}) { for my $dst (@{$sig{'dst'}}) { if ($sig{'proto'} eq 'icmp' or $sig{'proto'} eq 'ip') { $sig_search{$sig{'proto'}}{$src}{$dst}{$sig{'sid'}} = ''; } elsif ($sig{'proto'} eq 'tcp' or $sig{'proto'} eq 'udp' or $sig{'proto'} eq 'udplite') { my $sp_type = 'norm'; $sp_type = 'neg' if $sig{'sp_neg'}; my $dp_type = 'norm'; $dp_type = 'neg' if $sig{'dp_neg'}; $sig_search{$sig{'proto'}}{$src}{$dst}{$sp_type}{$sig{'sp_s'}} {$sig{'sp_e'}}{$dp_type}{$sig{'dp_s'}}{$sig{'dp_e'}} {$sig{'sid'}} = ''; } } } $sig_ctr++; } close SIGS; if ($get_next_rule_id) { $next_available_sid++; print "[+] Next available rule ID: $next_available_sid\n"; exit 0; } if ($debug and $verbose) { print STDERR "[+] Main signatures hash:\n", Dumper(\%sig_search), Dumper(\%sigs); } &sys_log("imported $sig_ctr psad Snort signatures " . "from $config{'SIGS_FILE'}"); return; } sub check_supported_options() { my ($rule_options, $line_num) = @_; for my $opt (@unsupported_snort_opts) { ### see if we match a regex belonging to an unsupported option if ($rule_options =~ m|[\s;]$opt:\s*.*?\s*;|) { my $msg = '[*] import_signatures(): Unsupported rule option: ' . qq|"$opt" at line: $line_num|; if ($config{'ENABLE_SNORT_SIG_STRICT'} eq 'Y') { die $msg; } else { &sys_log($msg); return 0; } } } return 1; } sub import_icmp_types() { my ($proto, $type_hr, $file) = @_; open F, "< $file" or die "[*] Could not open $file: $!"; my $icmp_type = -1; while () { next if /^\s*#/; if (/^(\d+)\s+(.*)/) { $icmp_type = $1; my $icmp_type_text = $2; if ($icmp_type_text =~ /unassigned/i) { $icmp_type = -1; } $type_hr->{$icmp_type}->{'text'} = $icmp_type_text; $type_hr->{$icmp_type}->{'codes'}{-1} = ''; next; } if (/^\s+(\d+)\s+(.*)/ and $icmp_type > -1) { my $icmp_code = $1; my $icmp_code_text = $2; next if $icmp_code_text =~ /unassigned/i; ### don't really need to add the icmp code text here since ### we validate against the icmp type first (i.e. an invalid ### icmp code is meaningless unless we first have a valid ### icmp type). $type_hr->{$icmp_type}->{'codes'}->{$icmp_code} = ''; } } close F; print STDERR Dumper $type_hr if $debug and $verbose; &sys_log("imported valid $proto types and codes"); return; } sub expand_sig_ips() { my ($ip_str, $line_num) = @_; my @arr = (); ### resolve any embedded vars if ($ip_str =~ m|\$.+\$|) { die "[*] import_signature(): Multiple embedded vars not supported ", "at line: $line_num"; } elsif ($ip_str =~ m|\$(\w+)|) { my $sub_var = $1; if (defined $config{$sub_var}) { $ip_str =~ s|\$\w+|$config{$sub_var}|; } else { die qq|[*] import_signatures(): sub-var "$sub_var" at line: |, "$line_num not defined in psad.conf"; } } if ($ip_str =~ m|,|) { my @ips = split /\s*,\s*/, $ip_str; for my $ip (@ips) { if ($ip =~ m|($ipv4_re/$ipv4_re)| or $ip =~ m|($ipv4_re/\d+)| or $ip =~ m|($ipv4_re)|) { push @arr, $1; $sig_ip_objs{$1} = new NetAddr::IP($1); } elsif ($ip =~ m|\:|) { push @arr, $ip; $sig_ip_objs{$ip} = new NetAddr::IP($ip) or die "[*] NetAddr::IP error for $ip"; } } } elsif ($ip_str =~ m|($ipv4_re/$ipv4_re)| or $ip_str =~ m|($ipv4_re/\d+)| or $ip_str =~ m|($ipv4_re)|) { push @arr, $1; $sig_ip_objs{$1} = new NetAddr::IP($1) or die "[*] NetAddr::IP error for $1"; } elsif ($ip_str =~ m|\:|) { push @arr, $ip_str; $sig_ip_objs{$ip_str} = new NetAddr::IP($ip_str) or die "[*] NetAddr::IP error for $ip_str"; } elsif ($ip_str eq 'any' or $ip_str =~ m|NOT_?USED|i) { ### handle NOT_USED case from older psad versions push @arr, 'any'; ### this will match any IPv4 or IPv6 address $sig_ip_objs{'any'} = new6 NetAddr::IP('0000:0000:0000:0000:0000:0000:0000:0000/0'); } else { die "[*] import_signatures(): Unrecognized src/dst: $ip_str ", "at line: $line_num"; } return \@arr; } sub build_sig_int_range() { my ($sig_hr, $keyword, $range_start, $range_end, $line_num) = @_; my $start_key = "${keyword}_s"; my $end_key = "${keyword}_e"; my $neg_key = "${keyword}_neg"; my $val = $sig_hr->{$keyword}; ### resolve any embedded vars if ($val =~ m|\$.+\$|) { die "[*] import_signature(): Multiple embedded vars not supported ", "at line: $line_num"; } elsif ($val =~ m|\$(\w+)|) { my $sub_var = $1; if (defined $config{$sub_var}) { $val =~ s|\$\w+|$config{$sub_var}|; $sig_hr->{$keyword} = $val; } else { die qq|[*] import_signatures(): sub-var "$sub_var" at line: |, "$line_num not defined in psad.conf"; } } $sig_hr->{$start_key} = $range_start; $sig_hr->{$end_key} = $range_end; if ($val =~ m|\!|) { $sig_hr->{$neg_key} = 1; } else { $sig_hr->{$neg_key} = 0; } $val =~ s|\!||; return if $val eq 'any'; if ($val =~ m|^\s*(\d+)\s*:\s*(\d+)|) { $sig_hr->{$start_key} = $1; $sig_hr->{$end_key} = $2; } elsif ($val =~ m|^\s*(\d+)\s*:|) { $sig_hr->{$start_key} = $1; } elsif ($val =~ m|^\s*:(\d+)|) { $sig_hr->{$end_key} = $1; } elsif ($val =~ m|^\s*\<=\s*(\d+)|) { $sig_hr->{$end_key} = $1; } elsif ($val =~ m|^\s*\<\s*(\d+)|) { $sig_hr->{$end_key} = $1-1; } elsif ($val =~ m|^\s*\>=\s*(\d+)|) { $sig_hr->{$start_key} = $1; } elsif ($val =~ m|^\s*\>\s*(\d+)|) { $sig_hr->{$start_key} = $1+1; } elsif ($val =~ m|^\s*(\d+)|) { $sig_hr->{$start_key} = $sig_hr->{$end_key} = $1; } else { die "[*] import_signatures(): Unrecognized value: ", "$val at line: $line_num"; } return; } sub import_auto_dl() { %auto_dl = (); ### undef so we don't leave old ips in %auto_dl my $i = 0; open F, "< $config{'AUTO_DL_FILE'}" or die '[*] Could not open ', "$config{'AUTO_DL_FILE'}: $!"; while () { $i++; next unless /\S/; next if /^\s*#/; my $ip = ''; my $mask = ''; my $dl = ''; my $opt_criteria = ''; if (m|^\s*($ipv4_re)\s*/\s*($ipv4_re)\s+([0-5])\s*(.*);|) { $ip = $1; $mask = $2; $dl = $3; $opt_criteria = $4; } elsif (m|^\s*($ipv4_re)\s*/\s*(\d+)\s+([0-5])\s*(.*);|) { $ip = $1; $mask = $2; $dl = $3; $opt_criteria = $4; } elsif (m|^\s*($ipv4_re)\s+([0-5])\s*(.*);|) { $ip = $1; $mask = '32'; ### single IP $dl = $2; $opt_criteria = $3; } elsif (m|^\s*(\S+)\s+([0-5])\s*(.*);|) { my $str = $1; $dl = $2; $opt_criteria = $3; if ($str =~ /\:/) { ### check to see if this is an IPv6 address if ($str =~ m|(\S+)/(\S+)|) { $ip = $1; $mask = $2; } else { $ip = $str; $mask = '128'; } } else { ### check to see if it is a hostname my $hostname = $str; my $iaddr = 0; $iaddr = inet_aton($hostname); unless ($iaddr) { &sys_log(qq|could not resolve auto_dl "$hostname" | . "to an IP at line $i"); next; } $ip = inet_ntoa($iaddr) or &sys_log(qq|could not resolve auto_dl "$hostname" | . "to an IP at line $i"); $mask = '32'; ### single IP } } else { &sys_log("improperly formatted auto_dl line $i"); next; } $auto_dl_ip_objs{$ip} = new NetAddr::IP($ip, $mask); unless (defined $auto_dl_ip_objs{$ip} and $auto_dl_ip_objs{$ip}) { &sys_log("auto_dl could not acquire NetAddr::IP object at line $i"); next; } $auto_dl{$ip}{'mask'} = $mask; $auto_dl{$ip}{'dl'} = $dl; if (m|icmp|i) { $auto_dl{$ip}{'proto'}{'icmp'}{'all'} = ''; } ### check for optional port/protocol criteria if (/tcp/i or /udp/i) { &parse_port_range(\%{$auto_dl{$ip}{'proto'}}, $opt_criteria); } if (m|tcp|i and not defined $auto_dl{$ip}{'proto'}{'tcp'}) { $auto_dl{$ip}{'proto'}{'tcp'}{'all'} = ''; } if (m|udplite|i and not defined $auto_dl{$ip}{'proto'}{'udplite'}) { $auto_dl{$ip}{'proto'}{'udplite'}{'all'} = ''; } if (m|udp|i and not defined $auto_dl{$ip}{'proto'}{'udp'}) { $auto_dl{$ip}{'proto'}{'udp'}{'all'} = ''; } unless ($ip and ($mask =~ /^0$/ or $mask)) { ### allow a mask of "0" unless ($fw_block_ip) { my $subject = "$config{'MAIL_ERROR_PREFIX'} import warning: " . "$config{'AUTO_DL_FILE'} error on line: $i"; &send_mail($subject, '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } } } close F; if (%auto_dl) { ### don't write syslog message if we are running in --fw-block-ip ### mode unless ($fw_block_ip) { my $msg = "imported auto_dl file, got " . (keys %auto_dl) . " IP addrs/networks"; print STDERR $msg, "\n" if $debug; &sys_log($msg); } } return; } sub import_p0f_ipv4_sigs() { %p0f_ipv4_sigs = (); my $p0f_file = $config{'P0F_FILE'}; open P, "< $p0f_file" or die '[*] Could not open ', "$p0f_file: $!"; my @lines =

; close P; my $os = ''; for my $line (@lines) { chomp $line; next if $line =~ /^\s*#/; next unless $line =~ /\S/; ### S3:64:1:60:M*,S,T,N,W1: Linux:2.5::Linux 2.5 (sometimes 2.4) ### 16384:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.4::FreeBSD 4.4 ### 16384:64:1:44:M*: FreeBSD:2.0-2.2::FreeBSD 2.0-4.1 if ($line =~ /^(\S+?):(\S+?):(\S+?):(\S+?):(\S+?):\s+(.*)\s*/) { my $win_size = $1; my $ttl = $2; my $frag_bit = $3; my $len = $4; my $options = $5; my $os = $6; my $sig_str = "$win_size:$ttl:$frag_bit:$len:$options"; ### don't know how to handle MTU-based window size yet unless ($win_size =~ /T/) { $p0f_ipv4_sigs{$len}{$frag_bit}{$ttl}{$win_size}{$options}{$os} = $sig_str; } } } print STDERR Dumper %p0f_ipv4_sigs if $debug and $verbose; &sys_log('imported p0f-based passive OS fingerprinting signatures'); return; } sub import_posf_sigs() { %posf_sigs = (); my $posf_file = $config{'POSF_FILE'}; open P, "< $posf_file" or die '[*] Could not open ', "$posf_file: $!"; my @lines =

; close P; my $os = ''; for my $line (@lines) { chomp $line; next if ($line =~ /^\s*#/); next unless ($line =~ /\S/); if ($line =~ /^\s*OS\s+(.*);/) { $os = $1; } elsif ($line =~ /^\s*NUMPKTS\s+(\d+);/) { $posf_sigs{$os}{'numpkts'} = $1; } elsif ($line =~ /^\s*TOS\s+(\w+);/) { $posf_sigs{$os}{'tos'} = $1; } elsif ($line =~ /^\s*LEN\s+(\d+);/) { $posf_sigs{$os}{'len'} = $1; } elsif ($line =~ /^\s*TTL\s+(\d+);/) { $posf_sigs{$os}{'ttl'} = $1; } elsif ($line =~ /^\s*ID\s+(\w+);/) { $posf_sigs{$os}{'id'} = $1; } elsif ($line =~ /^\s*WINDOW\s+(\d+);/) { $posf_sigs{$os}{'win'} = $1; } } ### make sure each of the os signatures has all fields defined OS: for my $os (keys %posf_sigs) { unless (defined $posf_sigs{$os}{'numpkts'}) { &sys_log("$posf_file: missing " . "NUMPKTS for os: $os"); delete $posf_sigs{$os}; next OS; } unless (defined $posf_sigs{$os}{'tos'}) { &sys_log("$posf_file: missing " . "TOS for os: $os"); delete $posf_sigs{$os}; next OS; } unless (defined $posf_sigs{$os}{'len'}) { &sys_log("$posf_file: missing " . "LEN for os: $os"); delete $posf_sigs{$os}; next OS; } unless (defined $posf_sigs{$os}{'ttl'}) { &sys_log("$posf_file: missing " . "TTL for os: $os"); delete $posf_sigs{$os}; next OS; } unless (defined $posf_sigs{$os}{'id'}) { &sys_log("$posf_file: missing " . "ID for os: $os"); delete $posf_sigs{$os}; next OS; } else { unless ($posf_sigs{$os}{'id'} eq 'SMALLINCR' || $posf_sigs{$os}{'id'} eq 'RANDOM') { &sys_log("$posf_file: ID must " . "be either SMALLINCR or RANDOM for os: $os"); delete $posf_sigs{$os}; next OS; } } unless (defined $posf_sigs{$os}{'win'}) { &sys_log("$posf_file: missing " . "WINDOW for os: $os"); delete $posf_sigs{$os}; next OS; } } print STDERR Dumper %posf_sigs if $debug and $verbose; &sys_log('imported TOS-based passive OS fingerprinting signatures'); return; } sub check_range() { my ($port, $start, $end) = @_; $start = $port if ($port < $start); $end = $port if ($port > $end); return $start, $end; } ### assign a danger level to each scan in the current interval. sub assign_danger_level() { my ($curr_scan_hr, $curr_sigs_dl_hr, $curr_sids_dl_hr) = @_; SRC: for my $src (keys %$curr_scan_hr) { my $changed_dl = 0; print STDERR "[+] assign_danger_level(): source IP: $src (dl: ", "$scan_dl{$src})\n" if $debug; if (defined $curr_sigs_dl_hr->{$src}) { if ($scan_dl{$src} < $curr_sigs_dl_hr->{$src}) { $scan_dl{$src} = $curr_sigs_dl_hr->{$src}; $changed_dl = 1; } } if (defined $curr_sids_dl_hr->{$src}) { if ($scan_dl{$src} < $curr_sids_dl_hr->{$src}) { $scan_dl{$src} = $curr_sids_dl_hr->{$src}; $changed_dl = 1; } } DST: for my $dst (keys %{$curr_scan_hr->{$src}}) { my $absnum = $scan{$src}{$dst}{'absnum'}; my $tot_protocols = $scan{$src}{$dst}{'tot_protocols'}; my $range = 0; my $s_port = 65535; my $e_port = 0; if ($changed_dl) { $scan{$src}{$dst}{'alerted'} = 0 if $config{'ALERT_ALL'} eq 'N'; } ### calculate the range over _both_ tcp and udp for my $proto (qw(tcp udp udplite)) { next unless defined $scan{$src}{$dst}{$proto}; next unless defined $scan{$src}{$dst}{$proto}{'abs_sp'}; if ($s_port > $scan{$src}{$dst}{$proto}{'abs_sp'}) { $s_port = $scan{$src}{$dst}{$proto}{'abs_sp'}; } if ($e_port < $scan{$src}{$dst}{$proto}{'abs_ep'}) { $e_port = $scan{$src}{$dst}{$proto}{'abs_ep'}; } } if ($e_port) { $range = $e_port - $s_port; } else { ### for icmp $range = $absnum; } ### if PORT_RANGE_SCAN_THRESHOLD is >= 1, then psad will not assign ### a danger level to repeated packets to the same port if ($absnum < $config{'DANGER_LEVEL1'}) { ### don't have enough packets to even reach danger level 1 yet. next DST; } if ($range >= $config{'PORT_RANGE_SCAN_THRESHOLD'} or $tot_protocols >= $config{'PROTOCOL_SCAN_THRESHOLD'}) { if ($absnum < $config{'DANGER_LEVEL2'}) { if ($scan_dl{$src} < 1) { $scan{$src}{$dst}{'alerted'} = 0 if $config{'ALERT_ALL'} eq 'N'; $scan_dl{$src} = 1; } } elsif ($absnum < $config{'DANGER_LEVEL3'}) { if ($scan_dl{$src} < 2) { $scan{$src}{$dst}{'alerted'} = 0 if $config{'ALERT_ALL'} eq 'N'; $scan_dl{$src} = 2; } } elsif ($absnum < $config{'DANGER_LEVEL4'}) { if ($scan_dl{$src} < 3) { $scan{$src}{$dst}{'alerted'} = 0 if $config{'ALERT_ALL'} eq 'N'; $scan_dl{$src} = 3; } } elsif ($absnum < $config{'DANGER_LEVEL5'}) { if ($scan_dl{$src} < 4) { $scan{$src}{$dst}{'alerted'} = 0 if $config{'ALERT_ALL'} eq 'N'; $scan_dl{$src} = 4; } } elsif ($scan_dl{$src} < 5) { $scan{$src}{$dst}{'alerted'} = 0 if $config{'ALERT_ALL'} eq 'N'; $scan_dl{$src} = 5; } } } print STDERR '[+] assign_danger_level(): DL (after assignment) = ', "$scan_dl{$src}\n" if $debug; } return; } sub assign_auto_danger_level() { my $pkt_hr = shift; ### see if the source should automatically be assigned a ### danger level NET: for my $net (keys %auto_dl) { my $dl = $auto_dl{$net}{'dl'}; my $mask = $auto_dl{$net}{'mask'}; ### may be a /32 (single IP) ### check to see if $pkt_hr->{'src'} is contained within an auto_dl network next NET unless $pkt_hr->{'s_obj'}->within($auto_dl_ip_objs{$net}); ### $pkt_hr->{'src'} is part of an ignored network return 0 if $dl == 0; if ($scan_dl{$pkt_hr->{'src'}} < $dl) { if (not defined $auto_dl{$net}{'proto'}) { ### all protocols are applicable $scan_dl{$pkt_hr->{'src'}} = $dl; &sys_log('auto-assigned danger level: ' . "$dl for IP: $pkt_hr->{'src'}"); return $dl; } else { for my $proto (keys %{$auto_dl{$net}{'proto'}}) { next unless $pkt_hr->{'proto'} eq $proto; if (defined $auto_dl{$net}{'proto'}{$proto}{'port'} or defined $auto_dl{$net}{'proto'}{$proto}{'range'}) { if (&match_port(\%{$auto_dl{$net}{'proto'}{$proto}}, $pkt_hr->{'dp'})) { $scan_dl{$pkt_hr->{'src'}} = $dl; &sys_log('auto-assigned danger ' . "level: $dl for IP: $pkt_hr->{'src'}"); return $dl; } } elsif (defined $auto_dl{$net}{'proto'}{$proto}{'all'}) { ### we only require to match the protocol $scan_dl{$pkt_hr->{'src'}} = $dl; &sys_log('auto-assigned danger ' . "level: $dl for IP: $pkt_hr->{'src'}"); return $dl; } } } } } return -1; } sub check_scan_proto() { my ($proto, $scan_hr) = @_; for my $dst (keys %$scan_hr) { return 1 if defined $scan_hr->{$dst}->{$proto}; } return 0; } sub write_global_packet_counters() { open P, "> $config{'PACKET_COUNTER_FILE'}" or die "[*] Could not open $config{'PACKET_COUNTER_FILE'}: $!"; for my $proto (keys %protocols) { next unless defined $proto_ctrs{$proto}; printf P "%8s: %d\n", $proto, $proto_ctrs{$proto}; } close P; return; } sub write_prefix_counters() { open P, "> $config{'IPT_PREFIX_COUNTER_FILE'}" or die "[*] Could not open $config{'IPT_PREFIX_COUNTER_FILE'}: $!"; for my $prefix (keys %ipt_prefixes) { my $count = $ipt_prefixes{$prefix}; print P "$prefix: $count\n"; } close P; return; } sub write_dshield_stats() { open D, "> $config{'DSHIELD_COUNTER_FILE'}" or die "[*] Could not open $config{'DSHIELD_COUNTER_FILE'}: $!"; print D "total emails: $dshield_email_ctr\n", "total packets: $dshield_lines_ctr\n"; close D; return; } sub write_src_packet_counters() { my ($hr, $tcp_absrange, $udp_absrange, $file) = @_; open P, "> $file" or die "[*] Could not open $file: $!"; for my $chain (keys %$hr) { for my $intf (keys %{$hr->{$chain}}) { for my $proto (keys %protocols) { next unless defined $hr->{$chain}->{$intf}->{$proto}; if ($proto eq 'tcp' and $tcp_absrange) { print P "${chain}_${intf}_${proto}: ", "$hr->{$chain}->{$intf}->{$proto} [$tcp_absrange]\n"; } elsif (($proto eq 'udp' or $proto eq 'udplite') and $udp_absrange) { print P "${chain}_${intf}_${proto}: ", "$hr->{$chain}->{$intf}->{$proto} [$udp_absrange]\n"; } else { print P "${chain}_${intf}_${proto}: ", "$hr->{$chain}->{$intf}->{$proto}\n"; } } } } close P; return; } sub collect_errors() { my $bad_packets_ar = shift; open ERR, ">> $config{'FW_ERROR_LOG'}" or die '[*] Could not open ', "$config{'FW_ERROR_LOG'}: $!"; for my $line (@$bad_packets_ar) { print ERR $line; } close ERR; return; } sub log_top_scans() { ### top scanned ports open P, "> $config{'TOP_SCANNED_PORTS_FILE'}.tmp" or die "[*] Could not open $config{'TOP_SCANNED_PORTS_FILE'}: $!"; print P "#\n# Format: \n#\n\n"; if (%top_tcp_ports) { print P "# Top scanned TCP ports:\n"; my $ctr = 0; for my $dp (sort {$top_tcp_ports{$b} <=> $top_tcp_ports{$a}} keys %top_tcp_ports) { printf P "tcp %-5d $top_tcp_ports{$dp}\n", $dp; $ctr++; if ($config{'TOP_PORTS_LOG_THRESHOLD'} > 0) { last if $ctr >= $config{'TOP_PORTS_LOG_THRESHOLD'}; } } print P "\n" if %top_udp_ports; } if (%top_udp_ports) { my $ctr = 0; print P "# Top scanned UDP ports:\n"; for my $dp (sort {$top_udp_ports{$b} <=> $top_udp_ports{$a}} keys %top_udp_ports) { printf P "udp %-5d $top_udp_ports{$dp}\n", $dp; $ctr++; if ($config{'TOP_PORTS_LOG_THRESHOLD'} > 0) { last if $ctr >= $config{'TOP_PORTS_LOG_THRESHOLD'}; } } } close P; move "$config{'TOP_SCANNED_PORTS_FILE'}.tmp", $config{'TOP_SCANNED_PORTS_FILE'}; ### top signature matches open S, "> $config{'TOP_SIGS_FILE'}.tmp" or die "[*] Could not open $config{'TOP_SIGS_FILE'}: $!"; print S "#\n# Format: \"\" ", "\n#\n\n"; my $ctr = 0; for my $sid (sort {$top_sigs{$b} <=> $top_sigs{$a}} keys %top_sigs) { my $found = 0; my $num_sources = keys %{$sig_sources{$sid}}; if (defined $sigs{$sid} and defined $sigs{$sid}{'msg'}) { print S qq|$sid "$sigs{$sid}{'msg'}" $top_sigs{$sid} | . qq|$num_sources $sigs{$sid}{'proto'}\n|; $found = 1; } elsif (defined $fwsnort_sigs{$sid} and defined $fwsnort_sigs{$sid}{'msg'}) { print S qq|$sid "$fwsnort_sigs{$sid}{'msg'}" $top_sigs{$sid} | . qq|$num_sources $fwsnort_sigs{$sid}{'proto'}\n|; $found = 1; } $ctr++ if $found; if ($config{'TOP_SIGS_LOG_THRESHOLD'} > 0) { last if $ctr >= $config{'TOP_SIGS_LOG_THRESHOLD'}; } } close S; move "$config{'TOP_SIGS_FILE'}.tmp", $config{'TOP_SIGS_FILE'}; ### top attackers open A, "> $config{'TOP_ATTACKERS_FILE'}" or die "[*] Could not open $config{'TOP_ATTACKERS_FILE'}: $!"; $ctr = 0; print A "#\n# Format:

", " \n#\n\n"; for my $src (sort {$scan_dl{$b} cmp $scan_dl{$a}} keys %scan_dl) { next unless $scan_dl{$src} >= $config{'MIN_DANGER_LEVEL'}; next unless defined $top_packet_counts{$src} or defined $top_sig_counts{$src}; my $str = sprintf "%-15s %d", $src, $scan_dl{$src}; if (defined $top_packet_counts{$src}) { $str .= " $top_packet_counts{$src}"; } else { $str .= ' 0'; } my $uniq_sigs = 0; for my $sid (keys %sig_sources) { $uniq_sigs++ if defined $sig_sources{$sid}{$src}; } $str .= " $uniq_sigs"; if (defined $top_sig_counts{$src}) { $str .= " $top_sig_counts{$src}"; } else { $str .= ' 0'; } if (defined $local_src{$src}) { $str .= ' 1'; } else { $str .= ' 0'; } print A $str, "\n"; $ctr++; if ($config{'TOP_IP_LOG_THRESHOLD'} > 0) { last if $ctr >= $config{'TOP_IP_LOG_THRESHOLD'}; } } close A; return; } sub scan_logr() { my $curr_scan_hr = shift; return if $benchmark; if ($analyze_mode) { return unless $analyze_write_data; ### Log scan data print "[+] Writing $config{'PSAD_DIR'}/ directories.\n"; print " This may take a while...\n" if ((keys %$curr_scan_hr) > 1000); if ($analysis_emails) { print "[+] Generating email alerts...\n"; unless ($no_whois) { print "[+] Issuing whois lookups (may take several seconds).\n"; } } } SRC: for my $src (keys %$curr_scan_hr) { print STDERR "[+] scan_logr(): source IP: $src\n" if $debug; ### only send alerts for scans that are at least at ### danger level 1 or above. unless ($scan_dl{$src} >= $config{'MIN_DANGER_LEVEL'}) { print STDERR " No alerts sent for $src; danger ", "level $scan_dl{$src} not high enough\n" if $debug; next SRC; } if ($config{'ENABLE_EMAIL_LIMIT_PER_DST'} eq 'N') { unless (defined $scan_email_ctrs{$src}{'email_ctr'}) { $scan_email_ctrs{$src}{'email_ctr'} = 0; } elsif ($config{'EMAIL_LIMIT'} > 0 and $scan_email_ctrs{$src}{'email_ctr'} >= $config{'EMAIL_LIMIT'}) { ### ignore EMAIL_LIMIT if it is zero unless (defined $scan_email_ctrs{$src}{'stop_email'} or $config{'EMAIL_LIMIT_STATUS_MSG'} eq 'N') { &email_limit_reached($src, ''); } &sys_log("email limit reached for src: $src"); next SRC; } unless ($no_email_alerts) { $scan_email_ctrs{$src}{'email_ctr'}++; } if ($config{'EMAIL_THROTTLE'} > 1) { next SRC if (($scan_email_ctrs{$src}{'email_ctr'} % $config{'EMAIL_THROTTLE'}) == 0); } } DST: for my $dst (keys %{$curr_scan_hr->{$src}}) { ### see if we have already sent an alert for $src ### (against $dst) for this danger level. if ($config{'ALERT_ALL'} eq 'N') { next DST if $scan{$src}{$dst}{'alerted'}; } my $syslog_flags = ''; my $src_dns_str = ''; my $dst_dns_str = ''; my $rdns = ''; my $src_subj = ''; my $dst_subj = ''; my $src_mac = ''; my $dst_mac = ''; my $syslog_range = ''; my $tcp_newrange = ''; my $tcp_absrange = ''; my $udp_newrange = ''; my $udp_absrange = ''; my $tcp_newpkts = 0; my $udp_newpkts = 0; my $icmp_newpkts = 0; my $other_proto_newpkts = 0; my $tcp_f = 0; my $udp_f = 0; my $icmp_f = 0; my $scan_style_str = ''; my $whois_info_ar; ### get the current danger level and the absolute number ### of packets used in the scan so far my $curr_dl = $scan_dl{$src}; if ($config{'ENABLE_EMAIL_LIMIT_PER_DST'} eq 'Y') { unless (defined $scan_email_ctrs{$src}{$dst}{'email_ctr'}) { $scan_email_ctrs{$src}{$dst}{'email_ctr'} = 0; } elsif ($config{'EMAIL_LIMIT'} > 0 and $scan_email_ctrs{$src}{$dst}{'email_ctr'} >= $config{'EMAIL_LIMIT'}) { ### ignore EMAIL_LIMIT if it is zero unless (defined $scan_email_ctrs{$src}{$dst}{'stop_email'} or $config{'EMAIL_LIMIT_STATUS_MSG'} eq 'N') { &email_limit_reached($src, $dst); } &sys_log("email limit reached for src: $src " . "against dst: $dst"); next DST; } unless ($no_email_alerts) { $scan_email_ctrs{$src}{$dst}{'email_ctr'}++; } if ($config{'EMAIL_THROTTLE'} > 1) { next DST if (($scan_email_ctrs{$src}{$dst}{'email_ctr'} % $config{'EMAIL_THROTTLE'}) == 0); } } print STDERR "[+] scan_logr(): dst IP: $dst\n" if $debug; ### make $src directory here in /var/log/psad ### unless it already exists mkdir "$config{'PSAD_DIR'}/${src}", 0700 unless -d "$config{'PSAD_DIR'}/${src}"; my $src_dir = "$config{'PSAD_DIR'}/${src}"; my $dl_file = "${src_dir}/danger_level"; my $posf_file = "${src_dir}/os_guess"; my $p0f_file = "${src_dir}/p0f_guess"; my $email_file = "${src_dir}/${dst}_email_alert"; my $log_sigs = "${src_dir}/${dst}_signatures"; my $s_time_file = "${src_dir}/${dst}_start_time"; my $pkt_ctr_file = "${src_dir}/${dst}_packet_ctr"; my $ecount_file = "${src_dir}/email_ctr"; if ($config{'ENABLE_EMAIL_LIMIT_PER_DST'} eq 'Y') { $ecount_file = "${src_dir}/${dst}_email_ctr"; } ### print the current danger level to the danger_level file. open DL, "> $dl_file" or die "[*] Could not open $dl_file: $!"; print DL $curr_dl, "\n"; close DL; ### write out the TOS-based os guess (if there is one). if (defined $posf{$src} and defined $posf{$src}{'guess'}) { open P, "> $posf_file" or die "[*] Could not open $posf_file: $!"; print P $posf{$src}{'guess'}, "\n"; close P; } ### write out the p0f-based os guess(es) (if there is one). if (defined $p0f{$src}) { open P, "> $p0f_file" or die "[*] Could not open $p0f_file: $!"; for my $os (keys %{$p0f{$src}}) { print P "$os\n"; } close P; } ### write out the start time. open T, "> $s_time_file" or die "[*] Could not open $s_time_file: $!"; print T $scan{$src}{$dst}{'s_time'}, "\n"; close T; $src_mac = $curr_scan_hr->{$src}->{$dst}->{'s_mac'} if defined $curr_scan_hr->{$src}->{$dst}->{'s_mac'}; $dst_mac = $curr_scan_hr->{$src}->{$dst}->{'d_mac'} if defined $curr_scan_hr->{$src}->{$dst}->{'d_mac'}; if (defined $scan{$src}{$dst}{'tcp'} and defined $scan{$src}{$dst}{'tcp'}{'abs_sp'}) { my $tcp_s_port = $scan{$src}{$dst}{'tcp'}{'abs_sp'}; my $tcp_e_port = $scan{$src}{$dst}{'tcp'}{'abs_ep'}; if ($tcp_s_port == $tcp_e_port) { $tcp_absrange = $tcp_s_port; } else { $tcp_absrange = "$tcp_s_port-$tcp_e_port"; } } if (defined $curr_scan_hr->{$src}->{$dst}->{'tcp'} and defined $curr_scan_hr->{$src}->{$dst}->{'tcp'}->{'strtp'}) { $tcp_f = 1; my $tcp_s_port = $curr_scan_hr->{$src}->{$dst}->{'tcp'}->{'strtp'}; my $tcp_e_port = $curr_scan_hr->{$src}->{$dst}->{'tcp'}->{'endp'}; if ($tcp_s_port == $tcp_e_port) { $tcp_newrange = $tcp_s_port; } else { $tcp_newrange = "$tcp_s_port-$tcp_e_port"; } $tcp_newpkts = $curr_scan_hr->{$src}->{$dst}->{'tcp'}->{'pkts'}; } if (defined $scan{$src}{$dst}{'udp'} and $scan{$src}{$dst}{'udp'}{'abs_sp'}) { my $udp_s_port = $scan{$src}{$dst}{'udp'}{'abs_sp'}; my $udp_e_port = $scan{$src}{$dst}{'udp'}{'abs_ep'}; if ($udp_s_port == $udp_e_port) { $udp_absrange = $udp_s_port; } else { $udp_absrange = "$udp_s_port-$udp_e_port"; } } if (defined $curr_scan_hr->{$src}->{$dst}->{'udp'} and $curr_scan_hr->{$src}->{$dst}->{'udp'}->{'strtp'}) { $udp_f = 1; my $udp_s_port = $curr_scan_hr->{$src}->{$dst}->{'udp'}->{'strtp'}; my $udp_e_port = $curr_scan_hr->{$src}->{$dst}->{'udp'}->{'endp'}; if ($udp_s_port == $udp_e_port) { $udp_newrange = $udp_s_port; } else { $udp_newrange = "$udp_s_port-$udp_e_port"; } $udp_newpkts = $curr_scan_hr->{$src}->{$dst}->{'udp'}->{'pkts'}; } if (defined $curr_scan_hr->{$src}->{$dst}->{'icmp'}) { $icmp_f = 1; $icmp_newpkts = $curr_scan_hr->{$src}->{$dst}->{'icmp'}->{'pkts'}; } for my $str (keys %{$curr_scan_hr->{$src}->{$dst}}) { next if $str eq 'tcp' or $str eq 'udp' or $str eq 'udplite' or $str eq 'icmp' or $str eq 'icmp6' or $str eq 'tot_protocols'; next unless defined $scan{$src}{$dst}{$str}; next unless defined $curr_scan_hr->{$src}->{$dst}->{$str}->{'pkts'}; $other_proto_newpkts += $curr_scan_hr->{$src}->{$dst}->{$str}->{'pkts'}; } ### write out the overall packet counters for $src. &write_src_packet_counters($scan{$src}{$dst}{'chain'}, $tcp_absrange, $udp_absrange, $pkt_ctr_file); ### get reverse dns info $src_subj = $src; $dst_subj = $dst; unless ($no_rdns) { $src_dns_str = &get_dns_info($src); if ($src_dns_str) { $src_subj = $src_dns_str; } else { $src_dns_str = '[No reverse dns info available]'; } $dst_dns_str = &get_dns_info($dst); if ($dst_dns_str) { $dst_subj = $dst_dns_str; } else { $dst_dns_str = '[No reverse dns info available]'; } } ### get whois info my $whois_which_ip_str = 'source IP'; unless ($no_whois) { my $lookup_ip = $src; ### default ### we're most likely interested in the whois information for ### a non local IP address if ($config{'ENABLE_WHOIS_FORCE_SRC_IP'} eq 'N' and &is_local($src, new NetAddr::IP($src)) and not &is_local($dst, new NetAddr::IP($dst))) { $whois_which_ip_str = 'destination IP'; $lookup_ip = $dst; } $whois_info_ar = &get_whois_info($lookup_ip, "${src_dir}/${lookup_ip}_whois"); if ($debug and $verbose) { print STDERR for @$whois_info_ar; } } print STDERR "[+] scan_logr(): generating email.....\n" if $test_mode or $debug; ### get the absolute starting time for the scan and the ### current time my $s_time = ''; my $abs_s_time = scalar localtime $scan{$src}{$dst}{'s_time'}; if (not $analyze_mode and ((time() - $config{'CHECK_INTERVAL'}) < $scan{$src}{$dst}{'s_time'})) { $s_time = $abs_s_time; } else { $s_time = scalar localtime((time() - $config{'CHECK_INTERVAL'})); } my $time = scalar localtime(); ### email file handle my $fh; ### open the email alert file if ($no_daemon) { $fh = *STDOUT; } else { open E, "> $email_file" or die "[*] Could not open $email_file: $!"; $fh = *E; } print $fh "=-=-=-=-=-=-=-=-=-=-=-= $time =-=-=-=-=-=-=-=", "-=-=-=-=\n\n\n"; printf $fh "%${log_len}s%s", 'Danger level: ', "[$scan_dl{$src}] (out of 5)"; if ($scan{$src}{$dst}{'tot_protocols'} > 1) { print $fh ' Multi-Protocol'; } if (defined $auto_assigned_msg{$src}) { print $fh ' Auto-assigned'; delete $auto_assigned_msg{$src}; } print $fh "\n\n"; if ($curr_scan_hr->{$src}->{$dst}->{'tot_protocols'} >= $config{'PROTOCOL_SCAN_THRESHOLD'}) { printf $fh "%${log_len}s%s%s\n", 'IP Protocol scan: ', "[$curr_scan_hr->{$src}->{$dst}->{'tot_protocols'}", ' unique protocols, Nmap -sO]'; $scan_style_str .= 'Nmap -sO IP protocol scan,'; } if ($tcp_f) { printf $fh "%${log_len}s%s\n", 'Scanned TCP ports: ', "[$tcp_newrange: $tcp_newpkts packets]"; my $prefix = 'TCP flags: '; for my $flags (keys %{$curr_scan_hr->{$src}-> {$dst}->{'tcp'}->{'flags'}}) { my $nmap_opts = ''; $syslog_flags .= "$flags "; my $n_pkts = $curr_scan_hr->{$src}->{$dst}-> {'tcp'}->{'flags'}->{$flags}; ### FUTURE: replace this with a simple hash lookup if ($flags eq 'SYN') { $nmap_opts = '-sT or -sS'; } elsif ($flags eq 'FIN') { $nmap_opts = '-sF'; } elsif ($flags eq 'URG PSH FIN') { $nmap_opts = '-sX'; } elsif ($flags eq 'NULL') { $nmap_opts = '-sN'; } elsif ($flags eq 'URG PSH SYN FIN') { $nmap_opts = '-O'; } if ($curr_scan_hr->{$src}->{$dst}->{'is_topera'}) { $scan_style_str = "Topera $flags scan"; printf $fh "%${log_len}s%s\n", $prefix, "[$flags: $n_pkts packets, Topera $flags scan]"; } else { if ($nmap_opts) { printf $fh "%${log_len}s%s\n", $prefix, "[$flags: $n_pkts packets, Nmap: $nmap_opts]"; } else { printf $fh "%${log_len}s%s\n", $prefix, "[$flags: $n_pkts packets]"; } if ($scan_style_str) { $scan_style_str .= " $nmap_opts scan,"; } else { $scan_style_str = "Nmap $nmap_opts scan,"; } $prefix = ''; } } if (defined $curr_scan_hr->{$src}->{$dst}->{'tcp'}->{'chain'}) { &print_chains_and_prefixes( $curr_scan_hr->{$src}->{$dst}->{'tcp'}->{'chain'}, $fh); } $syslog_flags =~ s/\s*$//; $syslog_range = "tcp: [$tcp_newrange]"; $syslog_range .= " flags: $syslog_flags" if $syslog_flags; } if ($udp_f) { printf $fh "%${log_len}s%s\n", 'Scanned UDP ports: ', "[$udp_newrange: $udp_newpkts packets, Nmap: -sU]"; if (defined $curr_scan_hr->{$src}->{$dst}->{'udp'}->{'chain'}) { &print_chains_and_prefixes( $curr_scan_hr->{$src}->{$dst}->{'udp'}->{'chain'}, $fh); } $syslog_range = "udp: [$udp_newrange]"; $scan_style_str .= ' -sU scan,'; } if ($icmp_f) { printf $fh "%${log_len}s%s\n", 'icmp packets: ', "[$icmp_newpkts]"; if (defined $curr_scan_hr->{$src}->{$dst}->{'icmp'}->{'chain'}) { &print_chains_and_prefixes( $curr_scan_hr->{$src}->{$dst}->{'icmp'}->{'chain'}, $fh); } } $scan_style_str =~ s/\,$//; printf $fh "\n%${log_len}s%s\n", 'Source: ', $src; printf $fh "%${log_len}s%s\n", 'DNS: ', $src_dns_str unless $no_rdns; if ($config{'ENABLE_MAC_ADDR_REPORTING'} eq 'Y') { printf $fh "%${log_len}s%s\n", 'MAC: ', $src_mac if $src_mac; } unless ($no_posf) { if (defined $p0f{$src}) { ### prefer p0f-based fingerprinting ### any p0f fingerprint that contains a "@" is an ### approximate match my $found_exact_match = 0; for my $os (keys %{$p0f{$src}}) { if ($os !~ /\@/) { $found_exact_match = 1; last; } } my $printed_guess_line = 0; for my $os (keys %{$p0f{$src}}) { if ($found_exact_match) { next if $os =~ /\@/; } if ($printed_guess_line) { printf $fh "%${log_len}s%s\n", ' ', $os; } else { printf $fh "%${log_len}s%s\n", 'OS guess: ', $os; } $printed_guess_line = 1; } } elsif (defined $posf{$src}{'guess'}) { printf $fh "%${log_len}s%s\n", 'OS guess: ', $posf{$src}{'guess'}; } } printf $fh "\n%${log_len}s%s\n", 'Destination: ', $dst; printf $fh "%${log_len}s%s\n", 'DNS: ', $dst_dns_str unless $no_rdns; if ($config{'ENABLE_MAC_ADDR_REPORTING'} eq 'Y') { printf $fh "%${log_len}s%s\n", 'MAC: ', $dst_mac if $dst_mac; } print $fh "\n"; ### print the overall stats since the scan began printf $fh "%${log_len}s%s\n", 'Overall scan start: ', $abs_s_time; if ($config{'ENABLE_EMAIL_LIMIT_PER_DST'} eq 'Y') { printf $fh "%${log_len}s%s\n", 'Total email alerts: ', $scan_email_ctrs{$src}{$dst}{'email_ctr'}; } else { printf $fh "%${log_len}s%s\n", 'Total email alerts: ', $scan_email_ctrs{$src}{'email_ctr'}; } if ($tcp_absrange) { printf $fh "%${log_len}s%s\n", 'Complete TCP range: ', "[$tcp_absrange]"; } if ($udp_absrange) { printf $fh "%${log_len}s%s\n", 'Complete UDP range: ', "[$udp_absrange]"; } if (defined $curr_scan_hr->{$src}->{$dst}->{'syslog_host'}) { my $syslog_hosts = ''; $syslog_hosts .= "$_, " for keys %{$curr_scan_hr->{$src}->{$dst}->{'syslog_host'}}; $syslog_hosts =~ s/\,\s+$//; if ($syslog_hosts =~ /\,/) { printf $fh "%${log_len}s%s\n", 'Syslog hostnames: ', $syslog_hosts; } else { printf $fh "%${log_len}s%s\n", 'Syslog hostname: ', $syslog_hosts; } } printf $fh "\n"; printf $fh "%${log_len}s\n", 'Global stats: '; printf $fh "%${log_len}s%-9s%-12s%-11s%-10s\n", '', 'chain:', 'interface:', 'protocol:', 'packets:'; for my $chain (keys %{$scan{$src}{$dst}{'chain'}}) { for my $intf (keys %{$scan{$src}{$dst}{'chain'}{$chain}}) { for my $proto (sort {$a cmp $b} keys %{$scan{$src}{$dst}{'chain'}{$chain}{$intf}}) { my $pkts = $scan{$src}{$dst}{'chain'}{$chain}{$intf}{$proto}; if (defined $protocol_strings{$proto}) { printf $fh "%${log_len}s%-9s%-12s%-11s%-10s\n", '', $chain, $intf, $protocol_strings{$proto}{'name'}, $pkts; } else { printf $fh "%${log_len}s%-9s%-12s%-11s%-10s\n", '', $chain, $intf, $proto, $pkts; } } } } ### print out any matched signatures to the email ### alert file and also to the signature log &scan_logr_signatures($src, $dst, $fh, $log_sigs); ### write a scan message to syslog my $syslog_str = 'scan detected '; $syslog_str .= "($scan_style_str): " if $scan_style_str; $syslog_str .= "$src -> $dst"; $syslog_str .= " $syslog_range" if $syslog_range; $syslog_str .= " tcp pkts: $tcp_newpkts" if $tcp_newpkts; $syslog_str .= " udp pkts: $udp_newpkts" if $udp_newpkts; $syslog_str .= " icmp pkts: $icmp_newpkts" if $icmp_newpkts; $syslog_str .= " other proto pkts: $other_proto_newpkts" if $other_proto_newpkts; $syslog_str .= " DL: $curr_dl"; &sys_log($syslog_str); unless ($no_whois) { print $fh "\n[+] Whois Information ($whois_which_ip_str):\n"; for my $line (@$whois_info_ar) { print $fh $line; } } print $fh "\n=-=-=-=-=-=-=-=-=-=-=-= $time =-=-=-=-=-=-=-=", "-=-=-=-=\n"; close $fh unless $no_daemon; if ($curr_dl >= $config{'EMAIL_ALERT_DANGER_LEVEL'} and not $no_daemon) { unless ($analyze_mode and not $analysis_emails) { my $subject; if ($analyze_mode) { $subject = "$config{'MAIL_ALERT_PREFIX'} " . "DL$curr_dl (analysis " . "mode) src: $src_subj dst: $dst_subj"; } else { $subject = "$config{'MAIL_ALERT_PREFIX'} " . "DL$curr_dl src: " . "$src_subj dst: $dst_subj"; } &send_mail($subject, $email_file, $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); ### print the number of email alerts we have sent open E, "> $ecount_file" or die "[*] Could not open ", "$ecount_file: $!"; if ($config{'ENABLE_EMAIL_LIMIT_PER_DST'} eq 'Y') { print E $scan_email_ctrs{$src}{$dst}{'email_ctr'}, "\n"; } else { print E $scan_email_ctrs{$src}{'email_ctr'}, "\n"; } close E; if ($config{'ENABLE_EXT_SCRIPT_EXEC'} eq 'Y') { if ($config{'EXEC_EXT_SCRIPT_PER_ALERT'} eq 'Y') { &exec_external_script($src); } else { &exec_external_script($src) unless defined $scan_ext_exec{$src}; } } } } ### we have sent an alert for $dst if ($config{'ALERT_ALL'} eq 'N') { $scan{$src}{$dst}{'alerted'} = 1; } } } return; } sub scan_logr_signatures() { my ($src, $dst, $email_fh, $log_sigs_file) = @_; my @del_sigs = (); my @log_sigs = (); my $found_sid = 0; for my $proto (keys %protocols) { next unless defined $scan{$src}{$dst}{$proto}; my $hr = $scan{$src}{$dst}{$proto}; next unless (defined $hr->{'sid'} or defined $hr->{'invalid_type'} or defined $hr->{'invalid_code'}); print $email_fh "\n\n[+] " . uc($proto) . " scan signatures:\n\n"; print STDERR "[+] scan_logr_signatures(): src: $src dst: $dst ", "proto: $proto\n" if $debug; my $sid_ctr = 0; for my $sid (keys %{$hr->{'sid'}}) { $sid_ctr++; if ($sid_ctr <= $config{'SIG_SID_SYSLOG_THRESHOLD'}) { $found_sid = 1; } for my $chain (keys %{$hr->{'sid'}->{$sid}}) { my $sig_hr = ''; my $email_chain = $chain; my $is_fwsnort = $hr->{'sid'}->{$sid} ->{$chain}->{'is_fwsnort'}; if ($is_fwsnort) { next unless defined $fwsnort_sigs{$sid}; $sig_hr = $fwsnort_sigs{$sid}; } else { next unless defined $sigs{$sid}; $sig_hr = $sigs{$sid}; } my $dp = 0; my $flags = 'NA'; if ($proto eq 'tcp' or $proto eq 'udp' or $proto eq 'udplite') { $dp = $hr->{'sid'}->{$sid}->{$chain}->{'dp'}; if ($proto eq 'tcp') { $flags = $hr->{'sid'}->{$sid}->{$chain}->{'flags'}; } } my $pkts = $hr->{'sid'}->{$sid}->{$chain}->{'pkts'}; ### string for the _signatures file push @log_sigs, "$hr->{'sid'}->{$sid}->{$chain}->{'time'} " . qq|$sid $pkts "$sig_hr->{'msg'}" $chain $proto $dp "$flags" | . "$is_fwsnort $sig_hr->{'is_psad_id'}"; if ($config{'ENABLE_SIG_MSG_SYSLOG'} eq 'Y' and $sid_ctr <= $config{'SIG_MSG_SYSLOG_THRESHOLD'}) { my $sig_log_str = "src: $src signature match: " . qq|"$sig_hr->{'msg'}" (sid: $sid) $proto|; if ($proto eq 'tcp' or $proto eq 'udp' or $proto eq 'udplite') { $sig_log_str .= " port: $dp"; } if ($is_fwsnort) { $sig_log_str .= ' fwsnort'; if (defined $hr->{'sid'}->{$sid}->{$chain} ->{'fwsnort_estab'}) { if ($chain eq 'INPUT') { $sig_log_str .= ' chain: FWSNORT_INPUT'; $email_chain = 'FWSNORT_INPUT'; } elsif ($chain eq 'FORWARD') { $sig_log_str .= ' chain: FWSNORT_FORWARD'; $email_chain = 'FWSNORT_FORWARD'; } elsif ($chain eq 'OUTPUT') { $sig_log_str .= ' chain: FWSNORT_OUTPUT'; $email_chain = 'FWSNORT_OUTPUT'; } if ($hr->{'sid'}->{$sid} ->{$chain}->{'fwsnort_estab'}) { $sig_log_str .= '_ESTAB'; $email_chain .= '_ESTAB'; } } if (defined $hr->{'sid'}->{$sid}->{$chain} ->{'fwsnort_rnum'} and $hr->{'sid'}->{$sid} ->{$chain}->{'fwsnort_rnum'}) { $sig_log_str .= ' rule: ' . $hr->{'sid'}->{$sid}->{$chain} ->{'fwsnort_rnum'}; } } ### write the signature match to syslog &sys_log($sig_log_str); } print $email_fh qq| "$sig_hr->{'msg'}"\n|; if ($proto eq 'tcp' or $proto eq 'udp' or $proto eq 'udplite') { if ($chain eq 'INPUT') { if (defined $local_ports{$proto} and defined $local_ports{$proto}{$dp}) { print $email_fh " dst port: $dp (server ", "bound to local port!)\n"; print $email_fh " flags: $flags\n" if $proto eq 'tcp'; } else { print $email_fh " dst port: $dp (no server ", "bound to local port)\n"; print $email_fh " flags: $flags\n" if $proto eq 'tcp'; } } else { print $email_fh " dst port: $dp\n"; print $email_fh " flags: $flags\n" if $proto eq 'tcp'; } } if ($is_fwsnort) { for my $content (@{$sig_hr->{'content'}}) { print $email_fh qq( content: "$content"\n); } } my $sid_str = ''; if ($sig_hr->{'is_psad_id'}) { $sid_str = " psad_id: $sid"; } else { $sid_str = " sid: $sid"; } if (defined $sig_hr->{'psad_derived_sids'}) { $sid_str .= ' (derived from: '; $sid_str .= "$_ " for @{$sig_hr->{'psad_derived_sids'}}; $sid_str =~ s|\s*$||; $sid_str .= ')'; } print $email_fh "$sid_str\n", " chain: $email_chain\n", " packets: $pkts\n"; if ($sig_hr->{'classtype'}) { print $email_fh " classtype: $sig_hr->{'classtype'}\n"; } if (defined $sig_hr->{'reference'} and $sig_hr->{'reference'}) { for my $reftype (keys %{$sig_hr->{'reference'}}) { my $baseurl = ''; if (defined $snort_ref_baseurl{$reftype}) { $baseurl = $snort_ref_baseurl{$reftype}; } else { next; } for my $ref (@{$sig_hr->{'reference'}->{$reftype}}) { print $email_fh " reference: ($reftype) ", "${baseurl}$ref\n"; } } } print $email_fh "\n"; } } if (defined $hr->{'invalid_type'}) { for my $type (keys %{$hr->{'invalid_type'}}) { for my $chain (keys %{$hr->{'invalid_type'}->{$type}}) { my $pkts = $hr->{'invalid_type'}-> {$type}->{$chain}->{'pkts'}; print $email_fh qq| Invalid ICMP type "$type" chain=$chain packets=$pkts\n|; } } } if (defined $hr->{'invalid_code'}) { for my $type (keys %{$hr->{'invalid_code'}}) { for my $code (keys %{$hr->{'invalid_code'}->{$type}}) { for my $chain (keys %{$hr->{'invalid_code'}->{$type}->{$code}}) { my $pkts = $hr->{'invalid_code'}-> {$type}->{$code}->{$chain}->{'pkts'}; if ($proto eq 'icmp') { print $email_fh qq| Invalid ICMP code "$code" for ICMP |, qq|"$valid_icmp_types{$type}{'text'}" packet\n|, " chain=$chain packets=$pkts\n"; } elsif ($proto eq 'icmp6') { print $email_fh qq| Invalid ICMP code "$code" for ICMP |, qq|"$valid_icmp6_types{$type}{'text'}" packet\n|, " chain=$chain packets=$pkts\n"; } } } } } push @del_sigs, $hr; } if (@log_sigs) { open LS, ">> $log_sigs_file.tmp" or die "[*] Could not open $log_sigs_file: $!"; print LS "#\n# Format: \"\" ", " \"\" \n#\n\n"; for my $str (@log_sigs) { print LS $str, "\n"; } close LS; move "$log_sigs_file.tmp", $log_sigs_file; } unless ($config{'SHOW_ALL_SIGNATURES'} eq 'Y') { for my $hr (@del_sigs) { ### need to delete the current signature so it ### won't show up in the next alert delete $hr->{'sid'} if defined $hr->{'sid'}; delete $hr->{'invalid_type'} if defined $hr->{'invalid_type'}; delete $hr->{'invalid_code'} if defined $hr->{'invalid_code'}; } } close LS; return; } sub print_chains_and_prefixes() { my ($chain_hr, $fh) = @_; for my $chain (keys %$chain_hr) { for my $prefix (keys %{$chain_hr->{$chain}}) { my $count = $chain_hr->{$chain}->{$prefix}; if ($prefix eq '*noprfx*') { printf $fh "%${log_len}s%s\n", 'iptables chain: ', "$chain, $count packets"; } else { my $print_chain = $chain; my $fwsnort_rnum = 0; if ($prefix =~ /$config{'SNORT_SID_STR'}/) { if ($prefix =~ /\[(\d+)\]/) { $fwsnort_rnum = $1; } if ($chain eq 'INPUT') { $print_chain = 'FWSNORT_INPUT'; } elsif ($chain eq 'FORWARD') { $print_chain = 'FWSNORT_FORWARD'; } elsif ($chain eq 'OUTPUT') { $print_chain = 'FWSNORT_OUTPUT'; } if ($prefix =~ /ESTAB/) { $print_chain .= '_ESTAB'; } } printf $fh "%${log_len}s%s\n", 'iptables chain: ', qq/$print_chain (prefix "$prefix"), $count packets/; if ($fwsnort_rnum) { printf $fh "%${log_len}s%s\n", 'fwsnort rule: ', $fwsnort_rnum; } } } } return; } sub exec_external_script() { my $src = shift; $scan_ext_exec{$src} = ''; my $cmd = $config{'EXTERNAL_SCRIPT'}; $cmd =~ s/SRCIP/$src/; my $pid; if ($pid = fork()) { local $SIG{'ALRM'} = sub {die "[*] External script timeout.\n"}; alarm 30; ### the external script should be finished in 30 secs. eval { waitpid($pid, 0); }; alarm 0; if ($@) { kill 9, $pid; } } else { die "[*] Could not fork for external script: $!" unless defined $pid; exec qq{$cmd}; } return; } sub renew_auto_blocked_ips() { ### note that if we are renewing IP blocking rules, we just use ### the AUTO_BLOCK_TIMEOUT value initially and then the AUTO_BLOCK_DLN... ### values will take over as psad gets up and running my $timeout_str = '.'; if ($config{'AUTO_BLOCK_TIMEOUT'} > 0) { $timeout_str = "for $config{'AUTO_BLOCK_TIMEOUT'} seconds."; } else { $timeout_str = '(unlimited time).'; } if ($config{'IPTABLES_BLOCK_METHOD'} eq 'Y' and -e $config{'AUTO_BLOCK_IPT_FILE'}) { open B, "< $config{'AUTO_BLOCK_IPT_FILE'}" or die "[*] Could not open $config{'AUTO_BLOCK_IPT_FILE'}: $!"; my @lines = ; close B; for my $line (@lines) { if ($line =~ /^\s*($ipv4_re)\s+(\d+)/) { my $ip = $1; my $orig_block_time = $2; if ($config{'AUTO_BLOCK_TIMEOUT'} == 0) { ### block the IP address (note that checks are built ### into this function to not add a duplicate rule) &ipt_block($ip, 'renew'); ### reset the block time to the original time that ### the rule was added so the rule does not stay ### around longer than it should. $auto_blocked_ips{$ip} = $orig_block_time; } else { if ((time() - $orig_block_time) > $config{'AUTO_BLOCK_TIMEOUT'}) { ### timeout has expired, so we should remove any ### existing blocking rule &ipt_rm_block($ip); } else { ### block the IP address (note that checks are built ### into this function to not add a duplicate rule) &ipt_block($ip, 'renew'); $auto_blocked_ips{$ip} = $orig_block_time; } } } } } if ($config{'TCPWRAPPERS_BLOCK_METHOD'} eq 'Y' && -e $config{'ETC_HOSTS_DENY_FILE'}) { open B, "< $config{'ETC_HOSTS_DENY_FILE'}" or die "[*] Could not open $config{'ETC_HOSTS_DENY_FILE'}: $!"; my @lines = ; close B; for my $line (@lines) { if ($line =~ /^\s*($ipv4_re)\s+(\d+)/) { my $ip = $1; my $orig_block_time = $2; if ($config{'AUTO_BLOCK_TIMEOUT'} == 0) { ### block the IP address (note that checks are built ### into this function to not add a duplicate rule) if (&tcpwr_test_block($ip)) { $auto_blocked_ips{$ip} = $orig_block_time unless defined $auto_blocked_ips{$ip}; } else { &sys_log("renewing tcpwrappers auto-block " . "against $ip $timeout_str"); &tcpwr_block($ip); if ($config{'ENABLE_RENEW_BLOCK_EMAILS'} eq 'Y') { &send_mail("$config{'MAIL_STATUS_PREFIX'} RENEWED " . "tcpwrappers BLOCK against $ip $timeout_str", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } $auto_blocked_ips{$ip} = $orig_block_time; } } else { if ((time() - $orig_block_time) > $config{'AUTO_BLOCK_TIMEOUT'}) { ### timeout has expired, so we should remove any ### existing blocking rule &tcpwr_rm_block($ip); } else { ### block the IP address (note that checks are built ### into this function to not add a duplicate rule) if (&tcpwr_test_block($ip)) { $auto_blocked_ips{$ip} = $orig_block_time unless defined $auto_blocked_ips{$ip}; } else { &sys_log("renewing tcpwrappers auto-block " . "against $ip $timeout_str"); &tcpwr_block($ip); if ($config{'ENABLE_RENEW_BLOCK_EMAILS'} eq 'Y') { &send_mail("$config{'MAIL_STATUS_PREFIX'} RENEWED " . "tcpwrappers BLOCK against $ip $timeout_str", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } $auto_blocked_ips{$ip} = $orig_block_time; } } } } } } return; } sub sockwrite_flush_auto_rules() { if (-e $config{'PSAD_PID_FILE'}) { if (&is_running($config{'PSAD_PID_FILE'}) and $config{'ENABLE_AUTO_IDS'} eq 'Y') { print "[+] Flushing psad chains via running psad daemon within\n", " $config{'CHECK_INTERVAL'} seconds.\n"; die "[*] $config{'AUTO_IPT_SOCK'} file is missing. Did something ", "remove it?\n You should stop psad, run the -F command ", "again, then restart psad." unless -e $config{'AUTO_IPT_SOCK'}; ### open domain socket with running psad process my $sock = IO::Socket::UNIX->new($config{'AUTO_IPT_SOCK'}) or die "[*] Could not acquire $config{'AUTO_IPT_SOCK'} ", "socket: $!"; if ($fw_del_chains) { print $sock "flush delchains\n"; } else { print $sock "flush\n"; } close $sock; return 0; } } ### if we make it here then we have to flush manually because ### it looks like psad is not running. if ($fw_del_chains) { print "[+] Flushing and deleting psad chains.\n"; } else { print "[+] Flushing psad chains.\n"; } &flush_auto_blocked_ips(); return 0; } sub flush_auto_blocked_ips() { my %ipt_opts = ( 'iptables' => $cmds{'iptables'}, 'iptout' => $config{'IPT_OUTPUT_FILE'}, 'ipterr' => $config{'IPT_ERROR_FILE'} ); $ipt_opts{'debug'} = 1 if $debug; $ipt_opts{'verbose'} = 1 if $verbose; my $ipt = new IPTables::ChainMgr(%ipt_opts) or die '[*] Could not acquire IPTables::ChainMgr object.'; &sys_log('flushing existing psad iptables ' . 'auto-response chains'); if (@ipt_config) { for my $hr (@ipt_config) { my $table = $hr->{'table'}; my $from_chain = $hr->{'from_chain'}; my $to_chain = $hr->{'to_chain'}; my ($rv, $out_ar, $err_ar) = $ipt->chain_exists($table, $to_chain); if ($rv) { if ($fw_del_chains) { ($rv, $out_ar, $err_ar) = $ipt->delete_chain($table, $from_chain, $to_chain); if ($rv) { &sys_log("deleted $table table $to_chain chain"); } else { &sys_log("could not delete $table " . "table $to_chain chain"); &sys_log_mline($err_ar); } } else { ($rv, $out_ar, $err_ar) = $ipt->flush_chain($table, $to_chain); if ($rv) { &sys_log("flushed: $to_chain"); } else { &sys_log("could not flush: $to_chain"); &sys_log_mline($err_ar); } } } } } ### zero out the in-memory cache of blocked addresses %auto_blocked_ips = (); if (-e $config{'AUTO_BLOCK_IPT_FILE'}) { ### we have removed the iptables block rules, so truncate ### the cache file. &truncate_file($config{'AUTO_BLOCK_IPT_FILE'}); } if (-e $config{'AUTO_BLOCK_TCPWR_FILE'}) { my $found_blocked = 0; &sys_log("removing tcpwrapper auto-generated block rules."); open B, "< $config{'AUTO_BLOCK_TCPWR_FILE'}" or die "[*] Could not open $config{'AUTO_BLOCK_TCPWR_FILE'}: $!"; my @lines = ; close B; for my $line (@lines) { if ($line =~ /($ipv4_re)/) { my $ip = $1; ### remove block rules for $ip if it has been blocked &tcpwr_rm_block($ip) if &tcpwr_test_block($ip); $found_blocked = 1; } } ### we have removed the tcpwrapper block rules, so truncate ### the cache file. &truncate_file($config{'AUTO_BLOCK_TCPWR_FILE'}); unless ($found_blocked) { &sys_log("currently there are no auto-generated " . "tcpwrapper blocking rules in effect."); } } return; } sub get_auto_response_domain_sock() { ### $ipt_sock is global $ipt_sock = IO::Socket::UNIX->new( Type => SOCK_STREAM, Local => $config{'AUTO_IPT_SOCK'}, Listen => SOMAXCONN, Timeout => .1 ) or die "[*] Could not acquire auto-response domain ", "socket $config{'AUTO_IPT_SOCK'}: $!"; return; } sub check_auto_response_sock() { ### we expect that the AUTO_IPT_SOCK file should exist ### in the filesystem. If not, then something deleted it ### and we can recover by reopening it. return if -e $config{'AUTO_IPT_SOCK'}; &sys_log("domain socket $config{'AUTO_IPT_SOCK'} does not " . "exist (a separate process must have deleted it), recreating it."); close $ipt_sock; ### reopen the socket &get_auto_response_domain_sock(); return; } sub ipt_block() { my ($ip, $renewed_status) = @_; return unless $ip and $ip =~ /^$ipv4_re$/; ### see if the IP is already blocked if (defined $auto_blocked_ips{$ip}) { print STDERR "[-] ipt_block($ip) already blocked.\n" if $debug; return; } print STDERR "[+] ipt_block($ip)\n" if $debug; my %ipt_opts = ( 'iptables' => $cmds{'iptables'}, 'iptout' => $config{'IPT_OUTPUT_FILE'}, 'ipterr' => $config{'IPT_ERROR_FILE'} ); $ipt_opts{'debug'} = 1 if $debug; $ipt_opts{'verbose'} = 1 if $verbose; my $ipt = new IPTables::ChainMgr(%ipt_opts) or die '[*] Could not acquire IPTables::ChainMgr object.'; my $block_success = 0; my $already_blocked = 0; my ($timeout, $timeout_str) = &get_block_timeout($scan_dl{$ip}); if ($config{'IPTABLES_PREREQ_CHECK'} > 1) { $iptables_prereq_check++; $iptables_prereq_check = 1 if $iptables_prereq_check == $config{'IPTABLES_PREREQ_CHECK'}; } ### add block rule for $ip unless it is already blocked for my $hr (@ipt_config) { my $target = $hr->{'target'}; my $direction = $hr->{'direction'}; my $table = $hr->{'table'}; my $from_chain = $hr->{'from_chain'}; my $to_chain = $hr->{'to_chain'}; my $jump_rule_position = $hr->{'jump_rule_position'}; my $auto_rule_position = $hr->{'auto_rule_position'}; my $src = ''; my $dst = ''; if ($config{'IPTABLES_PREREQ_CHECK'} == 1 or $iptables_prereq_check == 1) { print STDERR "[+] iptables chains and jump rule check.\n" if $debug; ### make sure "to_chain" exists my ($rv, $out_ar, $err_ar) = $ipt->create_chain($table, $to_chain); unless ($rv) { my $msg = "could not create $table $to_chain chain"; &sys_log($msg); &sys_log_mline($err_ar); print STDERR "[-] ipt_block(): $msg\n" if $debug; next; } unless ($test_mode) { ### add jump rule to the "to_chain" from the "from_chain" ($rv, $out_ar, $err_ar) = $ipt->add_jump_rule($table, $from_chain, $jump_rule_position, $to_chain); unless ($rv) { my $msg = "could not add jump rule to $to_chain chain"; &sys_log($msg); &sys_log_mline($err_ar); print STDERR "[-] ipt_block(): $msg\n" if $debug; next; } } } if ($direction eq 'src' or $direction eq 'both') { $src = $ip; $dst = '0.0.0.0/0'; } elsif ($direction eq 'dst') { $src = '0.0.0.0/0'; $dst = $ip; } my ($rv, $num_chain_rules) = $ipt->find_ip_rule($src, $dst, $table, $to_chain, $target); if ($rv) { print STDERR "[-] Test1, IP rule ($src->$dst $to_chain) ", "already exists.\n" if $debug; $already_blocked = 1; } else { my ($rv, $out_ar, $err_ar) = $ipt->add_ip_rule($src, $dst, $auto_rule_position, $table, $to_chain, $target); if ($rv) { print STDERR "[+] Test1 block success.\n" if $debug; $block_success = 1; } else { my $msg = "could not add block rule for $src -> $dst"; &sys_log($msg); &sys_log_mline($err_ar); print STDERR "[-] ipt_block(): $msg\n" if $debug; } } if ($direction eq 'both') { ### need to add reverse rule for FORWARD chain my $src2 = $dst; my $dst2 = $src; my ($rv, $num_chain_rules) = $ipt->find_ip_rule($src2, $dst2, $table, $to_chain, $target); if ($rv) { print STDERR "[-] Test2, IP rule ($src2->$dst2 $to_chain) ", "already exists.\n" if $debug; $already_blocked = 1; } else { my ($rv, $out_ar, $err_ar) = $ipt->add_ip_rule($src2, $dst2, $auto_rule_position, $table, $to_chain, $target); if ($rv) { print STDERR "[+] Test2 block success.\n" if $debug; $block_success = 1; } else { my $msg = "could not add block rule for $src2 -> $dst2"; &sys_log($msg); &sys_log_mline($err_ar); print STDERR "[-] ipt_block(): $msg\n" if $debug; } } } } if ($already_blocked) { &sys_log("block rule for IP: $ip already exists"); print STDERR "[-] Block rule for IP: $ip already exists\n" if $debug; } elsif ($block_success) { ### make sure the ip is in the auto_blocked_ips cache (the ip ### may have come from the command line with --fw-block-ip ### instead of through the iptables log). Also, don't re-define ### the block time if it has already been defined (note that we ### may instantiated multiple block actions in the for loop ### above). $auto_blocked_ips{$ip} = time() unless defined $auto_blocked_ips{$ip}; my $mail_msg = "iptables auto-block against $ip $timeout_str"; if ($renewed_status) { $mail_msg = "renewed $mail_msg"; } else { $mail_msg = "added $mail_msg"; } if ($config{'ENABLE_AUTO_IDS_EMAILS'} eq 'Y') { if ($renewed_status) { if ($config{'ENABLE_RENEW_BLOCK_EMAILS'} eq 'Y') { &send_mail("$config{'MAIL_STATUS_PREFIX'} $mail_msg", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } } else { &send_mail("$config{'MAIL_STATUS_PREFIX'} $mail_msg", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } } &sys_log($mail_msg); ### write the ip out to the auto blocked file &diskwrite_blocked_ip($ip, $config{'AUTO_BLOCK_IPT_FILE'}, $renewed_status); print STDERR "[+] ipt_block(): added block for $ip\n" if $debug; } else { &sys_log("could not add iptables " . "block rule for: $ip"); print STDERR "[-] Could not add iptables block rule for: $ip\n" if $debug; } return; } sub ipt_rm_block() { my $ip = shift; my %ipt_opts = ( 'iptables' => $cmds{'iptables'}, 'iptout' => $config{'IPT_OUTPUT_FILE'}, 'ipterr' => $config{'IPT_ERROR_FILE'} ); $ipt_opts{'debug'} = 1 if $debug; $ipt_opts{'verbose'} = 1 if $verbose; my $ipt = new IPTables::ChainMgr(%ipt_opts) or die '[*] Could not acquire IPTables::ChainMgr object.'; print STDERR "[+] ipt_rm_block($ip)\n" if $debug; ### delete block rule for $ip my $rm_block = 0; for my $hr (@ipt_config) { my $target = $hr->{'target'}; my $direction = $hr->{'direction'}; my $table = $hr->{'table'}; my $to_chain = $hr->{'to_chain'}; my $src = ''; my $dst = ''; if ($direction eq 'src' or $direction eq 'both') { $src = $ip; $dst = '0.0.0.0/0'; } elsif ($direction eq 'dst') { $src = '0.0.0.0/0'; $dst = $ip; } my ($rv, $out_ar, $err_ar) = $ipt->delete_ip_rule($src, $dst, $table, $to_chain, $target); if ($rv) { $rm_block = 1; } else { my $msg = "could not delete rule for $src -> $dst"; &sys_log($msg); &sys_log_mline($err_ar); print STDERR "[-] ipt_rm_block(): $msg\n" if $debug; } if ($direction eq 'both') { ### need to delete reverse rule for FORWARD chain my $src2 = $dst; my $dst2 = $src; ($rv, $out_ar, $err_ar) = $ipt->delete_ip_rule($src2, $dst2, $table, $to_chain, $target); if ($rv) { $rm_block = 1; } else { my $msg = "could not delete rule for $src -> $dst"; &sys_log($msg); &sys_log_mline($err_ar); print STDERR "[-] ipt_rm_block(): $msg\n" if $debug; } } } ### delete the ip from the hash (if new packets are seen ### from the same ip, then the hash will be updated again ### in check_scan()). delete $auto_blocked_ips{$ip} if defined $auto_blocked_ips{$ip}; &diskwrite_rm_blocked_ip($ip, $config{'AUTO_BLOCK_IPT_FILE'}); if ($rm_block) { unless ($flush_fw) { ### don't send timeout email if we are manually flushing ### the auto-block rules from the command line with --Flush. &sys_log("removed iptables auto-block against " . $ip); if ($config{'ENABLE_AUTO_IDS_EMAILS'} eq 'Y') { &send_mail("$config{'MAIL_STATUS_PREFIX'} removed " . "iptables block against $ip", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } } print STDERR "[+] ipt_rm_block(): removed iptables block ", "against $ip\n" if $debug; return 1; } &sys_log('warning: could not remove iptables ' . "block rule for $ip"); print STDERR "[-] Could not remove iptables block rule for $ip\n" if $debug; return 0; } sub ipt_list_auto_chains() { my %ipt_opts = ( 'iptables' => $cmds{'iptables'}, 'iptout' => $config{'IPT_OUTPUT_FILE'}, 'ipterr' => $config{'IPT_ERROR_FILE'} ); $ipt_opts{'debug'} = 1 if $debug; $ipt_opts{'verbose'} = 1 if $verbose; my $ipt = new IPTables::ChainMgr(%ipt_opts) or die '[*] Could not acquire IPTables::ChainMgr object.'; print "[+] Listing chains from IPT_AUTO_CHAIN keywords...\n"; if ($config{'ENABLE_AUTO_IDS'} eq 'N') { print '[-] NOTE: ENABLE_AUTO_IDS is currently disabled ', "in $config_file\n"; } print "\n"; for my $hr (@ipt_config) { my $table = $hr->{'table'}; my $to_chain = $hr->{'to_chain'}; my ($rv, $out_ar, $err_ar) = $ipt->chain_exists($table, $to_chain); if ($rv) { ($rv, $out_ar, $err_ar) = $ipt->run_ipt_cmd("$cmds{'iptables'} -t " . "$table -n -L $to_chain -v"); if ($rv and $out_ar) { print for @$out_ar; } print "\n"; } else { print "[-] Table: $table, chain: $to_chain, does not exist\n"; } } return 0; } sub check_ipt_cmd() { my $lines_ar = shift; my %uniq_cmds = (); for my $line (@$lines_ar) { chomp $line; next if defined $uniq_cmds{$line}; $uniq_cmds{$line} = ''; if ($line =~ /flush/i) { my $del_chains = 0; $del_chains = 1 if $line =~ /delchains/i; &flush_auto_blocked_ips($del_chains); } else { if ($line =~ m|add\s+($ipv4_re)\s*$|i or $line =~ m|add\s+($ipv4_re/\d+)\s*$|i or $line =~ m|add\s+($ipv4_re/$ipv4_re)\s*$|i) { ### instantiate the blocking rule &ipt_block($1, ''); } elsif ($line =~ m|del\s+($ipv4_re)\s*$|i or $line =~ m|del\s+($ipv4_re/\d+)\s*$|i or $line =~ m|del\s+($ipv4_re/$ipv4_re)\s*$|i) { ### remove the blocking rule &ipt_rm_block($1); } } } return; } ### this only gets used when issuing --fw-block ### from the command line. sub sockwrite_add_ipt_block_ip() { die "[*] --fw-block-ip takes either an IP or subnet as an argument." unless $fw_block_ip =~ /$ipv4_re/; my $block_ip = ''; my $block_mask = ''; my $block_net = ''; if ($fw_block_ip =~ m|^\s*($ipv4_re)\s*$|) { $block_ip = $1; $block_mask = '32'; $block_net = new NetAddr::IP($1); } elsif ($fw_block_ip =~ m|^\s*($ipv4_re)/($ipv4_re)\s*$|) { $block_ip = $1; $block_mask = $2; $block_net = new NetAddr::IP($1, $2); } elsif ($fw_block_ip =~ m|^\s*($ipv4_re)/(\d+)\s*$|) { $block_ip = $1; $block_mask = $2; $block_net = new NetAddr::IP($1, $2); } else { die "[*] Badly formatted block IP: $fw_block_ip"; } if ($block_net->masklen() != 32) { ### a subnet was given on the command line, so make ### sure we were also given a network address (iptables ### converts to the network address in -nL output) $fw_block_ip = $block_net->network()->cidr(); } ### import auto_dl file &import_auto_dl(); ### make sure $fw_block_ip is not supposed to be ignored NET: for my $net (keys %auto_dl) { my $dl = $auto_dl{$net}{'dl'}; my $mask = $auto_dl{$net}{'mask'}; ### may be a /32 (single IP) next NET unless $dl == 0; ### only care about the ignored IPs/nets if ($block_net->within($auto_dl_ip_objs{$net})) { die "[*] $fw_block_ip overlaps with whitelisted ", "$net/$mask in $config{'AUTO_DL_FILE'}"; } } if (-e $config{'PSAD_PID_FILE'}) { if ($config{'ENABLE_AUTO_IDS'} ne 'Y') { die "[*] ENABLE_AUTO_IDS is not set to 'Y', exiting."; } if (&is_running($config{'PSAD_PID_FILE'})) { print "[+] Writing $fw_block_ip to socket; psad will add the IP\n", " within $config{'CHECK_INTERVAL'} seconds.\n"; ### open domain socket with running psad process my $sock = IO::Socket::UNIX->new($config{'AUTO_IPT_SOCK'}) or die "[*] Could not acquire $config{'AUTO_IPT_SOCK'} ", "socket: $!"; print $sock "add $fw_block_ip\n"; close $sock; } else { print "[-] There is no running psad process. Exiting.\n"; } } else { print "[-] There is no running psad process. Exiting.\n"; } return 0; } sub sockwrite_rm_ipt_block_ip() { die "[*] --fw-rm-block-ip takes an IP/subnet as an argument." unless $fw_rm_block_ip =~ /$ipv4_re/; my $rm_block_ip = ''; my $rm_block_mask = ''; my $rm_block_net = ''; if ($fw_rm_block_ip =~ m|^\s*($ipv4_re)\s*$|) { $rm_block_ip = $1; $rm_block_mask = '32'; $rm_block_net = new NetAddr::IP($1); } elsif ($fw_rm_block_ip =~ m|^\s*($ipv4_re)/($ipv4_re)\s*$|) { $rm_block_ip = $1; $rm_block_mask = $2; $rm_block_net = new NetAddr::IP($1, $2); } elsif ($fw_rm_block_ip =~ m|^\s*($ipv4_re)/(\d+)\s*$|) { $rm_block_ip = $1; $rm_block_mask = $2; $rm_block_net = new NetAddr::IP($1, $2); } else { die "[*] Badly formatted rm block IP: $fw_rm_block_ip"; } if ($rm_block_net->masklen() != '32') { ### a subnet was given on the command line, so make ### sure we were also given a network address (iptables ### converts to the network address in -nL output) $fw_rm_block_ip = $rm_block_net->network()->cidr(); } if (-e $config{'PSAD_PID_FILE'}) { if ($config{'ENABLE_AUTO_IDS'} ne 'Y') { die "[*] ENABLE_AUTO_IDS is not set to 'Y', exiting."; } if (&is_running($config{'PSAD_PID_FILE'})) { print "[+] Writing $fw_rm_block_ip to socket; psad will remove the IP\n", " within $config{'CHECK_INTERVAL'} seconds.\n"; ### open domain socket with running psad process my $sock = IO::Socket::UNIX->new($config{'AUTO_IPT_SOCK'}) or die "[*] Could not acquire $config{'AUTO_IPT_SOCK'} ", "socket: $!"; print $sock "del $fw_rm_block_ip\n"; close $sock; return 0; } } return 0; } sub tcpwr_test_block() { my $ip = shift; open T, "< $config{'ETC_HOSTS_DENY_FILE'}" or die "[*] Could not open ", "$config{'ETC_HOSTS_DENY_FILE'}: $!"; my @lines = ; close T; for my $line (@lines) { chomp $line; return 1 if $line =~ /ALL:\s+$ip$/; } return 0; } sub tcpwr_block() { my $ip = shift; open T, ">> $config{'ETC_HOSTS_DENY_FILE'}" or die "[*] Could not open ", "$config{'ETC_HOSTS_DENY_FILE'}: $!"; print T "ALL: $ip\n"; close T; return; } sub tcpwr_rm_block() { my $ip = shift; my $rv = 0; open T, "< $config{'ETC_HOSTS_DENY_FILE'}" or die '[*] Could not open ', "$config{'ETC_HOSTS_DENY_FILE'}: $!"; my @lines = ; close T; open T, "> $config{'ETC_HOSTS_DENY_FILE'}.tmp" or die '[*] Could not open ', "$config{'ETC_HOSTS_DENY_FILE'}.tmp: $!"; for my $line (@lines) { chomp $line; if ($line =~ /ALL:\s+$ip$/) { &diskwrite_rm_blocked_ip($ip, $config{'AUTO_BLOCK_TCPWR_FILE'}); if ($config{'ENABLE_AUTO_IDS_EMAILS'} eq 'Y') { &send_mail("$config{'MAIL_STATUS_PREFIX'} removed " . "tcpwrappers block against $ip (timeout expired).", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } $rv = 1; } else { print T "$line\n"; } } close T; move "$config{'ETC_HOSTS_DENY_FILE'}.tmp", $config{'ETC_HOSTS_DENY_FILE'} or die "[*] Could not move $config{'ETC_HOSTS_DENY_FILE'}.tmp -> ", "$config{'ETC_HOSTS_DENY_FILE'}"; ### delete the ip from the hash (if new packets are seen ### from the same ip, then the hash will be updated again ### in check_scan()). delete $auto_blocked_ips{$ip} if defined $auto_blocked_ips{$ip}; return $rv; } sub auto_psad_response() { my ($curr_scan_hr, $auto_block_regex_match_hr) = @_; print STDERR "[+] auto_psad_response()\n" if $debug; SRC: for my $src (keys %$curr_scan_hr) { ### make sure we are not attempting to block 0.0.0.0 ### or 127.0.0.1 or any of the local interface IP's. next SRC if &auto_block_ignore_ip($src); if ($config{'ENABLE_AUTO_IDS_REGEX'} eq 'Y' and $config{'AUTO_BLOCK_REGEX'} ne 'NONE') { ### skip if AUTO_BLOCK_REGEX did not match --log-prefix unless (defined $auto_block_regex_match_hr->{$src}) { print STDERR "[+] Skipping IP from auto-block, ", "AUTO_BLOCK_REGEX $config{'AUTO_BLOCK_REGEX'} ", "did not match.\n" if $debug; next SRC; } } my $dl = $scan_dl{$src}; ### We only want to block the IP once. Currently this will block ### all traffic from the host to _all_ destinations that are ### protected by the firewall if the IP trips the $auto_psad_level ### threshold for _any_ destination. if ($dl >= $config{'AUTO_IDS_DANGER_LEVEL'}) { next SRC if defined $auto_blocked_ips{$src}; my ($timeout, $timeout_str) = &get_block_timeout($dl); ### we have seen at least one packet logged by the firewall ### at this point if ($config{'IPTABLES_BLOCK_METHOD'} eq 'Y') { &ipt_block($src, ''); } if ($config{'TCPWRAPPERS_BLOCK_METHOD'} eq 'Y') { &sys_log('initiating tcpwrappers auto-block ' . "against $src $timeout_str"); if ($config{'ENABLE_AUTO_IDS_EMAILS'} eq 'Y') { &send_mail("$config{'MAIL_STATUS_PREFIX'} " . "tcpwrappers AUTO-BLOCK against $src $timeout_str", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } my $found = 0; open H, "< $config{'ETC_HOSTS_DENY_FILE'}" or die "[*] Could not open $config{'ETC_HOSTS_DENY_FILE'}: $!"; my @lines = ; close H; for my $line (@lines) { chomp $line; $found = 1 if $line =~ /ALL:\s+$src$/; } unless ($found) { open H, ">> $config{'ETC_HOSTS_DENY_FILE'}" or die "[*] Could not open $config{'ETC_HOSTS_DENY_FILE'}: $!"; print H "ALL: $src\n"; close H; $auto_blocked_ips{$src} = time() unless defined $auto_blocked_ips{$src}; ### write the ip out to the auto blocked file &diskwrite_blocked_ip($src, $config{'AUTO_BLOCK_TCPWR_FILE'}, ''); } } } } return; } sub auto_block_ignore_ip() { my $ip = shift; for my $local_ip (keys %local_ips) { if ($ip eq $local_ip) { print STDERR "[+] Skipping local IP $ip from auto-block.\n" if $debug; return 1; } } ### matching the following two addresses is less likely (assuming ### iptables is not logging traffic from localhost) than matching ### a legitimate interface address if ($ip eq '127.0.0.1' or $ip eq '0.0.0.0') { print STDERR "[+] Skipping IP $ip from auto-block.\n" if $debug; return 1; } return 0; } sub get_block_timeout() { my $dl = shift; my $timeout = $config{'AUTO_BLOCK_DL1_TIMEOUT'}; if ($dl == 2) { $timeout = $config{'AUTO_BLOCK_DL2_TIMEOUT'}; } elsif ($dl == 3) { $timeout = $config{'AUTO_BLOCK_DL3_TIMEOUT'}; } elsif ($dl == 4) { $timeout = $config{'AUTO_BLOCK_DL4_TIMEOUT'}; } elsif ($dl == 5) { $timeout = $config{'AUTO_BLOCK_DL5_TIMEOUT'}; } my $timeout_str = "for $timeout seconds"; $timeout_str = '(unlimited timeout)' if $timeout == $PERMANENT; return ($timeout, $timeout_str); } sub timeout_auto_blocked_ips() { print STDERR "[+] timeout_auto_block_ips()\n" if $debug or $test_mode; for my $ip (keys %auto_blocked_ips) { my ($timeout, $timeout_str) = &get_block_timeout($scan_dl{$ip}); next if $timeout == $PERMANENT; if ((time() - $auto_blocked_ips{$ip}) > $timeout) { ### remove all Netfiler blocking rules for $ip if ($config{'IPTABLES_BLOCK_METHOD'} eq 'Y') { &ipt_rm_block($ip); } ### remove all tcpwrapper blocking rules for $ip if ($config{'TCPWRAPPERS_BLOCK_METHOD'} eq 'Y') { &tcpwr_rm_block($ip); } } } return; } sub build_ipt_config() { @ipt_config = (); &make_psad_dirs() unless -d $config{'PSAD_DIR'}; my %ipt_opts = ( 'iptables' => $cmds{'iptables'}, 'iptout' => $config{'IPT_OUTPUT_FILE'}, 'ipterr' => $config{'IPT_ERROR_FILE'} ); $ipt_opts{'debug'} = 1 if $debug; $ipt_opts{'verbose'} = 1 if $verbose; my $ipt = new IPTables::ChainMgr(%ipt_opts) or die '[*] Could not acquire IPTables::ChainMgr object.'; my $ctr = 1; VAR: while (defined $config{"IPT_AUTO_CHAIN$ctr"}) { my $value = $config{"IPT_AUTO_CHAIN$ctr"}; my @block = split /\s*,\s*/, $value; if ($#block == 4 or $#block == 6) { my %hsh = (); if ($#block == 4) { ### DROP, src, filter, INPUT, PSAD_BLOCK_INPUT; %hsh = ( 'target' => $block[0], 'direction' => $block[1], 'table' => $block[2], 'from_chain' => $block[3], 'to_chain' => $block[4], 'jump_rule_position' => 1, 'auto_rule_position' => 1 ); ### this is the old format; generate a warning my $msg = "the IPT_AUTO_CHAIN$ctr variable in psad.conf " . "needs to be updated to set the jump rule position and " . "the auto rule position; defaulting both to 1."; &sys_log($msg); print STDERR "[-] build_ipt_config(): $msg\n" if $debug; } else { ### DROP, src, filter, INPUT, 1, PSAD_BLOCK_INPUT, 1; %hsh = ( 'target' => $block[0], 'direction' => $block[1], 'table' => $block[2], 'from_chain' => $block[3], 'jump_rule_position' => $block[4], 'to_chain' => $block[5], 'auto_rule_position' => $block[6] ); } unless ($hsh{'direction'} eq 'src' or $hsh{'direction'} eq 'dst' or $hsh{'direction'} eq 'both') { my $msg = "invalid direction $hsh{'direction'} " . "in IPT_AUTO_CHAIN$ctr keyword"; &sys_log($msg); print STDERR "[-] build_ipt_config(): $msg\n" if $debug; next VAR; } if ($hsh{'from_chain'} eq $hsh{'to_chain'}) { my $msg = "cannot have identical from_chain and to_chain " . "in IPT_AUTO_CHAIN$ctr keyword"; &sys_log($msg); print STDERR "[-] build_ipt_config(): $msg\n" if $debug; next VAR; } my ($rv, $out_ar, $err_ar) = $ipt->chain_exists($hsh{'table'}, $hsh{'from_chain'}); if ($rv) { push @ipt_config, \%hsh; } else { my $msg = "invalid IPT_AUTO_CHAIN$ctr keyword, " . "$hsh{'from_chain'} chain does not exist."; &sys_log($msg); print STDERR "[-] build_ipt_config(): $msg\n" if $debug; } } else { my $msg = "invalid IPT_AUTO_CHAIN$ctr variable: $value"; &sys_log($msg); print STDERR "[-] build_ipt_config(): $msg\n" if $debug; } $ctr++; } return; } ### this is the main caching function that adds an IP upon a ### successful block. sub diskwrite_blocked_ip() { my ($src, $file, $renewed_status) = @_; print STDERR "[+] diskwrite_blocked_ip($src, $file, $renewed_status)\n" if $debug; my @lines = (); my $skip_src = 0; if (-e $file) { open F, "< $file" or die "[*] Could not open ", "$file: $!"; my @tmplines = ; close F; ### see if we have already written the ip to the block ### file (or update the time if $renewed_status) for my $line (@tmplines) { chomp $line; if ($line =~ /^\s*($ipv4_re)\s*$/) { ### old format; update to include time my $tmpsrc = $1; push @lines, "$tmpsrc " . $auto_blocked_ips{$tmpsrc}; $skip_src = 1 if $tmpsrc eq $src; } else { if ($renewed_status) { ### must update the time to now if ($line =~ m|^\s*$src\s|) { push @lines, "$src " . $auto_blocked_ips{$src}; $skip_src = 1; } else { push @lines, $line; } } else { if ($line =~ m|^\s*$src\s+\d+|) { return; ### already blocked $src, do nothing } else { push @lines, $line; } } } } } unless ($skip_src) { push @lines, "$src " . $auto_blocked_ips{$src}; } return unless @lines; open B, "> ${file}.tmp" or die "[*] Could not write to $file: $!"; print B $_, "\n" for @lines; close B; move "${file}.tmp", $file or die "[*] Could not move ", "${file}.tmp -> $file: $!"; return; } ### this gets called when we want to remove an IP from the disk ### cache sub diskwrite_rm_blocked_ip() { my ($src, $file) = @_; print STDERR "[+] rm_blocked_ip($src, $file)\n" if $debug; return unless -e $file; open B, "< $file" or die "[*] Could not open $file: $!"; my @lines = ; close B; return unless @lines; open W, "> ${file}.tmp" or die "[*] Could not open ${file}.tmp: $!"; for my $line (@lines) { chomp $line; if ($line =~ /^\s*($ipv4_re)/) { print W $line, "\n" unless $src eq $1; } } close W; move "${file}.tmp", $file or die "[*] Could not move ", "${file}.tmp -> $file: $!"; return; } sub email_limit_reached() { my ($src, $dst) = @_; my $subject = "$config{'MAIL_STATUS_PREFIX'} reached email message " . "limit for $src on $config{'HOSTNAME'}"; $subject .= " ($dst)" if $dst; &send_mail($subject, '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); if ($dst) { $scan_email_ctrs{$src}{$dst}{'stop_email'} = 1; } else { $scan_email_ctrs{$src}{'stop_email'} = 1; } return; } sub print_scan() { ### this should primarily be used for debugging my $scanfile = "$config{'PRINT_SCAN_HASH'}.$$"; open PSCAN, "> $scanfile" or warn '[-] Could not open ', "$scanfile: $!" and return; print PSCAN "[+] Passive OS fingerprinting hash:\n", Dumper(\%posf), "[+] Scan danger level hash:\n", Dumper(\%scan_dl), "[+] Main scan hash:\n", Dumper(\%scan); close PSCAN; chmod 0600, $scanfile; return; } sub get_local_ips() { print STDERR "[+] get_local_ips()\n" if $debug; if ($config{'IFCFGTYPE'} =~ /iproute2/i) { print STDERR "[+] : Using IFCFGTYPE iproute2\n" if $debug; my @ips = @{&run_command($cmds{'ip'}, 'addr')}; return unless @ips; for my $line (@ips) { if ($line =~ /inet\s+($ipv4_re)\/\d+\s/) { print STDERR "[+] : Adding $1 to local_ips\n" if $debug; $local_ips{$1} = ''; } elsif ($line =~ /inet6\s(\S+)/) { print STDERR "[+] : Adding $1 to local_ips\n" if $debug; $local_ips{$1} = ''; } } } else { print STDERR "[+] : Using IFCFGTYPE ifconfig\n" if $debug; my @ips = @{&run_command($cmds{'ifconfig'}, '-a')}; return unless @ips; for my $line (@ips) { if ($line =~ /inet\s+.*?:($ipv4_re)\s/) { print STDERR "[+] : Adding $1 to local_ips\n" if $debug; $local_ips{$1} = ''; } elsif ($line =~ /inet6\s+addr:\s+(\S+)/) { print STDERR "[+] : Adding $1 to local_ips\n" if $debug; $local_ips{$1} = ''; } } } return; } sub get_listening_ports() { %local_ports = (); my @lines = @{&run_command($cmds{'netstat'}, '-an 2> /dev/null')}; return unless @lines; for my $line (@lines) { next unless $line; chomp $line; if ($line =~ m/^\s*(tcp|udp)\s+\d+\s+\d+\s+\S+:(\d+)\s/) { ### $1 == protocol (tcp/udp), $2 == port number $local_ports{$1}{$2} = ''; } } return; } sub get_dns_info() { my $ip = shift; my $dns_str = ''; if (defined $dns_cache{$ip} and $dns_cache{$ip}{'ctr'} < $config{'DNS_LOOKUP_THRESHOLD'}) { $dns_str = $dns_cache{$ip}{'hostname'}; $dns_cache{$ip}{'ctr'}++; } else { my $ipaddr = gethostbyname($ip); ### gethostbyaddr($ipaddr, AF_INET); my $dns_tmp = gethostbyaddr($ipaddr, 2); $dns_str = $dns_tmp if $dns_tmp; $dns_cache{$ip}{'ctr'} = 0; $dns_cache{$ip}{'hostname'} = $dns_str; } return $dns_str; } sub get_whois_info() { my ($ip, $whois_datafile) = @_; print STDERR "[+] get_whois_info() IP: $ip\n" if $debug; my @whois_data; if (defined $whois_cache{$ip} and $whois_cache{$ip} < $config{'WHOIS_LOOKUP_THRESHOLD'} and -e $whois_datafile) { $whois_cache{$ip}++; } else { $whois_cache{$ip} = 0; eval { local $SIG{'ALRM'} = sub {die "whois alarm\n"}; alarm $config{'WHOIS_TIMEOUT'}; system "$cmds{'whois'} $ip > $whois_datafile 2> /dev/null"; alarm 0; }; if ($@) { ### die unless $@ eq "whois alarm\n"; ### warn "$@: $?"; ### let the warning handler save the error. warn $@; $#whois_data = 0; @whois_data = ("Whois data not available!\n"); unlink $whois_datafile; return \@whois_data; } } open W, "< $whois_datafile" or die "[*] Could not open $whois_datafile: $!"; @whois_data = ; close W; if ($config{'ENABLE_WHOIS_FORCE_ASCII'} eq 'Y') { for my $line (@whois_data) { ### replace any non-ascii chars in whois output with 'NA' $line =~ s/[^\x20-\x7e]/NA/g; } } return \@whois_data; } sub REAPER { my $pid; $pid = waitpid(-1, WNOHANG); # if (WIFEXITED($?)) { # print STDERR "[+] ** Process $pid exited.\n"; # } $SIG{'CHLD'} = \&REAPER; return; } sub stop_psad() { my $rv = 0; &sys_log('shutting down psad daemons'); ### must kill psadwatchd first since if not, it might try to restart ### any of the other two daemons. for my $pidname (qw(psadwatchd kmsgsd psad)) { my $pidfile = $pidfiles{$pidname}; if (-e $pidfile) { my $pid = &is_running($pidfile); if ($pid) { print "[+] Stopping $pidname, pid: $pid\n"; unless (kill 15, $pid) { kill 9, $pid or print "[*] psad: Could not kill ", "$pidname, pid: $pid $!\n"; $rv = 1; } else { unlink $pidfile; } } else { my $print = 1; if ($pidname eq 'kmsgsd' and ($config{'SYSLOG_DAEMON'} =~ /ulog/i or $config{'ENABLE_SYSLOG_FILE'} eq 'Y')) { $print = 0; } print "[-] psad: $pidname is not running on ", "$config{'HOSTNAME'}\n" if $print; $rv = 1; } } else { my $print = 1; if ($pidname eq 'kmsgsd' and ($config{'SYSLOG_DAEMON'} =~ /ulog/i or $config{'ENABLE_SYSLOG_FILE'} eq 'Y')) { $print = 0; } print "[-] psad: pid file $pidfile does not exist for ", "$pidname on $config{'HOSTNAME'}\n" if $print; $rv = 1; } } return $rv; } sub restart() { my $cmdline = ''; if (-e $cmdline_file) { open CMD, "< $cmdline_file" or die '[*] Could not open ', "$cmdline_file: $!"; $cmdline = ; close CMD; chomp $cmdline; } ### stop any running psad daemons. &stop_psad(); print "[+] Restarting psad daemons on $config{'HOSTNAME'}\n"; if ($cmdline) { system "$cmds{'psad'} $cmdline"; } else { system $cmds{'psad'}; } return 0; } sub analysis_mode() { ($analysis_tokens_ar, $analysis_match_criteria_ar) = &analysis_fields() if $analysis_fields; &make_psad_dirs(); unless (-d $config{'PSAD_DIR'}) { mkdir $config{'PSAD_DIR'} or die "[*] Could not mkdir ", "$config{'PSAD_DIR'}: $!"; } if (-d $config{'ANALYSIS_MODE_DIR'}) { print "[+] Removing old $config{'ANALYSIS_MODE_DIR'} directory.\n"; rmtree $config{'ANALYSIS_MODE_DIR'} or die "[*] Could not ", "remove $config{'ANALYSIS_MODE_DIR'}\n"; } mkdir $config{'ANALYSIS_MODE_DIR'} or die "[*] Could not mkdir ", $config{'ANALYSIS_MODE_DIR'}; ### change path for counter files, etc. for my $var (keys %config) { next if $var eq 'PSAD_DIR'; next if $var eq 'ANALYSIS_MODE_DIR'; next if $var eq 'ANALYSIS_OUTPUT_FILE'; my $val = $config{$var}; if ($val =~ m|$config{'PSAD_DIR'}/|) { $val =~ s|$config{'PSAD_DIR'}|$config{'ANALYSIS_MODE_DIR'}|; $config{$var} = $val; } } &make_psad_dirs(); ### one more time now that paths are updated ### setup to put all files in the ANALYSIS_MODE_DIR ### (by setting PSAD_DIR to ANALYSIS_MODE_DIR subroutines ### work more easily). $config{'PSAD_DIR'} = $config{'ANALYSIS_MODE_DIR'}; ### build @local_nets array my $connected_subnets_ar = &get_connected_subnets(); for my $net (@$connected_subnets_ar) { push @local_nets, $net; } print "[+] Entering analysis mode. Parsing $fw_data_file\n"; my $fh = ''; if ($pkts_from_stdin) { $fh = *STDIN; } else { open MSGS, "< $fw_data_file" or die "[*] Could not open ", "$fw_data_file: $!"; $fh = *MSGS; } my @ipt_msgs = (); my $pkt_ctr = 0; my $line_ctr = 0; PKT: while (<$fh>) { my $line = $_; $line_ctr++; if ($num_packets > 0) { last PKT if $pkt_ctr >= $num_packets; } if ($line =~ /IN.*OUT/) { if ($config{'FW_SEARCH_ALL'} eq 'Y') { push @ipt_msgs, $line; $pkt_ctr++; } else { if ($line =~ /$config{'SNORT_SID_STR'}/) { push @ipt_msgs, $line; $pkt_ctr++; } else { for my $fw_search_str (@fw_search) { if ($line =~ /$fw_search_str/) { push @ipt_msgs, $line; $pkt_ctr++; } } } } } } close $fh unless $pkts_from_stdin; print "[+] Found ", ($#ipt_msgs+1), " iptables log messages out of " . "$line_ctr total lines.\n"; print " This may take a while...\n" if $#ipt_msgs > 15000; ### analyze all packets &check_scan(\@ipt_msgs); print "\n[+] Finished --Analyze cycle.\n"; return 0; } sub ipt_match_criteria() { my ($pkt_hr, $tokens_ar, $match_criteria_ar) = @_; my @matched_fields = (); my $gnuplot_comment_str = ''; for (my $i=0; $i <= $#$tokens_ar; $i++) { my $tok = $tokens_ar->[$i]; if ($match_criteria_ar) { my $match_hr = $match_criteria_ar->[$i]; if (defined $match_hr->{'num'}) { return [], '' unless $pkt_hr->{$tok} =~ m|^\d+$|; if ($match_hr->{'negate'}) { return [], '' if $pkt_hr->{$tok} == $match_hr->{'num'}; } else { return [], '' unless $pkt_hr->{$tok} == $match_hr->{'num'}; } } elsif (defined $match_hr->{'gt'}) { return [], '' unless $pkt_hr->{$tok} =~ m|^\d+$|; if ($match_hr->{'negate'}) { return [], '' unless $pkt_hr->{$tok} <= $match_hr->{'gt'}; } else { return [], '' unless $pkt_hr->{$tok} > $match_hr->{'gt'}; } } elsif (defined $match_hr->{'lt'}) { return [], '' unless $pkt_hr->{$tok} =~ m|^\d+$|; if ($match_hr->{'negate'}) { return [], '' unless $pkt_hr->{$tok} >= $match_hr->{'lt'}; } else { return [], '' unless $pkt_hr->{$tok} < $match_hr->{'lt'}; } } elsif (defined $match_hr->{'str'}) { if ($match_hr->{'negate'}) { return [], '' if $pkt_hr->{$tok} eq $match_hr->{'str'}; } else { return [], '' unless $pkt_hr->{$tok} eq $match_hr->{'str'}; } } elsif (defined $match_hr->{'re'}) { if ($match_hr->{'negate'}) { return [], '' if $pkt_hr->{$tok} =~ m|$match_hr->{'re'}|; } else { return [], '' unless $pkt_hr->{$tok} =~ m|$match_hr->{'re'}|; } } elsif (defined $match_hr->{'net'} or defined $match_hr->{'ip'}) { my $net_or_ip_key = 'ip_obj'; $net_or_ip_key = 'net_obj' if defined $match_hr->{'net'}; if ($pkt_hr->{$tok} =~ m|$ipv4_re| or $pkt_hr->{$tok} =~ m|$ipv6_re|) { my $ip_match_obj = ''; if ($tok eq 'src') { $ip_match_obj = $pkt_hr->{'s_obj'}; } elsif ($tok eq 'dst') { $ip_match_obj = $pkt_hr->{'d_obj'}; } if ($match_hr->{'negate'}) { return [], '' if $ip_match_obj->within($match_hr->{$net_or_ip_key}); } else { return [], '' unless $ip_match_obj->within($match_hr->{$net_or_ip_key}); } } else { return [], ''; } } } push @matched_fields, $pkt_hr->{$tok}; } return \@matched_fields, $gnuplot_comment_str; } sub csv_mode() { if ($gnuplot_file_prefix) { $gnuplot_data_file = "$gnuplot_file_prefix.dat"; $gnuplot_plot_file = "$gnuplot_file_prefix.gnu"; $gnuplot_png_file = "$gnuplot_file_prefix.png"; } print "[+] Entering Gnuplot mode...\n" if $gnuplot_mode; ### see what we should be parsing out of the iptables logs my ($tokens_ar, $match_criteria_ar) = &csv_tokens(); $csv_regex = qr/$csv_regex/ if $csv_regex; $csv_neg_regex = qr/$csv_neg_regex/ if $csv_neg_regex; my %csv_uniq_lines = (); if ($csv_start_line) { die "[*] Cannot have start line > end line." if $csv_start_line > $csv_end_line; } my $fh = ''; if ($csv_stdin) { print "[+] Parsing iptables log messages from STDIN\n" if $gnuplot_mode; $fh = *STDIN; } else { print "[+] Parsing iptables log messages from file: $fw_data_file\n" if $gnuplot_mode; open MSGS, "< $fw_data_file" or die "[*] Could not open ", "$fw_data_file: $!"; $fh = *MSGS; } my $ctr = 0; my $line_ctr = 0; if ($gnuplot_mode and $store_file and -e $store_file) { @gnuplot_data = @{retrieve($store_file)}; } else { MSG: while (<$fh>) { my $pkt_str = $_; $line_ctr++; if ($csv_start_line) { next MSG unless $line_ctr >= $csv_start_line; } if ($csv_end_line) { last MSG if $line_ctr == $csv_end_line; } next MSG unless $pkt_str =~ /IN.*OUT/; my %pkt = %pkt_NF_init; if ($config{'FW_SEARCH_ALL'} eq 'Y') { my $rv = &parse_NF_pkt_str(\%pkt, $pkt_str); next MSG if $rv == $PKT_ERROR or $rv == $PKT_IGNORE; } else { if ($pkt_str =~ /$config{'SNORT_SID_STR'}/) { my $rv = &parse_NF_pkt_str(\%pkt, $pkt_str); next MSG if $rv == $PKT_ERROR or $rv == $PKT_IGNORE; } else { for my $fw_search_str (@fw_search) { if ($pkt_str =~ /$fw_search_str/) { my $rv = &parse_NF_pkt_str(\%pkt, $pkt_str); next MSG if $rv == $PKT_ERROR or $rv == $PKT_IGNORE; } } } } if ($csv_regex) { next MSG unless $pkt{'raw'} =~ m|$csv_regex|; } if ($csv_neg_regex) { next MSG unless $pkt{'raw'} !~ m|$csv_neg_regex|; } $pkt{'log_prefix'} =~ s/\W//g; $pkt{'log_prefix'} =~ s/\s//g; my ($matched_fields_ar, $gnuplot_comment_str) = &ipt_match_criteria(\%pkt, $tokens_ar, $match_criteria_ar); next MSG unless $#$matched_fields_ar > -1; $ctr++; if ($csv_line_limit > 0) { last if $ctr >= $csv_line_limit; } if ($gnuplot_mode) { ### cache the data since IP addresses have to mapped to ### integers across the entire data set push @gnuplot_data, $matched_fields_ar; } else { my $str = ''; ### here is where the output string is assembled $str .= $_ . $plot_separator for @$matched_fields_ar; $str =~ s/$plot_separator$//; if ($csv_print_uniq) { $csv_uniq_lines{$str} = ''; } else { ### The CSV data is printed here unless ### we are running with --CSV-unique-lines ### or we are running in --gnuplot mode print $str, "\n"; } } } close $fh unless $csv_stdin; } if ($gnuplot_mode) { if ($store_file) { if (-e $store_file) { print "[+] Retrieved Gnuplot array from: $store_file\n"; } else { ### store the @gnuplot_data array to disk for fast ### retrieval next time print "[+] Storing Gnuplot data to: $store_file\n"; store(\@gnuplot_data, $store_file); chmod 0644, $store_file; } } else { print "[+] Parsed $line_ctr iptables log messages.\n"; } ### print out the gnuplot data after appropriate ### integer conversions &gnuplot_write_data($tokens_ar); ### write out the gnuplot file &gnuplot_write_plot_file($tokens_ar); } elsif ($csv_print_uniq) { print "$_\n" for keys %csv_uniq_lines; } return 0; } sub gnuplot_header() { my @lines = (); push @lines, '#', '#'x$num_hash_marks, '#', "# Generated by psad v$version", "# Command line: 'psad @args_cp'", "# Time stamp: " . localtime(), '#', '#'x$num_hash_marks, '#', ''; return \@lines; } sub gnuplot_write_plot_file() { my $tokens_ar = shift; unless ($gnuplot_legend_title) { $gnuplot_legend_title = '('; for my $tok (@$tokens_ar) { $gnuplot_legend_title .= "$tok,"; } $gnuplot_legend_title =~ s/,$//; $gnuplot_legend_title .= ')'; $gnuplot_legend_title =~ s/_//g; } my @tokens = split /\s+/, $csv_fields; if ($gnuplot_template_file) { print "[+] Using Gnuplot template file: $gnuplot_template_file\n"; copy $gnuplot_template_file, $gnuplot_plot_file or die "[*] ", "Could not copy $gnuplot_template_file -> $gnuplot_plot_file: $!"; } else { print "[+] Writing gnuplot directive file: $gnuplot_plot_file\n"; open GP, "> $gnuplot_plot_file" or die "[*] Could not open ", "$gnuplot_plot_file: $!"; print GP "$_\n", for @{&gnuplot_header()}; unless ($gnuplot_title) { $gnuplot_title = "psad iptables log visualization: $csv_fields"; $gnuplot_title =~ s/_//g; ### some fonts used by Gnuplot don't like "-" chars } print GP "reset\n", qq|set title "$gnuplot_title"\n|; unless ($gnuplot_interactive) { if ($gnuplot_grayscale) { print GP "set terminal png transparent xffffff x000000 x606060 ", "x606060 x606060 x606060 x606060 x606060 nocrop enhanced\n", qq|set output "$gnuplot_png_file"\n|; } else { print GP "set terminal png transparent nocrop enhanced\n", qq|set output "$gnuplot_png_file"\n|; } } if ($tokens_ar->[0] eq 'timestamp') { print GP "set xdata time\n", qq|set timefmt x "%s"\n|, qq|set format x "%m/%d"\n|, qq|set xlabel "time"\n|; if ($gnuplot_x_range) { die "[*] range argument must be formatted as :" unless $gnuplot_x_range =~ /^\S+:\S+$/; $gnuplot_x_range =~ s/:/":"/; print GP qq|set xrange ["$gnuplot_x_range"]\n|; } } else { if ($gnuplot_x_label) { print GP qq|set xlabel "$gnuplot_x_label"\n|; } else { $tokens[0] =~ s/_//g; print GP qq|set xlabel "$tokens[0]"\n|; } if ($gnuplot_x_range) { die "[*] range argument must be formatted as :, ", "e.g. 1:10" unless $gnuplot_x_range =~ /^\d+:\d+$/; print GP qq|set xrange [$gnuplot_x_range]\n|; } } if ($gnuplot_y_label) { print GP qq|set ylabel "$gnuplot_y_label"\n|; } else { $tokens[1] =~ s/_//g; print GP qq|set ylabel "$tokens[1]"\n|; } if ($gnuplot_y_range) { die "[*] range argument must be formatted as :, e.g. 1:10" unless $gnuplot_y_range =~ /^\d+:\d+$/; print GP qq|set yrange [$gnuplot_y_range]\n|; } if ($#$tokens_ar == 2 or $gnuplot_3d) { ### include zaxis if ($gnuplot_z_label) { print GP qq|set zlabel "$gnuplot_z_label"\n|; } else { if ($gnuplot_count_type) { print GP qq|set zlabel "$gnuplot_count_type"\n|; } else { $tokens[2] =~ s/_//g; print GP qq|set zlabel "$tokens[2]"\n|; } } if ($gnuplot_z_range) { die "[*] range argument must be formatted as :, ", "e.g. 1:10" unless $gnuplot_z_range =~ /^\d+:\d+$/; print GP qq|set zrange [$gnuplot_z_range]\n|; } unless ($gnuplot_graph_style) { $gnuplot_graph_style = 'points'; } if ($gnuplot_view) { die "[*] View must be a coordinate pair such as 60,30" unless $gnuplot_view =~ /^\d+,\d+/; print GP qq|set view $gnuplot_view\n|; } print GP qq|splot '$gnuplot_data_file' using 1:2:3 with | . "$gnuplot_graph_style title '$gnuplot_legend_title'\n"; } else { unless ($gnuplot_graph_style) { $gnuplot_graph_style = 'linespoints'; } print GP qq|plot '$gnuplot_data_file' using 1:2 with | . "$gnuplot_graph_style title '$gnuplot_legend_title'\n"; } close GP; } chmod 0644, $gnuplot_plot_file; return; } sub gnuplot_write_data() { my $tokens_ar = shift; ### see how many years we need to go back &gnuplot_set_start_year($tokens_ar); ### resolve any IP addresses to minimal integers ### (this mapping preserves IP address subnet relationships ### in that IP's on the same subnet will appear close together ### in the plot). for my $aref (@gnuplot_data) { for (my $i=0; $i <= $#$tokens_ar; $i++) { my $tok = $tokens_ar->[$i]; if ($tok eq 'src' or $tok eq 'dst') { ### add the IP into the ip2int cache &ip2int($aref->[$i]); } } } ### now that all IP addresses have been mapped into the cache ### we map each IP to a mimimal integer my $ip_ctr = 1; for my $ip (sort {$ip2int_cache{$a} <=> $ip2int_cache{$b}} keys %ip2int_cache) { unless (defined $gnuplot_ip2int{$ip}) { $gnuplot_ip2int{$ip} = $ip_ctr; $ip_ctr++; } } ### check to see if we are generating data counts instead of ### values (for example number of packets to ports instead ### of just the port values themselves) my @gnuplot_count_data = (); print "[+] Writing parsed iptables data to: $gnuplot_data_file\n"; open GP, "> $gnuplot_data_file" or die "[*] Could not open ", "$gnuplot_data_file: $!"; print GP "$_\n", for @{&gnuplot_header()}; ### write gnuplot data out to stdout for my $aref (@gnuplot_data) { my @matched_fields = (); my $gnuplot_comment_str = ''; for (my $i=0; $i <= $#$tokens_ar; $i++) { my $tok = $tokens_ar->[$i]; my $val = $aref->[$i]; my ($rv, $gnuplot_comment) = &gnuplot_value($tok, $val); push @matched_fields, $rv; $gnuplot_comment_str .= "$rv=$gnuplot_comment " if $gnuplot_comment; } next unless @matched_fields; if ($gnuplot_count_type) { push @matched_fields, $gnuplot_comment_str; push @gnuplot_count_data, \@matched_fields; } else { my $str = ''; $str .= $_ . $plot_separator for @matched_fields; $str =~ s/$plot_separator$//; $str .= " ### $gnuplot_comment_str" if $gnuplot_comment_str; print GP $str, "\n"; } } if ($gnuplot_count_type) { &render_count_data(\@gnuplot_count_data, $tokens_ar, *GP); } close GP; chmod 0644, $gnuplot_data_file; return; } sub render_count_data() { my ($count_data_ar, $tokens_ar, $render_fh) = @_; my %render_count_data = (); my %render_comments = (); my $start_time = ''; my $end_time = ''; my $time_interval_incr = 0; if ($gnuplot_count_type eq 'countday' or $gnuplot_count_type eq 'counthour' or $gnuplot_count_type eq 'countmin' or $gnuplot_count_type eq 'countdayuniq' or $gnuplot_count_type eq 'counthouruniq' or $gnuplot_count_type eq 'countminuniq') { $start_time = $count_data_ar->[0]->[0]; $end_time = $count_data_ar->[$#$count_data_ar]->[0]; if ($gnuplot_count_type eq 'countday') { $time_interval_incr = 60 * 60 * 24; $gnuplot_count_type = 'count'; } elsif ($gnuplot_count_type eq 'counthour') { $time_interval_incr = 60 * 60; $gnuplot_count_type = 'count'; } elsif ($gnuplot_count_type eq 'countmin') { $time_interval_incr = 60; $gnuplot_count_type = 'count'; } elsif ($gnuplot_count_type eq 'countdayuniq') { $time_interval_incr = 60 * 60 * 24; $gnuplot_count_type = 'countuniq'; } elsif ($gnuplot_count_type eq 'counthouruniq') { $time_interval_incr = 60 * 60; $gnuplot_count_type = 'countuniq'; } elsif ($gnuplot_count_type eq 'countminuniq') { $time_interval_incr = 60; $gnuplot_count_type = 'countuniq'; } } for my $aref (@$count_data_ar) { my $x_axis = $aref->[0]; my $y_axis = $aref->[1]; my $z_axis = ''; if ($time_interval_incr) { ($x_axis, $start_time) = &gnuplot_time_to_interval($x_axis, $start_time, $end_time, $time_interval_incr); } if ($gnuplot_count_type eq 'count') { if ($gnuplot_count_element == 1) { if ($gnuplot_3d) { if ($#$aref == 2) { $render_comments{$x_axis}{$y_axis} = $aref->[2]; } $render_count_data{$x_axis}{$y_axis}++; } else { if ($#$aref == 2) { $render_comments{$x_axis} = $aref->[2]; } $render_count_data{$x_axis}++; } } elsif ($gnuplot_count_element == 2) { $z_axis = $aref->[2]; if ($#$aref == 3) { $render_comments{$x_axis}{$y_axis} = $aref->[3]; } ### FIXME $render_count_data{$x_axis}{$y_axis}{$z_axis}++; } } elsif ($gnuplot_count_type eq 'countuniq') { if ($gnuplot_count_element == 1) { if ($gnuplot_3d) { if ($#$aref == 2) { $render_comments{$x_axis}{$y_axis} = $aref->[2]; } unless (defined $gnuplot_cache_uniq{$x_axis}) { $render_count_data{$x_axis}{$y_axis}++; $gnuplot_cache_uniq{$x_axis}{$y_axis} = ''; } unless (defined $gnuplot_cache_uniq{$x_axis}{$y_axis}) { $render_count_data{$x_axis}{$y_axis}++; $gnuplot_cache_uniq{$x_axis}{$y_axis} = ''; } } else { if ($#$aref == 2) { $render_comments{$x_axis} = $aref->[2]; } unless (defined $gnuplot_cache_uniq{$x_axis}) { $render_count_data{$x_axis}++; $gnuplot_cache_uniq{$x_axis}{$y_axis} = ''; } unless (defined $gnuplot_cache_uniq{$x_axis}{$y_axis}) { $render_count_data{$x_axis}++; $gnuplot_cache_uniq{$x_axis}{$y_axis} = ''; } } } elsif ($gnuplot_count_element == 2) { $z_axis = $aref->[2]; if ($#$aref == 3) { $render_comments{$x_axis}{$y_axis} = $aref->[3]; } unless (defined $gnuplot_cache_uniq{$x_axis}) { $render_count_data{$x_axis}{$y_axis}++; $gnuplot_cache_uniq{$x_axis}{$y_axis}{$z_axis} = ''; } unless (defined $gnuplot_cache_uniq{$x_axis}{$y_axis}) { $render_count_data{$x_axis}{$y_axis}++; $gnuplot_cache_uniq{$x_axis}{$y_axis}{$z_axis} = ''; } unless (defined $gnuplot_cache_uniq{$x_axis}{$y_axis}{$z_axis}) { $render_count_data{$x_axis}{$y_axis}++; $gnuplot_cache_uniq{$x_axis}{$y_axis}{$z_axis} = ''; } } } } ### print the data to the .dat file now that it has been counted if ($gnuplot_count_type eq 'count') { if ($gnuplot_count_element == 1) { if ($gnuplot_3d) { &gnuplot_print_count_3d_data(\%render_count_data, \%render_comments, $render_fh); } else { &gnuplot_print_count_2d_data(\%render_count_data, \%render_comments, $render_fh); } } elsif ($gnuplot_count_element == 2) { for my $x_axis (sort {$a <=> $b} keys %render_count_data) { for my $y_axis (sort keys %{$render_count_data{$x_axis}}) { for my $key (keys %{$render_count_data{$x_axis}{$y_axis}}) { my $z_axis = $render_count_data{$x_axis}{$y_axis}{$key}; if (defined $render_comments{$x_axis} and defined $render_comments{$x_axis}{$y_axis}) { print $render_fh $x_axis . $plot_separator . $y_axis . $plot_separator. $z_axis . " ### $render_comments{$x_axis}{$y_axis}", "\n"; } else { print $render_fh $x_axis . $plot_separator . $y_axis . $plot_separator . $z_axis, "\n"; } } } } } } elsif ($gnuplot_count_type eq 'countuniq') { if ($gnuplot_count_element == 1) { if ($gnuplot_3d) { &gnuplot_print_count_3d_data(\%render_count_data, \%render_comments, $render_fh); } else { &gnuplot_print_count_2d_data(\%render_count_data, \%render_comments, $render_fh); } } elsif ($gnuplot_count_element == 2) { &gnuplot_print_count_3d_data(\%render_count_data, \%render_comments, $render_fh); } } return; } sub gnuplot_time_to_interval() { my ($time, $start_time, $end_time, $time_interval_incr) = @_; my $mapped_time = $time; if ($time <= $start_time) { $mapped_time = $start_time; } elsif ($time >= $end_time) { $mapped_time = $end_time; } else { while ($start_time < $time - $time_interval_incr) { $start_time += $time_interval_incr; } $mapped_time = $start_time; } return ($mapped_time, $start_time); } sub gnuplot_print_count_3d_data() { my ($data_hr, $comments_hr, $render_fh) = @_; for my $x_axis (sort {$a <=> $b} keys %$data_hr) { for my $y_axis (sort keys %{$data_hr->{$x_axis}}) { my $z_axis = $data_hr->{$x_axis}->{$y_axis}; if (defined $comments_hr->{$x_axis} and defined $comments_hr->{$x_axis}{$y_axis}) { print $render_fh $x_axis . $plot_separator . $y_axis . $plot_separator . $z_axis . " ### $comments_hr->{$x_axis}->{$y_axis}", "\n"; } else { print $render_fh $x_axis . $plot_separator . $y_axis . $plot_separator . $z_axis, "\n"; } } } return; } sub gnuplot_print_count_2d_data() { my ($data_hr, $comments_hr, $render_fh) = @_; if ($gnuplot_sort_style eq 'time') { for my $x_axis (sort {$a <=> $b} keys %$data_hr) { my $y_axis = $data_hr->{$x_axis}; if (defined $comments_hr->{$x_axis}) { print $render_fh $x_axis . $plot_separator . $y_axis . " ### $comments_hr->{$x_axis}", "\n"; } else { print $render_fh $x_axis . $plot_separator . $y_axis, "\n"; } } } elsif ($gnuplot_sort_style eq 'value') { for my $x_axis (sort {$data_hr->{$b} <=> $data_hr->{$a}} keys %$data_hr) { my $y_axis = $data_hr->{$x_axis}; if (defined $comments_hr->{$x_axis}) { print $render_fh $x_axis . $plot_separator . $y_axis . " ### $comments_hr->{$x_axis}", "\n"; } else { print $render_fh $x_axis . $plot_separator . $y_axis, "\n"; } } } else { for my $x_axis (keys %$data_hr) { my $y_axis = $data_hr->{$x_axis}; if (defined $comments_hr->{$x_axis}) { print $render_fh $x_axis . $plot_separator . $y_axis . " ### $comments_hr->{$x_axis}", "\n"; } else { print $render_fh $x_axis . $plot_separator . $y_axis, "\n"; } } } return; } sub gnuplot_set_start_year() { my $tokens_ar = shift; return unless $csv_have_timestamp; my $timestamp_field = -1; for (my $i=0; $i <= $#$tokens_ar; $i++) { if ($tokens_ar->[$i] eq 'timestamp') { $timestamp_field = $i; last; } } ### calculate the starting year by looking at the most recent ### event and working backwards die unless $timestamp_field > -1; my ($today_year, $today_mon, $today_day) = Today(); $gnuplot_year = $today_year; my $prev_month = 0; for (my $i=$#gnuplot_data; $i >= 0; $i--) { if ($gnuplot_data[$i]->[$timestamp_field] =~ /^\s*(\w+)\s+(\d+)\s+(\S+)/) { my $m_tmp = $1; ### kludge for Decode_Month() call my $mon = Decode_Month($m_tmp); my $day = $2; my $time = $3; if ($gnuplot_year) { if ($prev_month < $mon) { ### i.e., switched from Jan to Dec as we go backwards ### in the log (but there could be a hole like Jan to ### Oct as well, the key is that the new mon is less ### than the previous one) $gnuplot_year--; } $prev_month = $mon; } else { $prev_month = $mon; if ($today_mon < $mon) { $gnuplot_year = $today_year - 1; } else { $gnuplot_year = $today_year; } } } } return; } sub gnuplot_value() { my ($tok, $packet_val) = @_; my $rv = $packet_val; my $gnuplot_comment_str = ''; if ($tok eq 'timestamp') { ### reformat timestamp (e.g. "Feb 1 00:00:27" ### becomes 02/02/04:03:00:17) if ($packet_val =~ /^\s*(\w+)\s+(\d+)\s+(\S+)/) { my $m_tmp = $1; ### kludge for Decode_Month() call my $mon = Decode_Month($m_tmp); my $day = $2; my $time = $3; my $hour = 0; my $min = 0; my $sec = 0; if ($time =~ /(\d{2}):(\d{2}):(\d{2})/) { $hour = $1; $min = $2; $sec = $3; } $gnuplot_prev_mon = $mon unless $gnuplot_prev_mon; if ($mon < $gnuplot_prev_mon) { $gnuplot_year++; } $gnuplot_prev_mon = $mon; $rv = Mktime($gnuplot_year, $mon, $day, $hour, $min, $sec); $gnuplot_comment_str = $packet_val; } } else { ### see if this field is non-digit data, and map ### to a digit if so if (defined $gnuplot_non_digit_packet_fields{$tok}) { ### it is a non-digit value from the packet, so append a comment ### to the gnuplot data file that contains that original value. ### This allows the user to map integers back to their original ### value (say IP integers back to the dotted-quad notation). $gnuplot_comment_str = $packet_val; if ($gnuplot_non_digit_packet_fields{$tok} eq 'ip2int') { ### get the minimal IP integer from the %gnuplot_ip2int cache $rv = $gnuplot_ip2int{$packet_val}; } elsif ($gnuplot_non_digit_packet_fields{$tok} eq 'proto2int') { ### convert protocol to integer $rv = &proto2int($packet_val); } elsif ($gnuplot_non_digit_packet_fields{$tok} eq 'intf2int') { ### convert interface to integer $rv = &intf2int($packet_val); } elsif ($gnuplot_non_digit_packet_fields{$tok} eq 'hashentry') { ### map the value to a digit unless (defined $gnuplot_non_digit_map{$tok} and defined $gnuplot_non_digit_map{$tok}{'data'} and defined $gnuplot_non_digit_map{$tok} {'data'}{$packet_val}) { unless (defined $gnuplot_non_digit_map{$tok}{'ctr'}) { $gnuplot_non_digit_map{$tok}{'ctr'} = 0; } $gnuplot_non_digit_map{$tok}{'ctr'}++; $gnuplot_non_digit_map{$tok}{'data'}{$packet_val} = $gnuplot_non_digit_map{$tok}{'ctr'}; } $rv = $gnuplot_non_digit_map{$tok}{'data'}{$packet_val}; } } } return $rv, $gnuplot_comment_str; } sub ip2int() { my $ip = shift; my $ip_int = 0; unless (defined $ip2int_cache{$ip}) { # my @octets = split /\./, $ip; for (split /\./, $ip) { $ip_int = $ip_int*256 + $_; } $ip2int_cache{$ip} = $ip_int; } return; } sub proto2int() { my $proto = shift; my $rv = -1; if ($proto =~ /^\d+$/) { $rv = $proto; } elsif ($proto =~ /tcp/i) { $rv = 6; } elsif ($proto =~ /udp/i) { $rv = 17; } elsif ($proto =~ /icmp/i) { $rv = 1; } return $rv; } sub intf2int() { my $intf = shift; my $rv = -1; if ($intf =~ /(\d+)$/) { $rv = $1; } return $rv; } sub analysis_fields() { $csv_fields = $analysis_fields; return &csv_tokens(); } sub csv_tokens() { my @tokens = (); my @match_criteria = (); if ($csv_fields) { my @tok_tmp = split /\s+/, $csv_fields; for my $tok_str (@tok_tmp) { my $token = $tok_str; my $search = ''; my $negate = 0; if ($tok_str =~ m|(\w+):(\S+)|) { $token = $1; $search = $2; } else { if ($analysis_fields) { die "[*] $tok_str requires a search criteria in -A mode."; } } $search =~ s/\,$//; if ($token eq 'timestamp') { $csv_have_timestamp = 1; } if ($search =~ /^not/) { $negate = 1; $search =~ s/^not//; } for my $count_type (qw/countabs countuniq countday counthouruniq countminuniq countdayuniq counthour countmin countday count/) { if ($search =~ /,$count_type$/ or $search =~ /^$count_type/) { $search =~ s/,$count_type$//; $search =~ s/^$count_type//; die "[*] Counts against multiple fields are not supported." if $gnuplot_count_type; $gnuplot_count_type = $count_type; $gnuplot_count_element = $#tokens + 1; if ($count_type eq 'countday' or $count_type eq 'counthour' or $count_type eq 'countmin' or $count_type eq 'countdayuniq' or $count_type eq 'counthouruniq' or $count_type eq 'countminuniq') { $gnuplot_sort_style = 'time'; die "[*] The first search field must be 'timestamp' ", "for time-based counts" if $#tokens == -1; die "[*] The first search field must be 'timestamp' ", "for time-based counts" unless $tokens[0] eq 'timestamp'; } } } $token = 'src' if $token eq 'SRC'; $token = 'dst' if $token eq 'DST'; $token = 'sp' if $token eq 'SPT'; $token = 'dp' if $token eq 'DPT'; $token = 'proto' if $token eq 'PROTO'; $token = 'tos' if $token eq 'TOS'; $token = 'win' if $token eq 'WIN'; $token = 'itype' if $token eq 'TYPE'; $token = 'icode' if $token eq 'CODE'; $token = 'ttl' if $token eq 'TTL'; $token = 'ip_id' if $token eq 'ID'; $token = 'icmp_seq' if $token eq 'SEQ'; $token = 'ip_len' if $token eq 'LEN'; $token = 'intf' if $token eq 'IN' or $token eq 'OUT'; unless (defined $pkt_NF_init{$token}) { print "[*] $token is not a valid packet field; valid ", "fields are:\n"; for my $key (sort keys %pkt_NF_init) { print " $key\n"; } die; } push @tokens, $token; if ($search) { my %search_hsh = ('negate' => $negate); if ($search =~ m|^\d+$|) { $search_hsh{'num'} = $search; } elsif ($search =~ m|^>(\d+)$|) { $search_hsh{'gt'} = $1; die "[*] $token value must be >= 0" unless $1 >= 0; } elsif ($search =~ m|^<(\d+)$|) { $search_hsh{'lt'} = $1; die "[*] $token value must be >= 0" unless $1 >= 0; } elsif ($search =~ m|^/(.*?)/$|) { $search_hsh{'re'} = qr|$1|; } elsif ($search =~ m|^'(.*?)'$|) { $search_hsh{'str'} = $1; } elsif ($search =~ m|^$ipv4_re/$ipv4_re$|) { $search_hsh{'net'} = $search; $search_hsh{'net_obj'} = new NetAddr::IP($search) or die "[*] NetAddr::IP($search) error"; } elsif ($search =~ m|^$ipv4_re/\d+$|) { $search_hsh{'net'} = $search; $search_hsh{'net_obj'} = new NetAddr::IP($search) or die "[*] NetAddr::IP($search) error"; } elsif ($search =~ m|^$ipv4_re$|) { $search_hsh{'ip'} = $search; $search_hsh{'ip_obj'} = new NetAddr::IP($search) or die "[*] NetAddr::IP($search) error"; } elsif ($search =~ m|\:|) { ### see if this is an IPv6 address if ($search =~ m|\/|) { $search_hsh{'net'} = $search; $search_hsh{'net_obj'} = new6 NetAddr::IP($search) or die "[*] NetAddr::IP($search) error"; } else { $search_hsh{'net'} = $search; $search_hsh{'net_obj'} = new6 NetAddr::IP($search) or die "[*] NetAddr::IP($search) error"; } } else { die "[*] Unrecognized value for $token"; } push @match_criteria, \%search_hsh; } else { push @match_criteria, {}; } } } else { @tokens = (qw( timestamp src dst sp dp proto flags ip_len intf chain log_prefix )); } return \@tokens, \@match_criteria; } sub benchmark_mode() { my @fw_packets = (); print scalar localtime(), " [+] Entering benchmark mode.\n"; if ($num_packets) { print scalar localtime(), " [+] Executing a $num_packets packet test.\n"; } else { if ($fw_data_file) { print scalar localtime(), ' [+] The --packets command line ', "option was not specified.\n"; print scalar localtime(), " [+] Defaulting to read the entire $fw_data_file file.\n"; } else { print scalar localtime(), ' [+] The --packets command line ', "option was not specified.\n"; print scalar localtime(), " [+] Defaulting to a 10,000 packet test.\n"; $num_packets = 10000; } } ### initialize benchmarking test packets if we are running ### in benchmark mode if ($fw_data_file) { if ($num_packets) { print scalar localtime(), " [+] Creating $num_packets packet ", "array from $fw_data_file\n"; } else { print scalar localtime(), " [+] Creating packet ", "array from complete $fw_data_file\n"; } my $ctr = 0; open F, "< $fw_data_file" or die "[*] Could not open $fw_data_file: $!"; while () { chomp; push @fw_packets, $_; $ctr++; last if $num_packets > 0 and $ctr >= $num_packets; } close F; } else { ### FIXME better random packet data tests, add IP and TCP options, etc. my $test_pkt = 'Feb 15 16:42:58 orthanc kernel: DROP IN=eth0 ' . 'OUT= MAC=00:a0:cc:28:42:5a:00:03:6c:00:98:54:08:00 ' . 'SRC=192.168.10.2 DST=192.168.10.1 LEN=48 TOS=0x00 PREC=0x00 ' . 'TTL=110 ID=13383 DF PROTO=TCP SPT=1389 '; my $test_pktend = 'WINDOW=16384 RES=0x00 SYN URGP=0'; print scalar localtime(), " [+] Creating packet array.\n"; my $dp = 1000; for (my $i=0; $i <= $num_packets; $i++) { push @fw_packets, "$test_pkt DPT=$dp $test_pktend"; $dp++ if $dp < 50000; } } my $b_time = time(); print scalar localtime(), " [+] check_scan()\n" if $benchmark; &check_scan(\@fw_packets); print scalar localtime(), " [+] Packet creation and processing time: ", time() - $b_time, " sec.\n"; print scalar localtime(), " [+] Exiting benchmark mode.\n"; return 0; } sub fw_analyze_mode() { my $run_fw_check = 0; if ($fw_analyze) { $run_fw_check = 1; } else { ### if psad is running on a syslog server, do not check the firewall ### rules since they may not be local. Also, do not check the ### firewall if psad is configured to parse all iptables messages. unless ($no_fwcheck or $syslog_server or $config{'ENABLE_FW_LOGGING_CHECK'} eq 'N') { $run_fw_check = 1; } } my $exit_status = 0; if ($run_fw_check) { my $opts = "-c $config_file "; $opts .= " --fw-analyze" if $fw_analyze; $opts .= " --fw-file $fw_file" if $fw_file; $opts .= " -L $lib_dir" if $lib_dir; $opts .= " --test-mode" if $test_mode; $opts .= " -O $override_config_str" if $override_config_str; $opts .= " --no-fw-search-all" if $config{'FW_SEARCH_ALL'} eq 'N'; $exit_status = (system "$cmds{'fwcheck_psad'} $opts") >> 8; } return $exit_status; } ### display the status of all four psad daemons sub status() { my $rv = 0; ### assume psad is not running and test... for my $pidname (qw(psadwatchd kmsgsd psad)) { my $pidfile = $pidfiles{$pidname}; if (-e $pidfile) { my $pid = &is_running($pidfile); if ($pid) { print "[+] $pidname (pid: $pid)"; ### FIXME: should probably just parse /proc instead of ### using ps my @ps_out = @{&run_command($cmds{'ps'}, 'auxww')}; PS: for my $line (@ps_out) { chomp $line; if ($line =~ /^\S+\s+$pid\s+(\S+)\s+(\S+)/) { print " %CPU: $1 %MEM: $2\n"; print " Running since: " . localtime((stat($pidfile))[9]) . "\n"; ### print individual ip info &status_psad_daemon() if $pidname eq 'psad'; } } print "\n"; $rv = 1; } else { my $print = 1; if ($pidname eq 'kmsgsd' and ($config{'SYSLOG_DAEMON'} =~ /ulog/i or $config{'ENABLE_SYSLOG_FILE'} eq 'Y')) { $print = 0; } print "[-] psad: $pidname is not running on ", "$config{'HOSTNAME'}\n" if $print; } } else { my $print = 1; if ($pidname eq 'kmsgsd' and ($config{'SYSLOG_DAEMON'} =~ /ulog/i or $config{'ENABLE_SYSLOG_FILE'} eq 'Y')) { $print = 0; } print "[-] psad: pid file $pidfile does not exist for ", "$pidname on $config{'HOSTNAME'}\n" if $print; } } return $rv; } sub status_psad_daemon() { my $cmdline; ### get any command line args if (-e $cmdline_file) { open CMD, "< $cmdline_file" or die '[*] Could not open ', "$cmdline_file: $!"; $cmdline = ; chomp $cmdline; } if ($cmdline) { print " Command line arguments: $cmdline\n"; } else { print " Command line arguments: [none specified]\n"; } print " Alert email address(es): ", "$config{'EMAIL_ADDRESSES'}\n\n"; ### build @local_nets array my $connected_subnets_ar = &get_connected_subnets(); for my $net (@$connected_subnets_ar) { push @local_nets, $net; } ### import filesystem information in /var/log/psad/ &import_filesystem_scan_data(); ### print main status output to stdout &print_scan_status(); return; } sub import_filesystem_scan_data() { ### import /var/log/psad/ directories &import_ip_dirs(); ### import global packet counters &import_packet_counters(); ### import dshield stats &import_dshield_stats(); ### import iptables prefix stats &import_ipt_prefixes(); ### import top scanned ports &import_top_scanned_ports(); ### import top signatures &import_top_sigs(); ### import top attackers &import_top_attackers(); return; } sub print_scan_status() { my @lines = (); push @lines, "[+] Version: psad v$version\n\n"; ### print top signature matches push @lines, $_ for @{&print_top_sigs()}; ### print top attackers push @lines, $_ for @{&print_top_attackers()}; ### print top scanned ports push @lines, $_ for @{&print_top_scanned_ports()}; ### print iptables prefixes push @lines, $_ for @{&print_ipt_prefixes()}; ### print iptables prefixes unless ($analyze_mode) { push @lines, $_ for @{&print_dshield_stats()}; ### print block status of IP addresses blocked by iptables if ($status_ip) { my $ar = &print_blocked_ip_status($status_ip); push @lines, $_ for @$ar; } else { my $ar = &print_blocked_ip_status(''); push @lines, $_ for @$ar; } } ### print packet counters push @lines, $_ for @{&print_packet_counters()}; return if $status_summary; push @lines, "[+] IP Status Detail:\n"; my %uniq_srcs = (); my %uniq_dsts = (); my $printed = 0; for my $dl (qw/5 4 3 2 1/) { SRC: for my $src (sort keys %scan) { next SRC unless $scan_dl{$src} == $dl; my $dl = $scan_dl{$src}; next SRC unless $dl >= $config{'MIN_DANGER_LEVEL'}; if ($status_min_dl) { next unless $dl >= $status_min_dl; } $uniq_srcs{$src} = ''; my $total_dsts = keys %{$scan{$src}}; my $tot_pkts = 0; my $uniq_sigs = 0; my $tot_protocols = 0; for my $dst (keys %{$scan{$src}}) { if (defined $scan{$src}{$dst}{'tot_protocols'}) { $tot_protocols += $scan{$src}{$dst}{'tot_protocols'}; } next unless defined $scan{$src}{$dst}{'absnum'}; $tot_pkts += $scan{$src}{$dst}{'absnum'}; } for my $proto (keys %protocols) { for my $dst (keys %{$scan{$src}}) { next unless defined $scan{$src}{$dst}{$proto} and defined $scan{$src}{$dst}{$proto}{'sid'}; for my $sid (keys %{$scan{$src}{$dst}{$proto}{'sid'}}) { $uniq_sigs++; } } } ### source IP line my $src_str = "\nSRC: $src, DL: $dl, Dsts: $total_dsts" . ", Pkts: $tot_pkts, Total protocols: $tot_protocols, " . "Unique sigs: $uniq_sigs"; my $src_obj = new NetAddr::IP($src) or die "[*] NetAddr::IP($src) error"; if ($src_obj->version() == 6) { $src_str = "\nSRC: $src (" . $src_obj->short() . "), DL: $dl, Dsts: $total_dsts" . ", Pkts: $tot_pkts, Total protocols: $tot_protocols, " . "Unique sigs: $uniq_sigs"; } unless ($analyze_mode) { my $tot_alerts = 0; if ($config{'ENABLE_EMAIL_LIMIT_PER_DST'} eq 'Y') { for my $dst (keys %{$scan{$src}}) { $tot_alerts += $scan_email_ctrs{$src}{$dst}{'email_ctr'}; } } else { $tot_alerts += $scan_email_ctrs{$src}{'email_ctr'}; } $src_str .= ", Email alerts: $tot_alerts"; } if (&is_local($src, $src_obj)) { $src_str .= ', Local IP'; } $printed = 1; push @lines, "$src_str\n"; if (defined $p0f{$src}) { push @lines, " Source OS fingerprint(s):\n"; for my $os (keys %{$p0f{$src}}) { push @lines, " $os\n"; } } elsif (defined $posf{$src} and defined $posf{$src}{'guess'}) { push @lines, " Source OS fingerprint:\n", " $posf{$src}{'guess'}\n"; } next unless $total_dsts > 0; push @lines, "\n"; DST: for my $dst (keys %{$scan{$src}}) { my $dst_str = " DST: $dst"; my $dst_obj = new NetAddr::IP($dst) or die "[*] NetAddr::IP($dst) error"; if ($dst_obj->version() == 6) { $dst_str = " DST: $dst (" . $dst_obj->short() . ')'; } if (&is_local($dst, $dst_obj)) { $dst_str .= ', Local IP'; } push @lines, "$dst_str\n"; $uniq_dsts{$dst} = ''; if (defined $scan{$src}{$dst}{'chain'}) { for my $chain (keys %{$scan{$src}{$dst}{'chain'}}) { for my $intf (keys %{$scan{$src}{$dst}{'chain'} {$chain}}) { my $tot_scanned_protocols = 0; for my $proto (keys %{$scan{$src}{$dst}{'chain'} {$chain}{$intf}}) { $tot_scanned_protocols++; next unless $proto eq 'tcp' or $proto eq 'udp' or $proto eq 'udplite'; my $start_port = $scan{$src}{$dst} {$proto}{'abs_sp'}; my $end_port = $scan{$src}{$dst} {$proto}{'abs_ep'}; my $ctr = $scan{$src}{$dst}{'chain'}{$chain} {$intf}{$proto}; if ($start_port == $end_port) { push @lines, " Scanned ports: " . uc($proto) . " $start_port, Pkts: $ctr, " . "Chain: $chain, Intf: $intf\n"; } else { push @lines, " Scanned ports: " . uc($proto) . " $start_port-$end_port, Pkts: " . "$ctr, Chain: $chain, Intf: $intf\n"; } } push @lines, " Total scanned IP protocols: " . "$tot_scanned_protocols, Chain: $chain, Intf: $intf\n"; } } } ### signature matches for my $proto (keys %protocols) { next unless defined $scan{$src}{$dst}{$proto} and defined $scan{$src}{$dst}{$proto}{'sid'}; for my $sid (keys %{$scan{$src}{$dst}{$proto}{'sid'}}) { my $msg = '[NA]'; if (defined $fwsnort_sigs{$sid} and defined $fwsnort_sigs{$sid}{'msg'}) { $msg = $fwsnort_sigs{$sid}{'msg'}; } elsif (defined $sigs{$sid} and defined $sigs{$sid}{'msg'}) { $msg = $sigs{$sid}{'msg'}; } for my $chain (keys %{$scan{$src}{$dst}{$proto} {'sid'}{$sid}}) { my $matches = $scan{$src}{$dst}{$proto} {'sid'}{$sid}{$chain}{'pkts'}; my $sig_str = qq| Signature match: "$msg"\n| . " " . uc($proto) . ", Chain: $chain, Count: $matches"; if ($proto eq 'tcp' or $proto eq 'udp' or $proto eq 'udplite') { my $dp = $scan{$src}{$dst}{$proto} {'sid'}{$sid}{$chain}{'dp'}; $sig_str .= ", DP: $dp"; if ($proto eq 'tcp') { my $flags = $scan{$src}{$dst}{$proto} {'sid'}{$sid}{$chain}{'flags'}; $sig_str .= ", $flags"; } } $sig_str .= ", Sid: $sid"; push @lines, $sig_str, "\n"; } } } ### icmp validation for my $proto ('icmp', 'icmp6') { next unless defined $scan{$src}{$dst}{$proto}; if (defined $scan{$src}{$dst}{$proto}{'invalid_type'}) { my $hr = $scan{$src}{$dst}{$proto}{'invalid_type'}; for my $type (keys %$hr) { for my $chain (keys %{$hr->{$type}}) { my $pkts = $hr->{$type}->{$chain}->{'pkts'}; push @lines, qq| Invalid | . uc($proto) . qq| type: "$type" Chain: $chain, Packets: | . "$pkts\n"; } } } if (defined $scan{$src}{$dst}{$proto}{'invalid_code'}) { my $hr = $scan{$src}{$dst}{$proto}{'invalid_code'}; for my $type (keys %$hr) { for my $code (keys %{$hr->{$type}}) { for my $chain (keys %{$hr->{$type}->{$code}}) { my $pkts = $hr->{$type}->{$code}->{$chain}->{'pkts'}; my $type_text = ''; if ($proto eq 'icmp') { $type_text = $valid_icmp_types{$type}{'text'}; } else { $type_text = $valid_icmp6_types{$type}{'text'}; } push @lines, qq| Invalid | . uc($proto) . qq| code: "$code" for | . uc($proto) . qq| "$type_text" packet | . qq|Chain: $chain, Packets: $pkts\n|; } } } } } } } } unless ($printed) { push @lines, " [NONE]\n"; } push @lines, "\n Total scan sources: " . (keys %uniq_srcs) . "\n"; push @lines, " Total scan destinations: " . (keys %uniq_dsts) . "\n\n"; my $out_file = $config{'STATUS_OUTPUT_FILE'}; if ($analyze_mode) { $out_file = $config{'ANALYSIS_OUTPUT_FILE'}; } open F, "> $out_file" or die "[*] Could not open $out_file: $!"; for my $line (@lines) { print $line; print F $line; } close F; print "[+] These results are available in: $out_file\n"; return; } sub print_blocked_ip_status() { my $specific_ip = shift; return unless -e $config{'AUTO_BLOCK_IPT_FILE'}; unlink "$config{'AUTO_BLOCK_IPT_FILE'}.status" if -e "$config{'AUTO_BLOCK_IPT_FILE'}.status"; copy $config{'AUTO_BLOCK_IPT_FILE'}, "$config{'AUTO_BLOCK_IPT_FILE'}.status"; open F, "< $config{'AUTO_BLOCK_IPT_FILE'}.status" or die "[*] $config{'AUTO_BLOCK_IPT_FILE'}.status: $!"; my @lines = ; close F; unlink "$config{'AUTO_BLOCK_IPT_FILE'}.status"; my @print_lines = (); if ($specific_ip) { push @print_lines, " iptables auto-blocking status for: $specific_ip: \n"; } else { push @print_lines, " iptables auto-blocked IPs:\n"; } my %ipt_opts = ( 'iptables' => $cmds{'iptables'}, 'iptout' => $config{'IPT_OUTPUT_FILE'}, 'ipterr' => $config{'IPT_ERROR_FILE'} ); $ipt_opts{'debug'} = 1 if $debug; $ipt_opts{'verbose'} = 1 if $verbose; my $ipt = new IPTables::ChainMgr(%ipt_opts) or die '[*] Could not acquire IPTables::ChainMgr object.'; my $found_line = 0; for my $line (@lines) { chomp $line; if ($line =~ /^\s*(\S+)/) { my $ip = $1; ### this may be a subnet next unless $ip =~ /$ipv4_re/; if ($specific_ip) { next unless $ip eq $specific_ip; } my $timestamp = ''; my $time_remain = 0; ### older versions do not have the timestamp if ($line =~ /^\s*\S+\s+(\d+)/) { $timestamp = $1; my ($timeout, $timeout_str) = &get_block_timeout($scan_dl{$ip}); if ($timeout > 0) { $time_remain = $timeout - (time() - $timestamp); $time_remain = 0 if $time_remain < 0; push @print_lines, " $ip ($time_remain ", "seconds remaining)\n"; } else { push @print_lines, " $ip (unlimited timeout)\n"; } } else { push @print_lines, " $ip\n"; } my $blocked = 0; for my $hr (@ipt_config) { if ($ipt->find_ip_rule( $ip, '0.0.0.0/0', $hr->{'table'}, $hr->{'to_chain'}, $hr->{'target'})) { $blocked = 1; push @print_lines, " $hr->{'to_chain'}", "($hr->{'target'})\n" if $verbose; } } $found_line = 1; if ($time_remain > 0) { ### we should see that the IP is still blocked unless ($blocked) { push @print_lines, ' [not currently blocked, sending ', "cleanup message]\n"; system "$cmds{'psad'} --fw-rm-block-ip $ip"; } } else { push @print_lines, ' [expired timeout, sending ', "cleanup message]\n"; system "$cmds{'psad'} --fw-rm-block-ip $ip"; } } } push @print_lines, " [NONE]\n" unless $found_line; push @print_lines, "\n"; return \@print_lines; } sub import_old_scans() { &import_filesystem_scan_data(); &sys_log('imported ' . (keys %scan_dl) . ' scanning IP ' . 'addresses from previous psad instance'); return; } sub import_packet_counters() { return unless -e $config{'PACKET_COUNTER_FILE'}; open F, "< $config{'PACKET_COUNTER_FILE'}" or die "[*] $config{'PACKET_COUNTER_FILE'}: $!"; while () { if (/(\w+):\s+(\d+)/) { $proto_ctrs{$1} = $2; } } close F; return; } sub print_packet_counters() { my $str = " Total protocol packet counters:\n"; for my $proto (sort keys %protocols) { next unless defined $proto_ctrs{$proto}; if (defined $protocol_strings{$proto}) { $str .= sprintf "%12s: %s\n", $protocol_strings{$proto}{'name'}, "$proto_ctrs{$proto} pkts, ($protocol_strings{$proto}{'desc'})"; } else { $str .= sprintf "%12s: %s\n", $proto, "$proto_ctrs{$proto} pkts"; } } $str .= "\n"; return [$str]; } sub import_protocol_strings() { open F, "< $config{'PROTOCOLS_FILE'}" or die "[*] Could not ", "open $config{'PROTOCOLS_FILE'}: $!"; while () { ### gre 47 GRE # General Routing Encapsulation next if /^\s*#/; if (/^\s*(\S+)\s+(\d+)\s+\S+\s+#\s+(.*)/) { $protocol_strings{$2}{'name'} = $1; $protocol_strings{$2}{'desc'} = $3; } } close F; return; } sub import_dshield_stats() { if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y' and -e $config{'DSHIELD_COUNTER_FILE'}) { open DS, "< $config{'DSHIELD_COUNTER_FILE'}" or die "[*] Could not ", "open $config{'DSHIELD_COUNTER_FILE'}: $!"; my @lines = ; close DS; for my $line (@lines) { if ($line =~ /emails:\s+(\d+)/) { $dshield_email_ctr = $1; } elsif ($line =~ /packets:\s+(\d+)/) { $dshield_lines_ctr = $1; } } } return; } sub print_dshield_stats() { my @lines = (); if ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y' and -e $config{'DSHIELD_COUNTER_FILE'}) { push @lines, " DShield stats:\n"; push @lines, " total emails: $dshield_email_ctr\n"; push @lines, " total packets: $dshield_lines_ctr\n\n"; } return \@lines; } sub import_ipt_prefixes() { if (-e $config{'IPT_PREFIX_COUNTER_FILE'}) { open F, "< $config{'IPT_PREFIX_COUNTER_FILE'}" or die "[*] Could not ", "open $config{'IPT_PREFIX_COUNTER_FILE'}: $!"; while () { if (/^\s*(.*?):\s+(\d+)/) { my $prefix = $1; my $count = $2; $ipt_prefixes{$prefix} = $count; } } close F; } return; } sub print_ipt_prefixes() { my @lines = (); push @lines, "[+] iptables log prefix counters:\n"; if (%ipt_prefixes) { for my $prefix (keys %ipt_prefixes) { my $count = $ipt_prefixes{$prefix}; push @lines, " \"$prefix\": $count\n"; } } else { push @lines, " [NONE]\n"; } push @lines, "\n"; return \@lines; } sub import_ip_dirs() { opendir D, $config{'PSAD_DIR'} or die "[*] Could not open dir: $config{'PSAD_DIR'}: $!"; my @files = readdir D; closedir D; my $import_ctr = 0; my $curr_pwd = getcwd(); chdir $config{'PSAD_DIR'} or die $!; SRCIP: for my $src (@files) { next SRCIP unless ($src =~ /$ipv4_re/ and -d $src); ### define as many hash keys as we can (older versions ### of psad don't include several of these files). my $num_emails = 0; if (-e "${src}/danger_level") { open DL, "< ${src}/danger_level" or next SRCIP; my $dl =
; close DL; chomp $dl; next SRCIP unless $dl >= 1; $scan_dl{$src} = $dl; ### set the dl for $src } if (-e "${src}/os_guess") { open OS, "< ${src}/os_guess" or next SRCIP; my $os_guess = ; close OS; chomp $os_guess; ### set the os guess for $src $posf{$src}{'guess'} = $os_guess; } if (-e "${src}/p0f_guess") { open OS, "< ${src}/p0f_guess" or next SRCIP; my @lines = ; close OS; for my $line (@lines) { chomp $line; $p0f{$src}{$line} = ''; } } opendir IPDIR, $src or next SRCIP; my @scan_files = readdir IPDIR; closedir IPDIR; ### get all of the destination ip addresses my %dst_ips; for my $scan_file (@scan_files) { next if $scan_file =~ /_whois/; ### may be _whois if ($scan_file =~ /($ipv4_re)/) { $dst_ips{$1} = ''; } } for my $dst (keys %dst_ips) { my $email_ctr_file = ''; if (-e "${src}/${dst}_email_ctr") { $email_ctr_file = "${src}/${dst}_email_ctr"; } elsif (-e "${src}/email_ctr") { $email_ctr_file = "${src}/email_ctr"; } if ($email_ctr_file) { open E, "< $email_ctr_file" or die "[*] Could not open ", "$email_ctr_file: $!"; $num_emails = ; close E; chomp $num_emails; if ($config{'ENABLE_EMAIL_LIMIT_PER_DST'} eq 'Y') { $scan_email_ctrs{$src}{$dst}{'email_ctr'} = $num_emails; } else { $scan_email_ctrs{$src}{'email_ctr'} = $num_emails; } $scan{$src}{$dst}{'alerted'} = 1; } else { if ($config{'ENABLE_EMAIL_LIMIT_PER_DST'} eq 'Y') { $scan_email_ctrs{$src}{$dst}{'email_ctr'} = 0; } else { $scan_email_ctrs{$src}{'email_ctr'} = 0; } $scan{$src}{$dst}{'alerted'} = 0; } if (-e "${src}/${dst}_packet_ctr") { open PKTS, "< ${src}/${dst}_packet_ctr" or die $!; my @lines = ; close PKTS; for my $line (@lines) { my $chain; my $intf; my $pkts; my $proto; if ($line =~ /^(\w+)_(\w+)_icmp:\s+(\d+)/) { $chain = $1; $intf = $2; $pkts = $3; $chain = uc $chain if $chain eq 'input' or $chain eq 'forward' or $chain eq 'output'; $proto = 'icmp'; $scan{$src}{$dst}{'chain'}{$chain}{$intf}{$proto} += $pkts; $scan{$src}{$dst}{'absnum'} += $pkts; $scan{$src}{$dst}{'tot_protocols'}++; } elsif ($line =~ /^(\w+)_(\w+)_(tcp|udp): \s+(\d+)\s+\[(\S+)\]/x) { $chain = $1; $intf = $2; $proto = $3; $pkts = $4; $chain = uc $chain if $chain eq 'input' or $chain eq 'forward' or $chain eq 'output'; my $port_rng = $5; if ($port_rng =~ /(\d+)\-(\d+)/) { $scan{$src}{$dst}{$proto}{'abs_sp'} = $1; $scan{$src}{$dst}{$proto}{'abs_ep'} = $2; } elsif ($port_rng =~ /(\d+)/) { $scan{$src}{$dst}{$proto}{'abs_sp'} = $1; $scan{$src}{$dst}{$proto}{'abs_ep'} = $1; } $scan{$src}{$dst}{'chain'}{$chain}{$intf}{$proto} += $pkts; $scan{$src}{$dst}{'absnum'} += $pkts; $scan{$src}{$dst}{'tot_protocols'}++; } elsif ($line =~ /^(\w+)_(\w+)_(\d+):\s+(\d+)/) { $chain = $1; $intf = $2; $proto = $3; $pkts = $4; $chain = uc $chain if $chain eq 'input' or $chain eq 'forward' or $chain eq 'output'; $scan{$src}{$dst}{'chain'}{$chain}{$intf}{$proto} += $pkts; $scan{$src}{$dst}{'absnum'} += $pkts; $scan{$src}{$dst}{'tot_protocols'}++; } } } if (-e "${src}/${dst}_start_time") { open ST, "< ${src}/${dst}_start_time" or next SRCIP; my $s_time = ; close ST; chomp $s_time; $scan{$src}{$dst}{'s_time'} = $s_time; } if (-e "${src}/${dst}_signatures") { open F, "< ${src}/${dst}_signatures" or die "[*] Could not open ", "${src}/${dst}_signatures: $!"; my @lines = ; close F; for my $line (@lines) { ### Format: "" \ ### ### 1165099853 249 1 "DDOS mstream client to handler" \ ### INPUT tcp 15104 SYN 0 0 if ($line =~ /^\s*(\d+)\s+(\d+)\s+(\d+)\s+\"(.*?)\" \s+(\w+)\s+(\w+)\s+(\d+)\s+\"(.*?)\"\s+(\d+) \s+(\d+)/x) { my $time = $1; my $sid = $2; my $matches = $3; my $msg = $4; my $chain = $5; my $proto = $6; my $dp = $7; my $flags = $8; my $is_fwsnort = $9; my $is_psad = $10; ### build up the %scan sid data $scan{$src}{$dst}{$proto}{'sid'}{$sid}{$chain} {'time'} = $time; $scan{$src}{$dst}{$proto}{'sid'}{$sid}{$chain} {'dp'} = $dp; $scan{$src}{$dst}{$proto}{'sid'}{$sid}{$chain} {'flags'} = $flags; $scan{$src}{$dst}{$proto}{'sid'}{$sid}{$chain} {'pkts'} = $matches; $scan{$src}{$dst}{$proto}{'sid'}{$sid}{$chain} {'is_fwsnort'} = $is_fwsnort; $scan{$src}{$dst}{$proto}{'sid'}{$sid}{$chain} {'is_psad'} = $is_psad; } } } } $import_ctr++; } chdir $curr_pwd or die $!; return; } sub import_top_sigs() { return unless -e $config{'TOP_SIGS_FILE'}; open F, "< $config{'TOP_SIGS_FILE'}" or die "[*] Could not ", "open $config{'TOP_SIGS_FILE'}: $!"; while () { next unless /\S/; next if /^\s*#/; if (/^\s*(\d+)\s+\"(.*?)\"\s+(\d+)\s+(\d+)\s+(\w+)/) { my $sid = $1; my $msg = $2; my $matches = $3; my $sources = $4; my $proto = $5; $top_sigs{$sid} = $matches; for (my $i=0; $i<$sources; $i++) { ### this is a hack since we can't recover ### the specific IP addresses $sig_sources{$sid}{$i} = ''; } } } close F; return; } sub print_top_sigs() { my @lines = (); my $printed = 0; if ($config{'STATUS_SIGS_THRESHOLD'} > 0) { push @lines, "[+] Top $config{'STATUS_SIGS_THRESHOLD'} " . "signature matches:\n"; } else { push @lines, "[+] Top signature matches:\n"; } my $ctr = 0; for my $sid (sort {$top_sigs{$b} <=> $top_sigs{$a}} keys %top_sigs) { my $found = 0; my $num_sources = keys %{$sig_sources{$sid}}; if (defined $sigs{$sid} and defined $sigs{$sid}{'msg'}) { push @lines, qq| "$sigs{$sid}{'msg'}" | . qq|($sigs{$sid}{'proto'}), | . qq|Count: $top_sigs{$sid}, Unique sources: | . qq|$num_sources, Sid: $sid\n|; $found = 1; $printed = 1; } elsif (defined $fwsnort_sigs{$sid} and defined $fwsnort_sigs{$sid}{'msg'}) { push @lines, qq| "$fwsnort_sigs{$sid}{'msg'}" | . qq|($fwsnort_sigs{$sid}{'proto'}), | . qq|Count: $top_sigs{$sid}, Unique sources: | . qq|$num_sources, Sid: $sid\n|; $found = 1; $printed = 1; } $ctr++ if $found; if ($config{'STATUS_SIGS_THRESHOLD'} > 0) { last if $ctr >= $config{'STATUS_SIGS_THRESHOLD'}; } } unless ($printed) { push @lines, " [NONE]\n"; } push @lines, "\n"; return \@lines; } sub import_top_scanned_ports() { return unless -e $config{'TOP_SCANNED_PORTS_FILE'}; open F, "< $config{'TOP_SCANNED_PORTS_FILE'}" or die "[*] Could not open ", "$config{'TOP_SCANNED_PORTS_FILE'}: $!"; while () { next unless /\S/; next if /^\s*#\s*$/; next if /^\s*#\s*Format/; chomp; ### Format: if (/^\s*(\w+)\s+(\d+)\s+(\d+)/) { my $proto = $1; my $port = $2; my $count = $3; if ($proto eq 'tcp') { $top_tcp_ports{$port} = $count; } elsif ($proto eq 'udp') { $top_udp_ports{$port} = $count; } elsif ($proto eq 'udplite') { $top_udplite_ports{$port} = $count; } } } close F; return; } sub print_top_scanned_ports() { my @lines = (); my $printed = 0; if ($config{'STATUS_PORTS_THRESHOLD'} > 0) { push @lines, "[+] Top $config{'STATUS_PORTS_THRESHOLD'} scanned ports:\n"; } else { push @lines, "[+] Top scanned ports:\n"; } if (%top_tcp_ports) { my $ctr = 0; for my $dp (sort {$top_tcp_ports{$b} <=> $top_tcp_ports{$a}} keys %top_tcp_ports) { my $str = sprintf " tcp %-5d $top_tcp_ports{$dp} packets\n", $dp; push @lines, $str; $printed = 1; $ctr++; if ($config{'STATUS_PORTS_THRESHOLD'} > 0) { last if $ctr >= $config{'STATUS_PORTS_THRESHOLD'}; } } } if (%top_udp_ports) { my $ctr = 0; push @lines, "\n"; for my $dp (sort {$top_udp_ports{$b} <=> $top_udp_ports{$a}} keys %top_udp_ports) { my $str = sprintf " udp %-5d $top_udp_ports{$dp} packets\n", $dp; push @lines, $str; $printed = 1; $ctr++; if ($config{'STATUS_PORTS_THRESHOLD'} > 0) { last if $ctr >= $config{'STATUS_PORTS_THRESHOLD'}; } } } unless ($printed) { push @lines, " [NONE]\n"; } push @lines, "\n"; return \@lines; } sub import_top_attackers() { return unless -e $config{'TOP_ATTACKERS_FILE'}; open F, "< $config{'TOP_ATTACKERS_FILE'}" or die "[*] Could not open ", "$config{'TOP_ATTACKERS_FILE'}: $!"; while () { next unless /\S/; next if /^\s*#\s*$/; ### Format:
if (/^\s*($ipv4_re)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d)/) { my $ip = $1; my $dl = $2; my $tot_pkts = $3; my $uniq_sigs = $4; my $sig_matches = $5; my $is_local = $6; ### do not add any IP from the top_attackers file ### that does not have a /var/log/psad/ ### directory next unless defined $scan_dl{$ip}; if (defined $scan_dl{$ip}) { $scan_dl{$ip} = $dl if $scan_dl{$ip} < $dl; } else { $scan_dl{$ip} = $dl; } $local_src{$ip} = '' if $is_local; $top_packet_counts{$ip} = $tot_pkts; $top_sig_counts{$ip} = $sig_matches; } } close F; return; } sub print_top_attackers() { my @lines = (); my $printed = 0; if ($config{'STATUS_IP_THRESHOLD'} > 0) { push @lines, "[+] Top $config{'STATUS_IP_THRESHOLD'} attackers:\n"; } else { push @lines, "[+] Top attackers:\n"; } my $ctr = 0; my %pre_sort_dl = (); my %ip_objs = (); for my $src (sort {$scan_dl{$b} cmp $scan_dl{$a}} keys %scan_dl) { if ($status_min_dl) { next unless $scan_dl{$src} >= $status_min_dl; } else { next unless $scan_dl{$src} >= $config{'MIN_DANGER_LEVEL'}; } $pre_sort_dl{$scan_dl{$src}}{$src} = ''; } my $ip6_short_len = 0; for my $dl (qw/5 4 3 2 1/) { next unless defined $pre_sort_dl{$dl}; for my $src (sort keys %{$pre_sort_dl{$dl}}) { next unless defined $top_packet_counts{$src} or defined $top_sig_counts{$src}; my $ip_obj = new NetAddr::IP($src) or die "[*] NetAddr::IP $src error"; $ip_objs{$src} = $ip_obj; if ($ip_obj->version() == 6) { if (length($ip_obj->short()) > $ip6_short_len) { $ip6_short_len = length($ip_obj->short()); } } } } for my $dl (qw/5 4 3 2 1/) { next unless defined $pre_sort_dl{$dl}; for my $src (sort keys %{$pre_sort_dl{$dl}}) { next unless defined $top_packet_counts{$src} or defined $top_sig_counts{$src}; my $str = sprintf " %-15s DL: %d", $src, $scan_dl{$src}; if ($ip_objs{$src}->version() == 6) { $str = sprintf " %s (%-${ip6_short_len}s) DL: %d", $src, $ip_objs{$src}->short(), $scan_dl{$src}; } if (defined $top_packet_counts{$src}) { $str .= ", Packets: $top_packet_counts{$src}"; } else { $str .= ', Packets: 0'; } if (defined $top_sig_counts{$src}) { $str .= ", Sig count: $top_sig_counts{$src}"; } else { $str .= ', Sig count: 0'; } if (defined $local_src{$src}) { $str .= ', (local IP)'; } $ctr++; if ($config{'STATUS_IP_THRESHOLD'} > 0) { last if $ctr >= $config{'STATUS_IP_THRESHOLD'}; } push @lines, $str, "\n"; $printed = 1; } } unless ($printed) { push @lines, " [NONE]\n"; } push @lines, "\n"; return \@lines; } sub remove_old_scans() { opendir D, $config{'PSAD_DIR'} or die "[*] Could not open dir: $config{'PSAD_DIR'}: $!"; my @files = readdir D; closedir D; my $curr_pwd = getcwd(); chdir $config{'PSAD_DIR'} or die $!; SRCIP: for my $src (@files) { next SRCIP unless ($src =~ /$ipv4_re/ and -d $src); rmtree $src or die "[*] Could not remove $config{'PSAD_DIR'}/$src: $!"; } chdir $curr_pwd or die $!; return; } sub usr1() { my $rv = 0; my $psad_pidfile = $pidfiles{'psad'}; if (-e $psad_pidfile) { my $pid = &is_running($psad_pidfile); if ($pid) { ### make sure psad is actually running if (kill 'USR1', $pid) { $rv = 1; print "[+] USR1 signal sent to pid: $pid\n"; for (my $try=0; $try<=20; $try++) { ### limit attempts to 20 sleep 1; print "[+] Checking for file: ", "$config{'PSAD_DIR'}/scan_hash.${pid}\n"; if (-e "$config{'PSAD_DIR'}/scan_hash.${pid}") { open U, "< $config{'PSAD_DIR'}/scan_hash.${pid}" or print "[*] Sent psad pid $pid a USR1 ", "signal, but could not open\n", "\"$config{'PSAD_DIR'}/scan_hash.${pid}\n\"" and return $rv; print while(); close U; print "[+] Results available in: ", "$config{'PSAD_DIR'}/scan_hash.${pid}\n"; last; } } } else { print "[*] Could not send psad the USR1 signal on ", "$config{'HOSTNAME'}\n"; } } else { print "[-] psad is not running on $config{'HOSTNAME'}\n"; } } return $rv; } sub usr1_handler() { $usr1_flag = 1; return; } sub hup() { my $rv = 0; for my $pidname (qw(psadwatchd psad kmsgsd)) { my $pidfile = $pidfiles{$pidname}; my $pid = &is_running($pidfile); if ($pid) { if (kill 'HUP', $pid) { print "[+] HUP signal sent to $pidname (pid: $pid)\n"; } else { print "[*] Could not send $pidname ", "(pid: $pid) a HUP signal.\n"; $rv = 1; } } else { my $print = 1; if ($pidname eq 'kmsgsd' and ($config{'SYSLOG_DAEMON'} =~ /ulog/i or $config{'ENABLE_SYSLOG_FILE'} eq 'Y')) { $print = 0; } print "[-] $pidname daemon not running.\n" if $print; $rv = 1; } } return $rv; } sub hup_handler() { $hup_flag = 1; return; } sub die_handler() { $die_msg = shift; return; } ### write all warnings to a logfile sub warn_handler() { $warn_msg = shift; return; } sub archive_data() { my $curr_pwd = getcwd(); chdir $config{'PSAD_DIR'} or die "[*] Could not chdir ", "$config{'PSAD_DIR'}: $!"; unless (-d $config{'SCAN_DATA_ARCHIVE_DIR'}) { mkdir $config{'SCAN_DATA_ARCHIVE_DIR'}, 0700 or die "[*] Could not create dir: ", "$config{'SCAN_DATA_ARCHIVE_DIR'}: $!"; } ### archive all of the old ip address directories since ### we are restarting psad (should add a way to import ### these directories back into memory) opendir D, $config{'PSAD_DIR'} or die "[*] Could not open dir: ", "$config{'PSAD_DIR'}: $!"; my @files = readdir D; closedir D; IPDIR: for my $file (@files) { if ($file =~ /$ipv4_re/ and -d $file) { ### check for the danger level associated with this dir if (-e "$file/danger_level") { open F, "< $file/danger_level" or next IPDIR; my $dl = ; close F; chomp $dl; if ($dl >= $config{'MIN_ARCHIVE_DANGER_LEVEL'}) { ### $file is an old scaning ip from ### a previous psad execution my $old_ipdir = $file; my $archive_ipdir = "$config{'SCAN_DATA_ARCHIVE_DIR'}/$old_ipdir"; if (-d $archive_ipdir) { rmtree $archive_ipdir; } move $old_ipdir, $archive_ipdir or die "[*] Could not ", "move $old_ipdir -> $archive_ipdir"; } } } } ### archive the fwdata file my $fwdata = $fw_data_file; my $fwarchive = "$config{'SCAN_DATA_ARCHIVE_DIR'}/fwdata_archive"; ### first see how big the archive file is and zero out if ### it is larger than about 10,000 lines if (-e $fwarchive && (-s $fwarchive) > 2367766) { ### about 10,000 lines &truncate_file($fwarchive) unless $config{'ENABLE_SYSLOG_FILE'} eq 'Y'; } unless (-e $fwdata) { chdir $curr_pwd or die $!; return; } open FW, "< $fwdata" or die "$fwdata exists but couldn't open it: $!"; my @fwlines = ; close FW; open AR, ">> $fwarchive" or die "Could not open $fwarchive: $!"; print AR $_ for @fwlines; close AR; chdir $curr_pwd or die $!; return; } sub handle_cmdline() { if ($analysis_emails and not $analyze_mode) { die "[*] Can only specify --email-analysis flag ", "when run in --Analyze mode."; } ### be absolutely sure to disable auto-response for various ### offline modes $config{'ENABLE_AUTO_IDS'} = 'N' if ($analyze_mode and not $analyze_mode_auto_block) or $syslog_server or $benchmark or $status_mode; ### The -I switch was given $config{'CHECK_INTERVAL'} = $chk_interval if $chk_interval; ### The --snort-rdir switch was given $config{'SNORT_RULES_DIR'} = $snort_rules_dir if $snort_rules_dir; ### The --signatures switch was given $config{'SIGS_FILE'} = $sigs_file if $sigs_file; ### The --passive-os-file switch was given $config{'POSF_FILE'} = $posf_file if $posf_file; ### The --auto-dl switch was given $config{'AUTO_DL_FILE'} = $auto_dl_file if $auto_dl_file; ### make sure to go into status display mode if any of the following ### args were given. $status_mode = 1 if ($status_ip and not $status_mode); $status_mode = 1 if ($status_min_dl and not $status_mode); $status_mode = 1 if ($status_summary and not $status_mode); ### make sure to go into firewall analysis mode if a ruleset ### file was specified on the command line. $fw_analyze = 1 if $fw_file; ### disable whois and DNS lookups if we are running in -A mode. $no_whois = 1 if $analyze_mode and not $analysis_whois; $no_rdns = 1 if $analyze_mode and not $enable_analysis_dns; return; } sub make_psad_dirs() { for my $dir (qw( /var/lib /var/run )) { next if -d $dir; mkdir $dir, 0755 or die "[*] Could not mkdir $dir: $!"; } for my $dir (qw( PSAD_DIR PSAD_RUN_DIR PSAD_FIFO_DIR PSAD_CONF_DIR CONF_ARCHIVE_DIR PSAD_ERR_DIR )) { next if -d $config{$dir}; mkdir $config{$dir}, 0700 or die "[*] Could not mkdir $config{$dir}: $!"; } return; } sub setup() { &make_psad_dirs(); unless (-e $config{'PSAD_FIFO_FILE'} and -p $config{'PSAD_FIFO_FILE'}) { system "$cmds{'mknod'} -m 600 $config{'PSAD_FIFO_FILE'} p"; } ### make sure the new whois path exists if (-x '/usr/bin/whois.psad' and not -x $cmds{'whois'} and '/usr/bin/whois.psad' ne $cmds{'whois'}) { move '/usr/bin/whois.psad', $cmds{'whois'} or die "[*] Could not ", "move /usr/bin/whois.psad -> $cmds{'whois'}"; } $no_email_alerts = 1 if $config{'ALERTING_METHODS'} =~ /no.?e?mail/i; $no_syslog_alerts = 1 if $config{'ALERTING_METHODS'} =~ /no.?syslog/i; ### initialize dshield alerting interval $dshield_alert_interval = 3600 * $config{'DSHIELD_ALERT_INTERVAL'}; ### scale back the alerting interval from 24 hours by just enough ### to make sure that an alert will be sent each day. $dshield_alert_interval -= 1 + $config{'CHECK_INTERVAL'} if $config{'DSHIELD_ALERT_INTERVAL'} == 24; unless ($hup_flag) { my $truncate_or_create = 0; my $restart_kmsgsd = 0; if ($config{'TRUNCATE_FWDATA'} eq 'Y' and $config{'ENABLE_SYSLOG_FILE'} ne 'Y') { $truncate_or_create = 1; $restart_kmsgsd = 1; } else { unless (-e $fw_data_file) { $truncate_or_create = 1; } } ### create the iptables data file if it doesn't exist ### (this is better than dying because it isn't there). &truncate_file($fw_data_file) if $truncate_or_create; ### if we truncate fwdata then we have to restart ### any running kmsgsd process &restart_kmsgsd() if $restart_kmsgsd; ### unlink socket file if it exists from a previous run (only ### if we have not received a HUP signal) unlink $config{'AUTO_IPT_SOCK'} if -e $config{'AUTO_IPT_SOCK'}; ### if we are not importing old scans, then remove old counter ### values if ($config{'IMPORT_OLD_SCANS'} eq 'N') { &truncate_file($config{'PACKET_COUNTER_FILE'}); &truncate_file($config{'IPT_PREFIX_COUNTER_FILE'}); &truncate_file($config{'DSHIELD_COUNTER_FILE'}); &truncate_file($config{'TOP_SIGS_FILE'}); &truncate_file($config{'TOP_SCANNED_PORTS_FILE'}); &truncate_file($config{'TOP_ATTACKERS_FILE'}); } if ($config{'ENABLE_SYSLOG_FILE'} ne 'Y') { chmod 0600, $fw_data_file; } chmod 0600, $config{'FW_ERROR_LOG'}; } ### we assume that ulogd is properly configured (FIXME?) return if $config{'SYSLOG_DAEMON'} =~ /ulog/i; return if $config{'ENABLE_SYSLOG_FILE'} eq 'Y'; die '[*] No system logger config file could be found.' unless (-e $config{'ETC_SYSLOG_CONF'} or -e $config{'ETC_RSYSLOG_CONF'} or -e $config{'ETC_SYSLOGNG_CONF'} or -e $config{'ETC_METALOG_CONF'}); ### attempt to correct syslog config file if it is not configured ### correctly. my $syslog_conf = ''; if ($config{'SYSLOG_DAEMON'} eq 'syslogd') { $syslog_conf = $config{'ETC_SYSLOG_CONF'}; } elsif ($config{'SYSLOG_DAEMON'} eq 'rsyslogd') { $syslog_conf = $config{'ETC_RSYSLOG_CONF'}; } if ($syslog_conf) { if (-e $syslog_conf) { unless (-e "$syslog_conf.orig") { copy $syslog_conf, "$syslog_conf.orig" or die "[*] Could not ", "copy $syslog_conf -> ", "$syslog_conf.orig"; } open RS, "< $syslog_conf" or die "[*] Unable to open $syslog_conf: $!"; my @lines = ; close RS; my $found = 0; for my $line (@lines) { if ($line =~ m/\|\s*$config{'PSAD_FIFO_FILE'}/) { $found = 1; last; } } unless ($found) { open SYSLOG, "> $syslog_conf" or die "[*] Unable to open $syslog_conf: $!"; ### this loop removes any old location for psadfifo for my $line (@lines) { unless ($line =~ /psadfifo/i) { print SYSLOG $line; } } ### reinstate kernel logging to our named pipe print SYSLOG "\n### Send kern.info messages to psadfifo for ", "analysis by kmsgsd\n"; print SYSLOG "kern.info\t\t|$config{'PSAD_FIFO_FILE'}\n"; close SYSLOG; &sys_log('reconfiguring syslogd to write ' . "kern.info messages to $config{'PSAD_FIFO_FILE'}"); system "$cmds{'killall'} -HUP syslogd"; } } else { &send_mail("$config{'MAIL_ERROR_PREFIX'} " . "$syslog_conf does not " . "exist, check SYSLOG_DAEMON setting on $config{'HOSTNAME'}", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } } elsif ($config{'SYSLOG_DAEMON'} eq 'syslog-ng') { if (-e $config{'ETC_SYSLOGNG_CONF'}) { unless (-e "$config{'ETC_SYSLOGNG_CONF'}.orig") { copy $config{'ETC_SYSLOGNG_CONF'}, "$config{'ETC_SYSLOGNG_CONF'}.orig" or die "[*] Could not ", "copy $config{'ETC_SYSLOGNG_CONF'} -> ", "$config{'ETC_SYSLOGNG_CONF'}.orig"; } open RS, "< $config{'ETC_SYSLOGNG_CONF'}" or die "[*] Unable to open $config{'ETC_SYSLOGNG_CONF'}: $!\n"; my @lines = ; close RS; my $found = 0; for my $line (@lines) { next if $line =~ /^\s*#/; if ($line =~ m/$config{'PSAD_FIFO_FILE'}/ or $line =~ /psadfifo/) { $found = 1; last; } } unless ($found) { open SYSLOGNG, ">> $config{'ETC_SYSLOGNG_CONF'}" or die "[*] Unable to open $config{'ETC_SYSLOGNG_CONF'}: $!"; print SYSLOGNG "\n", qq|source psadsrc { unix-stream("/dev/log"); |, qq|internal(); pipe("/proc/kmsg"); };\n|, qq|filter f_psad { facility(kern) and match("IN=") |, qq|and match("OUT="); };\n|, 'destination psadpipe { ', "pipe(\"$config{'PSAD_FIFO_FILE'}\"); };\n", 'log { source(psadsrc); filter(f_psad); ', "destination(psadpipe); };\n"; close SYSLOGNG; &sys_log('reconfiguring syslog-ng to write ' . "kern.info messages to $config{'PSAD_FIFO_FILE'}"); system "$cmds{'killall'} -HUP syslog-ng"; } } else { &send_mail("$config{'MAIL_ERROR_PREFIX'} " . "$config{'ETC_SYSLOGNG_CONF'} does not " . "exist, check SYSLOG_DAEMON setting on $config{'HOSTNAME'}", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } } elsif ($config{'SYSLOG_DAEMON'} eq 'metalog') { ### Metalog support added by Dennis Freise if (-e $config{'ETC_METALOG_CONF'}) { unless (-e "$config{'ETC_METALOG_CONF'}.orig") { copy $config{'ETC_METALOG_CONF'}, "$config{'ETC_METALOG_CONF'}.orig" or die "[*] Could not ", "copy $config{'ETC_METALOG_CONF'} -> ", "$config{'ETC_METALOG_CONF'}.orig"; } open RS, "< $config{'ETC_METALOG_CONF'}" or die "[*] Unable to open $config{'ETC_METALOG_CONF'}: $!\n"; my @lines = ; close RS; my $found = 0; for my $line (@lines) { if ($line =~ m/psadpipe\.sh/) { $found = 1; last; } } unless ($found) { open METALOG, "> $config{'ETC_METALOG_CONF'}" or die "[*] Unable to open $config{'ETC_METALOG_CONF'}: $!"; print METALOG "\n", "\nPSAD :\n", " facility = \"kern\"\n", ' command = ', "\"/usr/sbin/psadpipe.sh\"\n"; close METALOG; &sys_log('reconfiguring metalog to write ' . "kern-facility messages to /usr/sbin/psadpipe.sh"); open PIPESCRIPT, '> /usr/sbin/psadpipe.sh' or die "[*] Unable to open /usr/sbin/psadpipe.sh: $!"; print PIPESCRIPT "#!/bin/sh\n\n", "echo \"\$3\" >> $config{'PSAD_FIFO_FILE'}\n"; close PIPESCRIPT; chmod 0700, '/usr/sbin/psadpipe.sh'; &sys_log('generated /usr/sbin/psadpipe.sh ' . "which writes to $config{'PSAD_FIFO_FILE'}"); ### Metalog seems to simply die on SIGHUP and SIGALRM, and I ### found no signal or option to reload it's config... :-( die '[*] All files written. You have to manually restart metalog! ', 'When done, start psad again.'; # system "$cmds{'killall'} -HUP metalog"; } } else { &send_mail("$config{'MAIL_ERROR_PREFIX'} " . "$config{'ETC_METALOG_CONF'} does not " . "exist, check SYSLOG_DAEMON setting on $config{'HOSTNAME'}", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); } } return; } sub restart_kmsgsd() { return if $config{'ENABLE_SYSLOG_FILE'} eq 'Y'; return if $no_kmsgsd or $config{'SYSLOG_DAEMON'} =~ /ulog/i; return unless -e $pidfiles{'kmsgsd'}; my $pid = &is_running($pidfiles{'kmsgsd'}); return unless $pid; &sys_log('restarting kmsgsd since TRUNCATE_FWDATA is enabled'); kill 9, $pid unless kill 15, $pid; system $cmds{'kmsgsd'}; $kmsgsd_started = 1; return; } sub get_scale_factor() { my $num_packets = shift; return 0 unless $analyze_mode; my $val = 0; if ($num_packets < 100) { $val = $num_packets; } else { $val = int($num_packets/10); if ($val < 100) { $val -= $val % 10; } elsif ($val < 1000) { $val -= $val % 100; } elsif ($val < 10000) { $val -= $val % 1000; } elsif ($val < 100000) { $val -= $val % 10000; } elsif ($val < 1000000) { $val -= $val % 100000; } else { $val = 50000; } } $val++; return $val; } sub truncate_file() { my $file = shift; open F, "> $file" or die "[*] Could not open $file: $!"; close F; return; } sub disk_space_exceeded() { my $curr_pwd = getcwd(); my @df_data = @{&run_command($cmds{'df'}, $config{'PSAD_DIR'})}; my ($prcnt) = ($df_data[$#df_data] =~ /(\d+)%/); my $rv = 0; if ($config{'DISK_MAX_PERCENTAGE'} > 0 and $prcnt > $config{'DISK_MAX_PERCENTAGE'}) { ### need to remove data $rv = 1; $rm_data_ctr++; &sys_log("disk partition associated with " . "$config{'PSAD_DIR'} exceeded " . "$config{'DISK_MAX_PERCENTAGE'} prct utilization."); &send_mail("$config{'MAIL_ERROR_PREFIX'} Exceeded max disk " . "utilization for $config{'PSAD_DIR'} on $config{'HOSTNAME'}", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); &sys_log("removing data in $config{'PSAD_DIR'}"); if (-d $config{'SCAN_DATA_ARCHIVE_DIR'}) { ### remove the entire archive directory (we have run out of ### disk so keeping old scan directories around is the least ### of our worries). &sys_log("removing $config{'SCAN_DATA_ARCHIVE_DIR'} directory"); rmtree $config{'SCAN_DATA_ARCHIVE_DIR'}; mkdir $config{'SCAN_DATA_ARCHIVE_DIR'}, 0700; } opendir D, $config{'PSAD_DIR'} or die "[*] Could not open dir: $config{'PSAD_DIR'}: $!"; my @ipdirs = readdir D; closedir D; chdir $config{'PSAD_DIR'} or die $!; for my $ipdir (@ipdirs) { if ($ipdir =~ /$ipv4_re/ and -d $ipdir) { opendir IP, $ipdir or die $!; my @scanfiles = readdir IP; closedir IP; for my $file (@scanfiles) { if (-e "${ipdir}/$file" and $file =~ /_signatures/) { unlink "${ipdir}/$file"; } } } } if ($rm_data_ctr > $config{'DISK_MAX_RM_RETRIES'}) { &sys_log("could not sufficiently reduce disk " . "utilization in $config{'PSAD_DIR'} partition. " . "Stopping psad!"); &send_mail("$config{'MAIL_ERROR_PREFIX'} Could not " . "reduce disk utilization on " . $config{'HOSTNAME'}, '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); &send_mail("$config{'MAIL_FATAL_PREFIX'} Stopping psad " . "on $config{'HOSTNAME'}!", '', $config{'EMAIL_ADDRESSES'}, $cmds{'mail'}); for my $pidname (qw(psadwatchd kmsgsd)) { my $pidfile = $pidfiles{$pidname}; my $pid = &is_running($pidfile); if ($pid) { unless (kill 15, $pid) { ### attempt to stop with SIGTERM kill 9, $pid; } } } exit 1; } } else { ### the disk check interval was exceeded but the utilization is ok. $rm_data_ctr = 0; } chdir $curr_pwd or die $!; return $rv; } sub dump_conf() { my $fh = *STDOUT; $fh = *STDERR if $debug; ### uname output print $fh "[+] uname output:\n"; my @uname_out = @{&run_command($cmds{'uname'}, '-a')}; if (@uname_out) { for (@uname_out) { s/Linux\s+(\S+)\s/Linux (removed) /; print $fh $_; } } print $fh "\n"; ### perl version (we assume perl is in the path) print $fh "[+] perl info:\n"; my @perl_info = @{&run_command('perl', '-V')}; if (@perl_info) { print $fh $_ for @perl_info; } print $fh "\n"; print $fh "[+] syslog processes:\n"; my @ps_out = @{&run_command($cmds{'ps'}, 'auxww')}; if (@ps_out) { for (@ps_out) { print $fh $_ if m|syslog|i; } } print $fh "\n"; print $fh "[+] psad processes:\n"; my @ps_psad_out = @{&run_command($cmds{'ps'}, 'auxww')}; if (@ps_psad_out) { for (@ps_psad_out) { print $fh $_ if m|psad|i; } } print $fh "\n"; if (defined $config{'IFCFGTYPE'} and $config{'IFCFGTYPE'} =~ /iproute2/i) { print $fh "[+] ip addr output:\n"; my @ifconfig_out = @{&run_command($cmds{'ip'}, 'addr')}; if (@ifconfig_out) { for (@ifconfig_out) { s/$ipv4_re/x.x.x.x/g; s/inet6\s+\S+/inet6 (removed)/; print $fh $_; } } } else { print $fh "[+] ifconfig output:\n"; my @ifconfig_out = @{&run_command($cmds{'ifconfig'}, '-a')}; if (@ifconfig_out) { for (@ifconfig_out) { s/$ipv4_re/x.x.x.x/g; s/inet6\s+addr:\s+\S+/inet6 addr: (removed)/; print $fh $_; } } } print $fh "\n"; print $fh "\n[+] psad v$version\n\n"; my $install_log = '/var/log/psad/install.log'; if (-e $install_log) { print $fh "[+] $install_log exists.\n\n"; } else { print $fh "[+] $install_log does NOT exist.\n\n"; } print $fh "[+] Dumping psad config from: $config_file\n\n"; for my $var (sort keys %config) { my $str = $config{$var}; ### sanitize sensitive information $str = '(removed)' if $var eq 'DSHIELD_USER_EMAIL'; $str = '(removed)' if $var eq 'DSHIELD_USER_ID'; $str = '(removed)' if $var eq 'EMAIL_ADDRESSES'; $str = '(removed)' if $var eq 'HOME_NET'; $str = '(removed)' if $var eq 'HOSTNAME'; $str = '(removed)' if $var eq 'EXTERNAL_NET'; $str = '(removed)' if $var =~ m|SERVERS|; printf $fh "%-30s %s\n", " $var", $str; } print $fh "\n[+] Command paths:\n\n"; for my $var (sort keys %cmds) { printf $fh "%-30s %s\n", "[+] $var", $cmds{$var}; } return 0; } sub dump_ipt_policy() { my $rv = 0; my $fh = *STDOUT; $fh = *STDERR if $debug; if ($config{'ENABLE_IPV6_DETECTION'} eq 'Y') { print $fh "\n[+] ip6tables policy dump:\n"; if (defined $cmds{'ip6tables'} and -x $cmds{'ip6tables'}) { my @ipt_ver = @{&run_command($cmds{'ip6tables'}, '-V')}; if (@ipt_ver) { print $fh $_ for @ipt_ver; print "\n"; } my @lines = @{&run_command($cmds{'ip6tables'}, '-v -n -L')}; for my $line (@lines) { unless ($fw_include_ips) { ### always include ::/0 $line =~ s|\s\:\:/0\s|___PsAd0Net___|g; $line =~ s|\s([A-Fa-f0-9\:]{2,40}/\d{1,3})\s| ::/x |g; $line =~ s|___PsAd0Net___| ::/0 |g; } print $fh $line; } } else { print $fh "[*] Could not find ip6tables command.\n"; $rv = 1; } } print $fh "\n[+] iptables policy dump:\n"; if (defined $cmds{'iptables'} and -x $cmds{'iptables'}) { my @ipt_ver = @{&run_command($cmds{'iptables'}, '-V')}; if (@ipt_ver) { print $fh $_ for @ipt_ver; print "\n"; } my @lines = @{&run_command($cmds{'iptables'}, '-v -n -L')}; for my $line (@lines) { unless ($fw_include_ips) { ### always include 0.0.0.0/0 $line =~ s|0\.0\.0\.0/0|___PsAd0Net___|g; $line =~ s|0\.0\.0\.0|___PsAd0IP___|g; $line =~ s|($ipv4_re/\d+)|x.x.x.x/x|g; $line =~ s|($ipv4_re)|x.x.x.x|g; $line =~ s|___PsAd0Net___|0.0.0.0/0|g; $line =~ s|___PsAd0IP___|0.0.0.0|g; } print $fh $line; } } else { print $fh "[*] Could not find iptables command.\n"; $rv = 1; } return $rv; } sub sys_log_mline() { my $aref = shift; for (my $i=0; $i<5 && $i<=$#$aref; $i++) { &sys_log($aref->[$i]); } return; } ### write a message to syslog sub sys_log() { my $msg = shift; print STDERR "[+] syslog msg: $msg\n" if ($test_mode or $debug); return unless $imported_syslog_module; return if $no_syslog_alerts; ### this is an ugly hack to avoid the 'can't use string as subroutine' ### error because of 'use strict' if ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL7/i) { openlog($config{'SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL7()); } elsif ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL6/i) { openlog($config{'SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL6()); } elsif ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL5/i) { openlog($config{'SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL5()); } elsif ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL4/i) { openlog($config{'SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL4()); } elsif ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL3/i) { openlog($config{'SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL3()); } elsif ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL2/i) { openlog($config{'SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL2()); } elsif ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL1/i) { openlog($config{'SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL1()); } elsif ($config{'SYSLOG_FACILITY'} =~ /LOG_LOCAL0/i) { openlog($config{'SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL0()); } if ($config{'SYSLOG_PRIORITY'} =~ /LOG_INFO/i) { syslog(&LOG_INFO(), $msg); } elsif ($config{'SYSLOG_PRIORITY'} =~ /LOG_DEBUG/i) { syslog(&LOG_DEBUG(), $msg); } elsif ($config{'SYSLOG_PRIORITY'} =~ /LOG_NOTICE/i) { syslog(&LOG_NOTICE(), $msg); } elsif ($config{'SYSLOG_PRIORITY'} =~ /LOG_WARNING/i) { syslog(&LOG_WARNING(), $msg); } elsif ($config{'SYSLOG_PRIORITY'} =~ /LOG_ERR/i) { syslog(&LOG_ERR(), $msg); } elsif ($config{'SYSLOG_PRIORITY'} =~ /LOG_CRIT/i) { syslog(&LOG_CRIT(), $msg); } elsif ($config{'SYSLOG_PRIORITY'} =~ /LOG_ALERT/i) { syslog(&LOG_ALERT(), $msg); } elsif ($config{'SYSLOG_PRIORITY'} =~ /LOG_EMERG/i) { syslog(&LOG_EMERG(), $msg); } closelog(); return; } sub run_command() { my ($cmd_path, $args) = @_; my $cmd = $cmd_path; $cmd .= " $args" if $args; open CMD, "$cmd |" or die "[*] Could not ", "execute $cmd: $!"; my @lines = ; close CMD; return \@lines; } sub download_signatures() { &archive_conf($config{'SIGS_FILE'}); ### for wget &check_commands({'sendmail'=>'', 'mail'=>''}); my $curr_pwd = getcwd(); chdir '/tmp' or die $!; print "[+] Downloading latest signatures from:\n", " $config{'SIG_UPDATE_URL'}\n"; unlink 'signatures' if -e 'signatures'; ### download the file system "$cmds{'wget'} $config{'SIG_UPDATE_URL'}"; die "[*] Could not download signature file" unless -e 'signatures'; unlink $config{'SIGS_FILE'} if -e $config{'SIGS_FILE'}; move 'signatures', $config{'SIGS_FILE'}; print "[+] New signature file $config{'SIGS_FILE'} has been put in\n", " place. You can restart psad (or use 'psad -H') to import the\n", " new sigs.\n"; chdir $curr_pwd or die $!; return 0; } sub date_time() { my $date_str = shift; my $time = time(); my $date = $time; ### Feb 27 12:36:57 if ($date_str =~ /^\s*(\w+)\s+(\d+)\s+(\d{2}):(\d{2}):(\d{2})/) { my $m_tmp = $1; ### kludge for Decode_Month() call my $month = Decode_Month($m_tmp); my $day = sprintf("%.2d", $2); my $hour = $3; my $min = $4; my $sec = $5; $date = Date_to_Time(This_Year(), $month, $day, $hour, $min, $sec); if ($date > $time) { ### date is in the future because the iptables syslog message ### does not include the year - subtract one year $date -= 60*60*24*356; } } return $date; } sub archive_conf() { my $file = shift; my $curr_pwd = getcwd(); chdir $config{'CONF_ARCHIVE_DIR'} or die $!; my ($filename) = ($file =~ m|.*/(.*)|); my $base = "${filename}.old"; for (my $i = 5; $i > 1; $i--) { ### keep five copies of old config files my $j = $i - 1; unlink "${base}${i}.gz" if -e "${base}${i}.gz"; if (-e "${base}${j}.gz") { move "${base}${j}.gz", "${base}${i}.gz" or die "[*] Could not ", "move ${base}${j}.gz -> ${base}${i}.gz: $!"; } } print "[+] Archiving original $file -> ${base}1\n"; unlink "${base}1.gz" if -e "${base}1.gz"; ### move $file into the archive directory copy $file, "${base}1" or die "[*] Could not copy ", "$file -> ${base}1: $!"; system "$cmds{'gzip'} ${base}1"; chdir $curr_pwd or die $!; return; } sub import_override_configs() { my @override_configs = split /,/, $override_config_str; for my $file (@override_configs) { die "[*] Override config file $file does not exist" unless -e $file; &import_config($file); } return; } sub import_config() { my $conf_file = shift; open C, "< $conf_file" or die "[*] Could not open " . "config file $conf_file: $!"; my @lines = ; close C; for my $line (@lines) { chomp $line; next if ($line =~ /^\s*#/); if ($line =~ /^\s*(\S+)\s+(.*?)\;/) { my $varname = $1; my $val = $2; if ($val =~ m|/.+| && $varname =~ /^\s*(\S+)Cmd$/) { ### found a command $cmds{$1} = $val unless defined $cmds{$1}; } else { $config{$varname} = $val unless defined $config{$varname}; } } } 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 is_root() { ### exceptions for my $var ($download_sigs, $analyze_mode, $get_next_rule_id, $gnuplot_mode, $csv_mode, $dump_conf, $status_mode, $benchmark, ) { return if $var; } $< == 0 and $> == 0 or die '[*] psad: You must be root (or equivalent ', "UID 0 account) to execute psad! Exiting."; } ### check to make sure all required varables are defined in the config ### this subroutine is passed different variables by each script that ### correspond to only those variables needed be each script). sub defined_vars() { my $varnames_ar = shift; for my $var (@$varnames_ar) { unless (defined $config{$var}) { ### missing var die "[*] The config file \"$config_file\" does not " . "contain the\nvariable: \"$var\". Exiting!"; } } return; } ### check paths to commands and attempt to correct if any are wrong. sub check_commands() { my $exceptions_hr = shift; if ($no_whois) { $exceptions_hr->{'whois'} = ''; } else { unless (defined $cmds{'whois'} and -x $cmds{'whois'}) { ### try setting to normal /usr/bin/whois path, and let ### the code below work out the correct path if necessary $cmds{'whois'} = '/usr/bin/whois'; } } my @path = (qw( /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin )); CMD: for my $cmd (keys %cmds) { next CMD if defined $exceptions_hr->{$cmd}; ### both mail and sendmail are special cases, mail is not required ### if "nomail" is set in REPORT_METHOD, and sendmail is only ### required if DShield alerting is enabled and a DShield user ### email is set. if ($cmd eq 'mail') { next CMD if $config{'ALERTING_METHODS'} =~ /no.?e?mail/i; } elsif ($cmd eq 'sendmail') { next CMD unless ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y' and $config{'DSHIELD_ALERT_EMAIL'} ne 'NONE'); } if ($cmd eq 'ip6tables') { next CMD unless $config{'ENABLE_IPV6_DETECTION'} eq 'Y'; } 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 (defined $exceptions_hr->{$cmd}) { die "[*] Could not find $cmd, edit $config_file"; } } } unless (-x $cmds{$cmd}) { unless (defined $exceptions_hr->{$cmd}) { die "[*] $cmd is located at ", "$cmds{$cmd}, but is not executable\n", " by uid: $<"; } } } unless ($no_whois) { ### disable whois lookups if a suitable whois client could not ### be found. unless (defined $cmds{'whois'} and -x $cmds{'whois'}) { ### we couldn't find whois_psad warn '[-] Could not locate whois binary, ', "disabling whois lookups.\n"; $no_whois = 1; } } return; } sub is_running() { my $pidfile = shift or die '[*] Must supply a pid file.'; return 0 unless -e $pidfile; open PIDFILE, "< $pidfile" or die "[*] Could not open $pidfile: $!"; my $pid = ; close PIDFILE; chomp $pid; return $pid if (kill 0, $pid); ### pid is running return 0; } ### make sure pid is unique sub unique_pid() { my $pidfile = shift; die "[*] $0 process is already running! Exiting.\n" if &is_running($pidfile); return; } ### write the pid to the pid file sub write_pid() { my $pidfile = shift; open PIDFILE, "> $pidfile" or die "[*] Could not ", "open pidfile $pidfile: $!\n"; print PIDFILE $$ . "\n"; close PIDFILE; chmod 0600, $pidfile; return; } ### write command line to cmd file sub write_cmd_line() { my ($args_ar, $cmdline_file) = @_; open CMD, "> $cmdline_file"; print CMD "@$args_ar\n"; close CMD; chmod 0600, $cmdline_file; return; } ### send mail message to all addresses contained in the ### EMAIL_ADDRESSES variable within psad.conf ($addr_str). ### TODO: Would it be better to use Net::SMTP here? sub send_mail() { my ($subject, $body_file, $addr_str, $mailCmd) = @_; return if $no_email_alerts; my @mail_body_lines = (); if ($body_file) { open F, "< $body_file" or die "[*] Could not open mail file: ", "$body_file: $!"; @mail_body_lines = ; close F; } open MAIL, qq{| $mailCmd -s "$subject" $addr_str > /dev/null 2>&1} or die qq{[*] Could not send mail: $mailCmd -s "$subject" $addr_str: $!}; if ($body_file and @mail_body_lines) { print MAIL for @mail_body_lines; } close MAIL; return; } ### write a message to a file sub print_sys_msg() { my ($msg, $file) = @_; open F, ">> $file" or die "[*] Could not open $file: $!"; print F scalar localtime(), " psad v$version ", "pid: $$ $msg"; close F; return; } sub getopt_wrapper() { ### make Getopts case sensitive Getopt::Long::Configure('no_ignore_case'); die "[*] See 'psad -h' for usage information" unless (GetOptions( 'signatures=s' => \$sigs_file, # Path to psad signatures file. 'sig-update' => \$download_sigs, # Download the latest signatures from # http://www.cipherdyne.org/psad/signatures 'passive-os-sigs=s' => \$posf_file, # Path to passive os fingerprinting # signatures. 'snort-type=s' => \$srules_type, # Only process snort rules of # this type (e.g. "ddos" or # "backdoor"). 'snort-rdir=s' => \$snort_rules_dir, # Specify a directory for snort # rules. 'auto-dl=s' => \$auto_dl_file, # Path to psad auto IPs file for # auto-setting IP danger level. 'use-store-file=s' => \$store_file, # Path to parsed data written to by Storable 'Analyze-msgs' => \$analyze_mode, # Analysis mode for old iptables # messages in the psad fwdata file # (or messages file; see # --messages). 'analysis-write-data' => \$analyze_write_data, # Write data to filesystem from # -A mode (this can take a long # time). 'analyze-write-data' => \$analyze_write_data, 'analysis-auto-block' => \$analyze_mode_auto_block, # enable auto-blocking (if # so configured) in -A mode. 'analysis-fields=s' => \$analysis_fields, # Place a criteria on various fields # that are parsed from an iptables # logfile. 'analyze-fields=s' => \$analysis_fields, # Synonym. 'stdin' => \$pkts_from_stdin, 'whois-analysis' => \$analysis_whois, # Issue whois lookups in analysis # mode. 'dns-analysis' => \$enable_analysis_dns, # Issue DNS lookups in -A mode. 'email-analysis' => \$analysis_emails, # Send analysis mode emails. 'messages-file=s' => \$fw_data_file, # Specify the path to file containing # old iptables messages (fwdata by # default). 'get-next-rule-id' => \$get_next_rule_id, # Show the next available signature ID. ### gnuplot options 'gnuplot' => \$gnuplot_mode, # gnuplot mode. 'gnuplot-dat-file=s' => \$gnuplot_data_file, # gnuplot .dat file. 'gnuplot-plot-file=s' => \$gnuplot_plot_file, # gnuplot .gnu file. 'gnuplot-png-file=s' => \$gnuplot_png_file, # gnuplot .gnu file. 'gnuplot-interactive' => \$gnuplot_interactive, # launch gnuplot. 'gnuplot-title=s' => \$gnuplot_title, # Set gnuplot title. 'gnuplot-legend-titls=s' => \$gnuplot_legend_title, # Set gnuplot legend title. 'gnuplot-x-label=s' => \$gnuplot_x_label, 'gnuplot-xlabel=s' => \$gnuplot_x_label, 'gnuplot-x-range=s' => \$gnuplot_x_range, 'gnuplot-xrange=s' => \$gnuplot_x_range, 'gnuplot-y-label=s' => \$gnuplot_y_label, 'gnuplot-ylabel=s' => \$gnuplot_y_label, 'gnuplot-y-range=s' => \$gnuplot_y_range, 'gnuplot-yrange=s' => \$gnuplot_y_range, 'gnuplot-z-label=s' => \$gnuplot_z_label, 'gnuplot-zlabel=s' => \$gnuplot_z_label, 'gnuplot-z-range=s' => \$gnuplot_z_range, 'gnuplot-zrange=s' => \$gnuplot_z_range, 'gnuplot-graph-style=s' => \$gnuplot_graph_style, 'gnuplot-sort-style=s' => \$gnuplot_sort_style, 'gnuplot-3d' => \$gnuplot_3d, 'gnuplot-3D' => \$gnuplot_3d, 'gnuplot-view=s' => \$gnuplot_view, 'gnuplot-grayscale' => \$gnuplot_grayscale, 'gnuplot-file-prefix=s' => \$gnuplot_file_prefix, 'gnuplot-template=s' => \$gnuplot_template_file, 'CSV' => \$csv_mode, # CSV mode. 'CSV-fields=s' => \$csv_fields, # Specify list of CSV fields. 'CSV-unique-lines' => \$csv_print_uniq, # Only print unique lines in CSV output. 'CSV-max-lines=i' => \$csv_line_limit, # Limit the number of CSV output lines. 'CSV-start-line=i' => \$csv_start_line, # Starting line in CSV file. 'CSV-end-line=i' => \$csv_end_line, # Ending line in CSV file. 'CSV-regex=s' => \$csv_regex, # Require additional regex match. 'CSV-neg-regex=s' => \$csv_neg_regex, # Require additional negative regex # match. 'CSV-stdin' => \$csv_stdin, # Acquire iptables log data from # stdin. 'plot-separator' => \$plot_separator, # Specify separator character for plot # data (both gnuplot and CSV data). 'debug' => \$debug, # Run in debug mode. 'debug-sid=i' => \$debug_sid, # Debug a specific signature. 'Dump-conf' => \$dump_conf, # Dump config and exit. 'Interval=i' => \$chk_interval, # Set $chk_interval from the # command line. 'interface=s' => \$cmdl_interface, # Specify the IN interface manually # and ignore packets on all others. 'config=s' => \$config_file, # Specify path to configuration file. 'Override-config=s' => \$override_config_str, 'fw-analyze' => \$fw_analyze, # Analyze the firewall ruleset and # exit. 'fw-file=s' => \$fw_file, # Analyze ruleset contained within # $fw_file instead of a running # policy. 'fw-list-auto' => \$fw_list_auto, # Display iptables chains used by # psad in auto blocking code. 'List' => \$fw_list_auto, # Synonym for --fw-list-auto 'fw-block-ip=s' => \$fw_block_ip, # Add an IP/net to the psad auto- # blocking chains. Then psad can # manage timeouts, etc. 'fw-rm-block-ip=s' => \$fw_rm_block_ip, # Delete any block rule against an IP 'fw-del-chains' => \$fw_del_chains, # Delete psad chains in addition to # flushing them (requires --F as # well). 'X' => \$fw_del_chains, # Synonym for --fw-del-chains. 'fw-dump' => \$dump_ipt_policy, # Dump the iptables policy # (requires -D as well). 'fw-include-ips' => \$fw_include_ips, # Include all IPs/nets in iptables # dump (--fw-dump) output. 'log-server' => \$syslog_server, # We are running psad on a syslog # logging server. 'Kill' => \$kill, # Kill all running psad processes. # (psadwatchd, psad, kmsgsd) 'Restart' => \$restart, # Restart psad with all options of # the currently running psad # process. 'Flush' => \$flush_fw, # Flush any rules that psad previously # added via the auto blocking code. 'Status' => \$status_mode, # Display status of any currently # running psad processes. 'status-ip=s' => \$status_ip, # Display status for a specific IP. 'status-dl=i' => \$status_min_dl, # Display status for scans that have # reached at least this danger # level. 'status-summary' => \$status_summary, # Only display status summary info. 'restrict-ip=s' => \$restrict_ip, # Only process packets that have # either this IP as the src or dst. 'Benchmark' => \$benchmark, # Run in benchmark mode. 'packets=i' => \$num_packets, # Specify number of packets to use # in benchmark test or in -A mode. 'USR1' => \$usr1, # Send an existing psad process a # USR1 signal (useful for debugging). 'HUP' => \$hup, # Send psad processes a HUP signal # to re-import config. 'lib-dir=s' => \$lib_dir, # Specify path to psad lib directory. 'no-snort-sids' => \$no_snort_sids, # Disable search for snort SID's # in iptables messages. 'no-whois' => \$no_whois, # Do not issue whois lookups against 'no-passiveos' => \$no_posf, # Do not attempt to passively 'no-passive-os' => \$no_posf, # Do not attempt to passively # fingerprint the remote OS. 'no-signatures' => \$no_signatures, # Disable signature processing. 'no-icmp-types' => \$no_icmp_types, # Disable icmp type/code validation. 'no-icmp6-types' => \$no_icmp6_types, # Disable icmp6 type/code validation. 'no-auto-dl' => \$no_auto_dl, # Disable auto danger level # assignment. 'no-daemon' => \$no_daemon, # Do not run as a daemon. 'no-fwcheck' => \$no_fwcheck, # Do not check firewall rules. 'no-rdns' => \$no_rdns, # Do not issue dns lookups against # scanning IP address. 'no-netstat' => \$no_netstat, # Do not check to see if the # firewall is listening on # localport that has been scanned. 'no-ipt-errors' => \$no_ipt_errors, # Do not write malformed packet. # messages to error log. 'no-kmsgsd' => \$no_kmsgsd, # Do not start kmsgsd (used for # debugging). 'test-mode' => \$test_mode, # Enable test mode (used by the # test suite). 'verbose' => \$verbose, # Verbose output (for both alerts # and debug info). 'Version' => \$print_ver, # Print the psad version and exit. 'help' => \$help, # Display help. )); &usage(0) if $help; ### Print the version number and exit if -V given on the command line. if ($print_ver) { print "[+] psad v$version by Michael Rash \n"; exit 0; } return; } sub required_vars() { my @required_vars = (qw( EMAIL_ADDRESSES CHECK_INTERVAL FW_DATA_FILE FW_ERROR_LOG HOME_NET SNORT_SID_STR ENABLE_AUTO_IDS IGNORE_CONNTRACK_BUG_PKTS SCAN_TIMEOUT DANGER_LEVEL1 DANGER_LEVEL2 DANGER_LEVEL3 DANGER_LEVEL4 DANGER_LEVEL5 PORT_RANGE_SCAN_THRESHOLD ALERT_ALL EMAIL_LIMIT IPTABLES_BLOCK_METHOD TCPWRAPPERS_BLOCK_METHOD EMAIL_ALERT_DANGER_LEVEL PSAD_FIFO_FILE WHOIS_LOOKUP_THRESHOLD DNS_LOOKUP_THRESHOLD WHOIS_TIMEOUT SNORT_RULES_DIR HOSTNAME PACKET_COUNTER_FILE DSHIELD_COUNTER_FILE SCAN_DATA_ARCHIVE_DIR ENABLE_PERSISTENCE AUTO_BLOCK_IPT_FILE AUTO_BLOCK_TCPWR_FILE SIGS_FILE AUTO_DL_FILE AUTO_BLOCK_TIMEOUT EXTERNAL_SCRIPT ENABLE_EXT_SCRIPT_EXEC EXEC_EXT_SCRIPT_PER_ALERT ENABLE_DSHIELD_ALERTS SYSLOG_DAEMON DSHIELD_ALERT_INTERVAL DSHIELD_ALERT_EMAIL DSHIELD_USER_ID DSHIELD_USER_EMAIL DSHIELD_DL_THRESHOLD DISK_CHECK_INTERVAL DISK_MAX_PERCENTAGE DISK_MAX_RM_RETRIES ETC_HOSTS_DENY_FILE ETC_SYSLOG_CONF ETC_SYSLOGNG_CONF MIN_ARCHIVE_DANGER_LEVEL ANALYSIS_MODE_DIR IMPORT_OLD_SCANS ICMP_TYPES_FILE SHOW_ALL_SIGNATURES IPT_PREFIX_COUNTER_FILE IGNORE_PORTS ENABLE_SCAN_ARCHIVE EMAIL_LIMIT_STATUS_MSG P0F_FILE IGNORE_PROTOCOLS IPT_AUTO_CHAIN1 AUTO_IPT_SOCK IGNORE_INTERFACES ALERTING_METHODS ULOG_DATA_FILE MAIL_ALERT_PREFIX MAIL_STATUS_PREFIX MAIL_ERROR_PREFIX MAIL_FATAL_PREFIX ENABLE_AUTO_IDS_EMAILS FLUSH_IPT_AT_INIT ENABLE_MAC_ADDR_REPORTING TRUNCATE_FWDATA PSAD_DIR PSAD_RUN_DIR PSAD_FIFO_DIR ENABLE_FW_LOGGING_CHECK ENABLE_RENEW_BLOCK_EMAILS DSHIELD_EMAIL_FILE AUTO_BLOCK_REGEX ENABLE_AUTO_IDS_REGEX IPTABLES_PREREQ_CHECK SNORT_RULE_DL_FILE IPT_OUTPUT_FILE IPT_ERROR_FILE PROC_FORWARD_FILE PSAD_CONF_DIR EXTERNAL_NET HTTP_SERVERS SMTP_SERVERS DNS_SERVERS SQL_SERVERS TELNET_SERVERS AIM_SERVERS HTTP_PORTS SHELLCODE_PORTS ORACLE_PORTS ENABLE_INTF_LOCAL_NETS ENABLE_SNORT_SIG_STRICT IP_OPTS_FILE SIG_UPDATE_URL CONF_ARCHIVE_DIR TOP_SIGS_FILE TOP_PORTS_LOG_THRESHOLD TOP_SIGS_LOG_THRESHOLD TOP_SCANNED_PORTS_FILE STATUS_OUTPUT_FILE TOP_ATTACKERS_FILE STATUS_PORTS_THRESHOLD STATUS_SIGS_THRESHOLD STATUS_IP_THRESHOLD ANALYSIS_OUTPUT_FILE TOP_IP_LOG_THRESHOLD PSAD_LIBS_DIR PSAD_PID_FILE PSAD_CMDLINE_FILE PSAD_ERR_DIR MIN_DANGER_LEVEL IGNORE_KERNEL_TIMESTAMP ENABLE_SIG_MSG_SYSLOG SIG_MSG_SYSLOG_THRESHOLD SIG_SID_SYSLOG_THRESHOLD PSADWATCHD_CHECK_INTERVAL PSADWATCHD_MAX_RETRIES SYSLOG_IDENTITY SYSLOG_FACILITY SYSLOG_PRIORITY ENABLE_EMAIL_LIMIT_PER_DST ENABLE_SYSLOG_FILE IPT_SYSLOG_FILE IPT_WRITE_FWDATA ETC_RSYSLOG_CONF IFCFGTYPE ENABLE_WHOIS_FORCE_ASCII ENABLE_WHOIS_FORCE_SRC_IP ENABLE_IPV6_DETECTION PERSISTENCE_CTR_THRESHOLD MAX_SCAN_IP_PAIRS INSTALL_ROOT ICMP6_TYPES_FILE PROTOCOL_SCAN_THRESHOLD PROTOCOLS_FILE AUTO_BLOCK_DL1_TIMEOUT AUTO_BLOCK_DL2_TIMEOUT AUTO_BLOCK_DL3_TIMEOUT AUTO_BLOCK_DL4_TIMEOUT AUTO_BLOCK_DL5_TIMEOUT EMAIL_THROTTLE )); &defined_vars(\@required_vars); return; } sub usage() { my $exitcode = shift; print <<_HELP_; psad: the Port Scan Attack Detector [+] Version: $version By Michael Rash (mbr\@cipherdyne.org) URL: http://www.cipherdyne.org/psad/ Usage: psad [options] Options: -A, --Analyze-msgs - Analyze iptables logfile and exit. -e, --email-analysis - Send emails for scans detected in offline analysis mode. -m, --messages-file - Specify the path to the iptables logfile (for --Analyze-msgs mode). -i, --interface - Restrict detection to IN interface (for INPUT and FORWARD chains) or OUT interface (for OUPUT chain). --sig-update - Download the latest set of psad signatures from: http://www.cipherdyne.org/ -w, --whois-analysis - Enable whois lookups when running in offline --Analyze-msgs mode. --dns-analysis - Enable reverse DNS lookups in --Analyze-msgs mode. --fw-analyze - Analyze the local iptables ruleset and exit. --fw-list-auto - List the contents of any iptables chains (for auto-blocking rules). --List - Synonym for --fw-list-auto (emulates iptables command line). --fw-block-ip - Add an IP/network to the auto-blocking chains. --fw-rm-block-ip - Remove an IP/network from the auto- blocking chains. --fw-file - Analyze the iptables ruleset contained within instead of a running policy. --fw-del-chains - Delete iptables chains used by psad for auto-blocking rules. -X - Synonym for --fw-del-chains (emulates iptables command line). --fw-dump - Dump a sanitized version of the local iptables policy. --fw-include-ips - Include all IPs/nets in iptables dump (--fw-dump) output. --snort-rdir - Path to snort rules directory. --debug, - Run psad in debugging mode. --debug-sid - Debug a specific Snort rule. -D, --Dump-conf - Dump psad configuration on STDOUT and exit. -l, --log-server - psad is being run on a syslog logging server. -F, --Flush - Remove any auto-generated firewall block rules (emulates iptables command line). -K, --Kill - Kill all running psad processes. -R, --Restart - Restart all running psad processes. -S, --Status - Displays the status of any currently running psad processes. --restrict-ip - Only process packets that have this IP or network as the src or dst. --status-ip - View status for a specific IP. --status-dl
- Display status information for only those scans that have reach at least
(from 1 to 5). --status-summary - Only display summary status output in --Status and --Analyze modes. -B, --Benchmark - Run psad in benchmark mode. --packets - Specify number of packets to use in --Analyze (default is unlimited) or --Benchmark (default is 10,000) modes. -U, --USR1 - Send a running psad process a USR1 signal (generates a dump of psad data structures on STDOUT). -H, --HUP - Send all psad daemons a HUP signal to have them re-import configs. --get-next-rule-id - Display the next available rule ID and exit. --gnuplot - Parse iptables log data and produce a file suitable for plotting with Gnuplot. --gnuplot-graph-style - Set the Gnuplot graph style (e.g. "dots", "lines", "linespoints", etc.). --gnuplot-file-prefix - Use a prefix for the .gnu, .dat, and .png files that are generated in Gnuplot mode. --gnuplot-interactive - Do not add the terminal directive to the Gnuplot .gnu file, so when Gnuplot loads the file it will graph the data in an interactive window. --gnuplot-title - Set the Gnuplot graph title. --gnuplot-legend-title - Set the Gnuplot legend title. --gnuplot-x-label - Set the x-axis label. --gnuplot-x-range - Set the x-axis range. --gnuplot-y-label - Set the y-axis label. --gnuplot-y-range - Set the y-axis range. --gnuplot-z-label - Set the z-axis label. --gnuplot-z-range - Set the z-axis range. --gnuplot-sort-style - Set the psad sorting style to either "time" or "value" (defaults to "value"). --gnuplot-3D - Create three-dimensional Gnuplot graph. --gnuplot-view - Set the viewing angle. --gnuplot-grayscale - Only use grayscale colors. --gnuplot-template - Use a template file for all Gnuplot directives. --gnuplot-dat-file - Specify path to .dat output file. --gnuplot-plot-file - Specify path to .gnu output file. --gnuplot-png-file - Specify path to .png output file. --CSV - Parse iptables log messages and dump fields to stdout in csv format. --CSV-fields - Restrict --CSV output to a list of specfic fields. --CSV-unique-lines - Only print unique lines in CSV output. --CSV-max-lines - Specify the maximum number of CSV output lines to print. --CSV-start-line - Starting line within iptables log file. --CSV-end-line - Ending line within iptables log file. --CSV-regex - Require iptables log messages to match an additional regex in --CSV mode. --CSV-neg-regex - Require iptables log messages to not match an additional regex in --CSV mode. --plot-separator - Specify a separator string between plot fields (in --gnuplot or --CSV plot modes (the default is a comma for CSV formatted output). --signatures - Manually specify the path to the psad signatures file. --snort-type - Restrict psad to look for specific Snort sids such as those in ddos.rules or backdoor.rules. --passive-os-sigs - Manually specify the path to the passive os fingerprinting sigs. --auto-dl
- Import auto-danger level file for automatic IP danger level increases or decreses. --analysis-write-data - Write data to filesystem from -A mode (the disk IO involved in this step can take a long time). -c, --config - Use instead of the normal config file located at $config_file. -O, --Override-config - Allow config variables from the normal $config_file to be superseded with values from the specified file(s). -I, --Interval - Configure the check interval from the command line to override the 5 second default. -v, --verbose - Run in verbose mode. -V, --Version - Print the psad version and exit. --no-snort-sids - Disable examination for snort sids (such as those generated by fwsnort) in iptables log messages. --no-signatures - Disable psad signature processing (independent of snort sid matching). --no-icmp-types - Disable icmp type/code validation. --no-auto-dl - Disable auto danger level assignment. --no-daemon - Do not run as a daemon. --no-ipt-errors - Do not write errors to the error log. --no-whois - Disable whois lookups. --no-fwcheck - Disable firewall rules verification. --no-rdns - Disable name resolution against scanning IP addresses. --no-kmsgsd - Disable startup of kmsgsd (useful for debugging with an existing file of iptables log messages). --no-netstat - Disable local port lookups for scan signatures. -h --help - Display usage on STDOUT and exit. _HELP_ exit $exitcode; } psad-2.2.1/packaging/0000775000175000017500000000000012071203757012517 5ustar mbrmbrpsad-2.2.1/packaging/psad-nodeps.spec0000664000175000017500000002456612071203757015625 0ustar mbrmbr%define name psad %define version 2.2.1 %define release 1 %define psadlogdir /var/log/psad %define psadrundir /var/run/psad %define psadvarlibdir /var/lib/psad Summary: psad analyzes iptables log messages for suspect traffic Name: %name Version: %version Release: %release License: GPL Group: Applications/Internet Url: http://www.cipherdyne.org/psad/ Source: %name-%version.tar.gz BuildRoot: %_tmppath/%{name}-buildroot Requires: iptables #Prereq: rpm-helper %description Port Scan Attack Detector (psad) is a collection of three lightweight system daemons written in Perl and in C that are designed to work with Linux iptables firewalling code to detect port scans and other suspect traffic. It features a set of highly configurable danger thresholds (with sensible defaults provided), verbose alert messages that include the source, destination, scanned port range, begin and end times, tcp flags and corresponding nmap options, reverse DNS info, email and syslog alerting, automatic blocking of offending ip addresses via dynamic configuration of iptables rulesets, and passive operating system fingerprinting. In addition, psad incorporates many of the tcp, udp, and icmp signatures included in the snort intrusion detection system (http://www.snort.org) to detect highly suspect scans for various backdoor programs (e.g. EvilFTP, GirlFriend, SubSeven), DDoS tools (mstream, shaft), and advanced port scans (syn, fin, xmas) which are easily leveraged against a machine via nmap. psad can also alert on snort signatures that are logged via fwsnort (http://www.cipherdyne.org/fwsnort/), which makes use of the iptables string match module to detect application layer signatures. %prep [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %setup -q %build ### build psad binaries (kmsgsd and psadwatchd) make OPTS="$RPM_OPT_FLAGS" %install ### config directory #mkdir -p $RPM_BUILD_ROOT%psadetcdir ### log directory mkdir -p $RPM_BUILD_ROOT%psadlogdir ### dir for psadfifo mkdir -p $RPM_BUILD_ROOT%psadvarlibdir ### dir for pidfiles mkdir -p $RPM_BUILD_ROOT%psadrundir mkdir -p $RPM_BUILD_ROOT%_bindir mkdir -p $RPM_BUILD_ROOT%{_mandir}/man8 mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1 mkdir -p $RPM_BUILD_ROOT%_sbindir ### psad config mkdir -p $RPM_BUILD_ROOT%_sysconfdir/%name ### psad init script mkdir -p $RPM_BUILD_ROOT%_initrddir ### the 700 permissions mode is fixed in the ### %post phase install -m 700 psad $RPM_BUILD_ROOT%_sbindir/ install -m 700 kmsgsd $RPM_BUILD_ROOT%_sbindir/ install -m 700 psadwatchd $RPM_BUILD_ROOT%_sbindir/ install -m 500 fwcheck_psad.pl $RPM_BUILD_ROOT%_sbindir/fwcheck_psad install -m 755 nf2csv $RPM_BUILD_ROOT/usr/bin/nf2csv install -m 755 init-scripts/psad-init.redhat $RPM_BUILD_ROOT%_initrddir/psad install -m 644 psad.conf $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 signatures $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 icmp_types $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 icmp6_types $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 ip_options $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 auto_dl $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 snort_rule_dl $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 pf.os $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 posf $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 *.8 $RPM_BUILD_ROOT%{_mandir}/man8/ install -m 644 nf2csv.1 $RPM_BUILD_ROOT%{_mandir}/man1/ %clean [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %pre #if [ ! -p /var/lib/psad/psadfifo ]; #then [ -e /var/lib/psad/psadfifo ] && /bin/rm -f /var/lib/psad/psadfifo #fi #/bin/mknod -m 600 /var/lib/psad/psadfifo p #chown root.root /var/lib/psad/psadfifo #chmod 0600 /var/lib/psad/psadfifo %post ### put the current hostname into the psad C binaries ### (kmsgsd and psadwatchd). perl -p -i -e 'use Sys::Hostname; my $hostname = hostname(); s/HOSTNAME(\s+)_?CHANGE.?ME_?/HOSTNAME${1}$hostname/' %_sysconfdir/%name/psad.conf /bin/touch %psadlogdir/fwdata chown root.root %psadlogdir/fwdata chmod 0500 %_sbindir/psad chmod 0500 %_sbindir/kmsgsd chmod 0500 %_sbindir/psadwatchd chmod 0600 %psadlogdir/fwdata if [ ! -p %psadvarlibdir/psadfifo ]; then [ -e %psadvarlibdir/psadfifo ] && /bin/rm -f %psadvarlibdir/psadfifo /bin/mknod -m 600 %psadvarlibdir/psadfifo p fi chown root.root %psadvarlibdir/psadfifo chmod 0600 %psadvarlibdir/psadfifo ### make psad start at boot /sbin/chkconfig --add psad if grep -q "EMAIL.*root.*localhost" /etc/psad/psad.conf; then echo "[+] You can edit the EMAIL_ADDRESSES variable in /etc/psad/psad.conf" echo " to have email alerts sent to an address other than root\@localhost" fi if grep -q "HOME_NET.*CHANGEME" /etc/psad/psad.conf; then echo "[+] Be sure to edit the HOME_NET variable in /etc/psad/psad.conf" echo " to define the internal network(s) attached to your machine." fi %preun #%_preun_service psad %files %defattr(-,root,root) %dir %psadlogdir %dir %psadvarlibdir %dir %psadrundir %_initrddir/* %_sbindir/* %_bindir/* %{_mandir}/man8/* %{_mandir}/man1/* %dir %_sysconfdir/%name %config(noreplace) %_sysconfdir/%name/*.conf %config(noreplace) %_sysconfdir/%name/signatures %config(noreplace) %_sysconfdir/%name/auto_dl %config(noreplace) %_sysconfdir/%name/ip_options %config(noreplace) %_sysconfdir/%name/snort_rule_dl %config(noreplace) %_sysconfdir/%name/posf %config(noreplace) %_sysconfdir/%name/pf.os %config(noreplace) %_sysconfdir/%name/icmp_types %config(noreplace) %_sysconfdir/%name/icmp6_types %changelog * Wed Jan 02 2013 Michael Rash - psad-2.2.1 release * Wed Apr 18 2012 Michael Rash - Update to use the NetAddr::IP module for all IP/subnet calculations - psad-2.2 release * Wed Jul 14 2010 Michael Rash - psad-2.1.7 release * Fri Jul 09 2010 Michael Rash - psad-2.1.6 release * Fri Feb 20 2009 Michael Rash - psad-2.1.5 release * Sun Aug 21 2008 Michael Rash - This spec file omits installing any perl modules that psad depends upon. - psad-2.1.4 release * Sat Jun 07 2008 Michael Rash - psad-2.1.3 release * Thu Apr 03 2008 Michael Rash - psad-2.1.2 release * Fri Jan 25 2008 Michael Rash - psad-2.1.1 release * Fri Oct 19 2007 Michael Rash - psad-2.1 release * Mon Jul 27 2007 Michael Rash - psad-2.0.8 release * Mon May 28 2007 Michael Rash - psad-2.0.7 release * Fri Mar 24 2007 Michael Rash - psad-2.0.6 release * Thu Mar 01 2007 Michael Rash - psad-2.0.5 release - Removed all config files except for psad.conf since the psad daemons now all reference the same config file (psad.conf). * Sat Jan 27 2007 Michael Rash - psad-2.0.4 release * Sun Dec 31 2006 Michael Rash - psad-2.0.3 release - Removed Psad.pm perl module and kmsgsd.pl and psadwatchd.pl scripts. This is a major change that allows psad to be more flexible and completely derive its config from the psad.conf file and from the command line. In the previous scheme, psad imported its config with a function within Psad.pm, and this required that psad imported the Psad perl module before reading its config. A consequence is that the PSAD_LIBS_DIR var could not be specified usefully within the config file. * Sat Dec 23 2006 Michael Rash - psad-2.0.2 release * Mon Dec 12 2006 Michael Rash - psad-2.0.1 release * Sun Dec 10 2006 Michael Rash - psad-2.0 release. - Added ip_options file for the Snort ipopts rule keyword. - Added nf2csv so that normal users can get CSV output from iptables log messages. * Sun Oct 15 2006 Michael Rash - psad-1.4.8 release. * Sun Sep 10 2006 Michael Rash - psad-1.4.7 release. * Sat Sep 02 2006 Michael Rash - Added updates from Mate Wierdl to get psad RPM building on x86_64 platforms. * Tue Jun 13 2006 Michael Rash - Added installation of snort_rule_dl file. - psad-1.4.6 release. * Fri Jan 13 2006 Michael Rash - psad-1.4.5 release. * Sun Nov 27 2005 Michael Rash - psad-1.4.4 release. * Tue Nov 22 2005 Michael Rash - Removed smtpdaemon dependency since psad can be run without sending email alerts by configuring /etc/psad/alert.conf appropriately. * Tue Jul 12 2005 Michael Rash - Updated to only update syslog.conf if it actually exists. psad is now comptable with other syslog daemons, and also with ulogd. * Thu Mar 10 2005 Michael Rash - Updated to new IPTables-Parse and IPTables-ChainMgr modules. - psad-1.4.1 release. * Fri Nov 26 2004 Michael Rash - Added ps.os file. - psad-1.4.0 release. * Sun Oct 17 2004 Michael Rash - psad-1.3.4 release. * Sat Sep 25 2004 Michael Rash - Added Bit::Vector back since not having it causes dependency problems with Date::Calc even though psad does not require any Date::Calc functions that require Bit::Vector functions. * Mon Sep 06 2004 Michael Rash - Updated to psad-1.3.3. - Fixed path to psad-init.redhat (Mate Wierdl) * Thu Jun 24 2004 Michael Rash - Updated to psad-1.3.2 (added fwcheck_psad.pl and fw_search.conf installation). * Mon Oct 14 2003 Michael Rash - Removed ipchains text from description. - Added test and config warning message for HOME_NET variable. - Updated to version 1.3 * Mon Oct 14 2003 Michael Rash - Removed diskmond since psad handles disk space thresholds directly. * Sat Oct 11 2003 Michael Rash - Updated spec file to build properly on both Red Hat 7.2 and Red Hat 9 systems. * Tue Sep 23 2003 Lenny Cartier 1.2.3-1mdk - mandrakized specfile * Fri Sep 12 2003 Michael Rash - Added interface tracking for scans. - Bugfix for not opening /etc/hosts.deny the right way in tcpwr_block(). - Bugfix for psadfifo path in syslog-ng config. - Better format for summary stats section in email alerts. - Bugfix for INIT_DIR path on non-RedHat systems. - Bugfix for gzip path. - Make Psad.pm installed last of all perl modules installed by psad. - Added additional call to incr_syscall_ctr() in psadwatchd.c * Mon Jul 28 2003 Michael Rash - Initial version. psad-2.2.1/packaging/psad.SlackBuild0000775000175000017500000000455112071203757015415 0ustar mbrmbr#!/bin/bash ################################################################################ # psad.SlackBuild -- v1.3 -- pyllyukko <~> maimed <~> org -- 26.1.2007 -> # # # # 5.4.2008: added -b to cd_rpmbuilder # # added 'set -e' # # # # NOTES: # # - you might wan't to change %_sysconfdir in # # /usr/lib/rpm/`uname -m`-linux/macros # # - you can build a different version of the program by executing: # # 'PSAD_VERSION=x.y.z bash ./psad.SlackBuild' # # # # TODO: # # - add slack-desc # ################################################################################ set -e declare -r RPM_BUILDER="http://www.cipherdyne.org/scripts/cd_rpmbuilder.tar.gz" declare -r RPM_ROOT_DIR="/usr/src/rpm" declare -r ARCH="i386" declare -ri BUILD=1 PSAD_VERSION=${PSAD_VERSION:-`wget --no-verbose --output-document=- http://www.cipherdyne.org/psad/psad-latest`} || { echo "error at line $[${LINENO}-1]!" 1>&2 exit 1 } ################################################################################ wget --no-verbose --output-document=- "${RPM_BUILDER}" | tar xz --to-stdout | perl -- - -p psad -r "${RPM_ROOT_DIR}" -b "${PSAD_VERSION}" [ $[ ${PIPESTATUS[0]} | ${PIPESTATUS[1]} | ${PIPESTATUS[2]} ] -ne 0 ] && { echo "error at line $[${LINENO}-2]!" 1>&2 exit 1 } [ ! -f "${RPM_ROOT_DIR}/RPMS/${ARCH}/psad-${PSAD_VERSION}-${BUILD}.${ARCH}.rpm" ] && { echo "error: file \`psad-${PSAD_VERSION}-${BUILD}.${ARCH}.rpm' doesn't exist!" 1>&2 exit 1 } pushd "${RPM_ROOT_DIR}/RPMS/${ARCH}" || exit 1 rpm2tgz "psad-${PSAD_VERSION}-${BUILD}.${ARCH}.rpm" || exit 1 mv -v "psad-${PSAD_VERSION}-${BUILD}.${ARCH}.tgz" "psad-${PSAD_VERSION}-${ARCH}-${BUILD}.tgz" || exit 1 ls -l "${RPM_ROOT_DIR}/RPMS/${ARCH}/psad-${PSAD_VERSION}-${ARCH}-${BUILD}.tgz" exit ${?} psad-2.2.1/packaging/psad-require-makemaker.spec0000664000175000017500000006025612071203757017740 0ustar mbrmbr%define name psad %define version 2.2.1 %define release 1 %define psadlibdir %_libdir/%name %define psadlogdir /var/log/psad %define psadrundir /var/run/psad %define psadvarlibdir /var/lib/psad ### get the first @INC directory that includes the string "linux". ### This may be 'i386-linux', or 'i686-linux-thread-multi', etc. %define psadmoddir `perl -e '$path=q|i386-linux|; for (@INC) { if($_ =~ m|.*/(.*linux.*)|) {$path = $1; last; }} print $path'` Summary: psad analyzes iptables log messages for suspect traffic Name: %name Version: %version Release: %release License: GPL Group: Applications/Internet URL: http://www.cipherdyne.org/psad/ Source: %name-%version.tar.gz BuildRoot: %_tmppath/%{name}-buildroot Requires: iptables BuildRequires: perl-ExtUtils-MakeMaker #Prereq: rpm-helper %description Port Scan Attack Detector (psad) is a collection of three lightweight system daemons written in Perl and in C that are designed to work with Linux iptables firewalling code to detect port scans and other suspect traffic. It features a set of highly configurable danger thresholds (with sensible defaults provided), verbose alert messages that include the source, destination, scanned port range, begin and end times, tcp flags and corresponding nmap options, reverse DNS info, email and syslog alerting, automatic blocking of offending ip addresses via dynamic configuration of iptables rulesets, and passive operating system fingerprinting. In addition, psad incorporates many of the tcp, udp, and icmp signatures included in the snort intrusion detection system (http://www.snort.org) to detect highly suspect scans for various backdoor programs (e.g. EvilFTP, GirlFriend, SubSeven), DDoS tools (mstream, shaft), and advanced port scans (syn, fin, xmas) which are easily leveraged against a machine via nmap. psad can also alert on snort signatures that are logged via fwsnort (http://www.cipherdyne.org/fwsnort/), which makes use of the iptables string match module to detect application layer signatures. %prep [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %setup -q cd deps cd IPTables-Parse && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd IPTables-ChainMgr && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd Bit-Vector && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd NetAddr-IP && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd Unix-Syslog && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd Date-Calc && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd ../.. %build ### build psad binaries (kmsgsd and psadwatchd) make OPTS="$RPM_OPT_FLAGS" ### build the whois client make OPTS="$RPM_OPT_FLAGS" -C deps/whois cd deps ### build perl modules used by psad make OPTS="$RPM_OPT_FLAGS" -C IPTables-Parse make OPTS="$RPM_OPT_FLAGS" -C IPTables-ChainMgr make OPTS="$RPM_OPT_FLAGS" -C Bit-Vector make OPTS="$RPM_OPT_FLAGS" -C NetAddr-IP make OPTS="$RPM_OPT_FLAGS" -C Unix-Syslog make OPTS="$RPM_OPT_FLAGS" -C Date-Calc cd .. %install ### config directory ### log directory mkdir -p $RPM_BUILD_ROOT%psadlogdir ### dir for psadfifo mkdir -p $RPM_BUILD_ROOT%psadvarlibdir ### dir for pidfiles mkdir -p $RPM_BUILD_ROOT%psadrundir ### psad module dirs mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Bit/Vector mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Bit mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Unix/Syslog mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Date/Calc mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/Util mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/IPTables/Parse mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/IPTables/ChainMgr mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Unix mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Carp mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar mkdir -p $RPM_BUILD_ROOT%psadlibdir/IPTables ### whois_psad binary mkdir -p $RPM_BUILD_ROOT%_bindir mkdir -p $RPM_BUILD_ROOT%{_mandir}/man8 mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1 mkdir -p $RPM_BUILD_ROOT%_sbindir ### psad config mkdir -p $RPM_BUILD_ROOT%_sysconfdir/%name ### psad init script mkdir -p $RPM_BUILD_ROOT%_initrddir ### the 700 permissions mode is fixed in the install -m 700 psad $RPM_BUILD_ROOT%_sbindir/ install -m 700 kmsgsd $RPM_BUILD_ROOT%_sbindir/ install -m 700 psadwatchd $RPM_BUILD_ROOT%_sbindir/ install -m 500 fwcheck_psad.pl $RPM_BUILD_ROOT%_sbindir/fwcheck_psad install -m 755 deps/whois/whois $RPM_BUILD_ROOT/usr/bin/whois_psad install -m 755 nf2csv $RPM_BUILD_ROOT/usr/bin/nf2csv install -m 755 init-scripts/psad-init.redhat $RPM_BUILD_ROOT%_initrddir/psad install -m 644 psad.conf $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 signatures $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 icmp_types $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 icmp6_types $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 ip_options $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 auto_dl $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 snort_rule_dl $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 pf.os $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 posf $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 *.8 $RPM_BUILD_ROOT%{_mandir}/man8/ install -m 644 nf2csv.1 $RPM_BUILD_ROOT%{_mandir}/man1/ ### install perl modules used by psad cd deps install -m 555 Bit-Vector/blib/arch/auto/Bit/Vector/Vector.so $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Bit/Vector/Vector.so install -m 444 Bit-Vector/blib/arch/auto/Bit/Vector/Vector.bs $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Bit/Vector/Vector.bs install -m 444 Bit-Vector/blib/lib/Bit/Vector.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Bit/Vector.pm install -m 555 Unix-Syslog/blib/arch/auto/Unix/Syslog/Syslog.so $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Unix/Syslog/Syslog.so install -m 444 Unix-Syslog/blib/arch/auto/Unix/Syslog/Syslog.bs $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Unix/Syslog/Syslog.bs [ -e Unix-Syslog/blib/lib/auto/Unix/Syslog/autosplit.ix ] && install -m 444 Unix-Syslog/blib/lib/auto/Unix/Syslog/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Unix/Syslog/autosplit.ix install -m 444 Unix-Syslog/blib/lib/Unix/Syslog.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Unix/Syslog.pm install -m 555 Date-Calc/blib/arch/auto/Date/Calc/Calc.so $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Date/Calc/Calc.so install -m 444 Date-Calc/blib/arch/auto/Date/Calc/Calc.bs $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Date/Calc/Calc.bs install -m 444 Date-Calc/blib/lib/Carp/Clan.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Carp/Clan.pod install -m 444 Date-Calc/blib/lib/Carp/Clan.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Carp/Clan.pm install -m 444 Date-Calc/blib/lib/Date/Calc.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc.pm install -m 444 Date-Calc/blib/lib/Date/Calc.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc.pod install -m 444 Date-Calc/blib/lib/Date/Calendar.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar.pm install -m 444 Date-Calc/blib/lib/Date/Calendar.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar.pod install -m 444 Date-Calc/blib/lib/Date/Calc/Object.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc/Object.pm install -m 444 Date-Calc/blib/lib/Date/Calc/Object.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc/Object.pod install -m 444 Date-Calc/blib/lib/Date/Calendar/Year.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar/Year.pm install -m 444 Date-Calc/blib/lib/Date/Calendar/Profiles.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar/Profiles.pod install -m 444 Date-Calc/blib/lib/Date/Calendar/Profiles.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar/Profiles.pm install -m 444 Date-Calc/blib/lib/Date/Calendar/Year.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar/Year.pod install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/hostenum.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/hostenum.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/compactref.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/compactref.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/nprefix.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/nprefix.al [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/.packlist ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/.packlist $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/.packlist install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/re.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/re.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/prefix.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/prefix.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/do_prefix.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/do_prefix.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/wildcard.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/wildcard.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/_compact_v6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/_compact_v6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/autosplit.ix [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/Util.so ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/Util.so $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/Util/Util.so [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/Util.bs ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/Util.bs $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/Util/Util.bs [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/autosplit.ix ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/Util/autosplit.ix install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/shiftleft.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/shiftleft.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/ipv4to6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/ipv4to6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/maskanyto6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/maskanyto6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/comp128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/comp128.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_deadlen.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_deadlen.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/sub128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/sub128.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/notcontiguous.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/notcontiguous.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bcdn2bin.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bcdn2bin.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/add128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/add128.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/ipv6to4.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/ipv6to4.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_bcdcheck.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_bcdcheck.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/mask4to6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/mask4to6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_128x2.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_128x2.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/ipanyto6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/ipanyto6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/hasbits.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/hasbits.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bcdn2txt.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bcdn2txt.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/slowadd128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/slowadd128.al [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/autosplit.ix ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/autosplit.ix install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/simple_pack.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/simple_pack.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bcd2bin.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bcd2bin.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bin2bcdn.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bin2bcdn.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_bin2bcdn.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_bin2bcdn.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bin2bcd.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bin2bcd.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_sa128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_sa128.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_bcd2bin.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_bcd2bin.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/addconst.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/addconst.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_128x10.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_128x10.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/mod_version.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/mod_version.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/_splitref.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/_splitref.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/_compV6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/_compV6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/inet_any2n.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/inet_any2n.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/_inet_ntop.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/_inet_ntop.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/inet_n2ad.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/inet_n2ad.al [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/autosplit.ix ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/autosplit.ix install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/_packzeros.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/_packzeros.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/inet_n2dx.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/inet_n2dx.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/ipv6_aton.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/ipv6_aton.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/ipv6_ntoa.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/ipv6_ntoa.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/inet_ntoa.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/inet_ntoa.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/_inet_pton.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/_inet_pton.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/coalesce.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/coalesce.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/re6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/re6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/short.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/short.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/_splitplan.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/_splitplan.al install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/InetBase.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/InetBase.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/UtilPP.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/UtilPP.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/Util.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/Util.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/Lite.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/Lite.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/Util_IS.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/Util_IS.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP.pm install -m 444 IPTables-Parse/blib/lib/IPTables/Parse.pm $RPM_BUILD_ROOT%psadlibdir/IPTables/Parse.pm install -m 444 IPTables-ChainMgr/blib/lib/IPTables/ChainMgr.pm $RPM_BUILD_ROOT%psadlibdir/IPTables/ChainMgr.pm cd .. ### install snort rules files cp -r deps/snort_rules $RPM_BUILD_ROOT%_sysconfdir/%name %clean [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %pre #if [ ! -p /var/lib/psad/psadfifo ]; #then [ -e /var/lib/psad/psadfifo ] && /bin/rm -f /var/lib/psad/psadfifo #fi #/bin/mknod -m 600 /var/lib/psad/psadfifo p #chown root.root /var/lib/psad/psadfifo #chmod 0600 /var/lib/psad/psadfifo %post ### put the current hostname into the psad C binaries ### (kmsgsd and psadwatchd). perl -p -i -e 'use Sys::Hostname; my $hostname = hostname(); s/HOSTNAME(\s+)_?CHANGE.?ME_?/HOSTNAME${1}$hostname/' %_sysconfdir/%name/psad.conf /bin/touch %psadlogdir/fwdata chown root.root %psadlogdir/fwdata chmod 0500 %_sbindir/psad chmod 0500 %_sbindir/kmsgsd chmod 0500 %_sbindir/psadwatchd chmod 0600 %psadlogdir/fwdata if [ ! -p %psadvarlibdir/psadfifo ]; then [ -e %psadvarlibdir/psadfifo ] && /bin/rm -f %psadvarlibdir/psadfifo /bin/mknod -m 600 %psadvarlibdir/psadfifo p fi chown root.root %psadvarlibdir/psadfifo chmod 0600 %psadvarlibdir/psadfifo ### make psad start at boot /sbin/chkconfig --add psad if grep -q "EMAIL.*root.*localhost" /etc/psad/psad.conf; then echo "[+] You can edit the EMAIL_ADDRESSES variable in /etc/psad/psad.conf" echo " to have email alerts sent to an address other than root\@localhost" fi if grep -q "HOME_NET.*CHANGEME" /etc/psad/psad.conf; then echo "[+] Be sure to edit the HOME_NET variable in /etc/psad/psad.conf" echo " to define the internal network(s) attached to your machine." fi %preun %files %defattr(-,root,root) %dir %psadlogdir %dir %psadvarlibdir %dir %psadrundir %_initrddir/* %_sbindir/* %_bindir/* %{_mandir}/man8/* %{_mandir}/man1/* %dir %_sysconfdir/%name %config(noreplace) %_sysconfdir/%name/*.conf %config(noreplace) %_sysconfdir/%name/signatures %config(noreplace) %_sysconfdir/%name/auto_dl %config(noreplace) %_sysconfdir/%name/ip_options %config(noreplace) %_sysconfdir/%name/snort_rule_dl %config(noreplace) %_sysconfdir/%name/posf %config(noreplace) %_sysconfdir/%name/pf.os %config(noreplace) %_sysconfdir/%name/icmp_types %config(noreplace) %_sysconfdir/%name/icmp6_types %dir %_sysconfdir/%name/snort_rules %config(noreplace) %_sysconfdir/%name/snort_rules/* %_libdir/%name %changelog * Wed Jan 02 2013 Michael Rash - psad-2.2.1 release * Wed Apr 18 2012 Michael Rash - Update to use the NetAddr::IP module for all IP/subnet calculations - psad-2.2 release * Wed Jul 14 2010 Michael Rash - psad-2.1.7 release * Fri Jul 09 2010 Michael Rash - psad-2.1.6 release * Fri Feb 20 2009 Michael Rash - psad-2.1.5 release * Thu Aug 21 2008 Michael Rash - Updated to use the deps/ directory for all perl module sources. - psad-2.1.4 release * Sat Jun 07 2008 Michael Rash - psad-2.1.3 release * Thu Apr 03 2008 Michael Rash - psad-2.1.2 release * Fri Jan 25 2008 Michael Rash - psad-2.1.1 release * Fri Oct 19 2007 Michael Rash - psad-2.1 release * Mon Jul 27 2007 Michael Rash - psad-2.0.8 release * Mon May 28 2007 Michael Rash - psad-2.0.7 release * Fri Mar 24 2007 Michael Rash - psad-2.0.6 release * Thu Mar 01 2007 Michael Rash - psad-2.0.5 release - Removed all config files except for psad.conf since the psad daemons now all reference the same config file (psad.conf). * Sat Jan 27 2007 Michael Rash - psad-2.0.4 release * Sun Dec 31 2006 Michael Rash - psad-2.0.3 release - Removed Psad.pm perl module and kmsgsd.pl and psadwatchd.pl scripts. This is a major change that allows psad to be more flexible and completely derive its config from the psad.conf file and from the command line. In the previous scheme, psad imported its config with a function within Psad.pm, and this required that psad imported the Psad perl module before reading its config. A consequence is that the PSAD_LIBS_DIR var could not be specified usefully within the config file. * Sat Dec 23 2006 Michael Rash - psad-2.0.2 release * Mon Dec 12 2006 Michael Rash - psad-2.0.1 release * Sun Dec 10 2006 Michael Rash - psad-2.0 release. - Added ip_options file for the Snort ipopts rule keyword. - Added nf2csv so that normal users can get CSV output from iptables log messages. * Sun Oct 15 2006 Michael Rash - psad-1.4.8 release. * Sun Sep 10 2006 Michael Rash - psad-1.4.7 release. * Sat Sep 02 2006 Michael Rash - Added updates from Mate Wierdl to get psad RPM building on x86_64 platforms. * Tue Jun 13 2006 Michael Rash - Added installation of snort_rule_dl file. - psad-1.4.6 release. * Fri Jan 13 2006 Michael Rash - psad-1.4.5 release. * Sun Nov 27 2005 Michael Rash - psad-1.4.4 release. * Tue Nov 22 2005 Michael Rash - Removed smtpdaemon dependency since psad can be run without sending email alerts by configuring /etc/psad/alert.conf appropriately. * Tue Jul 12 2005 Michael Rash - Updated to only update syslog.conf if it actually exists. psad is now comptable with other syslog daemons, and also with ulogd. * Thu Mar 10 2005 Michael Rash - Updated to new IPTables-Parse and IPTables-ChainMgr modules. - psad-1.4.1 release. * Fri Nov 26 2004 Michael Rash - Added ps.os file. - psad-1.4.0 release. * Sun Oct 17 2004 Michael Rash - psad-1.3.4 release. * Sat Sep 25 2004 Michael Rash - Added Bit::Vector back since not having it causes dependency problems with Date::Calc even though psad does not require any Date::Calc functions that require Bit::Vector functions. * Mon Sep 06 2004 Michael Rash - Updated to psad-1.3.3. - Fixed path to psad-init.redhat (Mate Wierdl) * Thu Jun 24 2004 Michael Rash - Updated to psad-1.3.2 (added fwcheck_psad.pl and fw_search.conf installation). * Mon Oct 14 2003 Michael Rash - Removed ipchains text from description. - Added test and config warning message for HOME_NET variable. - Updated to version 1.3 * Mon Oct 14 2003 Michael Rash - Removed diskmond since psad handles disk space thresholds directly. * Sat Oct 11 2003 Michael Rash - Updated spec file to build properly on both Red Hat 7.2 and Red Hat 9 systems. * Tue Sep 23 2003 Lenny Cartier 1.2.3-1mdk - mandrakized specfile * Fri Sep 12 2003 Michael Rash - Added interface tracking for scans. - Bugfix for not opening /etc/hosts.deny the right way in tcpwr_block(). - Bugfix for psadfifo path in syslog-ng config. - Better format for summary stats section in email alerts. - Bugfix for INIT_DIR path on non-RedHat systems. - Bugfix for gzip path. - Make Psad.pm installed last of all perl modules installed by psad. - Added additional call to incr_syscall_ctr() in psadwatchd.c * Mon Jul 28 2003 Michael Rash - Initial version. psad-2.2.1/packaging/psad.spec0000664000175000017500000006020712071203757014327 0ustar mbrmbr%define name psad %define version 2.2.1 %define release 1 %define psadlibdir %_libdir/%name %define psadlogdir /var/log/psad %define psadrundir /var/run/psad %define psadvarlibdir /var/lib/psad ### get the first @INC directory that includes the string "linux". ### This may be 'i386-linux', or 'i686-linux-thread-multi', etc. %define psadmoddir `perl -e '$path=q|i386-linux|; for (@INC) { if($_ =~ m|.*/(.*linux.*)|) {$path = $1; last; }} print $path'` Summary: psad analyzes iptables log messages for suspect traffic Name: %name Version: %version Release: %release License: GPL Group: Applications/Internet URL: http://www.cipherdyne.org/psad/ Source: %name-%version.tar.gz BuildRoot: %_tmppath/%{name}-buildroot Requires: iptables #Prereq: rpm-helper %description Port Scan Attack Detector (psad) is a collection of three lightweight system daemons written in Perl and in C that are designed to work with Linux iptables firewalling code to detect port scans and other suspect traffic. It features a set of highly configurable danger thresholds (with sensible defaults provided), verbose alert messages that include the source, destination, scanned port range, begin and end times, tcp flags and corresponding nmap options, reverse DNS info, email and syslog alerting, automatic blocking of offending ip addresses via dynamic configuration of iptables rulesets, and passive operating system fingerprinting. In addition, psad incorporates many of the tcp, udp, and icmp signatures included in the snort intrusion detection system (http://www.snort.org) to detect highly suspect scans for various backdoor programs (e.g. EvilFTP, GirlFriend, SubSeven), DDoS tools (mstream, shaft), and advanced port scans (syn, fin, xmas) which are easily leveraged against a machine via nmap. psad can also alert on snort signatures that are logged via fwsnort (http://www.cipherdyne.org/fwsnort/), which makes use of the iptables string match module to detect application layer signatures. %prep [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %setup -q cd deps cd IPTables-Parse && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd IPTables-ChainMgr && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd Bit-Vector && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd NetAddr-IP && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd Unix-Syslog && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd .. cd Date-Calc && perl Makefile.PL PREFIX=%psadlibdir LIB=%psadlibdir cd ../.. %build ### build psad binaries (kmsgsd and psadwatchd) make OPTS="$RPM_OPT_FLAGS" ### build the whois client make OPTS="$RPM_OPT_FLAGS" -C deps/whois cd deps ### build perl modules used by psad make OPTS="$RPM_OPT_FLAGS" -C IPTables-Parse make OPTS="$RPM_OPT_FLAGS" -C IPTables-ChainMgr make OPTS="$RPM_OPT_FLAGS" -C Bit-Vector make OPTS="$RPM_OPT_FLAGS" -C NetAddr-IP make OPTS="$RPM_OPT_FLAGS" -C Unix-Syslog make OPTS="$RPM_OPT_FLAGS" -C Date-Calc cd .. %install ### config directory ### log directory mkdir -p $RPM_BUILD_ROOT%psadlogdir ### dir for psadfifo mkdir -p $RPM_BUILD_ROOT%psadvarlibdir ### dir for pidfiles mkdir -p $RPM_BUILD_ROOT%psadrundir ### psad module dirs mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Bit/Vector mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Bit mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Unix/Syslog mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Date/Calc mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/Util mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/IPTables/Parse mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/IPTables/ChainMgr mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Unix mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Carp mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc mkdir -p $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar mkdir -p $RPM_BUILD_ROOT%psadlibdir/IPTables ### whois_psad binary mkdir -p $RPM_BUILD_ROOT%_bindir mkdir -p $RPM_BUILD_ROOT%{_mandir}/man8 mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1 mkdir -p $RPM_BUILD_ROOT%_sbindir ### psad config mkdir -p $RPM_BUILD_ROOT%_sysconfdir/%name ### psad init script mkdir -p $RPM_BUILD_ROOT%_initrddir ### the 700 permissions mode is fixed in the install -m 700 psad $RPM_BUILD_ROOT%_sbindir/ install -m 700 kmsgsd $RPM_BUILD_ROOT%_sbindir/ install -m 700 psadwatchd $RPM_BUILD_ROOT%_sbindir/ install -m 500 fwcheck_psad.pl $RPM_BUILD_ROOT%_sbindir/fwcheck_psad install -m 755 deps/whois/whois $RPM_BUILD_ROOT/usr/bin/whois_psad install -m 755 nf2csv $RPM_BUILD_ROOT/usr/bin/nf2csv install -m 755 init-scripts/psad-init.redhat $RPM_BUILD_ROOT%_initrddir/psad install -m 644 psad.conf $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 signatures $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 icmp_types $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 icmp6_types $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 ip_options $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 auto_dl $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 snort_rule_dl $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 pf.os $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 posf $RPM_BUILD_ROOT%_sysconfdir/%name/ install -m 644 *.8 $RPM_BUILD_ROOT%{_mandir}/man8/ install -m 644 nf2csv.1 $RPM_BUILD_ROOT%{_mandir}/man1/ ### install perl modules used by psad cd deps install -m 555 Bit-Vector/blib/arch/auto/Bit/Vector/Vector.so $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Bit/Vector/Vector.so install -m 444 Bit-Vector/blib/arch/auto/Bit/Vector/Vector.bs $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Bit/Vector/Vector.bs install -m 444 Bit-Vector/blib/lib/Bit/Vector.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Bit/Vector.pm install -m 555 Unix-Syslog/blib/arch/auto/Unix/Syslog/Syslog.so $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Unix/Syslog/Syslog.so install -m 444 Unix-Syslog/blib/arch/auto/Unix/Syslog/Syslog.bs $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Unix/Syslog/Syslog.bs [ -e Unix-Syslog/blib/lib/auto/Unix/Syslog/autosplit.ix ] && install -m 444 Unix-Syslog/blib/lib/auto/Unix/Syslog/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Unix/Syslog/autosplit.ix install -m 444 Unix-Syslog/blib/lib/Unix/Syslog.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Unix/Syslog.pm install -m 555 Date-Calc/blib/arch/auto/Date/Calc/Calc.so $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Date/Calc/Calc.so install -m 444 Date-Calc/blib/arch/auto/Date/Calc/Calc.bs $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/Date/Calc/Calc.bs install -m 444 Date-Calc/blib/lib/Carp/Clan.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Carp/Clan.pod install -m 444 Date-Calc/blib/lib/Carp/Clan.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Carp/Clan.pm install -m 444 Date-Calc/blib/lib/Date/Calc.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc.pm install -m 444 Date-Calc/blib/lib/Date/Calc.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc.pod install -m 444 Date-Calc/blib/lib/Date/Calendar.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar.pm install -m 444 Date-Calc/blib/lib/Date/Calendar.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar.pod install -m 444 Date-Calc/blib/lib/Date/Calc/Object.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc/Object.pm install -m 444 Date-Calc/blib/lib/Date/Calc/Object.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calc/Object.pod install -m 444 Date-Calc/blib/lib/Date/Calendar/Year.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar/Year.pm install -m 444 Date-Calc/blib/lib/Date/Calendar/Profiles.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar/Profiles.pod install -m 444 Date-Calc/blib/lib/Date/Calendar/Profiles.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar/Profiles.pm install -m 444 Date-Calc/blib/lib/Date/Calendar/Year.pod $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/Date/Calendar/Year.pod install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/hostenum.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/hostenum.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/compactref.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/compactref.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/nprefix.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/nprefix.al [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/.packlist ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/.packlist $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/.packlist install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/re.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/re.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/prefix.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/prefix.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/do_prefix.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/do_prefix.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/wildcard.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/wildcard.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/_compact_v6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/_compact_v6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/autosplit.ix [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/Util.so ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/Util.so $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/Util/Util.so [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/Util.bs ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/Util.bs $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/Util/Util.bs [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/autosplit.ix ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/Util/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/Util/autosplit.ix install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/shiftleft.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/shiftleft.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/ipv4to6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/ipv4to6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/maskanyto6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/maskanyto6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/comp128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/comp128.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_deadlen.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_deadlen.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/sub128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/sub128.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/notcontiguous.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/notcontiguous.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bcdn2bin.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bcdn2bin.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/add128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/add128.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/ipv6to4.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/ipv6to4.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_bcdcheck.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_bcdcheck.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/mask4to6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/mask4to6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_128x2.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_128x2.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/ipanyto6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/ipanyto6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/hasbits.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/hasbits.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bcdn2txt.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bcdn2txt.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/slowadd128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/slowadd128.al [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/autosplit.ix ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/autosplit.ix install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/simple_pack.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/simple_pack.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bcd2bin.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bcd2bin.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bin2bcdn.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bin2bcdn.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_bin2bcdn.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_bin2bcdn.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/bin2bcd.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/bin2bcd.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_sa128.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_sa128.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_bcd2bin.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_bcd2bin.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/addconst.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/addconst.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/UtilPP/_128x10.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/UtilPP/_128x10.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/mod_version.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/mod_version.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/_splitref.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/_splitref.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/_compV6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/_compV6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/inet_any2n.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/inet_any2n.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/_inet_ntop.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/_inet_ntop.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/inet_n2ad.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/inet_n2ad.al [ -e NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/autosplit.ix ] && install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/autosplit.ix $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/autosplit.ix install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/_packzeros.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/_packzeros.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/inet_n2dx.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/inet_n2dx.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/ipv6_aton.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/ipv6_aton.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/ipv6_ntoa.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/ipv6_ntoa.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/inet_ntoa.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/inet_ntoa.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/InetBase/_inet_pton.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/InetBase/_inet_pton.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/coalesce.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/coalesce.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/re6.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/re6.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/short.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/short.al install -m 444 NetAddr-IP/blib/lib/auto/NetAddr/IP/_splitplan.al $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/auto/NetAddr/IP/_splitplan.al install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/InetBase.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/InetBase.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/UtilPP.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/UtilPP.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/Util.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/Util.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/Lite.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/Lite.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP/Util_IS.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP/Util_IS.pm install -m 444 NetAddr-IP/blib/lib/NetAddr/IP.pm $RPM_BUILD_ROOT%psadlibdir/%psadmoddir/NetAddr/IP.pm install -m 444 IPTables-Parse/blib/lib/IPTables/Parse.pm $RPM_BUILD_ROOT%psadlibdir/IPTables/Parse.pm install -m 444 IPTables-ChainMgr/blib/lib/IPTables/ChainMgr.pm $RPM_BUILD_ROOT%psadlibdir/IPTables/ChainMgr.pm cd .. ### install snort rules files cp -r deps/snort_rules $RPM_BUILD_ROOT%_sysconfdir/%name %clean [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %pre #if [ ! -p /var/lib/psad/psadfifo ]; #then [ -e /var/lib/psad/psadfifo ] && /bin/rm -f /var/lib/psad/psadfifo #fi #/bin/mknod -m 600 /var/lib/psad/psadfifo p #chown root.root /var/lib/psad/psadfifo #chmod 0600 /var/lib/psad/psadfifo %post ### put the current hostname into the psad C binaries ### (kmsgsd and psadwatchd). perl -p -i -e 'use Sys::Hostname; my $hostname = hostname(); s/HOSTNAME(\s+)_?CHANGE.?ME_?/HOSTNAME${1}$hostname/' %_sysconfdir/%name/psad.conf /bin/touch %psadlogdir/fwdata chown root.root %psadlogdir/fwdata chmod 0500 %_sbindir/psad chmod 0500 %_sbindir/kmsgsd chmod 0500 %_sbindir/psadwatchd chmod 0600 %psadlogdir/fwdata if [ ! -p %psadvarlibdir/psadfifo ]; then [ -e %psadvarlibdir/psadfifo ] && /bin/rm -f %psadvarlibdir/psadfifo /bin/mknod -m 600 %psadvarlibdir/psadfifo p fi chown root.root %psadvarlibdir/psadfifo chmod 0600 %psadvarlibdir/psadfifo ### make psad start at boot /sbin/chkconfig --add psad if grep -q "EMAIL.*root.*localhost" /etc/psad/psad.conf; then echo "[+] You can edit the EMAIL_ADDRESSES variable in /etc/psad/psad.conf" echo " to have email alerts sent to an address other than root\@localhost" fi if grep -q "HOME_NET.*CHANGEME" /etc/psad/psad.conf; then echo "[+] Be sure to edit the HOME_NET variable in /etc/psad/psad.conf" echo " to define the internal network(s) attached to your machine." fi %preun %files %defattr(-,root,root) %dir %psadlogdir %dir %psadvarlibdir %dir %psadrundir %_initrddir/* %_sbindir/* %_bindir/* %{_mandir}/man8/* %{_mandir}/man1/* %dir %_sysconfdir/%name %config(noreplace) %_sysconfdir/%name/*.conf %config(noreplace) %_sysconfdir/%name/signatures %config(noreplace) %_sysconfdir/%name/auto_dl %config(noreplace) %_sysconfdir/%name/ip_options %config(noreplace) %_sysconfdir/%name/snort_rule_dl %config(noreplace) %_sysconfdir/%name/posf %config(noreplace) %_sysconfdir/%name/pf.os %config(noreplace) %_sysconfdir/%name/icmp_types %config(noreplace) %_sysconfdir/%name/icmp6_types %dir %_sysconfdir/%name/snort_rules %config(noreplace) %_sysconfdir/%name/snort_rules/* %_libdir/%name %changelog * Wed Jan 02 2013 Michael Rash - psad-2.2.1 release * Wed Apr 18 2012 Michael Rash - Update to use the NetAddr::IP module for all IP/subnet calculations - psad-2.2 release * Wed Jul 14 2010 Michael Rash - psad-2.1.7 release * Fri Jul 09 2010 Michael Rash - psad-2.1.6 release * Fri Feb 20 2009 Michael Rash - psad-2.1.5 release * Thu Aug 21 2008 Michael Rash - Updated to use the deps/ directory for all perl module sources. - psad-2.1.4 release * Sat Jun 07 2008 Michael Rash - psad-2.1.3 release * Thu Apr 03 2008 Michael Rash - psad-2.1.2 release * Fri Jan 25 2008 Michael Rash - psad-2.1.1 release * Fri Oct 19 2007 Michael Rash - psad-2.1 release * Mon Jul 27 2007 Michael Rash - psad-2.0.8 release * Mon May 28 2007 Michael Rash - psad-2.0.7 release * Fri Mar 24 2007 Michael Rash - psad-2.0.6 release * Thu Mar 01 2007 Michael Rash - psad-2.0.5 release - Removed all config files except for psad.conf since the psad daemons now all reference the same config file (psad.conf). * Sat Jan 27 2007 Michael Rash - psad-2.0.4 release * Sun Dec 31 2006 Michael Rash - psad-2.0.3 release - Removed Psad.pm perl module and kmsgsd.pl and psadwatchd.pl scripts. This is a major change that allows psad to be more flexible and completely derive its config from the psad.conf file and from the command line. In the previous scheme, psad imported its config with a function within Psad.pm, and this required that psad imported the Psad perl module before reading its config. A consequence is that the PSAD_LIBS_DIR var could not be specified usefully within the config file. * Sat Dec 23 2006 Michael Rash - psad-2.0.2 release * Mon Dec 12 2006 Michael Rash - psad-2.0.1 release * Sun Dec 10 2006 Michael Rash - psad-2.0 release. - Added ip_options file for the Snort ipopts rule keyword. - Added nf2csv so that normal users can get CSV output from iptables log messages. * Sun Oct 15 2006 Michael Rash - psad-1.4.8 release. * Sun Sep 10 2006 Michael Rash - psad-1.4.7 release. * Sat Sep 02 2006 Michael Rash - Added updates from Mate Wierdl to get psad RPM building on x86_64 platforms. * Tue Jun 13 2006 Michael Rash - Added installation of snort_rule_dl file. - psad-1.4.6 release. * Fri Jan 13 2006 Michael Rash - psad-1.4.5 release. * Sun Nov 27 2005 Michael Rash - psad-1.4.4 release. * Tue Nov 22 2005 Michael Rash - Removed smtpdaemon dependency since psad can be run without sending email alerts by configuring /etc/psad/alert.conf appropriately. * Tue Jul 12 2005 Michael Rash - Updated to only update syslog.conf if it actually exists. psad is now comptable with other syslog daemons, and also with ulogd. * Thu Mar 10 2005 Michael Rash - Updated to new IPTables-Parse and IPTables-ChainMgr modules. - psad-1.4.1 release. * Fri Nov 26 2004 Michael Rash - Added ps.os file. - psad-1.4.0 release. * Sun Oct 17 2004 Michael Rash - psad-1.3.4 release. * Sat Sep 25 2004 Michael Rash - Added Bit::Vector back since not having it causes dependency problems with Date::Calc even though psad does not require any Date::Calc functions that require Bit::Vector functions. * Mon Sep 06 2004 Michael Rash - Updated to psad-1.3.3. - Fixed path to psad-init.redhat (Mate Wierdl) * Thu Jun 24 2004 Michael Rash - Updated to psad-1.3.2 (added fwcheck_psad.pl and fw_search.conf installation). * Mon Oct 14 2003 Michael Rash - Removed ipchains text from description. - Added test and config warning message for HOME_NET variable. - Updated to version 1.3 * Mon Oct 14 2003 Michael Rash - Removed diskmond since psad handles disk space thresholds directly. * Sat Oct 11 2003 Michael Rash - Updated spec file to build properly on both Red Hat 7.2 and Red Hat 9 systems. * Tue Sep 23 2003 Lenny Cartier 1.2.3-1mdk - mandrakized specfile * Fri Sep 12 2003 Michael Rash - Added interface tracking for scans. - Bugfix for not opening /etc/hosts.deny the right way in tcpwr_block(). - Bugfix for psadfifo path in syslog-ng config. - Better format for summary stats section in email alerts. - Bugfix for INIT_DIR path on non-RedHat systems. - Bugfix for gzip path. - Make Psad.pm installed last of all perl modules installed by psad. - Added additional call to incr_syscall_ctr() in psadwatchd.c * Mon Jul 28 2003 Michael Rash - Initial version. psad-2.2.1/packaging/psad.ebuild0000664000175000017500000000767412071203757014652 0ustar mbrmbr# Copyright 1999-2006 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: /var/cvsroot/gentoo-x86/net-firewall/psad/psad-1.4.2.ebuild,v 1.3 2005/11/28 12:11:33 mcummings Exp $ inherit eutils perl-app IUSE="" DESCRIPTION="Port Scanning Attack Detection daemon" SRC_URI="http://www.cipherdyne.org/psad/download/${P}.tar.bz2" HOMEPAGE="http://www.cipherdyne.org/psad" SLOT="0" LICENSE="GPL-2" KEYWORDS="~x86 ~amd64 ~ppc ~alpha ~sparc" DEPEND="${DEPEND} dev-lang/perl" RDEPEND="virtual/logger dev-perl/Unix-Syslog dev-perl/Date-Calc virtual/mailx net-firewall/iptables" src_compile() { cd ${S}/Net-IPv4Addr SRC_PREP="no" perl-module_src_compile emake test cd ${S}/IPTables-Parse SRC_PREP="no" perl-module_src_compile emake test cd ${S}/IPTables-ChainMgr SRC_PREP="no" perl-module_src_compile emake test cd ${S} # We'll use the C binaries emake || die "Make failed: daemons" } src_install() { local myhostname= local mydomain= doman *.8 keepdir /var/lib/psad /var/log/psad /var/run/psad /var/lock/subsys/${PN} dodir /etc/psad cd ${S}/Net-IPv4Addr emake install DESTDIR=${D} || die "Install failed: Net-IPv4Addr.pm" cd ${S}/IPTables-ChainMgr emake install DESTDIR=${D} || die "Install failed: IPTables-Mgr.pm" cd ${S}/IPTables-Parse emake install DESTDIR=${D} || die "Install failed: IPTables-Parse.pm" cd ${S} insinto /usr dosbin kmsgsd psad psadwatchd newsbin fwcheck_psad.pl fwcheck_psad dobin pscan cd ${S} fix_psad_conf insinto /etc/psad doins *.conf doins psad_* doins auto_dl icmp_types posf signatures pf.os cd ${S}/init-scripts exeinto /etc/init.d newexe psad-init.gentoo psad cd ${S}/snort_rules dodir /etc/psad/snort_rules insinto /etc/psad/snort_rules doins *.rules cd ${S} dodoc BENCHMARK CREDITS Change* FW_EXAMPLE_RULES README LICENSE SCAN_LOG } pkg_postinst() { if [ ! -p ${ROOT}/var/lib/psad/psadfifo ] then ebegin "Creating syslog FIFO for PSAD" mknod -m 600 ${ROOT}/var/lib/psad/psadfifo p eend $? fi echo einfo "Please be sure to edit /etc/psad/psad.conf to reflect your system's" einfo "configuration or it may not work correctly or start up. Specifically, check" einfo "the validity of the HOSTNAME setting and replace the EMAIL_ADDRESSES and" einfo "HOME_NET settings at the least." echo if has_version ">=app-admin/syslog-ng-0.0.0" then ewarn "You appear to have installed syslog-ng. If you are using syslog-ng as your" ewarn "default system logger, please change the SYSLOG_DAEMON entry in" ewarn "/etc/psad/psad.conf to the following (per examples in psad.conf):" ewarn " SYSLOG_DAEMON syslog-ng;" echo fi if has_version ">=app-admin/sysklogd-0.0.0" then einfo "You have sysklogd installed. If this is your default system logger, no" einfo "special configuration is needed. If it is not, please set SYSLOG_DAEMON" einfo "in /etc/psad/psad.conf accordingly." echo fi if has_version ">=app-admin/metalog-0.0" then ewarn "You appear to have installed metalog. If you are using metalog as your" ewarn "default system logger, please change the SYSLOG_DAEMON entry in" ewarn "/etc/psad/psad.conf to the following (per examples in psad.conf):" ewarn " SYSLOG_DAEMON metalog" fi } fix_psad_conf() { cp psad.conf psad.conf.orig # Ditch the _CHANGEME_ for hostname, substituting in our real hostname [ -e /etc/hostname ] && myhostname="$(< /etc/hostname)" [ "${myhostname}" == "" ] && myhostname="$HOSTNAME" mydomain=".$(grep ^domain /etc/resolv.conf | cut -d" " -f2)" sed -i "s:HOSTNAME\(.\+\)\_CHANGEME\_;:HOSTNAME\1${myhostname}${mydomain};:" psad.conf || die "fix_psad_conf failed" # Fix up paths sed -i "s:/sbin/syslogd:/usr/sbin/syslogd:g" psad.conf || die "fix_psad_conf failed" sed -i "s:/sbin/syslog-ng:/usr/sbin/syslog-ng:g" psad.conf || die "fix_psad_conf failed" sed -i "s:/bin/uname:/usr/bin/uname:g" psad.conf || die "fix_psad_conf failed" sed -i "s:/bin/mknod:/usr/bin/mknod:g" psad.conf || die "fix_psad_conf failed" } psad-2.2.1/packaging/cd_rpmbuilder0000775000175000017500000001577212071203757015274 0ustar mbrmbr#!/usr/bin/perl -w # ############################################################################# # # File: cd_rpmbuilder "CipherDyne Rpm Builder" # # Purpose: Provides a consistent way to build RPMs of CipherDyne open source # projects (psad, fwsnort, fwsknop, and gpgdir). # # Author: Michael Rash # # Copyright (C) 2006-2008 Michael Rash (mbr@cipherdyne.org) # # License (GNU Public License - 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ############################################################################# # use File::Find; use File::Copy; use Getopt::Long 'GetOptions'; use strict; #============================ config ============================= my $rpm_root_dir = '/usr/src/redhat'; my $build_url_base = 'http://www.cipherdyne.org'; ### commands my $rpmbuildCmd = '/usr/bin/rpmbuild'; my $wgetCmd = '/usr/bin/wget'; #========================== end config =========================== my $version = '0.9'; my $project = ''; my $build_version = ''; my $print_version = 0; my $nodeps = 0; my $verbose = 0; my $help = 0; my @rpm_paths = (); my $RM = 1; my $PRINT = 2; my %projects = ( 'psad' => '', 'fwknop' => '', 'fwsnort' => '', 'gpgdir' => '' ); Getopt::Long::Configure('no_ignore_case'); &usage() unless (GetOptions( 'project=s' => \$project, 'build-version=s' => \$build_version, 'rpm-build-dir=s' => \$rpm_root_dir, 'no-deps' => \$nodeps, 'verbose' => \$verbose, 'Version' => \$print_version, 'help' => \$help )); &usage() if $help; if ($print_version) { print "[+] cd_rpmbuilder by Michael Rash \n"; exit 0; } if ($project) { unless (defined $projects{$project}) { print "[*] Unrecognized project: $project; must be one of:\n"; print $_, "\n" for keys %projects; exit 1; } } else { die "[*] Must specify a project with -p \n"; } die "[*] $wgetCmd is not a valid path to wget, update the config section." unless -x $wgetCmd; die "[*] $rpmbuildCmd is not a valid path to rpmbuild, update the config " . "section." unless -x $rpmbuildCmd; chdir "$rpm_root_dir/SPECS" or die "[*] Could not chdir $rpm_root_dir/SPECS"; unless ($build_version) { ### we need to get the latest version from cipherdyne.org &get_latest_version(); } my $spec_file = "$project-$build_version.spec"; my $tar_file = "$project-$build_version.tar.gz"; if ($nodeps) { $spec_file = "$project-nodeps-$build_version.spec"; $tar_file = "$project-nodeps-$build_version.tar.gz"; } ### remove old RPMS &find_rpms($RM); ### get the remote spec file &download_file($spec_file); &md5_check($spec_file); ### get the remote source tarball and md5 sum file &download_file($tar_file); &md5_check($tar_file); if ($nodeps) { move $tar_file, "../SOURCES/$project-$build_version.tar.gz" or die $!; } else { move $tar_file, '../SOURCES' or die $!; } ### build the rpm &build_rpm(); ### print the paths to the new RPMS &find_rpms($PRINT); exit 0; #======================= end main ======================== sub find_rpms() { my $action = shift; @rpm_paths = (); find(\&get_rpms, "$rpm_root_dir/SRPMS"); find(\&get_rpms, "$rpm_root_dir/RPMS"); if ($action == $PRINT) { if (@rpm_paths) { print "[+] The following RPMS were successfully built:\n\n"; } else { print "[-] No RPMS were successfully built; try running ", "with --verbose\n"; } } for my $rpm_file (@rpm_paths) { if ($action == $RM) { unlink $rpm_file or die "[*] Could not unlink $rpm_file: $!"; } elsif ($action == $PRINT) { if ($rpm_file =~ /\.src\.rpm/) { print " $rpm_file (source RPM)\n"; } else { print " $rpm_file\n"; } } } print "\n" if $action == $PRINT; return; } sub get_rpms() { my $file = $File::Find::name; if ($file =~ /$project-$build_version-.*\.rpm$/) { push @rpm_paths, $file; } return; } sub download_file() { my $file = shift; unlink $file if -e $file; print "[+] Downloading file:\n", " $build_url_base/$project/download/$file\n"; my $cmd = "$wgetCmd $build_url_base/$project/download/$file"; unless ($verbose) { $cmd .= ' > /dev/null 2>&1'; } system $cmd; die "[*] Could not download $file, try running with -v" unless -e $file; return; } sub md5_check() { my $file = shift; &download_file("$file.md5"); ### check MD5 sum open MD5, "md5sum -c $file.md5 |" or die $!; my $sum_line = ; close MD5; unless ($sum_line =~ m/$file:\s+OK/) { die "[*] MD5 sum check failed for $file, ", "exiting."; } print "[+] Valid md5 sum check for $file\n"; unlink "$file.md5"; return; } sub build_rpm() { print "[+] Building RPM, this may take a little while (try -v if you want\n", " to see all of the steps)...\n\n"; my $cmd = "$rpmbuildCmd -ba $spec_file"; unless ($verbose) { $cmd .= ' > /dev/null 2>&1'; } system $cmd; return; } sub get_latest_version() { unlink "$project-latest" if -e "$project-latest"; print "[+] Getting latest version file:\n", " $build_url_base/$project/$project-latest\n"; my $cmd = "$wgetCmd $build_url_base/$project/$project-latest"; unless ($verbose) { $cmd .= ' > /dev/null 2>&1'; } system $cmd; open F, "< $project-latest" or die "[*] Could not open $project-latest: $!"; my $line = ; close F; chomp $line; $build_version = $line; die "[*] Could not get build version" unless $build_version; unlink "$project-latest" if -e "$project-latest"; return; } sub usage() { print <<_HELP_; cd_rpmbuilder; the CipherDyne RPM builder [+] Version: $version [+] By Michael Rash (mbr\@cipherdyne.org, http://www.cipherdyne.org) Usage: cd_rpmbuilder -p [options] 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 $rpm_root_dir. -n, --no-deps - Build the specified project without any dependencies (such as perl modules). -v, --verbose - Run in verbose mode. -V, --Version - Print version and exit. -h, --help - Display usage information. _HELP_ exit 0; } psad-2.2.1/chainmgr_test.pl0000775000175000017500000000603512071203757013766 0ustar mbrmbr#!/usr/bin/perl -w use strict; ### path to default psad library directory for psad perl modules my $psad_lib_dir = '/usr/lib/psad'; ### import psad perl modules &import_psad_perl_modules(); my $ipt = new IPTables::ChainMgr( 'iptables' => '/sbin/iptables', 'verbose' => 1 ); my $total_rules = 0; my ($rv, $out_ar, $err_ar) = $ipt->create_chain('filter', 'PSAD'); print "create_chain() rv: $rv\n"; print "$_\n" for @$out_ar; print "$_\n" for @$err_ar; ($rv, $out_ar, $err_ar) = $ipt->add_jump_rule('filter', 'INPUT', 'PSAD'); print "add_jump_rule() rv: $rv\n"; print "$_\n" for @$out_ar; print "$_\n" for @$err_ar; ($rv, $out_ar, $err_ar) = $ipt->add_ip_rule('1.1.1.1', '0.0.0.0/0', 10, 'filter', 'PSAD', 'DROP'); print "add_ip_rule() rv: $rv\n"; print "$_\n" for @$out_ar; print "$_\n" for @$err_ar; ($rv, $total_rules) = $ipt->find_ip_rule('1.1.1.1', '0.0.0.0/0', 'filter', 'PSAD', 'DROP'); print "find ip: $rv, total chain rules: $total_rules\n"; ($rv, $out_ar, $err_ar) = $ipt->add_ip_rule('2.2.1.1', '0.0.0.0/0', 10, 'filter', 'PSAD', 'DROP'); print "add_ip_rule() rv: $rv\n"; print "$_\n" for @$out_ar; print "$_\n" for @$err_ar; ($rv, $out_ar, $err_ar) = $ipt->add_ip_rule('2.2.4.1', '0.0.0.0/0', 10, 'filter', 'PSAD', 'DROP'); print "add_ip_rule() rv: $rv\n"; print "$_\n" for @$out_ar; print "$_\n" for @$err_ar; ($rv, $out_ar, $err_ar) = $ipt->delete_ip_rule('1.1.1.1', '0.0.0.0/0', 'filter', 'PSAD', 'DROP'); print "delete_ip_rule() rv: $rv\n"; print "$_\n" for @$out_ar; print "$_\n" for @$err_ar; ($rv, $out_ar, $err_ar) = $ipt->delete_chain('filter', 'INPUT', 'PSAD'); print "delete_chain() rv: $rv\n"; print "$_\n" for @$out_ar; print "$_\n" for @$err_ar; ($rv, $out_ar, $err_ar) = $ipt->run_ipt_cmd('/sbin/iptables -nL INPUT'); print "list on 'INPUT' chain rv: $rv\n"; print for @$out_ar; print for @$err_ar; ($rv, $out_ar, $err_ar) = $ipt->run_ipt_cmd('/sbin/iptables -nL INPU'); print "bogus list on 'INPU' chain rv: $rv (this is expected).\n"; print for @$out_ar; print for @$err_ar; exit 0; sub import_psad_perl_modules() { my $mod_paths_ar = &get_psad_mod_paths(); push @$mod_paths_ar, @INC; splice @INC, 0, $#$mod_paths_ar+1, @$mod_paths_ar; require IPTables::Parse; require IPTables::ChainMgr; return; } sub get_psad_mod_paths() { my @paths = (); unless (-d $psad_lib_dir) { my $dir_tmp = $psad_lib_dir; $dir_tmp =~ s|lib/|lib64/|; if (-d $dir_tmp) { $psad_lib_dir = $dir_tmp; } else { die "[*] psad lib directory: $psad_lib_dir does not exist, ", "use --Lib-dir "; } } opendir D, $psad_lib_dir or die "[*] Could not open $psad_lib_dir: $!"; my @dirs = readdir D; closedir D; shift @dirs; shift @dirs; push @paths, $psad_lib_dir; for my $dir (@dirs) { ### get directories like "/usr/lib/psad/x86_64-linux" next unless -d "$psad_lib_dir/$dir"; push @paths, "$psad_lib_dir/$dir" if $dir =~ m|linux| or $dir =~ m|thread|; } return \@paths; } psad-2.2.1/posf0000664000175000017500000000357012071203757011472 0ustar mbrmbr# ############################################################################### # This is the file used to define passive operating system fingerprints # psad uses to try to determine what type of system scans originate # from. # ############################################################################### # ### Linux 2.2 OS Linux (2.2.x kernel); NUMPKTS 3; TOS 0x10; LEN 60; TTL 64; ID RANDOM; WINDOW 32120; ### Linux 2.4 OS Linux (2.4.x kernel); NUMPKTS 3; TOS 0x10; LEN 60; TTL 64; ID RANDOM; WINDOW 5840; ### FreeBSD OS FreeBSD 4.5; NUMPKTS 3; TOS 0x10; LEN 60; TTL 64; ID SMALLINCR; WINDOW 65535; ### OpenBSD OS OpenBSD; NUMPKTS 6; TOS 0x00; LEN 64; TTL 64; ID RANDOM; WINDOW 16384; ### Solaris 7 OS Solaris 7; NUMPKTS 3; TOS 0x00; LEN 44; TTL 255; ID SMALLINCR; WINDOW 8760; ### Solaris 8 OS Solaris 8; NUMPKTS 3; TOS 0x00; LEN 48; TTL 64; ID SMALLINCR; WINDOW 33360; ### AIX 4.3 #OS AIX 4.3; #NUMPKTS 3; #WINDOW 16384; #TOS 0x00; #LEN 48; #TTL 128; #ID SMALLINCR; ### HPUX 10 OS HPUX 10.x NUMPKTS 3; WINDOW 32768; TOS 0x00; LEN 44; TTL 64; ID SMALLINCR; ### HPUX 11 OS HPUX 11.x NUMPKTS 3; WINDOW 32768; TOS 0x00; LEN 48; TTL 64; ID SMALLINCR; ### Nokia IPSO 3.4 OS Nokia IPSO 3.4 NUMPKTS 3; WINDOW 16384; TOS 0x10; LEN 68; TTL 64 ID SMALLINCR; ### Windows XP/2000 OS Windows XP/2000; NUMPKTS 3; TOS 0x00; LEN 48; TTL 128; ID SMALLINCR; WINDOW 16384; ### Windows NT OS Windows NT; NUMPKTS 3; TOS 0x00; LEN 44; TTL 128; ID RANDOM; WINDOW 8192; psad-2.2.1/README0000664000175000017500000001230212071203757011451 0ustar mbrmbrpsad (Port Scan Attack Detector) Version: 3.0 Author: Michael Rash (mbr@cipherdyne.org) Website: http://www.cipherdyne.org/ Thanks to: (see the CREDITS file). =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- DESCRIPTION: The Port Scan Attack Detector (psad) is a collection of two lightweight system daemons written in Perl and in C that are designed to work with Linux iptables firewalling code to detect port scans and other suspect traffic. It features a set of highly configurable danger thresholds (with sensible defaults provided), verbose alert messages that include the source, destination, scanned port range, begin and end times, tcp flags and corresponding nmap options, reverse DNS info, email and syslog alerting, automatic blocking of offending ip addresses via dynamic configuration of iptables rulesets, passive operating system fingerprinting, and DSheild reporting. In addition, psad incorporates many of the tcp, udp, and icmp signatures included in the snort intrusion detection system (http://www.snort.org) to detect highly suspect scans for various backdoor programs (e.g. EvilFTP, GirlFriend, SubSeven), DDoS tools (mstream, shaft), and advanced port scans (syn, fin, xmas) which are easily leveraged against a machine via nmap. psad can also alert on snort signatures that are logged via fwsnort, which makes use of the iptables string match module to detect application layer signatures. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- CONFIGURATION INFORMATION: Information on config keywords referenced by psad may be found both in the psad(8) man page, and also here: http://www.cipherdyne.org/psad/docs/config.html =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- METHODOLOGY: All information psad analyzes is gathered from iptables log messages. psad by default reads the /var/log/messages file for new iptables messages and optionally writes them out to a dedicated file (/var/log/psad/fwdata). psad is then responsible for applying the danger threshold and signature logic in order to determine whether or not a port scan has taken place, send appropriate alert emails, and (optionally) block offending ip addresses. psad includes a signal handler such that if a USR1 signal is received, psad will dump the contents of the current scan hash data structure to /var/log/psad/scan_hash.$$ where "$$" represents the pid of the running psad daemon. NOTE: Since psad relies on iptables to generate appropriate log messages for unauthorized packets, psad is only as good as the logging rules included in the iptables ruleset. Usually the best way setup the firewall is with default "drop and log" rules at the end of the ruleset, and include rules above this last rule that only allow traffic that should be allowed through. Upon execution, the psad daemon will attempt to ascertain whether or not such a default deny rule exists, and will warn the administrator if it doesn't. See the FW_EXAMPLE_RULES file for example firewall rulesets that are compatible with psad. Additionally, extensive coverage of psad is included in the book "Linux Firewalls: Attack Detection and Response" published by No Starch Press, and a supporting script in this book is compatible with psad. This script can be found here: http://www.cipherdyne.org/LinuxFirewalls/ch01/ =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- INSTALLATION: See the INSTALL file in the psad sources directory. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- FIREWALL SETUP: See the FW_HELP file in the psad sources directory. Also, read the README.SYSLOG file. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- PLATFORMS: psad has been tested on RedHat 6.2 - 9.0, Fedora Core 1 and 2, and Gentoo Linux systems running various kernels. The only program that specifically depends on the RedHat architecture is psad-init, which depends on /etc/rc.d/init.d/functions. For non-RedHat systems a more generic init script is included called "psad-init.generic". The psad init scripts are mostly included as a nicety; psad can be run from the command line like any other program. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- COPYRIGHT: Copyright (C) 1999-2012 Michael Rash (mbr@cipherdyne.org) 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. psad makes use of many of the tcp, udp, and icmp signatures available in Snort (written by Marty Roesch, see http://www.snort.org). Snort is a registered trademark of Sourcefire, Inc. psad-2.2.1/psad.h0000664000175000017500000000516212071203757011677 0ustar mbrmbr/* ******************************************************************************** * * File: psad.h * * Author: Michael Rash (mbr@cipherdyne.org) * * Purpose: psad.h include appropriate system header files, and defines file * paths, function prototypes, and constants that are needed by * the C versions of psad. * * Credits: (see the CREDITS file) * * Copyright (C) 1999-2006 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * ******************************************************************************** */ #ifndef __PSAD_H__ #define __PSAD_H__ /* INCLUDES *******************************************************************/ #include #include #include #include /* read(), write(), and close() */ #include /* open() */ #include /* umask */ #include #include #include #include #include /* DEFINES ********************************************************************/ #define PSAD_VERSION "2.1.6-pre1" #define MAX_LINE_BUF 1024 #define MAX_PID_SIZE 6 #define MAX_PATH_LEN 100 #define MAX_MSG_LEN 120 #define MAX_GEN_LEN 80 #define MAX_EMAIL_LEN 300 #define MAX_ARG_LEN 30 #define MAX_NUM_LEN 6 /* PROTOTYPES *****************************************************************/ void slogr(const char *, const char *); void check_unique_pid(const char *, const char *); void write_pid(const char *, pid_t); void daemonize_process(const char *); void send_alert_email(const char *, const char *, const char *); int has_sub_var(char *var_name, char *value, char *sub_var, char *pre_str, char *post_str); void expand_sub_var_value(char *value, const char *sub_var, const char *pre_str, const char *post_str); int find_char_var(const char *, char *, char *); int check_import_config(time_t *config_mtime, char *config_file); void *safe_malloc(const unsigned int len); void list_to_array(char *ptList, const char sep, char **array, unsigned char max_arg); /* From OpenBSD */ size_t strlcpy(char *, const char *, size_t); size_t strlcat(char *, const char *, size_t); #endif /* __PSAD_H__ */ psad-2.2.1/logrotate.psad0000664000175000017500000000100312071203757013436 0ustar mbrmbr#*********************************************************************** # # PSAD Log Rotation config # # Copyright (c) 2006, Albert Whale, ABS Computer Technology, Inc. # All rights reserved. # # This program may be distributed under the terms of the GNU General # Public License, Version 2, or (at your option) any later version. # #*********************************************************************** /var/log/psad/fwdata { missingok rotate 5 weekly postrotate psad --HUP endscript } psad-2.2.1/icmp6_types0000664000175000017500000000473712071203757012773 0ustar mbrmbr# ############################################################################### # # File: icmp6_types # # Purpose: This file contains all valid icmp6 types and corresponding codes as # defined by IANA. If a packet is logged by iptables that does # not have a valid type and/or code, then an alert will be generated. # ############################################################################### # # Type # Code values 0 Reserved 1 Destination Unreachable 0 No route to destination 1 Communication with destination administratively prohibited 2 Beyond scope of source address 3 Address unreachable 4 Port unreachable 5 Source address failed ingress/egress policy 6 Reject route to destination 7 Error in Source Routing Header 2 Packet Too Big 0 3 Time Exceeded 0 Hop limit exceeded in transit 1 Fragment reassembly time exceeded 4 Parameter Problem 0 Erroneous header field encountered 1 Unrecognized Next Header type encountered 2 Unrecognized IPv6 option encountered 128 Echo Request 0 129 Echo Reply 0 130 Multicast Listener Query 0 131 Multicast Listener Report 0 132 Multicast Listener Done 0 133 Router Solicitation 0 134 Router Advertisement 0 135 Neighbor Solicitation 0 136 Neighbor Advertisement 0 137 Redirect Message 0 138 Router Renumbering 0 Router Renumbering Command 1 Router Renumbering Result 255 Sequence Number Reset 139 ICMP Node Information Query 0 The Data field contains an IPv6 address which is the Subject of this Query. 1 The Data field contains a name which is the Subject of this Query, or is empty, as in the case of a NOOP. 2 The Data field contains an IPv4 address which is the Subject of this Query. 140 ICMP Node Information Response 0 A successful reply. The Reply Data field may or may not be empty. 1 The Responder refuses to supply the answer. The Reply Data field will be empty. 2 The Qtype of the Query is unknown to the Responder. The Reply Data field will be empty. 141 Inverse Neighbor Discovery Solicitation Message 0 142 Inverse Neighbor Discovery Advertisement Message 0 144 Home Agent Address Discovery Request Message 0 145 Home Agent Address Discovery Reply Message 0 146 Mobile Prefix Solicitation 0 147 Mobile Prefix Advertisement 0 psad-2.2.1/config_vars.conf0000664000175000017500000000011612071203757013740 0ustar mbrmbrpsad psad.conf kmsgsd.c psad.conf psadwatchd.c psad.conf psad-2.2.1/fwcheck_psad.80000664000175000017500000000250712071203757013311 0ustar mbrmbr.\" .TH FWCHECK_PSAD 8 "Aug, 2008" "Debian GNU/Linux" .SH NAME .B fwcheck_psad \- look for iptables rules that log and block unwanted packets. .SH SYNOPSIS .B fwcheck_psad [options] .SH DESCRIPTION .B fwcheck_psad parses the iptables ruleset on the underlying system to see if iptables has been configured to log and block unwanted packets by default. This program is called by .B psad , but can also be executed manually from the command line. .SH OPTIONS .TP .BR " \-\^\-config Specify path to the psad configuration file. By default this is .B /etc/psad/psad.conf. .TP .BR " \-\^\-fw-file Allow the user to analyze a specific rulset from a file rather than the local policy. .TP .BR " \-\^\-fw-analyze Analyze the local iptables ruleset and exit. .TP .BR " \-\^\-no-fw-search-all Look for specific log prefix defined through the FW_MSG_SEARCH variable(s) in the configuration file. .TP .BR " \-\^\-Lib-dir Specify path to psad lib directory. .TP .BR " \-\^\-help Display the help message. .SH "SEE ALSO" .BR iptables (8), .BR psad (8) .SH AUTHOR Michael Rash .SH BUGS Send bug reports to mbr@cipherdyne.org. Suggestions and/or comments are always welcome as well. .SH DISTRIBUTION .B psad is distributed under the GNU General Public License (GPL), and the latest version may be downloaded from: .B http://www.cipherdyne.org/ psad-2.2.1/README.RPM0000664000175000017500000000564412071203757012121 0ustar mbrmbr 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. psad-2.2.1/VERSION0000664000175000017500000000000612071203757011637 0ustar mbrmbr2.2.1 psad-2.2.1/signatures0000664000175000017500000012776112071203757012720 0ustar mbrmbr# ############################################################################## # # File: signatures (/etc/psad/signatures) # # Purpose: To provide a set of approximations to the Snort rule set for psad. # These signatures are the closest representations to Snort rules # that are possible given the iptables logging format. Note that # with the iptables string match extension, iptables along with # fwsnort is able to detect (and optionally block) attacks based on # application layer data, but this is not addressed within the # signatures file itself. # # psad_id: - Unique ID number (analogous to the Snort sid field). # psad_derived_sids: # - This field tracks all Snort rules that were used to # construct and approximate psad signature. # psad_dl: - The psad danger level # psad_dsize: - Requires a size on application layer data. The size # in this case is derived from the IP header length # for TCP and ICMP packets (by assuming a bound on the # average header sizes) and from the length field in # the UDP header for UDP packets. # psad_ip_len: - This allows psad to test the length field in the IP # header (logged as "LEN") within iptables logs. # ############################################################################## # ### snmp.rules alert tcp $EXTERNAL_NET any -> $HOME_NET 705 (msg:"SNMP AgentX/tcp request"; flags:S; reference:bugtraq,4088; reference:bugtraq,4089; reference:bugtraq,4132; reference:cve,2002-0012; reference:cve,2002-0013; classtype:attempted-recon; sid:1421; psad_id:100001; psad_dl:2;) ### finger.rules ### info.rules ### ddos.rules alert udp $EXTERNAL_NET any -> $HOME_NET 31335 (msg:"DDOS Trin00 Daemon to Master"; reference:arachnids,187; reference:url,www.sans.org/resources/idfaq/trinoo.php; classtype:attempted-recon; psad_dsize:>2; psad_id:100002; psad_dl:2; psad_derived_sids:223,231,232;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"DDOS TFN client command BE"; icmp_id:456; icmp_seq:0; itype:0; reference:arachnids,184; classtype:attempted-dos; sid:228; psad_id:100003; psad_dl:2;) alert tcp $HOME_NET 20432 -> $EXTERNAL_NET any (msg:"DDOS shaft client login to handler connection attempt"; flags:S; reference:arachnids,254; reference:url,security.royans.net/info/posts/bugtraq_ddos3.shtml; classtype:attempted-dos; sid:230; psad_id:100004; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 18753 (msg:"DDOS shaft handler to agent"; reference:arachnids,255; classtype:attempted-dos; psad_dsize:>10; sid:239; psad_id:100005; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 20433 (msg:"DDOS shaft agent to handler"; reference:arachnids,256; classtype:attempted-dos; psad_dsize:>4; sid:240; psad_id:100006; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 27665 (msg:"DDOS Trin00 Attacker to Master connection attempt"; flags:S; reference:arachnids,197; classtype:attempted-dos; psad_id:100007; psad_dl:2; psad_derived_sids:233,234,235;) alert udp $EXTERNAL_NET any -> $HOME_NET 27444 (msg:"DDOS Trin00 Master to Daemon default password attempt"; reference:arachnids,197; classtype:attempted-dos; psad_dsize:>6; sid:237; psad_id:100008; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 6838 (msg:"DDOS mstream agent to handler"; classtype:attempted-dos; psad_dsize:>8; sid:243; psad_id:100009; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 10498 (msg:"DDOS mstream handler to agent"; reference:cve,2000-0138; classtype:attempted-dos; psad_dsize:>3; psad_id:100010; psad_dl:2; psad_derived_sids:244,245,246;) alert tcp $EXTERNAL_NET any -> $HOME_NET 12754 (msg:"DDOS mstream client to handler"; flags:S; reference:cve,2000-0138; classtype:attempted-dos; sid:247; psad_id:100011; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 15104 (msg:"DDOS mstream client to handler"; flags:S,12; reference:arachnids,111; reference:cve,2000-0138; classtype:attempted-dos; sid:249; psad_id:100012; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"DDOS - TFN client command LE"; icmp_id:51201; icmp_seq:0; itype:0; reference:arachnids,183; classtype:attempted-dos; sid:251; psad_id:100013; psad_dl:2;) alert icmp 3.3.3.3/32 any -> $EXTERNAL_NET any (msg:"DDOS Stacheldraht server spoof"; icmp_id:666; itype:0; reference:arachnids,193; classtype:attempted-dos; sid:224; psad_id:100014; psad_dl:2;) ### virus.rules ### icmp.rules alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP PING NMAP"; dsize:0; itype:8; reference:arachnids,162; classtype:attempted-recon; sid:469; psad_id:100015; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP icmpenum v1.1.1"; dsize:0; icmp_id:666; icmp_seq:0; id:666; itype:8; reference:arachnids,450; classtype:attempted-recon; sid:471; psad_id:100016; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP redirect host"; icode:1; itype:5; reference:arachnids,135; reference:cve,1999-0265; classtype:bad-unknown; sid:472; psad_id:100017; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP redirect net"; icode:0; itype:5; reference:arachnids,199; reference:cve,1999-0265; classtype:bad-unknown; sid:473; psad_id:100018; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Source Quench"; icode:0; itype:4; classtype:bad-unknown; sid:477; psad_id:100019; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Broadscan Smurf Scanner"; dsize:4; icmp_id:0; icmp_seq:0; itype:8; classtype:attempted-recon; sid:478; psad_id:100020; psad_dl:2;) alert icmp any any -> any any (msg:"ICMP Destination Unreachable Communication Administratively Prohibited"; icode:13; itype:3; classtype:misc-activity; sid:485; psad_id:100021; psad_dl:2;) alert icmp any any -> any any (msg:"ICMP Destination Unreachable Communication with Destination Host is Administratively Prohibited"; icode:10; itype:3; classtype:misc-activity; sid:486; psad_id:100022; psad_dl:2;) alert icmp any any -> any any (msg:"ICMP Destination Unreachable Communication with Destination Network is Administratively Prohibited"; icode:9; itype:3; classtype:misc-activity; sid:487; psad_id:100023; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Large ICMP Packet"; dsize:>800; reference:arachnids,246; classtype:bad-unknown; sid:499; psad_id:100024; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP traceroute ipopts"; ipopts:rr; itype:0; reference:arachnids,238; classtype:attempted-recon; sid:475; psad_id:100198; psad_dl:2;) ### dns.rules ### rpc.rules alert tcp $EXTERNAL_NET any -> $HOME_NET 32771 (msg:"RPC portmap listing TCP 32771"; flags:S; reference:arachnids,429; classtype:rpc-portmap-decode; sid:599; psad_id:100025; psad_dl:2;) ### psad note: dsize:>12 was added since there were three content fields in the ### original Snort rule, each 4 bytes large (need to research depth,offset,distance, ### and within keywords better since these were in the Snort rule as well; might ### mean that the dsize value should be increased). alert udp $EXTERNAL_NET any -> $HOME_NET 32771 (msg:"RPC portmap listing UDP 32771"; reference:arachnids,429; classtype:rpc-portmap-decode; psad_dsize:>12; sid:1281; psad_id:100026; psad_dl:2;) ### backdoor.rules alert tcp $EXTERNAL_NET any -> $HOME_NET 16959 (msg:"BACKDOOR Subseven DEFCON8 2.1 connection Attempt"; flags:S; classtype:trojan-activity; sid:107; psad_id:100027; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 27374 (msg:"BACKDOOR Subseven connection attempt"; flags:S; classtype:trojan-activity; psad_id:100207; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 12345:12346 (msg:"BACKDOOR netbus Connection Cttempt"; flags:S; reference:arachnids,401; classtype:misc-activity; psad_id:100028; psad_dl:2; psad_derived_sids:109,110;) alert tcp $EXTERNAL_NET any -> $HOME_NET 20034 (msg:"BACKDOOR NetBus Pro 2.0 Connection Cttempt"; flags:S; classtype:misc-activity; psad_id:100029; psad_dl:2; psad_derived_sids:115,3009;) alert udp $EXTERNAL_NET any -> $HOME_NET 2140 (msg:"BACKDOOR DeepThroat 3.1 Connection attempt"; reference:mcafee,98574; reference:nessus,10053; classtype:misc-activity; psad_dsize:>1; sid:1980; psad_id:100030; psad_dl:2;) alert udp $HOME_NET 2140 -> $EXTERNAL_NET any (msg:"BACKDOOR DeepThroat 3.1 Server Response"; reference:arachnids,106; reference:mcafee,98574; reference:nessus,10053; classtype:misc-activity; psad_dsize:>21; sid:195; psad_id:100031; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 3150 (msg:"BACKDOOR DeepThroat 3.1 Connection attempt [3150]"; reference:mcafee,98574; reference:nessus,10053; classtype:misc-activity; psad_dsize:>1; sid:1981; psad_id:100032; psad_dl:2;) alert udp $HOME_NET 3150 -> $EXTERNAL_NET any (msg:"BACKDOOR DeepThroat 3.1 Server Response [3150]"; reference:arachnids,106; reference:mcafee,98574; reference:nessus,10053; classtype:misc-activity; psad_dsize:>21; sid:1982; psad_id:100033; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 4120 (msg:"BACKDOOR DeepThroat 3.1 Connection attempt [4120]"; reference:mcafee,98574; reference:nessus,10053; classtype:misc-activity; sid:1983; psad_id:100034; psad_dl:2;) alert udp $HOME_NET 4120 -> $EXTERNAL_NET any (msg:"BACKDOOR DeepThroat 3.1 Server Response [4120]"; reference:arachnids,106; reference:mcafee,98574; reference:nessus,10053; classtype:misc-activity; psad_dsize:>21; sid:1984; psad_id:100035; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 6789 (msg:"BACKDOOR Doly 2.0 Connection attempt"; flags:S; reference:arachnids,312; classtype:misc-activity; sid:119; psad_id:100036; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 1015 (msg:"BACKDOOR Doly 1.5 Connection attempt"; flags:S; classtype:trojan-activity; sid:1985; psad_id:100037; psad_dl:2;) alert tcp $EXTERNAL_NET 1024: -> $HOME_NET 2589 (msg:"BACKDOOR - Dagger_1.4.0 Connection attempt"; flags:S; reference:arachnids,483; reference:url,www.tlsecurity.net/backdoor/Dagger.1.4.html; classtype:misc-activity; psad_id:100038; psad_dl:2; psad_derived_sids:104,105;) alert tcp $EXTERNAL_NET any -> $HOME_NET 7597 (msg:"BACKDOOR QAZ Worm Client Login access"; flags:S; reference:MCAFEE,98775; classtype:misc-activity; sid:108; psad_id:100039; psad_dl:2;) alert tcp $EXTERNAL_NET 1000: -> $HOME_NET 146 (msg:"BACKDOOR Infector.1.x Connection attempt"; flags:S; reference:arachnids,315; classtype:misc-activity; psad_id:100040; psad_dl:2; psad_derived_sids:117,120,121;) alert tcp $EXTERNAL_NET 1024: -> $HOME_NET 666 (msg:"BACKDOOR SatansBackdoor.2.0.Beta, or BackConstruction 2.1 Connection Attempt"; flags:S; reference:arachnids,316; classtype:misc-activity; psad_id:100041; psad_dl:2; psad_derived_sids:118,157,158;) alert tcp $EXTERNAL_NET any -> $HOME_NET 31785 (msg:"BACKDOOR HackAttack 1.20 Connection attempt"; flags:S; classtype:misc-activity; sid:141; psad_id:100042; psad_dl:2;) alert tcp $EXTERNAL_NET !80 -> $HOME_NET 21554 (msg:"BACKDOOR GirlFriend Connection attempt"; flags:S; reference:arachnids,98; classtype:misc-activity; sid:145; psad_id:100043; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 30100:30102 (msg:"BACKDOOR NetSphere Connection attempt"; flags:S; reference:arachnids,76; classtype:misc-activity; psad_id:100044; psad_dl:2; psad_derived_sids:146,155;) alert tcp $EXTERNAL_NET any -> $HOME_NET 6969 (msg:"BACKDOOR GateCrasher Connection attempt"; flags:S; reference:arachnids,99; classtype:misc-activity; sid:147; psad_id:100045; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 5401:5402 (msg:"BACKDOOR BackConstruction 2.1 connection attempt"; flags:S; classtype:misc-activity; sid:152; psad_id:100046; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 23476 (msg:"BACKDOOR DonaldDick 1.53 connection attempt"; reference:mcafee,98575; classtype:misc-activity; sid:153; psad_id:100047; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 5032 (msg:"BACKDOOR NetMetro File List connection attempt"; flags:S; reference:arachnids,79; classtype:misc-activity; sid:159; psad_id:100048; psad_dl:2;) alert udp $EXTERNAL_NET 3344 -> $HOME_NET 3345 (msg:"BACKDOOR Matrix 2.0 Client connect"; reference:arachnids,83; classtype:misc-activity; psad_dsize:>7; sid:161; psad_id:100049; psad_dl:2;) alert udp $EXTERNAL_NET 3345 -> $HOME_NET 3344 (msg:"BACKDOOR Matrix 2.0 Server access"; reference:arachnids,83; classtype:misc-activity; psad_dsize:>8; sid:162; psad_id:100050; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 5714 (msg:"BACKDOOR WinCrash 1.0 communication attempt"; flags:S; reference:arachnids,36; classtype:misc-activity; sid:163; psad_id:100051; psad_dl:2;) #alert icmp 255.255.255.0/24 any -> $HOME_NET any (msg:"BACKDOOR SIGNATURE - Q ICMP"; dsize:>1; itype:0; reference:arachnids,202; classtype:misc-activity; sid:100; psad_id:100000; psad_dl:2;) alert tcp 255.255.255.0/24 any -> $HOME_NET any (msg:"BACKDOOR Q access"; flags:S; reference:arachnids,203; classtype:misc-activity; sid:184; psad_id:100052; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 555 (msg:"BACKDOOR PhaseZero Server Active on Network"; flags:S; classtype:misc-activity; sid:208; psad_id:100053; psad_dl:2;) alert tcp $EXTERNAL_NET 31790 -> $HOME_NET 31789 (msg:"BACKDOOR hack-a-tack connection attempt"; flags:S; reference:arachnids,314; classtype:attempted-recon; sid:614; psad_id:100054; psad_dl:2;) alert ip any any -> 216.80.99.202 any (msg:"BACKDOOR fragroute trojan connection attempt"; reference:bugtraq,4898; classtype:trojan-activity; sid:1791; psad_id:100055; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 35555 (msg:"BACKDOOR win-trin00 connection attempt"; reference:cve,2000-0138; reference:nessus,10307; classtype:attempted-admin; psad_dsize:>27; sid:1853; psad_id:100056; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 33270 (msg:"BACKDOOR trinity connection attempt"; flags:S; reference:cve,2000-0138; reference:nessus,10501; classtype:attempted-admin; sid:1843; psad_id:100057; psad_dl:2;) alert tcp any any -> 212.146.0.34 1963 (msg:"BACKDOOR TCPDUMP/PCAP trojan traffic"; reference:url,hlug.fscker.com; classtype:trojan-activity; sid:1929; psad_id:100058; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 34012 (msg:"BACKDOOR Remote PC Access connection attempt"; flags:S; reference:nessus,11673; classtype:trojan-activity; sid:2124; psad_id:100059; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"BACKDOOR typot trojan traffic"; flags:S; window:55808; reference:mcafee,100406; classtype:trojan-activity; sid:2182; psad_id:100060; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 3127:3199 (msg:"BACKDOOR DoomJuice file upload attempt"; flags:S; reference:url,securityresponse.symantec.com/avcenter/venc/data/w32.hllw.doomjuice.html; classtype:trojan-activity; sid:2375; psad_id:100061; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 63536 (msg:"BACKDOOR Insane Network 4.0 connection established port 63536"; classtype:misc-activity; sid:3016; psad_id:100062; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 22222 (msg:"BACKDOOR RUX the Tick connection attempt"; flags:S; classtype:misc-activity; psad_id:100063; psad_dl:2; psad_derived_sids:3010,3011,3012;) alert tcp $EXTERNAL_NET any -> $HOME_NET 23432 (msg:"BACKDOOR Asylum 0.1 connection request"; flags:S; classtype:misc-activity; psad_id:100064; psad_dl:2; psad_derived_sids:3013,3014;) ### scan.rules alert tcp $EXTERNAL_NET 10101 -> $HOME_NET any (msg:"SCAN myscan"; flags:S; ttl:>220; reference:arachnids,439; classtype:attempted-recon; sid:613; psad_id:100065; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN FIN"; flags:F; reference:arachnids,27; classtype:attempted-recon; sid:621; psad_id:100066; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN NULL"; flags:0; reference:arachnids,4; classtype:attempted-recon; sid:623; psad_id:100067; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN SYN FIN"; flags:SF; reference:arachnids,198; classtype:attempted-recon; sid:624; psad_id:100068; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN XMAS"; flags:SRAFPU; reference:arachnids,144; classtype:attempted-recon; sid:625; psad_id:100069; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN nmap XMAS"; flags:FPU; reference:arachnids,30; classtype:attempted-recon; sid:1228; psad_id:100070; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN synscan portscan"; flags:SF; id:39426; reference:arachnids,441; classtype:attempted-recon; sid:630; psad_id:100071; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN ipEye SYN scan"; flags:S; seq:1958810375; reference:arachnids,236; classtype:attempted-recon; sid:622; psad_id:100197; psad_dl:2;) ### x11.rules ### oracle.rules ### web-frontpage.rules ### PSAD-CUSTOM rules alert tcp $EXTERNAL_NET any -> $HOME_NET 17300 (msg:"PSAD-CUSTOM Kuang2 virus communication attempt"; flags:S; reference:url,isc.sans.org/port_details.php?port=17300; classtype:trojan-activity; psad_id:100206; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 1434 (msg:"PSAD-CUSTOM Slammer communication attempt"; reference:url,www.linklogger.com/UDP1434.htm; classtype:trojan-activity; psad_id:100208; psad_dl:2; psad_ip_len:404;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"PSAD-CUSTOM Nachi worm reconnaisannce"; itype:8; icode:0; reference:url,www.cisco.com/en/US/products/sw/voicesw/ps556/products_security_notice09186a00801b143a.html; classtype:trojan-activity; psad_id:100209; psad_dl:2; psad_ip_len:92;) ### misc.rules alert tcp $EXTERNAL_NET any -> $HOME_NET 1433 (msg:"MISC Microsoft SQL Server communication attempt"; flags:S; reference:url,www.linklogger.com/TCP1433.htm; classtype:attempted-admin; psad_id:100205; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 1417 (msg:"MISC Insecure TIMBUKTU communication attempt"; flags:S; reference:arachnids,229; classtype:bad-unknown; sid:505; psad_id:100072; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 5631:5632 (msg:"MISC PCAnywhere communication attempt"; flags:S; classtype:attempted-admin; psad_id:100073; psad_dl:2; psad_derived_sids:507,512;) alert tcp $EXTERNAL_NET any -> $HOME_NET 5900 (msg:"MISC VNC communication attempt"; flags:S; reference:url,isc.sans.org/port_details.php?port=5900; reference:url,secunia.com/advisories/20107; classtype:attempted-admin; psad_id:100202; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 7212 (msg:"MISC Ghostsurf communication attempt"; flags:S; reference:url,isc.sans.org/port_details.php?port=7212; reference:url,www.tenebril.com/src/advisories/open-proxy-relay.php; classtype:misc-activity; psad_id:100203; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 4899 (msg:"MISC Radmin Default install options attempt"; flags:S; reference:url,isc.sans.org/port_details.php?port=4899; reference:url,archives.neohapsis.com/archives/vulnwatch/2002-q3/0099.html; classtype:attempted-admin; psad_id:100204; psad_dl:2;) #alert ip $EXTERNAL_NET any -> $HOME_NET any (msg:"MISC Tiny Fragments"; dsize:< 25; fragbits:M; classtype:bad-unknown; sid:100; psad_id:100000; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 1900 (msg:"SCAN UPnP communication attempt"; classtype:misc-attack; psad_dsize:>8; psad_id:100074; psad_dl:2; psad_derived_sids:1917,1384,1388;) alert tcp $EXTERNAL_NET any -> $HOME_NET 32000 (msg:"MISC Xtramail communication attempt"; flags:S; reference:bugtraq,791; reference:cve,1999-1511; reference:nessus,10323; classtype:attempted-admin; sid:1636; psad_id:100075; psad_dl:2;) alert udp $EXTERNAL_NET 2002 -> $HTTP_SERVERS 2002 (msg:"MISC slapper worm admin traffic"; reference:url,isc.incidents.org/analysis.html?id=167; reference:url,www.cert.org/advisories/CA-2002-27.html; classtype:trojan-activity; psad_dsize:>20; sid:1889; psad_id:100076; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 3389 (msg:"MISC MS Terminal Server communication attempt"; flags:S; reference:bugtraq,3099; reference:cve,2001-0540; classtype:misc-activity; psad_id:100077; psad_dl:2; psad_derived_sids:1447,1448,2418;) alert tcp $EXTERNAL_NET any -> $HOME_NET 2533 (msg:"MISC Alcatel PABX 4400 connection attempt"; flags:S; reference:nessus,11019; classtype:misc-activity; sid:1819; psad_id:100078; psad_dl:2;) alert udp $EXTERNAL_NET any -> $HOME_NET 27155 (msg:"MISC GlobalSunTech Access Point Information Disclosure attempt"; reference:bugtraq,6100; classtype:misc-activity; psad_dsize:>8; sid:1966; psad_id:100079; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 7100 (msg:"MISC xfs communication attempt"; flags:S; reference:bugtraq,6241; reference:cve,2002-1317; reference:nessus,11188; classtype:misc-activity; sid:1987; psad_id:100080; psad_dl:2;) alert udp $HOME_NET 500 -> $EXTERNAL_NET 500 (msg:"MISC isakmp login failed"; classtype:misc-activity; psad_dsize:>29; sid:2043; psad_id:100081; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 1723 (msg:"MISC Microsoft PPTP communication attempt"; flags:S; reference:bugtraq,5807; reference:cve,2002-1214; classtype:attempted-admin; psad_id:100082; psad_dl:2; psad_derived_sids:2126,2044;) alert tcp $EXTERNAL_NET any -> $HOME_NET 639 (msg:"MISC LDAP communication attempt"; flags:S; reference:bugtraq,10116; reference:cve,2003-0719; reference:url,www.microsoft.com/technet/security/bulletin/MS04-011.mspx; classtype:attempted-admin; psad_id:100083; psad_dl:2; psad_derived_sids:2516,2532,2533,2534;) alert tcp $EXTERNAL_NET any -> $HOME_NET 8000 (msg:"MISC HP Web JetAdmin communication attempt"; flags:S; reference:bugtraq,9978; classtype:web-application-activity; psad_id:100084; psad_dl:2; psad_derived_sids:2547,2548,2549,2655;) alert udp $EXTERNAL_NET any -> $HOME_NET 1026:1029 (msg:"MISC Windows popup spam attempt"; classtype:misc-activity; reference:url,www.linklogger.com/UDP1026.htm; psad_dsize:>100; psad_id:100196; psad_dl:2;) alert ip $EXTERNAL_NET any -> $HOME_NET any (msg:"MISC source route lssr"; ipopts:lsrr; reference:arachnids,418; reference:bugtraq,646; reference:cve,1999-0909; classtype:bad-unknown; sid:500; psad_id:100199; psad_dl:2;); alert ip $EXTERNAL_NET any -> $HOME_NET any (msg:"MISC source route lssre"; ipopts:lsrre; reference:arachnids,420; reference:bugtraq,646; reference:cve,1999-0909; classtype:bad-unknown; sid:501; psad_id:100200; psad_dl:2;) alert ip $EXTERNAL_NET any -> $HOME_NET any (msg:"MISC source route ssrr"; ipopts:ssrr ; reference:arachnids,422; classtype:bad-unknown; sid:502; psad_id:100201; psad_dl:2;); ### shellcode.rules ### policy.rules alert udp $EXTERNAL_NET any -> $HOME_NET 5632 (msg:"POLICY PCAnywhere server response"; reference:arachnids,239; classtype:misc-activity; psad_dsize:>4; sid:556; psad_id:100085; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 9100 (msg:"POLICY HP JetDirect LCD commnication attempt"; flags:S; reference:arachnids,302; reference:bugtraq,2245; classtype:misc-activity; sid:568; psad_id:100086; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 9000:9002 (msg:"POLICY HP JetDirect LCD communication attempt"; flags:S; reference:arachnids,302; reference:bugtraq,2245; classtype:misc-activity; sid:510; psad_id:100087; psad_dl:2;) alert ip 66.151.158.177 any -> $HOME_NET any (msg:"POLICY poll.gotomypc.com access"; reference:url,www.gotomypc.com/help2.tmpl; classtype:misc-activity; sid:1429; psad_id:100088; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 5800:5802 (msg:"POLICY vncviewer Java applet communication attempt"; flags:S; reference:nessus,10758; classtype:misc-activity; sid:1846; psad_id:100089; psad_dl:2;) ### p2p.rules alert tcp $HOME_NET any -> $EXTERNAL_NET 8888 (msg:"P2P napster communication attempt"; flags:S; classtype:policy-violation; psad_id:100090; psad_dl:2; psad_derived_sids:549,550,551,552;) alert tcp $HOME_NET any <> $EXTERNAL_NET 6699 (msg:"P2P Napster Client Data communication attempt"; flags:S; classtype:policy-violation; sid:561; psad_id:100091; psad_dl:2;) alert tcp $HOME_NET any <> $EXTERNAL_NET 7777 (msg:"P2P Napster Client Data communication attempt"; flags:S; classtype:policy-violation; sid:562; psad_id:100092; psad_dl:2;) alert tcp $HOME_NET any <> $EXTERNAL_NET 6666 (msg:"P2P Napster Client Data communication attempt"; flags:S; classtype:policy-violation; sid:563; psad_id:100093; psad_dl:2;) alert tcp $HOME_NET any <> $EXTERNAL_NET 5555 (msg:"P2P Napster Client Data communication attempt"; flags:S; classtype:policy-violation; sid:564; psad_id:100094; psad_dl:2;) alert tcp $HOME_NET any <> $EXTERNAL_NET 8875 (msg:"P2P Napster Server Login communication attempt"; flags:S; classtype:policy-violation; sid:565; psad_id:100095; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 1214 (msg:"P2P Fastrack kazaa/morpheus communication attempt"; flags:S; reference:url,www.kazaa.com; reference:url,www.musiccity.com/technology.htm; classtype:policy-violation; sid:1383; psad_id:100096; psad_dl:2;) alert tcp $HOME_NET any -> $EXTERNAL_NET 6881:6889 (msg:"P2P BitTorrent communication attempt"; flags:S;; classtype:policy-violation; sid:2181; psad_id:100097; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 4242 (msg:"P2P eDonkey transfer attempt"; flags:S; reference:url,www.kom.e-technik.tu-darmstadt.de/publications/abstracts/HB02-1.html; classtype:policy-violation; sid:2586; psad_id:100098; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $EXTERNAL_NET 4711 (msg:"P2P eDonkey communication attempt"; flags:S; reference:url,www.emule-project.net; classtype:policy-violation; sid:2587; psad_id:100099; psad_dl:2;) ### ftp.rules alert tcp $EXTERNAL_NET any -> $HOME_NET 3535 (msg:"FTP Yak! FTP server communication attempt"; flags:S; reference:bugtraq,9072; classtype:suspicious-login; psad_id:100100; psad_dl:2; psad_derived_sids:2334,2335;) ### experimental.rules ### porn.rules ### sql.rules ### pop2.rules ### imap.rules ### smtp.rules ### web-coldfusion.rules ### local.rules ### bad-traffic.rules alert tcp $EXTERNAL_NET any <> $HOME_NET 0 (msg:"BAD-TRAFFIC tcp port 0 traffic"; classtype:misc-activity; sid:524; psad_id:100101; psad_dl:2;) alert udp $EXTERNAL_NET any <> $HOME_NET 0 (msg:"BAD-TRAFFIC udp port 0 traffic"; reference:bugtraq,576; reference:cve,1999-0675; reference:nessus,10074; classtype:misc-activity; sid:525; psad_id:100102; psad_dl:2;) ### note that psad derives the payload length of a TCP packet from the ### IP header, so it treats TCP SYN packets (which contain options) as ### being 44 bytes longer (this is the maximum possible) than other ### TCP packets. alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"BAD-TRAFFIC data in TCP SYN packet"; psad_dsize:>20; flags:S; reference:url,www.cert.org/incident_notes/IN-99-07.html; classtype:misc-activity; sid:207; psad_id:100000; psad_dl:2;) ### traffic may be logged over the loopback interface via iptables ### much more readily than running Snort on a loopback interface, ### so disable this sig. #alert ip any any <> 127.0.0.0/8 any (msg:"BAD-TRAFFIC loopback traffic"; reference:url,rr.sans.org/firewall/egress.php; classtype:bad-unknown; sid:100; psad_id:100000; psad_dl:2;) alert ip any any -> any any (msg:"BAD-TRAFFIC same SRC/DST"; sameip; reference:bugtraq,2666; reference:cve,1999-0016; reference:url,www.cert.org/advisories/CA-1997-28.html; classtype:bad-unknown; sid:527; psad_id:100103; psad_dl:2;) alert ip $EXTERNAL_NET any -> $HOME_NET any (msg:"BAD-TRAFFIC 0 ttl"; ttl:0; reference:url,support.microsoft.com/default.aspx?scid=kb\;EN-US\;q138268; reference:url,www.isi.edu/in-notes/rfc1122.txt; classtype:misc-activity; sid:1321; psad_id:100104; psad_dl:2;) #alert ip $EXTERNAL_NET any -> $HOME_NET any (msg:"BAD-TRAFFIC Unassigned/Reserved IP protocol"; ip_proto:>134; reference:url,www.iana.org/assignments/protocol-numbers; classtype:non-standard-protocol; sid:1627; psad_id:100105; psad_dl:2;) alert tcp any any -> [232.0.0.0/8,233.0.0.0/8,239.0.0.0/8] any (msg:"BAD-TRAFFIC syn to multicast address"; flags:S; classtype:bad-unknown; sid:1431; psad_id:100106; psad_dl:2;) #alert ip any any -> any any (msg:"BAD-TRAFFIC IP Proto 53 SWIPE"; ip_proto:53; reference:bugtraq,8211; reference:cve,2003-0567; classtype:non-standard-protocol; sid:2186; psad_id:100107; psad_dl:2;) #alert ip any any -> any any (msg:"BAD-TRAFFIC IP Proto 55 IP Mobility"; ip_proto:55; reference:bugtraq,8211; reference:cve,2003-0567; classtype:non-standard-protocol; sid:2187; psad_id:100108; psad_dl:2;) #alert ip any any -> any any (msg:"BAD-TRAFFIC IP Proto 77 Sun ND"; ip_proto:77; reference:bugtraq,8211; reference:cve,2003-0567; classtype:non-standard-protocol; sid:2188; psad_id:100109; psad_dl:2;) #alert ip any any -> any any (msg:"BAD-TRAFFIC IP Proto 103 PIM"; ip_proto:103; reference:bugtraq,8211; reference:cve,2003-0567; classtype:non-standard-protocol; sid:2189; psad_id:100110; psad_dl:2;) ### dos.rules #alert ip $EXTERNAL_NET any -> $HOME_NET any (msg:"DOS Jolt attack"; dsize:408; fragbits:M; reference:cve,1999-0345; classtype:attempted-dos; sid:216; psad_id:100000; psad_dl:2;) #alert udp $EXTERNAL_NET any -> $HOME_NET any (msg:"DOS Teardrop attack"; fragbits:M; id:242; reference:bugtraq,124; reference:cve,1999-0015; reference:nessus,10279; reference:url,www.cert.org/advisories/CA-1997-28.html; classtype:attempted-dos; sid:217; psad_id:100000; psad_dl:2;) alert tcp $EXTERNAL_NET any <> $HOME_NET any (msg:"DOS NAPTHA"; flags:S; id:413; seq:6060842; reference:bugtraq,2022; reference:cve,2000-1039; reference:url,razor.bindview.com/publish/advisories/adv_NAPTHA.html; reference:url,www.cert.org/advisories/CA-2000-21.html; reference:url,www.microsoft.com/technet/security/bulletin/MS00-091.mspx; classtype:attempted-dos; sid:275; psad_id:100111; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 7070 (msg:"DOS Real Audio Server communication attempt"; flags:S; reference:arachnids,411; reference:bugtraq,1288; reference:cve,2000-0474; classtype:attempted-dos; psad_id:100112; psad_dl:2; psad_derived_sids:276,277;) alert tcp $EXTERNAL_NET any -> $HOME_NET 617 (msg:"DOS arkiea backup communication attempt"; flags:S; reference:arachnids,261; reference:bugtraq,662; reference:cve,1999-0788; classtype:attempted-dos; sid:282; psad_id:100113; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 3372 (msg:"DOS MSDTC communication attempt"; flags:S; reference:bugtraq,4006; reference:cve,2002-0224; reference:nessus,10939; classtype:attempted-dos; sid:1408; psad_id:100114; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 6004 (msg:"DOS iParty DOS attempt"; flags:S; reference:bugtraq,6844; reference:cve,1999-1566; classtype:misc-attack; sid:1605; psad_id:100115; psad_dl:2;) alert tcp $EXTERNAL_NET any -> $HOME_NET 6789:6790 (msg:"DOS DB2 dos communication attempt"; flags:S; reference:bugtraq,3010; reference:cve,2001-1143; reference:nessus,10871; classtype:denial-of-service; sid:1641; psad_id:100116; psad_dl:2;) ### web-client.rules ### web-cgi.rules ### other-ids.rules ### pop3.rules ### multimedia.rules ### rservices.rules ### web-iis.rules ### mysql.rules ### icmp-info.rules alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP IRDP router advertisement"; itype:9; reference:arachnids,173; reference:bugtraq,578; reference:cve,1999-0875; classtype:misc-activity; sid:363; psad_id:100117; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP IRDP router selection"; itype:10; reference:arachnids,174; reference:bugtraq,578; reference:cve,1999-0875; classtype:misc-activity; sid:364; psad_id:100118; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP PING LINUX/*BSD"; dsize:8; id:13170; itype:8; reference:arachnids,447; classtype:misc-activity; sid:375; psad_id:100119; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP PING Sun Solaris"; dsize:8; itype:8; reference:arachnids,448; classtype:misc-activity; sid:381; psad_id:100120; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP traceroute"; itype:8; ttl:1; reference:arachnids,118; classtype:attempted-recon; sid:385; psad_id:100121; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP PING"; icode:0; itype:8; classtype:misc-activity; sid:384; psad_id:100122; psad_dl:2;) alert icmp $HOME_NET any -> $EXTERNAL_NET any (msg:"ICMP Address Mask Reply"; icode:0; itype:18; classtype:misc-activity; sid:386; psad_id:100123; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Address Mask Reply undefined code"; icode:>0; itype:18; classtype:misc-activity; sid:387; psad_id:100124; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Address Mask Request"; icode:0; itype:17; classtype:misc-activity; sid:388; psad_id:100125; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Address Mask Request undefined code"; icode:>0; itype:17; classtype:misc-activity; sid:389; psad_id:100126; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Alternate Host Address"; icode:0; itype:6; classtype:misc-activity; sid:390; psad_id:100127; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Alternate Host Address undefined code"; icode:>0; itype:6; classtype:misc-activity; sid:391; psad_id:100128; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Datagram Conversion Error"; icode:0; itype:31; classtype:misc-activity; sid:392; psad_id:100129; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Datagram Conversion Error undefined code"; icode:>0; itype:31; classtype:misc-activity; sid:393; psad_id:100130; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Destination Host Unknown"; icode:7; itype:3; classtype:misc-activity; sid:394; psad_id:100131; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Destination Network Unknown"; icode:6; itype:3; classtype:misc-activity; sid:395; psad_id:100132; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Fragmentation Needed and DF bit was set"; icode:4; itype:3; classtype:misc-activity; sid:396; psad_id:100133; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Host Precedence Violation"; icode:14; itype:3; classtype:misc-activity; sid:397; psad_id:100134; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Host Unreachable for Type of Service"; icode:12; itype:3; classtype:misc-activity; sid:398; psad_id:100135; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Host Unreachable"; icode:1; itype:3; classtype:misc-activity; sid:399; psad_id:100136; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Network Unreachable for Type of Service"; icode:11; itype:3; classtype:misc-activity; sid:400; psad_id:100137; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Network Unreachable"; icode:0; itype:3; classtype:misc-activity; sid:401; psad_id:100138; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Port Unreachable"; icode:3; itype:3; classtype:misc-activity; sid:402; psad_id:100139; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Precedence Cutoff in effect"; icode:15; itype:3; classtype:misc-activity; sid:403; psad_id:100140; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Protocol Unreachable"; icode:2; itype:3; classtype:misc-activity; sid:404; psad_id:100141; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Source Host Isolated"; icode:8; itype:3; classtype:misc-activity; sid:405; psad_id:100142; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable Source Route Failed"; icode:5; itype:3; classtype:misc-activity; sid:406; psad_id:100143; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Destination Unreachable cndefined code"; icode:>15; itype:3; classtype:misc-activity; sid:407; psad_id:100144; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Echo Reply"; icode:0; itype:0; classtype:misc-activity; sid:408; psad_id:100145; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Echo Reply undefined code"; icode:>0; itype:0; classtype:misc-activity; sid:409; psad_id:100146; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Fragment Reassembly Time Exceeded"; icode:1; itype:11; classtype:misc-activity; sid:410; psad_id:100147; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP IPV6 I-Am-Here"; icode:0; itype:34; classtype:misc-activity; sid:411; psad_id:100148; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP IPV6 I-Am-Here undefined code"; icode:>0; itype:34; classtype:misc-activity; sid:412; psad_id:100149; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP IPV6 Where-Are-You"; icode:0; itype:33; classtype:misc-activity; sid:413; psad_id:100150; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP IPV6 Where-Are-You undefined code"; icode:>0; itype:33; classtype:misc-activity; sid:414; psad_id:100151; psad_dl:2;) alert icmp $HOME_NET any -> $EXTERNAL_NET any (msg:"ICMP Information Reply"; icode:0; itype:16; classtype:misc-activity; sid:415; psad_id:100152; psad_dl:2;) alert icmp $HOME_NET any -> $EXTERNAL_NET any (msg:"ICMP Information Reply undefined code"; icode:>0; itype:16; classtype:misc-activity; sid:416; psad_id:100153; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Information Request"; icode:0; itype:15; classtype:misc-activity; sid:417; psad_id:100154; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Information Request undefined code"; icode:>0; itype:15; classtype:misc-activity; sid:418; psad_id:100155; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Mobile Host Redirect"; icode:0; itype:32; classtype:misc-activity; sid:419; psad_id:100156; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Mobile Host Redirect undefined code"; icode:>0; itype:32; classtype:misc-activity; sid:420; psad_id:100157; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Mobile Registration Reply"; icode:0; itype:36; classtype:misc-activity; sid:421; psad_id:100158; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Mobile Registration Reply undefined code"; icode:>0; itype:36; classtype:misc-activity; sid:422; psad_id:100159; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Mobile Registration Request"; icode:0; itype:35; classtype:misc-activity; sid:423; psad_id:100160; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Mobile Registration Request undefined code"; icode:>0; itype:35; classtype:misc-activity; sid:424; psad_id:100161; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Parameter Problem Bad Length"; icode:2; itype:12; classtype:misc-activity; sid:425; psad_id:100162; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Parameter Problem Missing a Required Option"; icode:1; itype:12; classtype:misc-activity; sid:426; psad_id:100163; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Parameter Problem Unspecified Error"; icode:0; itype:12; classtype:misc-activity; sid:427; psad_id:100164; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Parameter Problem undefined Code"; icode:>2; itype:12; classtype:misc-activity; sid:428; psad_id:100165; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Photuris Reserved"; icode:0; itype:40; classtype:misc-activity; sid:429; psad_id:100166; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Photuris Unknown Security Parameters Index"; icode:1; itype:40; classtype:misc-activity; sid:430; psad_id:100167; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Photuris Valid Security Parameters, But Authentication Failed"; icode:2; itype:40; classtype:misc-activity; sid:431; psad_id:100168; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Photuris Valid Security Parameters, But Decryption Failed"; icode:3; itype:40; classtype:misc-activity; sid:432; psad_id:100169; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Photuris undefined code!"; icode:>3; itype:40; classtype:misc-activity; sid:433; psad_id:100170; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Redirect for TOS and Host"; icode:3; itype:5; classtype:misc-activity; sid:436; psad_id:100171; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Redirect for TOS and Network"; icode:2; itype:5; classtype:misc-activity; sid:437; psad_id:100172; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Redirect undefined code"; icode:>3; itype:5; classtype:misc-activity; sid:438; psad_id:100173; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Reserved for Security Type 19"; icode:0; itype:19; classtype:misc-activity; sid:439; psad_id:100174; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Reserved for Security Type 19 undefined code"; icode:>0; itype:19; classtype:misc-activity; sid:440; psad_id:100175; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Router Advertisement"; icode:0; itype:9; reference:arachnids,173; classtype:misc-activity; sid:441; psad_id:100176; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Router Selection"; icode:0; itype:10; reference:arachnids,174; classtype:misc-activity; sid:443; psad_id:100177; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP SKIP"; icode:0; itype:39; classtype:misc-activity; sid:445; psad_id:100178; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP SKIP undefined code"; icode:>0; itype:39; classtype:misc-activity; sid:446; psad_id:100179; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Source Quench undefined code"; icode:>0; itype:4; classtype:misc-activity; sid:448; psad_id:100180; psad_dl:2;) alert icmp $HOME_NET any -> $EXTERNAL_NET any (msg:"ICMP Time-To-Live Exceeded in Transit"; icode:0; itype:11; classtype:misc-activity; sid:449; psad_id:100181; psad_dl:2;) alert icmp $HOME_NET any -> $EXTERNAL_NET any (msg:"ICMP Time-To-Live Exceeded in Transit undefined code"; icode:>1; itype:11; classtype:misc-activity; sid:450; psad_id:100182; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Timestamp Reply"; icode:0; itype:14; classtype:misc-activity; sid:451; psad_id:100183; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Timestamp Reply undefined code"; icode:>0; itype:14; classtype:misc-activity; sid:452; psad_id:100184; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Timestamp Request"; icode:0; itype:13; classtype:misc-activity; sid:453; psad_id:100185; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Timestamp Request undefined code"; icode:>0; itype:13; classtype:misc-activity; sid:454; psad_id:100186; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Traceroute"; icode:0; itype:30; classtype:misc-activity; sid:456; psad_id:100187; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP Traceroute undefined code"; icode:>0; itype:30; classtype:misc-activity; sid:457; psad_id:100188; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP unassigned type 1"; icode:0; itype:1; classtype:misc-activity; sid:458; psad_id:100189; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP unassigned type 1 undefined code"; itype:1; classtype:misc-activity; sid:459; psad_id:100190; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP unassigned type 2"; icode:0; itype:2; classtype:misc-activity; sid:460; psad_id:100191; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP unassigned type 2 undefined code"; itype:2; classtype:misc-activity; sid:461; psad_id:100192; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP unassigned type 7"; icode:0; itype:7; classtype:misc-activity; sid:462; psad_id:100193; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP unassigned type 7 undefined code"; itype:7; classtype:misc-activity; sid:463; psad_id:100194; psad_dl:2;) alert icmp $EXTERNAL_NET any -> $HOME_NET any (msg:"ICMP PING undefined code"; icode:>0; itype:8; classtype:misc-activity; sid:365; psad_id:100195; psad_dl:2;) ### web-php.rules ### telnet.rules ### netbios.rules ### nntp.rules ### attack-responses.rules ### tftp.rules ### web-attacks.rules psad-2.2.1/protocols0000664000175000017500000000556512071203757012555 0ustar mbrmbr# Internet (IP) protocols # # Updated from http://www.iana.org/assignments/protocol-numbers and other # sources. # New protocols will be added on request if they have been officially # assigned by IANA and are not historical. # If you need a huge list of used numbers please install the nmap package. ip 0 IP # internet protocol, pseudo protocol number #hopopt 0 HOPOPT # IPv6 Hop-by-Hop Option [RFC1883] icmp 1 ICMP # internet control message protocol igmp 2 IGMP # Internet Group Management ggp 3 GGP # gateway-gateway protocol ipencap 4 IP-ENCAP # IP encapsulated in IP (officially ``IP'') st 5 ST # ST datagram mode tcp 6 TCP # transmission control protocol egp 8 EGP # exterior gateway protocol igp 9 IGP # any private interior gateway (Cisco) pup 12 PUP # PARC universal packet protocol udp 17 UDP # user datagram protocol hmp 20 HMP # host monitoring protocol xns-idp 22 XNS-IDP # Xerox NS IDP rdp 27 RDP # "reliable datagram" protocol iso-tp4 29 ISO-TP4 # ISO Transport Protocol class 4 [RFC905] dccp 33 DCCP # Datagram Congestion Control Prot. [RFC4340] xtp 36 XTP # Xpress Transfer Protocol ddp 37 DDP # Datagram Delivery Protocol idpr-cmtp 38 IDPR-CMTP # IDPR Control Message Transport ipv6 41 IPv6 # Internet Protocol, version 6 ipv6-route 43 IPv6-Route # Routing Header for IPv6 ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6 idrp 45 IDRP # Inter-Domain Routing Protocol rsvp 46 RSVP # Reservation Protocol gre 47 GRE # General Routing Encapsulation esp 50 IPSEC-ESP # Encap Security Payload [RFC2406] ah 51 IPSEC-AH # Authentication Header [RFC2402] skip 57 SKIP # SKIP ipv6-icmp 58 IPv6-ICMP # ICMP for IPv6 ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6 ipv6-opts 60 IPv6-Opts # Destination Options for IPv6 rspf 73 RSPF CPHB # Radio Shortest Path First (officially CPHB) vmtp 81 VMTP # Versatile Message Transport eigrp 88 EIGRP # Enhanced Interior Routing Protocol (Cisco) ospf 89 OSPFIGP # Open Shortest Path First IGP ax.25 93 AX.25 # AX.25 frames ipip 94 IPIP # IP-within-IP Encapsulation Protocol etherip 97 ETHERIP # Ethernet-within-IP Encapsulation [RFC3378] encap 98 ENCAP # Yet Another IP encapsulation [RFC1241] # 99 # any private encryption scheme pim 103 PIM # Protocol Independent Multicast ipcomp 108 IPCOMP # IP Payload Compression Protocol vrrp 112 VRRP # Virtual Router Redundancy Protocol [RFC5798] l2tp 115 L2TP # Layer Two Tunneling Protocol [RFC2661] isis 124 ISIS # IS-IS over IPv4 sctp 132 SCTP # Stream Control Transmission Protocol fc 133 FC # Fibre Channel mobility-header 135 Mobility-Header # Mobility Support for IPv6 [RFC3775] udplite 136 UDPLite # UDP-Lite [RFC3828] mpls-in-ip 137 MPLS-in-IP # MPLS-in-IP [RFC4023] manet 138 # MANET Protocols [RFC5498] hip 139 HIP # Host Identity Protocol shim6 140 Shim6 # Shim6 Protocol [RFC5533] wesp 141 WESP # Wrapped Encapsulating Security Payload rohc 142 ROHC # Robust Header Compression psad-2.2.1/selinux/0000775000175000017500000000000012071203757012262 5ustar mbrmbrpsad-2.2.1/selinux/README0000664000175000017500000000025712071203757013146 0ustar mbrmbr This directory contains policy files for making psad compatible with SELinux. These policies were contributed on 01/23/09 by Miroslav Grepl () at Red Hat. psad-2.2.1/selinux/psad.if0000664000175000017500000001433212071203757013534 0ustar mbrmbr## Psad SELinux policy ######################################## ## ## Execute a domain transition to run psad. ## ## ## ## Domain allowed to transition. ## ## # interface(`psad_domtrans',` gen_require(` type psad_t, psad_exec_t; ') domtrans_pattern($1, psad_exec_t, psad_t) ') ######################################## ## ## Read and write psad UDP sockets. ## ## ## ## Domain allowed access. ## ## # interface(`psad_rw_udp_sockets',` gen_require(` type psad_t; ') allow $1 psad_t:udp_socket { read write }; ') ######################################## ## ## Read and write psad packet sockets. ## ## ## ## Domain allowed access. ## ## # interface(`psad_rw_packet_sockets',` gen_require(` type psad_t; ') allow $1 psad_t:packet_socket { read write }; ') ######################################## ## ## Send a generic signal to psad ## ## ## ## Domain allowed access. ## ## # interface(`psad_signal',` gen_require(` type psad_t; ') allow $1 psad_t:process signal; ') ####################################### ## ## Send a null signal to psad. ## ## ## ## Domain allowed access. ## ## # interface(`psad_signull',` gen_require(` type psad_t; ') allow $1 psad_t:process signull; ') ######################################## ## ## Read psad etc configuration files. ## ## ## ## Domain allowed access. ## ## ## # interface(`psad_read_etc',` gen_require(` type psad_etc_t; ') files_search_etc($1) read_files_pattern($1, psad_etc_t, psad_etc_t) ') ######################################## ## ## Manage psad etc configuration files. ## ## ## ## Domain allowed access. ## ## ## # interface(`psad_manage_etc',` gen_require(` type psad_etc_t; ') files_search_etc($1) manage_dirs_pattern($1, psad_etc_t, psad_etc_t) manage_files_pattern($1, psad_etc_t, psad_etc_t) ') ######################################## ## ## Read psad PID files. ## ## ## ## Domain allowed access. ## ## ## # interface(`psad_read_pid_files',` gen_require(` type psad_var_run_t; ') files_search_pids($1) read_files_pattern($1, psad_var_run_t, psad_var_run_t) ') ######################################## ## ## Read psad PID files. ## ## ## ## Domain allowed access. ## ## ## # interface(`psad_rw_pid_files',` gen_require(` type psad_var_run_t; ') files_search_pids($1) rw_files_pattern($1, psad_var_run_t, psad_var_run_t) ') ######################################## ## ## Allow the specified domain to read psad's log files. ## ## ## ## Domain allowed access. ## ## ## ## # interface(`psad_read_log',` gen_require(` type psad_var_log_t; ') logging_search_logs($1) list_dirs_pattern($1, psad_var_log_t, psad_var_log_t) read_files_pattern($1, psad_var_log_t, psad_var_log_t) ') ######################################## ## ## Allow the specified domain to append to psad's log files. ## ## ## ## Domain allowed access. ## ## ## ## # interface(`psad_append_log',` gen_require(` type psad_var_log_t; ') logging_search_logs($1) list_dirs_pattern($1, psad_var_log_t, psad_var_log_t) append_files_pattern($1, psad_var_log_t, psad_var_log_t) ') ######################################## ## ## Read and write psad fifo files. ## ## ## ## Domain allowed access. ## ## # interface(`psad_rw_fifo_file',` gen_require(` type psad_t; ') files_search_var_lib($1) search_dirs_pattern($1, psad_var_lib_t, psad_var_lib_t) rw_fifo_files_pattern($1, psad_var_lib_t, psad_var_lib_t) ') ####################################### ## ## Read and write psad tmp files. ## ## ## ## Domain allowed access. ## ## # interface(`psad_rw_tmp_files',` gen_require(` type psad_tmp_t; ') files_search_tmp($1) rw_files_pattern($1, psad_tmp_t, psad_tmp_t) ') ######################################## ## ## All of the rules required to administrate ## an psad environment ## ## ## ## Domain allowed access. ## ## ## ## ## The role to be allowed to manage the syslog domain. ## ## ## # interface(`psad_admin',` gen_require(` type psad_t, psad_var_run_t, psad_var_log_t; type psad_initrc_exec_t, psad_var_lib_t; type psad_tmp_t; ') allow $1 psad_t:process { ptrace signal_perms }; ps_process_pattern($1, psad_t) init_labeled_script_domtrans($1, psad_initrc_exec_t) domain_system_change_exemption($1) role_transition $2 psad_initrc_exec_t system_r; allow $2 system_r; files_search_etc($1) admin_pattern($1, psad_etc_t) files_search_pids($1) admin_pattern($1, psad_var_run_t) logging_search_logs($1) admin_pattern($1, psad_var_log_t) files_search_var_lib($1) admin_pattern($1, psad_var_lib_t) files_search_tmp($1) admin_pattern($1, psad_tmp_t) ') psad-2.2.1/selinux/psad.te0000664000175000017500000000457512071203757013556 0ustar mbrmbrpolicy_module(psad,1.0.0) ######################################## # # Declarations # type psad_t; type psad_exec_t; init_daemon_domain(psad_t, psad_exec_t) type psad_initrc_exec_t; init_script_file(psad_initrc_exec_t) # config files type psad_etc_t; files_config_file(psad_etc_t) # var/lib files type psad_var_lib_t; files_type(psad_var_lib_t) # log files type psad_var_log_t; logging_log_file(psad_var_log_t) # pid files type psad_var_run_t; files_pid_file(psad_var_run_t) # tmp files type psad_tmp_t; files_tmp_file(psad_tmp_t) ######################################## # # psad local policy # allow psad_t self:capability { net_admin net_raw setuid setgid dac_override }; dontaudit psad_t self:capability { sys_tty_config }; allow psad_t self:process { signal signull }; allow psad_t self:fifo_file rw_fifo_file_perms; allow psad_t self:rawip_socket create_socket_perms; # config files read_files_pattern(psad_t,psad_etc_t,psad_etc_t) list_dirs_pattern(psad_t,psad_etc_t,psad_etc_t) # pid file manage_files_pattern(psad_t, psad_var_run_t,psad_var_run_t) manage_sock_files_pattern(psad_t, psad_var_run_t,psad_var_run_t) files_pid_filetrans(psad_t,psad_var_run_t, { file sock_file }) # log files manage_files_pattern(psad_t, psad_var_log_t, psad_var_log_t) manage_dirs_pattern(psad_t, psad_var_log_t, psad_var_log_t) logging_log_filetrans(psad_t,psad_var_log_t, { file dir }) # tmp files manage_dirs_pattern(psad_t,psad_tmp_t,psad_tmp_t) manage_files_pattern(psad_t,psad_tmp_t,psad_tmp_t) files_tmp_filetrans(psad_t, psad_tmp_t, { file dir }) # /var/lib files search_dirs_pattern(psad_t, psad_var_lib_t, psad_var_lib_t) manage_fifo_files_pattern(psad_t, psad_var_lib_t, psad_var_lib_t) kernel_read_system_state(psad_t) kernel_read_network_state(psad_t) #kernel_read_kernel_sysctls(psad_t) kernel_read_net_sysctls(psad_t) corecmd_exec_shell(psad_t) corecmd_exec_bin(psad_t) auth_use_nsswitch(psad_t) corenet_tcp_connect_whois_port(psad_t) dev_read_urand(psad_t) files_read_etc_runtime_files(psad_t) fs_getattr_all_fs(psad_t) libs_use_ld_so(psad_t) libs_use_shared_libs(psad_t) miscfiles_read_localization(psad_t) logging_read_generic_logs(psad_t) logging_read_syslog_config(psad_t) logging_send_syslog_msg(psad_t) #sysnet_domtrans_ifconfig(psad_t) sysnet_exec_ifconfig(psad_t) iptables_domtrans(psad_t) optional_policy(` mta_send_mail(psad_t) mta_read_queue(psad_t) ') permissive psad_t; psad-2.2.1/selinux/psad.fc0000664000175000017500000000113212071203757013520 0ustar mbrmbr /etc/rc\.d/init\.d/psad -- gen_context(system_u:object_r:psad_initrc_exec_t,s0) /etc/psad(/.*)? gen_context(system_u:object_r:psad_etc_t,s0) /usr/sbin/psad -- gen_context(system_u:object_r:psad_exec_t,s0) #/usr/sbin/psadwatchd -- gen_context(system_u:object_r:psadwatchd_exec_t,s0) #/usr/sbin/kmsgsd -- gen_context(system_u:object_r:kmsgsd_exec_t,s0) /var/run/psad(/.*)? gen_context(system_u:object_r:psad_var_run_t,s0) /var/lib/psad(/.*)? gen_context(system_u:object_r:psad_var_lib_t,s0) /var/log/psad(/.*)? gen_context(system_u:object_r:psad_var_log_t,s0) psad-2.2.1/fwcheck_psad.pl0000775000175000017500000005116112071203757013560 0ustar mbrmbr#!/usr/bin/perl -w # ############################################################################### # # File: fwcheck_psad.pl (/usr/sbin/fwcheck_psad) # # Purpose: To parse the iptables ruleset on the underlying system to see if # iptables has been configured to log and block unwanted packets by # default. This program is called by psad, but can also be executed # manually from the command line. # # Author: Michael Rash (mbr@cipherdyne.org) # # Credits: (see the CREDITS file bundled with the psad sources.) # # Copyright (C) 1999-2012 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ############################################################################### # use Getopt::Long 'GetOptions'; use strict; ### default psad config file. my $config_file = '/etc/psad/psad.conf'; ### config hash my %config = (); my $override_config_str = ''; ### commands hash my %cmds = (); ### fw search string array my @fw_search = (); my $help = 0; my $test_mode = 0; my $fw_analyze = 0; my $fw_file = ''; my $fw_search_all = 1; my $no_fw_search_all = 0; my $log_and_drop_table = 'filter'; my $enable_ipv6 = 0; my $psad_lib_dir = ''; &usage(1) unless (GetOptions( 'config=s' => \$config_file, # Specify path to configuration file. 'fw-file=s' => \$fw_file, # Analyze ruleset contained within # $fw_file instead of a running # policy. 'fw-analyze' => \$fw_analyze, # Analyze the local iptables ruleset # and exit. 'no-fw-search-all' => \$no_fw_search_all, # looking for specific log # prefixes 'Lib-dir=s' => \$psad_lib_dir,# Specify path to psad lib directory. 'Override-config=s' => \$override_config_str, 'test-mode' => \$test_mode, # Used by the test suite. 'help' => \$help, # Display help. )); &usage(0) if $help; $fw_search_all = 0 if $no_fw_search_all; ### Everthing after this point must be executed as root. $< == 0 and $> == 0 or die '[*] fwcheck_psad.pl: You must be root (or equivalent ', "UID 0 account) to execute fwcheck_psad.pl! Exiting.\n"; if ($fw_file) { die "[*] iptables dump file: $fw_file does not exist." unless -e $fw_file; } ### import any override config files first &import_override_configs() if $override_config_str; ### import psad.conf &import_config($config_file); $enable_ipv6 = 1 if $config{'ENABLE_IPV6_DETECTION'} eq 'Y'; ### import FW_MSG_SEARCH strings &import_fw_search($config_file); ### expand any embedded vars within config values &expand_vars(); ### check to make sure the commands specified in the config section ### are in the right place, and attempt to correct automatically if not. &check_commands({}); ### import psad perl modules &import_psad_perl_modules(); open FWCHECK, "> $config{'FW_CHECK_FILE'}" or die "[*] Could not ", "open $config{'FW_CHECK_FILE'}: $!"; unless ($fw_search_all) { print FWCHECK "[+] Available search strings in $config_file:\n\n"; print FWCHECK " $_\n" for @fw_search; print FWCHECK "\n[+] Additional search strings can be added be specifying more\n", " FW_MSG_SEARCH lines in $config_file\n\n"; } ### check the iptables policy my $rv = &fw_check(); close FWCHECK; exit $rv; #========================== end main ========================= sub fw_check() { ### only send a firewall config alert if we really need to. my $send_alert = 0; my $forward_chain_rv = 1; my $input_chain_rv = &ipt_chk_chain('INPUT', $cmds{'iptables'}); unless ($input_chain_rv) { &print_fw_help('INPUT', $cmds{'iptables'}); $send_alert = 1; } if ($enable_ipv6) { my $tmp_rv = &ipt_chk_chain('INPUT', $cmds{'ip6tables'}); unless ($tmp_rv) { &print_fw_help('INPUT', $cmds{'ip6tables'}); $send_alert = 1; $input_chain_rv = 0; } } ### we don't always have more than one interface or forwarding ### turned on, so we only check the FORWARD iptables chain if we ### do and we have multiple interfaces on the box. if (&check_forwarding()) { $forward_chain_rv = &ipt_chk_chain('FORWARD', $cmds{'iptables'}); unless ($forward_chain_rv) { &print_fw_help('FORWARD', $cmds{'iptables'}); $send_alert = 1; } } if ($send_alert) { unless ($fw_search_all) { print FWCHECK "\n[+] NOTE: IPTables::Parse does not yet parse user defined chains and so\n", " it is possible your firewall config is compatible with psad anyway.\n"; } unless ($config{'ALERTING_METHODS'} =~ /no.?e?mail/i or $test_mode) { &send_mail("[psad-status] firewall setup warning on " . "$config{'HOSTNAME'}!", $config{'FW_CHECK_FILE'}, $config{'EMAIL_ADDRESSES'}, $cmds{'mail'} ); } if ($fw_analyze and not $test_mode) { print "[-] Errors found in firewall config.\n"; print " emailed to ", "$config{'EMAIL_ADDRESSES'}\n"; } } else { print FWCHECK "[+] The iptables ruleset on $config{'HOSTNAME'} will log and block unwanted\n", " packets in both the INPUT and FORWARD chains. Firewall config success!\n"; if ($fw_analyze) { print "[+] Firewall config looks good.\n", "[+] Completed check of firewall ruleset.\n"; } } if ($fw_analyze) { print "[+] Results in $config{'FW_CHECK_FILE'}\n", "[+] Exiting.\n"; } return $forward_chain_rv and $input_chain_rv; } sub print_fw_help() { my ($chain, $ipt_bin) = @_; print FWCHECK "[-] You may just need to add a default logging rule to the $ipt_bin\n", " '$log_and_drop_table' '$chain' chain on $config{'HOSTNAME'}. For more information,\n", " see the file \"FW_HELP\" in the psad sources directory or visit:\n\n", " http://www.cipherdyne.org/psad/docs/fwconfig.html\n\n"; return; } sub check_forwarding() { ### check to see if there are multiple interfaces on the ### machine and return false if not since the machine will ### not be able to forward packets anyway (e.g. desktop ### machines). Also return false if forwarding is turned ### off (we have to trust the machine config is as the ### admin wants it). my $forwarding; if (-e $config{'PROC_FORWARD_FILE'}) { open F, "< $config{'PROC_FORWARD_FILE'}" or die "[*] Could not open $config{'PROC_FORWARD_FILE'}: $!"; $forwarding = ; close F; chomp $forwarding; return 0 if $forwarding == 0; } else { die "[*] Make sure the path to the IP forwarding file correct.\n", " The PROC_FORWARD_FILE in $config_file points to\n", " $config{'PROC_FORWARD_FILE'}"; } if ($config{'IFCFGTYPE'} =~ /iproute2/i) { open IFC, "$cmds{'ip'} addr |" or die "[*] Could not ", "execute: $cmds{'ip'} addr: $!"; my @if_out = ; close IFC; my $intf_name = ''; my $intf_inet_count = 0; my $num_intf = 0; for my $line (@if_out) { if ($line =~ /^\d+:\s+(\S+)\:\s 0) { $num_intf++; } $intf_inet_count = 0; next; } next if $intf_name eq 'lo'; next if $intf_name =~ /dummy/i; if ($line =~ /inet\s+/i) { $intf_inet_count++; } } if ($intf_inet_count > 0) { $num_intf++; } if ($num_intf < 2) { return 0; } } else { open IFC, "$cmds{'ifconfig'} -a |" or die "[*] Could not ", "execute: $cmds{'ifconfig'} -a: $!"; my @if_out = ; close IFC; my $num_intf = 0; for my $line (@if_out) { if ($line =~ /inet\s+/i and $line !~ /127\.0\.0\.1/) { $num_intf++; } } if ($num_intf < 2) { return 0; } } return 1; } sub ipt_chk_chain() { my ($chain, $ipt_bin) = @_; my $rv = 1; my $ipt = new IPTables::Parse 'iptables' => $ipt_bin or die "[*] Could not acquire IPTables::Parse object: $!"; if ($fw_analyze) { print "[+] Parsing $ipt_bin $chain chain rules.\n"; } if ($fw_search_all) { ### we are not looking for specific log ### prefixes, but we need _some_ logging rule my ($ipt_log, $ipt_rv) = $ipt->default_log($log_and_drop_table, $chain, $fw_file); return 0 unless $ipt_rv; if (defined $ipt_log->{'all'}) { ### found real default logging rule (assuming it is above a default ### drop rule, which we are not actually checking here). return 1; } else { my $log_protos = ''; my $no_log_protos = ''; for my $proto (qw(tcp udp icmp)) { if (defined $ipt_log->{$proto}) { $log_protos .= "$proto/"; } else { $no_log_protos .= "$proto/"; } } $log_protos =~ s|/$||; $no_log_protos =~ s|/$||; if ($log_protos) { print FWCHECK "[-] Your firewall config on $config{'HOSTNAME'} includes logging rules for\n", " $log_protos but not for $no_log_protos in the $chain chain.\n\n"; return 0; } else { print FWCHECK "[-] Could not determine whether the $ipt_bin $chain chain is configured with\n", " a default logging rule on $config{'HOSTNAME'}.\n\n"; return 0; } } } else { ### we are looking for specific log prefixes. ### for now we are only looking at the filter table, so if ### the iptables ruleset includes the log and drop rules in ### a user defined chain then psad will not see this. my ($ld_hr, $ipt_rv) = $ipt->default_drop($log_and_drop_table, $chain, $fw_file); return 0 unless $ipt_rv; my $num_keys = 0; if (defined $ld_hr and keys %$ld_hr) { $num_keys++; my @protos; if (defined $ld_hr->{'all'}) { @protos = (qw(all)); } else { @protos = (qw(tcp udp icmp)); } for my $proto (@protos) { my $str1; my $str2; if (! defined $ld_hr->{$proto}->{'LOG'}) { if ($proto eq 'all') { $str1 = 'for all protocols'; $str2 = 'scans'; } else { $str1 = "for the $proto protocol"; $str2 = "$proto scans"; } print FWCHECK "[-] The $chain chain in the $ipt_bin ruleset on $config{'HOSTNAME'} does not\n", " appear to include a default LOG rule $str1. psad will not be able to\n", " detect $str2 without such a rule.\n\n"; $rv = 0; } if (defined $ld_hr->{$proto}->{'LOG'}->{'prefix'}) { my $found = 0; for my $fwstr (@fw_search) { $found = 1 if $ld_hr->{$proto}->{'LOG'}->{'prefix'} =~ /$fwstr/; } unless ($found) { if ($proto eq 'all') { $str1 = "[-] The $chain chain in the $ipt_bin ruleset " . "on $config{'HOSTNAME'} includes a default\n LOG rule for " . "all protocols,"; $str2 = 'scans'; } else { $str1 = "[-] The $chain chain in the $ipt_bin ruleset " . "on $config{'HOSTNAME'} inclues a default\n LOG rule for " . "the $proto protocol,"; $str2 = "$proto scans"; } print FWCHECK "$str1\n", " but the rule does not include one of the log prefixes mentioned above.\n", " It appears as though the log prefix is set to \"$ld_hr->{$proto}->{'LOG'}->{'prefix'}\"\n", " psad will not be able to detect $str2 without adding one of the above\n", " logging prefixes to the rule.\n\n"; $rv = 0; } } if (! defined $ld_hr->{$proto}->{'DROP'}) { if ($proto eq 'all') { $str1 = "for all protocols"; } else { $str1 = "for the $proto protocol"; } print FWCHECK "[-] The $chain chain in the $ipt_bin ruleset on $config{'HOSTNAME'} does not\n", " appear to include a default DROP rule $str1.\n\n"; $rv = 0; } } } ### make sure there was _something_ returned from the IPTables::Parse ### module. return 0 unless $num_keys > 0; } return $rv; } sub import_psad_perl_modules() { my $mod_paths_ar = &get_psad_mod_paths(); if ($#$mod_paths_ar > -1) { ### /usr/lib/psad/ exists push @$mod_paths_ar, @INC; splice @INC, 0, $#$mod_paths_ar+1, @$mod_paths_ar; } require IPTables::Parse; return; } sub get_psad_mod_paths() { my @paths = (); $config{'PSAD_LIBS_DIR'} = $psad_lib_dir if $psad_lib_dir; unless (-d $config{'PSAD_LIBS_DIR'}) { my $dir_tmp = $config{'PSAD_LIBS_DIR'}; $dir_tmp =~ s|lib/|lib64/|; if (-d $dir_tmp) { $config{'PSAD_LIBS_DIR'} = $dir_tmp; } else { return []; } } opendir D, $config{'PSAD_LIBS_DIR'} or die "[*] Could not open $config{'PSAD_LIBS_DIR'}: $!"; my @dirs = readdir D; closedir D; push @paths, $config{'PSAD_LIBS_DIR'}; for my $dir (@dirs) { ### get directories like "/usr/lib/psad/x86_64-linux" next unless -d "$config{'PSAD_LIBS_DIR'}/$dir"; push @paths, "$config{'PSAD_LIBS_DIR'}/$dir" if $dir =~ m|linux| or $dir =~ m|thread| or (-d "$config{'PSAD_LIBS_DIR'}/$dir/auto"); } return \@paths; } sub import_fw_search() { my $config_file = shift; open F, "< $config_file" or die "[*] Could not open fw search ", "string file $config_file: $!"; my @lines = ; close F; for my $line (@lines) { next unless $line =~ /\S/; next if $line =~ /^\s*#/; if ($line =~ /^\s*FW_MSG_SEARCH\s+(.*?);/) { push @fw_search, $1; } } return; } ### send mail message to all addresses contained in the ### EMAIL_ADDRESSES variable within psad.conf ($addr_str). ### TODO: Would it be better to use Net::SMTP here? sub send_mail() { my ($subject, $body_file, $addr_str, $mailCmd) = @_; open MAIL, "| $mailCmd -s \"$subject\" $addr_str > /dev/null" or die "[*] Could not send mail: $mailCmd -s \"$subject\" $addr_str: $!"; if ($body_file) { open F, "< $body_file" or die "[*] Could not open mail file: ", "$body_file: $!"; my @lines = ; close F; print MAIL for @lines; } close MAIL; return; } sub import_override_configs() { my @override_configs = split /,/, $override_config_str; for my $file (@override_configs) { die "[*] Override config file $file does not exist" unless -e $file; &import_config($file); } return; } sub import_config() { my $conf_file = shift; open C, "< $conf_file" or die "[*] Could not open " . "config file $conf_file: $!"; my @lines = ; close C; for my $line (@lines) { chomp $line; next if ($line =~ /^\s*#/); if ($line =~ /^\s*(\S+)\s+(.*?)\;/) { my $varname = $1; my $val = $2; if ($val =~ m|/.+| and $varname =~ /^\s*(\S+)Cmd$/) { ### found a command $cmds{$1} = $val unless defined $cmds{$1}; } else { $config{$varname} = $val unless defined $config{$varname}; } } } 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; } ### check paths to commands and attempt to correct if any are wrong. sub check_commands() { my $exceptions_hr = shift; my $caller = $0; my @path = (qw( /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin )); CMD: for my $cmd (keys %cmds) { ### both mail and sendmail are special cases, mail is not required ### if "nomail" is set in REPORT_METHOD, and sendmail is only ### required if DShield alerting is enabled and a DShield user ### email is set. if ($cmd eq 'mail') { next CMD if $config{'ALERTING_METHODS'} =~ /no.?e?mail/i; } elsif ($cmd eq 'sendmail') { next CMD unless ($config{'ENABLE_DSHIELD_ALERTS'} eq 'Y' and $config{'DSHIELD_ALERT_EMAIL'} ne 'NONE'); } next if $cmd eq 'wget'; ### only used in --sig-update mode 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 (defined $exceptions_hr->{$cmd}) { die "[*] ($caller): Could not find $cmd ", "anywhere!!!\n Please edit the config section ", "to include the path to $cmd."; } } } unless (-x $cmds{$cmd}) { unless (defined $exceptions_hr->{$cmd}) { die "[*] ($caller): $cmd is located at ", "$cmds{$cmd}, but is not executable\n", " by uid: $<"; } } } return; } sub usage() { my $exitcode = shift; print <<_HELP_; Options: --config - Specify path to configuration file. --fw-file - Analyze ruleset contained within fw_file instead of a running policy. --fw-analyze - Analyze the local iptables ruleset and exit. --no-fw-search-all - looking for specific log prefixes. --Lib-dir - Path to the psad lib directory. --test-mode - Enable test mode (used by the test suite). --help - Display help. _HELP_ exit $exitcode; } psad-2.2.1/SCAN_LOG0000664000175000017500000000344312071203757011747 0ustar mbrmbr This file contains a sample psad alert, and many more examples can be found here: http://www.cipherdyne.org/psad/docs/ Here is an example of psad alert (version 2.0.2) for a scan for the Microsoft VNC service against my Linux box (running kernel 2.6.18): =-=-=-=-=-=-=-=-=-=-=-= Fri Dec 22 12:10:38 2006 =-=-=-=-=-=-=-=-=-=-=-= Danger level: [2] (out of 5) Scanned tcp ports: [5900: 1 packets] tcp flags: [SYN: 1 packets, Nmap: -sT or -sS] iptables chain: INPUT (prefix "DROP"), 1 packets Source: 71.127.83.44 DNS: static-71-127-83-44.aubnin.fios.verizon.net Destination: 71.127.x.x Syslog hostname: minastirith Current interval: Fri Dec 22 12:10:33 2006 (start) Fri Dec 22 12:10:38 2006 (end) Overall scan start: Thu Dec 21 20:37:49 2006 Total email alerts: 36 Complete tcp range: [1433-5900] chain: interface: tcp: udp: icmp: INPUT eth0 44 0 0 [+] tcp scan signatures: "MISC VNC communication attempt" dst port: 5900 (no server bound to local port) flags: SYN psad_id: 100202 chain: INPUT packets: 1 classtype: attempted-admin reference: (url) http://isc.sans.org/port_details.php?port=5900 reference: (url) http://secunia.com/advisories/20107 [+] Whois Information: Verizon Internet Services Inc. VIS-71-96 (NET-71-96-0-0-1) 71.96.0.0 - 71.127.255.255 PORTAL MAGIC FTTP (NET-71-127-83-40-1) 71.127.83.40 - 71.127.83.47 # ARIN WHOIS database, last updated 2006-12-21 19:10 # Enter ? for additional hints on searching ARIN's WHOIS database. =-=-=-=-=-=-=-=-=-=-=-= Fri Dec 22 12:10:38 2006 =-=-=-=-=-=-=-=-=-=-=-= psad-2.2.1/LICENSE0000664000175000017500000004307712071203757011613 0ustar mbrmbr GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. psad-2.2.1/init-scripts/0000775000175000017500000000000012071203757013223 5ustar mbrmbrpsad-2.2.1/init-scripts/psad-init.gentoo0000775000175000017500000000212712071203757016335 0ustar mbrmbr#!/sbin/runscript ## Copyright 2006 Michael Rash # Distributed under the terms of the GNU General Public License v2 # Author: Michael Rash # Developed for the Gentoo Linux distribution depend() { need logger } checkconfig() { if [ ! -f /etc/psad/psad.conf ] ; then eerror "Please create /etc/psad/psad.conf" eerror "You can find a sample config file at /etc/psad/psad.conf.sample" return 1 fi return 0 } start() { checkconfig || return 1 ebegin "Starting ${SVCNAME}" start-stop-daemon \ --start \ --quiet \ --name psad \ --exec /usr/sbin/psad \ --pidfile /var/run/psad/psad.pid eend $? "Failed to start ${SVCNAME}" } stop() { ebegin "Stopping psadwatchd" start-stop-daemon --stop --quiet --pidfile /var/run/psad/psadwatchd.pid eend $? "Failed to stop psadwatchd" if [ -f /var/run/psad/kmsgsd.pid ] ; then ebegin "Stopping kmsgsd" start-stop-daemon --stop --quiet --pidfile /var/run/psad/kmsgsd.pid eend $? "Failed to stop kmsgsd" fi ebegin "Stopping ${SVCNAME}" start-stop-daemon --stop --quiet --pidfile /var/run/psad/psad.pid eend $? "Failed to stop ${SVCNAME}" } psad-2.2.1/init-scripts/psad-init.generic0000775000175000017500000000124212071203757016453 0ustar mbrmbr#!/bin/sh # # Startup script for psad # # chkconfig: 345 99 05 # description: The Port Scan Attack Detector (psad) # processname: psad # pidfile: /var/run/psad.pid # config: /etc/psad/psad.conf # restart() { $0 stop $0 start } # See how we were called. case "$1" in start) echo -n "Starting psad: " ### psad enables signature matching and auto ### danger level assignment by default, so ### command line args are not necessary here. /usr/sbin/psad echo ;; stop) /usr/sbin/psad --Kill ;; status) /usr/sbin/psad --Status ;; restart) restart ;; *) echo "Usage: psad {start|stop|status|restart}" exit 1 esac psad-2.2.1/init-scripts/psad-init.fedora0000775000175000017500000000240712071203757016303 0ustar mbrmbr#!/bin/bash # # /etc/rc.d/init.d/psad # # Starts the psad daemon # # chkconfig: 345 95 5 # description: The Port Scan Attack Detector (psad) # processname: psad # Source function library. . /etc/init.d/functions test -x /usr/sbin/psad || exit 0 RETVAL=0 # # See how we were called. # prog="psad" start() { # Check if psad is already running if [ ! -f /var/lock/subsys/psad ]; then echo -n $"Starting $prog: " daemon /usr/sbin/psad RETVAL=$? [ $RETVAL -eq 0 ] && touch /var/lock/subsys/psad echo fi return $RETVAL } stop() { echo -n $"Stopping $prog: " killproc /usr/sbin/psadwatchd if [ -f /var/run/psad/kmsgsd.pid ]; then killproc /usr/sbin/kmsgsd fi killproc /usr/sbin/psad RETVAL=$? [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/psad echo return $RETVAL } restart() { stop start } reload() { restart } status_psad() { if [ -f /var/run/psad/kmsgsd.pid ]; then status /usr/sbin/kmsgsd fi status /usr/sbin/psadwatchd status /usr/sbin/psad } case "$1" in start) start ;; stop) stop ;; reload|restart) restart ;; condrestart) if [ -f /var/lock/subsys/psad ]; then restart fi ;; status) status_psad ;; *) echo $"Usage: $0 {start|stop|restart|condrestart|status}" exit 1 esac exit $? exit $RETVAL psad-2.2.1/init-scripts/psad-init.redhat0000775000175000017500000000253312071203757016312 0ustar mbrmbr#!/bin/sh # # Startup script for psad # # chkconfig: 345 99 05 # description: The Port Scan Attack Detector (psad) # processname: psad # pidfile: /var/run/psad.pid # config: /etc/psad/psad.conf # # Source function library. . /etc/rc.d/init.d/functions restart() { $0 stop $0 start } # See how we were called. case "$1" in start) echo -n "Starting psad: " ### psad enables signature matching and auto ### danger level assignment by default, so ### command line args are not necessary here. daemon /usr/sbin/psad RETVAL=$? echo if [ $RETVAL -eq 0 ]; then touch /var/lock/subsys/psad fi ;; stop) echo -n "Shutting down the psad psadwatchd daemon: " killproc psadwatchd echo echo -n "Shutting down the psad daemon: " killproc psad RETVAL=$? [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/psad echo if [ -f /var/run/psad/kmsgsd.pid ]; then echo -n "Shutting down the psad kmsgsd daemon: " killproc kmsgsd echo fi ;; status) if [ -f /var/run/psad/kmsgsd.pid ]; then status kmsgsd fi status psad status psadwatchd ;; restart|reload) restart ;; condrestart) [ -f /var/lock/subsys/psad ] && restart || : ;; *) echo "Usage: psad {start|stop|status|restart|reload|condrestart}" exit 1 esac psad-2.2.1/kmsgsd.80000664000175000017500000000275012071203757012160 0ustar mbrmbr.\" .TH KMSGSD 8 "November 2002" "Debian GNU/Linux" .SH NAME .B kmsgsd \- separates iptables messages from all other kernel messages. .SH SYNOPSIS .B kmsgsd .SH DESCRIPTION .B kmsgsd reads messages from the /var/lib/psad/psadfifo named pipe and prints any firewall related log messages to the psad data file "/var/log/psad/fwdata". .B psad cannot detect port scans or other suspect traffic without kmsgsd running on the machine. .B kmsgsd uses the psad.conf configuration file which by default is located at /etc/psad/psad.conf, but a different path can be specified on the command line. .SH OPTIONS .TP .BR \-c\ \ Specify path to config file instead of using the default configuration file /etc/psad/psad.conf. .TP .BR \-D Dump the configuration values that .B kmsgd derives from /etc/psad/psad.conf (or other override files) on STDERR. .TP .BR \-h Display usage information and exit. .TP .BR \-O\ \ Override config variable values that are normally read from the /etc/psad/psad.conf file with values from the specified file. Multiple override config files can be given as a comma separated list. .SH SEE ALSO .BR psad (8), .BR psadwatchd (8), .SH AUTHOR Michael Rash (mbr@cipherdyne.org) This manual page was written by Daniel Gubser for the Debian GNU/Linux system (but may be used by others). .SH DISTRIBUTION .B psad is distributed under the GNU General Public License (GPL), and the latest version may be downloaded from .B http://www.cipherdyne.org psad-2.2.1/config_vars.pl0000775000175000017500000000624212071203757013437 0ustar mbrmbr#!/usr/bin/perl -w # ############################################################################ # # File: config_vars.pl # # Purpose: To provide basic usage validation for cipherdyne.org project # variables. # ############################################################################ # use strict; my $config_file = 'config_vars.conf'; open C, "< $config_file" or die $!; my @lines = ; close C; my %config = (); for my $line (@lines) { next unless $line; next if $line =~ /^\s*#/; if ($line =~ /^\s*(\S+)\s+(\S+)/) { $config{$1}{$2} = ''; } } PROG: for my $prog (keys %config) { unless (-e $prog) { print "[-] program: $prog does not exist in current directory.\n"; next PROG; } open F, "< $prog" or die "[*] Could not open $prog: $!"; my @prog_lines = ; close F; my %config_vars = (); my %used_vars = (); CONF: for my $config (keys %{$config{$prog}}) { unless (-e $config) { print "[-] config: $config for program: $prog does not exist.\n"; next CONF; } open F, "< $config" or die "[*] Could not open $config: $!"; my @config_lines = ; close F; for my $line (@config_lines) { next unless $line; next unless $line =~ /\S/; next if $line =~ /^\s*#/; if ($line =~ /^\s*(\S+)\s/) { $config_vars{$1} = ''; } } } my $line_num = 1; ### see if the program is using an undefined configuration ### variable for my $line (@prog_lines) { if ($prog =~ /\.c/) { ### C code file if ($line =~ m|find_char_var\(\"(\w+)\"|) { my $var = $1; unless (defined $config_vars{$var}) { print "[-] Config var: $var (line $line_num) ", "is not defined in config files for program: $prog\n"; } $used_vars{$var} = ''; } } else { my $var1 = ''; my $var2 = ''; if ($line =~ m|\$config\{\'(\S+?)\'\}|) { $var1 = $1; } if ($line =~ m|\$cmds\{\'(\S+?)\'\}|) { $var2 = "$1Cmd"; } if ($var1) { unless (defined $config_vars{$var1}) { print "[-] Config var: $var1 (line $line_num) ", "is not defined in config files for program: $prog\n"; } $used_vars{$var1} = ''; } if ($var2) { unless (defined $config_vars{$var2}) { print "[-] Config var: $var2 (line $line_num) ", "is not defined in config files for program: $prog\n"; } $used_vars{$var2} = ''; } } $line_num++; } ### see if the config files define a configuration variable ### that is not used by the program for my $var (sort keys %config_vars) { unless (defined $used_vars{$var}) { print "[-] $var is defined in config files, ", "but not used in $prog\n"; } } } exit 0; psad-2.2.1/psad.80000664000175000017500000010407312071203757011620 0ustar mbrmbr.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH PSAD 8 "March 2009" Linux .SH NAME .B psad \- The Port Scan Attack Detector .SH SYNOPSIS .B psad [options] .SH DESCRIPTION .B psad makes use of iptables log messages to detect, alert, and (optionally) block port scans and other suspect traffic. For TCP scans psad analyzes TCP flags to determine the scan type (syn, fin, xmas, etc.) and corresponding command line options that could be supplied to nmap to generate such a scan. In addition, psad makes use of many TCP, UDP, and ICMP signatures contained within the Snort intrusion detection system (see http://www.snort.org/) to detect suspicious network traffic such as probes for common backdoors, DDoS tools, OS fingerprinting attempts, and more. By default psad also provides alerts for snort rules that are detected directly by iptables through the use of a ruleset generated by .B fwsnort (http://www.cipherdyne.org/fwsnort/). This enables psad to send alerts for application layer attacks. .B psad features a set of highly configurable danger thresholds (with sensible defaults provided) that allow the administrator to define what constitutes a port scan or other suspect traffic. Email alerts sent by psad contain the scanning ip, number of packets sent to each port, any TCP, UDP, or ICMP signatures that have been matched (e.g. "NMAP XMAS scan"), the scanned port range, the current danger level (from 1 to 5), reverse dns info, and whois information. .B psad also makes use of various packet header fields associated with TCP SYN packets to passively fingerprint remote operating systems (in a manner similar to the .B p0f fingerprinter) from which scans originate. This requires the use of the .B --log-tcp-options argument for iptables logging rules; if this option is not used, .B psad will fall back to a fingerprinting method that makes use of packet length, TTL and TOS values, IP ID, and TCP window sizes. .PP .B psad reads all iptables log data by default from the .I /var/log/messages file. By parsing firewall log messages, psad is provided with data that represents packets that have been logged (and possibly dropped) by the running iptables policy. In this sense, psad is supplied with a pure data stream that exclusively contains packets that the firewall has deemed unfit to enter the network. .B psad consists of three daemons: psad, kmsgsd, and psadwatchd. .B psad is responsible for processing all packets that have been logged by the firewall and applying the signature logic in order to determine what type of scan has been leveraged against the machine and/or network. .B kmsgsd .B (deprecated) reads all messages that have been written to the .I /var/lib/psad/psadfifo named pipe and writes any message that matches a particular regular expression (or string) to .I /var/log/psad/fwdata. kmsgsd is only used if the .B ENABLE_SYSLOG_FILE variable is disabled in psad.conf. .B psadwatchd is a software watchdog that will restart any of the other two daemons should a daemon die for any reason. .SH OPTIONS .TP .BR \-A ", " \-\^\-Analyze-msgs Analyze an iptables logfile for scans and exit. This will generate email alerts just as a normal running psad process would have for all logged scans. By default the psad data file .I /var/log/psad/fwdata is parsed for old scans, but any file can be specified through the use of the \-\-messages-file command line option. For example it might be useful to point psad at your .I /var/log/messages file. .TP .BR \-\^\-analysis-fields\ \ In \-\-Analyze mode restrict analysis to iptables log messages that have specific values for particular fields. Examples include "SRC:1.2.3.4", "DST:10.0.0.0/24, and "TTL:64", and multiple fields are supported as a comma-separated list like "SRC:1.2.3.4, LEN:44, DST:10.0.0.0/24". .TP .BR \-i "\fR,\fP " \-\^\-interface\ \ Specify the interface that .B psad will examine for iptables log messages. This interface will be the .B IN= interface for packets that are logged in the .B INPUT and .B FORWARD chains, and the .B OUT= interface for packets logged in the .B OUTPUT chain. .TP .BR \-\^\-sig-update Instruct .B psad to download the latest set of modified Snort signatures from http://www.cipherdyne.org/psad/signatures so that psad can take advantage of signature updates before a new release is made. .TP .BR \-O "\fR,\fP " \-\^\-Override-config\ \ Override config variable values that are normally read from the /etc/psad/psad.conf file with values from the specified file. Multiple override config files can be given as a comma separated list. .TP .BR \-D ", " \-\^\-Dump-conf Dump the current psad config to STDOUT and exit. Various pieces of information such as the home network, alert email addresses, and DShield user id are removed from the resulting output so it is safe to send to others. .TP .BR \-F ", " \-\^\-Flush Remove any auto-generated firewall block rules if psad was configured to automatically respond to scans (see the ENABLE_AUTO_IDS variable in psad.conf). .TP .BR \-S ", " \-\^\-Status Display the status of any psad processes that may or not be running. The status output contains a listing of the number of packets that have been processed by psad, along with all IP addresses and corresponding danger levels that have scanned the network. .TP .BR \-\^\-status-ip\ \ Display status information associated with .I ip such as the protocol packet counters as well as the last 10 packets logged by iptables. .TP .BR \-\^\-status-dl\ \
Display status information only for scans that have reached a danger level of at least .I dl .TP .BR \-\^\-status-summary Instruct .B psad to omit detailed IP information from .I --Status and .I --Analyze modes. .TP .BR \-m "\fR,\fP " \-\^\-messages-file\ \ This option is used to specify the file that will be parsed in analysis mode (see the \-\-Analyze-msgs option). The default path is the psad data file .I /var/log/psad/fwdata. .TP .BR \-\^\-CSV Instruct .B psad to parse iptables log messages out of .I /var/log/messages (by defult, but this path can be changed with the .I -m option), and print the packet fields on STDOUT in comma-separate value format. This is useful for graphing iptables log data with AfterGlow (see http://afterglow.sourceforge.net/index.html). .TP .BR \-\^\-CSV-fields\ \ Instruct .B psad to only include a specific set of iptables log message fields within the CSV output. AfterGlow accepts up to three fields for its graph data, so the most common usage of this option is "src dst dp" to print the source and destination IP addresses, and the destination port number. .TP .BR \-K ", " \-\^\-Kill Kill the current psad process along with psadwatchd and kmsgsd. This provides a quick and easy way to kill all psad processes without having to look in the process table or appeal to the psad-init script. .TP .BR \-R ", " \-\^\-Restart Restart the currently running psad processes. This option will preserve the command line options that were supplied to the original psad process. .TP .BR \-U ", " \-\^\-USR1 Send a running psad process a USR1 signal. This will cause psad to dump the contents of the %Scan hash to the file "/var/log/psad/scan_hash.$$" where "$$" represents the pid of the psad process. This is mostly useful for debugging purposes, but it also allows the administrator to peer into the %Scan hash, which is the primary data structure used to store scan data within system memory. .TP .BR \-H ", " \-\^\-HUP Send all running psad daemons a HUP signal. This will instruct the daemons to re-read their respective configuration files without causing scan data to be lost in the process. .TP .BR \-B ", " \-\^\-Benchmark Run psad in benchmark mode. By default benchmark mode will simulate a scan of 10,000 packets (see the \-\-packets option) and then report the elapsed time. This is useful to see how fast psad can process packets on a specific machine. .TP .BR \-p "\fR,\fP " \-\^\-packets\ \ Specify the number of packets to analyze in \-\-Analyze mode or use in \-\-Benchmark mode. The default is 10,000 packets in \-\-Benchmark mode, and unlimited in \-\-Analyze mode. .TP .BR \-d ", " \-\^\-debug Run psad in debugging mode. This will automatically prevent psad from running as a daemon, and will print the contents of the %Scan hash and a few other things on STDOUT at crucial points as psad executes. .TP .BR \-c "\fR,\fP " \-\^\-config\ \ By default all of the psad makes use of the configuration file .I /etc/psad/psad.conf for almost all configuration parameters. .B psad can be made to override this path by specifying a different file on the command line with the \-\-config option. .TP .BR \-\^\-signatures\ \ The iptables firewalling code included within the linux 2.4.x kernel series has the ability to distinguish and log any of the TCP flags present within TCP packets that traverse the firewall interfaces. .B psad makes use of this logging capability to detect several types of TCP scan signatures included within .I /etc/psad/signatures. The signatures were originally included within the snort intrusion detection system. New signatures can be included and modifications to existing signatures can be made to the signature file and psad will import the changes upon receiving a HUP signal (see the \-\-HUP command line option) without having to restart the psad process. .B psad also detects many UDP and ICMP signatures that were originally included within snort. .TP .BR \-e ", " \-\^\-email-analysis Send alert emails when run in \-\-Analyze-msgs mode. Depending on the size of the iptables logfile, using the \-\-email-analysis option could extend the runtime of psad by quite a bit since normally both DNS and whois lookups will be issued against each scanning IP address. As usual these lookups can be disabled with the \-\-no-rdns and \-\-no-whois options respectively. .TP .BR \-w ", " \-\^\-whois-analysis By default .B psad does not issue whois lookups when running in \-\-Analyze-msgs mode. The \-\-whois-analysis option will override this behavior (when run in analysis mode) and instruct psad to issue whois lookups against IP addresses from which scans or other suspect traffic has originated. .TP .BR \-\^\-analysis-auto-block Enable auto-blocking responses when running in \-\-Analyze-msgs mode. This is mostly useful only for the .B psad test suite when auto-blocking responses are tested and verified. .TP .BR \-\^\-snort-type\ \ Restrict the type of snort sids to .I type. Allowed types match the file names given to snort rules files such as "ddos", "backdoor", and "web-attacks". .TP .BR \-\^\-snort-rdir\ \ Manually specify the directory where the snort rules files are located. The default is .I /etc/psad/snort_rules. .TP .BR \-\^\-passive-os-sigs\ \ Manually specify the path to the passive operating system fingerprinting signatures file. The default is .I /etc/psad/posf. .TP .BR \-\^\-auto-dl\ \ Occasionally certain IP addresses are repeat offenders and should automatically be given a higher danger level than would normally be assigned. Additionally, some IP addresses can always be ignored depending on your network configuration (the loopback interface 127.0.0.1 might be a good candidate for example). .I /etc/psad/auto_dl provides an interface for psad to automatically increase/decrease/ignore scanning IP danger levels. Modifications can be made to auto_dl (installed by default in /etc/psad) and psad will import them with 'psad \-H' or by restarting the psad process. .TP .BR \-\^\-fw-search\ \ By default all of the psad makes use of the firewall search configuration file .I /etc/psad/fw_search.conf for firewall search mode and search strings. .B psad can be made to override this path by specifying a different file on the command line with the \-\-fw-search option. .TP .BR \-\^\-fw-list-auto List all rules in iptables chains that are used by .B psad in auto-blocking mode. .TP .BR \-\^\-fw-analyze Analyze the local iptables ruleset, send any alerts if errors are discovered, and then exit. .TP .BR \-\^\-fw-del-chains By default, if ENABLE_AUTO_IDS is set to "Y" .B psad will not delete the auto-generated iptables chains (see the IPT_AUTO_CHAIN keywords in psad.conf) if the \-\-Flush option is given. The \-\-fw-del-chains option overrides this behavior and deletes the auto-blocking chains from a running iptables firewall. .TP .BR \-\^\-fw-dump Instruct .B psad to dump the contents of the iptables policy that is running on the local system. All IP addresses are removed from the resulting output, so it is safe to post to the psad list, or communicate to others. This option is most often used with \-\-Dump-conf. .TP .BR \-\^\-fw-block-ip\ \ Specify an IP address or network to add to the iptables controls that are auto-generated by psad. This allows psad to manage the rule timeouts. .TP .BR \-\^\-fw-rm-block-ip\ \ Specify an IP address or network to remove from the iptables controls that are auto-generated by psad. .TP .BR \-\^\-fw-file\ \ Analyze the iptables ruleset contained within .B policy-file instead of the ruleset currently loaded on the local system. .TP .BR \-\^\-CSV-regex\ \ Instruct .B psad to only print CSV data that matches the supplied regex. This regex is used to match against each of the entire iptables log messages. .TP .BR \-\^\-CSV-neg-regex\ \ Instruct .B psad to only print CSV data that does not match the supplied regex. This regex is used to negatively match against each of the entire iptables log messages. .TP .BR \-\^\-CSV-uniq-lines Instruct .B psad to only print unique CSV data. That is, each line printed in .I --CSV mode will be unique. .TP .BR \-\^\-CSV-max-lines\ \ Limit the number of CSV-formatted lines that .B psad generates on STDOUT. This is useful to allow AfterGlow graphs to be created that are not too cluttered. .TP .BR \-\^\-CSV-start-line\ \ Specify the beginning line number to start parsing out of the iptables log file in .I --CSV output mode. This is useful for when the log file is extremely large, and you want to begin parsing a specific place within the file. The default is begin parsing at the beginning of the file. .TP .BR \-\^\-CSV-end-line\ \ Specify the ending line number to stop parsing the iptables log file in .I --CSV output mode. This is useful for when the log file is extremely large, and you do not want .B psad to process the entire thing. .TP .BR \-\^\-gnuplot Enter into Gnuplot mode whereby .B psad parses an iptables logfile and creates .gnu and .dat files that are suitable for graphing with Gnuplot. The various .I --CSV command line arguments apply to plotting iptables log with Gnuplot. .TP .BR \-\^\-gnuplot-template\ \ Use a template file for all Gnuplot graphing directives (this is usually a .gnu file by convention). Normally .B psad builds all of the graphing directives based on various --gnuplot command line arguments, but the \-\-gnuplot-template switch allows you to override this behavior. .TP .BR \-\^\-gnuplot-file-prefix\ \ Specify a prefix for the .gnu, .dat, and .png files that are generated in .I --gnuplot mode. So, when visualizing attacks captured in an iptables logfile (let's say you are interested in port scans), you could use this option to have .B psad create the two files portscan.dat, portscan.gnu, and Gnuplot will create an additional file portscan.png when the portscan.gnu file is loaded. .TP .BR \-\^\-gnuplot-x-label\ \