HTTP Request Smuggling in puma
Description
Puma is a simple, fast, multi-threaded, parallel HTTP 1.1 server for Ruby/Rack applications. When using Puma behind a proxy that does not properly validate that the incoming HTTP request matches the RFC7230 standard, Puma and the frontend proxy may disagree on where a request starts and ends. This would allow requests to be smuggled via the front-end proxy to Puma. The vulnerability has been fixed in 5.6.4 and 4.3.12. Users are advised to upgrade as soon as possible. Workaround: when deploying a proxy in front of Puma, turning on any and all functionality to make sure that the request matches the RFC7230 standard.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Puma HTTP server vulnerable to request smuggling when behind a proxy that does not validate RFC7230 compliance, fixed in 5.6.4 and 4.3.12.
Vulnerability
Puma versions before 5.6.4 and 4.3.12 contain an HTTP request smuggling vulnerability. When Puma is deployed behind a proxy that does not properly validate incoming HTTP requests against RFC7230, the proxy and Puma may disagree on request boundaries. This allows an attacker to craft requests that are interpreted differently by the front-end proxy and Puma, leading to request smuggling. The issue lies in how Puma parses Transfer-Encoding headers, as seen in the fix commit [4].
Exploitation
An attacker needs network access to send HTTP requests through the front-end proxy. The proxy must be configured in a way that does not enforce RFC7230 compliance. By sending a specially crafted request with ambiguous Transfer-Encoding or Content-Length headers, the attacker can cause the proxy to see one request while Puma sees a different one. This can be achieved by exploiting differences in header parsing, such as using multiple Transfer-Encoding values or malformed chunked encoding [2]. No authentication is required.
Impact
Successful exploitation allows an attacker to smuggle a malicious request past the proxy, potentially bypassing security controls, accessing sensitive data, or compromising other application users. The impact is similar to typical HTTP request smuggling attacks, which can lead to session hijacking, cache poisoning, or privilege escalation [2]. The attacker can effectively poison the connection between proxy and Puma, affecting subsequent requests.
Mitigation
The vulnerability is fixed in Puma versions 5.6.4 and 4.3.12, released on 2022-03-30 [1][3]. Users should upgrade immediately. As a workaround, ensure that any proxy in front of Puma validates incoming requests against RFC7230, including proper handling of Transfer-Encoding and Content-Length headers. Turning on request validation features in the proxy can mitigate the risk until patching is possible [1].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pumaRubyGems | >= 5.0.0, < 5.6.4 | 5.6.4 |
pumaRubyGems | < 4.3.12 | 4.3.12 |
Affected products
63- ghsa-coords62 versionspkg:gem/pumapkg:rpm/opensuse/ruby3.2-rubygem-puma-5&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/rubygem-puma-4&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/rubygem-puma-5&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/rubygem-puma&distro=openSUSE%20Leap%2015.3pkg:rpm/opensuse/rubygem-puma&distro=openSUSE%20Leap%2015.4pkg:rpm/suse/ardana-ansible&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/ardana-ansible&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/ardana-ansible&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/ardana-cobbler&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/ardana-cobbler&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/ardana-cobbler&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/ardana-tempest&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/grafana&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/grafana&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-heat-templates&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-heat-templates&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-horizon-plugin-gbp-ui&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-horizon-plugin-gbp-ui&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-murano&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-murano&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-murano&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-murano-doc&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-murano-doc&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-murano-doc&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/openstack-neutron-gbp&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-neutron-gbp&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/openstack-nova&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/openstack-nova&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-Django&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/rabbitmq-server&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/rabbitmq-server&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/rabbitmq-server&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/rabbitmq-server&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/rabbitmq-server&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/rubygem-puma&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015pkg:rpm/suse/rubygem-puma&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015%20SP1pkg:rpm/suse/rubygem-puma&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015%20SP2pkg:rpm/suse/rubygem-puma&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015%20SP3pkg:rpm/suse/rubygem-puma&distro=SUSE%20Linux%20Enterprise%20High%20Availability%20Extension%2015%20SP4pkg:rpm/suse/rubygem-puma&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/rubygem-puma&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/venv-openstack-heat&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-heat&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-heat&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-horizon&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-horizon&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-horizon-hpe&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-murano&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/venv-openstack-murano&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/venv-openstack-neutron&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/venv-openstack-nova&distro=SUSE%20OpenStack%20Cloud%209
>= 5.0.0, < 5.6.4+ 61 more
- (no CPE)range: >= 5.0.0, < 5.6.4
- (no CPE)range: < 5.6.5-1.7
- (no CPE)range: < 4.3.12-1.1
- (no CPE)range: < 5.6.5-1.1
- (no CPE)range: < 4.3.12-150000.3.9.1
- (no CPE)range: < 4.3.12-150000.3.9.1
- (no CPE)range: < 8.0+git.1660773729.3789a6d-3.85.1
- (no CPE)range: < 8.0+git.1660773729.3789a6d-3.85.1
- (no CPE)range: < 9.0+git.1660748476.c118d23-3.32.1
- (no CPE)range: < 8.0+git.1660773402.d845a45-3.47.1
- (no CPE)range: < 8.0+git.1660773402.d845a45-3.47.1
- (no CPE)range: < 9.0+git.1660747489.119efcd-3.19.1
- (no CPE)range: < 9.0+git.1651855288.a2341ad-3.22.1
- (no CPE)range: < 6.7.4-4.23.1
- (no CPE)range: < 6.7.4-4.23.1
- (no CPE)range: < 6.7.4-3.29.1
- (no CPE)range: < 6.7.4-4.23.1
- (no CPE)range: < 6.7.4-3.29.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a-3.27.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a-3.27.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a7-3.15.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a-3.27.1
- (no CPE)range: < 0.0.0+git.1654529662.75fa04a7-3.15.1
- (no CPE)range: < 14.0.1~dev4-3.12.1
- (no CPE)range: < 14.0.1~dev4-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 4.0.2~dev3-3.12.1
- (no CPE)range: < 14.0.1~dev46-3.34.1
- (no CPE)range: < 14.0.1~dev46-3.34.1
- (no CPE)range: < 18.3.1~dev92-3.43.1
- (no CPE)range: < 18.3.1~dev92-3.43.1
- (no CPE)range: < 1.11.29-3.40.1
- (no CPE)range: < 1.11.29-3.40.1
- (no CPE)range: < 1.11.29-3.42.1
- (no CPE)range: < 1.11.29-3.42.1
- (no CPE)range: < 1.11.29-3.42.1
- (no CPE)range: < 3.6.16-3.13.1
- (no CPE)range: < 3.6.16-3.13.1
- (no CPE)range: < 3.6.16-4.3.1
- (no CPE)range: < 3.6.16-3.13.1
- (no CPE)range: < 3.6.16-4.3.1
- (no CPE)range: < 4.3.12-150000.3.9.1
- (no CPE)range: < 4.3.12-150000.3.9.1
- (no CPE)range: < 4.3.12-150000.3.9.1
- (no CPE)range: < 4.3.12-150000.3.9.1
- (no CPE)range: < 4.3.12-150000.3.9.1
- (no CPE)range: < 2.16.0-3.18.1
- (no CPE)range: < 2.16.0-4.18.1
- (no CPE)range: < 9.0.8~dev22-12.45.1
- (no CPE)range: < 9.0.8~dev22-12.45.1
- (no CPE)range: < 11.0.4~dev4-3.37.1
- (no CPE)range: < 12.0.5~dev6-14.48.1
- (no CPE)range: < 14.1.1~dev11-4.41.1
- (no CPE)range: < 12.0.5~dev6-14.48.1
- (no CPE)range: < 4.0.2~dev3-12.38.1
- (no CPE)range: < 4.0.2~dev3-12.38.1
- (no CPE)range: < 13.0.8~dev206-6.41.1
- (no CPE)range: < 18.3.1~dev92-3.41.1
- puma/pumav5Range: < 4.3.12
Patches
15bb7d202e24dMerge pull request from GHSA-h99w-9q5r-gjq9
6 files changed · +289 −15
lib/puma/client.rb+54 −11 modified@@ -23,6 +23,8 @@ module Puma class ConnectionError < RuntimeError; end + class HttpParserError501 < IOError; end + # An instance of this class represents a unique request from a client. # For example, this could be a web request from a browser or from CURL. # @@ -35,7 +37,21 @@ class ConnectionError < RuntimeError; end # Instances of this class are responsible for knowing if # the header and body are fully buffered via the `try_to_finish` method. # They can be used to "time out" a response via the `timeout_at` reader. + # class Client + + # this tests all values but the last, which must be chunked + ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze + + # chunked body validation + CHUNK_SIZE_INVALID = /[^\h]/.freeze + CHUNK_VALID_ENDING = "\r\n".freeze + + # Content-Length header value validation + CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze + + TE_ERR_MSG = 'Invalid Transfer-Encoding' + # The object used for a request with no body. All requests with # no body share this one object since it has no state. EmptyBody = NullIO.new @@ -302,24 +318,40 @@ def setup_body body = @parser.body te = @env[TRANSFER_ENCODING2] - if te - if te.include?(",") - te.split(",").each do |part| - if CHUNKED.casecmp(part.strip) == 0 - return setup_chunked_body(body) - end + te_lwr = te.downcase + if te.include? ',' + te_ary = te_lwr.split ',' + te_count = te_ary.count CHUNKED + te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e } + if te_ary.last == CHUNKED && te_count == 1 && te_valid + @env.delete TRANSFER_ENCODING2 + return setup_chunked_body body + elsif te_count >= 1 + raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'" + elsif !te_valid + raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'" end - elsif CHUNKED.casecmp(te) == 0 - return setup_chunked_body(body) + elsif te_lwr == CHUNKED + @env.delete TRANSFER_ENCODING2 + return setup_chunked_body body + elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr + raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'" + else + raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'" end end @chunked_body = false cl = @env[CONTENT_LENGTH] - unless cl + if cl + # cannot contain characters that are not \d + if cl =~ CONTENT_LENGTH_VALUE_INVALID + raise HttpParserError, "Invalid Content-Length: #{cl.inspect}" + end + else @buffer = body.empty? ? nil : body @body = EmptyBody set_ready @@ -478,7 +510,13 @@ def decode_chunk(chunk) while !io.eof? line = io.gets if line.end_with?("\r\n") - len = line.strip.to_i(16) + # Puma doesn't process chunk extensions, but should parse if they're + # present, which is the reason for the semicolon regex + chunk_hex = line.strip[/\A[^;]+/] + if chunk_hex =~ CHUNK_SIZE_INVALID + raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'" + end + len = chunk_hex.to_i(16) if len == 0 @in_last_chunk = true @body.rewind @@ -509,7 +547,12 @@ def decode_chunk(chunk) case when got == len - write_chunk(part[0..-3]) # to skip the ending \r\n + # proper chunked segment must end with "\r\n" + if part.end_with? CHUNK_VALID_ENDING + write_chunk(part[0..-3]) # to skip the ending \r\n + else + raise HttpParserError, "Chunk size mismatch" + end when got <= len - 2 write_chunk(part) @partial_part_left = len - part.size
lib/puma/const.rb+5 −3 modified@@ -76,7 +76,7 @@ class UnsupportedOption < RuntimeError 508 => 'Loop Detected', 510 => 'Not Extended', 511 => 'Network Authentication Required' - } + }.freeze # For some HTTP status codes the client only expects headers. # @@ -85,7 +85,7 @@ class UnsupportedOption < RuntimeError 204 => true, 205 => true, 304 => true - } + }.freeze # Frequently used constants when constructing requests or responses. Many times # the constant just refers to a string with the same contents. Using these constants @@ -145,9 +145,11 @@ module Const 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze, # Indicate that there was an internal error, obviously. 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze, + # Incorrect or invalid header value + 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze, # A common header for indicating the server is too busy. Not used yet. 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze - } + }.freeze # The basic max request size we'll try to read. CHUNK_SIZE = 16 * 1024
lib/puma/server.rb+3 −0 modified@@ -515,6 +515,9 @@ def client_error(e, client) when HttpParserError client.write_error(400) @events.parse_error e, client + when HttpParserError501 + client.write_error(501) + @events.parse_error e, client else client.write_error(500) @events.unknown_error e, nil, "Read"
test/helper.rb+3 −0 modified@@ -174,6 +174,9 @@ def skip_unless(eng, bt: caller) Minitest::Test.include TestSkips class Minitest::Test + + REPO_NAME = ENV['GITHUB_REPOSITORY'] ? ENV['GITHUB_REPOSITORY'][/[^\/]+\z/] : 'puma' + def self.run(reporter, options = {}) # :nodoc: prove_it! super
test/test_puma_server.rb+4 −1 modified@@ -602,17 +602,20 @@ def test_Expect_100 def test_chunked_request body = nil content_length = nil + transfer_encoding = nil server_run { |env| body = env['rack.input'].read content_length = env['CONTENT_LENGTH'] + transfer_encoding = env['HTTP_TRANSFER_ENCODING'] [200, {}, [""]] } - data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n" + data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: gzip,chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n" assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data assert_equal "hello", body assert_equal "5", content_length + assert_nil transfer_encoding end def test_large_chunked_request
test/test_request_invalid.rb+220 −0 added@@ -0,0 +1,220 @@ +require_relative "helper" +require "puma/events" + +# These tests check for invalid request headers and metadata. +# Content-Length, Transfer-Encoding, and chunked body size +# values are checked for validity +# +# See https://datatracker.ietf.org/doc/html/rfc7230 +# +# https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 Content-Length +# https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1 Transfer-Encoding +# https://datatracker.ietf.org/doc/html/rfc7230#section-4.1 chunked body size +# +class TestRequestInvalid < Minitest::Test + # running parallel seems to take longer... + # parallelize_me! unless JRUBY_HEAD + + GET_PREFIX = "GET / HTTP/1.1\r\nConnection: close\r\n" + CHUNKED = "1\r\nH\r\n4\r\nello\r\n5\r\nWorld\r\n0\r\n\r\n" + + def setup + @host = '127.0.0.1' + + @ios = [] + + # this app should never be called, used for debugging + app = ->(env) { + body = ''.dup + env.each do |k,v| + body << "#{k} = #{v}\n" + if k == 'rack.input' + body << "#{v.read}\n" + end + end + [200, {}, [body]] + } + + @log_writer = Puma::LogWriter.strings + events = Puma::Events.new + @server = Puma::Server.new app, @log_writer, events + @port = (@server.add_tcp_listener @host, 0).addr[1] + @server.run + sleep 0.15 if Puma.jruby? + end + + def teardown + @server.stop(true) + @ios.each { |io| io.close if io && !io.closed? } + end + + def send_http_and_read(req) + send_http(req).read + end + + def send_http(req) + new_connection << req + end + + def new_connection + TCPSocket.new(@host, @port).tap {|sock| @ios << sock} + end + + def assert_status(str, status = 400) + assert str.start_with?("HTTP/1.1 #{status}"), "'#{str[/[^\r]+/]}' should be #{status}" + end + + # ──────────────────────────────────── below are invalid Content-Length + + def test_content_length_multiple + te = [ + 'Content-Length: 5', + 'Content-Length: 5' + ].join "\r\n" + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n" + + assert_status data + end + + def test_content_length_bad_characters_1 + te = 'Content-Length: 5.01' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n" + + assert_status data + end + + def test_content_length_bad_characters_2 + te = 'Content-Length: +5' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n" + + assert_status data + end + + def test_content_length_bad_characters_3 + te = 'Content-Length: 5 test' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n" + + assert_status data + end + + # ──────────────────────────────────── below are invalid Transfer-Encoding + + def test_transfer_encoding_chunked_not_last + te = [ + 'Transfer-Encoding: chunked', + 'Transfer-Encoding: gzip' + ].join "\r\n" + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" + + assert_status data + end + + def test_transfer_encoding_chunked_multiple + te = [ + 'Transfer-Encoding: chunked', + 'Transfer-Encoding: gzip', + 'Transfer-Encoding: chunked' + ].join "\r\n" + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" + + assert_status data + end + + def test_transfer_encoding_invalid_single + te = 'Transfer-Encoding: xchunked' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" + + assert_status data, 501 + end + + def test_transfer_encoding_invalid_multiple + te = [ + 'Transfer-Encoding: x_gzip', + 'Transfer-Encoding: gzip', + 'Transfer-Encoding: chunked' + ].join "\r\n" + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" + + assert_status data, 501 + end + + def test_transfer_encoding_single_not_chunked + te = 'Transfer-Encoding: gzip' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" + + assert_status data + end + + # ──────────────────────────────────── below are invalid chunked size + + def test_chunked_size_bad_characters_1 + te = 'Transfer-Encoding: chunked' + chunked ='5.01' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n" + + assert_status data + end + + def test_chunked_size_bad_characters_2 + te = 'Transfer-Encoding: chunked' + chunked ='+5' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n" + + assert_status data + end + + def test_chunked_size_bad_characters_3 + te = 'Transfer-Encoding: chunked' + chunked ='5 bad' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n" + + assert_status data + end + + def test_chunked_size_bad_characters_4 + te = 'Transfer-Encoding: chunked' + chunked ='0xA' + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHelloHello\r\n0\r\n\r\n" + + assert_status data + end + + # size is less than bytesize + def test_chunked_size_mismatch_1 + te = 'Transfer-Encoding: chunked' + chunked = + "5\r\nHello\r\n" \ + "4\r\nWorld\r\n" \ + "0" + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{chunked}\r\n\r\n" + + assert_status data + end + + # size is greater than bytesize + def test_chunked_size_mismatch_2 + te = 'Transfer-Encoding: chunked' + chunked = + "5\r\nHello\r\n" \ + "6\r\nWorld\r\n" \ + "0" + + data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{chunked}\r\n\r\n" + + assert_status data + end +end
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- github.com/advisories/GHSA-h99w-9q5r-gjq9ghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/F6YWGIIKL7KKTS3ZOAYMYPC7D6WQ5OA5/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/L7NESIBFCNSR3XH7LXDPKVMSUBNUB43G/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/TUBFJ44NCKJ34LECZRAP4N5VL6USJSIB/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2022-24790ghsaADVISORY
- security.gentoo.org/glsa/202208-28ghsavendor-advisoryx_refsource_GENTOOWEB
- www.debian.org/security/2022/dsa-5146ghsavendor-advisoryx_refsource_DEBIANWEB
- github.com/puma/puma/commit/5bb7d202e24dec00a898dca4aa11db391d7787a5ghsax_refsource_MISCWEB
- github.com/puma/puma/security/advisories/GHSA-h99w-9q5r-gjq9ghsax_refsource_CONFIRMWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/puma/CVE-2022-24790.ymlghsaWEB
- lists.debian.org/debian-lts-announce/2022/08/msg00015.htmlghsamailing-listx_refsource_MLISTWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/F6YWGIIKL7KKTS3ZOAYMYPC7D6WQ5OA5ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/L7NESIBFCNSR3XH7LXDPKVMSUBNUB43GghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/TUBFJ44NCKJ34LECZRAP4N5VL6USJSIBghsaWEB
- portswigger.net/web-security/request-smugglingghsaWEB
News mentions
0No linked articles in our index yet.