Skip to content

Commit c830138

Browse files
authored
Merge pull request #174 from jhartzler/master
Prevent timing attack on CSRF, completing wonderful pr by @eutopian
2 parents 3e7ee11 + cec0655 commit c830138

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

lib/omniauth/strategies/oauth2.rb

+12-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def token_params
8383

8484
def callback_phase # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
8585
error = request.params["error_reason"] || request.params["error"]
86-
if !options.provider_ignores_state && (request.params["state"].to_s.empty? || request.params["state"] != session.delete("omniauth.state"))
86+
if !options.provider_ignores_state && (request.params["state"].to_s.empty? || !secure_compare(request.params["state"], session.delete("omniauth.state")))
8787
fail!(:csrf_detected, CallbackError.new(:csrf_detected, "CSRF detected"))
8888
elsif error
8989
fail!(error, CallbackError.new(request.params["error"], request.params["error_description"] || request.params["error_reason"], request.params["error_uri"]))
@@ -144,6 +144,17 @@ def options_for(option)
144144
hash
145145
end
146146

147+
# constant-time comparison algorithm to prevent timing attacks
148+
def secure_compare(string_a, string_b)
149+
return false unless string_a.bytesize == string_b.bytesize
150+
151+
l = string_a.unpack "C#{string_a.bytesize}"
152+
153+
res = 0
154+
string_b.each_byte { |byte| res |= byte ^ l.shift }
155+
res.zero?
156+
end
157+
147158
# An error that is indicated in the OAuth 2.0 callback.
148159
# This could be a `redirect_uri_mismatch` or other
149160
class CallbackError < StandardError

spec/omniauth/strategies/oauth2_spec.rb

+10
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ def app
168168
end
169169
end
170170
end
171+
172+
describe "#secure_compare" do
173+
subject { fresh_strategy }
174+
175+
it "returns true when the two inputs are the same and false otherwise" do
176+
instance = subject.new("abc", "def")
177+
expect(instance.send(:secure_compare, "a", "a")).to be true
178+
expect(instance.send(:secure_compare, "b", "a")).to be false
179+
end
180+
end
171181
end
172182

173183
describe OmniAuth::Strategies::OAuth2::CallbackError do

0 commit comments

Comments
 (0)