Information Exposure Affecting activeadmin package, versions <2.12.0


Severity

Recommended
0.0
medium
0
10

CVSS assessment made by Snyk's Security Team. Learn more

Threat Intelligence

Exploit Maturity
Proof of concept
EPSS
0.09% (42nd percentile)

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications
  • Snyk IDSNYK-RUBY-ACTIVEADMIN-6129507
  • published17 Dec 2023
  • disclosed16 Dec 2023
  • creditemilong

Introduced: 16 Dec 2023

CVE-2023-50448  (opens in a new tab)
CWE-200  (opens in a new tab)

How to fix?

Upgrade activeadmin to version 2.12.0 or higher.

Overview

Affected versions of this package are vulnerable to Information Exposure due to a concurrency issue that results in a shared variable not being properly synchronized. An attacker with access to the same ActiveAdmin application can obtain private data intended for another user by timing their request to coincide with the victim's CSV export action or by frequently requesting CSV exports in the hope of a concurrent request occurring.

PoC


# frozen_string_literal: true
require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  # Use local changes or ActiveAdmin master.
  if ENV["ACTIVE_ADMIN_PATH"]
    gem "activeadmin", path: ENV["ACTIVE_ADMIN_PATH"], require: false
  else
    gem "activeadmin", github: "activeadmin/activeadmin", require: false
  end

  # Change Rails version if necessary.
  gem "rails", "6.1.4"

  gem "sprockets", "3.7.2"
  gem "sassc-rails", "2.1.2"
  gem "sqlite3", "1.4.2", platform: :mri
  gem "activerecord-jdbcsqlite3-adapter", "61.0", platform: :jruby
end

require "active_record"

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :active_admin_comments, force: true do |_t|
  end

  create_table :users, force: true do |t|
    t.string :full_name
  end
end

require "action_controller/railtie"
require "action_view/railtie"
require "active_admin"

class TestApp < Rails::Application
  config.root = __dir__
  config.session_store :cookie_store, key: "cookie_store_key"
  secrets.secret_token = "secret_token"
  secrets.secret_key_base = "secret_key_base"

  config.eager_load = false
  config.logger = Logger.new($stdout)

  config.hosts = "www.example.com"
end

class ApplicationController < ActionController::Base
  include Rails.application.routes.url_helpers
end

class User < ActiveRecord::Base
end

class TestSpy
  attr_reader :count

  def initialize
    @count = 0
  end

  def increment
    @count += 1
  end

  def reset
    @count = 0
  end
end
TEST_SPY = TestSpy.new

ActiveAdmin.setup do |config|
  # Authentication disabled by default. Override if necessary.
  config.authentication_method = false
  config.current_user_method = false
end

Rails.application.initialize!

ActiveAdmin.register_page "Dashboard" do
  menu priority: 1, label: proc { I18n.t("active_admin.dashboard") }
  content do
    "Test Me"
  end
end

ActiveAdmin.register User do
  actions :index

  controller do
    def find_collection(*)
      TEST_SPY.increment
      # Rails.logger.debug { ">> FETCHING COLLECTION\n#{caller.join("\n")}" }
      super
    end
  end
end

Rails.application.routes.draw do
  ActiveAdmin.routes(self)
end

require "minitest/autorun"
require "rack/test"
require "rails/test_help"

# Replace this with the code necessary to make your test fail.
class BugTest < ActionDispatch::IntegrationTest
  def test_csv_fetches_collection_once
    user = User.create! full_name: 'John Doe'
    TEST_SPY.reset
    get admin_users_url(format: :csv)
    csv_response_body = response.body.delete('')
    assert_equal "Id,Full name\n#{user.id},John Doe\n", csv_response_body
    assert_response :success
    assert_equal 1, TEST_SPY.count
  end

  def test_html_fetches_collection_once
    User.create! full_name: 'John Doe'
    TEST_SPY.reset
    get admin_users_url
    assert_match /John Doe/, response.body
    assert_response :success
    assert_equal 1, TEST_SPY.count
  end

  private

  def app
    Rails.application
  end
end

CVSS Scores

version 3.1