Projects
Eulaceura:Mainline
rubygem-puma
_service:obs_scm:CVE-2023-40175.patch
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:CVE-2023-40175.patch of Package rubygem-puma
From 7405a219801dcebc0ad6e0aa108d4319ca23f662 Mon Sep 17 00:00:00 2001 From: Nate Berkopec <nate.berkopec@gmail.com> Date: Fri, 18 Aug 2023 09:47:23 +0900 Subject: [PATCH] Merge pull request from GHSA-68xg-gqqm-vgj8 Origin: https://github.com/puma/puma/commit/7405a219801dcebc0ad6e0aa108d4319ca23f662 * Reject empty string for Content-Length * Ignore trailers in last chunk * test_puma_server.rb - use heredoc, test_cl_and_te_smuggle * client.rb - stye/RubyCop * test_puma_server.rb - indented heredoc rubocop disable * Dentarg comments * Remove unused variable --------- Co-authored-by: MSP-Greg <Greg.mpls@gmail.com> --- lib/puma/client.rb | 23 ++++++++++++++-------- test/test_puma_server.rb | 42 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/lib/puma/client.rb b/lib/puma/client.rb index e966f995e8..9c11912caa 100644 --- a/lib/puma/client.rb +++ b/lib/puma/client.rb @@ -45,7 +45,8 @@ class Client # chunked body validation CHUNK_SIZE_INVALID = /[^\h]/.freeze - CHUNK_VALID_ENDING = "\r\n".freeze + CHUNK_VALID_ENDING = Const::LINE_END + CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize # Content-Length header value validation CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze @@ -347,8 +348,8 @@ def setup_body cl = @env[CONTENT_LENGTH] if cl - # cannot contain characters that are not \d - if cl =~ CONTENT_LENGTH_VALUE_INVALID + # cannot contain characters that are not \d, or be empty + if cl =~ CONTENT_LENGTH_VALUE_INVALID || cl.empty? raise HttpParserError, "Invalid Content-Length: #{cl.inspect}" end else @@ -509,7 +510,7 @@ def decode_chunk(chunk) while !io.eof? line = io.gets - if line.end_with?("\r\n") + if line.end_with?(CHUNK_VALID_ENDING) # 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[^;]+/] @@ -521,13 +522,19 @@ def decode_chunk(chunk) @in_last_chunk = true @body.rewind rest = io.read - last_crlf_size = "\r\n".bytesize - if rest.bytesize < last_crlf_size + if rest.bytesize < CHUNK_VALID_ENDING_SIZE @buffer = nil - @partial_part_left = last_crlf_size - rest.bytesize + @partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize return false else - @buffer = rest[last_crlf_size..-1] + # if the next character is a CRLF, set buffer to everything after that CRLF + start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING) + CHUNK_VALID_ENDING_SIZE + else # we have started a trailer section, which we do not support. skip it! + rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2 + end + + @buffer = rest[start_of_rest..-1] @buffer = nil if @buffer.empty? set_ready return true diff --git a/test/test_puma_server.rb b/test/test_puma_server.rb index 298e44b439..2bfaf98848 100644 --- a/test/test_puma_server.rb +++ b/test/test_puma_server.rb @@ -627,7 +627,7 @@ def test_large_chunked_request [200, {}, [""]] } - header = "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n" + header = "GET / HTTP/1.1\r\nConnection: close\r\nContent-Length: 200\r\nTransfer-Encoding: chunked\r\n\r\n" chunk_header_size = 6 # 4fb8\r\n # Current implementation reads one chunk of CHUNK_SIZE, then more chunks of size 4096. @@ -1365,4 +1365,44 @@ def test_rack_url_scheme_user data = send_http_and_read "GET / HTTP/1.0\r\n\r\n" assert_equal "user", data.split("\r\n").last end + + def test_cl_empty_string + server_run do |env| + [200, {}, [""]] + end + + empty_cl_request = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length:\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n" + + data = send_http_and_read empty_cl_request + assert_operator data, :start_with?, 'HTTP/1.1 400 Bad Request' + end + + def test_crlf_trailer_smuggle + server_run do |env| + [200, {}, [""]] + end + + smuggled_payload = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nHost: whatever\r\n\r\n0\r\nX:POST / HTTP/1.1\r\nHost: whatever\r\n\r\nGET / HTTP/1.1\r\nHost: whatever\r\n\r\n" + + data = send_http_and_read smuggled_payload + assert_equal 2, data.scan("HTTP/1.1 200 OK").size + end + + # test to check if content-length is ignored when 'transfer-encoding: chunked' + # is used. See also test_large_chunked_request + def test_cl_and_te_smuggle + body = nil + server_run { |env| + body = env['rack.input'].read + [200, {}, [""]] + } + + req = "POST /search HTTP/1.1\r\nHost: vulnerable-website.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 4\r\nTransfer-Encoding: chunked\r\n\r\n7b\r\nGET /404 HTTP/1.1\r\nHost: vulnerable-website.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 144\r\n\r\nx=\r\n0\r\n\r\n" + + data = send_http_and_read req + + assert_includes body, "GET /404 HTTP/1.1\r\n" + assert_includes body, "Content-Length: 144\r\n" + assert_equal 1, data.scan("HTTP/1.1 200 OK").size + end end
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2