Blob Blame History Raw
From 3f591af1e74ec511e38bd40afc9ebbceacdc9fef Mon Sep 17 00:00:00 2001
From: usa <usa@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>
Date: Wed, 28 Mar 2018 14:50:27 +0000
Subject: [PATCH] webrick: prevent response splitting and header injection

Original patch by tenderlove (with minor style adjustments).

* lib/webrick/httpresponse.rb (send_header): call check_header
  (check_header): raise on embedded CRLF in header value
* test/webrick/test_httpresponse.rb
  (test_prevent_response_splitting_headers): new test
* (test_prevent_response_splitting_cookie_headers): ditto

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_2_2@63022 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
---
 lib/webrick/httpresponse.rb       | 27 +++++++++++++++++++++++++--
 test/webrick/test_httpresponse.rb | 23 +++++++++++++++++++++++
 2 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb
index 8e3eb39a31..11cc78d845 100644
--- a/lib/webrick/httpresponse.rb
+++ b/lib/webrick/httpresponse.rb
@@ -20,6 +20,8 @@ module WEBrick
   # WEBrick HTTP Servlet.
 
   class HTTPResponse
+    class InvalidHeader < StandardError
+    end
 
     ##
     # HTTP Response version
@@ -285,14 +287,19 @@ module WEBrick
         data = status_line()
         @header.each{|key, value|
           tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
-          data << "#{tmp}: #{value}" << CRLF
+          data << "#{tmp}: #{check_header(value)}" << CRLF
         }
         @cookies.each{|cookie|
-          data << "Set-Cookie: " << cookie.to_s << CRLF
+          data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
         }
         data << CRLF
         _write_data(socket, data)
       end
+    rescue InvalidHeader => e
+      @header.clear
+      @cookies.clear
+      set_error e
+      retry
     end
 
     ##
@@ -349,6 +356,22 @@ module WEBrick
         host, port = @config[:ServerName], @config[:Port]
       end
 
+      error_body(backtrace, ex, host, port)
+    end
+
+    private
+
+    def check_header(header_value)
+      if header_value =~ /\r\n/
+        raise InvalidHeader
+      else
+        header_value
+      end
+    end
+
+    # :stopdoc:
+
+    def error_body(backtrace, ex, host, port)
       @body = ''
       @body << <<-_end_of_html_
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
diff --git a/test/webrick/test_httpresponse.rb b/test/webrick/test_httpresponse.rb
index d5d5552796..bdf38e6b5c 100644
--- a/test/webrick/test_httpresponse.rb
+++ b/test/webrick/test_httpresponse.rb
@@ -1,5 +1,7 @@
 require "webrick"
 require "minitest/autorun"
+require "stringio"
+require "net/http"
 
 module WEBrick
   class TestHTTPResponse < MiniTest::Unit::TestCase
@@ -26,6 +28,27 @@ module WEBrick
       @res.keep_alive  = true
     end
 
+    def test_prevent_response_splitting_headers
+      res['X-header'] = "malicious\r\nCookie: hack"
+      io = StringIO.new
+      res.send_response io
+      io.rewind
+      res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
+      assert_equal '500', res.code
+      refute_match 'hack', io.string
+    end
+
+    def test_prevent_response_splitting_cookie_headers
+      user_input = "malicious\r\nCookie: hack"
+      res.cookies << WEBrick::Cookie.new('author', user_input)
+      io = StringIO.new
+      res.send_response io
+      io.rewind
+      res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
+      assert_equal '500', res.code
+      refute_match 'hack', io.string
+    end
+
     def test_304_does_not_log_warning
       res.status      = 304
       res.setup_header
-- 
2.17.1