sinatra / sinatra

Classy web-development dressed in a DSL (official / canonical repo)

Home Page:https://sinatrarb.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DOS attack due to automatic Rack body parsing (incorrect Content-Type)

md-work opened this issue · comments

HTTP requests containing a large body can cause huge memory and CPU consumption on the server.
Sending a 4 GB body caused the Ruby process to grow over 40 GB in memory. (then swapping started)

This can be used by a malicious client to DOS attack the server!

It happens if the Content-Type suggests the body can be parsed by Rack.
Bad example: Content-Type: application/x-www-form-urlencoded
Good example: Content-Type: application/octet-stream
These are just two example content types which reproduce good and bad behavior.
The content type must not even be wrong. The client might be sending 4 GB of x-www-form-urlencoded data.

 

Details

In some way this is a problem of Rack. But Sinatra is invoking Rack to start the parsing. So I'm opening the issue here.
Stacktrace (captured via gdb call (void)rb_backtrace() on Debian-12-Testing / Bookworm):

[...]
/usr/share/rubygems-integration/all/gems/sinatra-3.0.5/lib/sinatra/base.rb:939:in `block in call!'
/usr/share/rubygems-integration/all/gems/sinatra-3.0.5/lib/sinatra/base.rb:1116:in `dispatch!'
/usr/share/rubygems-integration/all/gems/sinatra-3.0.5/lib/sinatra/base.rb:79:in `params'
/usr/share/rubygems-integration/all/gems/rack-2.2.6.4/lib/rack/request.rb:32:in `params'
/usr/share/rubygems-integration/all/gems/rack-2.2.6.4/lib/rack/request.rb:469:in `params'
/usr/share/rubygems-integration/all/gems/rack-2.2.6.4/lib/rack/request.rb:454:in `POST'
[...]

In Rack::Request#POST this is happening:

        elsif form_data? || parseable_data?
          unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
            form_vars = get_header(RACK_INPUT).read

parseable_data? is true depending on the Content-Type.

 

Reproduction:

truncate --size="$((4*1024**3))" file_4g
wget --tries=1 --post-file=file_4g --debug 'http://127.0.0.1:8080/'`

If the client adds --header='Content-Type: application/octet-stream' the problem will be avoided.

 

Questions, Proposal, Related

Is there a way to disable this behavior in Sinatra?
So Sinatra won't ask Rack to parse the body.
When using pure Rack there's no automatic body parsing.

I guess either Sinatra or Rack should check the body size or limit the maximum number of bytes being parsed.
A simple solution would be to add a maximum number of bytes to the read call:

            form_vars = get_header(RACK_INPUT).read(16*1024**2)  #16 MiB

Related: rack/rack#2049

I will convert this to a discussion because I don't think there's anything actionable for Sinatra to do at this point.