1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
| | Fix CVE-2017-17742:
https://www.ruby-lang.org/en/news/2018/03/28/http-response-splitting-in-webrick-cve-2017-17742/
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-17742
https://security-tracker.debian.org/tracker/CVE-2017-17742
Patch copied from snapshot.debian.org:
https://snapshot.debian.org/archive/debian-security/20180423T104456Z/pool/updates/main/r/ruby1.8/ruby1.8_1.8.7.358-7.1%2Bdeb7u6.debian.tar.gz
From bbda1a027475bf7ce5e1a9583a7b55d0be71c8fe 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 3/3] merge revision(s) 62968:
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
---
ChangeLog | 12 ++++++++++++
lib/webrick/httpresponse.rb | 27 +++++++++++++++++++++++++--
test/webrick/test_httpresponse.rb | 22 ++++++++++++++++++++++
version.h | 2 +-
4 files changed, 60 insertions(+), 3 deletions(-)
Index: ruby1.8/lib/webrick/httpresponse.rb
===================================================================
--- ruby1.8.orig/lib/webrick/httpresponse.rb
+++ ruby1.8/lib/webrick/httpresponse.rb
@@ -16,6 +16,9 @@ require 'webrick/httpstatus'
module WEBrick
class HTTPResponse
+ class InvalidHeader < StandardError
+ end
+
BUFSIZE = 1024*4
attr_reader :http_version, :status, :header
@@ -171,14 +174,19 @@ module WEBrick
data = status_line()
@header.each{|key, value|
tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.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
def send_body(socket) # :nodoc:
@@ -225,6 +233,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">
Index: ruby1.8/test/webrick/test_httpresponse.rb
===================================================================
--- ruby1.8.orig/test/webrick/test_httpresponse.rb
+++ ruby1.8/test/webrick/test_httpresponse.rb
@@ -1,6 +1,7 @@
require "webrick"
require "minitest/autorun"
require "stringio"
+require "net/http"
module WEBrick
class TestHTTPResponse < MiniTest::Unit::TestCase
@@ -27,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
|