CVE-2026-42256
Description
Net::IMAP implements Internet Message Access Protocol (IMAP) client functionality in Ruby. From versions 0.4.0 to before 0.4.24, 0.5.0 to before 0.5.14, and 0.6.0 to before 0.6.4, when authenticating a connection with SCRAM-SHA1 or SCRAM-SHA256, a hostile server can perform a computational denial-of-service attack on the client process by sending a big iteration count value. This issue has been patched in versions 0.4.24, 0.5.14, and 0.6.4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
net-imapRubyGems | >= 0.6.0, < 0.6.4 | 0.6.4 |
net-imapRubyGems | >= 0.5.0, < 0.5.14 | 0.5.14 |
net-imapRubyGems | >= 0.4.0, < 0.4.24 | 0.4.24 |
Affected products
1Patches
3808001bc45c0🔀 Merge pull request #656 from ruby/backport/v0.5/scram-maximum_iterations
2 files changed · +133 −0
lib/net/imap/sasl/scram_authenticator.rb+74 −0 modified@@ -75,13 +75,19 @@ class ScramAuthenticator # * #password ― Password or passphrase associated with this #username. # * _optional_ #authzid ― Alternate identity to act as or on behalf of. # * _optional_ #min_iterations - Overrides the default value (4096). + # * _optional_ #max_iterations - Overrides the default value (2³¹ - 1). # # Any other keyword parameters are quietly ignored. + # + # *NOTE:* <em>It is the user's responsibility</em> to enforce minimum + # and maximum iteration counts that are appropriate for their security + # context. def initialize(username_arg = nil, password_arg = nil, authcid: nil, username: nil, authzid: nil, password: nil, secret: nil, min_iterations: 4096, # see both RFC5802 and RFC7677 + max_iterations: 2**31 - 1, # max int32 cnonce: nil, # must only be set in tests **options) @username = username || username_arg || authcid or @@ -94,7 +100,22 @@ def initialize(username_arg = nil, password_arg = nil, @min_iterations.positive? or raise ArgumentError, "min_iterations must be positive" + @max_iterations = Integer max_iterations.to_int + @min_iterations <= @max_iterations or + raise ArgumentError, "max_iterations must be more than min_iterations" + @cnonce = cnonce || SecureRandom.base64(32) + + # These attrs are set from the server challenges + @server_first_message = @snonce = @salt = @iterations = nil + @server_error = nil + + # Memoized after @salt and @iterations have been sent. + @salted_password = @client_key = @server_key = nil + + # These values are created and cached in response to server challenges + @client_first_message_bare = nil + @client_final_message_without_proof = nil end # Authentication identity: the identity that matches the #password. @@ -127,8 +148,43 @@ def initialize(username_arg = nil, password_arg = nil, # The minimal allowed iteration count. Lower #iterations will raise an # Error. + # + # *WARNING:* The default value (4096) is set to match guidance from + # both {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#page-12] + # and RFC7677[https://www.rfc-editor.org/rfc/rfc7677#section-4], but + # {modern recommendations}[https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2] + # are significantly higher. + # + # It is ultimately the server's responsibility to securely store + # password hashes. While this parameter can alert the user to + # insecure password storage and prevent insecure authentication + # exchange, updating the iteration count generally requires resetting + # the password on the server. attr_reader :min_iterations + # The maximal allowed iteration count. Higher #iterations will raise an + # Error. + # + # As noted in {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#section-9] + # >>> + # A hostile server can perform a computational denial-of-service + # attack on clients by sending a big iteration count value. + # + # *WARNING:* The default value is <tt>2³¹ - 1</tt>, the maximum signed + # 32-bit integer. This is large enough for the computation to take + # several minutes, and insufficient protection against hostile servers. + # + # Note that <tt>OpenSSL::KDF.pbkdf2_hmac</tt> is implemented by a + # blocking C function, and cannot be interrupted by +Timeout+ or + # <tt>Thread.raise</tt>. And it keeps the Global VM lock, as of v4.0 of + # the +openssl+ gem, so other ruby threads will not be able to run. + # + # <em>To prevent a denial of service attack,</em> this must be set to a + # safe value, depending on hardware and version of OpenSSL. <em>It is + # the user's responsibility</em> to enforce minimum and maximum + # iteration counts that are appropriate for their security context. + attr_reader :max_iterations + # The client nonce, generated by SecureRandom attr_reader :cnonce @@ -147,6 +203,15 @@ def initialize(username_arg = nil, password_arg = nil, # Net::IMAP::NoResponseError. attr_reader :server_error + # Memoized ScramAlgorithm#salted_password (needs #salt and #iterations) + def salted_password; @salted_password ||= compute_salted { super } end + + # Memoized ScramAlgorithm#client_key (needs #salt and #iterations) + def client_key; @client_key ||= compute_salted { super } end + + # Memoized ScramAlgorithm#server_key (needs #salt and #iterations) + def server_key; @server_key ||= compute_salted { super } end + # Returns a new OpenSSL::Digest object, set to the appropriate hash # function for the chosen mechanism. # @@ -186,6 +251,13 @@ def done?; @state == :done end private + # Checks for +salt+ and +iterations+ before yielding + def compute_salted + salt in String or raise Error, "unknown salt" + iterations in Integer or raise Error, "unknown iterations" + yield + end + # Need to store this for auth_message attr_reader :server_first_message @@ -202,6 +274,8 @@ def recv_server_first_message(server_first_message) raise Error, "server did not send iteration count" min_iterations <= iterations or raise Error, "too few iterations: %d" % [iterations] + max_iterations.nil? || iterations <= max_iterations or + raise Error, "too many iterations: %d" % [iterations] mext = sparams["m"] and raise Error, "mandatory extension: %p" % [mext] snonce.start_with? cnonce or
test/net/imap/test_authenticators.rb+59 −0 modified@@ -213,6 +213,65 @@ def test_scram_sha256_authenticator assert authenticator.done? end + def test_scram_min_iterations + # i=1000, below default of 4096 + authenticator = scram_sha1("user", "pencil", + cnonce: "fyko+d2lbbFgONRv9qkxdawL") + assert_equal 4_096, authenticator.min_iterations + assert_equal 2_147_483_647, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too few iterations/) do + authenticator.process( + "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," \ + "s=QSXCR+Q6sek8bf92," \ + "i=1000") + end + + # i=4096, below configured 100,000 + authenticator = scram_sha256("user", "pencil", + cnonce: "rOprNGfwEbeRWgbNEkqO", + min_iterations: 100_000) + assert_equal 100_000, authenticator.min_iterations + assert_equal 2_147_483_647, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too few iterations/) do + authenticator.process( + "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," \ + "s=W22ZaJ0SNY7soEsUEjb6gQ==," \ + "i=4096") + end + end + + def test_scram_max_iterations + # i=1000, below default of 4096 + authenticator = scram_sha1("user", "pencil", + cnonce: "fyko+d2lbbFgONRv9qkxdawL", + max_iterations: 1_000_000) + assert_equal 4_096, authenticator.min_iterations + assert_equal 1_000_000, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too many iterations/) do + authenticator.process( + "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," \ + "s=QSXCR+Q6sek8bf92," \ + "i=2147483647") + end + + # i=4096, below configured 100,000 + authenticator = scram_sha256("user", "pencil", + cnonce: "rOprNGfwEbeRWgbNEkqO", + max_iterations: 100_000) + assert_equal 4_096, authenticator.min_iterations + assert_equal 100_000, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too many iterations/) do + authenticator.process( + "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," \ + "s=W22ZaJ0SNY7soEsUEjb6gQ==," \ + "i=2147483647") + end + end + # ---------------------- # XOAUTH2 # ----------------------
158d0b505074🔀 Merge pull request #655 from ruby/backport/v0.4/scram-maximum_iterations
2 files changed · +133 −0
lib/net/imap/sasl/scram_authenticator.rb+74 −0 modified@@ -75,13 +75,19 @@ class ScramAuthenticator # * #password ― Password or passphrase associated with this #username. # * _optional_ #authzid ― Alternate identity to act as or on behalf of. # * _optional_ #min_iterations - Overrides the default value (4096). + # * _optional_ #max_iterations - Overrides the default value (2³¹ - 1). # # Any other keyword parameters are quietly ignored. + # + # *NOTE:* <em>It is the user's responsibility</em> to enforce minimum + # and maximum iteration counts that are appropriate for their security + # context. def initialize(username_arg = nil, password_arg = nil, authcid: nil, username: nil, authzid: nil, password: nil, secret: nil, min_iterations: 4096, # see both RFC5802 and RFC7677 + max_iterations: 2**31 - 1, # max int32 cnonce: nil, # must only be set in tests **options) @username = username || username_arg || authcid or @@ -94,7 +100,22 @@ def initialize(username_arg = nil, password_arg = nil, @min_iterations.positive? or raise ArgumentError, "min_iterations must be positive" + @max_iterations = Integer max_iterations.to_int + @min_iterations <= @max_iterations or + raise ArgumentError, "max_iterations must be more than min_iterations" + @cnonce = cnonce || SecureRandom.base64(32) + + # These attrs are set from the server challenges + @server_first_message = @snonce = @salt = @iterations = nil + @server_error = nil + + # Memoized after @salt and @iterations have been sent. + @salted_password = @client_key = @server_key = nil + + # These values are created and cached in response to server challenges + @client_first_message_bare = nil + @client_final_message_without_proof = nil end # Authentication identity: the identity that matches the #password. @@ -127,8 +148,43 @@ def initialize(username_arg = nil, password_arg = nil, # The minimal allowed iteration count. Lower #iterations will raise an # Error. + # + # *WARNING:* The default value (4096) is set to match guidance from + # both {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#page-12] + # and RFC7677[https://www.rfc-editor.org/rfc/rfc7677#section-4], but + # {modern recommendations}[https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2] + # are significantly higher. + # + # It is ultimately the server's responsibility to securely store + # password hashes. While this parameter can alert the user to + # insecure password storage and prevent insecure authentication + # exchange, updating the iteration count generally requires resetting + # the password on the server. attr_reader :min_iterations + # The maximal allowed iteration count. Higher #iterations will raise an + # Error. + # + # As noted in {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#section-9] + # >>> + # A hostile server can perform a computational denial-of-service + # attack on clients by sending a big iteration count value. + # + # *WARNING:* The default value is <tt>2³¹ - 1</tt>, the maximum signed + # 32-bit integer. This is large enough for the computation to take + # several minutes, and insufficient protection against hostile servers. + # + # Note that <tt>OpenSSL::KDF.pbkdf2_hmac</tt> is implemented by a + # blocking C function, and cannot be interrupted by +Timeout+ or + # <tt>Thread.raise</tt>. And it keeps the Global VM lock, as of v4.0 of + # the +openssl+ gem, so other ruby threads will not be able to run. + # + # <em>To prevent a denial of service attack,</em> this must be set to a + # safe value, depending on hardware and version of OpenSSL. <em>It is + # the user's responsibility</em> to enforce minimum and maximum + # iteration counts that are appropriate for their security context. + attr_reader :max_iterations + # The client nonce, generated by SecureRandom attr_reader :cnonce @@ -147,6 +203,15 @@ def initialize(username_arg = nil, password_arg = nil, # Net::IMAP::NoResponseError. attr_reader :server_error + # Memoized ScramAlgorithm#salted_password (needs #salt and #iterations) + def salted_password; @salted_password ||= compute_salted { super } end + + # Memoized ScramAlgorithm#client_key (needs #salt and #iterations) + def client_key; @client_key ||= compute_salted { super } end + + # Memoized ScramAlgorithm#server_key (needs #salt and #iterations) + def server_key; @server_key ||= compute_salted { super } end + # Returns a new OpenSSL::Digest object, set to the appropriate hash # function for the chosen mechanism. # @@ -186,6 +251,13 @@ def done?; @state == :done end private + # Checks for +salt+ and +iterations+ before yielding + def compute_salted + String === salt or raise Error, "unknown salt" + Integer === iterations or raise Error, "unknown iterations" + yield + end + # Need to store this for auth_message attr_reader :server_first_message @@ -202,6 +274,8 @@ def recv_server_first_message(server_first_message) raise Error, "server did not send iteration count" min_iterations <= iterations or raise Error, "too few iterations: %d" % [iterations] + max_iterations.nil? || iterations <= max_iterations or + raise Error, "too many iterations: %d" % [iterations] mext = sparams["m"] and raise Error, "mandatory extension: %p" % [mext] snonce.start_with? cnonce or
test/net/imap/test_imap_authenticators.rb+59 −0 modified@@ -206,6 +206,65 @@ def test_scram_sha256_authenticator assert authenticator.done? end + def test_scram_min_iterations + # i=1000, below default of 4096 + authenticator = scram_sha1("user", "pencil", + cnonce: "fyko+d2lbbFgONRv9qkxdawL") + assert_equal 4_096, authenticator.min_iterations + assert_equal 2_147_483_647, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too few iterations/) do + authenticator.process( + "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," \ + "s=QSXCR+Q6sek8bf92," \ + "i=1000") + end + + # i=4096, below configured 100,000 + authenticator = scram_sha256("user", "pencil", + cnonce: "rOprNGfwEbeRWgbNEkqO", + min_iterations: 100_000) + assert_equal 100_000, authenticator.min_iterations + assert_equal 2_147_483_647, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too few iterations/) do + authenticator.process( + "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," \ + "s=W22ZaJ0SNY7soEsUEjb6gQ==," \ + "i=4096") + end + end + + def test_scram_max_iterations + # i=1000, below default of 4096 + authenticator = scram_sha1("user", "pencil", + cnonce: "fyko+d2lbbFgONRv9qkxdawL", + max_iterations: 1_000_000) + assert_equal 4_096, authenticator.min_iterations + assert_equal 1_000_000, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too many iterations/) do + authenticator.process( + "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," \ + "s=QSXCR+Q6sek8bf92," \ + "i=2147483647") + end + + # i=4096, below configured 100,000 + authenticator = scram_sha256("user", "pencil", + cnonce: "rOprNGfwEbeRWgbNEkqO", + max_iterations: 100_000) + assert_equal 4_096, authenticator.min_iterations + assert_equal 100_000, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too many iterations/) do + authenticator.process( + "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," \ + "s=W22ZaJ0SNY7soEsUEjb6gQ==," \ + "i=2147483647") + end + end + # ---------------------- # XOAUTH2 # ----------------------
99f59eab6064🔀 Merge pull request #654 from ruby/scram-maximum_iterations
3 files changed · +173 −0
benchmarks/openssl-pbkdf2.yml+40 −0 added@@ -0,0 +1,40 @@ +prelude: | + require "openssl" + require "securerandom" + + PWDS = Array.new(1024) { + Array.new(rand(1..8)) { + Array.new(4) { rand(97..122).chr }.join + rand(0..9).to_s + }.join(" -_".chars.sample) + } + SALTS = Array.new(1024) { SecureRandom.random_bytes(16) } + @idx = 0 + + $sha1_len = OpenSSL::Digest.new("SHA1") .digest_length + $sha256_len = OpenSSL::Digest.new("SHA256").digest_length + + def sha1(iterations) + pass, salt, @idx = PWDS[@idx], SALTS[@idx], (@idx + 1) & 1023 + OpenSSL::KDF.pbkdf2_hmac( + pass, salt:, iterations:, hash: "SHA1", length: $sha1_len + ) + end + + def sha256(iterations) + pass, salt, @idx = PWDS[@idx], SALTS[@idx], (@idx + 1) & 1023 + OpenSSL::KDF.pbkdf2_hmac( + pass, salt:, iterations:, hash: "SHA256", length: $sha256_len + ) + end + +benchmark: + + # SHA1 + "SHA1, 1m iterations": "sha1 1_000_000" + "SHA1, 10m iterations": "sha1 10_000_000" + "SHA1, 100m iterations": "sha1 100_000_000" + + # SHA256 + "SHA256, 1m iterations": "sha256 1_000_000" + "SHA256, 10m iterations": "sha256 10_000_000" + "SHA256, 100m iterations": "sha256 100_000_000"
lib/net/imap/sasl/scram_authenticator.rb+74 −0 modified@@ -75,13 +75,19 @@ class ScramAuthenticator # * #password ― Password or passphrase associated with this #username. # * _optional_ #authzid ― Alternate identity to act as or on behalf of. # * _optional_ #min_iterations - Overrides the default value (4096). + # * _optional_ #max_iterations - Overrides the default value (2³¹ - 1). # # Any other keyword parameters are quietly ignored. + # + # *NOTE:* <em>It is the user's responsibility</em> to enforce minimum + # and maximum iteration counts that are appropriate for their security + # context. def initialize(username_arg = nil, password_arg = nil, authcid: nil, username: nil, authzid: nil, password: nil, secret: nil, min_iterations: 4096, # see both RFC5802 and RFC7677 + max_iterations: 2**31 - 1, # max int32 cnonce: nil, # must only be set in tests **options) @username = username || username_arg || authcid or @@ -94,7 +100,22 @@ def initialize(username_arg = nil, password_arg = nil, @min_iterations.positive? or raise ArgumentError, "min_iterations must be positive" + @max_iterations = Integer max_iterations.to_int + @min_iterations <= @max_iterations or + raise ArgumentError, "max_iterations must be more than min_iterations" + @cnonce = cnonce || SecureRandom.base64(32) + + # These attrs are set from the server challenges + @server_first_message = @snonce = @salt = @iterations = nil + @server_error = nil + + # Memoized after @salt and @iterations have been sent. + @salted_password = @client_key = @server_key = nil + + # These values are created and cached in response to server challenges + @client_first_message_bare = nil + @client_final_message_without_proof = nil end # Authentication identity: the identity that matches the #password. @@ -127,8 +148,43 @@ def initialize(username_arg = nil, password_arg = nil, # The minimal allowed iteration count. Lower #iterations will raise an # Error. + # + # *WARNING:* The default value (4096) is set to match guidance from + # both {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#page-12] + # and RFC7677[https://www.rfc-editor.org/rfc/rfc7677#section-4], but + # {modern recommendations}[https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2] + # are significantly higher. + # + # It is ultimately the server's responsibility to securely store + # password hashes. While this parameter can alert the user to + # insecure password storage and prevent insecure authentication + # exchange, updating the iteration count generally requires resetting + # the password on the server. attr_reader :min_iterations + # The maximal allowed iteration count. Higher #iterations will raise an + # Error. + # + # As noted in {RFC5802}[https://www.rfc-editor.org/rfc/rfc5802#section-9] + # >>> + # A hostile server can perform a computational denial-of-service + # attack on clients by sending a big iteration count value. + # + # *WARNING:* The default value is <tt>2³¹ - 1</tt>, the maximum signed + # 32-bit integer. This is large enough for the computation to take + # several minutes, and insufficient protection against hostile servers. + # + # Note that <tt>OpenSSL::KDF.pbkdf2_hmac</tt> is implemented by a + # blocking C function, and cannot be interrupted by +Timeout+ or + # <tt>Thread.raise</tt>. And it keeps the Global VM lock, as of v4.0 of + # the +openssl+ gem, so other ruby threads will not be able to run. + # + # <em>To prevent a denial of service attack,</em> this must be set to a + # safe value, depending on hardware and version of OpenSSL. <em>It is + # the user's responsibility</em> to enforce minimum and maximum + # iteration counts that are appropriate for their security context. + attr_reader :max_iterations + # The client nonce, generated by SecureRandom attr_reader :cnonce @@ -147,6 +203,15 @@ def initialize(username_arg = nil, password_arg = nil, # Net::IMAP::NoResponseError. attr_reader :server_error + # Memoized ScramAlgorithm#salted_password (needs #salt and #iterations) + def salted_password = @salted_password ||= compute_salted { super } + + # Memoized ScramAlgorithm#client_key (needs #salt and #iterations) + def client_key = @client_key ||= compute_salted { super } + + # Memoized ScramAlgorithm#server_key (needs #salt and #iterations) + def server_key = @server_key ||= compute_salted { super } + # Returns a new OpenSSL::Digest object, set to the appropriate hash # function for the chosen mechanism. # @@ -186,6 +251,13 @@ def done?; @state == :done end private + # Checks for +salt+ and +iterations+ before yielding + def compute_salted + salt in String or raise Error, "unknown salt" + iterations in Integer or raise Error, "unknown iterations" + yield + end + # Need to store this for auth_message attr_reader :server_first_message @@ -202,6 +274,8 @@ def recv_server_first_message(server_first_message) raise Error, "server did not send iteration count" min_iterations <= iterations or raise Error, "too few iterations: %d" % [iterations] + max_iterations.nil? || iterations <= max_iterations or + raise Error, "too many iterations: %d" % [iterations] mext = sparams["m"] and raise Error, "mandatory extension: %p" % [mext] snonce.start_with? cnonce or
test/net/imap/test_authenticators.rb+59 −0 modified@@ -213,6 +213,65 @@ def test_scram_sha256_authenticator assert authenticator.done? end + def test_scram_min_iterations + # i=1000, below default of 4096 + authenticator = scram_sha1("user", "pencil", + cnonce: "fyko+d2lbbFgONRv9qkxdawL") + assert_equal 4_096, authenticator.min_iterations + assert_equal 2_147_483_647, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too few iterations/) do + authenticator.process( + "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," \ + "s=QSXCR+Q6sek8bf92," \ + "i=1000") + end + + # i=4096, below configured 100,000 + authenticator = scram_sha256("user", "pencil", + cnonce: "rOprNGfwEbeRWgbNEkqO", + min_iterations: 100_000) + assert_equal 100_000, authenticator.min_iterations + assert_equal 2_147_483_647, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too few iterations/) do + authenticator.process( + "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," \ + "s=W22ZaJ0SNY7soEsUEjb6gQ==," \ + "i=4096") + end + end + + def test_scram_max_iterations + # i=1000, below default of 4096 + authenticator = scram_sha1("user", "pencil", + cnonce: "fyko+d2lbbFgONRv9qkxdawL", + max_iterations: 1_000_000) + assert_equal 4_096, authenticator.min_iterations + assert_equal 1_000_000, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too many iterations/) do + authenticator.process( + "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j," \ + "s=QSXCR+Q6sek8bf92," \ + "i=2147483647") + end + + # i=4096, below configured 100,000 + authenticator = scram_sha256("user", "pencil", + cnonce: "rOprNGfwEbeRWgbNEkqO", + max_iterations: 100_000) + assert_equal 4_096, authenticator.min_iterations + assert_equal 100_000, authenticator.max_iterations + authenticator.process(nil) + assert_raise_with_message(Net::IMAP::SASL::Error, /too many iterations/) do + authenticator.process( + "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0," \ + "s=W22ZaJ0SNY7soEsUEjb6gQ==," \ + "i=2147483647") + end + end + # ---------------------- # XOAUTH2 # ----------------------
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-87pf-fpwv-p7m7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-42256ghsaADVISORY
- github.com/ruby/net-imap/commit/158d0b505074397cdb5ceb58935e42dd2bcfa612nvdWEB
- github.com/ruby/net-imap/commit/808001bc45c06f7297a7e96d341279e041a7f7f4nvdWEB
- github.com/ruby/net-imap/commit/99f59eab6064955a23debd95410263ad144df758nvdWEB
- github.com/ruby/net-imap/releases/tag/v0.4.24nvdWEB
- github.com/ruby/net-imap/releases/tag/v0.5.14nvdWEB
- github.com/ruby/net-imap/releases/tag/v0.6.4nvdWEB
- github.com/ruby/net-imap/security/advisories/GHSA-87pf-fpwv-p7m7nvdWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/net-imap/CVE-2026-42256.ymlghsaWEB
- www.rfc-editor.org/rfc/rfc7804.htmlghsaWEB
News mentions
1- Patch Tuesday - May 2026Rapid7 Blog · May 13, 2026