Skip to content

Commit

Permalink
Merge pull request #52 from rmm5t/strip_set_cookie
Browse files Browse the repository at this point in the history
Rack::Cache caches Set-Cookie response headers yielding potential security holes in apps
  • Loading branch information
rtomayko committed Feb 15, 2012
2 parents 8ecd5b4 + 6379364 commit 2e3a64d
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 0 deletions.
7 changes: 7 additions & 0 deletions lib/rack/cache/context.rb
Expand Up @@ -260,6 +260,7 @@ def fetch

# Write the response to the cache.
def store(response)
strip_ignore_headers(response)
metastore.store(@request, response, entitystore)
response.headers['Age'] = response.age.to_s
rescue Exception => e
Expand All @@ -269,6 +270,12 @@ def store(response)
record :store
end

# Remove all ignored response headers before writing to the cache.
def strip_ignore_headers(response)
stripped_values = ignore_headers.map { |name| response.headers.delete(name) }
record :ignore if stripped_values.any?
end

def log_error(exception)
@env['rack.errors'].write("cache error: #{exception.message}\n#{exception.backtrace.join("\n")}\n")
end
Expand Down
9 changes: 9 additions & 0 deletions lib/rack/cache/options.rb
Expand Up @@ -78,6 +78,14 @@ def option_name(key)
# Default: 0
option_accessor :default_ttl

# Set of response headers that are removed before storing them in the
# cache. These headers are only removed for cacheable responses. For
# example, in most cases, it makes sense to prevent cookies from being
# stored in the cache.
#
# Default: ['Set-Cookie']
option_accessor :ignore_headers

# Set of request headers that trigger "private" cache-control behavior
# on responses that don't explicitly state whether the response is
# public or private via a Cache-Control directive. Applications that use
Expand Down Expand Up @@ -138,6 +146,7 @@ def initialize_options(options={})
'rack-cache.metastore' => 'heap:/',
'rack-cache.entitystore' => 'heap:/',
'rack-cache.default_ttl' => 0,
'rack-cache.ignore_headers' => ['Set-Cookie'],
'rack-cache.private_headers' => ['Authorization', 'Cookie'],
'rack-cache.allow_reload' => false,
'rack-cache.allow_revalidate' => false,
Expand Down
35 changes: 35 additions & 0 deletions test/context_test.rb
Expand Up @@ -57,6 +57,7 @@
response.should.be.ok
cache.trace.should.include :miss
cache.trace.should.include :store
cache.trace.should.not.include :ignore
response.headers.should.include 'Age'
response.headers['Cache-Control'].should.equal 'public'
end
Expand Down Expand Up @@ -85,6 +86,40 @@
response.headers['Cache-Control'].should.equal 'private'
end

it 'does remove Set-Cookie response header from a cacheable response' do
respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"', 'Set-Cookie' => 'TestCookie=OK'
get '/'

app.should.be.called
response.should.be.ok
cache.trace.should.include :store
cache.trace.should.include :ignore
response.headers['Set-Cookie'].should.be.nil
end

it 'does remove all configured ignore_headers from a cacheable response' do
respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"', 'SET-COOKIE' => 'TestCookie=OK', 'X-Strip-Me' => 'Secret'
get '/', 'rack-cache.ignore_headers' => ['set-cookie', 'x-strip-me']

app.should.be.called
response.should.be.ok
cache.trace.should.include :store
cache.trace.should.include :ignore
response.headers['Set-Cookie'].should.be.nil
response.headers['x-strip-me'].should.be.nil
end

it 'does not remove Set-Cookie response header from a private response' do
respond_with 200, 'Cache-Control' => 'private', 'Set-Cookie' => 'TestCookie=OK'
get '/'

app.should.be.called
response.should.be.ok
cache.trace.should.not.include :store
cache.trace.should.not.include :ignore
response.headers['Set-Cookie'].should.equal 'TestCookie=OK'
end

it 'responds with 304 when If-Modified-Since matches Last-Modified' do
timestamp = Time.now.httpdate
respond_with do |req,res|
Expand Down

0 comments on commit 2e3a64d

Please sign in to comment.