SECURITY: Prevent large staff actions causing DoS

This commit operates at three levels of abstraction:

 1. We want to prevent user history rows from being unbounded in size.
    This commit adds rails validations to limit the sizes of columns on
    user_histories,

 2. However, we don't want to prevent certain actions from being
    completed if these columns are too long. In those cases, we truncate
    the values that are given and store the truncated versions,

 3. For endpoints that perform staff actions, we can further control
    what is permitted by explicitly validating the params that are given
    before attempting the action,
This commit is contained in:
Daniel Waterworth
2024-02-22 13:47:15 -06:00
committed by Nat
parent 003b80e62f
commit 8cade1e825
8 changed files with 167 additions and 19 deletions

View File

@ -107,6 +107,11 @@ class Admin::UsersController < Admin::StaffController
def suspend
guardian.ensure_can_suspend!(@user)
reason = params[:reason]
if reason && (!reason.is_a?(String) || reason.size > 300)
raise Discourse::InvalidParameters.new(:reason)
end
if @user.suspended?
suspend_record = @user.suspend_record
@ -128,6 +133,10 @@ class Admin::UsersController < Admin::StaffController
all_users = [@user]
if Array === params[:other_user_ids]
if params[:other_user_ids].size > MAX_SIMILAR_USERS
raise Discourse::InvalidParameters.new(:other_user_ids)
end
all_users.concat(User.where(id: params[:other_user_ids]).to_a)
all_users.uniq!
end
@ -360,6 +369,11 @@ class Admin::UsersController < Admin::StaffController
def silence
guardian.ensure_can_silence_user! @user
reason = params[:reason]
if reason && (!reason.is_a?(String) || reason.size > 300)
raise Discourse::InvalidParameters.new(:reason)
end
if @user.silenced?
silenced_record = @user.silenced_record
@ -379,6 +393,10 @@ class Admin::UsersController < Admin::StaffController
all_users = [@user]
if Array === params[:other_user_ids]
if params[:other_user_ids].size > MAX_SIMILAR_USERS
raise Discourse::InvalidParameters.new(:other_user_ids)
end
all_users.concat(User.where(id: params[:other_user_ids]).to_a)
all_users.uniq!
end

View File

@ -5,18 +5,26 @@ class ExportCsvController < ApplicationController
def export_entity
guardian.ensure_can_export_entity!(export_params[:entity])
entity = export_params[:entity]
raise Discourse::InvalidParameters.new(:entity) unless entity.is_a?(String) && entity.size < 100
if export_params[:entity] == "user_archive"
(export_params[:args] || {}).each do |key, value|
unless value.is_a?(String) && value.size < 100
raise Discourse::InvalidParameters.new("args.#{key}")
end
end
if entity == "user_archive"
Jobs.enqueue(:export_user_archive, user_id: current_user.id, args: export_params[:args])
else
Jobs.enqueue(
:export_csv_file,
entity: export_params[:entity],
entity: entity,
user_id: current_user.id,
args: export_params[:args],
)
end
StaffActionLogger.new(current_user).log_entity_export(export_params[:entity])
StaffActionLogger.new(current_user).log_entity_export(entity)
render json: success_json
rescue Discourse::InvalidAccess
render_json_error I18n.t("csv_export.rate_limit_error")