# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'digest' module Contrast module Utils # We use this class to provide hashes for our Request and Finding objects # based upon our definitions of uniqueness. # While the uniqueness of the request object is something internal to the # Ruby agent, the uniqueness of the Finding hash is defined by a # specification shared across all agent teams. The spec can be found here: # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/vulnerability/preflight.md class HashDigest < Digest::Class include Digest::Instance CONTENT_LENGTH_HEADER = 'Content-Length' CRYPTO_RULES = %w[ crypto-bad-ciphers crypto-bad-mac ].cs__freeze CONFIG_PATH_KEY = 'path' CONFIG_SESSION_ID_KEY = 'sessionId' CLASS_SOURCE_KEY = 'source' CLASS_CONSTANT_NAME_KEY = 'name' CLASS_LINE_NO_KEY = 'lineNo' class << self def generate_request_hash request hash = new hash.update(request.request_method) hash.update(request.normalized_uri) request.parameters.each_key do |name| hash.update(name) end cl = request.normalized_request_headers[CONTENT_LENGTH_HEADER] hash.update(request.normalized_length_header(cl)) if cl hash.finish end def generate_event_hash finding, source return generate_dataflow_hash(finding) if finding.events.length.to_i > 1 id = finding.rule_id return generate_crypto_hash(finding, source) if CRYPTO_RULES.include?(id) generate_trigger_hash(finding) end def generate_config_hash finding hash = new hash.update(finding.rule_id) path = finding.properties[CONFIG_PATH_KEY] hash.update(path) method = finding.properties[CONFIG_SESSION_ID_KEY] hash.update(method) hash.finish end def generate_class_scanning_hash finding hash = new hash.update(finding.rule_id) module_name = finding.properties[CLASS_SOURCE_KEY] hash.update(module_name) # We're not currently collecting this. 30/7/19 HM line_no = finding.properties[CLASS_LINE_NO_KEY] hash.update(line_no) field = finding.properties[CLASS_CONSTANT_NAME_KEY] hash.update(field) hash.finish end def generate_crypto_hash finding, algorithm hash = new hash.update(finding.rule_id) hash.update(algorithm) hash.update_on_request hash.finish end def generate_dataflow_hash finding hash = new hash.update(finding.rule_id) hash.update_on_sources(finding.events) hash.update_on_request hash.finish end def generate_response_hash finding hash = new hash.update(finding.rule_id) hash.update_on_request hash.finish end def generate_trigger_hash finding hash = new hash.update(finding.rule_id) hash.update_on_request hash.finish end end def update_on_request context = Contrast::Agent::REQUEST_TRACKER.current return unless context route = context.route request = context.request if route update(route.route) update(route.verb) elsif request update(request.normalized_uri) update(request.request_method) end end def update_on_sources events return unless events&.any? events.each do |event| event.event_sources.each do |source| update(source.type) update(source.name) end end end def initialize @crc32 = 0 end def update str return unless str @crc32 = Zlib.crc32(str, @crc32) end def reset @crc32 = 0 end def finish @crc32.to_s end end end end