mirror of
https://github.com/github/codeql.git
synced 2026-04-26 01:05:15 +02:00
Merge remote-tracking branch 'origin/main' into regex
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
private import codeql_ruby.DataFlow
|
||||
private import codeql_ruby.Frameworks
|
||||
|
||||
/**
|
||||
* A data-flow node that executes SQL statements.
|
||||
|
||||
6
ql/src/codeql_ruby/Frameworks.qll
Normal file
6
ql/src/codeql_ruby/Frameworks.qll
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Helper file that imports all framework modeling.
|
||||
*/
|
||||
|
||||
private import codeql_ruby.frameworks.ActionController
|
||||
private import codeql_ruby.frameworks.ActiveRecord
|
||||
@@ -113,6 +113,11 @@ class ReturningCfgNode extends AstCfgNode {
|
||||
}
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `StringComponent` AST expression. */
|
||||
class StringComponentCfgNode extends AstCfgNode {
|
||||
StringComponentCfgNode() { this.getNode() instanceof StringComponent }
|
||||
}
|
||||
|
||||
private Expr desugar(Expr n) {
|
||||
result = n.getDesugared()
|
||||
or
|
||||
@@ -197,7 +202,7 @@ module ExprNodes {
|
||||
|
||||
BinaryOperationCfgNode() { e = bo }
|
||||
|
||||
final override BinaryOperation getExpr() { result = super.getExpr() }
|
||||
override BinaryOperation getExpr() { result = super.getExpr() }
|
||||
|
||||
/** Gets the left operand of this binary operation. */
|
||||
final ExprCfgNode getLeftOperand() { e.hasCfgChild(bo.getLeftOperand(), this, result) }
|
||||
@@ -253,7 +258,7 @@ module ExprNodes {
|
||||
class MethodCallCfgNode extends CallCfgNode {
|
||||
MethodCallCfgNode() { super.getExpr() instanceof MethodCall }
|
||||
|
||||
final override MethodCall getExpr() { result = super.getExpr() }
|
||||
override MethodCall getExpr() { result = super.getExpr() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `CaseExpr` AST expression. */
|
||||
@@ -330,4 +335,44 @@ module ExprNodes {
|
||||
|
||||
final override VariableReadAccess getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `StringInterpolationComponent` AST expression. */
|
||||
class StringInterpolationComponentCfgNode extends StmtSequenceCfgNode {
|
||||
StringInterpolationComponentCfgNode() { this.getNode() instanceof StringInterpolationComponent }
|
||||
}
|
||||
|
||||
private class StringlikeLiteralChildMapping extends ExprChildMapping, StringlikeLiteral {
|
||||
override predicate relevantChild(Expr e) { e = this.getComponent(_) }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `StringlikeLiteral` AST expression. */
|
||||
class StringlikeLiteralCfgNode extends ExprCfgNode {
|
||||
override StringlikeLiteralChildMapping e;
|
||||
|
||||
final override StringlikeLiteral getExpr() { result = super.getExpr() }
|
||||
|
||||
/** Gets a component of this `StringlikeLiteral` */
|
||||
StringComponentCfgNode getAComponent() { e.hasCfgChild(e.getComponent(_), this, result) }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `StringLiteral` AST expression. */
|
||||
class StringLiteralCfgNode extends ExprCfgNode {
|
||||
override StringLiteral e;
|
||||
|
||||
final override StringLiteral getExpr() { result = super.getExpr() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `ComparisonOperation` AST expression. */
|
||||
class ComparisonOperationCfgNode extends BinaryOperationCfgNode {
|
||||
ComparisonOperationCfgNode() { e instanceof ComparisonOperation }
|
||||
|
||||
final override ComparisonOperation getExpr() { result = super.getExpr() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps an `ElementReference` AST expression. */
|
||||
class ElementReferenceCfgNode extends MethodCallCfgNode {
|
||||
ElementReferenceCfgNode() { e instanceof ElementReference }
|
||||
|
||||
final override ElementReference getExpr() { result = super.getExpr() }
|
||||
}
|
||||
}
|
||||
|
||||
68
ql/src/codeql_ruby/dataflow/BarrierGuards.qll
Normal file
68
ql/src/codeql_ruby/dataflow/BarrierGuards.qll
Normal file
@@ -0,0 +1,68 @@
|
||||
/** Provides commonly used barriers to dataflow. */
|
||||
|
||||
private import ruby
|
||||
private import codeql_ruby.DataFlow
|
||||
private import codeql_ruby.CFG
|
||||
|
||||
/**
|
||||
* A validation of value by comparing with a constant string value, for example
|
||||
* in:
|
||||
*
|
||||
* ```rb
|
||||
* dir = params[:order]
|
||||
* dir = "DESC" unless dir == "ASC"
|
||||
* User.order("name #{dir}")
|
||||
* ```
|
||||
*
|
||||
* the equality operation guards against `dir` taking arbitrary values when used
|
||||
* in the `order` call.
|
||||
*/
|
||||
class StringConstCompare extends DataFlow::BarrierGuard,
|
||||
CfgNodes::ExprNodes::ComparisonOperationCfgNode {
|
||||
private CfgNode checkedNode;
|
||||
|
||||
StringConstCompare() {
|
||||
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
|
||||
this.getExpr() instanceof EqExpr or
|
||||
this.getExpr() instanceof CaseEqExpr
|
||||
|
|
||||
this.getLeftOperand() = strLitNode and this.getRightOperand() = checkedNode
|
||||
or
|
||||
this.getLeftOperand() = checkedNode and this.getRightOperand() = strLitNode
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAGuardedNode() { result.asExpr() = checkedNode }
|
||||
}
|
||||
|
||||
/**
|
||||
* A validation of a value by checking for inclusion in an array of string
|
||||
* literal values, for example in:
|
||||
*
|
||||
* ```rb
|
||||
* name = params[:user_name]
|
||||
* if %w(alice bob charlie).include? name
|
||||
* User.find_by("username = #{name}")
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* the `include?` call guards against `name` taking arbitrary values when used
|
||||
* in the `find_by` call.
|
||||
*/
|
||||
//
|
||||
class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
|
||||
CfgNodes::ExprNodes::MethodCallCfgNode {
|
||||
private CfgNode checkedNode;
|
||||
|
||||
StringConstArrayInclusionCall() {
|
||||
exists(ArrayLiteral aLit |
|
||||
this.getExpr().getMethodName() = "include?" and
|
||||
this.getExpr().getReceiver() = aLit
|
||||
|
|
||||
forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and
|
||||
this.getArgument(0) = checkedNode
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAGuardedNode() { result.asExpr() = checkedNode }
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
private import codeql_ruby.dataflow.internal.DataFlowPublic as DataFlow
|
||||
// Need to import since frameworks can extend `RemoteFlowSource::Range`
|
||||
private import codeql_ruby.Frameworks
|
||||
|
||||
/**
|
||||
* A data flow source of remote user input.
|
||||
|
||||
@@ -140,8 +140,6 @@ class Content extends TContent {
|
||||
/**
|
||||
* A guard that validates some expression.
|
||||
*/
|
||||
class BarrierGuard extends CfgNodes::ExprCfgNode {
|
||||
BarrierGuard() { none() }
|
||||
|
||||
abstract class BarrierGuard extends CfgNodes::ExprCfgNode {
|
||||
Node getAGuardedNode() { none() }
|
||||
}
|
||||
|
||||
@@ -15,9 +15,17 @@ predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
|
||||
*/
|
||||
cached
|
||||
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// operation involving `nodeFrom`
|
||||
exists(CfgNodes::ExprNodes::OperationCfgNode op |
|
||||
op = nodeTo.asExpr() and
|
||||
op.getAnOperand() = nodeFrom.asExpr() and
|
||||
not op.getExpr() instanceof AssignExpr
|
||||
)
|
||||
or
|
||||
// string interpolation of `nodeFrom` into `nodeTo`
|
||||
nodeFrom.asExpr() =
|
||||
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
or
|
||||
// element reference from nodeFrom
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ElementReferenceCfgNode).getReceiver()
|
||||
}
|
||||
|
||||
@@ -52,18 +52,49 @@ class ActiveRecordModelClassMethodCall extends MethodCall {
|
||||
or
|
||||
// e.g. Foo.joins(:bars).where(...)
|
||||
this.getReceiver() instanceof ActiveRecordModelClassMethodCall
|
||||
or
|
||||
// e.g. self.where(...) within an ActiveRecordModelClass
|
||||
this.getReceiver() instanceof Self and
|
||||
this.getEnclosingModule() instanceof ActiveRecordModelClass
|
||||
}
|
||||
}
|
||||
|
||||
private predicate methodWithSqlFragmentArg(string methodName, int argIndex) {
|
||||
methodName =
|
||||
[
|
||||
"delete_all", "destroy_all", "exists?", "find_by", "find_by_sql", "from", "group", "having",
|
||||
"joins", "lock", "not", "order", "pluck", "where"
|
||||
] and
|
||||
argIndex = 0
|
||||
private Expr sqlFragmentArgument(MethodCall call) {
|
||||
exists(string methodName |
|
||||
methodName = call.getMethodName() and
|
||||
(
|
||||
methodName =
|
||||
[
|
||||
"delete_all", "destroy_all", "exists?", "find_by", "find_by_sql", "from", "group",
|
||||
"having", "joins", "lock", "not", "order", "pluck", "where"
|
||||
] and
|
||||
result = call.getArgument(0)
|
||||
or
|
||||
methodName = "calculate" and result = call.getArgument(1)
|
||||
or
|
||||
// This format was supported until Rails 2.3.8
|
||||
methodName = ["all", "find", "first", "last"] and
|
||||
result = call.getKeywordArgument("conditions")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// An expression that, if tainted by unsanitized input, should not be used as
|
||||
// part of an argument to an SQL executing method
|
||||
private predicate unsafeSqlExpr(Expr sqlFragmentExpr) {
|
||||
// Literals containing an interpolated value
|
||||
exists(StringInterpolationComponent interpolated |
|
||||
interpolated = sqlFragmentExpr.(StringlikeLiteral).getComponent(_)
|
||||
)
|
||||
or
|
||||
methodName = "calculate" and argIndex = 1
|
||||
// String concatenations
|
||||
sqlFragmentExpr instanceof AddExpr
|
||||
or
|
||||
// Variable reads
|
||||
sqlFragmentExpr instanceof VariableReadAccess
|
||||
or
|
||||
// Method call
|
||||
sqlFragmentExpr instanceof MethodCall
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,29 +112,21 @@ private predicate methodWithSqlFragmentArg(string methodName, int argIndex) {
|
||||
* rather than just one with a matching name.
|
||||
*/
|
||||
class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
|
||||
// The name of the method invoked
|
||||
private string methodName;
|
||||
// The zero-indexed position of the SQL fragment sink argument
|
||||
private int sqlFragmentArgumentIndex;
|
||||
// The SQL fragment argument itself
|
||||
private Expr sqlFragmentExpr;
|
||||
|
||||
// TODO: `find` with `lock:` option also takes an SQL fragment
|
||||
// TODO: refine this further to account for cases where the method called has
|
||||
// been overriden to perform validation on its arguments
|
||||
PotentiallyUnsafeSqlExecutingMethodCall() {
|
||||
methodName = this.getMethodName() and
|
||||
sqlFragmentExpr = this.getArgument(sqlFragmentArgumentIndex) and
|
||||
methodWithSqlFragmentArg(methodName, sqlFragmentArgumentIndex) and
|
||||
(
|
||||
// select only literals containing an interpolated value...
|
||||
exists(StringInterpolationComponent interpolated |
|
||||
interpolated = sqlFragmentExpr.(StringlikeLiteral).getComponent(_)
|
||||
exists(Expr arg |
|
||||
arg = sqlFragmentArgument(this) and
|
||||
unsafeSqlExpr(sqlFragmentExpr) and
|
||||
(
|
||||
sqlFragmentExpr = arg
|
||||
or
|
||||
sqlFragmentExpr = arg.(ArrayLiteral).getElement(0)
|
||||
)
|
||||
or
|
||||
// ...or string concatenations...
|
||||
sqlFragmentExpr instanceof AddExpr
|
||||
or
|
||||
// ...or variable reads
|
||||
sqlFragmentExpr instanceof VariableReadAccess
|
||||
)
|
||||
}
|
||||
|
||||
@@ -124,3 +147,5 @@ class ActiveRecordSqlExecutionRange extends SqlExecution::Range {
|
||||
|
||||
override DataFlow::Node getSql() { result = this }
|
||||
}
|
||||
// TODO: model `ActiveRecord` sanitizers
|
||||
// https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html
|
||||
|
||||
64
ql/src/queries/security/cwe-089/SqlInjection.qhelp
Normal file
64
ql/src/queries/security/cwe-089/SqlInjection.qhelp
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
If a database query (such as a SQL or NoSQL query) is built from
|
||||
user-provided data without sufficient sanitization, a malicious user
|
||||
may be able to run malicious database queries.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Most database connector libraries offer a way of safely embedding
|
||||
untrusted data into a query by means of query parameters or
|
||||
prepared statements.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the following Rails example, an <code>ActionController</code> class
|
||||
has a <code>text_bio</code> method to handle requests to fetch a biography
|
||||
for a specified user.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The user is specified using a parameter, <code>user_name</code> provided by
|
||||
the client. This value is accessible using the <code>params</code> method.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The method illustrates three different ways to construct and execute an SQL
|
||||
query to find the user by name.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the first case, the parameter <code>user_name</code> is inserted into an
|
||||
SQL fragment using string interpolation. The parameter is user-supplied and
|
||||
is not sanitized. An attacker could use this to construct SQL queries that
|
||||
were not intended to be executed here.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The second case uses string concatenation and is vulnerable in the same way
|
||||
that the first case is.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the third case, the name is passed in a hash instead.
|
||||
<code>ActiveRecord</code> will construct a parameterized SQL query that is not
|
||||
vulnerable to SQL injection attacks.
|
||||
</p>
|
||||
|
||||
<sample src="examples/SqlInjection.rb" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/SQL_injection">SQL injection</a>.</li>
|
||||
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html">SQL Injection Prevention Cheat Sheet</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
38
ql/src/queries/security/cwe-089/SqlInjection.ql
Normal file
38
ql/src/queries/security/cwe-089/SqlInjection.ql
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @name SQL query built from user-controlled sources
|
||||
* @description Building a SQL query from user-controlled sources is vulnerable to insertion of
|
||||
* malicious SQL code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id rb/sql-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-089
|
||||
* external/owasp/owasp-a1
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql_ruby.Concepts
|
||||
import codeql_ruby.DataFlow
|
||||
import codeql_ruby.dataflow.BarrierGuards
|
||||
import codeql_ruby.dataflow.RemoteFlowSources
|
||||
import codeql_ruby.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class SQLInjectionConfiguration extends TaintTracking::Configuration {
|
||||
SQLInjectionConfiguration() { this = "SQLInjectionConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof SqlExecution }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof StringConstCompare or
|
||||
guard instanceof StringConstArrayInclusionCall
|
||||
}
|
||||
}
|
||||
|
||||
from SQLInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This SQL query depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
15
ql/src/queries/security/cwe-089/examples/SqlInjection.rb
Normal file
15
ql/src/queries/security/cwe-089/examples/SqlInjection.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
class UserController < ActionController::Base
|
||||
def text_bio
|
||||
# BAD -- Using string interpolation
|
||||
user = User.find_by "name = '#{params[:user_name]}'"
|
||||
|
||||
# BAD -- Using string concatenation
|
||||
find_str = "name = '" + params[:user_name] + "'"
|
||||
user = User.find_by(find_str)
|
||||
|
||||
# GOOD -- Using a hash to parameterize arguments
|
||||
user = User.find_by name: params[:user_name]
|
||||
|
||||
render plain: user&.text_bio
|
||||
end
|
||||
end
|
||||
@@ -3,11 +3,15 @@ activeRecordModelClasses
|
||||
| ActiveRecordInjection.rb:5:1:7:3 | User |
|
||||
| ActiveRecordInjection.rb:9:1:10:3 | Admin |
|
||||
activeRecordSqlExecutionRanges
|
||||
| ActiveRecordInjection.rb:19:30:19:44 | ...[...] |
|
||||
| ActiveRecordInjection.rb:22:21:22:41 | "id = #{...}" |
|
||||
| ActiveRecordInjection.rb:25:23:25:43 | "id = #{...}" |
|
||||
| ActiveRecordInjection.rb:28:16:28:21 | <<-SQL |
|
||||
| ActiveRecordInjection.rb:32:35:32:60 | "user.id = #{...}" |
|
||||
| ActiveRecordInjection.rb:45:21:45:33 | ... + ... |
|
||||
activeRecordModelClassMethodCalls
|
||||
| ActiveRecordInjection.rb:2:3:2:17 | call to has_many |
|
||||
| ActiveRecordInjection.rb:6:3:6:24 | call to belongs_to |
|
||||
| ActiveRecordInjection.rb:19:5:19:45 | call to calculate |
|
||||
| ActiveRecordInjection.rb:22:5:22:42 | call to delete_all |
|
||||
| ActiveRecordInjection.rb:25:5:25:45 | call to destroy_all |
|
||||
@@ -16,7 +20,9 @@ activeRecordModelClassMethodCalls
|
||||
| ActiveRecordInjection.rb:32:5:32:61 | call to where |
|
||||
| ActiveRecordInjection.rb:45:5:45:34 | call to delete_all |
|
||||
potentiallyUnsafeSqlExecutingMethodCall
|
||||
| ActiveRecordInjection.rb:19:5:19:45 | call to calculate |
|
||||
| ActiveRecordInjection.rb:22:5:22:42 | call to delete_all |
|
||||
| ActiveRecordInjection.rb:25:5:25:45 | call to destroy_all |
|
||||
| ActiveRecordInjection.rb:28:5:28:35 | call to where |
|
||||
| ActiveRecordInjection.rb:32:5:32:61 | call to where |
|
||||
| ActiveRecordInjection.rb:45:5:45:34 | call to delete_all |
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
class UserGroup < ActiveRecord::Base
|
||||
has_many :users
|
||||
end
|
||||
|
||||
class User < ApplicationRecord
|
||||
belongs_to :user_group
|
||||
|
||||
def self.authenticate(name, pass)
|
||||
# BAD: possible untrusted input interpolated into SQL fragment
|
||||
find(:first, :conditions => "name='#{name}' and pass='#{pass}'")
|
||||
end
|
||||
end
|
||||
|
||||
class Admin < User
|
||||
end
|
||||
|
||||
class FooController < ActionController::Base
|
||||
|
||||
MAX_USER_ID = 100_000
|
||||
|
||||
# A string tainted by user input is inserted into an SQL query
|
||||
def some_request_handler
|
||||
# BAD: executes `SELECT AVG(#{params[:column]}) FROM "users"`
|
||||
# where `params[:column]` is unsanitized
|
||||
User.calculate(:average, params[:column])
|
||||
|
||||
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
|
||||
# where `params[:id]` is unsanitized
|
||||
User.delete_all("id = '#{params[:id]}'")
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
|
||||
# where `params[:id]` is unsanitized
|
||||
User.destroy_all(["id = '#{params[:id]}'"])
|
||||
|
||||
# BAD: executes `SELECT "users".* FROM "users" WHERE id BETWEEN '#{params[:min_id]}' AND 100000`
|
||||
# where `params[:min_id]` is unsanitized
|
||||
User.where(<<-SQL, MAX_USER_ID)
|
||||
id BETWEEN '#{params[:min_id]}' AND ?
|
||||
SQL
|
||||
|
||||
# BAD: chained method case
|
||||
# executes `SELECT "users".* FROM "users" WHERE (NOT (user_id = 'params[:id]'))`
|
||||
# where `params[:id]` is unsanitized
|
||||
User.where.not("user.id = '#{params[:id]}'")
|
||||
|
||||
User.authenticate(params[:name], params[:pass])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class BarController < ApplicationController
|
||||
|
||||
def some_other_request_handler
|
||||
ps = params
|
||||
uid = ps[:id]
|
||||
uidEq = "= '#{uid}'"
|
||||
|
||||
# BAD: executes `DELETE FROM "users" WHERE (id = #{uid})`
|
||||
# where `uid` is unsantized
|
||||
User.delete_all("id " + uidEq)
|
||||
end
|
||||
|
||||
def sanitized_paths
|
||||
|
||||
dir = params[:order]
|
||||
# GOOD: barrier guard prevents taint flow
|
||||
dir = "DESC" unless dir == "ASC"
|
||||
User.order("name #{dir}")
|
||||
|
||||
name = params[:user_name]
|
||||
# GOOD: barrier guard prevents taint flow
|
||||
if %w(alice bob charlie).include? name
|
||||
User.find_by("username = #{name}")
|
||||
end
|
||||
|
||||
name = params[:user_name]
|
||||
# GOOD: hash arguments are sanitized by ActiveRecord
|
||||
User.find_by(user_name: name)
|
||||
end
|
||||
end
|
||||
|
||||
class BazController < BarController
|
||||
end
|
||||
42
ql/test/query-tests/security/cwe-089/SqlInjection.expected
Normal file
42
ql/test/query-tests/security/cwe-089/SqlInjection.expected
Normal file
@@ -0,0 +1,42 @@
|
||||
edges
|
||||
| ActiveRecordInjection.rb:8:25:8:28 | name : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" |
|
||||
| ActiveRecordInjection.rb:8:31:8:34 | pass : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" |
|
||||
| ActiveRecordInjection.rb:25:30:25:35 | call to params : | ActiveRecordInjection.rb:25:30:25:44 | ...[...] |
|
||||
| ActiveRecordInjection.rb:29:30:29:35 | call to params : | ActiveRecordInjection.rb:29:21:29:43 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:33:32:33:37 | call to params : | ActiveRecordInjection.rb:33:23:33:45 | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:38:21:38:26 | call to params : | ActiveRecordInjection.rb:37:16:37:21 | <<-SQL |
|
||||
| ActiveRecordInjection.rb:44:34:44:39 | call to params : | ActiveRecordInjection.rb:44:20:44:47 | "user.id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:46:23:46:28 | call to params : | ActiveRecordInjection.rb:46:23:46:35 | ...[...] : |
|
||||
| ActiveRecordInjection.rb:46:23:46:35 | ...[...] : | ActiveRecordInjection.rb:8:25:8:28 | name : |
|
||||
| ActiveRecordInjection.rb:46:38:46:43 | call to params : | ActiveRecordInjection.rb:46:38:46:50 | ...[...] : |
|
||||
| ActiveRecordInjection.rb:46:38:46:50 | ...[...] : | ActiveRecordInjection.rb:8:31:8:34 | pass : |
|
||||
| ActiveRecordInjection.rb:54:10:54:15 | call to params : | ActiveRecordInjection.rb:60:21:60:33 | ... + ... |
|
||||
nodes
|
||||
| ActiveRecordInjection.rb:8:25:8:28 | name : | semmle.label | name : |
|
||||
| ActiveRecordInjection.rb:8:31:8:34 | pass : | semmle.label | pass : |
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | semmle.label | "name='#{...}' and pass='#{...}'" |
|
||||
| ActiveRecordInjection.rb:25:30:25:35 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:25:30:25:44 | ...[...] | semmle.label | ...[...] |
|
||||
| ActiveRecordInjection.rb:29:21:29:43 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:29:30:29:35 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:33:23:33:45 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:33:32:33:37 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:37:16:37:21 | <<-SQL | semmle.label | <<-SQL |
|
||||
| ActiveRecordInjection.rb:38:21:38:26 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:44:20:44:47 | "user.id = '#{...}'" | semmle.label | "user.id = '#{...}'" |
|
||||
| ActiveRecordInjection.rb:44:34:44:39 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:46:23:46:28 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:46:23:46:35 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ActiveRecordInjection.rb:46:38:46:43 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:46:38:46:50 | ...[...] : | semmle.label | ...[...] : |
|
||||
| ActiveRecordInjection.rb:54:10:54:15 | call to params : | semmle.label | call to params : |
|
||||
| ActiveRecordInjection.rb:60:21:60:33 | ... + ... | semmle.label | ... + ... |
|
||||
#select
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:46:23:46:28 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:46:23:46:28 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:46:38:46:43 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:46:38:46:43 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:25:30:25:44 | ...[...] | ActiveRecordInjection.rb:25:30:25:35 | call to params : | ActiveRecordInjection.rb:25:30:25:44 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:25:30:25:35 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:29:21:29:43 | "id = '#{...}'" | ActiveRecordInjection.rb:29:30:29:35 | call to params : | ActiveRecordInjection.rb:29:21:29:43 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:29:30:29:35 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:33:23:33:45 | "id = '#{...}'" | ActiveRecordInjection.rb:33:32:33:37 | call to params : | ActiveRecordInjection.rb:33:23:33:45 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:33:32:33:37 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:37:16:37:21 | <<-SQL | ActiveRecordInjection.rb:38:21:38:26 | call to params : | ActiveRecordInjection.rb:37:16:37:21 | <<-SQL | This SQL query depends on $@. | ActiveRecordInjection.rb:38:21:38:26 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:44:20:44:47 | "user.id = '#{...}'" | ActiveRecordInjection.rb:44:34:44:39 | call to params : | ActiveRecordInjection.rb:44:20:44:47 | "user.id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:44:34:44:39 | call to params | a user-provided value |
|
||||
| ActiveRecordInjection.rb:60:21:60:33 | ... + ... | ActiveRecordInjection.rb:54:10:54:15 | call to params : | ActiveRecordInjection.rb:60:21:60:33 | ... + ... | This SQL query depends on $@. | ActiveRecordInjection.rb:54:10:54:15 | call to params | a user-provided value |
|
||||
1
ql/test/query-tests/security/cwe-089/SqlInjection.qlref
Normal file
1
ql/test/query-tests/security/cwe-089/SqlInjection.qlref
Normal file
@@ -0,0 +1 @@
|
||||
queries/security/cwe-089/SqlInjection.ql
|
||||
Reference in New Issue
Block a user