VYPR
Moderate severityNVD Advisory· Published Feb 28, 2020· Updated Aug 4, 2024

HTTP Response Splitting in Puma

CVE-2020-5247

Description

In Puma (RubyGem) before 4.3.2 and before 3.12.3, if an application using Puma allows untrusted input in a response header, an attacker can use newline characters (i.e. CR, LF or/r, /n) to end the header and inject malicious content, such as additional headers or an entirely new response body. This vulnerability is known as HTTP Response Splitting. While not an attack in itself, response splitting is a vector for several other attacks, such as cross-site scripting (XSS). This is related to CVE-2019-16254, which fixed this vulnerability for the WEBrick Ruby web server. This has been fixed in versions 4.3.2 and 3.12.3 by checking all headers for line endings and rejecting headers with those characters.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Puma HTTP response splitting vulnerability allows injection of arbitrary headers/body via newline characters in untrusted response header values.

Vulnerability

CVE-2020-5247 is an HTTP response splitting vulnerability in the Puma Ruby web server, affecting versions before 4.3.2 and before 3.12.3. The root cause is that Puma did not validate response header values for newline characters (CR, LF, or \r, \n). If an application using Puma includes untrusted input in a response header, an attacker can terminate the header early and inject arbitrary content, such as additional headers or an entirely new response body. This is akin to the vulnerability addressed in CVE-2019-16254 for the WEBrick Ruby web server [1][2].

Exploitation

Exploitation requires the target application to reflect untrusted user input into an HTTP response header without sanitization. The attacker sends a request with malicious newline characters embedded in a parameter that will be echoed back in a header. The underlying HTTP parser in Puma would then process those characters as line breaks, allowing the attacker to control the remainder of the HTTP response [2][3]. No authentication is needed; the attack is purely through crafting malicious HTTP requests [2].

Impact

A successful response splitting attack can be used to conduct cross-site scripting (XSS), web cache poisoning, or cross-user defacement. By injecting a complete fake response body, an attacker can serve malicious scripts to the victim's browser under the origin of the vulnerable application [2][3]. This can lead to session theft, credential harvesting, or other client-side attacks.

Mitigation

The vulnerability has been fixed in Puma versions 4.3.2 and 3.12.3. The fix adds a check for carriage return and line feed characters (CRLF_REGEX = /[\r\n]/) and rejects any headers containing those characters [4]. Users should upgrade to the patched versions immediately. If upgrading is not possible, ensure that any untrusted input is thoroughly sanitized before being placed into response headers, and consider using a WAF to block requests containing CR or LF characters in header values.

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.

PackageAffected versionsPatched versions
pumaRubyGems
< 3.12.43.12.4
pumaRubyGems
>= 4.0.0, < 4.3.34.3.3

Affected products

244

Patches

1
c36491756f68

Merge pull request from GHSA-84j7-475p-hp8v

https://github.com/puma/pumaNate BerkopecFeb 27, 2020via ghsa
8 files changed · +55 1
  • benchmarks/wrk/hello.sh+1 1 modified
    @@ -3,6 +3,6 @@
     bundle exec bin/puma -t 4 test/rackup/hello.ru &
     PID1=$!
     sleep 5
    -wrk -c 4 --latency http://localhost:9292
    +wrk -c 4 -d 30 --latency http://localhost:9292
     
     kill $PID1
    
  • benchmarks/wrk/many_long_headers.sh+6 0 added
    @@ -0,0 +1,6 @@
    +bundle exec bin/puma -t 4 test/rackup/many_long_headers.ru &
    +PID1=$!
    +sleep 5
    +wrk -c 4 -d 30 --latency http://localhost:9292
    +
    +kill $PID1
    
  • benchmarks/wrk/realistic_response.sh+6 0 added
    @@ -0,0 +1,6 @@
    +bundle exec bin/puma -t 4 test/rackup/realistic_response.ru &
    +PID1=$!
    +sleep 5
    +wrk -c 4 -d 30 --latency http://localhost:9292
    +
    +kill $PID1
    
  • lib/puma/const.rb+1 0 modified
    @@ -228,6 +228,7 @@ module Const
         COLON = ": ".freeze
     
         NEWLINE = "\n".freeze
    +    CRLF_REGEX = /[\r\n]/.freeze
     
         HIJACK_P = "rack.hijack?".freeze
         HIJACK = "rack.hijack".freeze
    
  • lib/puma/server.rb+2 0 modified
    @@ -686,6 +686,8 @@ def handle_request(req, lines)
               status, headers, res_body = @app.call(env)
     
               return :async if req.hijacked
    +          # Checking to see if an attacker is trying to inject headers into the response
    +          headers.reject! { |_k, v| CRLF_REGEX =~ v.to_s }
     
               status = status.to_i
     
    
  • test/rackup/many_long_headers.ru+9 0 added
    @@ -0,0 +1,9 @@
    +require 'securerandom'
    +
    +long_header_hash = {}
    +
    +30.times do |i|
    +  long_header_hash["X-My-Header-#{i}"] = SecureRandom.hex(1000)
    +end
    +
    +run lambda { |env| [200, long_header_hash, ["Hello World"]] }
    
  • test/rackup/realistic_response.ru+11 0 added
    @@ -0,0 +1,11 @@
    +require 'securerandom'
    +
    +long_header_hash = {}
    +
    +25.times do |i|
    +  long_header_hash["X-My-Header-#{i}"] = SecureRandom.hex(25)
    +end
    +
    +response = SecureRandom.hex(100_000) # A 100kb document
    +
    +run lambda { |env| [200, long_header_hash.dup, [response.dup]] }
    
  • test/test_puma_server.rb+19 0 modified
    @@ -771,4 +771,23 @@ def test_open_connection_wait_no_queue
         @server = Puma::Server.new @app, @events, queue_requests: false
         test_open_connection_wait
       end
    +
    +  # https://github.com/ruby/ruby/commit/d9d4a28f1cdd05a0e8dabb36d747d40bbcc30f16
    +  def test_prevent_response_splitting_headers
    +    server_run app: ->(_) { [200, {'X-header' => "malicious\r\nCookie: hack"}, ["Hello"]] }
    +    data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
    +    refute_match 'hack', data
    +  end
    +
    +  def test_prevent_response_splitting_headers_cr
    +    server_run app: ->(_) { [200, {'X-header' => "malicious\rCookie: hack"}, ["Hello"]] }
    +    data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
    +    refute_match 'hack', data
    +  end
    +
    +  def test_prevent_response_splitting_headers_lf
    +    server_run app: ->(_) { [200, {'X-header' => "malicious\nCookie: hack"}, ["Hello"]] }
    +    data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
    +    refute_match 'hack', 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

14

News mentions

0

No linked articles in our index yet.