-
-
Save abrom/effe58b27a4f4ac1b97fb85593d7c3be to your computer and use it in GitHub Desktop.
require 'omniauth/strategies/<your strategy>' | |
require_relative 'omniauth_authenticity_checker' | |
OmniAuth.config.allowed_request_methods = [:post] | |
OmniAuth.config.before_request_phase = OmniauthAuthenticityChecker.new(reaction: :drop_session) | |
# And if you haven't already, you should be loading in the `Rack::Protection` middleware: | |
use Rack::Protection, reaction: :drop_session, use: :authenticity_token | |
# You will also need to make sure your users are POSTing to your authorise route, and not simply | |
# linking/redirecting to it. This change would make GETing the authorise route 404 so you may wish to | |
# handle this by rendering a nicer error message, and/or rendering a form with a button to POST to your | |
# authorise route. |
require 'rack-protection' | |
class OmniauthAuthenticityChecker < Rack::Protection::AuthenticityToken | |
def initialize(options = {}) | |
@options = default_options.merge(options) | |
end | |
def call(env) | |
unless accepts? env | |
instrument env | |
react env | |
end | |
end | |
def deny(env) | |
warn env, "attack prevented by #{self.class}" | |
raise Net::HTTPForbidden, options[:message] | |
end | |
end |
It'd depend on what reaction you've specified. Because this overloaded version of the AuthenticityToken isn't actually middleware, it won't return the result of react env
through your stack so a reaction of deny
wouldn't work. One option would be to also overload the deny
method. ie
def deny(env)
warn env, "attack prevented by #{self.class}"
raise Net::HTTPForbidden, options[:message]
end
or similar..
The option I've suggested drop_session
will still nuke the request session, thus mitigate the CVE issue (existing user is no longer authenticated, thus a successful authentication attempt will still succeed but no chance of it associating the existing account).
To validate this you could have one action put something in your session, then hit the end point without the CSRF token. The value you assigned to the session should have been removed.
This solution didn't work for me. If I didn't include a CSRF token in the request, nothing would happen.
What did work for me is this:
use Rack::Session::Cookie, secret: ENV["SESSION_SECRET"]
OmniAuth.config.allowed_request_methods = [:post]
use Rack::Protection::AuthenticityToken
get '/auth/login' do
<<-HTML
<form action="/auth/google_oauth2" method="post">
<input type="hidden" name="authenticity_token" value="#{env['rack.session'][:csrf]}" />
<input type="submit" value="Sign in with Google"/>
</form>
HTML
end
Omitting the CSRF token results in a 403 Forbidden response.
It sounds like it was working exactly as expected?! i.e. returning a 403 (from the raise Net::HTTPForbidden
in the deny
method) if you don't include the authenticity token. That's the whole point of the gist :)
Am I missing something??
Ah no, sorry I wasn't clear. I included only the code I posted, so I didn't use the OmniauthAuthenticityChecker
class, nor OmniAuth.config.before_request_phase
.
Also note how I configured Rack Protection. use Rack::Protection, reaction: :drop_session, use: :authenticity_token
didn't work, but use Rack::Protection::AuthenticityToken
did.
Not sure I can add anything. No doubt something else in your app is configured differently - middleware order etc.
If your solution works for you and it verifies the authenticity token (and acts appropriately on failure) then happy days.
Absolutely! Thanks for pointing me in the right direction.
for what it's worth, in whatever is different with my setup, I couldn't get this
OmniauthAuthenticityChecker
to actually stop anything if the csrf token was missing/different. I had to instead resort toraise unless accepts? env
, which ins't graceful, but did work.