Class: Scryer::Rules::MassAssignmentRule
- Inherits:
-
Scryer::Rule
- Object
- Scryer::Rule
- Scryer::Rules::MassAssignmentRule
- Defined in:
- lib/scryer/rules/mass_assignment_rule.rb
Overview
Flags Model.new(params[...]) / Model.new(params) / .update(params[...])
/ .assign_attributes(params) where the argument is params (or a
subscript of it) with no .permit(...) anywhere in the same argument
expression — i.e. attributes are being mass-assigned straight from the
request with no allow-list.
Constant Summary collapse
- ASSIGNMENT_METHODS =
%w[new create create! update update! assign_attributes attributes=].freeze
- IVAR_RECEIVER_METHODS =
update/update!/assign_attributes/attributes=on an instance variable (@order.update(params[:order])) is at least as common a real-world shape asModel.new(...)/Model.create(...)on a bare constant — the standardbefore_action :set_thing+@thing.updatescaffold pattern — but was invisible to this rule entirely until found via the accuracy benchmark (benchmark/corpus.rb's "mass_assignment" vulnerable sample), since likely_model_receiver? only accepted a bare constant or implicit (nil) receiver..new/create/create!deliberately stay constant/nil-only: those verbs only make sense as a receiver-less/class-level call in normal Rails code, whereas an ivar could just as easily be a plain Hash (Hash# updateis a realmerge!alias) — restricting the ivar allowance to only the update-style verbs keeps this from also matching things like@filters.update(params[:filters])on a Hash. %w[update update! assign_attributes attributes=].freeze
- NON_MODEL_RECEIVERS =
Common stdlib/gem constants with their own
.new/.create-style factory methods that have nothing to do with ActiveRecord mass assignment (e.g.BCrypt::Password.create(params[:password])is hashing a single value, not setting a hash of model attributes). Excluding these — plus anything referenced through a namespacedA::Bpath, which real Rails models are less commonly called via at the exact call site — cuts down false positives significantly. %w[ Struct OpenStruct Data Class Module BCrypt OpenSSL Net URI Digest JSON YAML Marshal String Array Hash Integer Float Symbol Comparable ].freeze
Instance Attribute Summary
Attributes inherited from Scryer::Rule
Instance Method Summary collapse
Methods inherited from Scryer::Rule
Constructor Details
This class inherits a constructor from Scryer::Rule
Instance Method Details
#scan ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/scryer/rules/mass_assignment_rule.rb', line 47 def scan findings = [] Ast.each_node(sexp) do |node| next unless Ast.tagged?(node, :method_add_arg, :command, :command_call) inner = Ast.tagged?(node, :method_add_arg) ? node[1] : node receiver_and_name = Ast.call_name(inner) next unless receiver_and_name receiver, method_name = receiver_and_name next unless ASSIGNMENT_METHODS.include?(method_name) next unless likely_model_receiver?(receiver, method_name) args = Ast.call_arguments(node) next if args.empty? arg = args.first next unless references_raw_params?(arg) line = Ast.line_of(arg) || Ast.line_of(node) findings << finding( line: line, message: "`#{method_name}` receives `params` (or a subscript of it) directly, " \ "with no `.permit(...)` call — every attribute in the request can be " \ "set, including ones the form/API was never meant to expose (e.g. `admin`, `role_id`).", suggested_fix: "Wrap the params in a strong-parameters method, e.g. " \ "`#{method_name}(order_params)` with `def order_params; " \ "params.require(:order).permit(:status, :total); end` — only the " \ "explicitly permitted keys get through." ) end findings end |