62 lines
1.5 KiB
Ruby
62 lines
1.5 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
module Gitlab
|
||
|
module Middleware
|
||
|
# There is no valid reason for a request to contain a null byte (U+0000)
|
||
|
# so just return HTTP 400 (Bad Request) if we receive one
|
||
|
class HandleNullBytes
|
||
|
NULL_BYTE_REGEX = Regexp.new(Regexp.escape("\u0000")).freeze
|
||
|
|
||
|
attr_reader :app
|
||
|
|
||
|
def initialize(app)
|
||
|
@app = app
|
||
|
end
|
||
|
|
||
|
def call(env)
|
||
|
return [400, {}, ["Bad Request"]] if request_has_null_byte?(env)
|
||
|
|
||
|
app.call(env)
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def request_has_null_byte?(request)
|
||
|
return false if ENV['REJECT_NULL_BYTES'] == "1"
|
||
|
|
||
|
request = Rack::Request.new(request)
|
||
|
|
||
|
request.params.values.any? do |value|
|
||
|
param_has_null_byte?(value)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def param_has_null_byte?(value, depth = 0)
|
||
|
# Guard against possible attack sending large amounts of nested params
|
||
|
# Should be safe as deeply nested params are highly uncommon.
|
||
|
return false if depth > 2
|
||
|
|
||
|
depth += 1
|
||
|
|
||
|
if value.respond_to?(:match)
|
||
|
string_contains_null_byte?(value)
|
||
|
elsif value.respond_to?(:values)
|
||
|
value.values.any? do |hash_value|
|
||
|
param_has_null_byte?(hash_value, depth)
|
||
|
end
|
||
|
elsif value.is_a?(Array)
|
||
|
value.any? do |array_value|
|
||
|
param_has_null_byte?(array_value, depth)
|
||
|
end
|
||
|
else
|
||
|
false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def string_contains_null_byte?(string)
|
||
|
string.match?(NULL_BYTE_REGEX)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|