Class: Contrast::Agent::Assess::Rule::Provider::HardcodedKey

Inherits:
Object
  • Object
show all
Includes:
HardcodedValueRule
Defined in:
lib/contrast/agent/assess/rule/provider/hardcoded_key.rb

Overview

Determine if there are any cryptographic keys hardcoded into the sourcecode of the application. A constant is a cryptographic key if: 1) the name contains a KEY_FIELD_NAME value 2) the value is a non-empty array of only Fixnums

Constant Summary collapse

REDACTED_MARKER =
' = [**REDACTED**]'
NAME =
'hardcoded-key'
KEY_FIELD_NAMES =

These are names, determined by the security team (Matt & Ar), that indicate a field is likely to be a password or secret token of some sort.

%w[KEY AES DES IV SECRET].cs__freeze
NON_KEY_PARTIAL_NAMES =

These are markers whose presence indicates that a field is more likely to be a descriptor or requirement than an actual key. We should ignore fields that contain them.

%w[CONTENT_CODES RESPONSE_CODES ERROR_CODES].cs__freeze
BYTE_HOLDERS =
%i[ARRAY LIST].cs__freeze

Constants included from HardcodedValueRule

Contrast::Agent::Assess::Rule::Provider::HardcodedValueRule::CODE_SOURCE_KEY, Contrast::Agent::Assess::Rule::Provider::HardcodedValueRule::CONSTANT_NAME_KEY, Contrast::Agent::Assess::Rule::Provider::HardcodedValueRule::SOURCE_KEY

Instance Method Summary collapse

Methods included from HardcodedValueRule

#disabled?, #parse

Methods included from Components::Logger::InstanceMethods

#cef_logger, #logger

Instance Method Details

#bytes_call?(value_node) ⇒ Boolean

A node is a bytes_call if it’s the Node for String#bytes. We care about this specifically as it’s likely to be a common way to generate a key constant, rather than directly declaring an integer array.

Parameters:

  • value_node (RubyVM::AbstractSyntaxTree::Node)

    the node to evaluate

Returns:

  • (Boolean)

    is this a node for String#bytes or not



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/contrast/agent/assess/rule/provider/hardcoded_key.rb', line 80

def bytes_call? value_node
  return false unless value_node.type == :CALL

  children = value_node.children
  return false unless children
  return false unless children.length >= 2

  potential_string_node = children[0]
  unless potential_string_node.cs__is_a?(RubyVM::AbstractSyntaxTree::Node) &&
        potential_string_node.type == :STR

    return false
  end

  children[1] == :bytes
end

#name_passes?(constant_string) ⇒ Boolean

Returns:

  • (Boolean)


31
32
33
34
# File 'lib/contrast/agent/assess/rule/provider/hardcoded_key.rb', line 31

def name_passes? constant_string
  KEY_FIELD_NAMES.any? { |name| constant_string.index(name) } &&
      NON_KEY_PARTIAL_NAMES.none? { |name| constant_string.index(name) }
end

#redacted_markerObject



68
69
70
# File 'lib/contrast/agent/assess/rule/provider/hardcoded_key.rb', line 68

def redacted_marker
  REDACTED_MARKER
end

#rule_idObject



27
28
29
# File 'lib/contrast/agent/assess/rule/provider/hardcoded_key.rb', line 27

def rule_id
  NAME
end

#value_node_passes?(value_node) ⇒ Boolean

Determine if the given value node violates the hardcode key rule

Parameters:

  • value_node (RubyVM::AbstractSyntaxTree::Node)

    the node to evaluate

Returns:

  • (Boolean)


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/contrast/agent/assess/rule/provider/hardcoded_key.rb', line 40

def value_node_passes? value_node
  # If it's a freeze call, then evaluate the entity being frozen
  value_node = value_node.children[0] if freeze_call?(value_node)
  # If it's a String being turned into bytes, then it matches key
  # expectations
  return true if bytes_call?(value_node)

  type = value_node.type
  return false unless BYTE_HOLDERS.include?(type)
  return false unless value_node.children.any?

  # Unless this is an array of literal numerics, we don't match.
  # That array seems to always end in a nil value, so we allow
  # those as well.
  value_node.children.each do |child|
    next unless child

    unless child.cs__is_a?(RubyVM::AbstractSyntaxTree::Node) &&
          child.type == :LIT &&
          child.children[0]&.cs__is_a?(Integer)

      return false
    end
  end

  true
end