unified2 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +5 -7
- data/example/connect.rb +20 -0
- data/example/example.rb +59 -28
- data/example/models.rb +196 -0
- data/example/search.rb +14 -0
- data/example/{classification.config → seeds/classification.config} +0 -0
- data/example/{gen-msg.map → seeds/gen-msg.map} +0 -0
- data/example/{sid-msg.map → seeds/sid-msg.map} +1086 -182
- data/example/{unified2 → seeds/unified2} +0 -0
- data/lib/unified2/classification.rb +2 -2
- data/lib/unified2/config_file.rb +80 -0
- data/lib/unified2/event.rb +30 -22
- data/lib/unified2/payload.rb +3 -1
- data/lib/unified2/signature.rb +2 -2
- data/lib/unified2/version.rb +1 -1
- data/lib/unified2.rb +10 -59
- metadata +12 -34
data/README.rdoc
CHANGED
@@ -9,10 +9,9 @@ A ruby interface for unified2 output. rUnified2 allows you to manipulate unified
|
|
9
9
|
|
10
10
|
== Features
|
11
11
|
|
12
|
-
* Monitor unified2 logs
|
13
|
-
*
|
14
|
-
*
|
15
|
-
* Numerous connivence methods for statistical analysis
|
12
|
+
* Monitor/Read unified2 logs & manipulate the data.
|
13
|
+
* Numerous connivence methods
|
14
|
+
* Simple & Intuitive to Use
|
16
15
|
|
17
16
|
== Examples
|
18
17
|
|
@@ -34,9 +33,8 @@ A ruby interface for unified2 output. rUnified2 allows you to manipulate unified
|
|
34
33
|
Unified2.watch('/var/log/snort/merged.log', :last) do |event|
|
35
34
|
next if event.signature.name.blank?
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
puts event.classification.name
|
36
|
+
puts event
|
37
|
+
|
40
38
|
end
|
41
39
|
|
42
40
|
# Unified2#read
|
data/example/connect.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'datamapper'
|
3
|
+
require 'dm-mysql-adapter'
|
4
|
+
require 'models'
|
5
|
+
|
6
|
+
class Connect
|
7
|
+
|
8
|
+
def self.setup
|
9
|
+
@connection = DataMapper.setup(:default, {
|
10
|
+
:adapter => "mysql",
|
11
|
+
:host => "localhost",
|
12
|
+
:database => "rUnified2",
|
13
|
+
:username => "rUnified2",
|
14
|
+
:password => "password"
|
15
|
+
})
|
16
|
+
DataMapper.finalize
|
17
|
+
DataMapper.auto_upgrade!
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/example/example.rb
CHANGED
@@ -1,43 +1,74 @@
|
|
1
1
|
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "example")
|
3
|
+
|
2
4
|
require 'unified2'
|
5
|
+
require 'connect'
|
3
6
|
require 'pp'
|
4
7
|
|
8
|
+
# Initialize Database Connection
|
9
|
+
Connect.setup
|
10
|
+
|
5
11
|
# Unified2 Configuration
|
6
12
|
Unified2.configuration do
|
7
|
-
# Sensor Configurations
|
8
|
-
sensor :id => 200, :name => 'Hello Sensor', :interface => 'en1'
|
9
13
|
|
14
|
+
# Sensor Configurations
|
15
|
+
sensor :interface => 'en1', :name => 'Example Sensor'
|
16
|
+
|
10
17
|
# Load signatures, generators & classifications into memory
|
11
|
-
load :signatures, 'sid-msg.map'
|
12
|
-
load :generators, 'gen-msg.map'
|
13
|
-
load :classifications, 'classification.config'
|
18
|
+
load :signatures, 'seeds/sid-msg.map'
|
19
|
+
load :generators, 'seeds/gen-msg.map'
|
20
|
+
load :classifications, 'seeds/classification.config'
|
21
|
+
|
14
22
|
end
|
15
23
|
|
16
|
-
#
|
17
|
-
# the
|
18
|
-
#
|
24
|
+
# Locate the sensor in the database using
|
25
|
+
# the hostname and interface. If this fails
|
26
|
+
# rUnified2 will create a new sensor record.
|
27
|
+
sensor = Sensor.find(Unified2.sensor)
|
28
|
+
Unified2.sensor.id = sensor.id
|
19
29
|
|
20
|
-
|
30
|
+
# Load the classifications, generators &
|
31
|
+
# signatures into the database and store the
|
32
|
+
# md5 in the sensor record. This will only
|
33
|
+
# update if the md5s DO NOT match.
|
34
|
+
[[Classification,:classifications], [Signature, :signatures], [Signature, :generators]].each do |klass, method|
|
35
|
+
unless sensor.send(:"#{method}_md5") == Unified2.send(method).send(:md5)
|
36
|
+
klass.send(:import, { method => Unified2.send(method).send(:data), :force => true })
|
37
|
+
sensor.update(:"#{method}_md5" => Unified2.send(method).send(:md5))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Monitor the unfied2 log and process the data.
|
42
|
+
# The second argument is the last event processed by
|
43
|
+
# the sensor. If the last_event_id column is blank in the
|
44
|
+
# sensor table it will begin at the first available event.
|
45
|
+
Unified2.watch('/var/log/snort/merged.log', sensor.last_event_id + 1 || :first) do |event|
|
21
46
|
next if event.signature.blank?
|
22
47
|
|
23
|
-
puts event
|
48
|
+
#puts event
|
24
49
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
50
|
+
insert_event = Event.new({
|
51
|
+
:event_id => event.id,
|
52
|
+
:uid => event.uid,
|
53
|
+
:created_at => event.timestamp,
|
54
|
+
:sensor_id => event.sensor.id,
|
55
|
+
:source_ip => event.source_ip,
|
56
|
+
:source_port => event.source_port,
|
57
|
+
:destination_ip => event.destination_ip,
|
58
|
+
:destination_port => event.destination_port,
|
59
|
+
:severity_id => event.severity,
|
60
|
+
:protocol => event.protocol,
|
61
|
+
:link_type => event.payload.linktype,
|
62
|
+
:packet_length => event.payload.length,
|
63
|
+
:packet => event.payload.hex,
|
64
|
+
:classification_id => event.classification.id,
|
65
|
+
:signature_id => event.signature.id
|
66
|
+
})
|
67
|
+
|
68
|
+
if insert_event.save
|
69
|
+
insert_event.update_sensor
|
70
|
+
else
|
71
|
+
STDERR.puts "VALIDATION ERROR OR RECORD ALREADY EXISTS #{insert_event.errors}"
|
72
|
+
end
|
31
73
|
|
32
|
-
|
33
|
-
# unified2 output and return records untill EOF.
|
34
|
-
|
35
|
-
# @signatures = []
|
36
|
-
# Unified2.watch('unified2', 101) do |event|
|
37
|
-
# next if event.signature.name.blank?
|
38
|
-
# next if @signatures.include?(event.signature.id)
|
39
|
-
#
|
40
|
-
# @signatures.push event.signature.id
|
41
|
-
#
|
42
|
-
# puts "#{event.id} | #{event.ip_destination} | #{event.ip_source} | #{event.signature.name}"
|
43
|
-
# end
|
74
|
+
end
|
data/example/models.rb
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
class Event
|
2
|
+
include DataMapper::Resource
|
3
|
+
storage_names[:default] = "events"
|
4
|
+
|
5
|
+
timestamps :created_at, :updated_at
|
6
|
+
|
7
|
+
property :id, Serial, :index => true
|
8
|
+
|
9
|
+
property :uid, String, :index => true
|
10
|
+
|
11
|
+
property :event_id, Integer, :index => true
|
12
|
+
|
13
|
+
property :sensor_id, Integer, :index => true
|
14
|
+
|
15
|
+
property :source_ip, String, :index => true
|
16
|
+
|
17
|
+
property :source_port, Integer
|
18
|
+
|
19
|
+
property :destination_ip, String, :index => true
|
20
|
+
|
21
|
+
property :destination_port, Integer
|
22
|
+
|
23
|
+
property :severity_id, Integer, :index => true
|
24
|
+
|
25
|
+
property :classification_id, Integer, :index => true
|
26
|
+
|
27
|
+
property :category_id, Integer, :index => true
|
28
|
+
|
29
|
+
property :user_id, Integer, :index => true
|
30
|
+
|
31
|
+
property :protocol, String, :index => true
|
32
|
+
|
33
|
+
property :link_type, Integer
|
34
|
+
|
35
|
+
property :packet_length, Integer
|
36
|
+
|
37
|
+
property :packet, Text
|
38
|
+
|
39
|
+
belongs_to :sensor
|
40
|
+
|
41
|
+
belongs_to :classification
|
42
|
+
|
43
|
+
belongs_to :signature
|
44
|
+
|
45
|
+
validates_uniqueness_of :uid
|
46
|
+
|
47
|
+
def update_sensor
|
48
|
+
sensor.update(:last_event_id => self.event_id)
|
49
|
+
sensor.save
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
class Sensor
|
55
|
+
include DataMapper::Resource
|
56
|
+
timestamps :created_at, :updated_at
|
57
|
+
|
58
|
+
property :id, Serial, :index => true
|
59
|
+
|
60
|
+
property :hostname, Text, :index => true
|
61
|
+
|
62
|
+
property :interface, String
|
63
|
+
|
64
|
+
property :name, String, :index => true
|
65
|
+
|
66
|
+
property :last_event_id, Integer, :index => true
|
67
|
+
|
68
|
+
property :signatures_md5, String, :length => 32, :index => true
|
69
|
+
|
70
|
+
property :generators_md5, String, :length => 32, :index => true
|
71
|
+
|
72
|
+
property :classifications_md5, String, :length => 32, :index => true
|
73
|
+
|
74
|
+
has n, :events
|
75
|
+
|
76
|
+
validates_uniqueness_of :hostname, :name
|
77
|
+
|
78
|
+
def events_count
|
79
|
+
last_event_id
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.find(object)
|
83
|
+
name = object.name ? object.name : object.hostname
|
84
|
+
|
85
|
+
sensor = first_or_create({:hostname => object.hostname, :interface => object.interface}, {
|
86
|
+
:hostname => object.hostname,
|
87
|
+
:interface => object.interface,
|
88
|
+
:name => name,
|
89
|
+
})
|
90
|
+
|
91
|
+
sensor
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
class Signature
|
97
|
+
include DataMapper::Resource
|
98
|
+
storage_names[:default] = "signatures"
|
99
|
+
|
100
|
+
timestamps :created_at, :updated_at
|
101
|
+
|
102
|
+
property :id, Serial, :index => true
|
103
|
+
|
104
|
+
property :signature_id, Integer, :index => true
|
105
|
+
|
106
|
+
property :generator_id, Integer, :index => true
|
107
|
+
|
108
|
+
property :name, Text
|
109
|
+
|
110
|
+
has n, :events
|
111
|
+
|
112
|
+
validates_uniqueness_of :name
|
113
|
+
|
114
|
+
def self.import(options={})
|
115
|
+
|
116
|
+
if options.has_key?(:signatures)
|
117
|
+
|
118
|
+
options[:signatures].each do |key, value|
|
119
|
+
signature = Signature.get(:signature_id => key)
|
120
|
+
next if signature && options[:force]
|
121
|
+
|
122
|
+
if signature
|
123
|
+
signature.update(value)
|
124
|
+
else
|
125
|
+
value.merge!(:signature_id => key, :generator_id => 1)
|
126
|
+
Signature.create(value)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
if options.has_key?(:generators)
|
134
|
+
|
135
|
+
options[:generators].each do |key, value|
|
136
|
+
genid, sid = key.split('.')
|
137
|
+
signature = Signature.get(:signature_id => sid, :generator_id => genid)
|
138
|
+
next if signature && options[:force]
|
139
|
+
|
140
|
+
if signature
|
141
|
+
signature.update(value)
|
142
|
+
else
|
143
|
+
value.merge!(:signature_id => sid, :generator_id => genid)
|
144
|
+
Signature.create(value)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Classification
|
155
|
+
include DataMapper::Resource
|
156
|
+
|
157
|
+
storage_names[:default] = "classifications"
|
158
|
+
|
159
|
+
timestamps :created_at, :updated_at
|
160
|
+
|
161
|
+
property :id, Serial, :index => true
|
162
|
+
|
163
|
+
property :classification_id, Integer, :index => true
|
164
|
+
|
165
|
+
property :name, Text
|
166
|
+
|
167
|
+
property :short, String
|
168
|
+
|
169
|
+
property :severity_id, Integer, :index => true
|
170
|
+
|
171
|
+
has n, :events
|
172
|
+
|
173
|
+
# belongs_to :severity
|
174
|
+
|
175
|
+
validates_uniqueness_of :name, :classification_id
|
176
|
+
|
177
|
+
def self.import(options={})
|
178
|
+
|
179
|
+
if options.has_key?(:classifications)
|
180
|
+
|
181
|
+
options[:classifications].each do |key,value|
|
182
|
+
classification = Classification.get(:classification_id => key)
|
183
|
+
next if classification && options[:force]
|
184
|
+
|
185
|
+
if classification
|
186
|
+
classification.update(value)
|
187
|
+
else
|
188
|
+
value.merge!(:classification_id => key)
|
189
|
+
Classification.create(value)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
data/example/search.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "example")
|
3
|
+
|
4
|
+
require 'unified2'
|
5
|
+
require 'connect'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
Connect.setup
|
9
|
+
|
10
|
+
@sensor = Sensor.first
|
11
|
+
|
12
|
+
@sensor.events.each do |event|
|
13
|
+
puts event.signature.name
|
14
|
+
end
|
File without changes
|
File without changes
|