Class: Scryer::Rules::UnsafeDeserializationRule
- Inherits:
-
Scryer::Rule
- Object
- Scryer::Rule
- Scryer::Rules::UnsafeDeserializationRule
- Defined in:
- lib/scryer/rules/unsafe_deserialization_rule.rb
Overview
Flags Marshal.load, YAML.load (as opposed to YAML.safe_load), and
JSON.load (as opposed to JSON.parse) — all three can instantiate
arbitrary Ruby objects from untrusted input, a known RCE vector in Rails
apps (several real-world CVEs trace back to exactly this).
Constant Summary collapse
- UNSAFE_CALLS =
{ %w[Marshal load] => "Marshal.load can instantiate arbitrary Ruby objects, including ones " \ "that execute code as a side effect of being constructed — never call " \ "it on data that came from a user, request, or external service.", %w[YAML load] => "YAML.load (unlike YAML.safe_load) can instantiate arbitrary Ruby objects " \ "from the document, which is a known remote-code-execution vector when " \ "the YAML source isn't fully trusted.", %w[JSON load] => "JSON.load can invoke arbitrary `create_id`-tagged object construction, " \ "unlike the safer JSON.parse." }.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
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/scryer/rules/unsafe_deserialization_rule.rb', line 24 def scan findings = [] Ast.each_node(sexp) do |node| # Only match the method_add_arg wrapper (call + its parenthesized # args) — the :call node it wraps would otherwise also match this # loop on its own and double-count every finding. next unless Ast.tagged?(node, :method_add_arg) inner = node[1] next unless Ast.tagged?(inner, :call) receiver = inner[1] method_name = Ast.ident_text(inner[3]) receiver_name = Ast.ident_text(receiver.is_a?(Array) ? receiver[1] : nil) if Ast.tagged?(receiver, :var_ref) match = UNSAFE_CALLS.keys.find { |(recv, meth)| recv == receiver_name && meth == method_name } next unless match line = Ast.line_of(node) findings << finding( line: line, message: UNSAFE_CALLS[match], suggested_fix: safe_alternative(match) ) end findings end |