2022-07-31 08:56:44 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2011-05-23 04:07:54 -04:00
|
|
|
require 'rack/protection'
|
2015-05-25 13:37:29 -04:00
|
|
|
require 'rack/utils'
|
2011-05-29 07:01:47 -04:00
|
|
|
require 'digest'
|
2011-05-25 06:27:18 -04:00
|
|
|
require 'logger'
|
2011-05-29 05:45:27 -04:00
|
|
|
require 'uri'
|
2011-05-23 04:07:54 -04:00
|
|
|
|
|
|
|
module Rack
|
|
|
|
module Protection
|
|
|
|
class Base
|
2011-05-25 06:27:18 -04:00
|
|
|
DEFAULT_OPTIONS = {
|
2022-07-31 08:56:44 -04:00
|
|
|
reaction: :default_reaction, logging: true,
|
|
|
|
message: 'Forbidden', encryptor: Digest::SHA1,
|
|
|
|
session_key: 'rack.session', status: 403,
|
|
|
|
allow_empty_referrer: true,
|
|
|
|
report_key: 'protection.failed',
|
|
|
|
html_types: %w[text/html application/xhtml text/xml application/xml]
|
2011-05-25 06:27:18 -04:00
|
|
|
}
|
|
|
|
|
2011-05-23 04:07:54 -04:00
|
|
|
attr_reader :app, :options
|
|
|
|
|
|
|
|
def self.default_options(options)
|
|
|
|
define_method(:default_options) { super().merge(options) }
|
|
|
|
end
|
|
|
|
|
2011-05-25 06:27:18 -04:00
|
|
|
def self.default_reaction(reaction)
|
|
|
|
alias_method(:default_reaction, reaction)
|
|
|
|
end
|
|
|
|
|
2011-05-23 04:07:54 -04:00
|
|
|
def default_options
|
|
|
|
DEFAULT_OPTIONS
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(app, options = {})
|
2022-07-31 08:56:44 -04:00
|
|
|
@app = app
|
|
|
|
@options = default_options.merge(options)
|
2011-05-23 04:07:54 -04:00
|
|
|
end
|
|
|
|
|
2011-05-25 06:27:18 -04:00
|
|
|
def safe?(env)
|
|
|
|
%w[GET HEAD OPTIONS TRACE].include? env['REQUEST_METHOD']
|
|
|
|
end
|
|
|
|
|
2011-05-23 04:07:54 -04:00
|
|
|
def accepts?(env)
|
|
|
|
raise NotImplementedError, "#{self.class} implementation pending"
|
|
|
|
end
|
|
|
|
|
|
|
|
def call(env)
|
|
|
|
unless accepts? env
|
2013-08-21 14:50:51 -04:00
|
|
|
instrument env
|
2011-06-19 09:26:39 -04:00
|
|
|
result = react env
|
2011-05-23 04:07:54 -04:00
|
|
|
end
|
2011-06-19 09:26:39 -04:00
|
|
|
result or app.call(env)
|
|
|
|
end
|
|
|
|
|
|
|
|
def react(env)
|
|
|
|
result = send(options[:reaction], env)
|
2022-07-31 08:56:44 -04:00
|
|
|
result if (Array === result) && (result.size == 3)
|
2011-05-23 04:07:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def warn(env, message)
|
|
|
|
return unless options[:logging]
|
2022-07-31 08:56:44 -04:00
|
|
|
|
2011-05-23 04:07:54 -04:00
|
|
|
l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors'])
|
|
|
|
l.warn(message)
|
|
|
|
end
|
|
|
|
|
2013-08-21 14:50:51 -04:00
|
|
|
def instrument(env)
|
2022-07-31 08:56:44 -04:00
|
|
|
return unless (i = options[:instrumenter])
|
|
|
|
|
2013-08-21 14:50:51 -04:00
|
|
|
env['rack.protection.attack'] = self.class.name.split('::').last.downcase
|
|
|
|
i.instrument('rack.protection', env)
|
|
|
|
end
|
|
|
|
|
2011-05-25 06:27:18 -04:00
|
|
|
def deny(env)
|
2014-03-13 03:55:59 -04:00
|
|
|
warn env, "attack prevented by #{self.class}"
|
2022-07-31 08:56:44 -04:00
|
|
|
[options[:status], { 'Content-Type' => 'text/plain' }, [options[:message]]]
|
2011-05-25 06:27:18 -04:00
|
|
|
end
|
|
|
|
|
2013-03-10 14:09:27 -04:00
|
|
|
def report(env)
|
2014-03-13 03:55:59 -04:00
|
|
|
warn env, "attack reported by #{self.class}"
|
2013-03-10 14:09:27 -04:00
|
|
|
env[options[:report_key]] = true
|
|
|
|
end
|
|
|
|
|
2011-05-29 07:01:47 -04:00
|
|
|
def session?(env)
|
|
|
|
env.include? options[:session_key]
|
|
|
|
end
|
|
|
|
|
2011-05-28 11:51:54 -04:00
|
|
|
def session(env)
|
2011-05-29 07:01:47 -04:00
|
|
|
return env[options[:session_key]] if session? env
|
2022-07-31 08:56:44 -04:00
|
|
|
|
|
|
|
raise "you need to set up a session middleware *before* #{self.class}"
|
2011-05-29 07:01:47 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def drop_session(env)
|
|
|
|
session(env).clear if session? env
|
2011-05-28 11:51:54 -04:00
|
|
|
end
|
|
|
|
|
2011-05-29 05:45:27 -04:00
|
|
|
def referrer(env)
|
2011-06-20 07:08:39 -04:00
|
|
|
ref = env['HTTP_REFERER'].to_s
|
2022-07-31 08:56:44 -04:00
|
|
|
return if !options[:allow_empty_referrer] && ref.empty?
|
|
|
|
|
2011-06-20 07:08:39 -04:00
|
|
|
URI.parse(ref).host || Request.new(env).host
|
2014-01-23 17:15:44 -05:00
|
|
|
rescue URI::InvalidURIError
|
2011-05-29 05:45:27 -04:00
|
|
|
end
|
|
|
|
|
2012-09-05 04:08:09 -04:00
|
|
|
def origin(env)
|
|
|
|
env['HTTP_ORIGIN'] || env['HTTP_X_ORIGIN']
|
|
|
|
end
|
|
|
|
|
2011-05-28 11:51:54 -04:00
|
|
|
def random_string(secure = defined? SecureRandom)
|
2022-07-31 08:56:44 -04:00
|
|
|
secure ? SecureRandom.hex(16) : '%032x' % rand((2**128) - 1)
|
2011-11-08 09:44:32 -05:00
|
|
|
rescue NotImplementedError
|
2011-05-28 11:51:54 -04:00
|
|
|
random_string false
|
|
|
|
end
|
|
|
|
|
2011-05-29 07:01:47 -04:00
|
|
|
def encrypt(value)
|
|
|
|
options[:encryptor].hexdigest value.to_s
|
|
|
|
end
|
2011-06-20 03:16:15 -04:00
|
|
|
|
2015-05-25 13:37:29 -04:00
|
|
|
def secure_compare(a, b)
|
|
|
|
Rack::Utils.secure_compare(a.to_s, b.to_s)
|
|
|
|
end
|
|
|
|
|
2011-06-20 03:16:15 -04:00
|
|
|
alias default_reaction deny
|
2012-12-10 10:42:48 -05:00
|
|
|
|
|
|
|
def html?(headers)
|
2022-07-31 08:56:44 -04:00
|
|
|
return false unless (header = headers.detect { |k, _v| k.downcase == 'content-type' })
|
|
|
|
|
|
|
|
options[:html_types].include? header.last[%r{^\w+/\w+}]
|
2012-12-10 10:42:48 -05:00
|
|
|
end
|
2011-05-23 04:07:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|