# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'contrast/utils/object_share' cs__scoped_require 'contrast/utils/random_util' # This class is used by the CSRF rule to inject the Contrast CSRF token into # the Response's body, allowing for the detection of CSRF attacks. class Contrast::Agent::Protect::Rule::Csrf::CsrfTokenInjector # rubocop:disable Style/ClassAndModuleChildren TKN_NAME = 'cs_csrf_tkn' def token_name TKN_NAME end SESSION_TOKEN_PREFIX = '__CONTRAST__' def token_key @_token_key ||= SESSION_TOKEN_PREFIX + token_name end def javascript @_javascript ||= build_javascript end SCRIPT_LOC = File.join('csrf', 'inject.js').cs__freeze NAME_MARKER = '!TOKEN_NAME!' VALUE_MARKER = '!TOKEN_VALUE!' def build_javascript script = Contrast::Utils::ResourceLoader.load(SCRIPT_LOC) script&.sub!(NAME_MARKER, TKN_NAME) script end CSRF_TOKEN_LENGTH = 8 def get_expected_token context cookies = context.request.request_cookies token = cookies[token_key] return token if token return unless context.response build_token(context) end def build_token context token = Contrast::Utils::RandomUtil.secure_random_string(CSRF_TOKEN_LENGTH) context&.response&.set_header(token_key, token) token end CONTENT_TYPE = 'CONTENT-TYPE' def wedge_token? response return true unless response content_type = response.header(CONTENT_TYPE) return true unless content_type content_type = content_type.to_s !content_type.start_with?(*DATA_CONTENT_TYPES) end END_BODY_TAG = %r{</body>}i.cs__freeze def do_injection context return unless wedge_token?(context&.response) body_string = context&.response&.body return unless body_string index = body_string.index(END_BODY_TAG) return unless index injection = get_expected_token(context) return unless injection injection = javascript.sub(VALUE_MARKER, injection) body_string.insert(index, injection) context&.response&.update_body(body_string) end DATA_CONTENT_TYPES = [ 'image/', 'audio/', 'video/', 'application/json', 'application/xml', 'application/octet', 'application/force', 'text/json', 'text/xml' ].cs__freeze end