unified2 0.1.1
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/.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
|