CVE-2021-41136 (published 2021-10-12T16:15Z)

Puma is a HTTP 1.1 server for Ruby/Rack applications. Prior to versions 5.5.1 and 4.3.9, using `puma` with a proxy which forwards HTTP header values which contain the LF character could allow HTTP request smugggling. A client could smuggle a request through a proxy, causing the proxy to send a response back to another unknown client. The only proxy which has this behavior, as far as the Puma team is aware of, is Apache Traffic Server. If the proxy uses persistent connections and the client adds another request in via HTTP pipelining, the proxy may mistake it as the first request's body. Puma, however, would see it as two requests, and when processing the second request, send back a response that the proxy does not expect. If the proxy has reused the persistent connection to Puma to send another request for a different client, the second response from the first client will be sent to the second client. This vulnerability was patched in Puma 5.5.1 and 4.3.9. As a workaround, do not use Apache Traffic Server with `puma`.

Severity: LOW
CVSS v2 base score: 3.6 (AV:N/AC:H/Au:S/C:P/I:P/A:N)
CVSS v3 base score: 3.7, LOW (CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-444
cwe_name: Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')
description: 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.
extended_description: 
	   HTTP requests or responses ("messages") can be
	   malformed or unexpected in ways that cause web servers or
	   clients to interpret the messages in different ways than
	   intermediary HTTP agents such as load balancers, reverse
	   proxies, web caching proxies, application firewalls,
	   etc. For example, an adversary may be able to add duplicate
	   or different header fields that a client or server might
	   interpret as one set of messages, whereas the intermediary
	   might interpret the same sequence of bytes as a different
	   set of messages. For example, discrepancies can arise in
	   how to handle duplicate headers like two Transfer-encoding
	   (TE) or two Content-length (CL), or the malicious HTTP
	   message will have different headers for TE and
	   CL.
	   The inconsistent parsing and interpretation of messages
	   can allow the adversary to "smuggle" a message to the
	   client/server without the intermediary being aware of it.
	   This weakness is usually the result of the usage
	   of outdated or incompatible HTTP protocol versions in the
	   HTTP agents.
         
         

Fix information:
repo_url: https://github.com/puma/puma
commit_details: repo_url: https://github.com/puma/puma
author: Nate Berkopec
author_date: 2021-10-12 08:38:40-06:00
msg: Merge pull request from GHSA-48w2-rm65-62xx

* Fix HTTP request smuggling vulnerability

See GHSA-48w2-rm65-62xx or CVE-2021-41136 for more info.

* 4.3.9 release note

* 5.5.1 release note

* 5.5.1
repository: repo_url: https://github.com/puma/puma
repo_name: puma/puma
description: A Ruby/Rack web server built for parallelism
repo_language: Ruby
owner: puma
file_changes: filename: History.md
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,8 @@
+## 5.5.1 / 2021-10-12
+
+* Security
+  * Do not allow LF as a line ending in a header (CVE-2021-41136)
+
 ## 5.5.0 / 2021-09-19
 
 * Features
@@ -251,6 +256,11 @@
   * Support parallel tests in verbose progress reporting ([#2223])
   * Refactor error handling in server accept loop ([#2239])
 
+## 4.3.9 / 2021-10-12
+
+* Security
+  * Do not allow LF as a line ending in a header (CVE-2021-41136)
+
 ## 4.3.8 / 2021-05-11
 
 * Security

programming_language: Markdown
filename: http11_parser.c
change_type: ModificationType.MODIFY
diff: @@ -426,10 +426,13 @@ st18:
 case 18:
 #line 428 "ext/puma_http11/http11_parser.c"
 	switch( (*p) ) {
+		case 9: goto tr25;
 		case 13: goto tr26;
 		case 32: goto tr27;
 	}
-	goto tr25;
+	if ( 33 <= (*p) && (*p) <= 126 )
+		goto tr25;
+	goto st0;
 tr25:
 #line 46 "ext/puma_http11/http11_parser.rl"
 	{ MARK(mark, p); }
@@ -438,10 +441,14 @@ st19:
 	if ( ++p == pe )
 		goto _test_eof19;
 case 19:
-#line 442 "ext/puma_http11/http11_parser.c"
-	if ( (*p) == 13 )
-		goto tr29;
-	goto st19;
+#line 445 "ext/puma_http11/http11_parser.c"
+	switch( (*p) ) {
+		case 9: goto st19;
+		case 13: goto tr29;
+	}
+	if ( 32 <= (*p) && (*p) <= 126 )
+		goto st19;
+	goto st0;
 tr9:
 #line 53 "ext/puma_http11/http11_parser.rl"
 	{
@@ -484,7 +491,7 @@ st20:
 	if ( ++p == pe )
 		goto _test_eof20;
 case 20:
-#line 488 "ext/puma_http11/http11_parser.c"
+#line 495 "ext/puma_http11/http11_parser.c"
 	switch( (*p) ) {
 		case 32: goto tr31;
 		case 60: goto st0;
@@ -505,7 +512,7 @@ st21:
 	if ( ++p == pe )
 		goto _test_eof21;
 case 21:
-#line 509 "ext/puma_http11/http11_parser.c"
+#line 516 "ext/puma_http11/http11_parser.c"
 	switch( (*p) ) {
 		case 32: goto tr33;
 		case 60: goto st0;
@@ -526,7 +533,7 @@ st22:
 	if ( ++p == pe )
 		goto _test_eof22;
 case 22:
-#line 530 "ext/puma_http11/http11_parser.c"
+#line 537 "ext/puma_http11/http11_parser.c"
 	switch( (*p) ) {
 		case 43: goto st22;
 		case 58: goto st23;
@@ -551,7 +558,7 @@ st23:
 	if ( ++p == pe )
 		goto _test_eof23;
 case 23:
-#line 555 "ext/puma_http11/http11_parser.c"
+#line 562 "ext/puma_http11/http11_parser.c"
 	switch( (*p) ) {
 		case 32: goto tr8;
 		case 34: goto st0;
@@ -571,7 +578,7 @@ st24:
 	if ( ++p == pe )
 		goto _test_eof24;
 case 24:
-#line 575 "ext/puma_http11/http11_parser.c"
+#line 582 "ext/puma_http11/http11_parser.c"
 	switch( (*p) ) {
 		case 32: goto tr37;
 		case 34: goto st0;
@@ -594,7 +601,7 @@ st25:
 	if ( ++p == pe )
 		goto _test_eof25;
 case 25:
-#line 598 "ext/puma_http11/http11_parser.c"
+#line 605 "ext/puma_http11/http11_parser.c"
 	switch( (*p) ) {
 		case 32: goto tr41;
 		case 34: goto st0;
@@ -614,7 +621,7 @@ st26:
 	if ( ++p == pe )
 		goto _test_eof26;
 case 26:
-#line 618 "ext/puma_http11/http11_parser.c"
+#line 625 "ext/puma_http11/http11_parser.c"
 	switch( (*p) ) {
 		case 32: goto tr44;
 		case 34: goto st0;

programming_language: C
filename: http11_parser_common.rl
change_type: ModificationType.MODIFY
diff: @@ -43,7 +43,7 @@
 
   field_name = ( token -- ":" )+ >start_field $snake_upcase_field %write_field;
 
-  field_value = any* >start_value %write_value;
+  field_value = ( print | "\t" )* >start_value %write_value;
 
   message_header = field_name ":" " "* field_value :> CRLF;
 

programming_language: C
filename: Http11Parser.java
change_type: ModificationType.MODIFY
diff: @@ -34,9 +34,9 @@ private static short[] init__puma_parser_key_offsets_0()
 {
 	return new short [] {
 	    0,    0,    8,   17,   27,   29,   30,   31,   32,   33,   34,   36,
-	   39,   41,   44,   45,   61,   62,   78,   80,   81,   89,   97,  107,
-	  115,  124,  132,  140,  149,  158,  167,  176,  185,  194,  203,  212,
-	  221,  230,  239,  248,  257,  266,  275,  284,  293,  302,  303
+	   39,   41,   44,   45,   61,   62,   78,   83,   87,   95,  103,  113,
+	  121,  130,  138,  146,  155,  164,  173,  182,  191,  200,  209,  218,
+	  227,  236,  245,  254,  263,  272,  281,  290,  299,  308,  309
 	};
 }
 
@@ -52,14 +52,13 @@ private static char[] init__puma_parser_trans_keys_0()
 	   46,   48,   57,   48,   57,   13,   48,   57,   10,   13,   33,  124,
 	  126,   35,   39,   42,   43,   45,   46,   48,   57,   65,   90,   94,
 	  122,   10,   33,   58,  124,  126,   35,   39,   42,   43,   45,   46,
-	   48,   57,   65,   90,   94,  122,   13,   32,   13,   32,   60,   62,
-	  127,    0,   31,   34,   35,   32,   60,   62,  127,    0,   31,   34,
-	   35,   43,   58,   45,   46,   48,   57,   65,   90,   97,  122,   32,
-	   34,   35,   60,   62,  127,    0,   31,   32,   34,   35,   60,   62,
-	   63,  127,    0,   31,   32,   34,   35,   60,   62,  127,    0,   31,
-	   32,   34,   35,   60,   62,  127,    0,   31,   32,   36,   95,   45,
-	   46,   48,   57,   65,   90,   32,   36,   95,   45,   46,   48,   57,
-	   65,   90,   32,   36,   95,   45,   46,   48,   57,   65,   90,   32,
+	   48,   57,   65,   90,   94,  122,    9,   13,   32,   33,  126,    9,
+	   13,   32,  126,   32,   60,   62,  127,    0,   31,   34,   35,   32,
+	   60,   62,  127,    0,   31,   34,   35,   43,   58,   45,   46,   48,
+	   57,   65,   90,   97,  122,   32,   34,   35,   60,   62,  127,    0,
+	   31,   32,   34,   35,   60,   62,   63,  127,    0,   31,   32,   34,
+	   35,   60,   62,  127,    0,   31,   32,   34,   35,   60,   62,  127,
+	    0,   31,   32,   36,   95,   45,   46,   48,   57,   65,   90,   32,
 	   36,   95,   45,   46,   48,   57,   65,   90,   32,   36,   95,   45,
 	   46,   48,   57,   65,   90,   32,   36,   95,   45,   46,   48,   57,
 	   65,   90,   32,   36,   95,   45,   46,   48,   57,   65,   90,   32,
@@ -71,7 +70,8 @@ private static char[] init__puma_parser_trans_keys_0()
 	   65,   90,   32,   36,   95,   45,   46,   48,   57,   65,   90,   32,
 	   36,   95,   45,   46,   48,   57,   65,   90,   32,   36,   95,   45,
 	   46,   48,   57,   65,   90,   32,   36,   95,   45,   46,   48,   57,
-	   65,   90,   32,    0
+	   65,   90,   32,   36,   95,   45,   46,   48,   57,   65,   90,   32,
+	   36,   95,   45,   46,   48,   57,   65,   90,   32,    0
 	};
 }
 
@@ -82,7 +82,7 @@ private static byte[] init__puma_parser_single_lengths_0()
 {
 	return new byte [] {
 	    0,    2,    3,    4,    2,    1,    1,    1,    1,    1,    0,    1,
-	    0,    1,    1,    4,    1,    4,    2,    1,    4,    4,    2,    6,
+	    0,    1,    1,    4,    1,    4,    3,    2,    4,    4,    2,    6,
 	    7,    6,    6,    3,    3,    3,    3,    3,    3,    3,    3,    3,
 	    3,    3,    3,    3,    3,    3,    3,    3,    3,    1,    0
 	};
@@ -95,7 +95,7 @@ private static byte[] init__puma_parser_range_lengths_0()
 {
 	return new byte [] {
 	    0,    3,    3,    3,    0,    0,    0,    0,    0,    0,    1,    1,
-	    1,    1,    0,    6,    0,    6,    0,    0,    2,    2,    4,    1,
+	    1,    1,    0,    6,    0,    6,    1,    1,    2,    2,    4,    1,
 	    1,    1,    1,    3,    3,    3,    3,    3,    3,    3,    3,    3,
 	    3,    3,    3,    3,    3,    3,    3,    3,    3,    0,    0
 	};
@@ -108,9 +108,9 @@ private static short[] init__puma_parser_index_offsets_0()
 {
 	return new short [] {
 	    0,    0,    6,   13,   21,   24,   26,   28,   30,   32,   34,   36,
-	   39,   41,   44,   46,   57,   59,   70,   73,   75,   82,   89,   96,
-	  104,  113,  121,  129,  136,  143,  150,  157,  164,  171,  178,  185,
-	  192,  199,  206,  213,  220,  227,  234,  241,  248,  255,  257
+	   39,   41,   44,   46,   57,   59,   70,   75,   79,   86,   93,  100,
+	  108,  117,  125,  133,  140,  147,  154,  161,  168,  175,  182,  189,
+	  196,  203,  210,  217,  224,  231,  238,  245,  252,  259,  261
 	};
 }
 
@@ -125,23 +125,23 @@ private static byte[] init__puma_parser_indicies_0()
 	   10,    1,   11,    1,   12,    1,   13,    1,   14,    1,   15,    1,
 	   16,   15,    1,   17,    1,   18,   17,    1,   19,    1,   20,   21,
 	   21,   21,   21,   21,   21,   21,   21,   21,    1,   22,    1,   23,
-	   24,   23,   23,   23,   23,   23,   23,   23,   23,    1,   26,   27,
-	   25,   29,   28,   30,    1,    1,    1,    1,    1,   31,   32,    1,
-	    1,    1,    1,    1,   33,   34,   35,   34,   34,   34,   34,    1,
-	    8,    1,    9,    1,    1,    1,    1,   35,   36,    1,   38,    1,
-	    1,   39,    1,    1,   37,   40,    1,   42,    1,    1,    1,    1,
-	   41,   43,    1,   45,    1,    1,    1,    1,   44,    2,   46,   46,
-	   46,   46,   46,    1,    2,   47,   47,   47,   47,   47,    1,    2,
-	   48,   48,   48,   48,   48,    1,    2,   49,   49,   49,   49,   49,
-	    1,    2,   50,   50,   50,   50,   50,    1,    2,   51,   51,   51,
-	   51,   51,    1,    2,   52,   52,   52,   52,   52,    1,    2,   53,
-	   53,   53,   53,   53,    1,    2,   54,   54,   54,   54,   54,    1,
-	    2,   55,   55,   55,   55,   55,    1,    2,   56,   56,   56,   56,
-	   56,    1,    2,   57,   57,   57,   57,   57,    1,    2,   58,   58,
-	   58,   58,   58,    1,    2,   59,   59,   59,   59,   59,    1,    2,
-	   60,   60,   60,   60,   60,    1,    2,   61,   61,   61,   61,   61,
-	    1,    2,   62,   62,   62,   62,   62,    1,    2,   63,   63,   63,
-	   63,   63,    1,    2,    1,    1,    0
+	   24,   23,   23,   23,   23,   23,   23,   23,   23,    1,   25,   26,
+	   27,   25,    1,   28,   29,   28,    1,   30,    1,    1,    1,    1,
+	    1,   31,   32,    1,    1,    1,    1,    1,   33,   34,   35,   34,
+	   34,   34,   34,    1,    8,    1,    9,    1,    1,    1,    1,   35,
+	   36,    1,   38,    1,    1,   39,    1,    1,   37,   40,    1,   42,
+	    1,    1,    1,    1,   41,   43,    1,   45,    1,    1,    1,    1,
+	   44,    2,   46,   46,   46,   46,   46,    1,    2,   47,   47,   47,
+	   47,   47,    1,    2,   48,   48,   48,   48,   48,    1,    2,   49,
+	   49,   49,   49,   49,    1,    2,   50,   50,   50,   50,   50,    1,
+	    2,   51,   51,   51,   51,   51,    1,    2,   52,   52,   52,   52,
+	   52,    1,    2,   53,   53,   53,   53,   53,    1,    2,   54,   54,
+	   54,   54,   54,    1,    2,   55,   55,   55,   55,   55,    1,    2,
+	   56,   56,   56,   56,   56,    1,    2,   57,   57,   57,   57,   57,
+	    1,    2,   58,   58,   58,   58,   58,    1,    2,   59,   59,   59,
+	   59,   59,    1,    2,   60,   60,   60,   60,   60,    1,    2,   61,
+	   61,   61,   61,   61,    1,    2,   62,   62,   62,   62,   62,    1,
+	    2,   63,   63,   63,   63,   63,    1,    2,    1,    1,    0
 	};
 }
 

programming_language: Rust
filename: const.rb
change_type: ModificationType.MODIFY
diff: @@ -100,7 +100,7 @@ module Puma
   # too taxing on performance.
   module Const
 
-    PUMA_VERSION = VERSION = "5.5.0".freeze
+    PUMA_VERSION = VERSION = "5.5.1".freeze
     CODE_NAME = "Zawgyi".freeze
 
     PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze

programming_language: Ruby
filename: test_http11.rb
change_type: ModificationType.MODIFY
diff: @@ -208,4 +208,34 @@ class Http11ParserTest < Minitest::Test
 
     assert_equal "Strip This", req["HTTP_X_STRIP_ME"]
   end
+
+  def test_newline_smuggler
+    parser = Puma::HttpParser.new
+    req = {}
+    http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: x\nDummy2: y\r\n\r\n"
+
+    parser.execute(req, http, 0) rescue nil # We test the raise elsewhere.
+
+    assert parser.error?, "Parser SHOULD have error"
+  end
+
+  def test_newline_smuggler_two
+    parser = Puma::HttpParser.new
+    req = {}
+    http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: x\r\nDummy: y\nDummy2: z\r\n\r\n"
+
+    parser.execute(req, http, 0) rescue nil
+
+    assert parser.error?, "Parser SHOULD have error"
+  end
+
+  def test_htab_in_header_val
+    parser = Puma::HttpParser.new
+    req = {}
+    http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: Valid\tValue\r\n\r\n"
+
+    parser.execute(req, http, 0)
+
+    assert_equal "Valid\tValue", req['HTTP_DUMMY']
+  end
 end

programming_language: Ruby