# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'digest' require 'contrast/utils/hash_digest_extend' 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 extend Contrast::Utils::HashDigestExtend CONTENT_LENGTH_HEADER = 'Content-Length' CHARS = %w[a b c d e f g].cs__freeze 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' def initialize super @crc32 = 0 end # Update to CRC checksum the finding route and verb if finding route # [Contrast::Api::Dtm::RouteCoverage] is available else update the passed # request or Contrast::REQUEST_TRACKER.current.request uri and used request # method. # # @param finding [Contrast::Api::Dtm::Finding, Contrast::Agent::Reporting::Finding] finding to be reported # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request. # @return checksum [Integer, nil] returns nil if there is no request context or tracking # is disabled. def update_on_request finding, request context = Contrast::Agent::REQUEST_TRACKER.current return unless context || ::Contrast::ASSESS.non_request_tracking? if (route = finding.routes[0]) if finding.cs__is_a?(Contrast::Agent::Reporting::Finding) && (observation = route.observations[0]) update(observation.url) update(observation.verb) else update(route.route) # the normalized URL used to access the method in the route. update(route.verb) # the HTTP Verb used to access the method in the route. end return end return unless request ||= context&.request update(request.normalized_uri) # the normalized URL used to access the method in the route. update(request.request_method) # The HTTP method used in the request end # Update to CRC checksum the event source name and source type. # Expects Contrast::Api::Dtm::TraceEvent || Contrast::Agent::Assess::Events::SourceEvent # # @param events [Protobuf::Field::FieldArray, # ] # Array of TraceEvents # @return checksum [Integer, nil] returns nil if there is no events def update_on_sources events return unless events&.any? events.each do |event| if event.cs__is_a?(Contrast::Api::Dtm::TraceEvent) event.event_sources&.each do |source| update(source.type) update(source.name) # rubocop:disable Security/Module/Name end elsif event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent) update(event.source_type) update(event.source_name) end end end # This method converts and integer value for length into a string value # that we can hash on, based on the logarithmic value of the length, and # updates the current hash with that value. # @param chr [Numeric] the length to translate def update_on_content_length chr update(CHARS[Math.log10(chr.to_s.length).to_i] || CHARS[-1]) end # Converts given string to CRC checksum. CRC32 checksum ensures that If error # of a single bit occurs, the CRC checksum will fail, regardless of any other # property of the transmitted data, including its length. Called several times # with previous CRC to recalculate the new output. # # @param str [String] # @return @crc32 [Integer, nil] updated value of crc 32 bit integer checksum or # nil if passed string is nil or empty def update str return unless str @crc32 = Zlib.crc32(str, @crc32) end # Casts current CRC checksum to String # # @return @crc32 [String] String representation of CRC32 checksum def finish @crc32.to_s end end end end