cgi_multipart_eof_fix 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +43 -0
- data/Rakefile +53 -0
- data/cgi_multipart_eof_fix_test.rb +31 -0
- data/lib/cgi_multipart_eof_fix.rb +112 -0
- metadata +49 -0
data/README.txt
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
DESCRIPTION
|
3
|
+
|
4
|
+
Fix an exploitable bug in CGI multipart parsing which affects Ruby <= 1.8.5
|
5
|
+
when multipart boundary attribute contains a non-halting regular expression
|
6
|
+
string. The boundary searcher in the CGI module does not properly escape
|
7
|
+
the user-supplied parameter and will execute arbitrary regular expressions.
|
8
|
+
The fix adds escaping for the user data.
|
9
|
+
|
10
|
+
This is fix is cumulative with previous CGI multipart vulnerability fixes; see
|
11
|
+
version 1.0.0 of the gem by Zed Shaw.
|
12
|
+
|
13
|
+
SCOPE
|
14
|
+
|
15
|
+
Affected: standalone CGI, Mongrel, WEBrick
|
16
|
+
Unaffected: FastCGI
|
17
|
+
Unknown: mod_ruby
|
18
|
+
|
19
|
+
USAGE
|
20
|
+
|
21
|
+
Install the hotfix gem and run the included test to verify the flaw is
|
22
|
+
corrected. You must require the gem in every affected application, as follows:
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require 'cgi_multipart_eof_fix'
|
26
|
+
|
27
|
+
If you only use mongrel_rails for application hosting, you may install mongrel
|
28
|
+
like so:
|
29
|
+
|
30
|
+
sudo gem install mongrel --source=http://mongrel.rubyforge.org/releases
|
31
|
+
|
32
|
+
Then mongrel will require the fix for you, provided you have installed version 2.0.0
|
33
|
+
of this gem. This is a hack, and mongrel may change in the future.
|
34
|
+
|
35
|
+
RESOURCES
|
36
|
+
|
37
|
+
http://www.ruby-lang.org/en/news/2006/12/04/another-dos-vulnerability-in-cgi-library/
|
38
|
+
http://blog.evanweaver.com/articles/2006/12/05/cgi-rb-vulnerability-hotfix
|
39
|
+
|
40
|
+
LICENSE
|
41
|
+
|
42
|
+
Licensed under the same license as Ruby itself. Software contains the work of others.
|
43
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
NAME = "cgi_multipart_eof_fix"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'rake/clean'
|
8
|
+
require 'echoe'
|
9
|
+
require 'fileutils'
|
10
|
+
|
11
|
+
AUTHOR = "Evan Weaver"
|
12
|
+
EMAIL = "evan at cloudbur dot st"
|
13
|
+
DESCRIPTION = "Fix an exploitable bug in CGI multipart parsing which affects Ruby <= 1.8.5 when multipart boundary attribute contains a non-halting regular expression string."
|
14
|
+
RUBYFORGE_NAME = "fauna"
|
15
|
+
GEM_NAME = "cgi_multipart_eof_fix"
|
16
|
+
HOMEPATH = "http://blog.evanweaver.com"
|
17
|
+
RELEASE_TYPES = ["gem"]
|
18
|
+
REV = nil
|
19
|
+
VERS = "2.0.1"
|
20
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
21
|
+
RDOC_OPTS = ['--quiet', '--title', "cgi_multipart_eof_fix documentation",
|
22
|
+
"--opname", "index.html",
|
23
|
+
"--line-numbers",
|
24
|
+
"--main", "README",
|
25
|
+
"--inline-source"]
|
26
|
+
|
27
|
+
include FileUtils
|
28
|
+
require File.join(File.dirname(__FILE__), 'lib', 'cgi_multipart_eof_fix')
|
29
|
+
|
30
|
+
echoe = Echoe.new(GEM_NAME, VERS) do |p|
|
31
|
+
p.author = AUTHOR
|
32
|
+
p.rubyforge_name = RUBYFORGE_NAME
|
33
|
+
p.name = NAME
|
34
|
+
p.description = DESCRIPTION
|
35
|
+
p.email = EMAIL
|
36
|
+
p.summary = DESCRIPTION
|
37
|
+
p.url = HOMEPATH
|
38
|
+
p.test_globs = ["*_test.rb"]
|
39
|
+
p.clean_globs = CLEAN
|
40
|
+
end
|
41
|
+
|
42
|
+
rescue LoadError => boom
|
43
|
+
puts "You are missing a dependency required for meta-operations on this gem."
|
44
|
+
puts "#{boom.to_s.capitalize}."
|
45
|
+
|
46
|
+
desc 'Run the default tasks'
|
47
|
+
task :default => :test
|
48
|
+
|
49
|
+
desc 'Run the test suite.'
|
50
|
+
task :test do
|
51
|
+
system "ruby -Ibin:lib:test #{NAME}_test.rb"
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'cgi'
|
3
|
+
require 'stringio'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
def test_read_multipart_eof_fix
|
7
|
+
boundary = '%?%(\w*)\\((\w*)\\)'
|
8
|
+
data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"a_field\"\r\n\r\nBang!\r\n--#{boundary}--\r\n"
|
9
|
+
|
10
|
+
ENV['REQUEST_METHOD'] = "POST"
|
11
|
+
ENV['CONTENT_TYPE'] = "multipart/form-data; boundary=\"#{boundary}\""
|
12
|
+
ENV['CONTENT_LENGTH'] = data.length.to_s
|
13
|
+
|
14
|
+
$stdin = StringIO.new(data)
|
15
|
+
|
16
|
+
begin
|
17
|
+
Timeout.timeout(3) { CGI.new }
|
18
|
+
$stderr.puts ' => CGI is safe: read_multipart does not hang on malicious multipart requests.'
|
19
|
+
rescue TimeoutError
|
20
|
+
$stderr.puts ' => CGI is exploitable: read_multipart hangs on malicious multipart requests.'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
$stderr.puts 'Testing malicious multipart boundary request injection'
|
25
|
+
test_read_multipart_eof_fix
|
26
|
+
|
27
|
+
$stderr.puts 'Patching CGI::QueryExtension.read_multipart'
|
28
|
+
require 'rubygems'
|
29
|
+
require 'cgi_multipart_eof_fix'
|
30
|
+
|
31
|
+
test_read_multipart_eof_fix
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
class CGI
|
4
|
+
module QueryExtension
|
5
|
+
def read_multipart(boundary, content_length)
|
6
|
+
params = Hash.new([])
|
7
|
+
boundary = "--" + boundary
|
8
|
+
quoted_boundary = Regexp.quote(boundary, "n")
|
9
|
+
buf = ""
|
10
|
+
bufsize = 10 * 1024
|
11
|
+
boundary_end=""
|
12
|
+
|
13
|
+
# start multipart/form-data
|
14
|
+
stdinput.binmode if defined? stdinput.binmode
|
15
|
+
boundary_size = boundary.size + EOL.size
|
16
|
+
content_length -= boundary_size
|
17
|
+
status = stdinput.read(boundary_size)
|
18
|
+
if nil == status
|
19
|
+
raise EOFError, "no content body"
|
20
|
+
elsif boundary + EOL != status
|
21
|
+
raise EOFError, "bad content body #{status.inspect} expected, got #{(boundary + EOL).inspect}"
|
22
|
+
end
|
23
|
+
|
24
|
+
loop do
|
25
|
+
head = nil
|
26
|
+
if 10240 < content_length
|
27
|
+
require "tempfile"
|
28
|
+
body = Tempfile.new("CGI")
|
29
|
+
else
|
30
|
+
begin
|
31
|
+
require "stringio"
|
32
|
+
body = StringIO.new
|
33
|
+
rescue LoadError
|
34
|
+
require "tempfile"
|
35
|
+
body = Tempfile.new("CGI")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
body.binmode if defined? body.binmode
|
39
|
+
|
40
|
+
until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
|
41
|
+
|
42
|
+
if (not head) and /#{EOL}#{EOL}/n.match(buf)
|
43
|
+
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
|
44
|
+
head = $1.dup
|
45
|
+
""
|
46
|
+
end
|
47
|
+
next
|
48
|
+
end
|
49
|
+
|
50
|
+
if head and ( (EOL + boundary + EOL).size < buf.size )
|
51
|
+
body.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
|
52
|
+
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
|
53
|
+
end
|
54
|
+
|
55
|
+
c = if bufsize < content_length
|
56
|
+
stdinput.read(bufsize)
|
57
|
+
else
|
58
|
+
stdinput.read(content_length)
|
59
|
+
end
|
60
|
+
if c.nil? || c.empty?
|
61
|
+
raise EOFError, "bad content body"
|
62
|
+
end
|
63
|
+
buf.concat(c)
|
64
|
+
content_length -= c.size
|
65
|
+
end
|
66
|
+
|
67
|
+
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
|
68
|
+
body.print $1
|
69
|
+
if "--" == $2
|
70
|
+
content_length = -1
|
71
|
+
end
|
72
|
+
boundary_end = $2.dup
|
73
|
+
""
|
74
|
+
end
|
75
|
+
|
76
|
+
body.rewind
|
77
|
+
|
78
|
+
/Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
|
79
|
+
filename = ($1 or "")
|
80
|
+
if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
|
81
|
+
/Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
|
82
|
+
(not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
|
83
|
+
filename = CGI::unescape(filename)
|
84
|
+
end
|
85
|
+
|
86
|
+
/Content-Type: (.*)/ni.match(head)
|
87
|
+
content_type = ($1 or "")
|
88
|
+
|
89
|
+
(class << body; self; end).class_eval do
|
90
|
+
alias local_path path
|
91
|
+
define_method(:original_filename) {filename.dup.taint}
|
92
|
+
define_method(:content_type) {content_type.dup.taint}
|
93
|
+
end
|
94
|
+
|
95
|
+
/Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
|
96
|
+
name = $1.dup
|
97
|
+
|
98
|
+
if params.has_key?(name)
|
99
|
+
params[name].push(body)
|
100
|
+
else
|
101
|
+
params[name] = [body]
|
102
|
+
end
|
103
|
+
break if buf.size == 0
|
104
|
+
break if content_length === -1
|
105
|
+
end
|
106
|
+
raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
|
107
|
+
|
108
|
+
params
|
109
|
+
end # read_multipart
|
110
|
+
private :read_multipart
|
111
|
+
end
|
112
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0.9
|
3
|
+
specification_version: 1
|
4
|
+
name: cgi_multipart_eof_fix
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 2.0.1
|
7
|
+
date: 2007-01-10 00:00:00 -05:00
|
8
|
+
summary: Fix an exploitable bug in CGI multipart parsing which affects Ruby <= 1.8.5 when multipart boundary attribute contains a non-halting regular expression string.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: evan at cloudbur dot st
|
12
|
+
homepage: http://blog.evanweaver.com
|
13
|
+
rubyforge_project: fauna
|
14
|
+
description: Fix an exploitable bug in CGI multipart parsing which affects Ruby <= 1.8.5 when multipart boundary attribute contains a non-halting regular expression string.
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Evan Weaver
|
31
|
+
files:
|
32
|
+
- README.txt
|
33
|
+
- Rakefile
|
34
|
+
- lib/cgi_multipart_eof_fix.rb
|
35
|
+
- cgi_multipart_eof_fix_test.rb
|
36
|
+
test_files:
|
37
|
+
- cgi_multipart_eof_fix_test.rb
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
executables: []
|
43
|
+
|
44
|
+
extensions: []
|
45
|
+
|
46
|
+
requirements: []
|
47
|
+
|
48
|
+
dependencies: []
|
49
|
+
|