metasploit_data_models 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +8 -8
  2. data/app/models/mdm/host.rb +7 -0
  3. data/app/models/mdm/service.rb +30 -1
  4. data/app/models/mdm/tag.rb +10 -0
  5. data/app/models/metasploit_data_models/ip_address/v4/cidr.rb +14 -0
  6. data/app/models/metasploit_data_models/ip_address/v4/nmap.rb +14 -0
  7. data/app/models/metasploit_data_models/ip_address/v4/range.rb +12 -0
  8. data/app/models/metasploit_data_models/ip_address/v4/segment/nmap/list.rb +126 -0
  9. data/app/models/metasploit_data_models/ip_address/v4/segment/nmap/range.rb +12 -0
  10. data/app/models/metasploit_data_models/ip_address/v4/segment/single.rb +123 -0
  11. data/app/models/metasploit_data_models/ip_address/v4/segmented.rb +200 -0
  12. data/app/models/metasploit_data_models/ip_address/v4/single.rb +53 -0
  13. data/app/models/metasploit_data_models/search/operation/ip_address.rb +60 -0
  14. data/app/models/metasploit_data_models/search/operator/ip_address.rb +33 -0
  15. data/app/models/metasploit_data_models/search/visitor/attribute.rb +1 -0
  16. data/app/models/metasploit_data_models/search/visitor/includes.rb +1 -0
  17. data/app/models/metasploit_data_models/search/visitor/joins.rb +1 -0
  18. data/app/models/metasploit_data_models/search/visitor/where.rb +51 -0
  19. data/config/locales/en.yml +35 -4
  20. data/lib/metasploit_data_models/ip_address.rb +5 -0
  21. data/lib/metasploit_data_models/ip_address/cidr.rb +174 -0
  22. data/lib/metasploit_data_models/ip_address/range.rb +181 -0
  23. data/lib/metasploit_data_models/match/child.rb +48 -0
  24. data/lib/metasploit_data_models/match/parent.rb +103 -0
  25. data/lib/metasploit_data_models/version.rb +4 -4
  26. data/metasploit_data_models.gemspec +2 -1
  27. data/spec/app/models/mdm/cred_spec.rb +164 -31
  28. data/spec/app/models/mdm/service_spec.rb +33 -44
  29. data/spec/app/models/metasploit_data_models/ip_address/v4/cidr_spec.rb +121 -0
  30. data/spec/app/models/metasploit_data_models/ip_address/v4/nmap_spec.rb +151 -0
  31. data/spec/app/models/metasploit_data_models/ip_address/v4/range_spec.rb +300 -0
  32. data/spec/app/models/metasploit_data_models/ip_address/v4/segment/nmap/list_spec.rb +278 -0
  33. data/spec/app/models/metasploit_data_models/ip_address/v4/segment/nmap/range_spec.rb +304 -0
  34. data/spec/app/models/metasploit_data_models/ip_address/v4/segment/segmented_spec.rb +29 -0
  35. data/spec/app/models/metasploit_data_models/ip_address/v4/segment/single_spec.rb +315 -0
  36. data/spec/app/models/metasploit_data_models/ip_address/v4/single_spec.rb +183 -0
  37. data/spec/app/models/metasploit_data_models/search/operation/ip_address_spec.rb +182 -0
  38. data/spec/app/models/metasploit_data_models/search/operator/ip_address_spec.rb +19 -0
  39. data/spec/app/models/metasploit_data_models/search/visitor/relation_spec.rb +229 -36
  40. data/spec/dummy/config/application.rb +1 -1
  41. data/spec/dummy/db/structure.sql +3011 -0
  42. data/spec/factories/mdm/services.rb +3 -1
  43. data/spec/lib/metasploit_data_models/ip_address/cidr_spec.rb +350 -0
  44. data/spec/lib/metasploit_data_models/ip_address/range_spec.rb +77 -0
  45. data/spec/lib/metasploit_data_models/match/child_spec.rb +61 -0
  46. data/spec/lib/metasploit_data_models/match/parent_spec.rb +155 -0
  47. data/spec/support/matchers/match_regex_exactly.rb +28 -0
  48. data/spec/support/shared/contexts/rex/text.rb +15 -0
  49. data/spec/support/shared/examples/metasploit_data_models/search/operation/ipaddress/match.rb +109 -0
  50. metadata +58 -9
  51. data/spec/dummy/db/schema.rb +0 -609
@@ -0,0 +1,53 @@
1
+ # A single IPv4 address, in standard, dotted decimal notation.
2
+ #
3
+ # @example Dotted Decimal Notation
4
+ # '1.2.3.4'
5
+ #
6
+ class MetasploitDataModels::IPAddress::V4::Single < MetasploitDataModels::IPAddress::V4::Segmented
7
+ #
8
+ # Segments
9
+ #
10
+
11
+ segment class_name: 'MetasploitDataModels::IPAddress::V4::Segment::Single'
12
+
13
+ #
14
+ # Instance Methods
15
+ #
16
+
17
+ # Adds `other` IPv4 address to this IPv4 address.
18
+ #
19
+ # @return [MetasploitDataModels::IPAddress::V4::Single] a new IPv4 address contain the sum of the two addresses
20
+ # segments with carries from lower to higher segments.
21
+ # @raise [TypeError] if `other` isn't the same class.
22
+ # @raise [ArgmentError] if `self` plus `other` yields an IP address greater than 255.255.255.255.
23
+ # @see succ
24
+ def +(other)
25
+ unless other.is_a? self.class
26
+ raise TypeError, "Cannot add #{other.class} to #{self.class}"
27
+ end
28
+
29
+ carry = 0
30
+ sum_segments = []
31
+ low_to_high_segments = segments.zip(other.segments).reverse
32
+
33
+ low_to_high_segments.each do |self_segment, other_segment|
34
+ segment, carry = self_segment.add_with_carry(other_segment, carry)
35
+ sum_segments.unshift segment
36
+ end
37
+
38
+ unless carry == 0
39
+ raise ArgumentError,
40
+ "#{self} + #{other} is not a valid IP address. It is #{sum_segments.join('.')} with a carry (#{carry})"
41
+ end
42
+
43
+ self.class.new(segments: sum_segments)
44
+ end
45
+
46
+ # The succeeding IPv4 address.
47
+ #
48
+ # @see #+
49
+ # @raise (see #+)
50
+ def succ
51
+ self + self.class.new(value: '0.0.0.1')
52
+ end
53
+ end
@@ -0,0 +1,60 @@
1
+ # Searches an `inet` column in a PostgreSQL database using
2
+ # {MetasploitDataModels::IPAddress::V4::Single a standard IPv4 address},
3
+ # {MetasploitDataModels::IPAddress::V4::CIDR an IPv4 CIDR block}, or
4
+ # {MetasploitDataModels::IPAddress::V4::Range an IPv4 address range}.
5
+ class MetasploitDataModels::Search::Operation::IPAddress < Metasploit::Model::Search::Operation::Base
6
+ include MetasploitDataModels::Match::Parent
7
+
8
+ #
9
+ # Match Children
10
+ #
11
+
12
+ # in order of precedence, so simpler single IPv4 addresses are matched before the more complex ranges which may
13
+ # degenerate to equivalent formatted value
14
+ match_children_named %w{
15
+ MetasploitDataModels::IPAddress::V4::Single
16
+ MetasploitDataModels::IPAddress::V4::CIDR
17
+ MetasploitDataModels::IPAddress::V4::Range
18
+ }
19
+
20
+ #
21
+ #
22
+ # Validations
23
+ #
24
+ #
25
+
26
+ #
27
+ # Validation Methods
28
+ #
29
+
30
+ validate :value_valid
31
+
32
+ #
33
+ # Attribute Validations
34
+ #
35
+
36
+ validates :value,
37
+ presence: true
38
+
39
+ #
40
+ # Instance Method
41
+ #
42
+
43
+ # @param formatted_value [#to_s]
44
+ def value=(formatted_value)
45
+ @value = match_child(formatted_value) || formatted_value
46
+ end
47
+
48
+ private
49
+
50
+ # Validates that `#value` is valid.
51
+ #
52
+ # @return [void]
53
+ def value_valid
54
+ if value.present?
55
+ unless value.respond_to?(:valid?) && value.valid?
56
+ errors.add(:value, :invalid)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ # Operator for `inet` columns in a PostgreSQL database, which operates on formatted values using
2
+ # {MetasploitDataModels::Search::Operation::IPAddress}.
3
+ class MetasploitDataModels::Search::Operator::IPAddress < Metasploit::Model::Search::Operator::Single
4
+ #
5
+ # Attributes
6
+ #
7
+
8
+ # @!attribute [r] attribute
9
+ # The attribute on `Metasploit::Model::Search::Operator::Base#klass` that is searchable.
10
+ #
11
+ # @return [Symbol] the attribute name
12
+ attr_accessor :attribute
13
+
14
+ #
15
+ # Validations
16
+ #
17
+
18
+ validates :attribute,
19
+ presence: true
20
+
21
+ #
22
+ # Instance Methods
23
+ #
24
+
25
+ alias_method :name, :attribute
26
+
27
+ # The class used for `Metasploit::Model::Search::Operator::Single#operate_on`.
28
+ #
29
+ # @return [String] `'MetasploitDataModels::Search::Operation::IPAddress'`
30
+ def operation_class_name
31
+ @operation_class_name ||= 'MetasploitDataModels::Search::Operation::IPAddress'
32
+ end
33
+ end
@@ -7,6 +7,7 @@ class MetasploitDataModels::Search::Visitor::Attribute
7
7
  end
8
8
 
9
9
  visit 'Metasploit::Model::Search::Operator::Attribute',
10
+ 'MetasploitDataModels::Search::Operator::IPAddress',
10
11
  'MetasploitDataModels::Search::Operator::Port::List' do |operator|
11
12
  table = operator.klass.arel_table
12
13
  table[operator.attribute]
@@ -23,6 +23,7 @@ class MetasploitDataModels::Search::Visitor::Includes
23
23
  end
24
24
 
25
25
  visit 'Metasploit::Model::Search::Operator::Attribute',
26
+ 'MetasploitDataModels::Search::Operator::IPAddress',
26
27
  'MetasploitDataModels::Search::Operator::Port::List' do |_operator|
27
28
  []
28
29
  end
@@ -43,6 +43,7 @@ class MetasploitDataModels::Search::Visitor::Joins
43
43
  end
44
44
 
45
45
  visit 'Metasploit::Model::Search::Operator::Attribute',
46
+ 'MetasploitDataModels::Search::Operator::IPAddress',
46
47
  'MetasploitDataModels::Search::Operator::Port::List' do |_|
47
48
  []
48
49
  end
@@ -45,6 +45,45 @@ class MetasploitDataModels::Search::Visitor::Where
45
45
  attribute.matches(match_value)
46
46
  end
47
47
 
48
+ visit 'MetasploitDataModels::IPAddress::CIDR' do |cidr|
49
+ cast_to_inet "#{cidr.address}/#{cidr.prefix_length}"
50
+ end
51
+
52
+ visit 'MetasploitDataModels::IPAddress::Range' do |ip_address_range|
53
+ range = ip_address_range.value
54
+
55
+ begin_node = visit range.begin
56
+ end_node = visit range.end
57
+
58
+ # AND nodes should be created with a list
59
+ Arel::Nodes::And.new([begin_node, end_node])
60
+ end
61
+
62
+ visit 'MetasploitDataModels::IPAddress::V4::Single' do |ip_address|
63
+ cast_to_inet(ip_address.to_s)
64
+ end
65
+
66
+ visit 'MetasploitDataModels::Search::Operation::IPAddress' do |operation|
67
+ attribute = attribute_visitor.visit operation.operator
68
+ value = operation.value
69
+ value_node = visit value
70
+
71
+ case value
72
+ when MetasploitDataModels::IPAddress::CIDR
73
+ Arel::Nodes::InfixOperation.new(
74
+ '<<',
75
+ attribute,
76
+ value_node
77
+ )
78
+ when MetasploitDataModels::IPAddress::Range
79
+ Arel::Nodes::Between.new(attribute, value_node)
80
+ when MetasploitDataModels::IPAddress::V4::Single
81
+ Arel::Nodes::Equality.new(attribute, value_node)
82
+ else
83
+ raise TypeError, "Don't know how to handle #{value.class}"
84
+ end
85
+ end
86
+
48
87
  visit 'MetasploitDataModels::Search::Operation::Port::Range' do |range_operation|
49
88
  attribute = attribute_visitor.visit range_operation.operator
50
89
 
@@ -69,5 +108,17 @@ class MetasploitDataModels::Search::Visitor::Where
69
108
  @method_visitor ||= MetasploitDataModels::Search::Visitor::Method.new
70
109
  end
71
110
 
111
+ private
112
+
113
+ # Casts a literal string to INET in AREL.
114
+ #
115
+ # @return [Arel::Nodes::NamedFunction]
116
+ def cast_to_inet(string)
117
+ cast_argument = Arel::Nodes::As.new(string, Arel::Nodes::SqlLiteral.new('INET'))
118
+ Arel::Nodes::NamedFunction.new('CAST', [cast_argument])
119
+ end
120
+
121
+ public
122
+
72
123
  Metasploit::Concern.run(self)
73
124
  end
@@ -5,12 +5,39 @@ en:
5
5
  messages:
6
6
  # have to duplicate activerecord.model.errors.message.taken because of the different i18n_scope
7
7
  taken: "has already been taken"
8
-
9
8
  models:
10
- metasploit_data_models/search/operator/multitext:
9
+ metasploit_data_models/ip_address/range:
11
10
  attributes:
12
- operator_names:
13
- too_short: "is too short (minimum is %{count} operator names)"
11
+ value:
12
+ order: "beginning of range (%{begin}) cannot be after end of range (%{end})"
13
+ metasploit_data_models/ip_address/v4/segmented:
14
+ attributes:
15
+ segments:
16
+ segment_invalid: "has invalid segment (%{segment}) at index %{index}"
17
+ wrong_length: "is the wrong length (should be %{count} segments)"
18
+ metasploit_data_models/ip_address/v4/segment/nmap/list:
19
+ attributes:
20
+ value:
21
+ array: "is not an Array"
22
+ element: "has invalid element (%{element}) at index %{index}"
23
+ metasploit_data_models/ip_address/v4/segment/nmap/range:
24
+ attributes:
25
+ value:
26
+ order: "beginning of range (%{begin}) cannot be after end of range (%{end})"
27
+ metasploit_data_models/search/operation/ip_address/invalid_range:
28
+ attributes:
29
+ value:
30
+ format: "does not match any known formats (IPv4, IPv4 CIDR, IPv4 NMAP, IPv4 Range, IPv6, IPv6 CIDR, IPv6 Range)"
31
+ metasploit_data_models/search/operation/ip_address/v4/range:
32
+ attributes:
33
+ value:
34
+ extreme: "%{extreme} (%{extreme_value}) is not an IPAddr"
35
+ order: "beginning of range (%{begin}) cannot be after end of range (%{end})"
36
+ type: "is not a range"
37
+ metasploit_data_models/search/operation/ip_address/v4/single:
38
+ attributes:
39
+ value:
40
+ format: "does not match IPv4 dotted decimal format"
14
41
  metasploit_data_models/search/operation/port/range:
15
42
  attributes:
16
43
  value:
@@ -21,3 +48,7 @@ en:
21
48
  value:
22
49
  order: "is not in order: begin (%{begin} is greater than end (%{end})."
23
50
  range: "is not a range"
51
+ metasploit_data_models/search/operator/multitext:
52
+ attributes:
53
+ operator_names:
54
+ too_short: "is too short (minimum is %{count} operator names)"
@@ -0,0 +1,5 @@
1
+ # Namespace for models for validating various IPv4 formats beyond those supported by the Ruby standard library's
2
+ # `IPAddr`.
3
+ module MetasploitDataModels::IPAddress
4
+
5
+ end
@@ -0,0 +1,174 @@
1
+ # Common behavior for Class-InterDomain Routing (`<address>/<prefix-length>`) notation under
2
+ # {MetasploitDataModels::IPAddress},
3
+ module MetasploitDataModels::IPAddress::CIDR
4
+ # so that translations for errors messages can be filed under metasploit_data_models/ip_address/cidr
5
+ extend ActiveModel::Naming
6
+ extend ActiveSupport::Concern
7
+
8
+ #
9
+ # CONSTANTS
10
+ #
11
+
12
+ # Separator between the {#address} and {#prefix_length}
13
+ SEPARATOR = '/'
14
+
15
+ #
16
+ # Attributes
17
+ #
18
+
19
+ # @!attribute address
20
+ # The IP address being masked by {#prefix_length} `1` bits.
21
+ #
22
+ # @return [Object] an instance of {address_class}
23
+ attr_reader :address
24
+
25
+ # @!attribute prefix_length
26
+ # The significant number of bits in {#address}.
27
+ #
28
+ # @return [Integer] number of `1` bits in the netmask of {#address}
29
+ attr_reader :prefix_length
30
+
31
+ included do
32
+ include ActiveModel::Validations
33
+
34
+ #
35
+ #
36
+ # Validations
37
+ #
38
+ #
39
+
40
+ #
41
+ # Validation Methods
42
+ #
43
+
44
+ validate :address_valid
45
+
46
+ #
47
+ # Attribute Validations
48
+ #
49
+
50
+ validates :address,
51
+ presence: true
52
+ end
53
+
54
+ # Class methods added to the including `Class`.
55
+ module ClassMethods
56
+ include MetasploitDataModels::Match::Child
57
+
58
+ #
59
+ # Attributes
60
+ #
61
+
62
+ # @!attribute address_class
63
+ # The Class` whose instance are usd for {MetasploitDataModels::IPAddress::CIDR#address}.
64
+ #
65
+ # @return [Class]
66
+ attr_reader :address_class
67
+
68
+ #
69
+ # Methods
70
+ #
71
+
72
+ # @note `address_class` must respond to `#segment_class` and `#segment_count` so {#maximum_prefix_length} can be
73
+ # calculated.
74
+ #
75
+ # Sets up the address class and allowed {#maximum_prefix_length} for the including `Class`.
76
+ #
77
+ # @param options [Hash{Symbol => Class}]
78
+ # @option options [Class, #segment_class, #segment_count] :address_class The `Class` whose instances will be used
79
+ # for {#address}.
80
+ def cidr(options={})
81
+ options.assert_valid_keys(:address_class)
82
+
83
+ @address_class = options.fetch(:address_class)
84
+
85
+ #
86
+ # Validations
87
+ #
88
+
89
+ validates :prefix_length,
90
+ numericality: {
91
+ only_integer: true,
92
+ greater_than_or_equal_to: 0,
93
+ less_than_or_equal_to: maximum_prefix_length
94
+ }
95
+ end
96
+
97
+ # Regular expression that matches a string that contains only a CIDR IP address.
98
+ #
99
+ # @return [Regexp]
100
+ def match_regexp
101
+ @match_regexp ||= /\A#{regexp}\z/
102
+ end
103
+
104
+ # The maximum number of bits in a prefix for the {#address_class}.
105
+ #
106
+ # @return [Integer] the number of bits across all segments of {#address_class}.
107
+ def maximum_prefix_length
108
+ @maximum_prefix_length ||= address_class.segment_count * address_class.segment_class.bits
109
+ end
110
+
111
+ # Regular expression that matches a portion of string that contains a CIDR IP address.
112
+ #
113
+ # @return [Regexp]
114
+ def regexp
115
+ @regexp ||= /(?<address>#{address_class.regexp})#{Regexp.escape(SEPARATOR)}(?<prefix_length>\d+)/
116
+ end
117
+ end
118
+
119
+ #
120
+ # Instance Methods
121
+ #
122
+
123
+ # Set {#address}.
124
+ #
125
+ # @param formatted_address [#to_s]
126
+ def address=(formatted_address)
127
+ @address = self.class.address_class.new(value: formatted_address)
128
+ end
129
+
130
+ # Set {#prefix_length}.
131
+ #
132
+ # @param formatted_prefix_length [#to_s]
133
+ def prefix_length=(formatted_prefix_length)
134
+ @prefix_length_before_type_cast = formatted_prefix_length
135
+
136
+ begin
137
+ # use Integer() instead of String#to_i as String#to_i will ignore trailing letters (i.e. '1two' -> 1) and turn all
138
+ # string without an integer in it to 0.
139
+ @prefix_length = Integer(formatted_prefix_length.to_s)
140
+ rescue ArgumentError
141
+ @prefix_length = formatted_prefix_length
142
+ end
143
+ end
144
+
145
+ # The formatted_prefix_length passed to {#prefix_length=}
146
+ #
147
+ # @return [#to_s]
148
+ def prefix_length_before_type_cast
149
+ @prefix_length_before_type_cast
150
+ end
151
+
152
+ # Parses the `formatted_value` into an {#address} and {#prefix_length}.
153
+ #
154
+ # @param formatted_value [#to_s]
155
+ def value=(formatted_value)
156
+ formatted_address, formatted_prefix_length = formatted_value.to_s.split(SEPARATOR, 2)
157
+
158
+ self.address = formatted_address
159
+ self.prefix_length = formatted_prefix_length
160
+
161
+ [address, prefix_length]
162
+ end
163
+
164
+ private
165
+
166
+ # Validates that {#address} is valid.
167
+ #
168
+ # @return [void]
169
+ def address_valid
170
+ if address && !address.valid?
171
+ errors.add(:address, :invalid)
172
+ end
173
+ end
174
+ end