Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions swift/ql/lib/codeql/swift/security/SqlInjectionExtensions.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* Provides classes and predicates for reasoning about database
* queries built from user-controlled sources (that is, SQL injection
* vulnerabilities).
*/

import swift
import codeql.swift.dataflow.DataFlow
private import codeql.swift.dataflow.ExternalFlow

/**
* A dataflow sink for SQL injection vulnerabilities.
*/
abstract class SqlInjectionSink extends DataFlow::Node { }

/**
* A sanitizer for SQL injection vulnerabilities.
*/
abstract class SqlInjectionSanitizer extends DataFlow::Node { }

/**
* A unit class for adding additional taint steps.
*/
class SqlInjectionAdditionalTaintStep extends Unit {
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
}

/**
* A default SQL injection sink for the sqlite3 C API.
*/
private class CApiDefaultSqlInjectionSink extends SqlInjectionSink {
CApiDefaultSqlInjectionSink() {
// `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 default SQL injection sink for the `SQLite.swift` library.
*/
private class SQLiteSwiftDefaultSqlInjectionSink extends SqlInjectionSink {
SQLiteSwiftDefaultSqlInjectionSink() {
// 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 default SQL injection sink for the GRDB library.
*/
private class GrdbDefaultSqlInjectionSink extends SqlInjectionSink {
GrdbDefaultSqlInjectionSink() {
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 sink defined in a CSV model.
*/
private class DefaultSqlInjectionSink extends SqlInjectionSink {
DefaultSqlInjectionSink() { sinkNode(this, "sql") }
}
30 changes: 30 additions & 0 deletions swift/ql/lib/codeql/swift/security/SqlInjectionQuery.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* 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
import codeql.swift.security.SqlInjectionExtensions

/**
* 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 SqlInjectionSink }

override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer instanceof SqlInjectionSanitizer
}

override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
any(SqlInjectionAdditionalTaintStep s).step(nodeFrom, nodeTo)
}
}
151 changes: 151 additions & 0 deletions swift/ql/lib/codeql/swift/security/UnsafeJsEvalExtensions.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* Provides classes and predicates for reasoning about javascript
* evaluation vulnerabilities.
*/

import swift
import codeql.swift.dataflow.DataFlow
import codeql.swift.dataflow.FlowSources
private import codeql.swift.dataflow.ExternalFlow

/**
* A dataflow sink for javascript evaluation vulnerabilities.
*/
abstract class UnsafeJsEvalSink extends DataFlow::Node { }

/**
* A sanitizer for javascript evaluation vulnerabilities.
*/
abstract class UnsafeJsEvalSanitizer extends DataFlow::Node { }

/**
* A unit class for adding additional taint steps.
*/
class UnsafeJsEvalAdditionalTaintStep extends Unit {
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
}

/**
* A default SQL injection sink for the `WKWebView` interface.
*/
private class WKWebViewDefaultUnsafeJsEvalSink extends UnsafeJsEvalSink {
WKWebViewDefaultUnsafeJsEvalSink() {
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()
}
}

/**
* A default SQL injection sink for the `WKUserContentController` interface.
*/
private class WKUserContentControllerDefaultUnsafeJsEvalSink extends UnsafeJsEvalSink {
WKUserContentControllerDefaultUnsafeJsEvalSink() {
any(CallExpr ce |
ce.getStaticTarget()
.(MethodDecl)
.hasQualifiedName("WKUserContentController", "addUserScript(_:)")
).getArgument(0).getExpr() = this.asExpr()
}
}

/**
* A default SQL injection sink for the `UIWebView` and `WebView` interfaces.
*/
private class UIWebViewDefaultUnsafeJsEvalSink extends UnsafeJsEvalSink {
UIWebViewDefaultUnsafeJsEvalSink() {
any(CallExpr ce |
ce.getStaticTarget()
.(MethodDecl)
.hasQualifiedName(["UIWebView", "WebView"], "stringByEvaluatingJavaScript(from:)")
).getArgument(0).getExpr() = this.asExpr()
}
}

/**
* A default SQL injection sink for the `JSContext` interface.
*/
private class JSContextDefaultUnsafeJsEvalSink extends UnsafeJsEvalSink {
JSContextDefaultUnsafeJsEvalSink() {
any(CallExpr ce |
ce.getStaticTarget()
.(MethodDecl)
.hasQualifiedName("JSContext", ["evaluateScript(_:)", "evaluateScript(_:withSourceURL:)"])
).getArgument(0).getExpr() = this.asExpr()
}
}

/**
* A default SQL injection sink for the `JSEvaluateScript` function.
*/
private class JSEvaluateScriptDefaultUnsafeJsEvalSink extends UnsafeJsEvalSink {
JSEvaluateScriptDefaultUnsafeJsEvalSink() {
any(CallExpr ce |
ce.getStaticTarget().(FreeFunctionDecl).hasName("JSEvaluateScript(_:_:_:_:_:_:)")
).getArgument(1).getExpr() = this.asExpr()
}
}

/**
* A default SQL injection sanitrizer.
*/
private class DefaultUnsafeJsEvalAdditionalTaintStep extends UnsafeJsEvalAdditionalTaintStep {
override predicate step(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
)
}
}

/**
* A sink defined in a CSV model.
*/
private class DefaultUnsafeJsEvalSink extends UnsafeJsEvalSink {
DefaultUnsafeJsEvalSink() { sinkNode(this, "js-eval") }
}
29 changes: 29 additions & 0 deletions swift/ql/lib/codeql/swift/security/UnsafeJsEvalQuery.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 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
import codeql.swift.security.UnsafeJsEvalExtensions

/**
* 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 FlowSource }

override predicate isSink(DataFlow::Node node) { node instanceof UnsafeJsEvalSink }

override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer instanceof UnsafeJsEvalSanitizer
}

override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
any(UnsafeJsEvalAdditionalTaintStep s).step(nodeFrom, nodeTo)
}
}
Loading