require 'brakeman/checks/base_check' #Checks for session key length and http_only settings class Brakeman::CheckSessionSettings < Brakeman::BaseCheck Brakeman::Checks.add self @description = "Checks for session key length and http_only settings" def initialize *args super unless tracker.options[:rails3] @session_settings = Sexp.new(:colon2, Sexp.new(:const, :ActionController), :Base) else @session_settings = nil end end def run_check settings = tracker.config.session_settings check_for_issues settings, @app_tree.file_path("config/environment.rb") session_store = @app_tree.file_path("config/initializers/session_store.rb") secret_token = @app_tree.file_path("config/initializers/secret_token.rb") [session_store, secret_token].each do |file| if tracker.initializers[file] and not ignored? file.basename process tracker.initializers[file] end end if tracker.options[:rails4] check_secrets_yaml end end #Looks for ActionController::Base.session = { ... } #in Rails 2.x apps # #and App::Application.config.secret_token = #in Rails 3.x apps # #and App::Application.config.secret_key_base = #in Rails 4.x apps def process_attrasgn exp if not tracker.options[:rails3] and exp.target == @session_settings and exp.method == :session= check_for_issues exp.first_arg, @app_tree.file_path("config/initializers/session_store.rb") end if tracker.options[:rails3] and settings_target?(exp.target) and (exp.method == :secret_token= or exp.method == :secret_key_base=) and string? exp.first_arg warn_about_secret_token exp.line, @app_tree.file_path("config/initializers/secret_token.rb") end exp end #Looks for Rails3::Application.config.session_store :cookie_store, { ... } #in Rails 3.x apps def process_call exp if tracker.options[:rails3] and settings_target?(exp.target) and exp.method == :session_store check_for_rails3_issues exp.second_arg, @app_tree.file_path("config/initializers/session_store.rb") end exp end private def settings_target? exp call? exp and exp.method == :config and node_type? exp.target, :colon2 and exp.target.rhs == :Application end def check_for_issues settings, file if settings and hash? settings if value = (hash_access(settings, :session_http_only) || hash_access(settings, :http_only) || hash_access(settings, :httponly)) if false? value warn_about_http_only value.line, file end end if value = hash_access(settings, :secret) if string? value warn_about_secret_token value.line, file end end end end def check_for_rails3_issues settings, file if settings and hash? settings if value = hash_access(settings, :httponly) if false? value warn_about_http_only value.line, file end end if value = hash_access(settings, :secure) if false? value warn_about_secure_only value.line, file end end end end def check_secrets_yaml secrets_file = @app_tree.file_path("config/secrets.yml") if secrets_file.exists? and not ignored? "secrets.yml" and not ignored? "config/*.yml" yaml = secrets_file.read require 'yaml' begin secrets = YAML.safe_load yaml rescue Psych::SyntaxError, RuntimeError => e Brakeman.notify "[Notice] #{self.class}: Unable to parse `#{secrets_file}`" Brakeman.debug "Failed to parse #{secrets_file}: #{e.inspect}" return end if secrets && secrets["production"] and secret = secrets["production"]["secret_key_base"] unless secret.include? "<%=" line = yaml.lines.find_index { |l| l.include? secret } + 1 warn_about_secret_token line, @app_tree.file_path(secrets_file) end end end end def warn_about_http_only line, file warn :warning_type => "Session Setting", :warning_code => :http_cookies, :message => "Session cookies should be set to HTTP only", :confidence => :high, :line => line, :file => file, :cwe_id => [1004] end def warn_about_secret_token line, file warn :warning_type => "Session Setting", :warning_code => :session_secret, :message => "Session secret should not be included in version control", :confidence => :high, :line => line, :file => file, :cwe_id => [798] end def warn_about_secure_only line, file warn :warning_type => "Session Setting", :warning_code => :secure_cookies, :message => "Session cookie should be set to secure only", :confidence => :high, :line => line, :file => file, :cwe_id => [614] end def ignored? file [".", "config", "config/initializers"].each do |dir| ignore_file = @app_tree.file_path("#{dir}/.gitignore") if @app_tree.exists? ignore_file input = ignore_file.read return true if input.include? file end end false end end