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