Class: Msf::Exploit::Remote::HTTP::Kubernetes::AuthParser

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb

Overview

Parses the succinct Kubernetes authentication API response and converts it into a more consumable format

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(auth_response) ⇒ AuthParser

Returns a new instance of AuthParser.


6
7
8
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 6

def initialize(auth_response)
  @auth_response = auth_response
end

Instance Attribute Details

#auth_responseObject (readonly, protected)

Returns the value of attribute auth_response.


42
43
44
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 42

def auth_response
  @auth_response
end

Instance Method Details

#as_simple_rule(policy_rule) ⇒ Object (protected)

returns nil if it's not possible to simplify this rule


138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 138

def as_simple_rule(policy_rule)
  return nil if policy_rule[:resourceNames].count > 1 || policy_rule[:nonResourceURLs].count > 0
  return nil if policy_rule[:apiGroups].count != 1 || policy_rule[:resources].count != 1

  allowed_keys = %i[apiGroups resources verbs resourceNames nonResourceURLs]
  unsupported_keys = policy_rule.keys - allowed_keys
  return nil if unsupported_keys.any?

  simple_rule = {
    group: policy_rule[:apiGroups][0],
    resource: policy_rule[:resources][0]
  }

  if policy_rule[:resourceNames].any?
    simple_rule.merge(
      {
        resourceName: policy_rule[:resourceNames][0]
      }
    )
  end

  simple_rule
end

#as_tableObject

Converts the kubernetes auth response into an array of human readable table


26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 26

def as_table
  columns = ['Resources', 'Non-Resource URLs', 'Resource Names', 'Verbs']
  rows = rules.map do |rule|
    [
      combine_resource_groups(rule[:resources], rule[:apiGroups]),
      "[#{rule[:nonResourceURLs].join(' ')}]",
      "[#{rule[:resourceNames].join(' ')}]",
      "[#{rule[:verbs].join(' ')}]"
    ]
  end

  { columns: columns, rows: rows }
end

#breakdown_policy_rule(policy_rule) ⇒ Object (protected)

Converts the original policy rule into its smaller policy rules, where there is at most one verb for each rule


56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 56

def breakdown_policy_rule(policy_rule)
  sub_rules = []
  policy_rule.fetch(:apiGroups, []).each do |group|
    policy_rule.fetch(:resources, []).each do |resource|
      policy_rule.fetch(:verbs, []).each do |verb|
        if policy_rule.fetch(:resourceNames, []).any?
          sub_rules += policy_rule[:resourceNames].map do |resource_name|
            policy_rule_for(
              apiGroups: [group],
              resources: [resource],
              verbs: [verb],
              resourceNames: [resource_name]
            )
          end
        else
          sub_rules << policy_rule_for(
            apiGroups: [group],
            resources: [resource],
            verbs: [verb]
          )
        end
      end
    end
  end

  sub_rules += policy_rule.fetch(:nonResourceURLs, []).flat_map do |non_resource_url|
    policy_rule[:verbs].map do |verb|
      policy_rule_for(
        nonResourceURLs: [non_resource_url],
        verbs: [verb]
      )
    end
  end

  sub_rules
end

#combine_resource_groups(resources, groups) ⇒ Object (protected)


174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 174

def combine_resource_groups(resources, groups)
  return '' if resources.empty?

  parts = resources[0].split('/', 2)
  result = parts[0]

  if groups.count > 0 && groups[0] != ''
    result = result + '.' + groups[0]
  end

  if parts.count == 2
    result = result + '/' + parts[1]
  end

  result
end

#compact_policy_rules(policy_rules) ⇒ Object (protected)

Merge policy rules together, by joining rules that are associated with the same resource, but different verbs


114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 114

def compact_policy_rules(policy_rules)
  compact_rules = []
  simple_rules = {}
  policy_rules.each do |policy_rule|
    simple_rule = as_simple_rule(policy_rule)
    if simple_rule
      existing_rule = find_policy(simple_rules, simple_rule)

      if existing_rule
        existing_rule[:verbs] ||= []
        existing_rule[:verbs] = (existing_rule[:verbs] + policy_rule[:verbs]).uniq
      else
        simple_rules[simple_rule] = policy_rule.clone
      end
    else
      compact_rules << policy_rule
    end
  end

  compact_rules += simple_rules.values
  compact_rules
end

#find_policy(existing_simple_rules, simple_rule) ⇒ Object (protected)

Finds the original policy rule associated with a simplified rule


94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 94

def find_policy(existing_simple_rules, simple_rule)
  return nil if simple_rule.nil?

  existing_simple_rules.each do |existing_simple_rule, policy|
    is_match = (
      existing_simple_rule[:group] == simple_rule[:group] &&
        existing_simple_rule[:resource] == simple_rule[:resource] &&
        existing_simple_rule[:resourceName] == simple_rule[:resourceName]
    )

    if is_match
      return policy
    end
  end

  nil
end

#human_readable_policy_rule(rule) ⇒ Object (protected)


162
163
164
165
166
167
168
169
170
171
172
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 162

def human_readable_policy_rule(rule)
  parts = []

  parts << "APIGroups:[#{rule[:apiGroups].join(' ')}]" if rule[:apiGroups].any?
  parts << "Resources:[#{rule[:resources].join(' ')}]" if rule[:resources].any?
  parts << "NonResourceURLs:[#{rule[:nonResourceURLs].join(' ')}]" if rule[:nonResourceURLs].any?
  parts << "ResourceNames:[#{rule[:resourceNames].join(' ')}]" if rule[:resourceNames].any?
  parts << "Verbs:[#{rule[:verbs].join(' ')}]" if rule[:verbs].any?

  parts.join(', ')
end

#policy_rule_for(apiGroups: [], resources: [], verbs: [], resourceNames: [], nonResourceURLs: []) ⇒ Object (protected)


44
45
46
47
48
49
50
51
52
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 44

def policy_rule_for(apiGroups: [], resources: [], verbs: [], resourceNames: [], nonResourceURLs: [])
  {
    apiGroups: apiGroups,
    resources: resources,
    verbs: verbs,
    resourceNames: resourceNames,
    nonResourceURLs: nonResourceURLs
  }
end

#rulesObject

Extracts the list of rules associated with a kubernetes auth response


11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/msf/core/exploit/remote/http/kubernetes/auth_parser.rb', line 11

def rules
  resource_rules = auth_response.dig(:status, :resourceRules) || []
  non_resource_rules = auth_response.dig(:status, :nonResourceRules) || []
  policy_rules = resource_rules + non_resource_rules

  broke_down_policy_rules = policy_rules.flat_map do |policy_rule|
    breakdown_policy_rule(policy_rule)
  end
  compacted_rules = compact_policy_rules(broke_down_policy_rules)
  sorted_rules = compacted_rules.sort_by { |rule| human_readable_policy_rule(rule) }

  sorted_rules
end