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.
Files changed (44) hide show
  1. data/ChangeLog.rdoc +6 -0
  2. data/LICENSE.txt +1 -1
  3. data/README.md +72 -0
  4. data/example/{basic-example.rb → example.rb} +3 -2
  5. data/example/seeds/{unified2 → unified2.log} +0 -0
  6. data/gemspec.yml +2 -0
  7. data/lib/unified2/classification.rb +17 -3
  8. data/lib/unified2/config_file.rb +34 -10
  9. data/lib/unified2/constructor/construct.rb +83 -0
  10. data/lib/unified2/constructor/event_ip4.rb +47 -0
  11. data/lib/unified2/constructor/event_ip6.rb +44 -0
  12. data/lib/unified2/constructor/packet.rb +30 -0
  13. data/lib/unified2/constructor/primitive/ipv4.rb +31 -0
  14. data/lib/unified2/{primitive.rb → constructor/primitive.rb} +0 -0
  15. data/lib/unified2/constructor/record_header.rb +17 -0
  16. data/lib/unified2/constructor.rb +1 -0
  17. data/lib/unified2/core_ext/string.rb +10 -2
  18. data/lib/unified2/event.rb +250 -100
  19. data/lib/unified2/exceptions/file_not_found.rb +6 -3
  20. data/lib/unified2/exceptions/file_not_readable.rb +6 -3
  21. data/lib/unified2/exceptions/unknown_load_type.rb +6 -3
  22. data/lib/unified2/payload.rb +82 -13
  23. data/lib/unified2/protocol.rb +141 -0
  24. data/lib/unified2/sensor.rb +22 -0
  25. data/lib/unified2/signature.rb +28 -4
  26. data/lib/unified2/version.rb +2 -2
  27. data/lib/unified2.rb +84 -13
  28. data/spec/event_spec.rb +112 -0
  29. data/spec/spec_helper.rb +45 -1
  30. data/spec/unified2_spec.rb +87 -1
  31. metadata +45 -25
  32. data/README.rdoc +0 -60
  33. data/Rakefile.compiled.rbc +0 -775
  34. data/example/connect.rb +0 -20
  35. data/example/models.rb +0 -194
  36. data/example/mysql-example.rb +0 -73
  37. data/example/search.rb +0 -14
  38. data/example/untitled.rb +0 -31
  39. data/lib/unified2/construct.rb +0 -54
  40. data/lib/unified2/event_ip4.rb +0 -26
  41. data/lib/unified2/event_ip6.rb +0 -23
  42. data/lib/unified2/packet.rb +0 -16
  43. data/lib/unified2/primitive/ipv4.rb +0 -19
  44. data/lib/unified2/record_header.rb +0 -10
@@ -0,0 +1,141 @@
1
+ module Unified2
2
+ #
3
+ # Protocol
4
+ #
5
+ class Protocol
6
+
7
+ #
8
+ # Initialize protocol object
9
+ #
10
+ # @param [String] protocol Event protocol
11
+ #
12
+ # @param [Event#packet] packet PacketFu object
13
+ #
14
+ def initialize(protocol, packet=nil)
15
+ @protocol = protocol
16
+ @packet = packet
17
+ end
18
+
19
+ #
20
+ # Protocol Header
21
+ #
22
+ # @return [Object, nil] Protocol header object
23
+ #
24
+ def header
25
+ if @packet.has_data?
26
+ if @packet.send(:"is_#{@protocol.downcase}?")
27
+ @packet.send(:"#{@protocol.downcase}_header")
28
+ end
29
+ else
30
+ nil
31
+ end
32
+ end
33
+
34
+ #
35
+ # ICMP?
36
+ #
37
+ # @return [true, false] Check is protocol is icmp
38
+ #
39
+ def icmp?
40
+ return true if @protocol == :ICMP
41
+ false
42
+ end
43
+
44
+ #
45
+ # TCP?
46
+ #
47
+ # @return [true, false] Check is protocol is tcp
48
+ #
49
+ def tcp?
50
+ return true if @protocol == :TCP
51
+ false
52
+ end
53
+
54
+ #
55
+ # UDP?
56
+ #
57
+ # @return [true, false] Check is protocol is udp
58
+ #
59
+ def udp?
60
+ return true if @protocol == :UDP
61
+ false
62
+ end
63
+
64
+ #
65
+ # Convert To String
66
+ #
67
+ # @return [String] Protocol
68
+ #
69
+ # @example
70
+ # event.protocol #=> 'TCP'
71
+ #
72
+ def to_s
73
+ @protocol.to_s
74
+ end
75
+
76
+ #
77
+ # Convert To Hash
78
+ #
79
+ # @return [Hash] Protocol header hash object
80
+ #
81
+ # @example
82
+ # event.protocol.to_h #=> {:length=>379, :seq=>3934511163, :ack=>1584708129 ... }
83
+ #
84
+ def to_h
85
+ if send(:"#{@protocol.downcase}?")
86
+ self.send(:"#{@protocol.downcase}")
87
+ else
88
+ {}
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def icmp(include_body=false)
95
+ @icmp = {
96
+ :length => header.len,
97
+ :type => header.icmp_type,
98
+ :csum => header.icmp_sum,
99
+ :code => header.icmp_code
100
+ }
101
+
102
+ @icmp[:body] = header.body if include_body
103
+
104
+ @icmp
105
+ end
106
+
107
+ def udp(include_body=false)
108
+ @udp = {
109
+ :length => header.len,
110
+ :sum => header.udp_sum,
111
+ }
112
+
113
+ @udp[:body] = header.body if include_body
114
+
115
+ @udp
116
+ end
117
+
118
+ def tcp(include_body=false)
119
+ @tcp = {
120
+ :length => header.len,
121
+ :seq => header.tcp_seq,
122
+ :ack => header.tcp_ack,
123
+ :win => header.tcp_win,
124
+ :sum => header.tcp_sum,
125
+ :urg => header.tcp_urg,
126
+ :hlen => header.tcp_hlen,
127
+ :reserved => header.tcp_reserved,
128
+ :ecn => header.tcp_ecn,
129
+ :opts => header.tcp_opts,
130
+ :opts_len => header.tcp_opts_len,
131
+ :rand_port => header.rand_port,
132
+ :options => header.tcp_options
133
+ }
134
+
135
+ @tcp[:body] = header.body if include_body
136
+
137
+ @tcp
138
+ end
139
+
140
+ end
141
+ end
@@ -1,8 +1,20 @@
1
1
  module Unified2
2
+ #
3
+ # Sensor
4
+ #
2
5
  class Sensor
3
6
 
4
7
  attr_accessor :id, :hostname, :interface, :name, :checksum
5
8
 
9
+ #
10
+ # Initialize sensor object
11
+ #
12
+ # @param [Hash] options Sensor hash attributes
13
+ #
14
+ # @option options [Integer] :id Sensor id
15
+ # @option options [String] :name Sensor name
16
+ # @option options [String] :interface Sensor interface
17
+ #
6
18
  def initialize(options={})
7
19
  @id = options[:id] || 0
8
20
  @name = options[:name] || ""
@@ -11,6 +23,16 @@ module Unified2
11
23
  @checksum = nil
12
24
  end
13
25
 
26
+ #
27
+ # Update
28
+ #
29
+ # @param [Hash] attributes Sensor attributes
30
+ #
31
+ # @option attributes [Integer] :id Sensor id
32
+ # @option attributes [String] :hostname Sensor hostname
33
+ # @option attributes [String] :name Sensor name
34
+ # @option attributes [String] :interface Sensor interface
35
+ #
14
36
  def update(attributes={})
15
37
  return self if attributes.empty?
16
38
 
@@ -1,21 +1,45 @@
1
1
  module Unified2
2
-
2
+ #
3
+ # Signature
4
+ #
3
5
  class Signature
6
+
7
+ attr_accessor :id, :generator, :revision, :name, :blank
4
8
 
5
- attr_accessor :id, :generator, :revision, :name
6
-
9
+ #
10
+ # Initialize signature object
11
+ #
12
+ # @param [Hash] signature Signature hash attributes
13
+ #
14
+ # @option signature [Integer] :signature_id Signature id
15
+ # @option signature [Integer] :generator_id Generator id
16
+ # @option signature [Integer] :revision Signature revision
17
+ # @option signature [Integer] :name Signature name
18
+ # @option signature [true, false] :blank Signature exists
19
+ #
7
20
  def initialize(signature={})
8
21
  @id = signature[:signature_id] || 0
9
22
  @generator = signature[:generator_id]
10
23
  @revision = signature[:revision]
11
24
  @name = signature[:name].strip
12
- @blank = signature[:blank]
25
+ @blank = signature[:blank] || false
13
26
  end
14
27
 
28
+ #
29
+ # Blank?
30
+ #
31
+ # @return [true, false]
32
+ # Return true if signature exists
33
+ #
15
34
  def blank?
16
35
  @blank
17
36
  end
18
37
 
38
+ #
39
+ # References
40
+ #
41
+ # @return [Array<String,String>] Signature references
42
+ #
19
43
  def references
20
44
  @references
21
45
  end
@@ -1,4 +1,4 @@
1
1
  module Unified2
2
- # unified2 version
3
- VERSION = "0.4.0"
2
+ # Unified2 version
3
+ VERSION = "0.5.0"
4
4
  end
data/lib/unified2.rb CHANGED
@@ -1,34 +1,67 @@
1
1
  require 'bindata'
2
2
  require 'digest'
3
3
  require 'socket'
4
- # http://cvs.snort.org/viewcvs.cgi/snort/src/output-plugins/spo_unified2.c?rev=1.3&content-type=text/vnd.viewcvs-markup
5
4
 
6
- require 'unified2/construct'
5
+ require 'unified2/constructor'
7
6
  require 'unified2/config_file'
8
7
  require 'unified2/core_ext'
9
8
  require 'unified2/event'
10
9
  require 'unified2/exceptions'
11
10
  require 'unified2/version'
12
11
 
12
+ #
13
+ # Unified2 Namespace
14
+ #
13
15
  module Unified2
14
-
16
+
17
+ #
18
+ # Configuration File Types
19
+ #
20
+ # Holds the available configuration
21
+ # file types current supported.
22
+ #
15
23
  TYPES = [
16
24
  :signatures,
17
25
  :generators,
18
26
  :classifications
19
27
  ]
20
-
28
+
21
29
  class << self
22
30
  attr_accessor :signatures, :generators,
23
31
  :sensor, :hostname, :interface,
24
32
  :classifications
25
33
  end
26
34
 
35
+ #
36
+ # Configuration
37
+ #
38
+ # @param [Hash] options Sensor Configuration
39
+ # @yield [ConfigFile] block Configurations
40
+ #
41
+ # @option options [Integer] :id Sensor id
42
+ # @option options [String] :name Sensor name
43
+ # @option options [String] :interface Sensor interface
44
+ #
45
+ # @return [nil]
46
+ #
27
47
  def self.configuration(options={}, &block)
28
- @sensor ||= Sensor.new
48
+ @sensor ||= Sensor.new(options)
29
49
  self.instance_eval(&block)
30
50
  end
31
-
51
+
52
+ #
53
+ # Sensor
54
+ #
55
+ # @param [Hash] options Sensor Configuration
56
+ # @yield [Sensor] block Sensor attributes
57
+ #
58
+ # @option options [Integer] :id Sensor id
59
+ # @option options [String] :hostname Sensor hostname
60
+ # @option options [String] :name Sensor name
61
+ # @option options [String] :interface Sensor interface
62
+ #
63
+ # @return [nil]
64
+ #
32
65
  def self.sensor(options={}, &block)
33
66
  if block
34
67
  @sensor.instance_eval(&block)
@@ -36,6 +69,17 @@ module Unified2
36
69
  @sensor.update(options)
37
70
  end
38
71
 
72
+ #
73
+ # Load
74
+ #
75
+ # @param [String] type Configuration type
76
+ # @param [String] path Configuration path
77
+ #
78
+ # @return [nil]
79
+ #
80
+ # @raise [FileNotReadable] Path not readable
81
+ # @raise [FileNotFound] File not found
82
+ #
39
83
  def self.load(type, path)
40
84
  unless TYPES.include?(type.to_sym)
41
85
  raise UnknownLoadType, "Error - #{@type} is unknown."
@@ -52,6 +96,20 @@ module Unified2
52
96
  end
53
97
  end
54
98
 
99
+ #
100
+ # Watch
101
+ #
102
+ # Monitor the unified2 file for events and process.
103
+ #
104
+ # @param [String] path Unified2 file path
105
+ # @param [String,Symbol,Integer] position IO position
106
+ # @yield [Event] block Event object
107
+ #
108
+ # @raise [FileNotReadable] Path not readable
109
+ # @raise [FileNotFound] File not found
110
+ #
111
+ # @return [nil]
112
+ #
55
113
  def self.watch(path, position=:first, &block)
56
114
 
57
115
  unless File.exists?(path)
@@ -73,7 +131,7 @@ module Unified2
73
131
  when :last
74
132
 
75
133
  until io.eof?
76
- event = Unified2::Construct.read(io)
134
+ event = Unified2::Constructor::Construct.read(io)
77
135
  event_id = event.data.event_id if event
78
136
  end
79
137
 
@@ -86,7 +144,7 @@ module Unified2
86
144
  when :first
87
145
 
88
146
  first_open = File.open(path)
89
- first_event = Unified2::Construct.read(first_open)
147
+ first_event = Unified2::Constructor::Construct.read(first_open)
90
148
  first_open.close
91
149
  event_id = first_event.data.event_id
92
150
  @event = Event.new(event_id)
@@ -96,7 +154,7 @@ module Unified2
96
154
 
97
155
  loop do
98
156
  begin
99
- event = Unified2::Construct.read(io)
157
+ event = Unified2::Constructor::Construct.read(io)
100
158
 
101
159
  if event_id
102
160
  if event.data.event_id.to_i > (event_id - 1)
@@ -116,7 +174,21 @@ module Unified2
116
174
  raise FileNotReadable, "Error - #{path} not readable."
117
175
  end
118
176
  end
119
-
177
+
178
+ #
179
+ # Read
180
+ #
181
+ # Read the unified2 log until EOF and process
182
+ # events.
183
+ #
184
+ # @param [String] path Unified2 file path
185
+ # @yield [Event] block Event object
186
+ #
187
+ # @raise [FileNotReadable] Path not readable
188
+ # @raise [FileNotFound] File not found
189
+ #
190
+ # @return [nil]
191
+ #
120
192
  def self.read(path, &block)
121
193
 
122
194
  unless File.exists?(path)
@@ -127,13 +199,13 @@ module Unified2
127
199
  io = File.open(path)
128
200
 
129
201
  first_open = File.open(path)
130
- first_event = Unified2::Construct.read(first_open)
202
+ first_event = Unified2::Constructor::Construct.read(first_open)
131
203
  first_open.close
132
204
 
133
205
  @event = Event.new(first_event.data.event_id)
134
206
 
135
207
  until io.eof?
136
- event = Unified2::Construct.read(io)
208
+ event = Unified2::Constructor::Construct.read(io)
137
209
  check_event(event, block)
138
210
  end
139
211
 
@@ -142,7 +214,6 @@ module Unified2
142
214
  end
143
215
  end
144
216
 
145
-
146
217
  private
147
218
 
148
219
  def self.check_event(event, block)
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+ require 'unified2'
3
+
4
+ describe Event do
5
+
6
+ before(:all) do
7
+ @event = Unified2.first('example/seeds/unified2.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 == 0
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 == 0
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 payload thats not blank" do
100
+ @event.payload.blank?.should == false
101
+ end
102
+
103
+ it "should have a hex payload" do
104
+ p = "000000004520008323bc000032113a080a0001061813076e90c84fac006fe498"
105
+ @event.payload.hex.should == p
106
+ end
107
+
108
+ it "should have a payload length" do
109
+ @event.payload.length.should == 70
110
+ end
111
+
112
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,49 @@
1
1
  gem 'rspec', '~> 2.4'
2
2
  require 'rspec'
3
- require 'unified2/version'
4
3
 
4
+ require 'unified2'
5
5
  include Unified2
6
+
7
+ module Unified2
8
+
9
+ def self.first(path)
10
+ unless File.exists?(path)
11
+ raise FileNotFound, "Error - #{path} not found."
12
+ end
13
+
14
+ if File.readable?(path)
15
+ io = File.open(path)
16
+
17
+ first_open = File.open(path)
18
+ first_event = Unified2::Constructor::Construct.read(first_open)
19
+ first_open.close
20
+
21
+ @event = Event.new(first_event.data.event_id)
22
+
23
+ loop do
24
+ event = Unified2::Constructor::Construct.read(io)
25
+
26
+ if event.data.event_id.to_i == @event.id.to_i
27
+ @event.load(event)
28
+ else
29
+ return @event
30
+ end
31
+ end
32
+
33
+ else
34
+ raise FileNotReadable, "Error - #{path} not readable."
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ Unified2.configuration do
41
+ sensor :interface => 'en1',
42
+ :name => 'Example Sensor',
43
+ :hostname => 'W0ots.local',
44
+ :id => 50000000000
45
+
46
+ load :signatures, 'example/seeds/sid-msg.map'
47
+ load :generators, 'example/seeds/gen-msg.map'
48
+ load :classifications, 'example/seeds/classification.config'
49
+ end
@@ -2,7 +2,93 @@ require 'spec_helper'
2
2
  require 'unified2'
3
3
 
4
4
  describe Unified2 do
5
+
5
6
  it "should have a VERSION constant" do
6
7
  subject.const_get('VERSION').should_not be_empty
7
8
  end
8
- end
9
+
10
+ it "should have a TYPES constant" do
11
+ subject.const_get('TYPES').should_not be_empty
12
+ end
13
+
14
+ it "should have the correct signature size" do
15
+ Unified2.signatures.size.should == 16710
16
+ end
17
+
18
+ it "should have the correct signature name" do
19
+ signature_name = "EXPLOIT Oracle BEA Weblogic server " \
20
+ "console-help.portal cross-site scripting attempt"
21
+
22
+ Unified2.signatures.data['16710'][:name].should == signature_name
23
+ end
24
+
25
+ it "should have the correct signature id" do
26
+ Unified2.signatures.data['16710'][:signature_id].should == 16710
27
+ end
28
+
29
+ it "should have the correct signature generator id" do
30
+ Unified2.signatures.data['16710'][:generator_id].should == 1
31
+ end
32
+
33
+ it "should have the correct classification size" do
34
+ Unified2.classifications.size.should == 35
35
+ end
36
+
37
+ it "should have the correct classification id" do
38
+ Unified2.classifications.data['35'][:severity_id].should == 2
39
+ end
40
+
41
+ it "should have the correct classification name" do
42
+ classification_name = "Sensitive Data was Transmitted Across the Network"
43
+ Unified2.classifications.data['35'][:name].should == classification_name
44
+ end
45
+
46
+ it "should have the correct classification short name" do
47
+ Unified2.classifications.data['35'][:short].should == "sdf"
48
+ end
49
+
50
+ it "should have the correct generator size" do
51
+ Unified2.generators.size.should == 388
52
+ end
53
+
54
+ it "should have the correct generator id" do
55
+ Unified2.generators.data["138.6"][:generator_id].should == 138
56
+ end
57
+
58
+ it "should have the correct generator name" do
59
+ generator_name = "sensitive_data: sensitive data - U.S. phone numbers"
60
+ Unified2.generators.data["138.6"][:name].should == generator_name
61
+ end
62
+
63
+ it "should have the correct generator signature id" do
64
+ Unified2.generators.data["138.6"][:signature_id].should == 6
65
+ end
66
+
67
+ it "should have a sensor name" do
68
+ Unified2.sensor.name.should == "Example Sensor"
69
+ end
70
+
71
+ it "should have a sensor interface" do
72
+ Unified2.sensor.interface.should == 'en1'
73
+ end
74
+
75
+ it "should have a sensor hostname" do
76
+ Unified2.sensor.hostname.should == 'W0ots.local'
77
+ end
78
+
79
+ it "should have a new sensor hostname if set" do
80
+ Unified2.sensor.hostname = 'OMG.stuff.local'
81
+ Unified2.sensor.hostname.should == 'OMG.stuff.local'
82
+ end
83
+
84
+ it "should have a new sensor interface if set" do
85
+ Unified2.sensor.interface = 'eth1'
86
+ Unified2.sensor.interface.should == 'eth1'
87
+ end
88
+
89
+ it "should have a new sensor name if set" do
90
+ Unified2.sensor.name = 'Mephux FTW!'
91
+ Unified2.sensor.name.should == "Mephux FTW!"
92
+ end
93
+
94
+ end