mirror of
https://github.com/github/codeql.git
synced 2026-04-24 08:15:14 +02:00
Merge pull request #12329 from geoffw0/network
Swift: Modernize the cleartext-* queries
This commit is contained in:
@@ -93,6 +93,7 @@ private module Frameworks {
|
||||
private import codeql.swift.frameworks.StandardLibrary.WebView
|
||||
private import codeql.swift.frameworks.Alamofire.Alamofire
|
||||
private import codeql.swift.security.CleartextLoggingExtensions
|
||||
private import codeql.swift.security.CleartextStorageDatabaseExtensions
|
||||
private import codeql.swift.security.PathInjectionExtensions
|
||||
private import codeql.swift.security.PredicateInjectionExtensions
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about cleartext database
|
||||
* storage vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* A dataflow sink for cleartext database storage vulnerabilities. That is,
|
||||
* a `DataFlow::Node` that is something stored in a local database.
|
||||
*/
|
||||
abstract class CleartextStorageDatabaseSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for cleartext database storage vulnerabilities.
|
||||
*/
|
||||
abstract class CleartextStorageDatabaseSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A unit class for adding additional taint steps.
|
||||
*/
|
||||
class CleartextStorageDatabaseAdditionalTaintStep extends Unit {
|
||||
/**
|
||||
* Holds if the step from `node1` to `node2` should be considered a taint
|
||||
* step for paths related to cleartext database storage vulnerabilities.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that is an expression stored with the Core Data library.
|
||||
*/
|
||||
private class CoreDataStore extends CleartextStorageDatabaseSink {
|
||||
CoreDataStore() {
|
||||
// values written into Core Data objects through `set*Value` methods are a sink.
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("NSManagedObject",
|
||||
["setValue(_:forKey:)", "setPrimitiveValue(_:forKey:)"]) and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
or
|
||||
// any write into a class derived from `NSManagedObject` is a sink. For
|
||||
// example in `coreDataObj.data = sensitive` the post-update node corresponding
|
||||
// with `coreDataObj.data` is a sink.
|
||||
// (ideally this would be only members with the `@NSManaged` attribute)
|
||||
exists(ClassOrStructDecl cd, Expr e |
|
||||
cd.getABaseTypeDecl*().getName() = "NSManagedObject" and
|
||||
this.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = e and
|
||||
e.getFullyConverted().getType() = cd.getType() and
|
||||
not e.(DeclRefExpr).getDecl() instanceof SelfParamDecl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that is an expression stored with the Realm database
|
||||
* library.
|
||||
*/
|
||||
private class RealmStore extends CleartextStorageDatabaseSink instanceof DataFlow::PostUpdateNode {
|
||||
RealmStore() {
|
||||
// any write into a class derived from `RealmSwiftObject` is a sink. For
|
||||
// example in `realmObj.data = sensitive` the post-update node corresponding
|
||||
// with `realmObj.data` is a sink.
|
||||
exists(ClassOrStructDecl cd, Expr e |
|
||||
cd.getABaseTypeDecl*().getName() = "RealmSwiftObject" and
|
||||
this.getPreUpdateNode().asExpr() = e and
|
||||
e.getFullyConverted().getType() = cd.getType() and
|
||||
not e.(DeclRefExpr).getDecl() instanceof SelfParamDecl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class CleartextStorageDatabaseSinks extends SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
// GRDB sinks
|
||||
";Database;true;allStatements(sql:arguments:);;;Argument[1];database-store",
|
||||
";Database;true;execute(sql:arguments:);;;Argument[1];database-store",
|
||||
";SQLRequest;true;init(sql:arguments:adapter:cached:);;;Argument[1];database-store",
|
||||
";SQL;true;init(sql:arguments:);;;Argument[1];database-store",
|
||||
";SQL;true;append(sql:arguments:);;;Argument[1];database-store",
|
||||
";SQLStatementCursor;true;init(database:sql:arguments:prepFlags:);;;Argument[2];database-store",
|
||||
";TableRecord;true;select(sql:arguments:);;;Argument[1];database-store",
|
||||
";TableRecord;true;select(sql:arguments:as:);;;Argument[1];database-store",
|
||||
";TableRecord;true;filter(sql:arguments:);;;Argument[1];database-store",
|
||||
";TableRecord;true;order(sql:arguments:);;;Argument[1];database-store",
|
||||
";Row;true;fetchCursor(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";Row;true;fetchAll(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";Row;true;fetchSet(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";Row;true;fetchOne(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";DatabaseValueConvertible;true;fetchCursor(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";DatabaseValueConvertible;true;fetchAll(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";DatabaseValueConvertible;true;fetchSet(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";DatabaseValueConvertible;true;fetchOne(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";FetchableRecord;true;fetchCursor(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";FetchableRecord;true;fetchAll(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";FetchableRecord;true;fetchSet(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";FetchableRecord;true;fetchOne(_:sql:arguments:adapter:);;;Argument[2];database-store",
|
||||
";FetchableRecord;true;fetchCursor(_:arguments:adapter:);;;Argument[1];database-store",
|
||||
";FetchableRecord;true;fetchAll(_:arguments:adapter:);;;Argument[1];database-store",
|
||||
";FetchableRecord;true;fetchSet(_:arguments:adapter:);;;Argument[1];database-store",
|
||||
";FetchableRecord;true;fetchOne(_:arguments:adapter:);;;Argument[1];database-store",
|
||||
";Statement;true;execute(arguments:);;;Argument[0];database-store",
|
||||
";CommonTableExpression;true;init(recursive:named:columns:sql:arguments:);;;Argument[4];database-store",
|
||||
";Statement;true;setArguments(_:);;;Argument[0];database-store"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An encryption sanitizer for cleartext database storage vulnerabilities.
|
||||
*/
|
||||
private class CleartextStorageDatabaseEncryptionSanitizer extends CleartextStorageDatabaseSanitizer {
|
||||
CleartextStorageDatabaseEncryptionSanitizer() { this.asExpr() instanceof EncryptedExpr }
|
||||
}
|
||||
|
||||
/**
|
||||
* An additional taint step for cleartext database storage vulnerabilities.
|
||||
* Needed until we have proper content flow through arrays.
|
||||
*/
|
||||
private class CleartextStorageDatabaseArrayAdditionalTaintStep extends CleartextStorageDatabaseAdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(ArrayExpr arr |
|
||||
nodeFrom.asExpr() = arr.getAnElement() and
|
||||
nodeTo.asExpr() = arr
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink defined in a CSV model.
|
||||
*/
|
||||
private class DefaultCleartextStorageDatabaseSink extends CleartextStorageDatabaseSink {
|
||||
DefaultCleartextStorageDatabaseSink() { sinkNode(this, "database-store") }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about cleartext
|
||||
* database storage vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.security.CleartextStorageDatabaseExtensions
|
||||
|
||||
/**
|
||||
* A taint configuration from sensitive information to expressions that are
|
||||
* transmitted over a network.
|
||||
*/
|
||||
class CleartextStorageConfig extends TaintTracking::Configuration {
|
||||
CleartextStorageConfig() { this = "CleartextStorageConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof CleartextStorageDatabaseSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node sanitizer) {
|
||||
sanitizer instanceof CleartextStorageDatabaseSanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
any(CleartextStorageDatabaseAdditionalTaintStep s).step(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
override predicate isSanitizerIn(DataFlow::Node node) {
|
||||
// make sources barriers so that we only report the closest instance
|
||||
isSource(node)
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
// flow out from fields of an `NSManagedObject` or `RealmSwiftObject` at the sink,
|
||||
// for example in `realmObj.data = sensitive`.
|
||||
isSink(node) and
|
||||
exists(ClassOrStructDecl cd, Decl cx |
|
||||
cd.getABaseTypeDecl*().getName() = ["NSManagedObject", "RealmSwiftObject"] and
|
||||
cx.asNominalTypeDecl() = cd and
|
||||
c.getAReadContent().(DataFlow::Content::FieldContent).getField() = cx.getAMember()
|
||||
)
|
||||
or
|
||||
// any default implicit reads
|
||||
super.allowImplicitRead(node, c)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about cleartext preferences
|
||||
* storage vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* A dataflow sink for cleartext preferences storage vulnerabilities. That is,
|
||||
* a `DataFlow::Node` of something that gets stored in an application
|
||||
* preference store.
|
||||
*/
|
||||
abstract class CleartextStoragePreferencesSink extends DataFlow::Node {
|
||||
abstract string getStoreName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for cleartext preferences storage vulnerabilities.
|
||||
*/
|
||||
abstract class CleartextStoragePreferencesSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A unit class for adding additional taint steps.
|
||||
*/
|
||||
class CleartextStoragePreferencesAdditionalTaintStep extends Unit {
|
||||
/**
|
||||
* Holds if the step from `node1` to `node2` should be considered a taint
|
||||
* step for paths related to cleartext preferences storage vulnerabilities.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
|
||||
}
|
||||
|
||||
/** The `DataFlow::Node` of an expression that gets written to the user defaults database */
|
||||
private class UserDefaultsStore extends CleartextStoragePreferencesSink {
|
||||
UserDefaultsStore() {
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget().(MethodDecl).hasQualifiedName("UserDefaults", "set(_:forKey:)") and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override string getStoreName() { result = "the user defaults database" }
|
||||
}
|
||||
|
||||
/** The `DataFlow::Node` of an expression that gets written to the iCloud-backed NSUbiquitousKeyValueStore */
|
||||
private class NSUbiquitousKeyValueStore extends CleartextStoragePreferencesSink {
|
||||
NSUbiquitousKeyValueStore() {
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("NSUbiquitousKeyValueStore", "set(_:forKey:)") and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override string getStoreName() { result = "iCloud" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A more complicated case, this is a macOS-only way of writing to
|
||||
* NSUserDefaults by modifying the `NSUserDefaultsController.values: Any`
|
||||
* object via reflection (`perform(Selector)`) or the `NSKeyValueCoding`,
|
||||
* `NSKeyValueBindingCreation` APIs. (TODO)
|
||||
*/
|
||||
private class NSUserDefaultsControllerStore extends CleartextStoragePreferencesSink {
|
||||
NSUserDefaultsControllerStore() { none() }
|
||||
|
||||
override string getStoreName() { result = "the user defaults database" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An encryption sanitizer for cleartext preferences storage vulnerabilities.
|
||||
*/
|
||||
private class CleartextStoragePreferencesEncryptionSanitizer extends CleartextStoragePreferencesSanitizer {
|
||||
CleartextStoragePreferencesEncryptionSanitizer() { this.asExpr() instanceof EncryptedExpr }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink defined in a CSV model.
|
||||
*/
|
||||
private class DefaultCleartextStoragePreferencesSink extends CleartextStoragePreferencesSink {
|
||||
DefaultCleartextStoragePreferencesSink() { sinkNode(this, "preferences-store") }
|
||||
|
||||
override string getStoreName() { result = "a preferences store" }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about cleartext
|
||||
* preferences storage vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.security.CleartextStoragePreferencesExtensions
|
||||
|
||||
/**
|
||||
* A taint configuration from sensitive information to expressions that are
|
||||
* stored as preferences.
|
||||
*/
|
||||
class CleartextStorageConfig extends TaintTracking::Configuration {
|
||||
CleartextStorageConfig() { this = "CleartextStorageConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof CleartextStoragePreferencesSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node sanitizer) {
|
||||
sanitizer instanceof CleartextStoragePreferencesSanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
any(CleartextStoragePreferencesAdditionalTaintStep s).step(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
override predicate isSanitizerIn(DataFlow::Node node) {
|
||||
// make sources barriers so that we only report the closest instance
|
||||
this.isSource(node)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about cleartext transmission
|
||||
* vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* A dataflow sink for cleartext transmission vulnerabilities. That is,
|
||||
* a `DataFlow::Node` of something that is transmitted over a network.
|
||||
*/
|
||||
abstract class CleartextTransmissionSink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for cleartext transmission vulnerabilities.
|
||||
*/
|
||||
abstract class CleartextTransmissionSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A unit class for adding additional taint steps.
|
||||
*/
|
||||
class CleartextTransmissionAdditionalTaintStep extends Unit {
|
||||
/**
|
||||
* Holds if the step from `node1` to `node2` should be considered a taint
|
||||
* step for paths related to cleartext transmission vulnerabilities.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Expr` that is transmitted with `NWConnection.send`.
|
||||
*/
|
||||
private class NWConnectionSendSink extends CleartextTransmissionSink {
|
||||
NWConnectionSendSink() {
|
||||
// `content` arg to `NWConnection.send` is a sink
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("NWConnection", "send(content:contentContext:isComplete:completion:)") and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Expr` that is used to form a `URL`. Such expressions are very likely to
|
||||
* be transmitted over a network, because that's what URLs are for.
|
||||
*/
|
||||
private class UrlSink extends CleartextTransmissionSink {
|
||||
UrlSink() {
|
||||
// `string` arg in `URL.init` is a sink
|
||||
// (we assume here that the URL goes on to be used in a network operation)
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("URL", ["init(string:)", "init(string:relativeTo:)"]) and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Expr` that transmitted through the Alamofire library.
|
||||
*/
|
||||
private class AlamofireTransmittedSink extends CleartextTransmissionSink {
|
||||
AlamofireTransmittedSink() {
|
||||
// sinks are the first argument containing the URL, and the `parameters`
|
||||
// and `headers` arguments to appropriate methods of `Session`.
|
||||
exists(CallExpr call, string fName |
|
||||
call.getStaticTarget().(MethodDecl).hasQualifiedName("Session", fName) and
|
||||
fName.regexpMatch("(request|streamRequest|download)\\(.*") and
|
||||
(
|
||||
call.getArgument(0).getExpr() = this.asExpr() or
|
||||
call.getArgumentWithLabel(["headers", "parameters"]).getExpr() = this.asExpr()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An encryption sanitizer for cleartext transmission vulnerabilities.
|
||||
*/
|
||||
private class CleartextTransmissionEncryptionSanitizer extends CleartextTransmissionSanitizer {
|
||||
CleartextTransmissionEncryptionSanitizer() { this.asExpr() instanceof EncryptedExpr }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink defined in a CSV model.
|
||||
*/
|
||||
private class DefaultCleartextTransmissionSink extends CleartextTransmissionSink {
|
||||
DefaultCleartextTransmissionSink() { sinkNode(this, "transmission") }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about cleartext
|
||||
* transmission vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.security.CleartextTransmissionExtensions
|
||||
|
||||
/**
|
||||
* A taint configuration from sensitive information to expressions that are
|
||||
* transmitted over a network.
|
||||
*/
|
||||
class CleartextTransmissionConfig extends TaintTracking::Configuration {
|
||||
CleartextTransmissionConfig() { this = "CleartextTransmissionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof CleartextTransmissionSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node sanitizer) {
|
||||
sanitizer instanceof CleartextTransmissionSanitizer
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
any(CleartextTransmissionAdditionalTaintStep s).step(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
override predicate isSanitizerIn(DataFlow::Node node) {
|
||||
// make sources barriers so that we only report the closest instance
|
||||
isSource(node)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
private import codeql.swift.dataflow.ExternalFlow
|
||||
import codeql.swift.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* A dataflow sink for SQL injection vulnerabilities.
|
||||
|
||||
@@ -12,160 +12,10 @@
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.security.CleartextStorageDatabaseQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that is something stored in a local database.
|
||||
*/
|
||||
abstract class Stored extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that is an expression stored with the Core Data library.
|
||||
*/
|
||||
class CoreDataStore extends Stored {
|
||||
CoreDataStore() {
|
||||
// values written into Core Data objects through `set*Value` methods are a sink.
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("NSManagedObject",
|
||||
["setValue(_:forKey:)", "setPrimitiveValue(_:forKey:)"]) and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
or
|
||||
// any write into a class derived from `NSManagedObject` is a sink. For
|
||||
// example in `coreDataObj.data = sensitive` the post-update node corresponding
|
||||
// with `coreDataObj.data` is a sink.
|
||||
// (ideally this would be only members with the `@NSManaged` attribute)
|
||||
exists(ClassOrStructDecl cd, Expr e |
|
||||
cd.getABaseTypeDecl*().getName() = "NSManagedObject" and
|
||||
this.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = e and
|
||||
e.getFullyConverted().getType() = cd.getType() and
|
||||
not e.(DeclRefExpr).getDecl() instanceof SelfParamDecl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that is an expression stored with the Realm database
|
||||
* library.
|
||||
*/
|
||||
class RealmStore extends Stored instanceof DataFlow::PostUpdateNode {
|
||||
RealmStore() {
|
||||
// any write into a class derived from `RealmSwiftObject` is a sink. For
|
||||
// example in `realmObj.data = sensitive` the post-update node corresponding
|
||||
// with `realmObj.data` is a sink.
|
||||
exists(ClassOrStructDecl cd, Expr e |
|
||||
cd.getABaseTypeDecl*().getName() = "RealmSwiftObject" and
|
||||
this.getPreUpdateNode().asExpr() = e and
|
||||
e.getFullyConverted().getType() = cd.getType() and
|
||||
not e.(DeclRefExpr).getDecl() instanceof SelfParamDecl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that is an expression stored with the GRDB library.
|
||||
*/
|
||||
class GrdbStore extends Stored {
|
||||
GrdbStore() {
|
||||
exists(CallExpr call, MethodDecl method |
|
||||
call.getStaticTarget() = method and
|
||||
call.getArgumentWithLabel("arguments").getExpr() = this.asExpr()
|
||||
|
|
||||
method
|
||||
.hasQualifiedName("Database",
|
||||
["allStatements(sql:arguments:)", "execute(sql:arguments:)",])
|
||||
or
|
||||
method.hasQualifiedName("SQLRequest", "init(sql:arguments:adapter:cached:)")
|
||||
or
|
||||
method.hasQualifiedName("SQL", ["init(sql:arguments:)", "append(sql:arguments:)"])
|
||||
or
|
||||
method.hasQualifiedName("SQLStatementCursor", "init(database:sql:arguments:prepFlags:)")
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("TableRecord",
|
||||
[
|
||||
"select(sql:arguments:)", "select(sql:arguments:as:)", "filter(sql:arguments:)",
|
||||
"order(sql:arguments:)"
|
||||
])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName(["Row", "DatabaseValueConvertible", "FetchableRecord"],
|
||||
[
|
||||
"fetchCursor(_:sql:arguments:adapter:)", "fetchAll(_:sql:arguments:adapter:)",
|
||||
"fetchSet(_:sql:arguments:adapter:)", "fetchOne(_:sql:arguments:adapter:)"
|
||||
])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("FetchableRecord",
|
||||
[
|
||||
"fetchCursor(_:arguments:adapter:)", "fetchAll(_:arguments:adapter:)",
|
||||
"fetchSet(_:arguments:adapter:)", "fetchOne(_:arguments:adapter:)",
|
||||
])
|
||||
or
|
||||
method.hasQualifiedName("Statement", ["execute(arguments:)"])
|
||||
or
|
||||
method
|
||||
.hasQualifiedName("CommonTableExpression", "init(recursive:named:columns:sql:arguments:)")
|
||||
)
|
||||
or
|
||||
exists(CallExpr call, MethodDecl method |
|
||||
call.getStaticTarget() = method and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
|
|
||||
method.hasQualifiedName("Statement", "setArguments(_:)")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration from sensitive information to expressions that are
|
||||
* transmitted over a network.
|
||||
*/
|
||||
class CleartextStorageConfig extends TaintTracking::Configuration {
|
||||
CleartextStorageConfig() { this = "CleartextStorageConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof Stored }
|
||||
|
||||
override predicate isSanitizerIn(DataFlow::Node node) {
|
||||
// make sources barriers so that we only report the closest instance
|
||||
isSource(node)
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
// encryption barrier
|
||||
node.asExpr() instanceof EncryptedExpr
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
// Needed until we have proper content flow through arrays
|
||||
exists(ArrayExpr arr |
|
||||
node1.asExpr() = arr.getAnElement() and
|
||||
node2.asExpr() = arr
|
||||
)
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
// flow out from fields of an `NSManagedObject` or `RealmSwiftObject` at the sink,
|
||||
// for example in `realmObj.data = sensitive`.
|
||||
isSink(node) and
|
||||
exists(ClassOrStructDecl cd, Decl cx |
|
||||
cd.getABaseTypeDecl*().getName() = ["NSManagedObject", "RealmSwiftObject"] and
|
||||
cx.asNominalTypeDecl() = cd and
|
||||
c.getAReadContent().(DataFlow::Content::FieldContent).getField() = cx.getAMember()
|
||||
)
|
||||
or
|
||||
// any default implicit reads
|
||||
super.allowImplicitRead(node, c)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a prettier node to use in the results.
|
||||
*/
|
||||
|
||||
@@ -12,88 +12,10 @@
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.security.CleartextTransmissionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* An `Expr` that is transmitted over a network.
|
||||
*/
|
||||
abstract class Transmitted extends Expr { }
|
||||
|
||||
/**
|
||||
* An `Expr` that is transmitted with `NWConnection.send`.
|
||||
*/
|
||||
class NWConnectionSend extends Transmitted {
|
||||
NWConnectionSend() {
|
||||
// `content` arg to `NWConnection.send` is a sink
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("NWConnection", "send(content:contentContext:isComplete:completion:)") and
|
||||
call.getArgument(0).getExpr() = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Expr` that is used to form a `URL`. Such expressions are very likely to
|
||||
* be transmitted over a network, because that's what URLs are for.
|
||||
*/
|
||||
class Url extends Transmitted {
|
||||
Url() {
|
||||
// `string` arg in `URL.init` is a sink
|
||||
// (we assume here that the URL goes on to be used in a network operation)
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("URL", ["init(string:)", "init(string:relativeTo:)"]) and
|
||||
call.getArgument(0).getExpr() = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Expr` that transmitted through the Alamofire library.
|
||||
*/
|
||||
class AlamofireTransmitted extends Transmitted {
|
||||
AlamofireTransmitted() {
|
||||
// sinks are the first argument containing the URL, and the `parameters`
|
||||
// and `headers` arguments to appropriate methods of `Session`.
|
||||
exists(CallExpr call, string fName |
|
||||
call.getStaticTarget().(MethodDecl).hasQualifiedName("Session", fName) and
|
||||
fName.regexpMatch("(request|streamRequest|download)\\(.*") and
|
||||
(
|
||||
call.getArgument(0).getExpr() = this or
|
||||
call.getArgumentWithLabel(["headers", "parameters"]).getExpr() = this
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration from sensitive information to expressions that are
|
||||
* transmitted over a network.
|
||||
*/
|
||||
class CleartextTransmissionConfig extends TaintTracking::Configuration {
|
||||
CleartextTransmissionConfig() { this = "CleartextTransmissionConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node.asExpr() instanceof Transmitted }
|
||||
|
||||
override predicate isSanitizerIn(DataFlow::Node node) {
|
||||
// make sources barriers so that we only report the closest instance
|
||||
isSource(node)
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
// encryption barrier
|
||||
node.asExpr() instanceof EncryptedExpr
|
||||
}
|
||||
}
|
||||
|
||||
from CleartextTransmissionConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode
|
||||
where config.hasFlowPath(sourceNode, sinkNode)
|
||||
select sinkNode.getNode(), sourceNode, sinkNode,
|
||||
|
||||
@@ -11,78 +11,10 @@
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.security.SensitiveExprs
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.dataflow.TaintTracking
|
||||
import codeql.swift.security.CleartextStoragePreferencesQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` of something that gets stored in an application preference store.
|
||||
*/
|
||||
abstract class Stored extends DataFlow::Node {
|
||||
abstract string getStoreName();
|
||||
}
|
||||
|
||||
/** The `DataFlow::Node` of an expression that gets written to the user defaults database */
|
||||
class UserDefaultsStore extends Stored {
|
||||
UserDefaultsStore() {
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget().(MethodDecl).hasQualifiedName("UserDefaults", "set(_:forKey:)") and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override string getStoreName() { result = "the user defaults database" }
|
||||
}
|
||||
|
||||
/** The `DataFlow::Node` of an expression that gets written to the iCloud-backed NSUbiquitousKeyValueStore */
|
||||
class NSUbiquitousKeyValueStore extends Stored {
|
||||
NSUbiquitousKeyValueStore() {
|
||||
exists(CallExpr call |
|
||||
call.getStaticTarget()
|
||||
.(MethodDecl)
|
||||
.hasQualifiedName("NSUbiquitousKeyValueStore", "set(_:forKey:)") and
|
||||
call.getArgument(0).getExpr() = this.asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override string getStoreName() { result = "iCloud" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A more complicated case, this is a macOS-only way of writing to
|
||||
* NSUserDefaults by modifying the `NSUserDefaultsController.values: Any`
|
||||
* object via reflection (`perform(Selector)`) or the `NSKeyValueCoding`,
|
||||
* `NSKeyValueBindingCreation` APIs. (TODO)
|
||||
*/
|
||||
class NSUserDefaultsControllerStore extends Stored {
|
||||
NSUserDefaultsControllerStore() { none() }
|
||||
|
||||
override string getStoreName() { result = "the user defaults database" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration from sensitive information to expressions that are
|
||||
* stored as preferences.
|
||||
*/
|
||||
class CleartextStorageConfig extends TaintTracking::Configuration {
|
||||
CleartextStorageConfig() { this = "CleartextStorageConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof SensitiveExpr }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof Stored }
|
||||
|
||||
override predicate isSanitizerIn(DataFlow::Node node) {
|
||||
// make sources barriers so that we only report the closest instance
|
||||
this.isSource(node)
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
// encryption barrier
|
||||
node.asExpr() instanceof EncryptedExpr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a prettier node to use in the results.
|
||||
*/
|
||||
@@ -97,6 +29,6 @@ from CleartextStorageConfig config, DataFlow::PathNode sourceNode, DataFlow::Pat
|
||||
where config.hasFlowPath(sourceNode, sinkNode)
|
||||
select cleanupNode(sinkNode.getNode()), sourceNode, sinkNode,
|
||||
"This operation stores '" + sinkNode.getNode().toString() + "' in " +
|
||||
sinkNode.getNode().(Stored).getStoreName() +
|
||||
sinkNode.getNode().(CleartextStoragePreferencesSink).getStoreName() +
|
||||
". It may contain unencrypted sensitive data from $@.", sourceNode,
|
||||
sourceNode.getNode().toString()
|
||||
|
||||
Reference in New Issue
Block a user