Module: Contrast::Agent::Protect::Rule::InputClassification::Encoding

Includes:
Components::Logger::InstanceMethods
Included in:
Base
Defined in:
lib/contrast/agent/protect/rule/input_classification/encoding.rb

Overview

Module to hold different encoding utils.

Constant Summary collapse

KNOWN_DECODING_EXCEPTIONS =

Still a list is needed for this one, as it is not possible to determine if the value is encoded or not. As long as the list is short the method has a good percentage of success.

%w[cmd version if_modified_since].cs__freeze

Instance Method Summary collapse

Methods included from Components::Logger::InstanceMethods

#cef_logger, #logger

Instance Method Details

#cs__base64?(value, input_type) ⇒ Boolean

This methods is not performant, but is more safe for false positive. Base64 check is no trivial task. For example if one passes a value like ‘stringdw’ it will return true, or value ‘pass’, but it is indeed not encoded. using regexp like:

^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$

This will fail with any inputs from above, and this is because any characters with 4 bytes will be considered as base64 encoded, and without additional context it is impossible to determine if the value is encoded or not. Not to mention the above regexp will not detect empty spaces.

Alternative to the above regexp, acting the same way, could be this:

Base64.strict_encode64(Base64.decode64(value)) == value

Making an exception list is not a good idea, because it will be hard to maintain.

The Base64 method will return printable ascii characters, so we can use this to determine if the value is encoded or not.

The solution in this case is encoding the value, and then decoding it. If the value is already encoded it will not be eq to the original value. If the value is not encoded, it will be eq to the original value.

Parameters:

  • value (String)

    input to check for encoding status.

  • input_type (Symbol)

    input type.

Returns:

  • (Boolean)

    true if value is base64 encoded, false otherwise.



46
47
48
49
50
51
52
53
54
55
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
# File 'lib/contrast/agent/protect/rule/input_classification/encoding.rb', line 46

def cs__base64? value, input_type
  return false unless value.is_a?(String)
  return false if Contrast::Utils::DuckUtils.empty_duck?(value)

  # Encoded string levels of decoding example:
  #
  # Value encoded 'pass' => 'cGFzcw=='
  # decode level 0 => 'pass'
  # decode level 1 => '\xA5\xAB,'
  # decode level 2 => ''
  check_value = value.dup
  return false if KNOWN_DECODING_EXCEPTIONS.include?(check_value)

  level = 0
  iteration = 0
  until Contrast::Utils::DuckUtils.empty_duck?(Base64.decode64(check_value))
    iteration += 1
    # handle cases like 'command' or 'injection' which will check out as encoded regarding the level of
    # decoding, but will produce ascii escape characters on first iteration, rather than decoded value.
    level += 1 unless iteration == 2 && ::CGI.escape(check_value) != check_value

    check_value = Base64.decode64(check_value)
  end

  # if we have more than 2 levels the value is encoded.
  base64 = level > 1

  # Call base64 statistics:
  if base64
    Contrast::Agent::Protect::InputAnalyzer.base64_statistic.match!(input_type)
  else
    Contrast::Agent::Protect::InputAnalyzer.base64_statistic.mismatch!(input_type)
  end

  base64
rescue StandardError => e
  logger.error('Error while checking for base64 encoding',
               error: e,
               message: e.message,
               backtrace: e.backtrace)
  false
end

#cs__decode64(value, input_type) ⇒ String

This method will decode the value using Base64.decode64, only if value was encoded. If value is not encoded, it will return the original value.

Parameters:

  • value (String)

    input to decode.

  • input_type (Symbol)

    input type.

Returns:

  • (String)

    decoded or original value.

Raises:

  • (StandardError)


96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/contrast/agent/protect/rule/input_classification/encoding.rb', line 96

def cs__decode64 value, input_type
  return value unless cs__base64?(value, input_type)

  new_value = try_base64_decode(value)
  new_value, success = normalize_encoding(new_value)
  return new_value if success

  value
rescue StandardError => e
  logger.error('Error while decoding base64', error: e, message: e.message, backtrace: e.backtrace)
  value
end