Skip to content

Instantly share code, notes, and snippets.

@abrom
Last active March 30, 2022 07:43
Show Gist options
  • Save abrom/effe58b27a4f4ac1b97fb85593d7c3be to your computer and use it in GitHub Desktop.
Save abrom/effe58b27a4f4ac1b97fb85593d7c3be to your computer and use it in GitHub Desktop.
Address CVE-2015-9284 for generic Rack apps (eg Sinatra) for Omniauth
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
@will
Copy link

will commented Jun 28, 2019

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 to raise unless accepts? env, which ins't graceful, but did work.

@abrom
Copy link
Author

abrom commented Jun 28, 2019

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.

@pascalw
Copy link

pascalw commented Dec 26, 2019

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.

@abrom
Copy link
Author

abrom commented Dec 26, 2019

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??

@pascalw
Copy link

pascalw commented Dec 26, 2019

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.

@abrom
Copy link
Author

abrom commented Dec 26, 2019

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.

@pascalw
Copy link

pascalw commented Dec 26, 2019

Absolutely! Thanks for pointing me in the right direction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment