Model some SQL fragment sinks in ActiveRecord model classes

This commit is contained in:
Alex Ford
2021-06-03 17:14:44 +01:00
parent 743deee9ce
commit 7488d072d8
2 changed files with 105 additions and 3 deletions

View File

@@ -4,7 +4,7 @@
* provide concrete subclasses.
*/
import ruby
private import codeql_ruby.DataFlow
/**
* A data-flow node that executes SQL statements.

View File

@@ -1,6 +1,7 @@
// TODO: calls to methods where the receiver extends ActiveRecord::Base, directly or not
import ruby
private import codeql_ruby.AST
private import codeql_ruby.Concepts
private import codeql_ruby.controlflow.CfgNodes
private import codeql_ruby.DataFlow
private import codeql_ruby.ast.internal.Module
private class ActiveRecordBaseAccess extends ConstantReadAccess {
@@ -31,3 +32,104 @@ class ActiveRecordModelClass extends ClassDeclaration {
)
}
}
// TODO: methods are class methods rather than instance?
// A parameter that may represent a credential value
/*
* private DataFlow::LocalSourceNode activeRecordModelAccess(TypeTracker t) {
* t.start() and
* exists(AssignExpr ae, Ssa::WriteDefinition def, ActiveRecordModelClass cls |
* result.asExpr().getExpr() = def.getWriteAccess() and
* result.asExpr().getExpr() = ae.getLeftOperand() and
* resolveScopeExpr(ae.getRightOperand()) = cls.getModule()
* )
* or
* exists(TypeTracker t2 | result = activeRecordModelAccess(t2).track(t2, t))
* }
*
* private DataFlow::Node activeRecordModelAccess() {
* activeRecordModelAccess(TypeTracker::end()).flowsTo(result)
* }
*
* class ActiveRecordNode extends DataFlow::Node {
* ActiveRecordNode() {
* this = activeRecordModelAccess()
* }
* }
*/
/*
* class ActiveRecordModelReadAccess extends VariableReadAccess {
* ActiveRecordModelReadAccess() {
*
* }
* }
*/
// A class method call whose receiver is an ActiveRecord model class
class ActiveRecordModelClassMethodCall extends MethodCall {
// The model class that receives this call
private ActiveRecordModelClass recvCls;
ActiveRecordModelClassMethodCall() { recvCls.getModule() = resolveScopeExpr(this.getReceiver()) }
// TODO: handle some cases of non-constant receiver expressions
ActiveRecordModelClass getResolvedReceiverScope() { result = recvCls }
}
class SqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
// The name of the method invoked
private string methodName;
// The zero-indexed position of the SQL fragment sink argument
private int sqlFragmentArgumentIndex;
// TODO: determine when the argument may be a string, rather than a key-value pair
// TODO: `find` with `lock:` option also takes an SQL fragment
SqlExecutingMethodCall() {
methodName = this.getMethodName() and
(
methodName = "calculate" and sqlFragmentArgumentIndex = 1
or
sqlFragmentArgumentIndex = 0 and
(
methodName = "delete_all"
or
methodName = "destroy_all"
or
methodName = "exists?"
or
methodName = "find_by"
or
methodName = "from"
or
methodName = "group"
or
methodName = "having"
or
methodName = "joins"
or
methodName = "lock"
or
methodName = "not"
or
methodName = "order"
or
methodName = "pluck"
or
methodName = "where"
)
)
}
Expr getSqlFragmentSinkArgument() { result = this.getArgument(sqlFragmentArgumentIndex) }
}
class ActiveRecordSqlExecutionRange extends SqlExecution::Range {
ExprCfgNode sql;
ActiveRecordSqlExecutionRange() {
exists(SqlExecutingMethodCall mc | this.asExpr().getNode() = mc.getSqlFragmentSinkArgument())
}
override DataFlow::Node getSql() { result.asExpr() = sql }
}