class Brakeman::CheckMassAssignment

Checks for mass assignments to models.

See guides.rubyonrails.org/security.html#mass-assignment for details

Constants

LITERALS

Public Class Methods

new(*) click to toggle source
Calls superclass method Brakeman::BaseCheck::new
# File lib/brakeman/checks/check_mass_assignment.rb, line 12
def initialize(*)
  super
  @mass_assign_calls = nil
end

Public Instance Methods

all_literal_args?(exp) click to toggle source
# File lib/brakeman/checks/check_mass_assignment.rb, line 131
def all_literal_args? exp
  if call? exp
    exp.each_arg do |arg|
      return false unless literal? arg
    end

    true
  else
    exp.all? do |arg|
      literal? arg
    end
  end

end
calls_slice?(result) click to toggle source
# File lib/brakeman/checks/check_mass_assignment.rb, line 177
def calls_slice? result
  result[:chain].include? :slice or
    (result[:full_call] and result[:full_call][:chain].include? :slice)
end
check_call(call) click to toggle source

Want to ignore calls to Model.new that have no arguments

# File lib/brakeman/checks/check_mass_assignment.rb, line 109
def check_call call
  process_call_args call

  if call.method == :update
    arg = call.second_arg
  else
    arg = call.first_arg
  end

  if arg.nil? #empty new()
    false
  elsif hash? arg and not include_user_input? arg
    false
  elsif all_literal_args? call
    false
  else
    true
  end
end
check_mass_assignment() click to toggle source
# File lib/brakeman/checks/check_mass_assignment.rb, line 54
def check_mass_assignment
  return if mass_assign_disabled?

  Brakeman.debug "Processing possible mass assignment calls"
  find_mass_assign_calls.each do |result|
    process_result result
  end
end
check_permit!() click to toggle source

Look for and warn about uses of Parameters#permit! for mass assignment

# File lib/brakeman/checks/check_mass_assignment.rb, line 159
def check_permit!
  tracker.find_call(:method => :permit!, :nested => true).each do |result|
    if params? result[:call].target
      unless inside_safe_method? result or calls_slice? result
        warn_on_permit! result
      end
    end
  end
end
check_permit_all_parameters() click to toggle source
# File lib/brakeman/checks/check_mass_assignment.rb, line 211
def check_permit_all_parameters
  tracker.find_call(target: :"ActionController::Parameters", method: :permit_all_parameters=).each do |result|
    call = result[:call]

    if true? call.first_arg
      warn :result => result,
        :warning_type => "Mass Assignment",
        :warning_code => :mass_assign_permit_all,
        :message => msg('Mass assignment is globally enabled. Disable and specify exact keys using ', msg_code('params.permit'), ' instead'),
        :confidence => :high
    end
  end
end
find_mass_assign_calls() click to toggle source
# File lib/brakeman/checks/check_mass_assignment.rb, line 23
def find_mass_assign_calls
  return @mass_assign_calls if @mass_assign_calls

  models = []
  tracker.models.each do |name, m|
    if m.is_a? Hash
      p m
    end
    if m.unprotected_model?
      models << name
    end
  end

  return [] if models.empty?

  Brakeman.debug "Finding possible mass assignment calls on #{models.length} models"
  @mass_assign_calls = tracker.find_call :chained => true, :targets => models, :methods => [:new,
    :attributes=,
    :update_attributes,
    :update_attributes!,
    :create,
    :create!,
    :build,
    :first_or_create,
    :first_or_create!,
    :first_or_initialize!,
    :assign_attributes,
    :update
  ]
end
inside_safe_method?(result) click to toggle source

Ignore blah_some_path(params.permit!)

# File lib/brakeman/checks/check_mass_assignment.rb, line 170
def inside_safe_method? result
  parent_call = result.dig(:parent, :call)

  call? parent_call and
    parent_call.method.match(/_path$/)
end
literal?(exp) click to toggle source
# File lib/brakeman/checks/check_mass_assignment.rb, line 146
def literal? exp
  if sexp? exp
    if exp.node_type == :hash
      all_literal_args? exp
    else
      LITERALS.include? exp.node_type
    end
  else
    true
  end
end
process_result(res) click to toggle source

All results should be Model.new(…) or Model.attributes=() calls

# File lib/brakeman/checks/check_mass_assignment.rb, line 64
def process_result res
  call = res[:call]

  check = check_call call

  if check and original? res

    model = tracker.models[res[:chain].first]
    attr_protected = (model and model.attr_protected)
    first_arg = call.first_arg

    if attr_protected and tracker.options[:ignore_attr_protected]
      return
    elsif call? first_arg and (first_arg.method == :slice or first_arg.method == :only)
      return
    elsif input = include_user_input?(call.arglist)
      if not node_type? first_arg, :hash
        if attr_protected
          confidence = :medium
        else
          confidence = :high
        end
      else
        return
      end
    elsif node_type? call.first_arg, :lit, :str
      return
    else
      confidence = :weak
      input = nil
    end

    warn :result => res,
      :warning_type => "Mass Assignment",
      :warning_code => :mass_assign_call,
      :message => "Unprotected mass assignment",
      :code => call,
      :user_input => input,
      :confidence => confidence
  end

  res
end
run_check() click to toggle source
# File lib/brakeman/checks/check_mass_assignment.rb, line 17
def run_check
  check_mass_assignment
  check_permit!
  check_permit_all_parameters
end
subsequent_mass_assignment?(result) click to toggle source

Look for actual use of params in mass assignment to avoid warning about uses of Parameters#permit! without any mass assignment or when mass assignment is restricted by model instead.

# File lib/brakeman/checks/check_mass_assignment.rb, line 185
def subsequent_mass_assignment? result
  location = result[:location]
  line = result[:call].line
  find_mass_assign_calls.any? do |call|
    call[:location] == location and
    params? call[:call].first_arg and
    call[:call].line >= line
  end
end
warn_on_permit!(result) click to toggle source
# File lib/brakeman/checks/check_mass_assignment.rb, line 195
def warn_on_permit! result
  return unless original? result

  confidence = if subsequent_mass_assignment? result
                 :high
               else
                 :medium
               end

  warn :result => result,
    :warning_type => "Mass Assignment",
    :warning_code => :mass_assign_permit!,
    :message => msg('Specify exact keys allowed for mass assignment instead of using ', msg_code('permit!'), ' which allows any keys'),
    :confidence => confidence
end