mirror of
https://github.com/github/codeql.git
synced 2026-04-14 19:44:03 +02:00
Swift: Move query logic into .qlls.
This commit is contained in:
142
swift/ql/lib/codeql/swift/security/SqlInjectionQuery.qll
Normal file
142
swift/ql/lib/codeql/swift/security/SqlInjectionQuery.qll
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about database
|
||||
* queries built from user-controlled sources (that is, SQL injection
|
||||
* vulnerabilities).
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.dataflow.FlowSources
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that is a sink for a SQL string to be executed.
|
||||
*/
|
||||
abstract class SqlSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sink for the sqlite3 C API.
|
||||
*/
|
||||
class CApiSqlSink extends SqlSink {
|
||||
CApiSqlSink() {
|
||||
// `sqlite3_exec` and variants of `sqlite3_prepare`.
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(FreeFunctionDecl)
|
||||
.hasName([
|
||||
"sqlite3_exec(_:_:_:_:_:)", "sqlite3_prepare(_:_:_:_:_:)",
|
||||
"sqlite3_prepare_v2(_:_:_:_:_:)", "sqlite3_prepare_v3(_:_:_:_:_:_:)",
|
||||
"sqlite3_prepare16(_:_:_:_:_:)", "sqlite3_prepare16_v2(_:_:_:_:_:)",
|
||||
"sqlite3_prepare16_v3(_:_:_:_:_:_:)"
|
||||
]) and
|
||||
call.getArgument(1).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for the SQLite.swift library.
|
||||
*/
|
||||
class SQLiteSwiftSqlSink extends SqlSink {
|
||||
SQLiteSwiftSqlSink() {
|
||||
// Variants of `Connection.execute`, `connection.prepare` and `connection.scalar`.
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("Connection",
|
||||
["execute(_:)", "prepare(_:_:)", "run(_:_:)", "scalar(_:_:)"]) and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
or
|
||||
// String argument to the `Statement` constructor.
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget().(MethodDecl).hasQualifiedName("Statement", "init(_:_:)") and
|
||||
call.getArgument(1).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A sink for the GRDB library. */
|
||||
class GrdbSqlSink extends SqlSink {
|
||||
GrdbSqlSink() {
|
||||
exists(CallExpr call, MethodDecl method |
|
||||
call.getStaticTarget() = method and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
|
|
||||
method
|
||||
.hasQualifiedName("Database",
|
||||
[
|
||||
"allStatements(sql:arguments:)", "cachedStatement(sql:)",
|
||||
"internalCachedStatement(sql:)", "execute(sql:arguments:)", "makeStatement(sql:)",
|
||||
"makeStatement(sql:prepFlags:)"
|
||||
])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("SQLRequest",
|
||||
[
|
||||
"init(stringLiteral:)", "init(unicodeScalarLiteral:)",
|
||||
"init(extendedGraphemeClusterLiteral:)", "init(stringInterpolation:)",
|
||||
"init(sql:arguments:adapter:cached:)"
|
||||
])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("SQL",
|
||||
[
|
||||
"init(stringLiteral:)", "init(unicodeScalarLiteral:)",
|
||||
"init(extendedGraphemeClusterLiteral:)", "init(stringInterpolation:)",
|
||||
"init(sql:arguments:)", "append(sql:arguments:)"
|
||||
])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("TableDefinition", ["column(sql:)", "check(sql:)", "constraint(sql:)"])
|
||||
or
|
||||
method.hasQualifiedName("TableAlteration", "addColumn(sql:)")
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("ColumnDefinition",
|
||||
["check(sql:)", "defaults(sql:)", "generatedAs(sql:_:)"])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("TableRecord",
|
||||
[
|
||||
"select(sql:arguments:)", "select(sql:arguments:as:)", "filter(sql:arguments:)",
|
||||
"order(sql:arguments:)"
|
||||
])
|
||||
or
|
||||
method.hasQualifiedName("StatementCache", "statement(_:)")
|
||||
)
|
||||
or
|
||||
exists(CallExpr call, MethodDecl method |
|
||||
call.getStaticTarget() = method and
|
||||
call.getArgument(1).getExpr() = this.asExpr()
|
||||
|
|
||||
method
|
||||
.hasQualifiedName(["Row", "DatabaseValueConvertible"],
|
||||
[
|
||||
"fetchCursor(_:sql:arguments:adapter:)", "fetchAll(_:sql:arguments:adapter:)",
|
||||
"fetchSet(_:sql:arguments:adapter:)", "fetchOne(_:sql:arguments:adapter:)"
|
||||
])
|
||||
or
|
||||
method.hasQualifiedName("SQLStatementCursor", "init(database:sql:arguments:prepFlags:)")
|
||||
)
|
||||
or
|
||||
exists(CallExpr call, MethodDecl method |
|
||||
call.getStaticTarget() = method and
|
||||
call.getArgument(3).getExpr() = this.asExpr()
|
||||
|
|
||||
method
|
||||
.hasQualifiedName("CommonTableExpression", "init(recursive:named:columns:sql:arguments:)")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration for tainted data that reaches a SQL sink.
|
||||
*/
|
||||
class SqlInjectionConfig extends TaintTracking::Configuration {
|
||||
SqlInjectionConfig() { this = "SqlInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof FlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof SqlSink }
|
||||
}
|
||||
129
swift/ql/lib/codeql/swift/security/UnsafeJsEvalQuery.qll
Normal file
129
swift/ql/lib/codeql/swift/security/UnsafeJsEvalQuery.qll
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about javascript
|
||||
* evaluation vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.dataflow.FlowSources
|
||||
|
||||
/**
|
||||
* A source of untrusted, user-controlled data.
|
||||
*/
|
||||
class Source = FlowSource;
|
||||
|
||||
/**
|
||||
* A sink that evaluates a string of JavaScript code.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
class WKWebView extends Sink {
|
||||
WKWebView() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("WKWebView",
|
||||
[
|
||||
"evaluateJavaScript(_:)", "evaluateJavaScript(_:completionHandler:)",
|
||||
"evaluateJavaScript(_:in:in:completionHandler:)",
|
||||
"evaluateJavaScript(_:in:contentWorld:)",
|
||||
"callAsyncJavaScript(_:arguments:in:in:completionHandler:)",
|
||||
"callAsyncJavaScript(_:arguments:in:contentWorld:)"
|
||||
])
|
||||
).getArgument(0).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class WKUserContentController extends Sink {
|
||||
WKUserContentController() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("WKUserContentController", "addUserScript(_:)")
|
||||
).getArgument(0).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class UIWebView extends Sink {
|
||||
UIWebView() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName(["UIWebView", "WebView"], "stringByEvaluatingJavaScript(from:)")
|
||||
).getArgument(0).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class JSContext extends Sink {
|
||||
JSContext() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("JSContext", ["evaluateScript(_:)", "evaluateScript(_:withSourceURL:)"])
|
||||
).getArgument(0).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class JSEvaluateScript extends Sink {
|
||||
JSEvaluateScript() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget().(FreeFunctionDecl).hasName("JSEvaluateScript(_:_:_:_:_:_:)")
|
||||
).getArgument(1).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration from taint sources to sinks for this query.
|
||||
*/
|
||||
class UnsafeJsEvalConfig extends TaintTracking::Configuration {
|
||||
UnsafeJsEvalConfig() { this = "UnsafeJsEvalConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof Sink }
|
||||
|
||||
// TODO: convert to new taint flow models
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(Argument arg |
|
||||
arg =
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget().(MethodDecl).hasQualifiedName("String", "init(decoding:as:)")
|
||||
).getArgument(0)
|
||||
or
|
||||
arg =
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(FreeFunctionDecl)
|
||||
.hasName([
|
||||
"JSStringCreateWithUTF8CString(_:)", "JSStringCreateWithCharacters(_:_:)",
|
||||
"JSStringRetain(_:)"
|
||||
])
|
||||
).getArgument(0)
|
||||
|
|
||||
nodeFrom.asExpr() = arg.getExpr() and
|
||||
nodeTo.asExpr() = arg.getApplyExpr()
|
||||
)
|
||||
or
|
||||
exists(CallExpr ce, Expr self, AbstractClosureExpr closure |
|
||||
ce.getStaticTarget()
|
||||
.getName()
|
||||
.matches(["withContiguousStorageIfAvailable(%)", "withUnsafeBufferPointer(%)"]) and
|
||||
self = ce.getQualifier() and
|
||||
ce.getArgument(0).getExpr() = closure
|
||||
|
|
||||
nodeFrom.asExpr() = self and
|
||||
nodeTo.(DataFlow::ParameterNode).getParameter() = closure.getParam(0)
|
||||
)
|
||||
or
|
||||
exists(MemberRefExpr e, Expr self, VarDecl member |
|
||||
self.getType().getName().matches(["Unsafe%Buffer%", "Unsafe%Pointer%"]) and
|
||||
member.getName() = "baseAddress"
|
||||
|
|
||||
e.getBase() = self and
|
||||
e.getMember() = member and
|
||||
nodeFrom.asExpr() = self and
|
||||
nodeTo.asExpr() = e
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about unsafe
|
||||
* webview fetch vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.dataflow.FlowSources
|
||||
|
||||
/**
|
||||
* A sink that is a candidate result for this query, such as certain arguments
|
||||
* to `UIWebView.loadHTMLString`.
|
||||
*/
|
||||
class Sink extends DataFlow::Node {
|
||||
Expr baseUrl;
|
||||
|
||||
Sink() {
|
||||
exists(
|
||||
MethodDecl funcDecl, CallExpr call, string className, string funcName, int arg, int baseArg
|
||||
|
|
||||
// arguments to method calls...
|
||||
(
|
||||
// `loadHTMLString`
|
||||
className = ["UIWebView", "WKWebView"] and
|
||||
funcName = "loadHTMLString(_:baseURL:)" and
|
||||
arg = 0 and
|
||||
baseArg = 1
|
||||
or
|
||||
// `UIWebView.load`
|
||||
className = "UIWebView" and
|
||||
funcName = "load(_:mimeType:textEncodingName:baseURL:)" and
|
||||
arg = 0 and
|
||||
baseArg = 3
|
||||
or
|
||||
// `WKWebView.load`
|
||||
className = "WKWebView" and
|
||||
funcName = "load(_:mimeType:characterEncodingName:baseURL:)" and
|
||||
arg = 0 and
|
||||
baseArg = 3
|
||||
) and
|
||||
call.getStaticTarget() = funcDecl and
|
||||
// match up `funcName`, `paramName`, `arg`, `node`.
|
||||
funcDecl.hasQualifiedName(className, funcName) and
|
||||
call.getArgument(arg).getExpr() = this.asExpr() and
|
||||
// match up `baseURLArg`
|
||||
call.getArgument(baseArg).getExpr() = baseUrl
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `baseURL` argument associated with this sink.
|
||||
*/
|
||||
Expr getBaseUrl() { result = baseUrl }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration from taint sources to sinks (and `baseURL` arguments)
|
||||
* for this query.
|
||||
*/
|
||||
class UnsafeWebViewFetchConfig extends TaintTracking::Configuration {
|
||||
UnsafeWebViewFetchConfig() { this = "UnsafeWebViewFetchConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
node instanceof Sink or
|
||||
node.asExpr() = any(Sink s).getBaseUrl()
|
||||
}
|
||||
}
|
||||
@@ -14,71 +14,9 @@
|
||||
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.dataflow.FlowSources
|
||||
import codeql.swift.security.UnsafeWebViewFetchQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A sink that is a candidate result for this query, such as certain arguments
|
||||
* to `UIWebView.loadHTMLString`.
|
||||
*/
|
||||
class Sink extends DataFlow::Node {
|
||||
Expr baseUrl;
|
||||
|
||||
Sink() {
|
||||
exists(
|
||||
MethodDecl funcDecl, CallExpr call, string className, string funcName, int arg, int baseArg
|
||||
|
|
||||
// arguments to method calls...
|
||||
(
|
||||
// `loadHTMLString`
|
||||
className = ["UIWebView", "WKWebView"] and
|
||||
funcName = "loadHTMLString(_:baseURL:)" and
|
||||
arg = 0 and
|
||||
baseArg = 1
|
||||
or
|
||||
// `UIWebView.load`
|
||||
className = "UIWebView" and
|
||||
funcName = "load(_:mimeType:textEncodingName:baseURL:)" and
|
||||
arg = 0 and
|
||||
baseArg = 3
|
||||
or
|
||||
// `WKWebView.load`
|
||||
className = "WKWebView" and
|
||||
funcName = "load(_:mimeType:characterEncodingName:baseURL:)" and
|
||||
arg = 0 and
|
||||
baseArg = 3
|
||||
) and
|
||||
call.getStaticTarget() = funcDecl and
|
||||
// match up `funcName`, `paramName`, `arg`, `node`.
|
||||
funcDecl.hasQualifiedName(className, funcName) and
|
||||
call.getArgument(arg).getExpr() = this.asExpr() and
|
||||
// match up `baseURLArg`
|
||||
call.getArgument(baseArg).getExpr() = baseUrl
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `baseURL` argument associated with this sink.
|
||||
*/
|
||||
Expr getBaseUrl() { result = baseUrl }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration from taint sources to sinks (and `baseURL` arguments)
|
||||
* for this query.
|
||||
*/
|
||||
class UnsafeWebViewFetchConfig extends TaintTracking::Configuration {
|
||||
UnsafeWebViewFetchConfig() { this = "UnsafeWebViewFetchConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
node instanceof Sink or
|
||||
node.asExpr() = any(Sink s).getBaseUrl()
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
UnsafeWebViewFetchConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode,
|
||||
Sink sink, string message
|
||||
|
||||
@@ -12,142 +12,9 @@
|
||||
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.dataflow.FlowSources
|
||||
import codeql.swift.security.SqlInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that is a sink for a SQL string to be executed.
|
||||
*/
|
||||
abstract class SqlSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sink for the sqlite3 C API.
|
||||
*/
|
||||
class CApiSqlSink extends SqlSink {
|
||||
CApiSqlSink() {
|
||||
// `sqlite3_exec` and variants of `sqlite3_prepare`.
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(FreeFunctionDecl)
|
||||
.hasName([
|
||||
"sqlite3_exec(_:_:_:_:_:)", "sqlite3_prepare(_:_:_:_:_:)",
|
||||
"sqlite3_prepare_v2(_:_:_:_:_:)", "sqlite3_prepare_v3(_:_:_:_:_:_:)",
|
||||
"sqlite3_prepare16(_:_:_:_:_:)", "sqlite3_prepare16_v2(_:_:_:_:_:)",
|
||||
"sqlite3_prepare16_v3(_:_:_:_:_:_:)"
|
||||
]) and
|
||||
call.getArgument(1).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink for the SQLite.swift library.
|
||||
*/
|
||||
class SQLiteSwiftSqlSink extends SqlSink {
|
||||
SQLiteSwiftSqlSink() {
|
||||
// Variants of `Connection.execute`, `connection.prepare` and `connection.scalar`.
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("Connection",
|
||||
["execute(_:)", "prepare(_:_:)", "run(_:_:)", "scalar(_:_:)"]) and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
or
|
||||
// String argument to the `Statement` constructor.
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget().(MethodDecl).hasQualifiedName("Statement", "init(_:_:)") and
|
||||
call.getArgument(1).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A sink for the GRDB library. */
|
||||
class GrdbSqlSink extends SqlSink {
|
||||
GrdbSqlSink() {
|
||||
exists(CallExpr call, MethodDecl method |
|
||||
call.getStaticTarget() = method and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
|
|
||||
method
|
||||
.hasQualifiedName("Database",
|
||||
[
|
||||
"allStatements(sql:arguments:)", "cachedStatement(sql:)",
|
||||
"internalCachedStatement(sql:)", "execute(sql:arguments:)", "makeStatement(sql:)",
|
||||
"makeStatement(sql:prepFlags:)"
|
||||
])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("SQLRequest",
|
||||
[
|
||||
"init(stringLiteral:)", "init(unicodeScalarLiteral:)",
|
||||
"init(extendedGraphemeClusterLiteral:)", "init(stringInterpolation:)",
|
||||
"init(sql:arguments:adapter:cached:)"
|
||||
])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("SQL",
|
||||
[
|
||||
"init(stringLiteral:)", "init(unicodeScalarLiteral:)",
|
||||
"init(extendedGraphemeClusterLiteral:)", "init(stringInterpolation:)",
|
||||
"init(sql:arguments:)", "append(sql:arguments:)"
|
||||
])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("TableDefinition", ["column(sql:)", "check(sql:)", "constraint(sql:)"])
|
||||
or
|
||||
method.hasQualifiedName("TableAlteration", "addColumn(sql:)")
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("ColumnDefinition",
|
||||
["check(sql:)", "defaults(sql:)", "generatedAs(sql:_:)"])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("TableRecord",
|
||||
[
|
||||
"select(sql:arguments:)", "select(sql:arguments:as:)", "filter(sql:arguments:)",
|
||||
"order(sql:arguments:)"
|
||||
])
|
||||
or
|
||||
method.hasQualifiedName("StatementCache", "statement(_:)")
|
||||
)
|
||||
or
|
||||
exists(CallExpr call, MethodDecl method |
|
||||
call.getStaticTarget() = method and
|
||||
call.getArgument(1).getExpr() = this.asExpr()
|
||||
|
|
||||
method
|
||||
.hasQualifiedName(["Row", "DatabaseValueConvertible"],
|
||||
[
|
||||
"fetchCursor(_:sql:arguments:adapter:)", "fetchAll(_:sql:arguments:adapter:)",
|
||||
"fetchSet(_:sql:arguments:adapter:)", "fetchOne(_:sql:arguments:adapter:)"
|
||||
])
|
||||
or
|
||||
method.hasQualifiedName("SQLStatementCursor", "init(database:sql:arguments:prepFlags:)")
|
||||
)
|
||||
or
|
||||
exists(CallExpr call, MethodDecl method |
|
||||
call.getStaticTarget() = method and
|
||||
call.getArgument(3).getExpr() = this.asExpr()
|
||||
|
|
||||
method
|
||||
.hasQualifiedName("CommonTableExpression", "init(recursive:named:columns:sql:arguments:)")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration for tainted data that reaches a SQL sink.
|
||||
*/
|
||||
class SqlInjectionConfig extends TaintTracking::Configuration {
|
||||
SqlInjectionConfig() { this = "SqlInjectionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof FlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof SqlSink }
|
||||
}
|
||||
|
||||
from SqlInjectionConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode
|
||||
where config.hasFlowPath(sourceNode, sinkNode)
|
||||
select sinkNode.getNode(), sourceNode, sinkNode, "This query depends on a $@.",
|
||||
|
||||
@@ -14,130 +14,9 @@
|
||||
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.dataflow.FlowSources
|
||||
import codeql.swift.security.UnsafeJsEvalQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A source of untrusted, user-controlled data.
|
||||
*/
|
||||
class Source = FlowSource;
|
||||
|
||||
/**
|
||||
* A sink that evaluates a string of JavaScript code.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
class WKWebView extends Sink {
|
||||
WKWebView() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("WKWebView",
|
||||
[
|
||||
"evaluateJavaScript(_:)", "evaluateJavaScript(_:completionHandler:)",
|
||||
"evaluateJavaScript(_:in:in:completionHandler:)",
|
||||
"evaluateJavaScript(_:in:contentWorld:)",
|
||||
"callAsyncJavaScript(_:arguments:in:in:completionHandler:)",
|
||||
"callAsyncJavaScript(_:arguments:in:contentWorld:)"
|
||||
])
|
||||
).getArgument(0).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class WKUserContentController extends Sink {
|
||||
WKUserContentController() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("WKUserContentController", "addUserScript(_:)")
|
||||
).getArgument(0).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class UIWebView extends Sink {
|
||||
UIWebView() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName(["UIWebView", "WebView"], "stringByEvaluatingJavaScript(from:)")
|
||||
).getArgument(0).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class JSContext extends Sink {
|
||||
JSContext() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("JSContext", ["evaluateScript(_:)", "evaluateScript(_:withSourceURL:)"])
|
||||
).getArgument(0).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
class JSEvaluateScript extends Sink {
|
||||
JSEvaluateScript() {
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget().(FreeFunctionDecl).hasName("JSEvaluateScript(_:_:_:_:_:_:)")
|
||||
).getArgument(1).getExpr() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration from taint sources to sinks for this query.
|
||||
*/
|
||||
class UnsafeJsEvalConfig extends TaintTracking::Configuration {
|
||||
UnsafeJsEvalConfig() { this = "UnsafeJsEvalConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof Sink }
|
||||
|
||||
// TODO: convert to new taint flow models
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(Argument arg |
|
||||
arg =
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget().(MethodDecl).hasQualifiedName("String", "init(decoding:as:)")
|
||||
).getArgument(0)
|
||||
or
|
||||
arg =
|
||||
any(CallExpr ce |
|
||||
ce.getStaticTarget()
|
||||
.(FreeFunctionDecl)
|
||||
.hasName([
|
||||
"JSStringCreateWithUTF8CString(_:)", "JSStringCreateWithCharacters(_:_:)",
|
||||
"JSStringRetain(_:)"
|
||||
])
|
||||
).getArgument(0)
|
||||
|
|
||||
nodeFrom.asExpr() = arg.getExpr() and
|
||||
nodeTo.asExpr() = arg.getApplyExpr()
|
||||
)
|
||||
or
|
||||
exists(CallExpr ce, Expr self, AbstractClosureExpr closure |
|
||||
ce.getStaticTarget()
|
||||
.getName()
|
||||
.matches(["withContiguousStorageIfAvailable(%)", "withUnsafeBufferPointer(%)"]) and
|
||||
self = ce.getQualifier() and
|
||||
ce.getArgument(0).getExpr() = closure
|
||||
|
|
||||
nodeFrom.asExpr() = self and
|
||||
nodeTo.(DataFlow::ParameterNode).getParameter() = closure.getParam(0)
|
||||
)
|
||||
or
|
||||
exists(MemberRefExpr e, Expr self, VarDecl member |
|
||||
self.getType().getName().matches(["Unsafe%Buffer%", "Unsafe%Pointer%"]) and
|
||||
member.getName() = "baseAddress"
|
||||
|
|
||||
e.getBase() = self and
|
||||
e.getMember() = member and
|
||||
nodeFrom.asExpr() = self and
|
||||
nodeTo.asExpr() = e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
UnsafeJsEvalConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode, Sink sink
|
||||
where
|
||||
|
||||
Reference in New Issue
Block a user