Sinatra and Rack Protection setting
Asked Answered
B

5

17

I am using Sinatra and CORS to accept a file upload on domain A (hefty.burger.com). Domain B (fizzbuzz.com) has a form that uploads a file to a route on A.

I have an options route and a post route, both named '/uploader'.

options '/uploader' do
  headers 'Access-Control-Allow-Origin' => 'http://fizz.buzz.com',
  'Access-Control-Allow-Methods' => 'POST'
  200
end 

post '/uploader' do
  ... 
  content_type :json
  [{:mary => 'little lamb'}].to_json
end

The options gets hit first... and it works.. then the post gets hit and returns a 403.

If I disable protection, the post works... what kind of protection do I need to exclude from a list to maintain protection but allow these posts through?

I have only recently been burned by the new Rack protection kicking in on Heroku and causing me some grief... anyone have a good pointer for what to do here? The reason I say that, is all of a sudden I am seeing log entries with alerts to session hijacking issues (almost certainly due to nothing more than running > 1 Dyno for the App). I see rack-protection (1.2.0) in my Gemfile.lock even though I never asked for it... something in my manifest is calling for it, so it is loaded, but nothing in my Sinatra App even tries to require it or set it up.

Benedikt answered 9/5, 2012 at 4:18 Comment(2)
If you have logging enabled it should show which protection was responsible, something like attack prevented by RemoteToken (with the logger preamble before it).Urbanize
I am seeing this on Rack Protection 1.5.3. It did not happen with Rack Protection eb7e4c9a176d.Granado
M
20

Using this in your Sinatra app should solve your problem:

set :protection, :except => [:json_csrf]

A better solution may be to upgrade Sinatra to 1.4, which uses Rack::Protection 1.5 and should not cause the problem you are seeing.

The problem is that your version of RackProtection::JsonCsrf in is incompatible with CORS when you respond with Content-Type: application/json. Here is a snippet from the old json_csrf.rb in rack-protection:

def call(env)
  status, headers, body = app.call(env)
  if headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/
    if referrer(env) != Request.new(env).host
      result = react(env)
      warn env, "attack prevented by #{self.class}"
    end
  end
  result or [status, headers, body]
end

You can see this rejects requests that have an application/json response when the referrer is not from the same host as the server.

This problem was solved in a later version of rack-protection, which now considers whether the request is an XMLHttpRequest:

   def has_vector?(request, headers)
    return false if request.xhr?
    return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/
    origin(request.env).nil? and referrer(request.env) != request.host
  end

If you are using Sinatra 1.3.2 and cannot upgrade the solution is to disable this particular protection. With CORS you are explicitly enabling cross-domain XHR requests. Sinatra lets you disable protection entirely, or disable specific components of Rack::Protection (see "Configuring Attack Protection" in the Sinatra docs).

Rack::Protection provides 12 middleware components that help defeat common attacks:

  • Rack::Protection::AuthenticityToken
  • Rack::Protection::EscapedParams
  • Rack::Protection::FormToken
  • Rack::Protection::FrameOptions
  • Rack::Protection::HttpOrigin
  • Rack::Protection::IPSpoofing
  • Rack::Protection::JsonCsrf
  • Rack::Protection::PathTraversal
  • Rack::Protection::RemoteReferrer
  • Rack::Protection::RemoteToken
  • Rack::Protection::SessionHijacking
  • Rack::Protection::XssHeader

At time of writing, all but four of these are loaded automatically when you use the Rack::Protection middleware (Rack::Protection::AuthenticityToken, Rack::Protection::FormToken, Rack::Protection::RemoteReferrer, and Rack::Protection::EscapedParams must be added explicitly).

Sinatra uses Rack::Protection's default settings with one exception: it only adds SessionHijacking and RemoteToken if you enable sessions.

And, finally, if you are trying to use CORS with Sinatra, you might try rack-cors, which takes care of a lot of the details for you.

Melancholy answered 20/4, 2013 at 21:20 Comment(1)
But in which file do you put that line? Confusing.Carmarthenshire
S
5

If you see this issue, you are not using CORS (Cross-origin resource sharing), and are behind a reverse-proxy (such as nginx or apache), make sure that your reverse-proxy isn't stripping out host header and replacing it with localhost.

For example, in nginx you need to use proxy_set_header:

location / {
    proxy_pass http://localhost:9296;
    proxy_set_header Host $host;
}

When the header is stripped out from a request, Rack::Protection believes it to be a CSRF attack.

Socinus answered 26/2, 2016 at 3:57 Comment(1)
Your post was like a glove to my architecture, but adding proxy_set_header Host $host; to my nginx config, didn't solve it.Michelson
C
3

Let me guess, you're testing with the Chrome app 'Dev HTTP Client' ? Try this instead:

curl -v -X POST http://fizz.buzz.com/uploader

From the rack protection module: "Supported browsers:: Google Chrome 2, Safari 4 and later"

This should work:

class App < Sinatra::Base
  ...
  enable :protection
  use Rack::Protection, except: :http_origin
  use Rack::Protection::HttpOrigin, origin_whitelist: ["chrome-extension://aejoelaoggembcahagimdiliamlcdmfm", "http://fizz.buzz.com"]

  post '/uploader' do
    headers \
      'Allow'   => 'POST',
      'Access-Control-Allow-Origin' => 'http://fizz.buzz.com'
    body "it work's !"
  end

You probably wonder about chrome-extension://aejoelaoggembcahagimdiliamlcdmfm ? Well, that's what the rack protection gets as env['HTTP_ORIGIN'] when you send a POST request with the Chrome app.

Cootie answered 26/1, 2013 at 0:4 Comment(1)
In a new Sinatra app I had to change use Rack::Protection::HttpOrigin, origin_whitelist: ["chrome-extension://aejoelaoggembcahagimdiliamlcdmfm", "http://fizz.buzz.com"] TO set :protection, origin_whitelist: ['chrome-extension://aejoelaoggembcahagimdiliamlcdmfm']Cootie
C
1

Is this because you are not returning the allowed methods back in your options route?

A question here refers to it which notes the allowed methods back.

An extension here and middleware here might help you out.

Client answered 10/5, 2012 at 2:11 Comment(2)
No, I am setting the correct options. I explicitly allow POST and yet POST returns 403 forbidden. I am not sure which part of rack protection is controlling that. The docs are not at all clear and do not seem to contain anything concerning CORS.Benedikt
David, I must have been in space last night. Can you try setting, use Rack::Protection, :except => :json_csrf? Someone has an issue on rack::protection's git repo about Rack::Protection and CORS as well.Client
D
1

rack-protection allows to specify a custom check starting from 2.0.0:

set :protection, :allow_if => lambda{ |env| env['HTTP_REFERER'] && URI(env['HTTP_REFERER']).host == 'fizz.buzz.com' }

https://github.com/sinatra/sinatra/blob/a2fe3e698b19ac4065f166f1727afd31d0e72f95/rack-protection/lib/rack/protection/json_csrf.rb#L39

Derril answered 25/9, 2017 at 16:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.