Skip to content

Instantly share code, notes, and snippets.

@mclosson
Created November 3, 2015 14:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mclosson/d5ddcea64aa0a264dc9e to your computer and use it in GitHub Desktop.
Save mclosson/d5ddcea64aa0a264dc9e to your computer and use it in GitHub Desktop.
# 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