Created
November 3, 2015 14:50
-
-
Save mclosson/d5ddcea64aa0a264dc9e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Rack HoneyToken Middleware | |
# | |
# Honey Tokens are unique and unlikely values that should be planted in various | |
# places within your web application to assist in the detection of a security | |
# breach. They are useful at trying to detect SQL injection attacks where the | |
# intended logic of an SQL query is bypassed and the HTTP request is used to | |
# attempt to download private data instead for example the users or accounts | |
# table and associated password hashes. | |
# | |
# Below is an example of creating three fake users with their password hashes | |
# set to a honey token for detection by the in the event of a successful SQL | |
# injection attack. | |
# | |
# honey_tokens = 3.times.map do | |
# token = SecureRandom.hex | |
# user = User.create(..., password: token) | |
# token | |
# end | |
# | |
# Configuring a Rails 4.x application | |
# config/application.rb | |
# | |
# config.middleware.use( | |
# "HoneyToken", | |
# tokens: honey_tokens, | |
# logger: Proc.new { |token| Rails.logger.warn("Honey token exposed: #{token}") } | |
# ) | |
# | |
# Or to use a customize the status, headers and response by configuration you can | |
# provide a custom_strategy option which must return a [status, headers, response] | |
# array. | |
# | |
# config.middleware.use( | |
# 'HoneyToken', | |
# tokens: honey_tokens | |
# custom_strategy: Proc.new { |tuple, exposed_tokens| | |
# exposed_tokens.each { |token| Rails.logger.warn("Honey Token Exposed: #{token}") } | |
# status, headers, response = tuple | |
# response = ['My custom response'] | |
# [status, headers, response] | |
# } | |
# ) | |
# | |
# NOTE: Skillful attackers (or attackers with sufficiently advanced tools) may | |
# be able to encode or transform the honey tokens in the database query before | |
# pulling it back into the HTTP response effectively bypassing the protection | |
# however in practice many attackers tend to start with more basic attacks and | |
# may make enough noise in the logs to allow you to detect and fix the vulnerable | |
# code before they are able to craft a more stealthy request. | |
# | |
# Your reaction to an exposed HoneyToken in an HTTP response should be to | |
# alter or redirect the request to prevent private data from being downloaded | |
# and to fix or shutoff the vulnerable code path immediately as it means a | |
# successful attack is under way. | |
class HoneyToken | |
def initialize(app, options = {}) | |
self.app = app | |
self.custom_strategy = options.fetch(:custom_strategy, nil) | |
self.logger = options.fetch(:logger, nil) | |
self.tokens = options.fetch(:tokens, []) | |
end | |
def call(env) | |
status, headers, response = app.call(env) | |
exposed_tokens = tokens_present_in?(response) | |
if exposed_tokens.any? | |
if custom_strategy | |
status, headers, response = custom_strategy.call( | |
[status, headers, response], | |
exposed_tokens | |
) | |
else | |
response = ['Good try ;)'] | |
exposed_tokens.each { |token| log(token) } | |
end | |
end | |
[status, headers, response] | |
end | |
private | |
attr_accessor :app, :custom_strategy, :logger, :tokens | |
def tokens_present_in?(response) | |
if response.respond_to?(:body) | |
pattern = tokens.map { |token| Regexp.escape(token) }.join('|') | |
regex = Regexp.new(pattern) | |
response.body.scan(regex) | |
else | |
[] | |
end | |
end | |
def log(token) | |
logger.call(token) if logger | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment