threshold 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 80c9df2ec5e67d9e05e5424a136adf096550cfb2
4
+ data.tar.gz: c3e99e5725ba57c4f55c254348fc7a2168655018
5
+ SHA512:
6
+ metadata.gz: 6c8a93ec39c051fefa5f0b72a5f058ba9387f8fdfb11218c91315af3086ac8673229b08f0356b99870b98260e8918ff09ee9b15f5d5fae57eee4984694c57e6b
7
+ data.tar.gz: da6daf4ccea9b2581a9b9f3e6f043b4b29eec4d215db777e391e288181344383904116b5a0bc01289206dd383ccddce0e7bb516b621f6d40228a5b6ad7c18ad0
data/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ *Next Release*
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,119 @@
1
+ Contributing to Snort-thresholds
2
+ ============================
3
+
4
+ You're encouraged to submit [pull requests](https://github.com/shadowbq/snort-thresholds/pulls), [propose features and discuss issues](https://github.com/shadowbq/snort-thresholds/issues).
5
+
6
+ #### Fork the Project
7
+
8
+ Fork the [project on Github](https://github.com/shadowbq/snort-thresholds) and check out your copy.
9
+
10
+ ```
11
+ git clone https://github.com/contributor/snort-thresholds.git
12
+ cd snort-thresholds
13
+ git remote add upstream https://github.com/shadowbq/snort-thresholds.git
14
+ ```
15
+
16
+ #### Create a Topic Branch
17
+
18
+ All active development is based off of our 'master' branch. So, make sure your references are up to date, and create your topic branch off of upstream/master.
19
+
20
+ ```
21
+ git fetch upstream
22
+ git checkout -b my-feature-branch upstream/master
23
+ ```
24
+
25
+ #### Write Tests
26
+
27
+ If the area of code that you're working on supports unit tests, add tests.
28
+ Also, be sure to run all tests to make sure you didn't break anything.
29
+
30
+ #### Write Code
31
+
32
+ Implement your feature or bug fix.
33
+
34
+ #### Write Documentation
35
+
36
+ Document any external behavior in the [README](README.md).
37
+
38
+ #### Update Changelog
39
+
40
+ Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*. Make it look like every other line, including your name and link to your Github account.
41
+
42
+ #### Commit Changes
43
+
44
+ Make sure git knows your name and email address:
45
+
46
+ ```
47
+ git config --global user.name "Your Name"
48
+ git config --global user.email "contributor@example.com"
49
+ ```
50
+
51
+ Make commits of logical units. This might mean making multiple commits if you're changing distinct bits of functionality.
52
+ ```
53
+ git add ...
54
+ git commit
55
+ ```
56
+
57
+ It's very important that you write a good commit log! It should describe what changed and why. If you're fixing an issue that is already tracked within Github, mention it!
58
+
59
+ ````
60
+ Make a commit title that is brief and to the point
61
+
62
+ - Describe the change you made, and why you made it.
63
+ - Context is vital to a good commit message. Try to get future developers
64
+ up to speed with what your change does so that they don't bother you
65
+ offline.
66
+ - Mention any relevant Snort-thresholds issues by mentioning the number like this #55
67
+ - Github will automatically close any issues when your pull request is
68
+ accepted if you do something like this: closes #123
69
+
70
+ ````
71
+
72
+ #### Rebase
73
+
74
+ It's easy to for your feature branch to fall behind any changes that have been made in the main repository. This is to be expected on actively developed projects.
75
+ In order to make sure that your changes will still work when merged into the main repo, it's a good idea to rebase your feature branch. This basically peels the commits off of your feature branch, pulls in any updates that have been made to upstream/master, and then re-applies your commits. If all goes well, your commits will apply cleanly. If, however, someone else has made changes to the same code as you, git will report the conflicts and give you a chance to fix anything.
76
+
77
+ Basically, this will help ensure that your code will merge properly with any changes that may have been made in the main repository. If there are any conflicts, it will be easier to deal with those now, rather than finding out that your pull request can't merge.
78
+
79
+ ```
80
+ git fetch upstream
81
+ git rebase upstream/master
82
+ ```
83
+
84
+ #### Push
85
+
86
+ ```
87
+ git push origin my-feature-branch
88
+ ```
89
+
90
+ #### Make a Pull Request
91
+
92
+ Go to https://github.com/contributor/snort-thresholds and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days.
93
+
94
+ #### Update CHANGELOG Again
95
+
96
+ Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows.
97
+
98
+ ```
99
+ * [#123](https://github.com/shadowbq/snort-thresholds/pull/123): Reticulated splines - [@contributor](https://github.com/contributor).
100
+ ```
101
+
102
+ Amend your previous commit and force push the changes.
103
+
104
+ ```
105
+ git commit --amend
106
+ git push origin my-feature-branch -f
107
+ ```
108
+
109
+ #### Check on Your Pull Request
110
+
111
+ Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above.
112
+
113
+ #### Be Patient
114
+
115
+ It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there!
116
+
117
+ #### Thank You
118
+
119
+ Please do know that we really appreciate and value your time and work. We love you, really.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 shadowbq
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # snort-thresholds
2
+
3
+ [![Build Status](https://travis-ci.org/shadowbq/snort-thresholds.svg?branch=master)](https://travis-ci.org/shadowbq/snort-thresholds) [![Code Climate](https://codeclimate.com/github/shadowbq/snort-thresholds/badges/gpa.svg)](https://codeclimate.com/github/shadowbq/snort-thresholds) [![Test Coverage](https://codeclimate.com/github/shadowbq/snort-thresholds/badges/coverage.svg)](https://codeclimate.com/github/shadowbq/snort-thresholds)
4
+
5
+ Work in progress
6
+
7
+ https://github.com/jasonish/snort/blob/master/doc/README.filters
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler"
2
+ require "rake"
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ task :default => :spec
7
+
8
+ desc "Run all specs"
9
+ RSpec::Core::RakeTask.new(:spec) do |task|
10
+ task.pattern = "tests/**/*_spec.rb"
11
+ end
@@ -0,0 +1,22 @@
1
+ module Threshold
2
+
3
+ #Returns an Array of Grok Captures from the input file matching Threshold Conf standards
4
+ class Builder
5
+ def initialize(parsed_data)
6
+ @parsed_data = parsed_data
7
+ @parsed_data.reject! {|k,v| v.compact.first == nil }
8
+ end
9
+
10
+ def build
11
+ #Strip out NIL Stuctures
12
+ if @parsed_data.key?("SUPPRESSION")
13
+ return Threshold::Suppression.new(@parsed_data)
14
+ elsif @parsed_data.key?("RATEFILTER")
15
+ return Threshold::RateFilter.new(@parsed_data)
16
+ else @parsed_data.key?("EVENTFILTER")
17
+ return Threshold::EventFilter.new(@parsed_data)
18
+ end
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,134 @@
1
+
2
+ # In Snort 2.8.5, a new command event_filter was added with the following format.
3
+ # It functions the same as the global threshold command does in Snort 2.8.4 and
4
+ # earlier, except that it is not permitted within a rule.
5
+
6
+ # event_filter \
7
+ # gen_id <gid>, sig_id <sid>, \
8
+ # type <limit|threshold|both>, \
9
+ # track <by_src|by_dst>, \
10
+ # count <c>, seconds <s>
11
+
12
+ # This format supports the following options - all are required:
13
+
14
+ # * gen_id, sig_id: specify generator and signature ids of an associated rule. A
15
+ # sig_id of 0 will apply to all sig_ids for the given gen_id. If both gen_id
16
+ # and sig_id are zero, the event_filter applies to all events. Only one
17
+ # event_filter may be defined for a given gen_id, sig_id.
18
+
19
+ # * type <limit|threshold|both>: type limit alerts on the 1st m events during the
20
+ # time interval, then ignores events for the rest of the time interval. Type
21
+ # threshold alerts every m times we see this event during the time interval.
22
+ # Type both alerts once per time interval after seeing m occurrences of the
23
+ # event, then ignores any additional events during the time interval.
24
+
25
+ # * track by_src|by_dst: rate is tracked either by source IP address, or
26
+ # destination IP address. This means count is maintained at either by unique
27
+ # source IP addresses, or unique destination IP addresses.
28
+
29
+ # * count c: number of rule matching in s seconds that will cause event filter
30
+ # limit to exceed. C must be nonzero value. A count of -1 disables the
31
+ # event filter and can be used to override the global event_filter.
32
+
33
+ # * seconds s: time period over which count is accrued. S must be nonzero value.=end
34
+
35
+ # Example - rule event_filter - limit to logging 1 event per 60 seconds:
36
+
37
+ # event_filter \
38
+ # gen_id 1, sig_id 1851, \
39
+ # type limit, track by_src, \
40
+ # count 1, seconds 60
41
+
42
+
43
+ # Create a Standard Error Wrapper
44
+
45
+ module Threshold
46
+
47
+ class InvalidEventFilterObject < StandardError; end
48
+
49
+ # Create an Event Filter validator
50
+ class EventFilterValidator
51
+ include Veto.validator
52
+
53
+ validates :gid, :presence => true, :integer => true
54
+ validates :sid, :presence => true, :integer => true
55
+ validates :type, :presence => true, :inclusion => ['limit', 'threshold', 'both']
56
+ validates :track_by, :presence => true, :inclusion => ['src', 'dst']
57
+ validates :count, :presence => true, :integer => true
58
+ validates :seconds, :presence => true, :integer => true
59
+ validates :comment, :if => :comment_set?, :format => /^\s*?#.*/
60
+
61
+ def comment_set?(entity)
62
+ entity.comment
63
+ end
64
+
65
+ def track_by_set?(entity)
66
+ entity.track_by
67
+ end
68
+
69
+ def type_set?(type)
70
+ entity.type
71
+ end
72
+ end
73
+
74
+ class EventFilter
75
+
76
+ attr_accessor :gid, :sid, :type, :track_by, :count, :seconds, :comment
77
+
78
+ include Veto.model(EventFilterValidator.new)
79
+ include Comparable
80
+
81
+ def initialize(line="")
82
+ transform(line) unless line.empty?
83
+ end
84
+
85
+ def to_s
86
+ if self.valid?
87
+ if defined?(@comment)
88
+ "event_filter gen_id #{@gid}, sig_id #{@sid}, type #{@type}, track by_#{@track_by}, count #{@count}, seconds #{@seconds} #{@comment}"
89
+ else
90
+ "event_filter gen_id #{@gid}, sig_id #{@sid}, type #{@type}, track by_#{@track_by}, count #{@count}, seconds #{@seconds}"
91
+ end
92
+ else
93
+ raise InvalidEventFilterObject, 'Event Filter did not validate'
94
+ end
95
+ end
96
+
97
+ #Comparable
98
+ def <=>(anOther)
99
+ #gid <=> anOther.gid
100
+ c = self.class.to_s <=> anOther.class.to_s
101
+ if c == 0 then
102
+ d = self.gid <=> anOther.gid
103
+ if d == 0 then
104
+ self.sid <=> anOther.sid
105
+ else
106
+ return d
107
+ end
108
+ else
109
+ return c
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def transform(result)
116
+ begin
117
+ self.gid = result["GID"].compact.first.to_i
118
+ self.sid = result["SID"].compact.first.to_i
119
+ self.type = result["ETYPE"].compact.first
120
+ self.track_by = result["TRACK"].compact.first.split('_')[1]
121
+ self.count = result["COUNT"].compact.first.to_i
122
+ self.seconds = result["SECONDS"].compact.first.to_i
123
+ if result.key?("COMMENT")
124
+ self.comment = result["COMMENT"].compact.first
125
+ end
126
+ raise InvalidEventFilterObject unless self.valid?
127
+ rescue
128
+ raise InvalidEventFilterObject, 'Failed transformation from parser'
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,58 @@
1
+ module Threshold
2
+
3
+ #Returns an Array of Grok Captures from the input file matching Threshold Conf standards
4
+ class Parser
5
+
6
+ attr_reader :caps, :filehash
7
+
8
+ def initialize(file)
9
+
10
+ @file = file
11
+ @caps = []
12
+
13
+ patterns = {}
14
+ patterns["SUPPRESSION"] = "^suppress gen_id %{ID:GID}, sig_id %{ID:SID}(%{SUPPRESSIONOPTIONS})?(%{COMMENT})?"
15
+ patterns["EVENTFILTER"] = "^event_filter gen_id %{ID:GID}, sig_id %{ID:SID}, type %{ETYPE}, track %{TRACK}, count %{COUNT}, seconds %{SECONDS}(%{COMMENT})?"
16
+ patterns["RATEFILTER"] = "^rate_filter gen_id %{ID:GID}, sig_id %{ID:SID}, track %{TRACK}, count %{COUNT}, seconds %{SECONDS}, new_action %{NEW_ACTION}, timeout %{COUNT:TIMEOUT}(%{RATEFILTEROPTIONS})?(%{COMMENT})?"
17
+
18
+ patterns["SUPPRESSIONOPTIONS"] = ", track %{TRACK}, ip %{IP}"
19
+ patterns["RATEFILTEROPTIONS"] = ", apply_to %{IPCIDR}"
20
+
21
+ patterns["ID"] = '\\d+'
22
+ patterns["ETYPE"] = "limit|threshold|both"
23
+ patterns["COUNT"] = "\\d+"
24
+ patterns["SECONDS"] = "\\d+"
25
+ patterns["TRACK"] = "by_src|by_dst|by_rule"
26
+ patterns["COMMENT"] = "\s*?#.*"
27
+ patterns["NEW_ACTION"] = 'alert|drop|pass|log|sdrop|reject'
28
+ patterns["IPCIDR"] = '(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([1-9]|[1-2][0-9]|3[0-2]))?'
29
+
30
+ @grok = Grok.new
31
+ patterns.each {|k,v| @grok.add_pattern(k,v)}
32
+ custom_path = File.join(File.dirname(File.expand_path(__FILE__)), "patterns/base")
33
+ @grok.add_patterns_from_file(custom_path)
34
+
35
+ # Remember to call result["GID"].compact because of the PIPE or below in grok compile
36
+ @grok.compile("^%{SUPPRESSION}|%{EVENTFILTER}|%{RATEFILTER}")
37
+
38
+ loadfile(@file)
39
+ end
40
+
41
+ private
42
+
43
+ def loadfile(file)
44
+ # FLOCK this and hash it..
45
+ handler = File.open(file)
46
+ handler.flock(File::LOCK_EX)
47
+ handler.each do |line|
48
+ match = @grok.match(line)
49
+ @caps << match.captures if match
50
+ end
51
+ hash = Digest::MD5.file file
52
+ handler.close
53
+ @filehash = hash
54
+ end
55
+
56
+ end
57
+ end
58
+
@@ -0,0 +1,91 @@
1
+ USERNAME [a-zA-Z0-9_-]+
2
+ USER %{USERNAME}
3
+ INT (?:[+-]?(?:[0-9]+))
4
+ BASE10NUM (?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
5
+ NUMBER (?:%{BASE10NUM})
6
+ BASE16NUM (?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))
7
+ BASE16FLOAT \b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\.[0-9A-Fa-f]*)?)|(?:\.[0-9A-Fa-f]+)))\b
8
+
9
+ POSINT \b(?:[0-9]+)\b
10
+ WORD \b\w+\b
11
+ NOTSPACE \S+
12
+ DATA .*?
13
+ GREEDYDATA .*
14
+ #QUOTEDSTRING (?:(?<!\\)(?:"(?:\\.|[^\\"])*"|(?:'(?:\\.|[^\\'])*')|(?:`(?:\\.|[^\\`])*`)))
15
+ QUOTEDSTRING (?:(?<!\\)(?:"(?>[^\\"]+|\\.)*")|(?:'(?>[^\\']+|\\.)*')|(?:`(?>[^\\`]+|\\.)*`))
16
+
17
+ # Networking
18
+ MAC (?:%{CISCOMAC}|%{WINDOWSMAC}|%{COMMONMAC})
19
+ CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4})
20
+ WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})
21
+ COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})
22
+ IP (?<![0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])
23
+ HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b)
24
+ HOST %{HOSTNAME}
25
+ IPORHOST (?:%{HOSTNAME}|%{IP})
26
+ HOSTPORT (?:%{IPORHOST=~/\./}:%{POSINT})
27
+
28
+ # paths
29
+ PATH (?:%{UNIXPATH}|%{WINPATH})
30
+ UNIXPATH (?:/(?:[\w_%!$@:.,-]+|\\.)*)+
31
+ #UNIXPATH (?<![\w\/])(?:/[^\/\s?*]*)+
32
+ LINUXTTY (?:/dev/pts/%{POSINT})
33
+ BSDTTY (?:/dev/tty[pq][a-z0-9])
34
+ TTY (?:%{BSDTTY}|%{LINUXTTY})
35
+ WINPATH (?:[A-Za-z]+:|\\)(?:\\[^\\?*]*)+
36
+ URIPROTO [A-Za-z]+(\+[A-Za-z+]+)?
37
+ URIHOST %{IPORHOST}(?::%{POSINT:port})?
38
+ # uripath comes loosely from RFC1738, but mostly from what Firefox
39
+ # doesn't turn into %XX
40
+ URIPATH (?:/[A-Za-z0-9$.+!*'(),~:#%_-]*)+
41
+ #URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)?
42
+ URIPARAM \?[A-Za-z0-9$.+!*'(),~#%&/=:;_-]*
43
+ URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?
44
+ URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?
45
+
46
+ # Months: January, Feb, 3, 03, 12, December
47
+ MONTH \b(?:[Jj]an(?:uary)?|[Ff]eb(?:ruary)?|[Mm]ar(?:ch)?|[Aa]pr(?:il)?|[Mm]ay|[Jj]un(?:e)?|[Jj]ul(?:y)?|[Aa]ug(?:ust)?|[Ss]ep(?:tember)?|[Oo]ct(?:ober)?|[Nn]ov(?:ember)?|[Dd]ec(?:ember)?)\b
48
+ MONTHNUM (?:0?[1-9]|1[0-2])
49
+ MONTHDAY (?:3[01]|[1-2]?[0-9]|0?[1-9])
50
+
51
+ # Days: Monday, Tue, Thu, etc...
52
+ DAY (?:[Mm]on(?:day)?|[Tt]ue(?:sday)?|[Ww]ed(?:nesday)?|[Tt]hu(?:rsday)?|[Ff]ri(?:day)?|[Ss]at(?:urday)?|[Ss]un(?:day)?)
53
+
54
+ # Years?
55
+ YEAR [0-9]+
56
+ # Time: HH:MM:SS
57
+ #TIME \d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?
58
+ # I'm still on the fence about using grok to perform the time match,
59
+ # since it's probably slower.
60
+ # TIME %{POSINT<24}:%{POSINT<60}(?::%{POSINT<60}(?:\.%{POSINT})?)?
61
+ HOUR (?:2[0123]|[01][0-9])
62
+ MINUTE (?:[0-5][0-9])
63
+ # '60' is a leap second in most time standards and thus is valid.
64
+ SECOND (?:(?:[0-5][0-9]|60)(?:[.,][0-9]+)?)
65
+ TIME (?<![0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])
66
+ # datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it)
67
+ DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}
68
+ DATE_EU %{YEAR}[/-]%{MONTHNUM}[/-]%{MONTHDAY}
69
+ ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE}))
70
+ ISO8601_SECOND (?:%{SECOND}|60)
71
+ TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?
72
+ DATE %{DATE_US}|%{DATE_EU}
73
+ DATESTAMP %{DATE}[- ]%{TIME}
74
+ TZ (?:[PMCE][SD]T)
75
+ DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}
76
+ DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}
77
+
78
+ # Syslog Dates: Month Day HH:MM:SS
79
+ SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME}
80
+ PROG (?:[\w._/-]+)
81
+ SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])?
82
+ SYSLOGHOST %{IPORHOST}
83
+ SYSLOGFACILITY <%{POSINT:facility}.%{POSINT:priority}>
84
+ HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT:ZONE}
85
+
86
+ # Shortcuts
87
+ QS %{QUOTEDSTRING}
88
+
89
+ # Log formats
90
+ SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:
91
+ COMBINEDAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "%{WORD:verb} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}" %{NUMBER:response} (?:%{NUMBER:bytes}|-) "(?:%{URI:referrer}|-)" %{QS:agent}
@@ -0,0 +1,3 @@
1
+ JAVACLASS (?:[a-z-]+\.)[A-Za-z0-9]+
2
+ JAVAFILE (?:[A-Za-z0-9_.-]})
3
+ JAVASTACKTRACEPART "at %{JAVACLASS:class}\.%{WORD:method}\(%{JAVAFILE:file}:%{NUMBER:line}\)
@@ -0,0 +1,3 @@
1
+ RUBY_LOGLEVEL (?:DEBUG|FATAL|ERROR|WARN|INFO)
2
+ RUBY_LOGGER [DFEWI], \[%{TIMESTAMP_ISO8601} #{POSINT:pid}\] *%{RUBY_LOGLEVEL} -- : %{DATA:message}
3
+
@@ -0,0 +1,172 @@
1
+ # rate_filter provides rate based attack prevention by allowing users to
2
+ # configure a new action to take for a specified time when a given rate is
3
+ # exceeded. Multiple rate filters can be defined on the same rule, in which case
4
+ # they are evaluated in the order they appear in the configuration file, and the
5
+ # first applicable action is taken.
6
+
7
+ # Rate filters are used as standalone commands (outside any rule) and have the
8
+ # following format:
9
+
10
+ # rate_filter \
11
+ # gen_id <gid>, sig_id <sid>, \
12
+ # track <by_src|by_dst|by_rule>, \
13
+ # count <c>, seconds <s>, \
14
+ # new_action alert|drop|pass|log|sdrop|reject, \
15
+ # timeout <seconds>, \
16
+ # apply_to <ip-list>
17
+
18
+ # This format has the following options - all are required except apply_to, which
19
+ # is optional:
20
+
21
+ # * Track by_src|by_dst|by_rule: rate is tracked either by source IP address,
22
+ # destination IP address, or by rule. This means the match statistics are
23
+ # maintained for each unique source IP address, for each unique destination IP
24
+ # address, or they are aggregated at rule level. For rules related to Stream5
25
+ # sessions, source and destination means client and server respectively. track
26
+ # by_rule and apply_to may not be used together.
27
+
28
+ # * Count c: the maximum number of rule matches in s seconds before the rate
29
+ # filter limit to is exceeded. C must be positive.
30
+
31
+ # * seconds s: the time period over which count is accrued. 0 seconds means
32
+ # count is a total count instead of a specific rate. For example, rate filter
33
+ # may be used to detect if the number of connections to a specific server
34
+ # exceed a specific count. 0 seconds only applies to internal rules (gen_id 135)
35
+ # and other use will produce a fatal error by Snort.
36
+
37
+ # * new_action <alert|drop|pass|log|sdrop|reject>: new_action replaces rule
38
+ # action with alert|drop| pass|log|sdrop for r (timeout) seconds. Drop, reject
39
+ # and sdrop can be used only when snort is used in inline mode. sdrop and
40
+ # reject are conditionally compiled with #ifdef GIDS.
41
+
42
+ # * timeout r: revert to the original rule action after r seconds. If r is 0,
43
+ # then rule action is never reverted back. Event filter may be used to manage
44
+ # number of alerts after the rule action is enabled by rate filter.
45
+
46
+ # * apply_to <ip-list>: restrict the configuration to only to source or
47
+ # destination IP address (indicated by track parameter) determined by
48
+ # <ip-list>. track by_rule and apply_to may not be used together. Note that
49
+ # events are generated during the timeout period, even if the rate falls below
50
+ # the configured limit.
51
+
52
+
53
+ # Example - allow a maximum of 100 connection attempts per second from any one
54
+ # IP address, and block further connection attempts from that IP address for 10
55
+ # seconds:
56
+
57
+ # rate_filter \
58
+ # gen_id 135, sig_id 1, \
59
+ # track by_src, \
60
+ # count 100, seconds 1, \
61
+ # new_action drop, timeout 10
62
+
63
+
64
+ # Create a Standard Error Wrapper
65
+
66
+ module Threshold
67
+
68
+ class InvalidRateFilterObject < StandardError; end
69
+
70
+ # Create a Rate Filter validator
71
+ class RateFilterValidator
72
+ include Veto.validator
73
+
74
+ validates :gid, :presence => true, :integer => true
75
+ validates :sid, :presence => true, :integer => true
76
+ validates :track_by, :presence =>true, :inclusion => ['src', 'dst', 'rule']
77
+ validates :count, :presence => true, :integer => true
78
+ validates :seconds, :presence => true, :integer => true
79
+ validates :new_action, :presence => true, :inclusion => ['alert', 'drop', 'pass', 'log', 'sdrop', 'reject']
80
+ validates :timeout, :presence => true, :integer => true
81
+ validates :apply_to, :if => :not_track_by_rule?, :format => /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([1-9]|[1-2][0-9]|3[0-2]))?$/
82
+ validates :comment, :if => :comment_set?, :format => /^\s*#.*/
83
+
84
+ def comment_set?(entity)
85
+ entity.comment
86
+ end
87
+
88
+ def not_track_by_rule?(entity)
89
+ if entity.apply_to == nil
90
+ entity.track_by == false
91
+ else
92
+ entity.track_by != 'rule'
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ class RateFilter
99
+
100
+ attr_accessor :gid, :sid, :track_by, :count, :seconds, :new_action, :timeout, :apply_to, :comment
101
+
102
+ include Veto.model(RateFilterValidator.new)
103
+ include Comparable
104
+
105
+ def initialize(line="")
106
+ transform(line) unless line.empty?
107
+ end
108
+
109
+ def to_s
110
+ if self.valid?
111
+ if apply_to == nil then
112
+ if defined?(@comment)
113
+ "rate_filter gen_id #{@gid}, sig_id #{@sid}, track by_#{@track_by}, count #{@count}, seconds #{@seconds}, new_action #{@new_action}, timeout #{@timeout} #{@comment}"
114
+ else
115
+ "rate_filter gen_id #{@gid}, sig_id #{@sid}, track by_#{@track_by}, count #{@count}, seconds #{@seconds}, new_action #{@new_action}, timeout #{@timeout}"
116
+ end
117
+ else
118
+ if defined?(@comment)
119
+ "rate_filter gen_id #{@gid}, sig_id #{@sid}, count #{@count}, seconds #{@seconds}, new_action #{@new_action}, timeout #{@timeout} apply_to #{@apply_to} #{@comment}"
120
+ else
121
+ "rate_filter gen_id #{@gid}, sig_id #{@sid}, count #{@count}, seconds #{@seconds}, new_action #{@new_action}, timeout #{@timeout} apply_to #{@apply_to}"
122
+ end
123
+ end
124
+ else
125
+ raise InvalidRateFilterObject, 'Rate Filter did not validate'
126
+ end
127
+ end
128
+
129
+ #Comparable
130
+ def <=>(anOther)
131
+ #gid <=> anOther.gid
132
+ c = self.class.to_s <=> anOther.class.to_s
133
+ if c == 0 then
134
+ d = self.gid <=> anOther.gid
135
+ if d == 0 then
136
+ self.sid <=> anOther.sid
137
+ else
138
+ return d
139
+ end
140
+ else
141
+ return c
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ def transform(result)
148
+ begin
149
+ self.gid = result["GID"].compact.first.to_i
150
+ self.sid = result["SID"].compact.first.to_i
151
+ self.track_by = result["TRACK"].compact.first.split('_')[1]
152
+
153
+ self.count = result["COUNT"].compact.first.to_i
154
+ self.seconds = result["SECONDS"].compact.first.to_i
155
+ self.timeout = result["TIMEOUT"].compact.first.to_i
156
+ self.new_action = result["NEW_ACTION"].compact.first
157
+
158
+ if result.key("IPCIDR")
159
+ self.apply_to = result["IPCIDR"].compact.first
160
+ end
161
+ if result.key?("COMMENT")
162
+ self.comment = result["COMMENT"].compact.first
163
+ end
164
+ raise InvalidRateFilterObject unless self.valid?
165
+ rescue
166
+ raise InvalidRateFilterObject, 'Failed transformation from parser'
167
+ end
168
+ end
169
+
170
+ end
171
+
172
+ end
@@ -0,0 +1,114 @@
1
+
2
+ # suppress \
3
+ # gen_id <gid>, sig_id <sid>
4
+ #
5
+ # suppress \
6
+ # gen_id <gid>, sig_id <sid>, \
7
+ # track by_src|by_dst, \
8
+ # ip <ip-list>
9
+ # Note: ip can be 1.2.3.4 or 1.2.4.5/32
10
+
11
+ # Create a Standard Error Wrapper
12
+
13
+ module Threshold
14
+
15
+ class InvalidSuppressionObject < StandardError; end
16
+
17
+ # Create a Suppression validator
18
+ class SuppressionValidator
19
+ include Veto.validator
20
+
21
+ validates :comment, :if => :comment_set?, :format => /^\s*?#.*/
22
+
23
+ validates :gid, :presence => true, :integer => true
24
+ validates :sid, :presence => true, :integer => true
25
+
26
+ validates :track_by, :presence => true, :if => :ip_set?, :inclusion => ['src', 'dst']
27
+ validates :ip, :presence => true, :if => :track_by_set?, :format => /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([1-9]|[1-2][0-9]|3[0-2]))?$/
28
+
29
+ def comment_set?(entity)
30
+ entity.comment
31
+ end
32
+
33
+ def track_by_set?(entity)
34
+ entity.track_by
35
+ end
36
+
37
+ def ip_set?(entity)
38
+ entity.ip
39
+ end
40
+ end
41
+
42
+ class Suppression
43
+
44
+ attr_accessor :gid, :sid, :track_by, :ip, :comment
45
+
46
+ include Veto.model(SuppressionValidator.new)
47
+ include Comparable
48
+
49
+ def initialize(line="")
50
+ transform(line) unless line.empty?
51
+ end
52
+
53
+ #Comparable
54
+ def <=>(anOther)
55
+ #gid <=> anOther.gid
56
+ c = self.class.to_s <=> anOther.class.to_s
57
+ if c == 0 then
58
+ d = self.gid <=> anOther.gid
59
+ if d == 0 then
60
+ self.sid <=> anOther.sid
61
+ else
62
+ return d
63
+ end
64
+ else
65
+ return c
66
+ end
67
+ end
68
+
69
+ def to_s
70
+ if self.valid?
71
+ if track_by == nil then
72
+ if defined?(@comment)
73
+ "suppress gen_id #{@gid}, sig_id #{@sid} #{@comment}"
74
+ else
75
+ "suppress gen_id #{@gid}, sig_id #{@sid}"
76
+ end
77
+ else
78
+ if defined?(@comment)
79
+ "suppress gen_id #{@gid}, sig_id #{@sid}, track by_#{@track_by}, ip #{@ip} #{@comment}"
80
+ else
81
+ "suppress gen_id #{@gid}, sig_id #{@sid}, track by_#{@track_by}, ip #{@ip}"
82
+ end
83
+ end
84
+ else
85
+ raise InvalidSuppressionObject, 'Suppression did not validate'
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def transform(result)
92
+ begin
93
+ self.gid = result["GID"].compact.first.to_i
94
+ self.sid = result["SID"].compact.first.to_i
95
+ if result.key?("TRACK")
96
+ self.track_by = result["TRACK"].compact.first.split('_')[1]
97
+ end
98
+ if result.key?("IP")
99
+ self.ip = result["IP"].compact.first
100
+ end
101
+ if result.key?("COMMENT")
102
+ self.comment = result["COMMENT"].compact.first
103
+ end
104
+ raise InvalidSuppressionObject unless self.valid?
105
+ rescue
106
+ raise InvalidSuppressionObject, 'Failed transformation from parser'
107
+ end
108
+ end
109
+
110
+
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,124 @@
1
+ module Threshold
2
+
3
+ class InvalidThresholdsObject < StandardError; end
4
+ class ReadOnlyThresholdFile < StandardError; end
5
+ class NonExistantThresholdFile < StandardError; end
6
+ class MissingThresholdFileConfiguration < StandardError; end
7
+ class ThresholdAtomicLockFailure < StandardError; end
8
+
9
+ class Thresholds < Array
10
+
11
+ attr_accessor :file, :readonly
12
+
13
+ # Write changes to the file
14
+ def flush
15
+ begin
16
+ valid_existing_file?(@file)
17
+ raise ReadOnlyThresholdsFile if @readonly
18
+ hash = current_hash
19
+
20
+ file = File.open(@file, 'w+')
21
+ raise ThresholdAtomicLockFailure, 'The @file state/hash changed before we could flush the file' unless stored_hash == hash
22
+ file.write self.sort.to_s
23
+ file.close
24
+
25
+ rescue NonExistantThresholdFile
26
+ raise ReadOnlyThresholdsFile if @readonly
27
+
28
+ file = File.open(@file, 'w')
29
+ file.write self.sort.to_s
30
+ file.close
31
+ end
32
+ return true
33
+ end
34
+
35
+ # Clears current collection and Read in the thresholds.conf file
36
+ def loadfile!
37
+ self.clear
38
+ loadfile
39
+ end
40
+
41
+ # Append in the thresholds.conf file to current collection
42
+ def loadfile
43
+ valid_existing_file?(@file)
44
+
45
+ results = Threshold::Parser.new(@file)
46
+ @stored_hash= results.filehash
47
+ #puts stored_hash
48
+ results.caps.each do |result|
49
+ builder = Threshold::Builder.new(result)
50
+ self << builder.build
51
+ end
52
+
53
+ end
54
+
55
+ def valid?
56
+ begin
57
+ self.each do |threshold|
58
+ if threshold.respond_to?(:valid?)
59
+ return false unless threshold.valid?
60
+ else
61
+ raise InvalidThresholdsObject, "Container object has unknown objects"
62
+ end
63
+ end
64
+ return true
65
+ rescue InvalidThresholdsObject
66
+ return false
67
+ end
68
+ end
69
+
70
+ # This should transpose? back to a Thresholds class not return as an Array. (super)
71
+ def sort
72
+ raise InvalidThresholdsObject unless valid?
73
+ new_temp = super
74
+ temp = Thresholds.new
75
+ new_temp.each {|item| temp << item}
76
+ return temp
77
+ end
78
+
79
+ def sort!
80
+ raise InvalidThresholdsObject unless valid?
81
+ super
82
+ end
83
+
84
+ def to_s
85
+ output = ""
86
+
87
+ raise InvalidThresholdsObject, "Container object has unknown objects" unless valid?
88
+
89
+ self.each do |threshold|
90
+ output << threshold.to_s + "\n"
91
+ end
92
+ return output
93
+ end
94
+
95
+ def stored_hash
96
+ @stored_hash
97
+ end
98
+
99
+ private
100
+
101
+ def stored_hash=(foo)
102
+ @stored_hash=foo
103
+ end
104
+
105
+ def current_hash
106
+ file = File.open(@file, 'rb+')
107
+ file.flock(File::LOCK_EX)
108
+ hash = Digest::MD5.file @file
109
+ file.close
110
+ return hash
111
+ end
112
+
113
+ def valid_existing_file?(file)
114
+ if file !=nil
115
+ raise NonExistantThresholdFile, "Missing threshold.conf" unless (File.file?(file) and File.exists?(file))
116
+ else
117
+ raise MissingThresholdFileConfiguration, "Missing threshold.conf path. See README for Usage."
118
+ end
119
+ return true
120
+ end
121
+
122
+
123
+ end
124
+ end
@@ -0,0 +1,4 @@
1
+ module Threshold
2
+ VERSION = '0.0.3'
3
+ SNORT_VERSION='~>2.9.3'
4
+ end
data/lib/threshold.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'forwardable' # Needed by veto
3
+ require 'veto'
4
+ require 'grok-pure'
5
+ require 'digest'
6
+
7
+ module Threshold
8
+ $:.unshift(File.dirname(__FILE__))
9
+
10
+ #require library
11
+ require 'threshold/suppression'
12
+ require 'threshold/event_filter'
13
+ require 'threshold/thresholds'
14
+ require 'threshold/rate_filter'
15
+ require 'threshold/builder'
16
+ require 'threshold/parser'
17
+
18
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: threshold
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Shadowbq
8
+ - Yabbo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-01-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: veto
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: jls-grok
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 0.11.0
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 0.11.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: fivemat
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.1'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.1'
84
+ - !ruby/object:Gem::Dependency
85
+ name: simplecov
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: codeclimate-test-reporter
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ description: An ORM to map to Snort 2.9.x threshold.conf files
113
+ email: shadowbq@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - CHANGELOG.md
119
+ - CONTRIBUTING.md
120
+ - LICENSE
121
+ - README.md
122
+ - Rakefile
123
+ - lib/threshold.rb
124
+ - lib/threshold/builder.rb
125
+ - lib/threshold/event_filter.rb
126
+ - lib/threshold/parser.rb
127
+ - lib/threshold/patterns/base
128
+ - lib/threshold/patterns/java
129
+ - lib/threshold/patterns/ruby
130
+ - lib/threshold/rate_filter.rb
131
+ - lib/threshold/suppression.rb
132
+ - lib/threshold/thresholds.rb
133
+ - lib/threshold/version.rb
134
+ homepage: http://github.com/shadowbq/snort-thresholds
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options:
140
+ - "--charset=UTF-8"
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: 1.9.3
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.2.2
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: An ORM to map to Snort 2.9.x threshold.conf files
159
+ test_files: []