├── README.md
├── active-response
├── add_to_cdb.sh
├── alert2chat.sh
├── bhr.sh
├── cif.sh
├── command_search.sh
├── cymru_lookup.sh
├── deb_lookup.sh
├── ldap_lookup.sh
├── makelists.sh
├── puppetdb_lookup.sh
├── rpm_lookup.sh
├── rule-all.sh
├── syscheck-all.sh
├── syscheck2chat.sh
├── time_lookup.sh
├── virus_total.py
└── virustotal_lookup.sh
├── decoders
├── active_response.xml
├── kerberos_decoder.xml
└── sudo.xml
├── munin
├── ossec_ar_stats
├── ossec_munin.png
├── ossec_stats
├── ossec_top_groups
└── ossec_top_rules
├── nagios
├── check_ossec.py
└── check_ossec.sh
└── rules
├── kerberos_rules.xml
├── organization_ar_rules.xml
├── organization_rules.xml
├── organization_syscheck_rules.xml
└── overwrite_rules.xml
/README.md:
--------------------------------------------------------------------------------
1 | # ossec-tools
2 | Scripts and integrations for OSSEC. With the following code and configurations
3 | we transformed our OSSEC deployment from an overwhelming mess of alerts to a
4 | manageable and useful system where most of the alerts we actually care about.
5 |
6 | It includes things like code to block hosts at our perimeter, create intelligence feeds from
7 | the logs, and only e-mail alerts when a file has changed that is not managed by Puppet and is different from
8 | what's reported by the package manager's checksums - meaning no alerts for files changed after package updates :)
9 |
10 | ## Active Response
11 | Custom AR scripts
12 |
13 | * active-response/virustotal_lookup.sh/virus_total.py - Look up hash from syscheck alerts in VT database
14 | * active-response/cymru_lookup.sh - Look up hash from sysheck alerts in Team Cymru Malware Hash Registery
15 | * active-response/puppetdb_lookup.sh - Look up managed files in PuppetDB
16 | * active-response/rpm_lookup.sh - Look up files that changed from RPM install (must be present on agents)
17 | * active-response/deb_lookup.sh - Lookup file that changes from DEB install (must be present on agents)
18 | * active-response/time_lookup.sh - Check if system clock is off or time zone differs for analyzed logs
19 | * active-response/ldap_lookup.sh - Lookup employee usernames in LDAP database
20 | * active-response/command_search.sh - Search for malicious commands across logs
21 | * active-response/cif.sh - Create intelligence feed from alerts
22 | * active-response/bhr.sh - Block hosts at perimeter using Black Hole Router by Justin Azoff
23 | * active-response/add_to_cdb.sh - Add entries from alerts to CDB database (only collect system users at this time)
24 | * active-response/rule-all.sh - Run many of the above scripts
25 | * active-response/syscheck-all.sh - Run many of the syscheck scripts
26 |
27 | Many of the scripts generate logs which are fed into OSSEC, decoded, and analyzed.
28 | The decoders and rules are in the respective sections below. Don't include anything you don't need.
29 | For example, if you don't use puppet for configuration management or run Debian system don't include
30 | the puppetdb_lookup or rpm_lookup configurations.
31 |
32 | 1. Server: copy all the files above to `$OSSEC/active-response/bin/` directory.
33 | 2. Server: edit `$OSSEC/etc/ossec.conf` with the AR and localfile configuration.
34 | ```
35 |
36 | syscheck_all
37 | syscheck-all.sh
38 | filename
39 |
40 |
41 |
42 | ip_all
43 | rule-all.sh
44 | srcip
45 |
46 |
47 |
48 | rule_all
49 | rule-all.sh
50 |
51 |
52 |
53 |
54 | rpm_lookup
55 | rpm_lookup.sh
56 | filename
57 |
58 |
59 |
60 | deb_lookup
61 | deb_lookup.sh
62 | filename
63 |
64 |
65 |
66 | restart-ossec
67 | restart-ossec.sh
68 |
69 |
70 |
71 |
72 | syscheck_all
73 | syscheck,,
74 | 5
75 | server
76 |
77 |
78 |
79 | rpm_lookup
80 | syscheck,,
81 | 5
82 | local
83 |
84 |
85 |
86 | deb_lookup
87 | syscheck,,
88 | 5
89 | local
90 |
91 |
92 |
93 | ip_all
94 | 4
95 | server
96 |
97 |
98 |
99 | rule_all
100 | 4
101 | server
102 |
103 |
104 |
105 | restart-ossec
106 | local
107 | 110003
108 |
109 |
110 |
111 | syslog
112 | /var/ossec/logs/puppetdb_lookup.log
113 |
114 |
115 |
116 | syslog
117 | /var/ossec/logs/virustotal_lookup.log
118 |
119 |
120 |
121 | syslog
122 | /var/ossec/logs/cymru_lookup.log
123 |
124 |
125 |
126 | syslog
127 | /var/ossec/logs/rpm_lookup.log
128 |
129 |
130 |
131 | syslog
132 | /var/ossec/logs/deb_lookup.log
133 |
134 |
135 |
136 | syslog
137 | /var/ossec/logs/command_search.log
138 |
139 |
140 |
141 | syslog
142 | /var/ossec/logs/time_lookup.log
143 |
144 | ```
145 | 3. Agent: copy `rpm_lookup.sh` and `deb_lookup.sh` to `$OSSEC/active-response/bin/` and localfile config to `$OSSEC/etc/ossec.conf`.
146 | ```
147 |
148 | syslog
149 | /var/ossec/logs/rpm_lookup.log
150 |
151 |
152 |
153 | syslog
154 | /var/ossec/logs/deb_lookup.log
155 |
156 | ```
157 |
158 | 4. Server: Add `organization_ar_rules.xml` to `$OSSEC/rules/` and include the rules in `$OSSEC/etc/ossec.conf`.
159 | 5. Server: Add `active_response.xml` decoder to `$OSSEC/etc/` and include the decoder in `$OSSEC/etc/ossec.conf`.
160 | 6. Agent/Server: Before restarting create the log files so OSSEC begins reading them e.g. `touch $OSSEC/logs/{deb,rpm}_lookup.sh`
161 | 7. Restart the server and agent OSSEC software
162 |
163 | ## Decoders
164 | Custom decoders
165 |
166 | * decoders/kerberos.xml - Decoder for ksu and kadmind logs
167 | * decoders/active_response.xml - Decoder for logs generated from our active response scripts
168 | * decoders/sudo.xml - Improved sudo decoder
169 |
170 | ## Rules
171 | Custom rules
172 |
173 | * rules/kerberos_rules.xml - Kerberos rules
174 | * rules/organization_ar_rules.xml - Active response rules for our AR scripts
175 | * rules/organization_syscheck_rules.xml - Examples of syscheck rules
176 | * rules/organization_rules.xml - Examples of standard log rules
177 | * rules/overwrite_rules.xml - Existing rules that contain modifications
178 |
179 | ## Munin
180 | Plugins to graph OSSEC stats for Munin
181 |
182 | * munin/ossec_ar_stats - Graph active response script usage
183 | * munin/ossec_stats - Graph alert counts by 3 major types of alerts: rules, syscheck, or rootcheck
184 | * munin/ossec_top_groups - Graph alert counts by top 10 rule categories
185 | * munin/ossec_top_rules - Graph alert counts by top 10 rule descriptions
186 |
187 | Location
188 | ```
189 | $ ls /etc/munin/plugins/ossec_*
190 | /etc/munin/plugins/ossec_ar_stats /etc/munin/plugins/ossec_stats /etc/munin/plugins/ossec_top_groups /etc/munin/plugins/ossec_top_rules
191 | ```
192 |
193 | Configuration
194 | ```
195 | $ cat /etc/munin/plugin-conf.d/ossec_*
196 | [ossec_ar_stats]
197 | user root
198 | [ossec_stats]
199 | user root
200 | [ossec_top_groups]
201 | user root
202 | [ossec_top_rules]
203 | user root
204 | ```
205 |
206 | 
207 |
208 | ## Nagios
209 | Plugins to check OSSEC services. There's two scripts but the python script `check_ossec.py` is newer and has more features.
210 | Examples of both are listed below.
211 |
212 | * Check status of OSSEC services excluding active response service (execd)
213 | * `./check_ossec.py -T status`
214 | * Check that all agents are connected except host1
215 | * `./check_ossec.py -T connected --skip host1.blah.org`
216 | * Check that syscheck has been completed for all agents in the last 12 hours
217 | * `./check_ossec.py -T syscheck -c 12 -w 6`
218 | * Check that rootkit checks have been completed for all agents in the last 3 hours
219 | * `./check_ossec.py -T rootcheck -c 12 -w 6`
220 | * Check status of OSSEC services excluding active response i.e. execd
221 | * `./check_ossec.sh -s execd`
222 | * Check status of OSSEC agent
223 | * `./check_ossec.sh -a server1`
224 | * Check status of multiple OSSEC agents
225 | * `./check_ossec.sh -a "server1,server2,station3"`
226 | * Report critical if more than 3 agents are offline and warning if at least 1 is offline.
227 | * `./check_ossec.sh -c 3 -w 1`
228 |
--------------------------------------------------------------------------------
/active-response/add_to_cdb.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
4 | LOG="../logs/add_to_cdb.log"
5 | AR_LOG="${PWD}/../logs/active-responses.log"
6 | CDB="/var/ossec/lists/system_users.list"
7 | ACTION=$1
8 | USER=$2
9 | IP=$3
10 | ALERTID=$4
11 | RULEID=$5
12 | AGENT=$6
13 | SERVICE=$7
14 | FILENAME=$8
15 |
16 | die(){
17 | [[ -d $(dirname $LOG) ]] && printf "$PRE $*\n" | tee -a $LOG
18 | exit 1
19 | }
20 |
21 | log(){
22 | [[ -d $(dirname $LOG) ]] && printf "$PRE $*\n" | tee -a $LOG
23 | exit 0
24 | }
25 |
26 | check_args(){
27 | local argc
28 | argc="$1"
29 | [[ $argc -ge 5 ]] || die "ERROR: Not enough arguments given"
30 | }
31 |
32 | get_alert(){
33 | local alert
34 | alert=$(awk -v ts=$ALERTID 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log)
35 | [[ "$alert" ]] || return 1
36 | printf "$alert\n"
37 | }
38 |
39 | is_system_uid(){
40 | local uid="$1"
41 | [[ "$uid" ]] || die "ERROR: ${uid:-uid} was not found"
42 | [[ $uid -le 500 && $uid -gt 0 ]] || return 1
43 | return 0
44 | }
45 |
46 | get_uid_from_alert(){
47 | local alert="$@"
48 | local uid_field
49 | local uid
50 | uid_field=$(printf "$alert\n" | grep -o 'UID=[0-9]\+')
51 | uid=${uid_field#*=}
52 | printf "$uid\n"
53 | }
54 |
55 | get_username_from_alert(){
56 | local alert="$@"
57 | local name_field
58 | local username
59 | name_field=$(printf "$alert\n" | grep -o 'name=[a-zA-Z0-9_]\+')
60 | name=${name_field#*=}
61 | printf "$name\n"
62 | }
63 |
64 | send_cdb(){
65 | local username="$1"
66 | if [[ -w "$CDB" ]]; then
67 | printf "${username}:system\n" >> $CDB
68 | fi
69 | }
70 |
71 | check_cdb(){
72 | local username="$1"
73 | if [[ -r "$CDB" ]]; then
74 | grep -q "${username}:system" $CDB || return 1
75 | fi
76 | exit 0
77 | }
78 |
79 | # Check for arguments
80 | check_args $#
81 |
82 | LOCAL=$(dirname $0);
83 | cd $LOCAL
84 | cd ../
85 | PWD=$(pwd)
86 |
87 | # Logging the call
88 | [[ -f "$AR_LOG" ]] && printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> $AR_LOG
89 |
90 | # Only work on rule 5902 'new user'
91 | [[ $RULEID -eq 5902 ]] || die "ERROR: ${RULEID:-ruleid} did not match"
92 |
93 | # Getting full alert
94 | ALERT=$(get_alert) || die "ERROR: get_alert: No alert found matching timestamp $ALERTID"
95 |
96 | # Extract info from alert
97 | USERID="$(get_uid_from_alert "$ALERT")"
98 | is_system_uid "$USERID" || die "ERROR: UID $USERID is not in the system range"
99 | USER="$(get_username_from_alert "$ALERT")"
100 |
101 | # Check if we've looked up this user previously
102 | check_cdb "$USER" || send_cdb "$USER"
103 |
104 | # Enable
105 | /var/ossec/bin/ossec-makelists -c /var/ossec/etc/ossec.conf
106 |
107 | # Log
108 | log "OK: Added system user $USER to $CDB"
109 |
--------------------------------------------------------------------------------
/active-response/alert2chat.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Author: Jon Schipp
3 | CHAT=/usr/local/bin/ircsay
4 | CHANNEL="#ossec-alerts"
5 | ACTION=$1
6 | USER=$2
7 | IP=$3
8 | ALERTID=$4
9 | RULEID=$5
10 |
11 | # Check for chat program
12 | [[ -f $CHAT ]] || exit 1
13 |
14 | # Skip rules to avoid flooding of alerts of less importance
15 | # Syscheck
16 | [[ "$*" =~ syscheck ]] && exit
17 | # Login & SSH attempts
18 | [[ $RULEID =~ ^(570[1-3]|5710)$ ]] && exit
19 | # System
20 | [[ $RULEID =~ ^(2933|5113)$ ]] && exit
21 |
22 | LOCAL=$(dirname $0);
23 | cd $LOCAL
24 | cd ../
25 | PWD=$(pwd)
26 |
27 | # Logging the call
28 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> ${PWD}/../logs/active-responses.log
29 |
30 | # Getting full alert
31 | awk -v ts=$ALERTID 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log | $CHAT "$CHANNEL" -
32 |
--------------------------------------------------------------------------------
/active-response/bhr.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | TOKEN=""
3 | URL="https://bhr.org.tld/bhr/api/block"
4 | CHAT=/usr/local/bin/ircsay
5 | CHANNEL="#host-blocks"
6 | IP=$3
7 | RULEID=$5
8 | MESSAGE='OSSEC Rule '$RULEID''
9 |
10 | TIMEOUT=900
11 |
12 | # Requires IP
13 | [[ $IP ]] || exit
14 |
15 | # Don't block your nets
16 | [[ $IP =~ ^10\.1\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && exit
17 |
18 | # Skip RFC 1918 addresses
19 | [[ $IP =~ ^10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && exit
20 | [[ $IP =~ ^172\.16\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && exit
21 | [[ $IP =~ ^192\.168\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && exit
22 |
23 | # Only block for these rules
24 | [[ $RULEID =~ ^(5703|5712|31153|100022)$ ]] || exit
25 |
26 | # Logging the call
27 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> ${PWD}/../logs/active-responses.log
28 |
29 | curl -X POST -H "Authorization: Token ${TOKEN}" $URL \
30 | --data-urlencode source="ossec" \
31 | --data-urlencode duration="${TIMEOUT}" \
32 | --data-urlencode why="${MESSAGE}" \
33 | --data-urlencode cidr="${IP}/32" \
34 | --data-urlencode autoscale="1"
35 |
36 | [[ -f $CHAT ]] && printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" | $CHAT "$CHANNEL" -
37 |
--------------------------------------------------------------------------------
/active-response/cif.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Generates intel feed entries for CIF
3 | # Author: Jon Schipp
4 | CHAT=/usr/local/bin/ircsay
5 | CHANNEL="#feeds"
6 | ACTION=$1
7 | USER=$2
8 | IP=$3
9 | ALERTID=$4
10 | RULEID=$5
11 | DESCRIPTION=$(grep -h -A 10 ']' '/description/ { printf("%s", $3) } END { printf("\n") }')
12 | FILE=/var/www/html/ossec.csv
13 |
14 | # Logging the call
15 | echo "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8" >> ${PWD}/../logs/active-responses.log
16 |
17 | [[ $IP ]] || exit
18 |
19 | # RFC 1918
20 | [[ $IP =~ ^10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && exit
21 | [[ $IP =~ ^172\.16\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && exit
22 | [[ $IP =~ ^192\.168\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && exit
23 | # Add other networks to skip
24 |
25 | printf "${IP},OSSEC,${RULEID},${DESCRIPTION}\n" >> $FILE
26 | [[ -f $CHAT ]] && printf "${IP},OSSEC,${RULEID},${DESCRIPTION}\n" | $CHAT "$CHANNEL" -
27 |
--------------------------------------------------------------------------------
/active-response/command_search.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
4 | LOG="../logs/command_search.log"
5 | AR_LOG="${PWD}/../logs/active-responses.log"
6 | ACTION=$1
7 | USER=$2
8 | IP=$3
9 | ALERTID=$4
10 | RULEID=$5
11 | AGENT=$6
12 | SERVICE=$7
13 | FILENAME=$8
14 |
15 | die(){
16 | echo "$PRE $*" | tee -a -- "$LOG"
17 | exit 1
18 | }
19 |
20 | log(){
21 | echo "$PRE $*" >> "$LOG"
22 | exit 0
23 | }
24 |
25 | check_args(){
26 | local argc
27 | argc="$1"
28 | [[ $argc -ge 5 ]] || die "ERROR: Not enough arguments given"
29 | }
30 |
31 | get_alert(){
32 | local alert
33 | local alertid="$1"
34 | alert=$(awk -v ts=${alertid}: 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log)
35 | [[ "$alert" ]] || return 1
36 | echo "$alert"
37 | }
38 |
39 | get_msg(){
40 | local alert="$@"
41 | msg=$(echo "$alert" | grep '^[A-Z][a-z]\+ [0-9]\+ ')
42 | echo "$msg"
43 | }
44 |
45 | is_skip(){
46 | local msg="$@"
47 | # If we find pattern return 1 to skip logging
48 | echo "$msg" | egrep -q '\]: Updated: Bad protocol version identification|Bad protocol version identification| [iI]nvalid user |mess id 0x' && return 1
49 | return 0
50 | }
51 |
52 | search_commands(){
53 | local alert="$@"
54 | local p='[^a-zA-Z0-9\.=]'
55 | local na='[^a-zA-Z]' # No alpha
56 | local nan='[^a-zA-Z0-9]' # No alpha & numeric
57 | local recon="${p}id${p}|uname -a |uname${na}|${p}last${na}|${p}w${nan}|whoami|${p}who${na}|wtmp|btmp|shadow"
58 | local shells="${p}sh -i|bash -i |${p}bash |${p}sh -c |${p}sh "
59 | local escalation="${p}wget|${p}ftp |${p}curl|${p}fetch |${p}john${na}|${p}hashcat|crack|sniff|tcpdump|shark "
60 | local exploitation="msfpayload|msfcli|metasploit|meterpreter"
61 | local hiding="shred|HIST|history -"
62 | local commands="${recon}|${shells}|${escalation}|${hiding}"
63 | msg=$(get_msg "$alert")
64 | echo "$msg" | egrep -q -- "$commands" || return 1
65 | is_skip "$msg" || return 1 # Skip logs that we cannot match easily with patterns
66 | match="$(echo "$msg" | egrep -o -- "$commands")"
67 | echo "Matched $match in $msg"
68 | }
69 |
70 | # Check for arguments
71 | check_args $#
72 |
73 | LOCAL=$(dirname $0);
74 | cd $LOCAL
75 | cd ../
76 | PWD=$(pwd)
77 |
78 | # Logging the call
79 | [[ -f "$AR_LOG" ]] && echo "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8" >> $AR_LOG
80 |
81 | # Getting full alert
82 | ALERT=$(get_alert "$ALERTID") || exit
83 | # Skip alerts with multiple log lines
84 | [[ "$ALERT" =~ [Mm]ultiple ]] && exit
85 | # Get log messages if found command
86 | MSG=$(search_commands "$ALERT") || exit
87 |
88 | # Log
89 | log "WARNING: $MSG"
90 |
--------------------------------------------------------------------------------
/active-response/cymru_lookup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
4 | LOG="../logs/cymru_lookup.log"
5 | AR_LOG="${PWD}/../logs/active-responses.log"
6 | CDB="/var/ossec/lists/hashes.list"
7 | ACTION=$1
8 | USER=$2
9 | IP=$3
10 | ALERTID=$4
11 | RULEID=$5
12 | AGENT=$6
13 | SERVICE=$7
14 | FILENAME=$8
15 |
16 | die(){
17 | printf "$PRE $*\n" | tee -a $LOG
18 | exit 1
19 | }
20 |
21 | log(){
22 | printf "$PRE $*\n" | tee -a $LOG
23 | exit 0
24 | }
25 |
26 | send_cdb(){
27 | local checksum
28 | local file
29 | checksum="$1"
30 | file="$2"
31 | if [[ -w "$CDB" ]]; then
32 | printf "${checksum}:${file:-empty}\n" >> $CDB
33 | fi
34 | }
35 |
36 | check_cdb(){
37 | local checksum
38 | checksum="$1"
39 | if [[ -r "$CDB" ]]; then
40 | grep -q "$checksum" $CDB && die "Hash $checksum has been looked up previously"
41 | fi
42 | }
43 |
44 | LOCAL=$(dirname $0);
45 | cd $LOCAL
46 | cd ../
47 | PWD=$(pwd)
48 |
49 | # Logging the call
50 | [[ -f "$AR_LOG" ]] && printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> $AR_LOG
51 |
52 | # Getting full alert
53 | ALERT=$(awk -v ts=${ALERTID}: 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log)
54 |
55 | # Obtain hash
56 | SHA1=$(printf "$ALERT\n" | grep -o '[a-zA-Z0-9]\{40\}' | tail -n 1)
57 | [[ $SHA1 ]] || die "ERROR: No hash found (${ALERTID:-empty})"
58 |
59 | # Check if we've looked up this hash previously
60 | check_cdb "$SHA1"
61 |
62 | # Obtain other info for logging
63 | [[ "$FILENAME" ]] || FILENAME=$(printf "$ALERT\n" | awk -F "['']" '/^File|changed for:/ { print $2 }')
64 | [[ "$AGENT" ]] || AGENT=$(printf "$ALERT\n" | awk -F "[()]" '/syscheck/ { print $2 }' | tail -n 1)
65 |
66 | # Lookup
67 | result=$(timeout 1s dig +short ${SHA1}.malware.hash.cymru.com A)
68 |
69 | # Alert or exit
70 | [[ "$result" =~ '127.0.0.' ]] && log "WARNING: Malicious hash found for ${FILENAME:-(empty)} ($SHA1) on ${AGENT:-(empty)}"
71 | send_cdb "$SHA1" "$FILENAME"
72 | die "OK: No match found for ${FILENAME:-(empty)} ($SHA1) on ${AGENT:-(empty)}"
73 |
--------------------------------------------------------------------------------
/active-response/deb_lookup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
4 | LOG="../logs/deb_lookup.log"
5 | AR_LOG="${PWD}/../logs/active-responses.log"
6 | ALERTS_LOG="${PWD}/../logs/alerts/alerts.log"
7 | ACTION=$1
8 | USER=$2
9 | IP=$3
10 | ALERTID=$4
11 | RULEID=$5
12 | AGENT=$6
13 | [[ "$7" =~ syscheck ]] && FILENAME=$8 || FILENAME=$7
14 |
15 | die(){
16 | printf "$PRE $*\n" | tee -a $LOG
17 | exit 1
18 | }
19 |
20 | log(){
21 | printf "$PRE $*\n" | tee -a $LOG
22 | exit 0
23 | }
24 |
25 | get_hash(){
26 | # Obtain hash from lines e.g. "2eb1a3e346933962bdfbb7b118404b68 /bin/ls"
27 | local line="$@"
28 | printf "${line% *}\n"
29 | }
30 |
31 | deb_lookup() {
32 | local file="$1"
33 | results="$(dpkg -S "$file" 2>/dev/null)"
34 | [[ "$results" ]] || die "WARNING: $file not installed by package"
35 | pkg="${results%%:*}"
36 | hashfile="/var/lib/dpkg/info/$pkg.md5sums"
37 | [[ -s "$hashfile" ]] || die "ERROR: db $hashfile not available or empty"
38 | match="$(grep -w "${file#/}" "$hashfile")"
39 | db_hash=$(get_hash "$match")
40 | line="$(md5sum "$file")"
41 | fs_hash="$(get_hash "$line")"
42 | [[ "$db_hash" = "$fs_hash" ]] || return 1
43 | return 0
44 | }
45 |
46 | LOCAL=$(dirname $0);
47 | cd $LOCAL
48 | cd ../
49 | PWD=$(pwd)
50 |
51 | # Logging the call
52 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> $AR_LOG
53 | # System must be Debian based
54 | [[ -f /etc/debian_version ]] || exit 0
55 |
56 | # We need a filename
57 | [[ "$FILENAME" ]] || die "No file given"
58 |
59 | deb_lookup "$FILENAME" || die "WARNING: ${FILENAME:-(empty)} differs from DEB database"
60 | log "OK: ${FILENAME:-(empty)} DEB verification passed"
61 | exit 0
62 |
--------------------------------------------------------------------------------
/active-response/ldap_lookup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
4 | LOG="../logs/ldap_lookup.log"
5 | AR_LOG="${PWD}/../logs/active-responses.log"
6 | ALERTS_LOG="${PWD}/../logs/alerts/alerts.log"
7 | CDB="/var/ossec/lists/system_users.list"
8 | ACTION=$1
9 | USER=$2
10 | IP=$3
11 | ALERTID=$4
12 | RULEID=$5
13 | AGENT=$6
14 | LDAP_SERVER='ldaps://ldap.company.com/' # Replace
15 | OU='dc=blah,dc=company,dc=com' # Replace
16 |
17 | die(){
18 | printf "$PRE $*\n" | tee -a $LOG
19 | exit 1
20 | }
21 |
22 | log(){
23 | printf "$PRE $*\n" | tee -a $LOG
24 | exit 0
25 | }
26 |
27 | check_args(){
28 | local argc
29 | argc="$1"
30 | [[ $argc -ge 5 ]] || die "ERROR: Not enough arguments given"
31 | }
32 |
33 | ldap_lookup(){
34 | local user="$1"
35 | local server="$2"
36 | local ou="$3"
37 | ldap_info=$(ldapsearch -xLLL -b "$ou" -H "$server" "uid=$user" | awk '/uid: / { print $2 }')
38 | [[ "$ldap_info" ]] || return 1
39 | return 0
40 | }
41 |
42 | get_alert(){
43 | local alertid="$1"
44 | local alert
45 | alert=$(awk -v ts=${alertid}: 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log)
46 | [[ "$alert" ]] || return 1
47 | printf "$alert\n"
48 | }
49 |
50 | get_user(){
51 | local alert="$@"
52 | local user
53 | [[ "$alert" ]] || return 1
54 | user=$(echo "$alert" | awk '/^User: / { print $2 }')
55 | # Kerberos events and maybe others contain a realm/domain, trim domain
56 | user=${user%%@*}
57 | [[ "$user" ]] || return 1
58 | [[ "$user" == root ]] && return 1
59 | printf "$user\n"
60 | }
61 |
62 | get_ip(){
63 | local alert="$@"
64 | local ip
65 | [[ "$alert" ]] || return 1
66 | ip=$(echo "$alert" | awk '/^Src IP: / { print $3 }')
67 | [[ "$ip" ]] || return 1
68 | printf "$ip\n"
69 | }
70 |
71 | get_hostname(){
72 | local alert="$@"
73 | local host
74 | host=$(printf "$alert\n" | awk '/^20[0-9][0-9] [A-Z][a-z][a-z] [0-9][0-9]/ { print $5 }')
75 | # above returns strings as host->ip, we just want the IP
76 | host="${host##*>}"
77 | printf "$host\n"
78 | }
79 |
80 | check_cdb(){
81 | local username="$1"
82 | if [[ -r "$CDB" ]]; then
83 | grep -q "${username}:" $CDB || return 1
84 | fi
85 | }
86 |
87 | LOCAL=$(dirname $0);
88 | cd $LOCAL
89 | cd ../
90 | PWD=$(pwd)
91 |
92 | # Logging the call
93 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> $AR_LOG
94 |
95 | # Check for arguments
96 | check_args $#
97 |
98 | # Getting full alert
99 | ALERT=$(get_alert "$ALERTID") || { printf "Log for $ALERTID not found\n" && exit 1; }
100 | # Getting user
101 | USER=$(get_user "$ALERT") || { printf "User not found in $ALERTID\n" && exit 1; }
102 | # Check if system user and exit if true
103 | check_cdb "$USER" && { printf "User $USER found in $CDB\n" && exit 1; }
104 | # Getting source IP
105 | [[ "$IP" == - ]] && IP=$(get_ip "$ALERT")
106 | # Get hostname/ip for logs
107 | [[ "$AGENT" == - ]] && HOST=$(get_hostname "$ALERT") || HOST="$AGENT"
108 |
109 | MSG1="Employee lookup for attempted user"
110 | MSG2="Host ${HOST}, Src IP ${IP:-(empty)}, Orig. Alert $ALERTID"
111 | ldap_lookup "$USER" "$LDAP_SERVER" "$OU" || die "WARNING: $MSG1 ${USER}: $MSG2"
112 | log "OK: $MSG1 ${USER}: $MSG2"
113 | exit 0
114 |
--------------------------------------------------------------------------------
/active-response/makelists.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Generates CDB lists from intel files
3 | # Author: Jon Schipp
4 |
5 | LOCAL=$(dirname $0);
6 | cd $LOCAL
7 | cd ../
8 | PWD=$(pwd)
9 |
10 | # Logging the call
11 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> ${PWD}/../logs/active-responses.log
12 |
13 | # Build CDB databases
14 | /var/ossec/bin/ossec-makelists -c /var/ossec/etc/ossec.conf
15 |
--------------------------------------------------------------------------------
/active-response/puppetdb_lookup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PUPPETDB_SOCKET=http://puppet-master:8080
4 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
5 | LOG="../logs/puppetdb_lookup.log"
6 | AR_LOG="${PWD}/../logs/active-responses.log"
7 | ACTION=$1
8 | USER=$2
9 | IP=$3
10 | ALERTID=$4
11 | RULEID=$5
12 | AGENT="$6"
13 | SERVICE=$7
14 | FILENAME=$8
15 |
16 | LOCAL=$(dirname $0);
17 | cd $LOCAL
18 | cd ../
19 | PWD=$(pwd)
20 |
21 | die(){
22 | [[ -d $(dirname $LOG) ]] && printf "$PRE $*\n" | tee -a $LOG
23 | exit 1
24 | }
25 |
26 | log(){
27 | [[ -d $(dirname $LOG) ]] && printf "$PRE $*\n" | tee -a $LOG
28 | exit 0
29 | }
30 |
31 | check_args(){
32 | local argc
33 | [[ $1 ]] || die "ERROR: ${FUNCNAME}: no argument given"
34 | argc="$1"
35 | [[ $argc -ge 5 ]] || return 1
36 | }
37 |
38 | get_alert(){
39 | local id
40 | [[ $1 ]] || return 1
41 | id="$1"
42 | awk -v ts=${id}: 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log || return 1
43 | }
44 |
45 | get_host(){
46 | awk -F "[()]" '/syscheck/ { print $2 }' | tail -n 1
47 | }
48 |
49 | get_file(){
50 | awk -F "['']" '/^File|^New file|changed for:/ { print $2 }'
51 | }
52 |
53 | get_dns(){
54 | local host
55 | local fqdn
56 | [[ $1 ]] || return 1
57 | host="$1"
58 | #Add domains to search below
59 | for name in $host ${host}.domain1.com ${host}.domain2.com
60 | do
61 | is_fqdn="$(dig $name +short)"
62 | [[ "$is_fqdn" ]] && fqdn="$name $fqdn"
63 | done
64 | if [[ "$fqdn" ]]; then
65 | printf "${fqdn}\n"
66 | else
67 | return 1
68 | fi
69 | }
70 |
71 | query_api_for_file(){
72 | local file
73 | local name
74 | [[ $# -eq 2 ]] || die "ERROR: ${FUNCNAME}: Not enough arguments given"
75 | file="$1"
76 | name="$2"
77 | CURL="curl --connect-timeout 1 -X GET ${PUPPETDB_SOCKET}/v4/resources/File"
78 | JSON=$($CURL --data-urlencode 'query=["and",["=", "title", '\""$file"\"'],["=", "certname",'\""$name\""']]' 2>/dev/null)
79 | [[ "$JSON" ]] || die "ERROR: Something went wrong for API query, empty: ${dir}, $name"
80 | [[ "$JSON" =~ "$file" ]] ||
81 | return 1
82 | }
83 |
84 | query_api_for_dir(){
85 | local file
86 | local name
87 | [[ $# -eq 2 ]] || die "ERROR: ${FUNCNAME}: Not enough arguments given"
88 | file="$1"
89 | name="$2"
90 | dir=$(dirname $file)
91 | CURL="curl --connect-timeout 1 -X GET ${PUPPETDB_SOCKET}/v4/resources/File"
92 | JSON=$($CURL --data-urlencode 'query=["and",["=", "title", '\""$dir"\"'],["=", "certname",'\""$name\""']]' 2>/dev/null)
93 | [[ "$JSON" ]] || die "ERROR: Something went wrong for API query, empty: ${dir}, $name"
94 | [[ "$JSON" =~ "$dir" ]] &&
95 | [[ "$JSON" =~ directory ]] &&
96 | [[ "$JSON" =~ '"recurse" : true' ]] ||
97 | return 1
98 | }
99 |
100 | check_status(){
101 | local code
102 | [[ $1 ]] || die "ERROR: ${FUNCNAME}: No argument"
103 | code="$1"
104 | [[ $code -eq 0 ]] && log "OK: File $FILE managed by Puppet for $HOST_NAME" && return 0
105 | return 1
106 | }
107 |
108 | lookup_file(){
109 | local file
110 | [[ $1 ]] || return 1
111 | file="$1"
112 | printf "File: ${file}\nHost: ${HOST_NAME}\n"
113 | for name in $DNS_NAMES
114 | do
115 | query_api_for_file "$file" "$name"; check_status $? && return 0
116 | query_api_for_dir "$file" "$name"; check_status $? && return 0
117 | done
118 | return 1
119 | }
120 |
121 | # Logging the call
122 | [[ -f "$AR_LOG" ]] && printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 ${8}\n" >> $AR_LOG
123 |
124 | # Getting full alert if we're passed arguments (by OSSEC), otherwise assume manual use
125 | if check_args $#
126 | then
127 | ALERT=$(get_alert $ALERTID) || die "ERROR: Retrieving alert failed"
128 | # Verify we have alert
129 | [[ "$ALERT" ]] || die "ERROR: Alert not found by timestamp"
130 | fi
131 |
132 | # Get information we need if not in environment
133 | if ! [[ "$HOST_NAME" ]]; then
134 | HOST_NAME=$(printf "${ALERT}\n" | get_host) || die "ERROR: Retrieving hostname failed"
135 | fi
136 |
137 | if ! [[ "$FILENAME" ]]; then
138 | FILENAME=$(printf "${ALERT}\n" | get_file) || die "ERROR: Retreiving file failed for $HOST_NAME"
139 | fi
140 |
141 | # Check for valid DNS names so we don't send more requests to puppet then we have to
142 | DNS_NAMES=$(get_dns $HOST_NAME) || die "ERROR: No matching FQDN found for $HOST_NAME"
143 |
144 | # Lookup file in PuppetDB
145 | lookup_file $FILENAME || die "WARNING: File ${FILENAME:-($ALERTID)} not found for $HOST_NAME"
146 |
--------------------------------------------------------------------------------
/active-response/rpm_lookup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
4 | LOG="../logs/rpm_lookup.log"
5 | AR_LOG="${PWD}/../logs/active-responses.log"
6 | ALERTS_LOG="${PWD}/../logs/alerts/alerts.log"
7 | ACTION=$1
8 | USER=$2
9 | IP=$3
10 | ALERTID=$4
11 | RULEID=$5
12 | AGENT=$6
13 | [[ "$7" =~ syscheck ]] && FILENAME=$8 || FILENAME=$7
14 |
15 | die(){
16 | printf "$PRE $*\n" | tee -a $LOG
17 | exit 1
18 | }
19 |
20 | log(){
21 | printf "$PRE $*\n" | tee -a $LOG
22 | exit 0
23 | }
24 |
25 | file_type_lookup(){
26 | local rpm_info
27 | rpm_info="$1"
28 | info=$(printf "$rpm_info" | awk '{ print $2 }')
29 | TYPE="Regular file" # Default to regular file, make changes after
30 | [[ "$info" == "$FILENAME" ]] && return 0 # True if $2 in rpm_info was empty (no file type info)
31 | [[ "$info" =~ c ]] && TYPE="Config file"
32 | [[ "$info" =~ d ]] && TYPE="Documentation file"
33 | [[ "$info" =~ g ]] && TYPE="GHost file"
34 | [[ "$info" =~ l ]] && TYPE="License file"
35 | [[ "$info" =~ r ]] && TYPE="Readme file"
36 | }
37 |
38 | file_status_lookup(){
39 | local rpm_flags
40 | rpm_flags="$1"
41 | [[ "$rpm_flags" =~ missing ]] && STATUS="File is missing"
42 | [[ "$STATUS" ]] || STATUS="Size change"
43 | }
44 |
45 | rpm_lookup(){
46 | local file
47 | file="$1"
48 | rpm_info=$(rpm -Vvf "$1" 2>/dev/null | fgrep -w "$1")
49 | [[ "$rpm_info" ]] || die "No flags output found for $file"
50 | file_type_lookup "$rpm_info"
51 | rpm_flags="$(printf "$rpm_info" | cut -d " " -f1)"
52 | [[ "$rpm_flags" ]] || die "No flags found for $file"
53 | file_status_lookup "$rpm_flags"
54 | [[ "$rpm_flags" =~ S|5|missing ]] || return 0
55 | return 1
56 | }
57 |
58 | LOCAL=$(dirname $0);
59 | cd $LOCAL
60 | cd ../
61 | PWD=$(pwd)
62 |
63 | # Logging the call
64 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> $AR_LOG
65 | # System must be Red Hat based
66 | [[ -f /etc/redhat-release ]] || exit 0
67 |
68 | # We need a filename
69 | [[ "$FILENAME" ]] || die "No file given"
70 |
71 | rpm_lookup "$FILENAME" || die "WARNING: ${FILENAME:-(empty)} ($TYPE) differs from RPM database ($STATUS)"
72 | log "OK: ${FILENAME:-(empty)} ($TYPE) RPM verification passed"
73 | exit 0
74 |
--------------------------------------------------------------------------------
/active-response/rule-all.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | CHAT=/usr/local/bin/ircsay
4 | PROG=OSSEC
5 | SCRIPT=$0
6 | CHANNEL="#ossec-alerts"
7 | MAIL=user@org
8 | ACTION=$1
9 | USER=$2
10 | IP=$3
11 | ALERTID=$4
12 | RULEID=$5
13 |
14 | [[ "$*" =~ syscheck ]] && exit
15 |
16 | # This scripts calls others because only one can be executed by OSSEC
17 |
18 | die(){
19 | if [ -f ${COWSAY:-none} ]; then
20 | $COWSAY -d "$*"
21 | else
22 | printf "$*\n"
23 | fi
24 | exit 0
25 | }
26 |
27 | is_ip(){
28 | [[ $IP ]] || return 1
29 | [[ $IP == '-' ]] && return 1
30 | return 0
31 | }
32 |
33 | LOCAL=$(dirname $0);
34 | cd $LOCAL
35 | cd ../
36 | PWD=$(pwd)
37 | CHAT="$PWD/bin/alert2chat.sh"
38 | CIF="$PWD/bin/cif.sh"
39 | BHR="$PWD/bin/bhr.sh"
40 | CDB="$PWD/bin/add_to_cdb.sh"
41 | CMDS="$PWD/bin/command_search.sh"
42 | TS="$PWD/bin/time_lookup.sh"
43 | LDAP="$PWD/bin/ldap_lookup.sh"
44 |
45 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> ${PWD}/../logs/active-responses.log
46 |
47 | # Chat
48 | [[ -x $CHAT ]] && $CHAT $1 $2 $3 $4 $5
49 |
50 | # Collect system user accounts from 'new user' events, only work on rule 5902
51 | [[ -x $CDB ]] && [[ $RULEID -eq 5902 ]] && $CDB $1 $2 $3 $4 $5
52 |
53 | # Search for suspicious commands
54 | [[ -x $CMDS ]] && $CMDS $1 $2 $3 $4 $5
55 |
56 | # Check if system's clock is off
57 | [[ -x $TS ]] && $TS $1 $2 $3 $4 $5
58 |
59 | # Lookup user's in LDAP
60 | [[ -x $LDAP ]] && $LDAP $1 $2 $3 $4 $5 $6
61 |
62 | # CIF Feed
63 | is_ip && [[ -x $CIF ]] && $CIF $1 $2 $3 $4 $5
64 |
65 | # BHR Block
66 | is_ip && [[ -x $BHR ]] && $BHR $1 $2 $3 $4 $5
67 |
--------------------------------------------------------------------------------
/active-response/syscheck-all.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | SCRIPT=$0
4 | ACTION=$1
5 | USER=$2
6 | IP=$3
7 | ALERTID=$4
8 | RULEID=$5
9 | AGENT=$6
10 | SERVICE=$7
11 | FILENAME=$8
12 |
13 | # This scripts calls others because only one can be executed by OSSEC
14 |
15 | # Match syscheck rules
16 | [[ "$*" =~ syscheck ]] || exit
17 | [[ $RULEID =~ ^(51[069]|55[0-5]|580|59[5-8])$ ]] || exit
18 |
19 | die(){
20 | if [ -f ${COWSAY:-none} ]; then
21 | $COWSAY -d "$*"
22 | else
23 | printf "$*\n"
24 | fi
25 | exit 0
26 | }
27 |
28 | LOCAL=$(dirname $0);
29 | cd $LOCAL
30 | cd ../
31 | PWD=$(pwd)
32 | PUPPET="$PWD/bin/puppetdb_lookup.sh"
33 | CHAT="$PWD/bin/syscheck2chat.sh"
34 | CYMRU="$PWD/bin/cymru_lookup.sh"
35 | VT="$PWD/bin/virustotal_lookup.sh"
36 |
37 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> ${PWD}/../logs/active-responses.log
38 |
39 | # Puppet
40 | [[ -x $PUPPET ]] && $PUPPET $1 $2 $3 $4 $5 $6 $7 $8; found="$?"
41 | [[ $found ]] && [[ $found -eq 0 ]] && exit 0 # If we made it here, file is managed by puppet and we no longer care
42 |
43 | # Chat
44 | [[ -x $CHAT ]] && $CHAT $1 $2 $3 $4 $5 $6 $7 $8
45 |
46 | # Cymru Hash Lookup
47 | [[ -x $CYMRU ]] && $CYMRU $1 $2 $3 $4 $5 $6 $7 $8
48 |
49 | # Virus Total Hash Lookup
50 | [[ -x $VT ]] && $VT $1 $2 $3 $4 $5 $6 $7 $8
51 |
--------------------------------------------------------------------------------
/active-response/syscheck2chat.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Author: Jon Schipp
3 |
4 | CHAT=/usr/local/bin/ircsay
5 | CHANNEL="#ossec-syscheck"
6 | ACTION=$1
7 | USER=$2
8 | IP=$3
9 | ALERTID=$4
10 | RULEID=$5
11 |
12 | # Check for chat program
13 | [[ -f $CHAT ]] || exit 1
14 |
15 | # Match syscheck rules
16 | [[ "$*" =~ syscheck ]] || exit
17 | [[ $RULEID =~ ^(51[069]|55[0-5]|580|59[5-8])$ ]] || exit
18 |
19 | LOCAL=$(dirname $0);
20 | cd $LOCAL
21 | cd ../
22 | PWD=$(pwd)
23 |
24 | # Logging the call
25 | printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> ${PWD}/../logs/active-responses.log
26 |
27 | # Getting full alert
28 | awk -v ts=$ALERTID 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log | $CHAT "$CHANNEL" -
29 |
--------------------------------------------------------------------------------
/active-response/time_lookup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
4 | LOG="../logs/time_lookup.log"
5 | AR_LOG="${PWD}/../logs/active-responses.log"
6 | ACTION=$1
7 | USER=$2
8 | IP=$3
9 | ALERTID=$4
10 | RULEID=$5
11 | AGENT=$6
12 | SERVICE=$7
13 | FILENAME=$8
14 | MAX_TIME=300 # 5 min in seconds
15 |
16 | die(){
17 | printf "$PRE $*\n" | tee -a -- "$LOG"
18 | exit 1
19 | }
20 |
21 | log(){
22 | printf "$PRE $*\n" >> "$LOG"
23 | exit 0
24 | }
25 |
26 | check_args(){
27 | local argc
28 | argc="$1"
29 | [[ $argc -ge 5 ]] || die "ERROR: Not enough arguments given"
30 | }
31 |
32 | get_alert(){
33 | local alert
34 | local alertid="$1"
35 | alert=$(awk -v ts=${alertid}: 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log)
36 | [[ "$alert" ]] || return 1
37 | printf "$alert\n"
38 | }
39 |
40 | convert_timestamp(){
41 | local ts="$@"
42 | ts=$(date --date="$ts" +%s)
43 | [[ "$ts" ]] || return 1
44 | if [[ ${#ts} -eq 10 ]]; then
45 | printf "$ts\n"
46 | else
47 | return 1
48 | fi
49 | }
50 |
51 | get_timestamp(){
52 | local alert="$@"
53 |
54 | # Attempt to get timestamp from log by format
55 | ts=$(printf "$alert\n" | grep -o '^20[1-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}T[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}\.[0-9]\{6\}')
56 | if [[ "$ts" ]]; then
57 | epoch=$(convert_timestamp "$ts")
58 | if [[ "$epoch" ]]; then
59 | printf "$epoch"
60 | return 0
61 | fi
62 | fi
63 |
64 | ts=$(printf "$alert\n" | grep -o '^[A-Z][a-z]\+ [0-9]\{2\} [0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}')
65 | if [[ "$ts" ]]; then
66 | epoch=$(convert_timestamp "$ts")
67 | if [[ "$epoch" ]]; then
68 | printf "$epoch"
69 | return 0
70 | fi
71 | fi
72 | # Add more patterns to extract your logs timestamp if they differ
73 |
74 | # If we made it here none of the timestamps matched
75 | return 1
76 | }
77 |
78 | get_utc_timestamp(){
79 | local alert="$@"
80 | local ossec_ts
81 | local year
82 | local datetime
83 | local ossec_ts
84 | local ossec_epoch
85 | # Attempt to get OSSEC alert timestamp (UTC)
86 | ossec_ts=$(printf "$alert\n" | grep -o '^20[1-9]\{2\} [A-Z][a-z]\+ [0-9]\{2\} [0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}')
87 | if [[ "$ossec_ts" ]]; then
88 | # date: invalid date `2016 Feb 25 00:00:17'
89 | # Format so year is at end
90 | year="${ossec_ts%% *}"
91 | datetime="${ossec_ts#* }"
92 | ossec_ts="${datetime} ${year}"
93 | ossec_epoch=$(convert_timestamp "$ossec_ts") || return 1
94 | fi
95 | printf "$ossec_epoch\n"
96 | }
97 |
98 | get_hostname(){
99 | local alert="$@"
100 | local host
101 | host=$(printf "$alert\n" | awk '/^2016 [A-Z][a-z][a-z] [0-9][0-9]/ { print $5 }')
102 | printf "$host\n"
103 | }
104 |
105 | check_existing_entry(){
106 | local host="$1"
107 | fgrep -q "$host" $LOG && return 1
108 | return 0
109 | }
110 |
111 | is_utc(){
112 | local ossec_ts="$1"
113 | local log_ts="$2"
114 | local tz=0
115 | utc=$((log_ts-3600*5))
116 | dst1=$((log_ts-3600*6))
117 | dst2=$((log_ts-3600*7))
118 | for ts in $utc $dst1 $dst2; do
119 | result=$((ossec_ts-ts))
120 | [[ $result -eq 18000 ]] && tz=1
121 | [[ $result -eq 21600 ]] && tz=1
122 | [[ $result -eq 25200 ]] && tz=1
123 | done
124 | return $tz
125 | }
126 |
127 | check_timestamp(){
128 | local log_ts="$1"
129 | local host="$2"
130 | local current=$(date +"%s")
131 | seconds="$((current-log_ts))"
132 | [[ $seconds -lt 0 ]] && die "WARNING: system clock is ahead by $seconds seconds for $host on ${ALERTID}, ${current}-${log_ts}"
133 | [[ $seconds -lt $MAX_TIME ]] || die "WARNING: system clock is behind by $seconds seconds for $host on ${ALERTID}, ${current}-${log_ts}"
134 | }
135 |
136 | # Check for arguments
137 | check_args $#
138 |
139 | LOCAL=$(dirname $0);
140 | cd $LOCAL
141 | cd ../
142 | PWD=$(pwd)
143 |
144 | # Logging the call
145 | [[ -f "$AR_LOG" ]] && printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> $AR_LOG
146 |
147 | # Getting full alert
148 | ALERT=$(get_alert "$ALERTID") || exit
149 | # Skip if alert has multiple logs
150 | [[ "$ALERT" =~ [Mm]ultiple ]] && exit
151 | # Get timestamp
152 | TS=$(get_timestamp "$ALERT") || die "ERROR: get_timestamp: No timestamp found for $ALERTID"
153 | # Get hostname for logs
154 | HOST=$(get_hostname "$ALERT")
155 | # Don't run on ossec logs
156 | [[ $HOST =~ ossec ]] && exit
157 |
158 | # Skip writing warning logs if there's an entry for this host already
159 | # This will cut down on alerts
160 | check_existing_entry "$HOST" || exit 1
161 |
162 | UTC_EPOCH=$(get_utc_timestamp "$ALERT") || die "ERROR: get_utc_timestamp: utc epoch not found in alert"
163 | is_utc "$UTC_EPOCH" "$TS" || die "WARNING: System clock for $HOST is not localtime on ${ALERTID}, Alert_UTC:${UTC_EPOCH} Log_Local:${TS}"
164 | check_timestamp "$TS" "$HOST"
165 |
--------------------------------------------------------------------------------
/active-response/virus_total.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import json
3 | import urllib
4 | import urllib2
5 | import sys
6 |
7 | apikey = ''
8 |
9 | def usage():
10 | print '''Submit hash to virtus-total
11 | (Place your VirusTotal apikey in this script)
12 | Usage: %s ''' % sys.argv[0]
13 | exit(1)
14 |
15 | def collect(data):
16 | retrieve = data[0]
17 | sha1 = retrieve['sha1']
18 | filenames = retrieve['filenames']
19 | first_seen = retrieve['first-seen']
20 | last_seen = retrieve['last-seen']
21 | last_scan_permalink = retrieve['last-scan-permalink']
22 | last_scan_report = retrieve['last-scan-report']
23 | return sha1, filenames, first_seen, last_seen, last_scan_permalink, last_scan_report
24 |
25 | def msg(sha1, filenames, first_seen, last_seen, last_scan_permalink):
26 | print '''===Suspected Malware Item===
27 | SHA1: %s
28 | Filenames: %s
29 | First Seen: %s
30 | Last Seen: %s
31 | Link: %s''' % (sha1, filenames, first_seen, last_seen, last_scan_permalink)
32 |
33 | def is_malware(last_scan_report):
34 | for av, scan in last_scan_report.iteritems():
35 | if scan[0] is not None:
36 | return True
37 | return False
38 |
39 | def in_database(data, mhash):
40 | result = data[0]['result']
41 | if result == 0:
42 | return False
43 | return True
44 |
45 | def arguments():
46 | if len(sys.argv) < 2:
47 | usage()
48 | if '-h' in sys.argv[1]:
49 | usage()
50 | if not apikey:
51 | print "Set apikey in %s to value of your Virus Total key" % sys.argv[0]
52 | exit(1)
53 |
54 | mhash = sys.argv[1]
55 | return mhash
56 |
57 | def query_api(mhash, apikey):
58 | url = "http://api.vtapi.net/vtapi/get_file_infos.json"
59 | parameters = {"resources": mhash, "apikey": apikey}
60 | encoded = urllib.urlencode(parameters)
61 | req = urllib2.Request(url, encoded)
62 | response = urllib2.urlopen(req)
63 | response_string = response.read()
64 | data = json.loads(response_string)
65 | return data
66 |
67 | mhash = arguments()
68 | data = query_api(mhash, apikey)
69 |
70 | if not in_database(data, mhash):
71 | print 'No entry for %s in database' % mhash
72 | exit(1)
73 |
74 | # Positive match found
75 | sha1, filenames, first_seen, last_seen, last_scan_permalink, last_scan_report = collect(data)
76 | if is_malware(last_scan_report):
77 | msg(sha1, filenames, first_seen, last_seen, last_scan_permalink)
78 | exit(0)
79 | else:
80 | print 'Entry %s is not malicious' % mhash
81 | exit(1)
82 |
--------------------------------------------------------------------------------
/active-response/virustotal_lookup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Author: Jon Schipp
3 | PRE="$(date +"%Y-%m-%dT%T.%6N%:z") $(hostname -s) $(basename $0):"
4 | LOG="../logs/virustotal_lookup.log"
5 | AR_LOG="${PWD}/../logs/active-responses.log"
6 | CDB="/var/ossec/lists/hashes.list"
7 | ACTION=$1
8 | USER=$2
9 | IP=$3
10 | ALERTID=$4
11 | RULEID=$5
12 | AGENT=$6
13 | SERVICE=$7
14 | FILENAME=$8
15 |
16 | die(){
17 | [[ -d $(dirname $LOG) ]] && printf "$PRE $*\n" | tee -a $LOG
18 | exit 1
19 | }
20 |
21 | log(){
22 | [[ -d $(dirname $LOG) ]] && printf "$PRE $*\n" | tee -a $LOG
23 | exit 0
24 | }
25 |
26 | check_args(){
27 | local argc
28 | argc="$1"
29 | [[ $argc -ge 5 ]] || die "ERROR: Not enough arguments given"
30 | }
31 |
32 |
33 | get_alert(){
34 | local alert
35 | alert=$(awk -v ts=${ALERTID}: 'BEGIN { RS=""; ORS="\n" } $0 ~ ts { print }' ${PWD}/../logs/alerts/alerts.log)
36 | [[ "$alert" ]] || return 1
37 | printf "$alert\n"
38 | }
39 |
40 | is_hash(){
41 | local sha1
42 | sha1="$1"
43 | [[ $sha1 ]] || die "ERROR: ${FUNCNAME}: Hash variable empty"
44 | [[ $sha1 =~ ^\ +$ ]] && die "ERROR: ${FUNCNAME}: Hash variable is only whitespace"
45 | len="${#sha1}"; [[ $len -eq 40 ]] || die "ERROR: ${FUNCNAME}: The Hash variable does not contain a SHA1 hash"
46 | }
47 |
48 | get_hash_from_alert(){
49 | local alert
50 | local sha1
51 | alert="$@"
52 | [[ "$FILENAME" ]] || FILENAME=$(printf "$alert\n" | awk -F "['']" '/^File|changed for:/ { print $2 }')
53 | sha1=$(printf "$alert\n" | grep -o '[a-zA-Z0-9]\{40\}' | tail -n 1)
54 | printf "$sha1\n"
55 | }
56 |
57 | get_hash_from_filename(){
58 | local alert
59 | local sha1
60 | alert="$@"
61 | [[ "$FILENAME" ]] || FILENAME=$(printf "$alert\n" | awk -F "['']" '/^File|changed for:/ { print $2 }')
62 | [[ -r "$FILENAME" ]] || die "ERROR: ${FUNCNAME}: File not available on system"
63 | sha1=$(sha1sum $file | grep -o '[a-zA-Z0-9]\{40\}')
64 | printf "$sha1\n"
65 | }
66 |
67 | virus_lookup(){
68 | local sha1
69 | local results
70 | sha1="$1"
71 | results=$($PWD/bin/virus_total.py $sha1)
72 | status_code=$?
73 | printf "$results\n"
74 | return $status_code
75 | }
76 |
77 | is_executable(){
78 | local file
79 | file="$1"
80 | file -L "$file" 2>/dev/null | egrep -i -q 'script|exec|ELF|object|stripped|linked' || return 1
81 | return 0
82 | }
83 |
84 | send_cdb(){
85 | local checksum
86 | local file
87 | checksum="$1"
88 | file="$2"
89 | if [[ -w "$CDB" ]]; then
90 | printf "${checksum}:${file:-empty}\n" >> $CDB
91 | fi
92 | }
93 |
94 | check_cdb(){
95 | local checksum
96 | checksum="$1"
97 | if [[ -r "$CDB" ]]; then
98 | grep -q "$checksum" $CDB && exit 0
99 | fi
100 | }
101 |
102 | # Check for arguments
103 | check_args $#
104 |
105 | LOCAL=$(dirname $0);
106 | cd $LOCAL
107 | cd ../
108 | PWD=$(pwd)
109 |
110 | # Logging the call
111 | [[ -f "$AR_LOG" ]] && printf "$(date) $0 $1 $2 $3 $4 $5 $6 $7 $8\n" >> $AR_LOG
112 |
113 | # Getting full alert
114 | ALERT=$(get_alert) || die "ERROR: get_alert: No alert found matching timestamp $ALERTID"
115 |
116 | # Obtain hash
117 | [[ $RULEID -eq 554 ]] && HASH=$(get_hash_from_filename "$ALERT") || HASH=$(get_hash_from_alert "$ALERT")
118 | # Grab host for logging
119 | [[ "$AGENT" ]] || AGENT=$(printf "$ALERT\n" | awk -F "[()]" '/syscheck/ { print $2 }' | tail -n 1)
120 |
121 | # Only perform lookups for executable files
122 | is_executable "$FILENAME" || die "OK: ${FILENAME:-(empty)} is not an executable file"
123 |
124 | # Verify we do indeed have a hash
125 | is_hash "$HASH"
126 |
127 | # Check if we've looked up this hash previously
128 | check_cdb "$HASH"
129 |
130 | # Lookup
131 | RESULTS=$(virus_lookup $HASH) || { send_cdb "$HASH" "$FILENAME" && die "OK: No malware found for ${FILENAME:-(empty)} ${HASH:-(empty)} on ${AGENT:-(empty)}"; }
132 |
133 | # Log
134 | log "Malicious hash found for ${FILENAME:-(empty)} ($HASH) on ${AGENT:-(empty)}"
135 |
--------------------------------------------------------------------------------
/decoders/active_response.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 | ^rpm_lookup.sh$
9 |
10 |
11 |
12 | rpm_lookup
13 | ^(\S+): (\S+)
14 | status, id
15 |
16 |
17 |
23 |
24 | ^deb_lookup.sh$
25 |
26 |
27 |
28 | deb_lookup
29 | ^(\S+): (\S+)
30 | status, id
31 |
32 |
33 |
39 |
40 | ^puppetdb_lookup.sh$
41 |
42 |
43 |
44 | puppetdb_lookup
45 | ^(\S+): File (\S+)
46 | status, id
47 |
48 |
49 |
56 |
57 | ^cymru_lookup.sh$
58 |
59 |
60 |
61 | cymru_lookup
62 | ^(\S+): No match found for \S+ \((\S+)\)
63 | status, id
64 |
65 |
66 |
67 | cymru_lookup
68 | ^(\S+): Malicious hash found for \S+ \((\S+)\)
69 | status, id
70 |
71 |
72 |
79 |
80 | ^ldap_lookup.sh$
81 |
82 |
83 |
84 | ldap_lookup
85 | ^(\S+): NCSA employee lookup for attempted user (\w+): Host (\d+.\d+.\d+.\d+), Src IP (\d+.\d+.\d+.\d+),
86 | status, user, dstip, srcip
87 |
88 |
--------------------------------------------------------------------------------
/decoders/kerberos_decoder.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 | ^ksu
14 |
15 |
16 |
17 | kerberos_ksu
18 | ^'ksu \S+' authentication failed
19 | ^'ksu (\S+)' authentication failed for (\S+) on \S+$
20 | dstuser, id
21 |
22 |
23 |
24 | kerberos_ksu
25 | ^'ksu \S+' authenticated
26 | ^'ksu (\S+)' authenticated (\S+) for (\S+) on \S+$
27 | dstuser, id, extra_data
28 |
29 |
30 |
31 | kerberos_ksu
32 | ^Account \S+: authorization for \S+ successful
33 | ^Account (\S+): authorization for (\S+) successful$
34 | dstuser, id
35 |
36 |
37 |
38 | kerberos_ksu
39 | ^Account \S+: authorization for \S+ for execution of \S+ successful
40 | ^Account (\S+): authorization for (\S+) for execution of (\S+) successful$
41 | dstuser, id, extra_data
42 |
43 |
44 |
53 |
54 |
55 | ^kadmind
56 |
57 |
58 |
59 | kerberos_kadmin
60 | ^Request: kadm5_create_principal,
61 | ^Request: kadm5_create_principal, (\S+), (\S+), client=(\S+), service=\S+, addr=(\S+)$
62 | dstuser, status, id, srcip
63 |
64 |
65 |
66 | kerberos_kadmin
67 | ^Request: kadm5_get_principal,
68 | ^Request: kadm5_get_principal, (\S+), (\S+), client=(\S+), service=\S+, addr=(\S+)$|
69 | ^Request: kadm5_get_principal, (\S+), (Principal does not exist), client=(\S+), service=\S+, addr=(\S+)$
70 | dstuser, status, id, srcip
71 |
72 |
73 |
74 | kerberos_kadmin
75 | ^Request: kadm5_modify_principal,
76 | ^Request: kadm5_modify_principal, (\S+), (\S+), client=(\S+), service=\S+, addr=(\S+)$|
77 | ^Request: kadm5_modify_principal, (\S+), (Principal does not exist), client=(\S+), service=\S+, addr=(\S+)$
78 | dstuser, status, id, srcip
79 |
80 |
--------------------------------------------------------------------------------
/decoders/sudo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
33 |
34 | ^sudo
35 | ^\s+(\S+)\s:\sTTY=\S+\s;\sPWD=(\S+)\s;\sUSER=(\S+)\s;\sCOMMAND=(\.+)$
36 | id,url,dstuser,status
37 | name,user,location
38 | First time user executed the sudo command
39 |
40 |
--------------------------------------------------------------------------------
/munin/ossec_ar_stats:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | FILE=/var/ossec/logs/active-responses.log
3 | DIR=/var/ossec/active-response/bin
4 | config(){
5 | cat <= crit:
76 | return NAGIOS_CRITICAL
77 | if inactive_agents >= warn:
78 | return NAGIOS_WARNING
79 | return NAGIOS_OK
80 |
81 | def is_ossec(path):
82 | if os.path.isdir(path):
83 | files = [
84 | 'etc/ossec.conf',
85 | 'etc/shared/agent.conf',
86 | 'bin/syscheck_control',
87 | 'bin/rootcheck_control',
88 | 'bin/agent_control'
89 | ]
90 | for f in files:
91 | fp = path + '/' + f
92 | if not os.path.isfile(fp):
93 | print "Error: Installation missing file %s" % fp
94 | return False
95 | return True
96 |
97 | def get_output_dict(cmd, arg):
98 | c=0
99 | data={}
100 | command = path + '/' + cmd
101 | result = subprocess.Popen([command,arg],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
102 | set_result = set(result.stdout)
103 | for i in set_result:
104 | data[c]= [i.split(',')]
105 | c += 1
106 | return data
107 |
108 | def get_output_set(cmd, arg):
109 | command = path + '/' + cmd
110 | result = subprocess.Popen([command,arg],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
111 | data = set(result.stdout)
112 | return data
113 |
114 | def is_server():
115 | filename = '/etc/ossec-init.conf'
116 | try:
117 | with open(filename, 'r') as f:
118 | contents = f.readlines()
119 | install_type = contents[3].strip().split('=')[1]
120 | if install_type == '"server"':
121 | return True
122 | else:
123 | print "ERROR: Unable to detect OSSEC server: %s" % install_type
124 | return False
125 |
126 | except IOError:
127 | print "ERROR: Cannot open file %s. Is OSSEC installed?" % filename
128 | exit(NAGIOS_UNKNOWN)
129 |
130 | def open_queue(filename, service):
131 | try:
132 | with open(filename, 'r') as f:
133 | for i in f.readlines():
134 | if service == 'syscheck':
135 | if 'Starting syscheck scan.' in i:
136 | syscheck_start_ts = i[1:11]
137 | return syscheck_start_ts
138 | if service == 'rootcheck':
139 | if 'Starting rootcheck scan.' in i:
140 | rootcheck_start_ts = i[1:11]
141 | return rootcheck_start_ts
142 | else:
143 | return False
144 | except IOError:
145 | print "ERROR: Cannot read queue file %s" % filename
146 | exit(NAGIOS_UNKNOWN)
147 |
148 | def older_than(ts, name, crit_ts, warn_ts):
149 | if not ts:
150 | print "%s: %s: LastCheckTime: Unknown" % (STATUS_MSG[NAGIOS_UNKNOWN], name)
151 | return NAGIOS_UNKNOWN
152 | service_timestamp = datetime.datetime.fromtimestamp(float(ts))
153 | if service_timestamp < crit_ts:
154 | print "%s: %s: LastCheckTime: %s" % (STATUS_MSG[NAGIOS_CRITICAL], name, service_timestamp)
155 | return NAGIOS_CRITICAL
156 | elif service_timestamp < warn_ts:
157 | print "%s: %s: LastCheckTime: %s" % (STATUS_MSG[NAGIOS_WARNING], name, service_timestamp)
158 | return NAGIOS_WARNING
159 | else:
160 | return NAGIOS_OK
161 |
162 | def check_connected(agents, skip, crit, warn):
163 | data = get_output_dict('bin/agent_control', '-l')
164 | c=0
165 | inactive_agents = 0
166 | active = [ 'Active', 'Active/Local' ]
167 | notactive = {}
168 |
169 | for i in data:
170 | line = data[c][0]
171 | c += 1
172 | # Check for lines with fields we need
173 | # Extract agent name and status message
174 | if len(line) == 4:
175 | name=line[1][6:].lstrip()
176 | status=line[3].lstrip().rstrip()
177 |
178 | # If --agents is specified only check for specified agents
179 | if agents:
180 | if name not in agents:
181 | continue
182 | else:
183 | # If --agents is not specified check all except in skip
184 | if name in skip:
185 | continue
186 |
187 | if status not in active:
188 | notactive[name] = status
189 | inactive_agents += 1
190 |
191 | # Check if inactive agents were fonud
192 | if notactive:
193 | exit_code = threshold(inactive_agents, crit, warn)
194 | print "%s: %d agents not connected" % (STATUS_MSG[exit_code], len(notactive))
195 | for k,v in notactive.items():
196 | print "Name: %s, Status: %s" % (k,v)
197 | return exit_code
198 | else:
199 | print "%s: All agents connected" % STATUS_MSG[NAGIOS_OK]
200 | return NAGIOS_OK
201 |
202 | def check_service(service, agents, skip, crit, warn):
203 | dir = os.path.join(path,'queue/rootcheck')
204 | crit_ts = datetime.datetime.now() - datetime.timedelta(hours=crit)
205 | warn_ts = datetime.datetime.now() - datetime.timedelta(hours=warn)
206 | status_list = []
207 | for queue in os.listdir(dir):
208 | name = queue.strip('()').split()[0].rstrip(')')
209 | if agents:
210 | if name not in agents:
211 | continue
212 | else:
213 | if name == 'rootcheck':
214 | continue
215 | if name in skip:
216 | continue
217 | f = os.path.join(dir, queue)
218 | if os.path.isfile(f):
219 | ts = open_queue(f, service)
220 | status = older_than(ts, name, crit_ts, warn_ts)
221 | status_list.append(status)
222 | exit_code = max(status_list)
223 | if exit_code == NAGIOS_OK:
224 | print "%s: Agent %s runtimes are up to date" % (STATUS_MSG[NAGIOS_OK],service)
225 | return exit_code
226 |
227 | def check_status(agents, skip):
228 | data = get_output_set('bin/ossec-control', 'status')
229 | not_running=[]
230 | for i in data:
231 | # If --skip is used skip these
232 | if i in skip:
233 | continue
234 | # Add names of services not running to list
235 | if 'not running' in i:
236 | not_running.append(i)
237 | continue
238 | # Test for entries in list. Entries mean something wasn't running
239 | if not_running:
240 | print "%s: Some services running" % STATUS_MSG[NAGIOS_CRITICAL]
241 | for i in not_running:
242 | print i.rstrip()
243 | return NAGIOS_CRITICAL
244 | else:
245 | print "%s: All services running" % STATUS_MSG[NAGIOS_OK]
246 | return NAGIOS_OK
247 |
248 | def main():
249 | option, agents, skip, crit, warn = arguments()
250 |
251 | if not is_ossec(path):
252 | exit(NAGIOS_UNKNOWN)
253 | if not is_server():
254 | exit(NAGIOS_UNKNOWN)
255 |
256 | if option == "connected":
257 | exit(check_connected(agents, skip, crit, warn))
258 | elif option == "syscheck":
259 | exit(check_service(option, agents, skip, crit, warn))
260 | elif option == "rootcheck":
261 | exit(check_service(option, agents, skip, crit, warn))
262 | elif option == "status":
263 | exit(check_status(agents, skip))
264 | else:
265 | print "Invalid type option"
266 | exit(NAGIOS_UNKNOWN)
267 |
268 | if __name__ == "__main__":
269 | main()
270 |
--------------------------------------------------------------------------------
/nagios/check_ossec.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Author: Jon Schipp
4 | # Date: 11-07-2013
5 | ########
6 | # Examples:
7 |
8 | # 1.) Check status of OSSEC services excluding active response i.e. execd
9 | # $ ./check_ossec.sh -s execd
10 |
11 | # 2.) Check status of OSSEC agent
12 | # $ ./check_ossec.sh -a server1
13 |
14 | # 3.) Check status of multiple OSSEC agents
15 | # $ ./check_ossec.sh -a "server1,server2,station3"
16 |
17 | # 4.) Report critical if more than 3 agents are offline and warning if at least 1 is offline.
18 | # $ ./check_ossec.sh -c 3 -w 1
19 |
20 | # Nagios Exit Codes
21 | OK=0
22 | WARNING=1
23 | CRITICAL=2
24 | UNKNOWN=3
25 |
26 | # Default Location of the binaries
27 | # Set these to the proper location if your installation differs
28 | CONF=/var/ossec/etc/ossec.conf
29 | AGENT_CONF=/var/ossec/etc/shared/agent.conf
30 | AGENT_CONTROL=/var/ossec/bin/agent_control
31 | OSSEC_CONTROL=/var/ossec/bin/ossec-control
32 | SYSCHECK_CONTROL=/var/ossec/bin/syscheck-control
33 | ROOTKIT_CONTROL=/var/ossec/bin/rootkit-control
34 |
35 | if [ ! -f $AGENT_CONTROL ] && [ ! -f $OSSEC_CONTROL ];
36 | then
37 | echo "ERROR: OSSEC binaries not found. If you installed OSSEC in a location other than the default update the AGENT_CONTROL and OSSEC_CONTROL variables in $0."
38 | exit 1
39 | fi
40 |
41 | usage()
42 | {
43 | cat < Check status of agent or list of comma separated agents, "agent1,agent2".
50 | -c Critical threshold for number of inactive agents
51 | -l List all agents
52 | -s Check status of OSSEC server processes. Use ``-s all'' to check all.
53 | To exclude a service e.g execd pass as argument i.e. ``-s execd''
54 | -w Warning threshold for number of inactive agents
55 | -S Syscheck last scan check
56 | -R Rootkit last scan check
57 |
58 | Usage: $0 -a "server1,server2,station3"
59 | EOF
60 | }
61 |
62 | argcheck() {
63 | # if less than n argument
64 | if [ $ARGC -lt $1 ]; then
65 | echo "Missing arguments! Use \`\`-h'' for help."
66 | exit 1
67 | fi
68 | }
69 |
70 | # Initialize variables
71 | CRIT=0
72 | WARN=0
73 | CHECK_AGENT=0
74 | CHECK_THRESHOLD=0
75 | CHECK_SYSCHECK=0
76 | CHECK_ROOTCHECK=0
77 | LIST_AGENTS=0
78 | SERVER_CHECK=0
79 | EXCLUDE=all
80 | ACTIVE=0
81 | INACTIVE=0
82 | NEVER=0
83 | TOTAL=0
84 | DISCONNECTED=0
85 | CONNECTED=0
86 | NEVERCONNECTED=0
87 | UNKNOWN=0
88 | ARGC=$#
89 |
90 | argcheck 1
91 |
92 | while getopts "ha:c:ls:v:w:SR" OPTION
93 | do
94 | case $OPTION in
95 | h)
96 | usage;;
97 | a)
98 | CHECK_AGENT=1
99 | AGENT="$OPTARG"
100 | ;;
101 | c)
102 | CHECK_THRESHOLD=1
103 | CRIT="$OPTARG"
104 | ;;
105 | l)
106 | LIST_AGENTS=1
107 | ;;
108 | s)
109 | SERVER_CHECK=1
110 | EXCLUDE="$OPTARG"
111 | ;;
112 | v)
113 | CHECK_THRESHOLD=1
114 | VOL="$OPTARG"
115 | ;;
116 | w)
117 | WARN="$OPTARG";;
118 | S)
119 | CHECK_SYSCHECK=1;;
120 | R)
121 | CHECK_ROOTCHECK=1;;
122 | \?)
123 | exit 1;;
124 | esac
125 | done
126 |
127 | if [ $LIST_AGENTS -eq 1 ]; then
128 | $AGENT_CONTROL -l
129 | exit 0
130 | fi
131 |
132 | if [ $SERVER_CHECK -eq 1 ]; then
133 |
134 | $OSSEC_CONTROL status | grep -v $EXCLUDE | grep "not running"
135 |
136 | if [ $? -eq 0 ]; then
137 | echo "An OSSEC service is not running!"
138 | exit $CRITICAL
139 | else
140 | echo "All OSSEC services running"
141 | exit $OK
142 | fi
143 | fi
144 |
145 | if [ $CHECK_AGENT -eq 1 ]; then
146 |
147 | for host in $(echo $AGENT | sed 's/,/ /g');
148 | do
149 | RESULT=$($AGENT_CONTROL -l | grep ${host},)
150 | case $RESULT in
151 | *Disconnected)
152 | echo "Agent $host is not connected!"
153 | DISCONNECTED=$((DISCONNECTED+1))
154 | ;;
155 | *Active)
156 | echo "Agent $host is connected"
157 | CONNECTED=$((CONNECTED+1))
158 | ;;
159 | *Never*)
160 | echo "Agent $host has never connected to the server: $RESULT"
161 | NEVERCONNECTED=$((NEVERCONNECTED+1))
162 | ;;
163 | *)
164 | echo "Unknown status or agent: $host"
165 | UNKNOWN=$((UNKNOWN+1))
166 | ;;
167 | esac
168 | done
169 |
170 | if [ $DISCONNECTED -gt 0 ] || [ $NEVERCONNECTED -gt 0 ] || [ $UNKNOWN -gt 0 ]; then
171 | echo "-> $DISCONNECTED disconnected agent(s), $NEVERCONNECTED never connected agent(s), and $UNKNOWN agent(s) with unknown status (possible agent name typo?)."
172 | exit $CRITICAL
173 | else
174 | echo "All requested ($CONNECTED) agents are connected to the server!"
175 | exit $OK
176 | fi
177 | fi
178 |
179 |
180 | if [ $CHECK_THRESHOLD -eq 1 ]; then
181 |
182 | ACTIVE=$($AGENT_CONTROL -l | grep Active | wc -l)
183 | INACTIVE=$($AGENT_CONTROL -l | grep Disconnected | wc -l)
184 | NEVER=$($AGENT_CONTROL -l | grep Never | wc -l)
185 | TOTAL=$($AGENT_CONTROL -l | wc -l)
186 |
187 | if [ $INACTIVE -gt $CRIT ]; then
188 | echo "$INACTIVE of $TOTAL agents inactive! Active: $ACTIVE"
189 | exit $CRITICAL
190 | elif [ $INACTIVE -gt $WARN ]; then
191 | echo "$INACTIVE of $TOTAL agents inactive! Active: $ACTIVE"
192 | exit $WARNING
193 | else
194 | echo "Active: $ACTIVE - Inactive: $INACTIVE - Never connected:$NEVER - Total: $TOTAL"
195 | exit $OK
196 | fi
197 | fi
198 |
199 | [[ '' ]]
200 |
201 | if [ $CHECK_SYSCHECK -eq 1 ]; then
202 |
203 |
204 | fi
205 |
206 | if [ $CHECK_ROOTCHECK -eq 1 ]; then
207 | fi
208 |
--------------------------------------------------------------------------------
/rules/kerberos_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | kerberos_ksu
6 | Kerberos messages grouped.
7 |
8 |
9 |
10 |
11 |
12 | 15000
13 | kerberos_ksu
14 | authenticated
15 | ksu authentication succeeded.
16 | authentication_success,
17 |
18 |
19 |
20 | 15001
21 | root
22 | ksu root authentication succeeded.
23 | authentication_success,
24 |
25 |
26 |
27 |
28 |
29 | 15000
30 | kerberos_ksu
31 | authentication failed
32 | ksu authentication failed.
33 | authentication_failed,
34 |
35 |
36 |
37 | 15003
38 | root
39 | ksu authentication to root failed.
40 | authentication_failed,
41 |
42 |
43 |
44 |
45 |
46 | 15000
47 | kerberos_ksu
48 | authorization for \S+ successful
49 | ksu authorization succeeded.
50 | authentication_success,
51 |
52 |
53 |
54 | 15000
55 | kerberos_ksu
56 | authorization for \S+ for execution of \S+ successful
57 | ksu authorization succeeded to run command.
58 | authentication_success,
59 |
60 |
61 |
62 | 15005, 15006
63 | root
64 | ksu authorization to root succeeded.
65 | authentication_success,
66 |
67 |
68 |
69 | 15007
70 |
71 | alert_by_email
72 | First time (ksu) is executed by user.
73 |
74 |
75 |
76 |
77 |
78 | kerberos_kadmin
79 | Kerberos messages grouped.
80 |
81 |
82 |
83 |
84 |
85 | 15100
86 | create_principal
87 | success
88 | Kerberus principal created
89 | adduser,account_changed
90 |
91 |
92 |
93 | 15100
94 | get_principal
95 | success
96 | Kerberus principal successfully requested
97 | authentication_success,
98 |
99 |
100 |
101 | 15100
102 | modify_principal
103 | success
104 | Kerberus principal successfully modified
105 | adduser,account_changed
106 |
107 |
108 |
109 |
110 |
111 | 15100
112 | get_principal
113 | Principal does not exist
114 | Invalid Kerberus principal requested
115 | invalid_login
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/rules/organization_ar_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | virustotal_lookup.sh
7 | Malicious hash found
8 | Malware hash found in Virus Total
9 |
10 |
11 |
12 | cymru_lookup.sh
13 | WARNING
14 | Malware hash found in Team Cymru Malware Hash Registry
15 |
16 |
17 |
18 |
19 | rpm_lookup
20 | Custom RPM Lookup Alert
21 | rpm_lookup
22 |
23 |
24 |
25 | 120003
26 | rpm_lookup.sh
27 | WARNING
28 | File has been modified, entry in RPM database differs
29 | no_email_alert
30 | rpm_lookup
31 |
32 |
33 |
34 | puppetdb_lookup
35 | Custom PuppetDB Lookup Alert
36 | puppetdb_lookup
37 |
38 |
39 |
40 | 120005
41 | puppetdb_lookup.sh
42 | WARNING
43 | Modified file not managed by Puppet
44 | no_email_alert
45 | puppetdb_lookup
46 |
47 |
48 |
49 | 120006
50 | 120004
51 |
52 | File has been modified and is not in PuppetDB or RPM database
53 |
54 |
55 |
56 | cymru_lookup
57 | Custom Cymru Lookup Alert
58 | cymru_lookup
59 |
60 |
61 |
62 | 120008
63 | lists/mal.list
64 | Malware hash match found from intelligence data
65 |
66 |
67 |
68 | deb_lookup
69 | Custom DEB Lookup Alert
70 | deb_lookup
71 |
72 |
73 |
74 | 120010
75 | deb_lookup.sh
76 | WARNING
77 | File has been modified, entry in DEB database differs
78 | no_email_alert
79 | deb_lookup
80 |
81 |
82 |
83 | 5902
84 | UID=\d\d\d,
85 | System account created
86 |
87 |
88 |
89 | command_search.sh
90 | WARNING
91 | Possible suspicious command identified
92 |
93 |
94 |
95 | time_lookup.sh
96 | WARNING
97 | System clock is off
98 |
99 |
100 |
101 |
102 | 120014
103 | ossec-sec
104 | Ignore clock messages from OSSEC server
105 |
106 |
107 |
108 | ldap_lookup
109 | Custom LDAP Lookup Alert
110 | ldap_lookup
111 |
112 |
113 |
114 | 120020
115 | ldap_lookup.sh
116 | OK
117 | Attempted user is an employee
118 | ldap_lookup
119 |
120 |
121 |
122 | 120020
123 | ldap_lookup.sh
124 | WARNING
125 | Attempted user is not an employee
126 | ldap_lookup
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/rules/organization_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1.1.1.|1.2.2.
4 | team1|team2
5 | $SECOPS_USERS|blah1|blah2
6 | blah3|blah4jh
7 | nagios|nrpe
8 | $NAGIOS_USERS|git|qualys|cabackup|zimbra|postgres|gitwriter
9 | owencampbell@lawyer.com|DAS@sydenham.lewisham.sch.uk|upgradeteam@admin.in.th|techservice@discuz.org|manager.verificationdepartment@googlemail.com|webaccount@zbavitu.net|info@adimin.com|systemadmniteam@gmail.com|account-supporteam-verification@live.com|tech.dpart@info.lt|hlpdesk7@yahoo.co.uk|webservices@gala.net|admin@coyspu.com.ar|notice@mail2webmaster.com|merijnkatrien@zonnet.nl|notice@mail2webmaster.com|update-check@live.nl|helpweb13@yahoo.com.hk|accountverification@8u8.com|desk.loginupdate@yahoo.com.hk|email.upgrades@gmail.com|helpdeskict1@yahoo.com.hk|edu-accountupdate2008@live.com|webmailteam001@gmail.com|customerhelp_desk@email.com|upgrade.suppteam2008@sanook.com|supp.team2008@ymail.com|s.eamilupgrade@ymail.com|customer.careservice@live.com|abuse-itservice@live.com|upgreatunit@yahoo.com.hk|ldorow@utica.edu|suppteam@mail2webmaster.com|computer.desk@live.com|tech.service18@live.com|customercareunite111@mail2consultant.com|verification_01@live.com
10 |
11 |
12 |
13 |
14 |
15 |
16 | 5402,5403,5501,5502,5715
17 | $NAGIOS_USERS
18 | Ignore nagios/nrpe log
19 |
20 |
21 |
22 | ossec
23 | ossec-sec
24 | Ignore matching on ossec logs that are fed back in
25 |
26 |
27 |
28 | 4721
29 | $SECOPS_NETS
30 | Supress router changes from organization nets
31 |
32 |
33 |
34 | 5400
35 | blah1|blah2|blah3|blah4
36 | sudo use on bastions
37 |
38 |
39 |
40 | 5715
41 |
42 |
43 | Multiple SSH logins from same source IP
44 | authentication_success,
45 |
46 |
47 |
48 | 100020
49 | $SECOPS_NETS
50 | Ignore multiple SSH logins from same source SECOPS networks
51 | authentication_success,
52 |
53 |
54 |
55 | 5710
56 |
57 | Multiple attempt to login using a non-existent user
58 |
59 |
60 |
61 | WARN: Syscheck disabled.|WARN: Rootcheck disabled.
62 | OSSEC service disabled
63 |
64 |
65 |
66 | 100020
67 | garvey
68 | gssapi-with-mic
69 | 130.126.3.77
70 | Ignore multiple SSH logins from garvey by same source IP
71 |
72 |
73 |
74 | authentication_success
75 | lists/ip.list
76 | Successful authentication from bad IP
77 |
78 |
79 |
80 | authentication_success
81 | lists/bhr.list
82 | Successful authentication from previously BHR'd IP
83 |
84 |
85 |
86 | web_scan|recon|sqlinjection|attack
87 | lists/url.list
88 | URL Intel list match
89 |
90 |
91 |
92 | 5712
93 | 5715
94 |
95 |
96 |
97 | lists/networks.list
98 | Success after multiple failed SSH authentications from outside
99 |
100 |
101 |
102 | authentication_success
103 | authentication_failed,authenticaton_failures
104 |
105 |
106 | Success after failed authentication
107 |
108 |
109 |
110 | system_shutdown
111 | 503,504
112 |
113 | Ignore agent started after system shutdown
114 |
115 |
116 |
117 | /contact/|/administrator/|/admin/|/session.php
118 | JDatabaseDriverMysqli
119 | Possible Joomla! exploitation attempt
120 |
121 |
122 |
123 | 5715
124 | root
125 | lists/networks.list
126 | Non-organization root login
127 |
128 |
129 |
130 | 100035
131 | password
132 | Non-organization root login with password
133 |
134 |
135 |
136 |
137 | 15001,15003
138 | lists/ksu.list
139 | Authentication attempted from unvetted ksu user
140 |
141 |
142 |
143 |
144 | 5400
145 | lists/system_users.list
146 | System account executed sudo
147 |
148 |
149 |
150 | 5400
151 | lists/commands.list
152 | Suspicious command executed
153 |
154 |
155 |
156 | $BAD_SMTP
157 | Illegal E-mail-mail list from 2006 by Aashish
158 |
159 |
160 |
161 | OOM for frozen_buffer
162 | Kernel went into write mode
163 |
164 |
165 |
166 | kernel: Program Xnest tried to access /dev/mem|kernel: Program Xnest tried
167 | Possible Phalanx exploit
168 |
169 |
170 |
171 | 5749
172 | Disconnecting: Bad packet length
173 | Possible Phalanx exploit
174 |
175 |
176 |
177 |
178 | authentication_failed|authenticaton_failures|authentication_success|add_user|account_changed|invalid_login
179 | lists/watched_users.list
180 | Watched user alert triggered
181 |
182 |
183 |
184 |
185 | 5400
186 | syslog,sudo
187 | lists/sudo.list
188 | Authentication attempted from unvetted sudo user
189 |
190 |
191 |
192 | authentication_failed|authenticaton_failures|authentication_success|connection_attempt|virus|attacks|invalid_login|syslog
193 | lists/watched_ips.list
194 | Activity from watched IP list detected
195 |
196 |
197 |
198 |
--------------------------------------------------------------------------------
/rules/organization_syscheck_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 550
9 | /var/ossec/lists/
10 | a CDB list has been modified
11 | no_log
12 |
13 |
14 |
15 | 550
16 | /etc/krb5.keytab
17 | Priority file alert, Kerberos keytab file modified
18 |
19 |
20 |
21 | 550
22 | /etc/khupd2.p2
23 | Phalanx rootkitt
24 |
25 |
26 |
27 | syscheck
28 | /var/ossec/etc/shared/agent.conf
29 | agent.conf was modified
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/rules/overwrite_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | core_dumped|error|attack|bad |illegal |fatal|Segmentation Fault|Corrupted
4 |
5 |
6 |
7 |
8 |
9 |
10 | 500
11 | Ossec started
12 | Ossec server started.
13 |
14 |
15 |
16 | ossec
17 | syscheck_new_entry
18 | File added to the system.
19 | syscheck,
20 |
21 |
22 |
23 | 500
24 | ^ossec: File rotated
25 | Log file rotated.
26 |
27 |
28 |
29 | $NEW_BAD_WORDS
30 | alert_by_email
31 | Unknown problem somewhere in the system.
32 |
33 |
34 |
35 | Non standard syslog message (size too large).
36 |
37 |
38 |
39 | 5702
40 | Possible breakin attempt
41 | (high number of reverse lookup errors).
42 | no_email_alert
43 |
44 |
45 |
46 | 5700
47 | illegal user|invalid user
48 | Attempt to login using a non-existent user
49 | invalid_login,authentication_failed,
50 | no_log
51 |
52 |
53 |
54 | 5700
55 | Connection refused$
56 | ssh connection refused
57 | no_log
58 |
59 |
60 |
61 | 5100
62 | I/O error: dev |end_request: I/O error, dev
63 | Kernel Input/Output error
64 |
65 |
66 |
67 | authentication_success
68 |
69 | authentication_success
70 | First time user logged in.
71 |
72 |
73 |
74 | 5100
75 | Promiscuous mode enabled|
76 | device \S+ entered promiscuous mode
77 | Interface entered in promiscuous(sniffing) mode.
78 | promisc,
79 |
80 |
81 |
82 | adduser
83 | attacks
84 |
85 | Attacks followed by the addition
86 | of an user.
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------