unified2 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +3 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.rdoc +9 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +61 -0
- data/Rakefile +39 -0
- data/example/classification.config +67 -0
- data/example/example.rb +43 -0
- data/example/gen-msg.map +391 -0
- data/example/sid-msg.map +15806 -0
- data/example/unified2 +0 -0
- data/gemspec.yml +14 -0
- data/lib/unified2/classification.rb +14 -0
- data/lib/unified2/construct.rb +54 -0
- data/lib/unified2/core_ext/string.rb +8 -0
- data/lib/unified2/core_ext.rb +1 -0
- data/lib/unified2/event.rb +315 -0
- data/lib/unified2/event_ip4.rb +26 -0
- data/lib/unified2/event_ip6.rb +23 -0
- data/lib/unified2/exceptions/file_not_found.rb +4 -0
- data/lib/unified2/exceptions/file_not_readable.rb +4 -0
- data/lib/unified2/exceptions/unknown_load_type.rb +4 -0
- data/lib/unified2/exceptions.rb +2 -0
- data/lib/unified2/packet.rb +16 -0
- data/lib/unified2/payload.rb +32 -0
- data/lib/unified2/primitive/ipv4.rb +19 -0
- data/lib/unified2/primitive.rb +1 -0
- data/lib/unified2/record_header.rb +10 -0
- data/lib/unified2/sensor.rb +26 -0
- data/lib/unified2/signature.rb +24 -0
- data/lib/unified2/version.rb +4 -0
- data/lib/unified2.rb +206 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/unified2_spec.rb +8 -0
- data/unified2.gemspec +15 -0
- metadata +148 -0
data/example/unified2
ADDED
Binary file
|
data/gemspec.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
name: unified2
|
2
|
+
summary: "A ruby interface for unified2 output."
|
3
|
+
description: "A ruby interface for unified2 output. rUnified2 allows you to manipulate unified2 output for custom storage and/or analysis."
|
4
|
+
license: MIT
|
5
|
+
authors: Dustin Willis Webber
|
6
|
+
email: dustin.webber@gmail.com
|
7
|
+
homepage: https://github.com/mephux/unified2
|
8
|
+
|
9
|
+
development_dependencies:
|
10
|
+
bindata: ~> 1.3.1
|
11
|
+
hexdump: ~> 0.1.0
|
12
|
+
ore-tasks: ~> 0.4
|
13
|
+
rspec: ~> 2.4
|
14
|
+
yard: ~> 0.6.0
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Unified2
|
2
|
+
class Classification
|
3
|
+
|
4
|
+
attr_accessor :id, :name, :short, :priority
|
5
|
+
|
6
|
+
def initialize(classification={})
|
7
|
+
@id = classification[:classification_id]
|
8
|
+
@name = classification[:name]
|
9
|
+
@short = classification[:short]
|
10
|
+
@priority = classification[:priority]
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'unified2/event_ip4'
|
2
|
+
require 'unified2/event_ip6'
|
3
|
+
require 'unified2/record_header'
|
4
|
+
require 'unified2/packet'
|
5
|
+
|
6
|
+
module Unified2
|
7
|
+
|
8
|
+
class Construct < ::BinData::Record
|
9
|
+
record_header :header
|
10
|
+
|
11
|
+
choice :data, :selection => :type_selection do
|
12
|
+
packet "packet"
|
13
|
+
event_ip4 "ev4"
|
14
|
+
event_ip6 "ev6"
|
15
|
+
end
|
16
|
+
|
17
|
+
# padding
|
18
|
+
string :read_length => :padding_length
|
19
|
+
|
20
|
+
def type_selection
|
21
|
+
case header.u2type.to_i
|
22
|
+
when 1
|
23
|
+
# define UNIFIED2_EVENT 1
|
24
|
+
when 2
|
25
|
+
# define UNIFIED2_PACKET 2
|
26
|
+
"packet"
|
27
|
+
when 7
|
28
|
+
# define UNIFIED2_IDS_EVENT 7
|
29
|
+
"ev4"
|
30
|
+
when 66
|
31
|
+
# define UNIFIED2_EVENT_EXTENDED 66
|
32
|
+
when 67
|
33
|
+
# define UNIFIED2_PERFORMANCE 67
|
34
|
+
when 68
|
35
|
+
# define UNIFIED2_PORTSCAN 68
|
36
|
+
when 72
|
37
|
+
# define UNIFIED2_IDS_EVENT_IPV6 72
|
38
|
+
"ev6"
|
39
|
+
else
|
40
|
+
"unknown type #{header.u2type}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# sometimes the data needs extra padding
|
45
|
+
def padding_length
|
46
|
+
if header.u2length > data.num_bytes
|
47
|
+
header.u2length - data.num_bytes
|
48
|
+
else
|
49
|
+
0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'unified2/core_ext/string'
|
@@ -0,0 +1,315 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'json'
|
3
|
+
require 'unified2/classification'
|
4
|
+
require 'unified2/payload'
|
5
|
+
require 'unified2/sensor'
|
6
|
+
require 'unified2/signature'
|
7
|
+
|
8
|
+
module Unified2
|
9
|
+
|
10
|
+
class Event
|
11
|
+
|
12
|
+
attr_accessor :id, :metadata, :packet
|
13
|
+
|
14
|
+
def initialize(id)
|
15
|
+
@id = id
|
16
|
+
end
|
17
|
+
|
18
|
+
def packet_time
|
19
|
+
if @packet.has_key?(:packet_second)
|
20
|
+
@packet[:packet_second]
|
21
|
+
@timestamp = Time.at(@packet[:packet_second].to_i)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def event_time
|
26
|
+
if @packet.has_key?(:event_second)
|
27
|
+
@timestamp = Time.at(@packet[:event_second].to_i)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias :timestamp :event_time
|
31
|
+
|
32
|
+
def microseconds
|
33
|
+
if @metadata.has_key?(:event_microsecond)
|
34
|
+
@microseconds = @metadata[:event_microsecond]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def sensor
|
39
|
+
@sensor ||= Unified2.sensor
|
40
|
+
end
|
41
|
+
|
42
|
+
def packet_action
|
43
|
+
if @metadata.has_key?(:event_second)
|
44
|
+
@packet_action = @metadata[:packet_action]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def protocol
|
49
|
+
if @metadata.has_key?(:protocol)
|
50
|
+
@protocol = determine_protocol(@metadata[:protocol])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def icmp?
|
55
|
+
return true if protocol == :ICMP
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def tcp?
|
60
|
+
return true if protocol == :TCP
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
def udp?
|
65
|
+
return true if protocol == :UDP
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
def classification
|
70
|
+
if @metadata.is_a?(Hash)
|
71
|
+
@classification = Classification.new(@metadata[:classification]) if @metadata[:classification]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def signature
|
76
|
+
if @metadata.is_a?(Hash)
|
77
|
+
@signature = Signature.new(@metadata[:signature])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def generator_id
|
82
|
+
if @metadata.is_a?(Hash)
|
83
|
+
@metadata[:generator_id] if @metadata.has_key?(:generator_id)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def ip_source
|
88
|
+
if @metadata.is_a?(Hash)
|
89
|
+
@metadata[:ip_source] if @metadata.has_key?(:ip_source)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
alias :source_ip :ip_source
|
93
|
+
|
94
|
+
# Add ICMP type
|
95
|
+
def source_port
|
96
|
+
return 0 if icmp?
|
97
|
+
@source_port = @metadata[:sport_itype] if @metadata.has_key?(:sport_itype)
|
98
|
+
end
|
99
|
+
|
100
|
+
def ip_destination
|
101
|
+
if @metadata.is_a?(Hash)
|
102
|
+
@metadata[:ip_destination] if @metadata.has_key?(:ip_destination)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
alias :destination_ip :ip_destination
|
106
|
+
|
107
|
+
# Add ICMP code
|
108
|
+
def destination_port
|
109
|
+
return 0 if icmp?
|
110
|
+
@source_port = @metadata[:dport_icode] if @metadata.has_key?(:dport_icode)
|
111
|
+
end
|
112
|
+
|
113
|
+
def severity
|
114
|
+
@severity = @metadata[:priority_id] if @metadata.has_key?(:priority_id)
|
115
|
+
end
|
116
|
+
|
117
|
+
def payload
|
118
|
+
if @packet.is_a?(Hash)
|
119
|
+
Payload.new(@packet)
|
120
|
+
else
|
121
|
+
Payload.new
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def load(event)
|
126
|
+
if event.data.respond_to?(:signature_id)
|
127
|
+
@metadata ||= build_event_metadata(event)
|
128
|
+
end
|
129
|
+
|
130
|
+
if event.data.respond_to?(:packet_data)
|
131
|
+
@packet ||= build_packet_metadata(event)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_h
|
136
|
+
if @metadata.is_a?(Hash)
|
137
|
+
if @packet.is_a?(Hash)
|
138
|
+
data = {}
|
139
|
+
data.merge!(@metadata)
|
140
|
+
data.merge!(@packet)
|
141
|
+
return data
|
142
|
+
end
|
143
|
+
else
|
144
|
+
if @packet.is_a?(Hash)
|
145
|
+
return @packet
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_i
|
151
|
+
@id.to_i
|
152
|
+
end
|
153
|
+
|
154
|
+
def json
|
155
|
+
to_h.to_json
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_s
|
159
|
+
data = %{
|
160
|
+
#############################################################################
|
161
|
+
Event ID: #{id}
|
162
|
+
Timestamp: #{timestamp}
|
163
|
+
Severity: #{severity}
|
164
|
+
Protocol: #{protocol}
|
165
|
+
Source IP: #{source_ip}:#{source_port}
|
166
|
+
Destination IP: #{destination_ip}:#{destination_port}
|
167
|
+
Signature: #{signature.name}
|
168
|
+
Payload:
|
169
|
+
|
170
|
+
}
|
171
|
+
if payload.blank?
|
172
|
+
data + '#############################################################################'
|
173
|
+
else
|
174
|
+
payload.dump(:width => 30, :output => data)
|
175
|
+
data + "#############################################################################"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def build_event_metadata(event)
|
182
|
+
@event_hash = {}
|
183
|
+
|
184
|
+
@event_hash = {
|
185
|
+
:ip_destination => event.data.ip_destination,
|
186
|
+
:priority_id => event.data.priority_id,
|
187
|
+
:signature_revision => event.data.signature_revision,
|
188
|
+
:event_id => event.data.event_id,
|
189
|
+
:protocol => event.data.protocol,
|
190
|
+
:sport_itype => event.data.sport_itype,
|
191
|
+
:event_second => event.data.event_second,
|
192
|
+
:packet_action => event.data.packet_action,
|
193
|
+
:dport_icode => event.data.dport_icode,
|
194
|
+
:sensor_id => event.data.sensor_id,
|
195
|
+
:generator_id => event.data.generator_id,
|
196
|
+
:ip_source => event.data.ip_source,
|
197
|
+
:event_microsecond => event.data.event_microsecond
|
198
|
+
}
|
199
|
+
|
200
|
+
build_classifications(event)
|
201
|
+
|
202
|
+
if event.data.generator_id.to_i == 1
|
203
|
+
build_signature(event)
|
204
|
+
else
|
205
|
+
build_generator(event)
|
206
|
+
end
|
207
|
+
|
208
|
+
@event_hash
|
209
|
+
end
|
210
|
+
|
211
|
+
def build_packet_metadata(event)
|
212
|
+
@packet_hash = {}
|
213
|
+
@packet_hash = {
|
214
|
+
:linktype => event.data.linktype,
|
215
|
+
:packet_microsecond => event.data.packet_microsecond,
|
216
|
+
:packet_second => event.data.packet_second,
|
217
|
+
:payload => event.data.packet_data,
|
218
|
+
:event_second => event.data.event_second,
|
219
|
+
:packet_length => event.data.packet_length
|
220
|
+
}
|
221
|
+
|
222
|
+
@packet_hash
|
223
|
+
end
|
224
|
+
|
225
|
+
def build_generator(event)
|
226
|
+
if Unified2.generators
|
227
|
+
if Unified2.generators.has_key?("#{event.data.generator_id}.#{event.data.signature_id}")
|
228
|
+
sig = Unified2.generators["#{event.data.generator_id}.#{event.data.signature_id}"]
|
229
|
+
|
230
|
+
@event_hash[:signature] = {
|
231
|
+
:signature_id => event.data.signature_id,
|
232
|
+
:revision => event.data.signature_revision,
|
233
|
+
:name => sig[:name],
|
234
|
+
:references => sig[:references],
|
235
|
+
:blank => false
|
236
|
+
}
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
unless @event_hash.has_key?(:signature)
|
241
|
+
@event_hash[:signature] = {
|
242
|
+
:signature_id => event.data.signature_id,
|
243
|
+
:revision => 0,
|
244
|
+
:name => "Unknown Signature #{event.data.signature_id}",
|
245
|
+
:references => [],
|
246
|
+
:blank => true
|
247
|
+
}
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def build_signature(event)
|
252
|
+
if Unified2.signatures
|
253
|
+
if Unified2.signatures.has_key?(event.data.signature_id.to_s)
|
254
|
+
sig = Unified2.signatures[event.data.signature_id.to_s]
|
255
|
+
|
256
|
+
@event_hash[:signature] = {
|
257
|
+
:signature_id => event.data.signature_id,
|
258
|
+
:revision => event.data.signature_revision,
|
259
|
+
:name => sig[:name],
|
260
|
+
:references => sig[:references]
|
261
|
+
}
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
unless @event_hash.has_key?(:signature)
|
266
|
+
@event_hash[:signature] = {
|
267
|
+
:signature_id => event.data.signature_id,
|
268
|
+
:revision => 0,
|
269
|
+
:name => "Unknown Signature #{event.data.signature_id}",
|
270
|
+
:references => []
|
271
|
+
}
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def build_classifications(event)
|
276
|
+
if Unified2.classifications
|
277
|
+
if Unified2.classifications.has_key?("#{event.data.classification_id}")
|
278
|
+
classification = Unified2.classifications["#{event.data.classification_id}"]
|
279
|
+
|
280
|
+
@event_hash[:classification] = {
|
281
|
+
:classification_id => event.data.classification_id,
|
282
|
+
:name => classification[:name],
|
283
|
+
:short => classification[:short],
|
284
|
+
:priority => classification[:priority]
|
285
|
+
}
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
unless @event_hash.has_key?(:classification)
|
290
|
+
@event_hash[:classification] = {
|
291
|
+
:classification_id => event.data.classification_id,
|
292
|
+
:name => 'Unknown',
|
293
|
+
:short => 'n/a',
|
294
|
+
:priority => 0
|
295
|
+
}
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def determine_protocol(protocol)
|
300
|
+
case protocol.to_i
|
301
|
+
when 1
|
302
|
+
:ICMP # ICMP (Internet Control Message Protocol) packet type.
|
303
|
+
when 2
|
304
|
+
:IGMP # IGMP (Internet Group Message Protocol) packet type.
|
305
|
+
when 6
|
306
|
+
:TCP # TCP (Transmition Control Protocol) packet type.
|
307
|
+
when 17
|
308
|
+
:UDP # UDP (User Datagram Protocol) packet type.
|
309
|
+
else
|
310
|
+
:'N/A'
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'unified2/primitive/ipv4'
|
2
|
+
|
3
|
+
module Unified2
|
4
|
+
|
5
|
+
class EventIP4 < ::BinData::Record
|
6
|
+
|
7
|
+
endian :big
|
8
|
+
|
9
|
+
uint32 :sensor_id
|
10
|
+
uint32 :event_id
|
11
|
+
uint32 :event_second
|
12
|
+
uint32 :event_microsecond
|
13
|
+
uint32 :signature_id
|
14
|
+
uint32 :generator_id
|
15
|
+
uint32 :signature_revision
|
16
|
+
uint32 :classification_id
|
17
|
+
uint32 :priority_id
|
18
|
+
ipv4 :ip_source
|
19
|
+
ipv4 :ip_destination
|
20
|
+
uint16 :sport_itype
|
21
|
+
uint16 :dport_icode
|
22
|
+
uint8 :protocol
|
23
|
+
uint8 :packet_action
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Unified2
|
2
|
+
|
3
|
+
class EventIP6 < ::BinData::Record
|
4
|
+
endian :big
|
5
|
+
|
6
|
+
uint32 :sensor_id
|
7
|
+
uint32 :event_id
|
8
|
+
uint32 :event_second
|
9
|
+
uint32 :event_microsecond
|
10
|
+
uint32 :signature_id
|
11
|
+
uint32 :generator_id
|
12
|
+
uint32 :signature_revision
|
13
|
+
uint32 :classification_id
|
14
|
+
uint32 :priority_id
|
15
|
+
uint128 :ip_source
|
16
|
+
uint128 :ip_destination
|
17
|
+
uint16 :sport_itype
|
18
|
+
uint16 :dport_icode
|
19
|
+
uint8 :protocol
|
20
|
+
uint8 :packet_action
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Unified2
|
2
|
+
|
3
|
+
class Packet < ::BinData::Record
|
4
|
+
endian :big
|
5
|
+
|
6
|
+
uint32 :sensor_id
|
7
|
+
uint32 :event_id
|
8
|
+
uint32 :event_second
|
9
|
+
uint32 :packet_second
|
10
|
+
uint32 :packet_microsecond
|
11
|
+
uint32 :linktype
|
12
|
+
uint32 :packet_length
|
13
|
+
string :packet_data, :read_length => :packet_length
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'hexdump'
|
2
|
+
|
3
|
+
module Unified2
|
4
|
+
class Payload
|
5
|
+
|
6
|
+
attr_accessor :linktype, :length
|
7
|
+
|
8
|
+
def initialize(payload={})
|
9
|
+
@payload = payload[:payload]
|
10
|
+
@length = payload[:packet_length].to_i
|
11
|
+
@linktype = payload[:linktype]
|
12
|
+
end
|
13
|
+
|
14
|
+
def blank?
|
15
|
+
return true unless @payload
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def raw
|
20
|
+
@payload.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def hex
|
24
|
+
@payload.to_s.unpack('H*')
|
25
|
+
end
|
26
|
+
|
27
|
+
def dump(options={})
|
28
|
+
Hexdump.dump(@payload, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Unified2
|
2
|
+
module Primitive
|
3
|
+
|
4
|
+
class IPV4 < ::BinData::Primitive
|
5
|
+
array :octets, :type => :uint8, :initial_length => 4
|
6
|
+
|
7
|
+
def set(val)
|
8
|
+
ints = val.split(/\./).collect { |int| int.to_i }
|
9
|
+
self.octets = ints
|
10
|
+
end
|
11
|
+
|
12
|
+
def get
|
13
|
+
self.octets.collect { |octet| "%d" % octet }.join(".")
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'primitive/ipv4'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Unified2
|
2
|
+
class Sensor
|
3
|
+
|
4
|
+
attr_accessor :id, :hostname,
|
5
|
+
:interface, :name
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
@id = options[:id] || 0
|
9
|
+
@name = options[:name] || ""
|
10
|
+
@hostname ||= Socket.gethostname
|
11
|
+
@interface ||= options[:interface] || nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def update(attributes={})
|
15
|
+
return self if attributes.empty?
|
16
|
+
|
17
|
+
attributes.each do |key, value|
|
18
|
+
next unless self.respond_to?(key.to_sym)
|
19
|
+
instance_variable_set(:"@#{key}", value)
|
20
|
+
end
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Unified2
|
2
|
+
|
3
|
+
class Signature
|
4
|
+
|
5
|
+
attr_accessor :id, :revision, :name, :references
|
6
|
+
|
7
|
+
def initialize(signature={})
|
8
|
+
@id = signature[:signature_id] || 0
|
9
|
+
@revision = signature[:revision]
|
10
|
+
@name = signature[:name].strip
|
11
|
+
@references = signature[:references]
|
12
|
+
@blank = signature[:blank]
|
13
|
+
end
|
14
|
+
|
15
|
+
def blank?
|
16
|
+
@blank
|
17
|
+
end
|
18
|
+
|
19
|
+
def references
|
20
|
+
@references
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|