diff --git a/lib/omniauth/strategies/oauth2.rb b/lib/omniauth/strategies/oauth2.rb index 28b3ae1..ff6b4d5 100644 --- a/lib/omniauth/strategies/oauth2.rb +++ b/lib/omniauth/strategies/oauth2.rb @@ -67,7 +67,7 @@ def callback_phase # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength error = request.params["error_reason"] || request.params["error"] if error fail!(error, CallbackError.new(request.params["error"], request.params["error_description"] || request.params["error_reason"], request.params["error_uri"])) - elsif !options.provider_ignores_state && (request.params["state"].to_s.empty? || request.params["state"] != session.delete("omniauth.state")) + elsif !options.provider_ignores_state && (request.params["state"].to_s.empty? || !secure_compare(request.params["state"], session.delete("omniauth.state"))) fail!(:csrf_detected, CallbackError.new(:csrf_detected, "CSRF detected")) else self.access_token = build_access_token @@ -105,6 +105,17 @@ def options_for(option) hash end + # constant-time comparison algorithm to prevent timing attacks + def secure_compare(string_a, string_b) + return false unless string_a.bytesize == string_b.bytesize + + l = string_a.unpack "C#{string_a.bytesize}" + + res = 0 + string_b.each_byte { |byte| res |= byte ^ l.shift } + res.zero? + end + # An error that is indicated in the OAuth 2.0 callback. # This could be a `redirect_uri_mismatch` or other class CallbackError < StandardError diff --git a/spec/omniauth/strategies/oauth2_spec.rb b/spec/omniauth/strategies/oauth2_spec.rb index 2008cc9..33d69b6 100644 --- a/spec/omniauth/strategies/oauth2_spec.rb +++ b/spec/omniauth/strategies/oauth2_spec.rb @@ -87,6 +87,16 @@ def app instance.callback_phase end end + + describe "#secure_params" do + subject { fresh_strategy } + + it "returns true when the two inputs are the same and false otherwise" do + instance = subject.new("abc", "def") + expect(instance.send(:secure_compare, "a", "a")).to be true + expect(instance.send(:secure_compare, "b", "a")).to be false + end + end end describe OmniAuth::Strategies::OAuth2::CallbackError do