diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aedb8d71..d937832c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,10 +36,13 @@ jobs: - 3.3 - 3.4 - jruby-9.4 + - jruby-10.0 - truffleruby exclude: - os: windows-latest ruby-version: jruby-9.4 + - os: windows-latest + ruby-version: jruby-10.0 - os: windows-latest ruby-version: truffleruby runs-on: ${{ matrix.os }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f6165e3e..f62db877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,11 @@ * [#731](https://github.com/SAML-Toolkits/ruby-saml/pull/731) Add CI coverage for Ruby 3.4. Remove CI coverage for Ruby 1.x and 2.x. * [#735](https://github.com/SAML-Toolkits/ruby-saml/pull/735) Add `Settings#sp_uuid_prefix` and deprecate `Utils#set_prefix`. +### 1.18.1 (Jul 29, 2025) +* Fix vulnerability CVE-2025-54572 Prevent DOS due large SAML Message +* Adapt tests to be able to execute signature validation sooner +* CI Improvements. Support Ruby 3.4 + ### 1.18.0 (Mar 12, 2025) * [#750](https://github.com/SAML-Toolkits/ruby-saml/pull/750) Fix vulnerabilities: CVE-2025-25291, CVE-2025-25292: SAML authentication bypass via Signature Wrapping attack allowed due parser differential. Fix vulnerability: CVE-2025-25293: Potential DOS abusing of compressed messages. * [#718](https://github.com/SAML-Toolkits/ruby-saml/pull/718/) Add support to retrieve from SAMLResponse the AuthnInstant and AuthnContextClassRef values @@ -61,7 +66,7 @@ * [#614](https://github.com/SAML-Toolkits/ruby-saml/pull/614) Support :name_id_format option for IdpMetadataParser * [#611](https://github.com/SAML-Toolkits/ruby-saml/pull/611) IdpMetadataParser should always set idp_cert_multi, even when there is only one cert * [#610](https://github.com/SAML-Toolkits/ruby-saml/pull/610) New IDP sso/slo binding params which deprecate :embed_sign -* [#602](https://github.com/SAML-Toolkits/ruby-saml/pull/602) Refactor the OneLogin::RubySaml::Metadata class +* [#602](https://github.com/SAML-Toolkits/ruby-saml/pull/602) Refactor the RubySaml::Metadata class * [#586](https://github.com/SAML-Toolkits/ruby-saml/pull/586) Support milliseconds in cacheDuration parsing * [#585](https://github.com/SAML-Toolkits/ruby-saml/pull/585) Do not append " | " to StatusCode unnecessarily * [#607](https://github.com/SAML-Toolkits/ruby-saml/pull/607) Clean up @@ -136,7 +141,7 @@ * Updated invalid audience error message ### 1.7.2 (Feb 28, 2018) -* [#446](https://github.com/SAML-Toolkits/ruby-saml/pull/446) Normalize text returned by OneLogin::RubySaml::Utils.element_text +* [#446](https://github.com/SAML-Toolkits/ruby-saml/pull/446) Normalize text returned by RubySaml::Utils.element_text ### 1.7.1 (Feb 28, 2018) * [#444](https://github.com/SAML-Toolkits/ruby-saml/pull/444) Fix audience validation for empty audience restriction @@ -266,7 +271,7 @@ * [#226](https://github.com/SAML-Toolkits/ruby-saml/pull/226) Ensure IdP certificate is formatted properly * [#225](https://github.com/SAML-Toolkits/ruby-saml/pull/225) Add documentation to several methods. Fix xpath injection on xml_security.rb * [#223](https://github.com/SAML-Toolkits/ruby-saml/pull/223) Allow logging to be delegated to an arbitrary Logger -* [#222](https://github.com/SAML-Toolkits/ruby-saml/pull/222) No more silent failure fetching idp metadata (OneLogin::RubySaml::HttpError raised). +* [#222](https://github.com/SAML-Toolkits/ruby-saml/pull/222) No more silent failure fetching idp metadata (RubySaml::HttpError raised). ### 0.9.2 (Apr 28, 2015) * [#216](https://github.com/SAML-Toolkits/ruby-saml/pull/216) Add fingerprint algorithm support @@ -314,10 +319,10 @@ * [#183](https://github.com/SAML-Toolkits/ruby-saml/pull/183) Resolved a security vulnerability where string interpolation in a `REXML::XPath.first()` method call allowed for arbitrary code execution. ### 0.8.0 (Feb 21, 2014) -**IMPORTANT**: This release changed namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly. +**IMPORTANT**: This release changed namespace of the gem from `Saml` to `RubySaml`. Please update your implementations of the gem accordingly. -* [#111](https://github.com/SAML-Toolkits/ruby-saml/pull/111) `Onelogin::` is `OneLogin::` -* [#108](https://github.com/SAML-Toolkits/ruby-saml/pull/108) Change namespacing from `Onelogin::Saml` to `Onelogin::Rubysaml` +* [#111](https://github.com/SAML-Toolkits/ruby-saml/pull/111) `` is `` +* [#108](https://github.com/SAML-Toolkits/ruby-saml/pull/108) Change namespacing from `Saml` to `Rubysaml` ### 0.7.3 (Feb 20, 2014) Updated gem dependencies to be compatible with Ruby 1.8.7-p374 and 1.9.3-p448. Removed unnecessary `canonix` gem dependency. diff --git a/README.md b/README.md index 2ba2d898..e2365249 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,18 @@ Ruby SAML minor versions may introduce breaking changes. Please read ## Vulnerability Notice -There are **critical vulnerabilities** affecting ruby-saml < 1.18.0 which allow -SAML authentication bypass (CVE-2024-45409, CVE-2025-25291, CVE-2025-25292, CVE-2025-25293). -**Please upgrade to a fixed version (1.18.0 or 2.0.0) as soon as possible.** +Please note the following **critical vulnerabilities**: + +- CVE-2025-54572 (DOS attack vector) affects version ruby-saml < 1.18.1 +- CVE-2024-45409, CVE-2025-25291, CVE-2025-25292, CVE-2025-25293 (SAML authentication bypass) affects ruby-saml < 1.18.0 + +**Please upgrade to a fixed version (2.0.0 or 1.18.1) as soon as possible.** + +## Sponsors + +Thanks to the following sponsors for securing the open source ecosystem, + +[84codes](https://www.84codes.com) ## Overview @@ -46,7 +55,7 @@ it by email to the maintainer: sixto.martin.garcia+security@gmail.com The following Ruby versions are covered by CI testing: * Ruby (MRI) 3.0 to 3.4 -* JRuby 9.4 +* JRuby 9.4 to 10.0 * TruffleRuby (latest) Older Ruby versions are supported on the 1.x release of Ruby SAML. diff --git a/UPGRADING.md b/UPGRADING.md index 7d422559..9c84103f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -26,14 +26,14 @@ This issue is likely not critical for most IdPs, but since it is not tested, it ### Root "OneLogin" namespace changed to "RubySaml" -RubySaml version `2.0.0` changes the root namespace from `OneLogin::RubySaml::` to just `RubySaml::`. -Please remove `OneLogin::` and `onelogin/` everywhere in your codebase. Aside from this namespace change, +RubySaml version `2.0.0` changes the root namespace from `RubySaml::` to just `RubySaml::`. +Please remove `` and `onelogin/` everywhere in your codebase. Aside from this namespace change, the class names themselves have intentionally been kept the same. Note that the project folder structure has also been updated accordingly. Notably, the directory `lib/onelogin/schemas` is now `lib/ruby_saml/schemas`. -For backward compatibility, the alias `OneLogin = Object` has been set, so `OneLogin::RubySaml::` will still work +For backward compatibility, the alias `OneLogin = Object` has been set, so `RubySaml::` will still work as before. This alias will be removed in RubySaml version `3.0.0`. ### Deprecation and removal of "XMLSecurity" namespace @@ -319,7 +319,7 @@ options = { "RelayState" => raw_query_params["RelayState"], }, } -slo_logout_request = OneLogin::RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options) +slo_logout_request = RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options) raise "Invalid Logout Request" unless slo_logout_request.is_valid? ``` @@ -379,4 +379,4 @@ Version `0.9` adds many new features and improvements. ## Upgrading from 0.7.x to 0.8.x -Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly. +Version `0.8.x` changes the namespace of the gem from `Saml` to `RubySaml`. Please update your implementations of the gem accordingly. diff --git a/test/response_test.rb b/test/response_test.rb index 123d3931..a0050692 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -7,7 +7,7 @@ class RubySamlTest < Minitest::Test describe "Response" do let(:settings) { RubySaml::Settings.new } let(:response) { RubySaml::Response.new(response_document_without_recipient) } - let(:response_without_recipient) { OneLogin::RubySaml::Response.new(signed_response_document_without_recipient) } + let(:response_without_recipient) { RubySaml::Response.new(signed_response_document_without_recipient) } let(:response_without_attributes) { RubySaml::Response.new(response_document_without_attributes) } let(:response_with_multiple_attribute_statements) { RubySaml::Response.new(fixture(:response_with_multiple_attribute_statements)) } let(:response_without_reference_uri) { RubySaml::Response.new(response_document_without_reference_uri) } @@ -1752,6 +1752,74 @@ def generate_audience_error(expected, actual) end end + describe "DOS attack via oversized payload" do + it "rejects oversized payloads before attempting Base64 validation" do + large_saml_response = "A" * (RubySaml::Settings::DEFAULTS[:message_max_bytesize] + 100) + + assert_raises(RubySaml::ValidationError, "Encoded SAML Message exceeds #{RubySaml::Settings::DEFAULTS[:message_max_bytesize]} bytes, so was rejected") do + RubySaml::Response.new(large_saml_response) + end + end + + it "rejects oversized payloads before attempting Base64 validation with custom max_bytesize" do + custom_max = 10000 + large_saml_response = "A" * (custom_max + 100) + custom_settings = RubySaml::Settings.new({ message_max_bytesize: custom_max }) + + assert_raises(RubySaml::ValidationError, "Encoded SAML Message exceeds #{custom_max} bytes, so was rejected") do + RubySaml::Response.new(large_saml_response, settings: custom_settings) + end + end + end + + describe "Zlib bomb attack" do + it "rejects Zlib bomb attacks" do + # Create a message that when inflated would be extremely large + bomb_prefix = <<~XML + + + http://idp.example.com/ + + + http://idp.example.com/ + + XML + + bomb_suffix = <<~XML + + + + XML + + bomb_data = bomb_prefix + 'A' * (200_000 * 1024) + bomb_suffix + bomb = Base64.strict_encode64(Zlib::Deflate.deflate(bomb_data, 9)[2..-5]) + + assert_raises(RubySaml::ValidationError) do + RubySaml::Response.new(bomb) + end + end + + it "rejects Zlib bomb attacks with custom max_bytesize" do + custom_max = 100_000 + custom_settings = RubySaml::Settings.new({:message_max_bytesize => custom_max}) + + bomb_prefix = <<~XML + + + http://idp.example.com/ + XML + + bomb_suffix = "" + + bomb_data = bomb_prefix + 'A' * (custom_max + 100) + bomb_suffix + bomb = Base64.strict_encode64(Zlib::Deflate.deflate(bomb_data, 9)[2..-5]) + + assert_raises(RubySaml::ValidationError, "SAML Message exceeds #{custom_max} bytes, so was rejected") do + RubySaml::Response.new(bomb, settings: custom_settings) + end + end + end + describe "signature wrapping attack with encrypted assertion" do it "should not be valid" do settings.private_key = ruby_saml_key_text diff --git a/test/xml/decoder_test.rb b/test/xml/decoder_test.rb index 13172ecd..74f0557b 100644 --- a/test/xml/decoder_test.rb +++ b/test/xml/decoder_test.rb @@ -67,6 +67,29 @@ class XmlDecoderTest < Minitest::Test end end + it 'rejects oversized payloads before attempting Base64 validation' do + large_saml_message = 'A' * (RubySaml::XML::Decoder::DEFAULT_MAX_BYTESIZE + 100) + + assert_raises(RubySaml::ValidationError, "Encoded SAML Message exceeds #{RubySaml::XML::Decoder::DEFAULT_MAX_BYTESIZE} bytes, so was rejected") do + # Mock to ensure base64_encoded? is never called on oversized input + RubySaml::XML::Decoder.expects(:base64_encoded?).never + + RubySaml::XML::Decoder.decode_message(large_saml_message) + end + end + + it 'rejects oversized payloads before attempting Base64 validation with custom max' do + custom_max = 1000 + large_saml_message = 'A' * (custom_max + 100) + + assert_raises(RubySaml::ValidationError, "Encoded SAML Message exceeds #{custom_max} bytes, so was rejected") do + # Mock to ensure base64_encoded? is never called on oversized input + RubySaml::XML::Decoder.expects(:base64_encoded?).never + + RubySaml::XML::Decoder.decode_message(large_saml_message, custom_max) + end + end + it "rejects Zlib bomb attacks" do # Create a message that when inflated would be extremely large bomb_prefix = <<~XML