121925
From 3f591af1e74ec511e38bd40afc9ebbceacdc9fef Mon Sep 17 00:00:00 2001
121925
From: usa <usa@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>
121925
Date: Wed, 28 Mar 2018 14:50:27 +0000
121925
Subject: [PATCH] webrick: prevent response splitting and header injection
121925
121925
Original patch by tenderlove (with minor style adjustments).
121925
121925
* lib/webrick/httpresponse.rb (send_header): call check_header
121925
  (check_header): raise on embedded CRLF in header value
121925
* test/webrick/test_httpresponse.rb
121925
  (test_prevent_response_splitting_headers): new test
121925
* (test_prevent_response_splitting_cookie_headers): ditto
121925
121925
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_2_2@63022 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
121925
---
121925
 lib/webrick/httpresponse.rb       | 27 +++++++++++++++++++++++++--
121925
 test/webrick/test_httpresponse.rb | 23 +++++++++++++++++++++++
121925
 2 files changed, 48 insertions(+), 2 deletions(-)
121925
121925
diff --git a/lib/webrick/httpresponse.rb b/lib/webrick/httpresponse.rb
121925
index 8e3eb39a31..11cc78d845 100644
121925
--- a/lib/webrick/httpresponse.rb
121925
+++ b/lib/webrick/httpresponse.rb
121925
@@ -20,6 +20,8 @@ module WEBrick
121925
   # WEBrick HTTP Servlet.
121925
 
121925
   class HTTPResponse
121925
+    class InvalidHeader < StandardError
121925
+    end
121925
 
121925
     ##
121925
     # HTTP Response version
121925
@@ -285,14 +287,19 @@ module WEBrick
121925
         data = status_line()
121925
         @header.each{|key, value|
121925
           tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
121925
-          data << "#{tmp}: #{value}" << CRLF
121925
+          data << "#{tmp}: #{check_header(value)}" << CRLF
121925
         }
121925
         @cookies.each{|cookie|
121925
-          data << "Set-Cookie: " << cookie.to_s << CRLF
121925
+          data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
121925
         }
121925
         data << CRLF
121925
         _write_data(socket, data)
121925
       end
121925
+    rescue InvalidHeader => e
121925
+      @header.clear
121925
+      @cookies.clear
121925
+      set_error e
121925
+      retry
121925
     end
121925
 
121925
     ##
121925
@@ -349,6 +356,22 @@ module WEBrick
121925
         host, port = @config[:ServerName], @config[:Port]
121925
       end
121925
 
121925
+      error_body(backtrace, ex, host, port)
121925
+    end
121925
+
121925
+    private
121925
+
121925
+    def check_header(header_value)
121925
+      if header_value =~ /\r\n/
121925
+        raise InvalidHeader
121925
+      else
121925
+        header_value
121925
+      end
121925
+    end
121925
+
121925
+    # :stopdoc:
121925
+
121925
+    def error_body(backtrace, ex, host, port)
121925
       @body = ''
121925
       @body << <<-_end_of_html_
121925
 
121925
diff --git a/test/webrick/test_httpresponse.rb b/test/webrick/test_httpresponse.rb
121925
index d5d5552796..bdf38e6b5c 100644
121925
--- a/test/webrick/test_httpresponse.rb
121925
+++ b/test/webrick/test_httpresponse.rb
121925
@@ -1,5 +1,7 @@
121925
 require "webrick"
121925
 require "minitest/autorun"
121925
+require "stringio"
121925
+require "net/http"
121925
 
121925
 module WEBrick
121925
   class TestHTTPResponse < MiniTest::Unit::TestCase
121925
@@ -26,6 +28,27 @@ module WEBrick
121925
       @res.keep_alive  = true
121925
     end
121925
 
121925
+    def test_prevent_response_splitting_headers
121925
+      res['X-header'] = "malicious\r\nCookie: hack"
121925
+      io = StringIO.new
121925
+      res.send_response io
121925
+      io.rewind
121925
+      res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
121925
+      assert_equal '500', res.code
121925
+      refute_match 'hack', io.string
121925
+    end
121925
+
121925
+    def test_prevent_response_splitting_cookie_headers
121925
+      user_input = "malicious\r\nCookie: hack"
121925
+      res.cookies << WEBrick::Cookie.new('author', user_input)
121925
+      io = StringIO.new
121925
+      res.send_response io
121925
+      io.rewind
121925
+      res = Net::HTTPResponse.read_new(Net::BufferedIO.new(io))
121925
+      assert_equal '500', res.code
121925
+      refute_match 'hack', io.string
121925
+    end
121925
+
121925
     def test_304_does_not_log_warning
121925
       res.status      = 304
121925
       res.setup_header
121925
-- 
121925
2.17.1
121925