unified2 0.4.0 → 0.5.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/ChangeLog.rdoc +6 -0
- data/LICENSE.txt +1 -1
- data/README.md +72 -0
- data/example/{basic-example.rb → example.rb} +3 -2
- data/example/seeds/{unified2 → unified2.log} +0 -0
- data/gemspec.yml +2 -0
- data/lib/unified2/classification.rb +17 -3
- data/lib/unified2/config_file.rb +34 -10
- data/lib/unified2/constructor/construct.rb +83 -0
- data/lib/unified2/constructor/event_ip4.rb +47 -0
- data/lib/unified2/constructor/event_ip6.rb +44 -0
- data/lib/unified2/constructor/packet.rb +30 -0
- data/lib/unified2/constructor/primitive/ipv4.rb +31 -0
- data/lib/unified2/{primitive.rb → constructor/primitive.rb} +0 -0
- data/lib/unified2/constructor/record_header.rb +17 -0
- data/lib/unified2/constructor.rb +1 -0
- data/lib/unified2/core_ext/string.rb +10 -2
- data/lib/unified2/event.rb +250 -100
- data/lib/unified2/exceptions/file_not_found.rb +6 -3
- data/lib/unified2/exceptions/file_not_readable.rb +6 -3
- data/lib/unified2/exceptions/unknown_load_type.rb +6 -3
- data/lib/unified2/payload.rb +82 -13
- data/lib/unified2/protocol.rb +141 -0
- data/lib/unified2/sensor.rb +22 -0
- data/lib/unified2/signature.rb +28 -4
- data/lib/unified2/version.rb +2 -2
- data/lib/unified2.rb +84 -13
- data/spec/event_spec.rb +112 -0
- data/spec/spec_helper.rb +45 -1
- data/spec/unified2_spec.rb +87 -1
- metadata +45 -25
- data/README.rdoc +0 -60
- data/Rakefile.compiled.rbc +0 -775
- data/example/connect.rb +0 -20
- data/example/models.rb +0 -194
- data/example/mysql-example.rb +0 -73
- data/example/search.rb +0 -14
- data/example/untitled.rb +0 -31
- data/lib/unified2/construct.rb +0 -54
- data/lib/unified2/event_ip4.rb +0 -26
- data/lib/unified2/event_ip6.rb +0 -23
- data/lib/unified2/packet.rb +0 -16
- data/lib/unified2/primitive/ipv4.rb +0 -19
- data/lib/unified2/record_header.rb +0 -10
data/lib/unified2/event.rb
CHANGED
@@ -1,196 +1,345 @@
|
|
1
1
|
require 'unified2/classification'
|
2
2
|
require 'unified2/payload'
|
3
|
+
require 'unified2/protocol'
|
3
4
|
require 'unified2/sensor'
|
4
5
|
require 'unified2/signature'
|
5
6
|
|
7
|
+
require 'packetfu'
|
6
8
|
require 'ipaddr'
|
7
9
|
require 'json'
|
8
10
|
|
9
11
|
module Unified2
|
10
|
-
|
12
|
+
#
|
13
|
+
# Event
|
14
|
+
#
|
11
15
|
class Event
|
12
|
-
|
13
|
-
attr_accessor :id, :metadata, :packet
|
14
16
|
|
17
|
+
attr_accessor :id, :event_data, :packet_data
|
18
|
+
#
|
19
|
+
# Initialize event
|
20
|
+
#
|
21
|
+
# @param [Integer] id Event id
|
22
|
+
#
|
15
23
|
def initialize(id)
|
16
|
-
@id = id
|
24
|
+
@id = id.to_i
|
17
25
|
end
|
18
26
|
|
27
|
+
#
|
28
|
+
# Packet Time
|
29
|
+
#
|
30
|
+
# Time of creation for the unified2 packet.
|
31
|
+
#
|
32
|
+
# @return [Time, nil] Packet time object
|
33
|
+
#
|
19
34
|
def packet_time
|
20
|
-
if @
|
21
|
-
@
|
22
|
-
@timestamp = Time.at(@
|
35
|
+
if @packet_data.has_key?(:packet_second)
|
36
|
+
@packet_data[:packet_second]
|
37
|
+
@timestamp = Time.at(@packet_data[:packet_second].to_i)
|
23
38
|
end
|
24
39
|
end
|
25
|
-
|
40
|
+
|
41
|
+
#
|
42
|
+
# Checksum
|
43
|
+
#
|
44
|
+
# Create a unique checksum for each event
|
45
|
+
# using the ip source, destination, signature id,
|
46
|
+
# generator id, sensor id, severity id, and the
|
47
|
+
# classification id.
|
48
|
+
#
|
49
|
+
# @return [String] Event checksum
|
50
|
+
#
|
26
51
|
def checksum
|
27
52
|
checkdum = [ip_source, ip_destination, signature.id, signature.generator, sensor.id, severity, classification.id]
|
28
53
|
Digest::MD5.hexdigest(checkdum.join(''))
|
29
54
|
end
|
30
55
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
56
|
+
#
|
57
|
+
# Event Time
|
58
|
+
#
|
59
|
+
# The event timestamp created by unified2.
|
60
|
+
#
|
61
|
+
# @return [Time, nil] Event time object
|
62
|
+
#
|
35
63
|
def event_time
|
36
|
-
if @
|
37
|
-
@timestamp = Time.at(@
|
64
|
+
if @packet_data.has_key?(:event_second)
|
65
|
+
@timestamp = Time.at(@packet_data[:event_second].to_i)
|
38
66
|
end
|
39
67
|
end
|
40
68
|
alias :timestamp :event_time
|
41
69
|
|
70
|
+
#
|
71
|
+
# Microseconds
|
72
|
+
#
|
73
|
+
# The event time in microseconds.
|
74
|
+
#
|
75
|
+
# @return [String, nil] Event microseconds
|
76
|
+
#
|
42
77
|
def microseconds
|
43
|
-
if @
|
44
|
-
@microseconds = @
|
78
|
+
if @event_data.has_key?(:event_microsecond)
|
79
|
+
@microseconds = @event_data[:event_microsecond]
|
45
80
|
end
|
46
81
|
end
|
47
82
|
|
83
|
+
#
|
84
|
+
# Sensor
|
85
|
+
#
|
86
|
+
# @return [Sensor] Sensor object
|
87
|
+
#
|
48
88
|
def sensor
|
49
89
|
@sensor ||= Unified2.sensor
|
50
90
|
end
|
51
91
|
|
92
|
+
#
|
93
|
+
# Packet Action
|
94
|
+
#
|
95
|
+
# @return [Integer, nil] Packet action
|
96
|
+
#
|
52
97
|
def packet_action
|
53
|
-
if @
|
54
|
-
@
|
98
|
+
if @event_data.has_key?(:event_second)
|
99
|
+
@packet_data_action = @event_data[:packet_action]
|
55
100
|
end
|
56
101
|
end
|
57
102
|
|
103
|
+
#
|
104
|
+
# Protocol
|
105
|
+
#
|
106
|
+
# @return [Protocol] Event protocol object
|
107
|
+
#
|
58
108
|
def protocol
|
59
|
-
|
60
|
-
@protocol = determine_protocol(@metadata[:protocol])
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def icmp?
|
65
|
-
return true if protocol == :ICMP
|
66
|
-
false
|
67
|
-
end
|
68
|
-
|
69
|
-
def tcp?
|
70
|
-
return true if protocol == :TCP
|
71
|
-
false
|
109
|
+
@protocol = Protocol.new(determine_protocol(@event_data[:protocol]), packet)
|
72
110
|
end
|
73
111
|
|
74
|
-
def udp?
|
75
|
-
return true if protocol == :UDP
|
76
|
-
false
|
77
|
-
end
|
78
112
|
|
113
|
+
#
|
114
|
+
# Classification
|
115
|
+
#
|
116
|
+
# @return [Classification] Event classification object
|
117
|
+
#
|
79
118
|
def classification
|
80
|
-
|
81
|
-
@classification = Classification.new(@metadata[:classification]) if @metadata[:classification]
|
82
|
-
end
|
119
|
+
@classification = Classification.new(@event_data[:classification]) if @event_data[:classification]
|
83
120
|
end
|
84
121
|
|
122
|
+
#
|
123
|
+
# Signature
|
124
|
+
#
|
125
|
+
# @return [Signature, nil] Event signature object
|
126
|
+
#
|
85
127
|
def signature
|
86
|
-
if @
|
87
|
-
@signature = Signature.new(@
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def generator_id
|
92
|
-
if @metadata.is_a?(Hash)
|
93
|
-
@metadata[:generator_id] if @metadata.has_key?(:generator_id)
|
128
|
+
if @event_data.is_a?(Hash)
|
129
|
+
@signature = Signature.new(@event_data[:signature])
|
94
130
|
end
|
95
131
|
end
|
96
132
|
|
133
|
+
#
|
134
|
+
# Source IP Address
|
135
|
+
#
|
136
|
+
# @return [IPAddr] Event source ip address
|
137
|
+
#
|
97
138
|
def ip_source
|
98
|
-
if @
|
99
|
-
@
|
139
|
+
if @event_data.is_a?(Hash)
|
140
|
+
@event_data[:ip_source] if @event_data.has_key?(:ip_source)
|
100
141
|
end
|
101
142
|
end
|
102
143
|
alias :source_ip :ip_source
|
103
144
|
|
104
|
-
#
|
145
|
+
#
|
146
|
+
# Source Port
|
147
|
+
#
|
148
|
+
# @return [Integer] Event source port
|
149
|
+
#
|
150
|
+
# @note
|
151
|
+
# Event#source_port will return zero if the
|
152
|
+
# event protocol is icmp.
|
153
|
+
#
|
105
154
|
def source_port
|
106
|
-
return 0 if icmp?
|
107
|
-
@source_port = @
|
155
|
+
return 0 if protocol.icmp?
|
156
|
+
@source_port = @event_data[:sport_itype] if @event_data.has_key?(:sport_itype)
|
108
157
|
end
|
109
158
|
|
159
|
+
#
|
160
|
+
# Destination IP Address
|
161
|
+
#
|
162
|
+
# @return [IPAddr] Event destination ip address
|
163
|
+
#
|
110
164
|
def ip_destination
|
111
|
-
if @
|
112
|
-
@
|
165
|
+
if @event_data.is_a?(Hash)
|
166
|
+
@event_data[:ip_destination] if @event_data.has_key?(:ip_destination)
|
113
167
|
end
|
114
168
|
end
|
115
169
|
alias :destination_ip :ip_destination
|
116
170
|
|
117
|
-
#
|
171
|
+
#
|
172
|
+
# Destination Port
|
173
|
+
#
|
174
|
+
# @return [Integer] Event destination port
|
175
|
+
#
|
176
|
+
# @note
|
177
|
+
# Event#destination_port will return zero if the
|
178
|
+
# event protocol is icmp.
|
179
|
+
#
|
118
180
|
def destination_port
|
119
|
-
return 0 if icmp?
|
120
|
-
@source_port = @
|
181
|
+
return 0 if protocol.icmp?
|
182
|
+
@source_port = @event_data[:dport_icode] if @event_data.has_key?(:dport_icode)
|
121
183
|
end
|
122
184
|
|
185
|
+
#
|
186
|
+
# Severity
|
187
|
+
#
|
188
|
+
# @return [Integer] Event severity id
|
189
|
+
#
|
123
190
|
def severity
|
124
|
-
@severity = @
|
191
|
+
@severity = @event_data[:priority_id].to_i
|
125
192
|
end
|
126
193
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
194
|
+
#
|
195
|
+
# Packet
|
196
|
+
#
|
197
|
+
# @return [Packet] Event packet object
|
198
|
+
#
|
199
|
+
# @note
|
200
|
+
# Please view the packetfu documentation for more
|
201
|
+
# information. (http://code.google.com/p/packetfu/)
|
202
|
+
#
|
203
|
+
def packet
|
204
|
+
@packet = PacketFu::Packet.parse(@packet_data[:packet])
|
133
205
|
end
|
134
206
|
|
207
|
+
#
|
208
|
+
# Payload
|
209
|
+
#
|
210
|
+
# @return [Payload] Event payload object
|
211
|
+
#
|
212
|
+
def payload
|
213
|
+
Payload.new(packet.payload, @packet_data)
|
214
|
+
end
|
215
|
+
|
216
|
+
#
|
217
|
+
# Load
|
218
|
+
#
|
219
|
+
# Initializes the raw data returned by
|
220
|
+
# bindata into a more comfurtable format.
|
221
|
+
#
|
222
|
+
# @param [Hash] Name Description
|
223
|
+
#
|
224
|
+
# @return [nil]
|
225
|
+
#
|
135
226
|
def load(event)
|
136
227
|
if event.data.respond_to?(:signature_id)
|
137
|
-
@
|
228
|
+
@event_data ||= build_event_data(event)
|
138
229
|
end
|
139
230
|
|
140
231
|
if event.data.respond_to?(:packet_data)
|
141
|
-
@
|
232
|
+
@packet_data ||= build_packet_data(event)
|
142
233
|
end
|
143
234
|
end
|
144
235
|
|
236
|
+
#
|
237
|
+
# Convert To Hash
|
238
|
+
#
|
239
|
+
# @return [Hash] Event hash object
|
240
|
+
#
|
145
241
|
def to_h
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
data.merge!(@packet)
|
151
|
-
return data
|
152
|
-
end
|
153
|
-
else
|
154
|
-
if @packet.is_a?(Hash)
|
155
|
-
return @packet
|
156
|
-
end
|
242
|
+
@to_hash = {}
|
243
|
+
|
244
|
+
[@event_data, @packet_data].each do |hash|
|
245
|
+
@to_hash.merge!(hash) if hash.is_a?(Hash)
|
157
246
|
end
|
247
|
+
|
248
|
+
@to_hash
|
158
249
|
end
|
159
250
|
|
251
|
+
#
|
252
|
+
# Convert To Integer
|
253
|
+
#
|
254
|
+
# @return [Integer] Event id
|
255
|
+
#
|
160
256
|
def to_i
|
161
257
|
@id.to_i
|
162
258
|
end
|
163
|
-
|
259
|
+
|
260
|
+
#
|
261
|
+
# Convert To Json
|
262
|
+
#
|
263
|
+
# @return [String] Event hash in json format
|
264
|
+
#
|
164
265
|
def json
|
165
266
|
to_h.to_json
|
166
267
|
end
|
167
268
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
#
|
172
|
-
#
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
269
|
+
#
|
270
|
+
# Ethernet Header
|
271
|
+
#
|
272
|
+
# @return [Hash] Ethernet header
|
273
|
+
#
|
274
|
+
def eth_header
|
275
|
+
if ((packet.is_eth?) && packet.has_data?)
|
276
|
+
@ip_header = {
|
277
|
+
:v => payload.packet.ip_header.ip_v,
|
278
|
+
:hl => payload.packet.ip_header.ip_hl,
|
279
|
+
:tos => payload.packet.ip_header.ip_tos,
|
280
|
+
:len => payload.packet.ip_header.ip_len,
|
281
|
+
:id => payload.packet.ip_header.ip_id,
|
282
|
+
:frag => payload.packet.ip_header.ip_frag,
|
283
|
+
:ttl => payload.packet.ip_header.ip_ttl,
|
284
|
+
:proto => payload.packet.ip_header.ip_proto,
|
285
|
+
:sum => payload.packet.ip_header.ip_sum
|
286
|
+
}
|
185
287
|
else
|
288
|
+
@ip_header = {}
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
#
|
293
|
+
# IP Header
|
294
|
+
#
|
295
|
+
# @return [Hash] IP header
|
296
|
+
#
|
297
|
+
def ip_header
|
298
|
+
if ((packet.is_ip?) && packet.has_data?)
|
299
|
+
@ip_header = {
|
300
|
+
:v => packet.ip_header.ip_v,
|
301
|
+
:hl => packet.ip_header.ip_hl,
|
302
|
+
:tos => packet.ip_header.ip_tos,
|
303
|
+
:len => packet.ip_header.ip_len,
|
304
|
+
:id => packet.ip_header.ip_id,
|
305
|
+
:frag => packet.ip_header.ip_frag,
|
306
|
+
:ttl => packet.ip_header.ip_ttl,
|
307
|
+
:proto => packet.ip_header.ip_proto,
|
308
|
+
:sum => packet.ip_header.ip_sum
|
309
|
+
}
|
310
|
+
else
|
311
|
+
@ip_header = {}
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
#
|
316
|
+
# Convert To String
|
317
|
+
#
|
318
|
+
# @return [String] Event string object
|
319
|
+
#
|
320
|
+
def to_s
|
321
|
+
data = %{
|
322
|
+
Sensor: #{sensor.id}
|
323
|
+
Event ID: #{id}
|
324
|
+
Timestamp: #{timestamp.strftime('%D %H:%M:%S')}
|
325
|
+
Severity: #{severity}
|
326
|
+
Protocol: #{protocol}
|
327
|
+
Source IP: #{source_ip}:#{source_port}
|
328
|
+
Destination IP: #{destination_ip}:#{destination_port}
|
329
|
+
Signature: #{signature.name}
|
330
|
+
Classification: #{classification.name}
|
331
|
+
}
|
332
|
+
unless payload.blank?
|
333
|
+
data += "Payload:\n"
|
186
334
|
payload.dump(:width => 30, :output => data)
|
187
|
-
data + "#############################################################################"
|
188
335
|
end
|
336
|
+
|
337
|
+
data.gsub(/^\s+/, "")
|
189
338
|
end
|
190
339
|
|
191
340
|
private
|
192
341
|
|
193
|
-
def
|
342
|
+
def build_event_data(event)
|
194
343
|
@event_hash = {}
|
195
344
|
|
196
345
|
@event_hash = {
|
@@ -220,13 +369,13 @@ data = %{
|
|
220
369
|
@event_hash
|
221
370
|
end
|
222
371
|
|
223
|
-
def
|
372
|
+
def build_packet_data(event)
|
224
373
|
@packet_hash = {}
|
225
374
|
@packet_hash = {
|
226
375
|
:linktype => event.data.linktype,
|
227
376
|
:packet_microsecond => event.data.packet_microsecond,
|
228
377
|
:packet_second => event.data.packet_second,
|
229
|
-
:
|
378
|
+
:packet => event.data.packet_data,
|
230
379
|
:event_second => event.data.event_second,
|
231
380
|
:packet_length => event.data.packet_length
|
232
381
|
}
|
@@ -325,5 +474,6 @@ data = %{
|
|
325
474
|
end
|
326
475
|
end
|
327
476
|
|
328
|
-
end
|
329
|
-
|
477
|
+
end # class Event
|
478
|
+
|
479
|
+
end # module Unified2
|
data/lib/unified2/payload.rb
CHANGED
@@ -1,34 +1,103 @@
|
|
1
1
|
require 'hexdump'
|
2
2
|
|
3
3
|
module Unified2
|
4
|
+
#
|
5
|
+
# Payload
|
6
|
+
#
|
4
7
|
class Payload
|
5
|
-
|
6
|
-
attr_accessor :linktype, :length
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
|
9
|
+
attr_accessor :linktype, :length, :packet
|
10
|
+
|
11
|
+
#
|
12
|
+
# Initialize payload object
|
13
|
+
#
|
14
|
+
# @param [String] raw Raw binary payload
|
15
|
+
# @param [Hash] packet Packet attributes
|
16
|
+
#
|
17
|
+
# @option packet [String] :packet Packet
|
18
|
+
# @option packet [Integer] :packet_length Packet length
|
19
|
+
# @option packet [Integer] :linktype Packet linktype
|
20
|
+
#
|
21
|
+
def initialize(raw, packet={})
|
22
|
+
@packet = raw
|
23
|
+
@length = packet[:packet_length].to_i
|
24
|
+
@linktype = packet[:linktype]
|
12
25
|
end
|
13
26
|
|
27
|
+
#
|
28
|
+
# Blank?
|
29
|
+
#
|
30
|
+
# @return [true, false] Check is payload is blank
|
31
|
+
#
|
14
32
|
def blank?
|
15
|
-
return true unless @
|
33
|
+
return true unless @packet
|
16
34
|
false
|
17
35
|
end
|
18
36
|
|
37
|
+
#
|
38
|
+
# Raw
|
39
|
+
#
|
40
|
+
# @return [String] Raw binary payload
|
41
|
+
#
|
19
42
|
def raw
|
20
|
-
@
|
43
|
+
@packet
|
21
44
|
end
|
22
|
-
|
45
|
+
|
46
|
+
#
|
47
|
+
# Hex
|
48
|
+
#
|
49
|
+
# @return [String] Convert payload to hex
|
50
|
+
#
|
23
51
|
def hex
|
24
|
-
@hex = @
|
52
|
+
@hex = @packet.to_s.unpack('H*')
|
25
53
|
return @hex.first if @hex
|
26
54
|
nil
|
27
55
|
end
|
28
56
|
|
57
|
+
#
|
58
|
+
# Dump
|
59
|
+
#
|
60
|
+
# @param [options] options Hash of options for Hexdump#dump
|
61
|
+
#
|
62
|
+
# @option options [Integer] :width (16)
|
63
|
+
# The number of bytes to dump for each line.
|
64
|
+
#
|
65
|
+
# @option options [Symbol, Integer] :base (:hexadecimal)
|
66
|
+
# The base to print bytes in. Supported bases include, `:hexadecimal`,
|
67
|
+
# `:hex`, `16, `:decimal`, `:dec`, `10, `:octal`, `:oct`, `8`,
|
68
|
+
# `:binary`, `:bin` and `2`.
|
69
|
+
#
|
70
|
+
# @option options [Boolean] :ascii (false)
|
71
|
+
# Print ascii characters when possible.
|
72
|
+
#
|
73
|
+
# @option options [#<<] :output (STDOUT)
|
74
|
+
# The output to print the hexdump to.
|
75
|
+
#
|
76
|
+
# @yield [index,hex_segment,print_segment]
|
77
|
+
# The given block will be passed the hexdump break-down of each segment.
|
78
|
+
#
|
79
|
+
# @yieldparam [Integer] index
|
80
|
+
# The index of the hexdumped segment.
|
81
|
+
#
|
82
|
+
# @yieldparam [Array<String>] hex_segment
|
83
|
+
# The hexadecimal-byte representation of the segment.
|
84
|
+
#
|
85
|
+
# @yieldparam [Array<String>] print_segment
|
86
|
+
# The print-character representation of the segment.
|
87
|
+
#
|
88
|
+
# @return [nil]
|
89
|
+
#
|
90
|
+
# @raise [ArgumentError]
|
91
|
+
# The given data does not define the `#each_byte` method, or
|
92
|
+
#
|
93
|
+
# @note
|
94
|
+
# Please view the hexdump documentation for more
|
95
|
+
# information. Hexdump is a great lib by @postmodern.
|
96
|
+
# (http://github.com/postmodern/hexdump)
|
97
|
+
#
|
29
98
|
def dump(options={})
|
30
|
-
Hexdump.dump(@
|
99
|
+
Hexdump.dump(@packet, options)
|
31
100
|
end
|
32
101
|
|
33
102
|
end
|
34
|
-
end
|
103
|
+
end
|