CVE-2023-38697 (Ruby, published 2023-08-04T18:15Z)
Description: value: protocol-http1 provides a low-level implementation of the HTTP/1 protocol. RFC 9112 Section 7.1 defined the format of chunk size, chunk data and chunk extension. The value of Content-Length header should be a string of 0-9 digits, the chunk size should be a string of hex digits and should split from chunk data using CRLF, and the chunk extension shouldn't contain any invisible character. However, Falcon has following behaviors while disobey the corresponding RFCs: accepting Content-Length header values that have `+` prefix, accepting Content-Length header values that written in hexadecimal with `0x` prefix, accepting `0x` and `+` prefixed chunk size, and accepting LF in chunk extension. This behavior can lead to desync when forwarding through multiple HTTP parsers, potentially results in HTTP request smuggling and firewall bypassing. This issue is fixed in `protocol-http1` v0.15.1. There are no known workarounds.
Severity: nan (CVSS v3 base score: 5.3)
Weakness: CWE-444 \u2014 Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')
The product acts as an intermediary HTTP agent
         (such as a proxy or firewall) in the data flow between two
         entities such as a client and server, but it does not
         interpret malformed HTTP requests or responses in ways that
         are consistent with how the messages will be processed by
         those entities that are at the ultimate destination.
Repository: https://github.com/socketry/protocol-http1
Files changed:
lib/protocol/http1/body/chunked.rb
lib/protocol/http1/connection.rb
test/protocol/http1/connection.rb
test/protocol/http1/connection/bad.rb
Commit message: Strict validation of content length and chunk length. (#20)



Vulnerable code:
					length = read_line.to_i(16)
					if length = Integer(content_length, exception: false) and length >= 0
						yield length
				body = client.read_response_body("HEAD", 200, {'content-length' => 3773})
				body = client.read_response_body("HEAD", 200, {'content-length' => 0})

Fixed code:
				VALID_CHUNK_LENGTH = /\A[0-9a-fA-F]+\z/
				
					length, extensions = read_line.split(";", 2)
					
					unless length =~ VALID_CHUNK_LENGTH
						raise BadRequest, "Invalid chunk length: #{length.dump}"
					end
					
					length = Integer(length, 16)
			VALID_CONTENT_LENGTH = /\A\d+\z/
			
					if content_length =~ VALID_CONTENT_LENGTH
						yield Integer(content_length, 10)
				body = client.read_response_body("HEAD", 200, {'content-length' => '3773'})
				body = client.read_response_body("HEAD", 200, {'content-length' => '0'})
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2023, by Samuel Williams.

require 'protocol/http1/connection'
require 'connection_context'

describe Protocol::HTTP1::Connection do
	include_context ConnectionContext
	
	def before
		super
		
		client.stream.write(input)
		client.stream.close
	end
	
	with "invalid hexadecimal content-length" do
		def input
			<<~HTTP.gsub("\n", "\r\n")
			POST / HTTP/1.1
			Host: a.com
			Content-Length: 0x10
			Connection: close
			
			0123456789abcdef
			HTTP
		end
		
		it "should fail to parse the request body" do
			expect do
				server.read_request
			end.to raise_exception(Protocol::HTTP1::BadRequest)
		end
	end
	
	with "invalid +integer content-length" do
		def input
			<<~HTTP.gsub("\n", "\r\n")
			POST / HTTP/1.1
			Host: a.com
			Content-Length: +16
			Connection: close
			
			0123456789abcdef
			HTTP
		end
		
		it "should fail to parse the request body" do
			expect do
				server.read_request
			end.to raise_exception(Protocol::HTTP1::BadRequest)
		end
	end
	
	with "invalid -integer content-length" do
		def input
			<<~HTTP.gsub("\n", "\r\n")
			POST / HTTP/1.1
			Host: a.com
			Content-Length: -16
			Connection: close
			
			0123456789abcdef
			HTTP
		end
		
		it "should fail to parse the request body" do
			expect do
				server.read_request
			end.to raise_exception(Protocol::HTTP1::BadRequest)
		end
	end
	
	with "invalid hexidecimal chunk size" do
		def input
			<<~HTTP.gsub("\n", "\r\n")
			POST / HTTP/1.1
			Host: a.com
			Transfer-Encoding: chunked
			Connection: close
			
			0x10
			0123456789abcdef
			0
			HTTP
		end
		
		it "should fail to parse the request body" do
			authority, method, target, version, headers, body = server.read_request
			
			expect(body).to be_a(Protocol::HTTP1::Body::Chunked)
			
			expect do
				body.read
			end.to raise_exception(Protocol::HTTP1::BadRequest)
		end
	end
	
	with "invalid +integer chunk size" do
		def input
			<<~HTTP.gsub("\n", "\r\n")
			POST / HTTP/1.1
			Host: a.com
			Transfer-Encoding: chunked
			Connection: close
			
			+10
			0123456789abcdef
			0
			HTTP
		end
		
		it "should fail to parse the request body" do
			authority, method, target, version, headers, body = server.read_request
			
			expect(body).to be_a(Protocol::HTTP1::Body::Chunked)
			
			expect do
				body.read
			end.to raise_exception(Protocol::HTTP1::BadRequest)
		end
	end
end