diff --git a/README.md b/README.md
index 2cfb609b..400fda40 100644
--- a/README.md
+++ b/README.md
@@ -816,6 +816,27 @@ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_cloc
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
+## Deflation Limit
+
+To protect against decompression bombs (a form of DoS attack), SAML messages are limited to 250,000 bytes by default.
+Sometimes legitimate SAML messages will exceed this limit,
+for example due to custom claims like including groups a user is a member of.
+If you want to customize this limit, you need to provide a different setting when initializing the response object.
+Example:
+
+```ruby
+def consume
+ response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], { settings: saml_settings })
+ ...
+end
+
+private
+
+def saml_settings
+ OneLogin::RubySaml::Settings.new(message_max_bytesize: 500_000)
+end
+```
+
## Attribute Service
To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion.
diff --git a/lib/onelogin/ruby-saml/logoutresponse.rb b/lib/onelogin/ruby-saml/logoutresponse.rb
index 47b496e8..06c7ebe4 100644
--- a/lib/onelogin/ruby-saml/logoutresponse.rb
+++ b/lib/onelogin/ruby-saml/logoutresponse.rb
@@ -43,7 +43,7 @@ def initialize(response, settings = nil, options = {})
end
@options = options
- @response = decode_raw_saml(response)
+ @response = decode_raw_saml(response, settings)
@document = XMLSecurity::SignedDocument.new(@response)
end
diff --git a/lib/onelogin/ruby-saml/response.rb b/lib/onelogin/ruby-saml/response.rb
index 505e9365..fceb0026 100644
--- a/lib/onelogin/ruby-saml/response.rb
+++ b/lib/onelogin/ruby-saml/response.rb
@@ -63,7 +63,7 @@ def initialize(response, options = {})
end
end
- @response = decode_raw_saml(response)
+ @response = decode_raw_saml(response, settings)
@document = XMLSecurity::SignedDocument.new(@response, @errors)
if assertion_encrypted?
diff --git a/lib/onelogin/ruby-saml/saml_message.rb b/lib/onelogin/ruby-saml/saml_message.rb
index e680d5c1..d09f55e2 100644
--- a/lib/onelogin/ruby-saml/saml_message.rb
+++ b/lib/onelogin/ruby-saml/saml_message.rb
@@ -22,8 +22,6 @@ class SamlMessage
BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
@@mutex = Mutex.new
- MAX_BYTE_SIZE = 250000
-
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
#
def self.schema
@@ -88,11 +86,12 @@ def valid_saml?(document, soft = true)
# @param saml [String] The deflated and encoded SAML Message
# @return [String] The plain SAML Message
#
- def decode_raw_saml(saml)
+ def decode_raw_saml(saml, settings = nil)
return saml unless base64_encoded?(saml)
- if saml.bytesize > MAX_BYTE_SIZE
- raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
+ settings = OneLogin::RubySaml::Settings.new if settings.nil?
+ if saml.bytesize > settings.message_max_bytesize
+ raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
end
decoded = decode(saml)
diff --git a/lib/onelogin/ruby-saml/settings.rb b/lib/onelogin/ruby-saml/settings.rb
index 1a2f78e2..58e1afce 100644
--- a/lib/onelogin/ruby-saml/settings.rb
+++ b/lib/onelogin/ruby-saml/settings.rb
@@ -54,6 +54,7 @@ def initialize(overrides = {}, keep_security_attributes = false)
attr_accessor :compress_request
attr_accessor :compress_response
attr_accessor :double_quote_xml_attribute_values
+ attr_accessor :message_max_bytesize
attr_accessor :passive
attr_reader :protocol_binding
attr_accessor :attributes_index
@@ -264,6 +265,7 @@ def get_binding(value)
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
:compress_request => true,
:compress_response => true,
+ :message_max_bytesize => 250000,
:soft => true,
:double_quote_xml_attribute_values => false,
:security => {
diff --git a/lib/onelogin/ruby-saml/slo_logoutrequest.rb b/lib/onelogin/ruby-saml/slo_logoutrequest.rb
index a5c405e7..9d34a961 100644
--- a/lib/onelogin/ruby-saml/slo_logoutrequest.rb
+++ b/lib/onelogin/ruby-saml/slo_logoutrequest.rb
@@ -43,7 +43,7 @@ def initialize(request, options = {})
end
end
- @request = decode_raw_saml(request)
+ @request = decode_raw_saml(request, settings)
@document = REXML::Document.new(@request)
end
diff --git a/test/saml_message_test.rb b/test/saml_message_test.rb
index 7aa494d3..2ebe3f3c 100644
--- a/test/saml_message_test.rb
+++ b/test/saml_message_test.rb
@@ -54,21 +54,33 @@ class RubySamlTest < Minitest::Test
end
describe "Prevent Zlib bomb attack" do
+ let(:bomb) { Base64.encode64(Zlib::Deflate.deflate(bomb_data, 9)[2..-5]) }
+ let(:bomb_data) { bomb_prefix + "A" * (200000 * 1024) + bomb_suffix }
+ let(:bomb_prefix) { """
+
+ """ }
+ let(:bomb_suffix) { """
+ ONELOGIN_f92cc1834efc0f73e9c09f482fce80037a6251e7
+ """ }
+
it "raises error when SAML Message exceed the allowed bytes" do
- prefix= """
-
- """
- suffix= """
- ONELOGIN_f92cc1834efc0f73e9c09f482fce80037a6251e7
- """
-
- data = prefix + "A" * (200000 * 1024) + suffix
- bomb = Base64.encode64(Zlib::Deflate.deflate(data, 9)[2..-5])
- assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds " + OneLogin::RubySaml::SamlMessage::MAX_BYTE_SIZE.to_s + " bytes, so was rejected") do
+ assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds #{OneLogin::RubySaml::Settings::DEFAULTS[:message_max_bytesize]} bytes, so was rejected") do
saml_message = OneLogin::RubySaml::SamlMessage.new
saml_message.send(:decode_raw_saml, bomb)
end
end
+
+ describe 'with a custom setting for message_max_bytesize' do
+ let(:message_max_bytesize) { 100_00 }
+ let(:settings) { OneLogin::RubySaml::Settings.new(message_max_bytesize: message_max_bytesize) }
+
+ it 'uses the custom setting' do
+ assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds #{message_max_bytesize} bytes, so was rejected") do
+ saml_message = OneLogin::RubySaml::SamlMessage.new
+ saml_message.send(:decode_raw_saml, bomb, settings)
+ end
+ end
+ end
end
end
end
\ No newline at end of file
diff --git a/test/settings_test.rb b/test/settings_test.rb
index 37453eb0..18826452 100644
--- a/test/settings_test.rb
+++ b/test/settings_test.rb
@@ -17,7 +17,7 @@ class SettingsTest < Minitest::Test
:idp_attribute_names, :issuer, :assertion_consumer_service_url, :single_logout_service_url,
:sp_name_qualifier, :name_identifier_format, :name_identifier_value, :name_identifier_value_requested,
:sessionindex, :attributes_index, :passive, :force_authn,
- :compress_request, :double_quote_xml_attribute_values,
+ :compress_request, :double_quote_xml_attribute_values, :message_max_bytesize,
:security, :certificate, :private_key,
:authn_context, :authn_context_comparison, :authn_context_decl_ref,
:assertion_consumer_logout_service_url
@@ -89,6 +89,7 @@ class SettingsTest < Minitest::Test
:idp_slo_service_url => "http://sso.muda.no/slo",
:idp_slo_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
:idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
+ :message_max_bytesize => 750000,
:valid_until => '2029-04-16T03:35:08.277Z',
:name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
:attributes_index => 30,