Skip to content

Commit 2fcf06a

Browse files
committed
Merge pull request #22 from u-s-p/feature-modularize
Feature modularization, refactoring into separate config files
2 parents b166e9b + 2ca328e commit 2fcf06a

28 files changed

Lines changed: 644 additions & 442 deletions

0000_header.conf

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
##########################################################
2+
##########################################################
3+
#
4+
# Example Modsecurity audit log ingestor
5+
# configuration for Logstash
6+
#
7+
# @author bitsofinfo.g[at]gmail.com
8+
# built/tested w logstash v1.3.x line
9+
#
10+
#
11+
# @see http://logstash.net/
12+
# @see https://github.com/SpiderLabs/ModSecurity/wiki/ModSecurity-2-Data-Formats
13+
# @see http://bitsofinfo.wordpress.com/2013/09/19/logstash-for-modsecurity-audit-logs/
14+
#
15+
# @license http://www.apache.org/licenses/LICENSE-2.0
16+
#
17+
# @notes NOTE: this is not perfect and I am no Ruby expert
18+
# however this worked when processing quite a bit of
19+
# high volume mod-sec logs with lots of different
20+
# variations in what A-K sections were and were not
21+
# present. At a minimum its a good starting point
22+
# to start tackling a complex log format.
23+
#
24+
# Be careful w/ the custom ruby filter blocks and be aware
25+
# of https://logstash.jira.com/browse/LOGSTASH-1375
26+
#
27+
# This config file for whatever reason will not run
28+
# if you try to add the "-- web" option onto the logstash
29+
# flat jar. This has been reported to the developers.
30+
# Recommend you run this without the "-- web" option and just
31+
# hook up Kibana separately.
32+
#
33+
# Enable the "-v" verbose option when starting logstash
34+
# to aid in debugging things. Disable the "-v" option
35+
# when running in real/non-debug environment
36+
#
37+
#
38+
##########################################################
39+
##########################################################

1000_input_stdin_example.conf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
input {
2+
3+
stdin {
4+
type => "mod_security"
5+
6+
codec => multiline {
7+
pattern => "^--[a-fA-F0-9]{8}-Z--$"
8+
negate => true
9+
what => previous
10+
}
11+
}
12+
13+
}

1010_input_file_example.conf

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
input {
2+
file {
3+
# IMPORTANT! set this correctly to the charset
4+
# that your server writes these log files in
5+
path => "/path/to/your/modsec/audit/logs/*.log"
6+
type => "mod_security"
7+
8+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9+
# merge all modsec events for a given entity into the same event.
10+
# so essentially the modsec -Z marker is used as the splitter
11+
# which is the end of each modsec logical event in the logfile
12+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13+
codec => multiline {
14+
charset => "US-ASCII"
15+
pattern => "^--[a-fA-F0-9]{8}-Z--$"
16+
negate => true
17+
what => previous
18+
}
19+
}
20+
}

2000_filter_sections_split.conf

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
filter {
2+
if [type] == "mod_security" {
3+
4+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5+
# Due to the complexity of the collapsed single string
6+
# we get from multiline and the variance of exactly
7+
# which modsec sections (A-K) may or may not be in each
8+
# log entry, we run some custom ruby code that will
9+
# split on each modsec "section" and store each found in
10+
# new fields named "rawSection[A-K]" as appropriate, the value
11+
# of each of these fields contains the raw un-parsed data
12+
# from that modsec section. Sections that are non-existant
13+
# will not have a key in "fields"
14+
#
15+
# A bit long and crazy yes, but after spending many hours
16+
# just doing this w/ grok patterns, this ended up being the
17+
# most reliable way to break up this in-consistent format into
18+
# more usable blocks
19+
#
20+
# @see https://github.com/SpiderLabs/ModSecurity/wiki/ModSecurity-2-Data-Formats
21+
#
22+
# READ the above to get a good understanding of the sections
23+
# and which ones can actively contain data depending on your modsec
24+
# version and environment!
25+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26+
27+
ruby {
28+
code => "
29+
if !event['message'].nil?
30+
modSecSectionData = event['message'].split(/(?:--[a-fA-F0-9]{8}-([A-Z])--)/)
31+
modSecSectionData.shift
32+
for i in 0..((modSecSectionData.length-1)/2)
33+
sectionName = 'rawSection'.concat(modSecSectionData.shift)
34+
sectionData = modSecSectionData.shift
35+
sectionName = sectionName.strip
36+
if !sectionData.nil?
37+
sectionData = sectionData.strip
38+
end
39+
event.to_hash.merge!(sectionName => sectionData)
40+
end
41+
end
42+
"
43+
}
44+
}
45+
}

2010_filter_section_a_parse.conf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
filter {
2+
if [type] == "mod_security" {
3+
4+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5+
# Parse out fields from Section A (general event basics)
6+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7+
grok {
8+
match => {
9+
"rawSectionA" => "\[%{L1_TIMESTAMP:modsec_timestamp}\] %{DATA:uniqueId} %{IP:sourceIp} %{INT:sourcePort} %{IP:destIp} %{INT:destPort}"
10+
}
11+
patterns_dir => "./patterns/logstash_modsecurity_patterns"
12+
}
13+
}
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
filter {
2+
if [type] == "mod_security" {
3+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4+
# Parse out fields from Section B (request related line 1)
5+
# note line one could be garbage OR adhere to the
6+
# httpMethod [space] uri [space] protocol pattern
7+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8+
9+
# if a legit line... normal http request
10+
if [rawSectionB] =~ /.+/ {
11+
grok {
12+
match => {
13+
"rawSectionB" => [ "(?m)^%{DATA:httpMethod}\s(?<requestedUri>\S+)\s(?<incomingProtocol>[^\n]+)(?:\n(?<raw_requestHeaders>.+)?)?$",
14+
"(?<httpMethod>^(.*)$)" ]
15+
}
16+
}
17+
}
18+
}
19+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
filter {
2+
if [type] == "mod_security" {
3+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4+
# Convert raw request headers into a key/value
5+
# pair map
6+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7+
8+
if [raw_requestHeaders] =~ /.+/ {
9+
kv {
10+
source => "raw_requestHeaders"
11+
field_split => "\n"
12+
value_split => ":"
13+
target => "requestHeaders"
14+
}
15+
16+
17+
# trim leading/trailing hack @see https://logstash.jira.com/browse/LOGSTASH-1369
18+
ruby {
19+
code => "
20+
requestHeaders = event.to_hash['requestHeaders']
21+
requestHeaders.each { |k, v|
22+
if !v.nil? and v.is_a? String
23+
requestHeaders[k] = v.strip
24+
end
25+
}
26+
"
27+
}
28+
}
29+
}
30+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
filter {
2+
if [type] == "mod_security" {
3+
4+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5+
# Example of looking for a specific Cookie and promoting
6+
# it to a first class field
7+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8+
9+
if [raw_requestHeaders] =~ /Cookie/ and [raw_requestHeaders] =~ /myCookie=.+\b/ {
10+
11+
grok {
12+
match => {
13+
"raw_requestHeaders" => "(?<myCookie>myCookie[^; \s]+)"
14+
}
15+
}
16+
}
17+
}
18+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
filter {
2+
if [type] == "mod_security" {
3+
4+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5+
# Promote real request IP to a field if it exists
6+
# in the request headers section
7+
#
8+
# NOTE this is an example of promoting a custom header to a first
9+
# class field that might be set by a app firewall or other
10+
# upstream proxy
11+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12+
13+
if [raw_requestHeaders] =~ /X-Forwarded-For:/ {
14+
15+
grok {
16+
match => {
17+
"raw_requestHeaders" => "X-Forwarded-For: %{IPORHOST:XForwardedFor}"
18+
}
19+
}
20+
}
21+
}
22+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
filter {
2+
if [type] == "mod_security" {
3+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4+
# Example of splitting all Cookies from the requestHeader Cookie
5+
# and promoting it to a first class field
6+
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7+
8+
if [requestHeaders][Cookie] =~ /.+/ {
9+
kv {
10+
source => "[requestHeaders][Cookie]"
11+
field_split => "; "
12+
value_split => "="
13+
target => "requestCookies"
14+
}
15+
}
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)