unified2 0.5.4 → 0.6.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.md +10 -0
- data/README.md +41 -35
- data/Rakefile +3 -2
- data/bin/ru2 +76 -0
- data/example/example.rb +10 -18
- data/example/example2.rb +44 -0
- data/example/seeds/classification.config +1 -1
- data/example/seeds/gen-msg.map +86 -9
- data/example/seeds/sid-msg.map +2849 -316
- data/example/seeds/unified2-current.log +0 -0
- data/example/seeds/{unified2.log → unified2-legacy.log} +0 -0
- data/gemspec.yml +2 -1
- data/lib/unified2/classification.rb +12 -0
- data/lib/unified2/config_file.rb +4 -1
- data/lib/unified2/constructor/construct.rb +52 -6
- data/lib/unified2/constructor/event_ip4.rb +18 -3
- data/lib/unified2/constructor/event_ip6.rb +22 -4
- data/lib/unified2/constructor/extra_construct.rb +46 -0
- data/lib/unified2/constructor/extra_data.rb +37 -0
- data/lib/unified2/constructor/extra_data_header.rb +28 -0
- data/lib/unified2/constructor/legacy_event_ip4.rb +54 -0
- data/lib/unified2/constructor/legacy_event_ip6.rb +52 -0
- data/lib/unified2/constructor/packet.rb +9 -1
- data/lib/unified2/constructor/primitive/ipv4.rb +9 -0
- data/lib/unified2/constructor/record_header.rb +9 -0
- data/lib/unified2/constructor.rb +2 -1
- data/lib/unified2/core_ext/string.rb +2 -1
- data/lib/unified2/event.rb +290 -165
- data/lib/unified2/exceptions/binary_read_error.rb +11 -0
- data/lib/unified2/exceptions/file_not_found.rb +4 -1
- data/lib/unified2/exceptions/file_not_readable.rb +4 -1
- data/lib/unified2/exceptions/unknown_load_type.rb +4 -1
- data/lib/unified2/exceptions.rb +2 -1
- data/lib/unified2/extra.rb +128 -0
- data/lib/unified2/packet.rb +211 -0
- data/lib/unified2/protocol.rb +54 -63
- data/lib/unified2/sensor.rb +14 -2
- data/lib/unified2/signature.rb +12 -0
- data/lib/unified2/version.rb +4 -1
- data/lib/unified2.rb +65 -81
- data/spec/event_spec.rb +40 -27
- data/spec/legacy_event_spec.rb +122 -0
- data/spec/spec_helper.rb +10 -21
- data/spec/unified2_spec.rb +3 -3
- metadata +124 -140
- data/lib/unified2/payload.rb +0 -114
data/lib/unified2.rb
CHANGED
@@ -10,10 +10,10 @@ require 'unified2/exceptions'
|
|
10
10
|
require 'unified2/version'
|
11
11
|
|
12
12
|
#
|
13
|
-
# Unified2
|
13
|
+
# Unified2
|
14
14
|
#
|
15
15
|
module Unified2
|
16
|
-
|
16
|
+
|
17
17
|
#
|
18
18
|
# Configuration File Types
|
19
19
|
#
|
@@ -107,78 +107,51 @@ module Unified2
|
|
107
107
|
#
|
108
108
|
# @raise [FileNotReadable] Path not readable
|
109
109
|
# @raise [FileNotFound] File not found
|
110
|
+
# @raise [BinaryReadError] Invalid position or file
|
110
111
|
#
|
111
112
|
# @return [nil]
|
112
113
|
#
|
113
114
|
def self.watch(path, position=:first, &block)
|
115
|
+
validate_path(path)
|
114
116
|
|
115
|
-
|
116
|
-
raise FileNotFound, "Error - #{path} not found."
|
117
|
-
end
|
118
|
-
|
119
|
-
if File.readable?(path)
|
120
|
-
io = File.open(path)
|
121
|
-
|
122
|
-
case position
|
123
|
-
when Integer, Fixnum
|
124
|
-
|
125
|
-
event_id = position.to_i.zero? ? 1 : position.to_i
|
126
|
-
@event = Event.new(event_id)
|
127
|
-
|
128
|
-
when Symbol, String
|
129
|
-
|
130
|
-
case position.to_sym
|
131
|
-
when :last
|
132
|
-
|
133
|
-
until io.eof?
|
134
|
-
event = Unified2::Constructor::Construct.read(io)
|
135
|
-
event_id = event.data.event_id if event
|
136
|
-
end
|
117
|
+
io = File.open(path)
|
137
118
|
|
138
|
-
|
119
|
+
case position
|
120
|
+
when Integer
|
121
|
+
io.sysseek(position, IO::SEEK_CUR)
|
139
122
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
first_open = File.open(path)
|
148
|
-
first_event = Unified2::Constructor::Construct.read(first_open)
|
149
|
-
first_open.close
|
150
|
-
event_id = first_event.data.event_id
|
151
|
-
@event = Event.new(event_id)
|
152
|
-
|
153
|
-
rescue EOFError
|
154
|
-
sleep 5
|
155
|
-
retry
|
156
|
-
end
|
157
|
-
|
158
|
-
end
|
123
|
+
when Symbol, String
|
124
|
+
|
125
|
+
if position == :last
|
126
|
+
io.sysseek(0, IO::SEEK_END)
|
127
|
+
else
|
128
|
+
io.sysseek(0, IO::SEEK_SET)
|
159
129
|
end
|
130
|
+
|
131
|
+
else
|
132
|
+
io.sysseek(0, IO::SEEK_SET)
|
133
|
+
end
|
160
134
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
if event_id
|
166
|
-
if event.data.event_id.to_i > (event_id - 1)
|
167
|
-
check_event(event, block)
|
168
|
-
end
|
169
|
-
else
|
170
|
-
check_event(event, block)
|
171
|
-
end
|
172
|
-
|
173
|
-
rescue EOFError
|
174
|
-
sleep 5
|
175
|
-
retry
|
176
|
-
end
|
177
|
-
end
|
135
|
+
# Start with a null event.
|
136
|
+
# This will always be ignored.
|
137
|
+
@event = Event.new(0)
|
178
138
|
|
179
|
-
|
180
|
-
|
139
|
+
loop do
|
140
|
+
begin
|
141
|
+
event = Unified2::Constructor::Construct.read(io)
|
142
|
+
check_event(event, block)
|
143
|
+
rescue EOFError
|
144
|
+
sleep 5
|
145
|
+
retry
|
146
|
+
end
|
181
147
|
end
|
148
|
+
|
149
|
+
rescue RuntimeError
|
150
|
+
raise(BinaryReadError, "incorrect file format or position seek error")
|
151
|
+
rescue Interrupt
|
152
|
+
io.pos if io
|
153
|
+
ensure
|
154
|
+
io.close if io
|
182
155
|
end
|
183
156
|
|
184
157
|
#
|
@@ -196,40 +169,51 @@ module Unified2
|
|
196
169
|
# @return [nil]
|
197
170
|
#
|
198
171
|
def self.read(path, &block)
|
172
|
+
validate_path(path)
|
199
173
|
|
200
|
-
|
201
|
-
|
202
|
-
|
174
|
+
io = File.open(path)
|
175
|
+
|
176
|
+
# Start with a null event.
|
177
|
+
# This will always be ignored.
|
178
|
+
@event = Event.new(0)
|
203
179
|
|
204
|
-
|
205
|
-
|
180
|
+
until io.eof?
|
181
|
+
event = Unified2::Constructor::Construct.read(io)
|
182
|
+
check_event(event, block)
|
183
|
+
end
|
206
184
|
|
207
|
-
|
208
|
-
|
209
|
-
|
185
|
+
rescue Interrupt
|
186
|
+
ensure
|
187
|
+
io.close if io
|
188
|
+
end
|
210
189
|
|
211
|
-
|
190
|
+
private
|
212
191
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
192
|
+
def self.validate_path(path)
|
193
|
+
unless File.exists?(path)
|
194
|
+
raise FileNotFound, "Error - #{path} not found."
|
195
|
+
end
|
217
196
|
|
218
|
-
|
197
|
+
unless File.readable?(path)
|
219
198
|
raise FileNotReadable, "Error - #{path} not readable."
|
220
|
-
end
|
199
|
+
end
|
221
200
|
end
|
222
201
|
|
223
|
-
|
202
|
+
def self.check_event(event, block)
|
224
203
|
|
225
|
-
|
204
|
+
if event.data.respond_to?(:event_id)
|
226
205
|
if @event.id == event.data.event_id
|
227
206
|
@event.load(event)
|
228
207
|
else
|
229
|
-
block.call(@event)
|
208
|
+
block.call(@event) unless @event.id.zero?
|
209
|
+
@extra_data = false
|
230
210
|
@event = Event.new(event.data.event_id)
|
231
211
|
@event.load(event)
|
232
212
|
end
|
213
|
+
else
|
214
|
+
@event.load(event)
|
233
215
|
end
|
234
216
|
|
217
|
+
end
|
218
|
+
|
235
219
|
end
|
data/spec/event_spec.rb
CHANGED
@@ -4,15 +4,15 @@ require 'unified2'
|
|
4
4
|
describe Event do
|
5
5
|
|
6
6
|
before(:all) do
|
7
|
-
@event = Unified2.first('example/seeds/unified2.log')
|
7
|
+
@event = Unified2.first('example/seeds/unified2-current.log')
|
8
8
|
end
|
9
9
|
|
10
10
|
it "should have an event_id" do
|
11
|
-
@event.
|
11
|
+
@event.id.should == 1
|
12
12
|
end
|
13
13
|
|
14
14
|
it "should have an event time" do
|
15
|
-
@event.timestamp.to_s.should == '
|
15
|
+
@event.timestamp.to_s.should == '2011-11-12 16:04:25 -0500'
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should have a sensor id" do
|
@@ -32,35 +32,35 @@ describe Event do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
it "should have a source address" do
|
35
|
-
@event.source_ip.should == "
|
35
|
+
@event.source_ip.should == "10.0.1.6"
|
36
36
|
end
|
37
37
|
|
38
38
|
it "should have a source port" do
|
39
|
-
@event.source_port.should ==
|
39
|
+
@event.source_port.should == 52378
|
40
40
|
end
|
41
41
|
|
42
42
|
it "should have a destination address" do
|
43
|
-
@event.destination_ip.should == "
|
43
|
+
@event.destination_ip.should == "199.47.216.149"
|
44
44
|
end
|
45
45
|
|
46
46
|
it "should have a destination port" do
|
47
|
-
@event.destination_port.should ==
|
47
|
+
@event.destination_port.should == 80
|
48
48
|
end
|
49
49
|
|
50
50
|
it "should have a protocol" do
|
51
|
-
@event.protocol.to_s.should == '
|
51
|
+
@event.protocol.to_s.should == 'TCP'
|
52
52
|
end
|
53
53
|
|
54
54
|
it "should have a severity" do
|
55
|
-
@event.severity.should ==
|
55
|
+
@event.severity.should == 1
|
56
56
|
end
|
57
57
|
|
58
58
|
it "should have an event checksum" do
|
59
|
-
@event.checksum.should == "
|
59
|
+
@event.checksum.should == "01af8a5245fce250989b00990406fa63"
|
60
60
|
end
|
61
61
|
|
62
62
|
it "should have a signature id" do
|
63
|
-
@event.signature.id.should ==
|
63
|
+
@event.signature.id.should == 18608
|
64
64
|
end
|
65
65
|
|
66
66
|
it "should have a signature generator id" do
|
@@ -68,7 +68,7 @@ describe Event do
|
|
68
68
|
end
|
69
69
|
|
70
70
|
it "should have a signature revision" do
|
71
|
-
@event.signature.revision.should ==
|
71
|
+
@event.signature.revision.should == 3
|
72
72
|
end
|
73
73
|
|
74
74
|
it "should have a signature thats not blank" do
|
@@ -76,37 +76,50 @@ describe Event do
|
|
76
76
|
end
|
77
77
|
|
78
78
|
it "should have a signature name" do
|
79
|
-
@event.signature.name.should == "
|
80
|
-
" Communication Administratively Prohibited"
|
79
|
+
@event.signature.name.should == "POLICY Dropbox desktop software in use"
|
81
80
|
end
|
82
81
|
|
83
82
|
it "should have a classification id" do
|
84
|
-
@event.classification.id.should ==
|
83
|
+
@event.classification.id.should == 33
|
85
84
|
end
|
86
85
|
|
87
86
|
it "should have a classification short name" do
|
88
|
-
@event.classification.short.should == "
|
87
|
+
@event.classification.short.should == "policy-violation"
|
89
88
|
end
|
90
89
|
|
91
90
|
it "should have a classification severity" do
|
92
|
-
@event.classification.severity.should ==
|
91
|
+
@event.classification.severity.should == 1
|
93
92
|
end
|
94
93
|
|
95
94
|
it "should have a classification name" do
|
96
|
-
@event.classification.name.should == "
|
95
|
+
@event.classification.name.should == "Potential Corporate Privacy Violation"
|
97
96
|
end
|
98
|
-
|
99
|
-
it "should have
|
100
|
-
@event.
|
97
|
+
|
98
|
+
it "should have zero packets associated with this event" do
|
99
|
+
@event.packets.count.should == 0
|
101
100
|
end
|
102
|
-
|
103
|
-
it "
|
104
|
-
|
105
|
-
|
101
|
+
|
102
|
+
it "event extras count should equal 2" do
|
103
|
+
@event.extras.count.should == 2
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should have extra data thats not blank" do
|
107
|
+
@event.extras.first.blank?.should == false
|
106
108
|
end
|
107
109
|
|
108
|
-
it "should have
|
109
|
-
@event.
|
110
|
+
it "extra data should have the correct value" do
|
111
|
+
@event.extras.first.value.should == "/subscribe?host_int=26273724&ns_map=2895792_52721831662858160,15287777_4310255073,2027874_776915740822270306,2816020_68722292756,564088_4146784271384222584,555213_5414107641578813645&ts=1321131865"
|
112
|
+
@event.extras.last.value.should == "notify9.dropbox.com"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "extra data should have a value length" do
|
116
|
+
@event.extras.first.length.should == 204
|
117
|
+
end
|
118
|
+
|
119
|
+
it "extra data should have a header" do
|
120
|
+
@event.extras.first.header[:event_type].should == 4
|
121
|
+
@event.extras.first.header[:event_length].should == 228
|
110
122
|
end
|
111
123
|
|
112
124
|
end
|
125
|
+
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'unified2'
|
3
|
+
|
4
|
+
describe "LegacyEvent" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
@event = Unified2.first('example/seeds/unified2-legacy.log')
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have an event_id" do
|
11
|
+
@event.to_i.should == 1
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have an event time" do
|
15
|
+
@event.timestamp.to_s.should == '2010-10-05 22:50:18 -0400'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have a sensor id" do
|
19
|
+
@event.sensor.id.should == 50000000000
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should have a sensor name" do
|
23
|
+
@event.sensor.name.should == "Example Sensor"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have a sensor interface" do
|
27
|
+
@event.sensor.interface.should == "en1"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should have a sensor hostname" do
|
31
|
+
@event.sensor.hostname.should == "W0ots.local"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have a source address" do
|
35
|
+
@event.source_ip.should == "24.19.7.110"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should have a source port" do
|
39
|
+
@event.source_port.should == 3
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have a destination address" do
|
43
|
+
@event.destination_ip.should == "10.0.1.6"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should have a destination port" do
|
47
|
+
@event.destination_port.should == 13
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should have a protocol" do
|
51
|
+
@event.protocol.to_s.should == 'ICMP'
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should have a severity" do
|
55
|
+
@event.severity.should == 3
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have an event checksum" do
|
59
|
+
@event.checksum.should == "6e96db6e8fe649c939711400ea4625eb"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should have a signature id" do
|
63
|
+
@event.signature.id.should == 485
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should have a signature generator id" do
|
67
|
+
@event.signature.generator.should == 1
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should have a signature revision" do
|
71
|
+
@event.signature.revision.should == 5
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should have a signature thats not blank" do
|
75
|
+
@event.signature.blank.should == false
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should have a signature name" do
|
79
|
+
@event.signature.name.should == "DELETED ICMP Destination Unreachable" \
|
80
|
+
" Communication Administratively Prohibited"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should have a classification id" do
|
84
|
+
@event.classification.id.should == 29
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should have a classification short name" do
|
88
|
+
@event.classification.short.should == "misc-activity"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should have a classification severity" do
|
92
|
+
@event.classification.severity.should == 3
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should have a classification name" do
|
96
|
+
@event.classification.name.should == "Misc activity"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should have a one packet associated with this event" do
|
100
|
+
@event.packets.count.should == 1
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should have a payload thats not blank" do
|
104
|
+
@event.packets.first.blank?.should == false
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should have a hex payload without the header" do
|
108
|
+
p = "000000004520008323bc000032113a080a0001061813076e90c84fac006fe498"
|
109
|
+
@event.packets.first.hex(false).should == p
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should have a hex payload with the header" do
|
113
|
+
p = "0026bb1b75d30016cbc32b43080045000038c3e80000f001dc551813076e0a000106" +
|
114
|
+
"030d3776000000004520008323bc000032113a080a0001061813076e90c84fac006fe498"
|
115
|
+
@event.packets.first.hex(true).should == p
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should have a payload length" do
|
119
|
+
@event.packets.first.length.should == 70
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -7,31 +7,20 @@ include Unified2
|
|
7
7
|
module Unified2
|
8
8
|
|
9
9
|
def self.first(path)
|
10
|
-
|
11
|
-
raise FileNotFound, "Error - #{path} not found."
|
12
|
-
end
|
13
|
-
|
14
|
-
if File.readable?(path)
|
15
|
-
io = File.open(path)
|
10
|
+
validate_path(path)
|
16
11
|
|
17
|
-
|
18
|
-
|
19
|
-
first_open.close
|
12
|
+
io = File.open(path)
|
13
|
+
io.sysseek(0, IO::SEEK_SET)
|
20
14
|
|
21
|
-
|
15
|
+
@event = Event.new(1)
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
if event.data.event_id
|
27
|
-
@event.load(event)
|
28
|
-
else
|
29
|
-
return @event
|
30
|
-
end
|
17
|
+
loop do
|
18
|
+
event = Unified2::Constructor::Construct.read(io)
|
19
|
+
if event.data.respond_to?(:event_id)
|
20
|
+
return @event if event.data.event_id != @event.id
|
31
21
|
end
|
32
22
|
|
33
|
-
|
34
|
-
raise FileNotReadable, "Error - #{path} not readable."
|
23
|
+
@event.load(event)
|
35
24
|
end
|
36
25
|
end
|
37
26
|
|
@@ -46,4 +35,4 @@ Unified2.configuration do
|
|
46
35
|
load :signatures, 'example/seeds/sid-msg.map'
|
47
36
|
load :generators, 'example/seeds/gen-msg.map'
|
48
37
|
load :classifications, 'example/seeds/classification.config'
|
49
|
-
end
|
38
|
+
end
|
data/spec/unified2_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe Unified2 do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it "should have the correct signature size" do
|
15
|
-
Unified2.signatures.size.should ==
|
15
|
+
Unified2.signatures.size.should == 19243
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should have the correct signature name" do
|
@@ -48,7 +48,7 @@ describe Unified2 do
|
|
48
48
|
end
|
49
49
|
|
50
50
|
it "should have the correct generator size" do
|
51
|
-
Unified2.generators.size.should ==
|
51
|
+
Unified2.generators.size.should == 461
|
52
52
|
end
|
53
53
|
|
54
54
|
it "should have the correct generator id" do
|
@@ -91,4 +91,4 @@ describe Unified2 do
|
|
91
91
|
Unified2.sensor.name.should == "Mephux FTW!"
|
92
92
|
end
|
93
93
|
|
94
|
-
end
|
94
|
+
end
|