From a9f49ce6cae770cbebda02c25a3296cd73cbefb4 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Wed, 20 Mar 2024 10:24:23 +0000 Subject: [PATCH] Add mass assignment query --- .../ruby/security/MassAssignmentQuery.qll | 100 ++++++++++++++++++ .../security/cwe-915/MassAssignment.ql | 18 ++++ 2 files changed, 118 insertions(+) create mode 100644 ruby/ql/lib/codeql/ruby/security/MassAssignmentQuery.qll create mode 100644 ruby/ql/src/queries/security/cwe-915/MassAssignment.ql diff --git a/ruby/ql/lib/codeql/ruby/security/MassAssignmentQuery.qll b/ruby/ql/lib/codeql/ruby/security/MassAssignmentQuery.qll new file mode 100644 index 0000000000000..1a8c2f29e4d60 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/security/MassAssignmentQuery.qll @@ -0,0 +1,100 @@ +/** + * Provides a taint tracking configuration for reasoning about insecure mass assignment. + */ + +private import codeql.ruby.AST +private import codeql.ruby.DataFlow +private import codeql.ruby.TaintTracking +private import codeql.ruby.dataflow.RemoteFlowSources + +/** Provides default sources and sinks for the mass assignment query. */ +module MassAssignment { + /** + * A data flow source for user input used for mass assignment. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for user input used for mass assignment. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A call that permits arbitrary parameters to be used for mass assignment. + */ + abstract class MassPermit extends DataFlow::Node { + /** Gets the argument for the parameters to be permitted */ + abstract DataFlow::Node getParamsArgument(); + + /** Gets the result node of the permitted parameters. */ + abstract DataFlow::Node getPermittedParamsResult(); + } + + private class RemoteSource extends Source instanceof RemoteFlowSource { } + + private class CreateSink extends Sink { + CreateSink() { + this.asExpr().getExpr().(MethodCall).getMethodName() = ["create", "new", "update"] + } + } + + private class PermitCall extends MassPermit instanceof DataFlow::CallNode { + PermitCall() { + this.asExpr().getExpr().(MethodCall).getMethodName() = ["permit!", "to_unsafe_h"] + } + + override DataFlow::Node getParamsArgument() { result = this.(DataFlow::CallNode).getReceiver() } + + override DataFlow::Node getPermittedParamsResult() { result = this } + } +} + +private module FlowState { + private newtype TState = + TUnpermitted() or + TPermitted() + + /** A flow state used to distinguish whether arbitrary user parameters have been permitted to be used for mass assignment. */ + class State extends TState { + string toString() { + this = TUnpermitted() and result = "unpermitted" + or + this = TPermitted() and result = "permitted" + } + } + + /** A flow state used for user parameters for which arbitrary parameters have not been permitted to use for mass assignment. */ + class Unpermitted extends State, TUnpermitted { } + + /** A flow state used for user parameters for which arbitrary parameters have been permitted to use for mass assignment. */ + class Permitted extends State, TPermitted { } +} + +/** A flow configuration for reasoning about insecure mass assignment. */ +private module Config implements DataFlow::StateConfigSig { + class FlowState = FlowState::State; + + predicate isSource(DataFlow::Node node, FlowState state) { + node instanceof MassAssignment::Source and + state instanceof FlowState::Unpermitted + } + + predicate isSink(DataFlow::Node node, FlowState state) { + node instanceof MassAssignment::Sink and + state instanceof FlowState::Permitted + } + + predicate isAdditionalFlowStep( + DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 + ) { + exists(MassAssignment::MassPermit permit | + node1 = permit.getParamsArgument() and + state1 instanceof FlowState::Unpermitted and + node2 = permit.getPermittedParamsResult() and + state2 instanceof FlowState::Permitted + ) + } +} + +/** Taint tracking for reasoning about user input used for mass assignment. */ +module MassAssignmentFlow = TaintTracking::GlobalWithState; diff --git a/ruby/ql/src/queries/security/cwe-915/MassAssignment.ql b/ruby/ql/src/queries/security/cwe-915/MassAssignment.ql new file mode 100644 index 0000000000000..951d1efaf560d --- /dev/null +++ b/ruby/ql/src/queries/security/cwe-915/MassAssignment.ql @@ -0,0 +1,18 @@ +/** + * @name Insecure Mass Assignment + * @description Using mass assignment with user-controlled keys allows unintended parameters to be set. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id ruby/insecure-mass-assignment + * @tags security + * external/cwe/cwe-915 + */ + +import codeql.ruby.security.MassAssignmentQuery +import MassAssignmentFlow::PathGraph + +from MassAssignmentFlow::PathNode source, MassAssignmentFlow::PathNode sink +where MassAssignmentFlow::flowPath(source, sink) +select sink.getNode(), source, sink, "mass assignment"