CVE-2024-35231 (published 2024-05-27T17:15Z)

rack-contrib provides contributed rack middleware and utilities for Rack, a Ruby web server interface. Versions of rack-contrib prior to 2.5.0 are vulnerable to denial of service due to the fact that the user controlled data `profiler_runs` was not constrained to any limitation. This would lead to allocating resources on the server side with no limitation and a potential denial of service by remotely user-controlled data. Version 2.5.0 contains a patch for the issue.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/rack/rack-contrib
commit_details: repo_url: https://github.com/rack/rack-contrib
author: Samuel Williams
author_date: 2024-05-24 09:48:48+09:00
msg: Specify a maximum number of runs to limit the request params -> DoS. (#191)
repository: repo_url: https://github.com/rack/rack-contrib
repo_name: rack/rack-contrib
description: Contributed Rack Middleware and Utilities
repo_language: Ruby
owner: rack
file_changes: filename: profiler.rb
change_type: ModificationType.MODIFY
diff: @@ -35,8 +35,11 @@ module Rack
       @profile = nil
       @printer = parse_printer(options[:printer] || DEFAULT_PRINTER)
       @times = (options[:times] || 1).to_i
+      @maximum_runs = options.fetch(:maximum_runs, 10)
     end
 
+    attr :maximum_runs
+
     def call(env)
       if mode = profiling?(env)
         profile(env, mode)
@@ -61,14 +64,32 @@ module Rack
         end
       end
 
+      # How many times to run the request within the profiler.
+      # If the profiler_runs query parameter is set, use that.
+      # Otherwise, use the :times option passed to `#initialize`.
+      # If the profiler_runs query parameter is greater than the
+      # :maximum option passed to `#initialize`, use the :maximum
+      # option.
+      def runs(request)
+        if profiler_runs = request.params['profiler_runs']
+          profiler_runs = profiler_runs.to_i
+          if profiler_runs > @maximum_runs
+            return @maximum_runs
+          else
+            return profiler_runs
+          end
+        else
+          return @times
+        end
+      end
+
       def profile(env, mode)
         @profile = ::RubyProf::Profile.new(measure_mode: ::RubyProf.const_get(mode.upcase))
 
         GC.enable_stats if GC.respond_to?(:enable_stats)
         request = Rack::Request.new(env.clone)
-        runs = (request.params['profiler_runs'] || @times).to_i
         result = @profile.profile do
-          runs.times { @app.call(env) }
+          runs(request).times { @app.call(env) }
         end
         GC.disable_stats if GC.respond_to?(:disable_stats)
 

programming_language: Ruby
filename: spec_rack_profiler.rb
change_type: ModificationType.MODIFY
diff: @@ -27,6 +27,13 @@ begin
       _(body.to_enum.to_a.join).must_match(/\[#{runs} calls, #{runs} total\]/)
     end
 
+    specify 'called more than the default maximum times via query params' do
+      runs = 20
+      req = Rack::MockRequest.env_for("/", :params => "profile=process_time&profiler_runs=#{runs}")
+      body = profiler(app).call(req)[2]
+      _(body.to_enum.to_a.join).must_match(/\[10 calls, 10 total\]/)
+    end
+
     specify 'CallStackPrinter has content-type test/html' do
       headers = profiler(app, :printer => :call_stack).call(request)[1]
       _(headers).must_equal "content-type"=>"text/html"

programming_language: Ruby