November 22, 2022·5 min read·engineeringrubysecurityhttpcgi

Ruby CGI validates response headers and cookie attributes

CVE-2021-33621 is disclosed with patched cgi releases. The fixes reject line breaks in response fields and validate cookie names, paths and domains before serialization.

A string inserted into an HTTP header occupies a different syntactic position from text placed in a response body. Line breaks can change the structure of the response instead of remaining part of one field. Ruby's CGI security update adds checks at that output boundary, where an application turns values into protocol text.

The November 22 advisory for CVE-2021-33621 announces fixes in cgi 0.3.5, 0.2.2 and 0.1.0.2. An application generating responses from untrusted input could allow injected headers or body content. The notice also describes insufficient validation of CGI::Cookie contents and includes preventive checks for its initialization arguments.

I find Ruby's readable interfaces helpful, but readability does not remove the grammar of the format an API emits. A hash entry that looks like an ordinary string can become a header field. The output routine needs to preserve the distinction between that field's value and the separators that delimit the response.

Validation belongs where fields become protocol text

The cgi 0.3.5 header implementation defines a helper that converts a supplied value to a string and rejects either carriage return or line feed. It applies that check when constructing status and header fields. The exception makes invalid output fail before the field is appended to the generated response.

The same helper appears in both the simple content-type path and the hash-based path. That coverage matters because a response API can provide several convenient input forms. Repairing only the path used by one example would leave other forms with different structural guarantees.

Custom headers receive checks on both their names and values. Common fields, including content type and content length, also pass through validation. The implementation's purpose is to keep an input string from contributing a line separator inside a field that is supposed to occupy one line.

The check is not an HTML escaping operation. HTML and an HTTP header have different grammars, and this failure occurs while constructing the response fields. Replacing characters for presentation inside an HTML document would not describe what this header validation actually does.

This is a useful distinction when examining an application's data flow. A value may be safe to display as escaped body text while remaining inappropriate for a header. The serialization context determines which characters can change structure and therefore what the library needs to reject.

The fixed cookie implementation validates a cookie's name, path and domain through setters. Construction calls those setters, so initialization receives the same validation as later assignment through the public methods.

The name check uses a token-oriented character rule. The path rule excludes the semicolon separator, and the domain rule checks its label structure and length. These are different checks because those components occupy different positions in the serialized cookie.

The serialization routine escapes the cookie's values before joining them. It then appends attributes such as domain and path separately. That explains why escaping a value is insufficient to protect an unvalidated attribute. The value's escaping does not transform the text later appended as the path or domain.

A small example shows the boundary without constructing an injected response. The path setter validates assignment after the object has already been initialized.

require "cgi"

cookie = CGI::Cookie.new("name" => "session", "value" => "abc")
cookie.path = "/account"

begin
  cookie.path = "/account;extra"
rescue ArgumentError
  puts "invalid cookie path"
end

The rejected assignment contains a separator that the path rule does not permit. The program receives an explicit invalid-input result instead of serializing the supplied string as though it were a valid path. This demonstrates the fixed setter's behaviour, with the ordinary cookie value remaining independent of that attribute.

The advisory separates likelihood from the repair

The notice says that applications creating cookie objects from the relevant untrusted inputs are considered unlikely. It still includes the initialization checks preventively. That is a measured statement about expected application patterns, not evidence that the API cannot be reached with external input.

The response-generation issue has its own condition. An application must use the CGI gem to generate responses with untrusted user input reaching the affected fields. Merely having Ruby installed does not establish that data flow. Conversely, an application's assumption that callers usually supply conventional values does not replace checks at a protocol boundary.

The cookie changes also show why constructor validation matters even when a class has named attributes. A caller can provide a hash that looks structured, but the strings inside it still need validation for their eventual output positions. Giving the fields names does not itself constrain their contents.

I consider those checks an appropriate responsibility for the serialization library. The library knows which separators and component rules it uses. Application-specific decisions about which path or domain should be allowed can remain additional constraints, while the library rejects values that cannot represent its fields correctly.

Updating the gem is the published remediation

The advisory recommends updating cgi directly and supplies the command. This updates the package that implements response and cookie construction.

gem update cgi

For Bundler-managed applications it recommends adding a cgi requirement of at least 0.3.5 to the Gemfile. The notice also names fixed releases on the other maintained gem lines. Those are cgi package versions, so the remediation should not be reduced to checking only the Ruby interpreter's patch number.

The CVE identifier contains an earlier year, while the advisory records publication at 02:00 UTC on November 22. The disclosure date is the relevant date for this announcement. Its fixed code supplies the concrete explanation, rejecting response field line breaks and validating cookie attributes through the same setters used at construction and later assignment.

The header implementation also handles cookies supplied in several forms, including individual objects and collections. It checks the resulting serialized cookie string before adding the Set-Cookie field. That output check and the component setters address different layers of the same construction. Component validation rejects an invalid attribute, while the field-level check prevents a line break from being included in the final header text. Both layers are visible in the fixed source.