Vydáno ve v2.0.26 a v2.1.0, 25. 10. 2026  Dvě opravy v upstreamu gemu oauth2

oauth2 pro Ruby: tajemství šla za přesměrováním a unikala do chyb

Náš skener našel dvě místa, kde oauth2, OAuth 2.0 klient pro Ruby, na kterém stojí omniauth-oauth2 a který má na RubyGems přes 280 milionů stažení, mohl předat tajné údaje na špatné místo. Ke každému jsme otevřeli pull request s opravou a regresními testy. Správce Peter Boling (pboling) oba 25. 10. 2026 začlenil, ještě týž večer je zpevnil a vydal ve v2.0.26 a v2.1.0. Changelog verze 2.1.0 je oba připisuje nám.

1. Přesměrování na jiný původ si neslo tělo požadavku

OAuth2::Client#request následuje přesměrování 301, 302, 303 a 307. Když přesměrování vedlo na jiný původ (origin), sanitize_redirect_options odstranila hlavičku Authorization a nic dalšího:

def sanitize_redirect_options(req_opts, current_location, next_location)
  return req_opts unless cross_origin_redirect?(current_location, next_location)

  headers = req_opts[:headers]
  return req_opts unless headers && headers.any? { |key, _value| authorization_header?(key) }

  safe_opts = req_opts.dup
  safe_headers = headers.dup
  safe_headers.delete_if { |key, _value| authorization_header?(key) }
  safe_opts[:headers] = safe_headers
  safe_opts
end

Kromě odpovědi 303, která přepne na GET a tělo zahodí, šlo tělo požadavku i parametry dotazu na nový host beze změny. U požadavku na token tělo obsahuje jednorázový autorizační kód nebo refresh token a při auth_scheme: :request_body také client_id a client_secret. Kdyby tedy token endpoint nebo proxy před ním odpověděl přesměrováním 307 na jiný host, klient by tam kód i tajemství klienta poslal znovu.

Náš pull request #775 při přesměrování na jiný původ zahazuje i :body a :params, pod symbolovými i řetězcovými klíči, a při přesměrování v rámci stejného původu je ponechává. Čtyři nové testy na původním kódu selhaly, jeden z nich na POST tělu s přihlašovacími údaji, které dorazilo na cizí host, a s opravou prošly; celá sada 601 příkladů zůstala zelená.

2. Odpověď bez tokenu skončila celá v chybové hlášce

Když odpověď token endpointu neobsahuje access token ani refresh token a je zapnuté raise_errors (výchozí stav), OAuth2::AccessToken#initialize vyhodí OAuth2::Error s popisem "Options are: #{opts.inspect}". V tu chvíli opts pořád obsahuje zbytek odpovědi poskytovatele, třeba OpenID Connect id_token nebo token pod jiným klíčem, takže celé hodnoty končily ve zprávách výjimek, v logách a v nástrojích na sledování chyb.

Náš pull request #776 před výpisem nahradí hodnoty citlivých klíčů textem [FILTERED]; testy ukazují, že se tajné údaje ve zprávě už neobjeví. Peter Boling na něj odpověděl:

„This is pretty good.“

Dodal, že nové vydání jeho gemu auth-sanitizer přinese pomocnou funkci, která naše ručně psané maskování zjednoduší, a vydal ji v auth-sanitizer #27.

Co přidal správce

Pár minut po začlenění otevřel Peter Boling pull request #777, který:

Verze 2.1.0 navíc nastavuje allow_cross_origin_redirects výchozí hodnotu false, takže klient přesměrování na jiný původ odmítne dřív, než cokoli znovu odešle; aplikace, která si ho znovu povolí, dostane požadavek očištěný naší opravou.

Časová osa

Jak to číst

Únik přes přesměrování vyžaduje, aby token endpoint nebo něco před ním poslalo přesměrování na jiný původ: kompromitovaný nebo špatně nastavený server či proxy, ne libovolný cizí útočník. Útočník, který token endpoint plně ovládá, požadavek vidí tak jako tak, takže oprava se týká přesměrování ze serveru, kterému aplikace jinak věří. Únik do chybové hlášky se do logů dostane jen tehdy, když poskytovatel vrátí odpověď bez tokenu. Obojí je zpevnění široce používané knihovny, ne díra zneužitelná na dálku. Nálezy jsou podněty pro lidi, ne rozsudky.

Zdroje

← Zpět na příběhy úspěchu