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 a
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.
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
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.
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.
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.