-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Swift: Queries for CWE-311 (originally CWE-200) #10061
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
c1be060
dd51b7f
1c52836
2aa6dd2
dacb7f5
0cd2efc
456ab98
3126fb9
698a9e2
2690732
9a53a40
926da4b
7fd64f1
2d57786
645364e
4984d8f
430a8e1
3e4a6be
60fad4d
c0bc0d7
129ed42
92a927e
d3250a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/** | ||
* Provides classes for heuristically identifying expressions that contain | ||
* 'sensitive' data, meaning that they contain or return a password or other | ||
* credential, or sensitive private information. | ||
*/ | ||
|
||
import swift | ||
|
||
private newtype TSensitiveDataType = | ||
TCredential() or | ||
TPrivateInfo() | ||
|
||
/** | ||
* A type of sensitive expression. | ||
*/ | ||
abstract class SensitiveDataType extends TSensitiveDataType { | ||
abstract string toString(); | ||
|
||
/** | ||
* Gets a regexp for identifying expressions of this type. | ||
*/ | ||
abstract string getRegexp(); | ||
} | ||
|
||
/** | ||
* The type of sensitive expression for passwords and other credentials. | ||
*/ | ||
class SensitiveCredential extends SensitiveDataType, TCredential { | ||
override string toString() { result = "credential" } | ||
|
||
override string getRegexp() { | ||
result = ".*(password|passwd|accountid|account.?key|accnt.?key|license.?key|trusted).*" | ||
} | ||
} | ||
|
||
/** | ||
* The type of sensitive expression for private information. | ||
*/ | ||
class SensitivePrivateInfo extends SensitiveDataType, TPrivateInfo { | ||
override string toString() { result = "private information" } | ||
|
||
override string getRegexp() { | ||
result = | ||
".*(" + | ||
// Inspired by the list on https://cwe.mitre.org/data/definitions/359.html | ||
// Government identifiers, such as Social Security Numbers | ||
"social.?security|national.?insurance|" + | ||
// Contact information, such as home addresses | ||
"post.?code|zip.?code|home.?address|" + | ||
// and telephone numbers | ||
"telephone|home.?phone|mobile|fax.?no|fax.?number|" + | ||
// Geographic location - where the user is (or was) | ||
"latitude|longitude|" + | ||
// Financial data - such as credit card numbers, salary, bank accounts, and debts | ||
"credit.?card|debit.?card|salary|bank.?account|" + | ||
// Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc. | ||
"email|" + | ||
// Health - medical conditions, insurance status, prescription records | ||
"birthday|birth.?date|date.?of.?birth|medical|" + | ||
// Relationships - work and family | ||
"employer|spouse" + | ||
// --- | ||
").*" | ||
} | ||
} | ||
|
||
/** | ||
* A regexp string to identify variables etc that might be safe because they | ||
* contain hashed or encrypted data, or are only a reference to data that is | ||
* actually stored elsewhere. | ||
*/ | ||
private string regexpProbablySafe() { result = ".*(hash|crypt|file|path|invalid).*" } | ||
|
||
/** | ||
* A `VarDecl` that might be used to contain sensitive data. | ||
*/ | ||
private class SensitiveVarDecl extends VarDecl { | ||
SensitiveDataType sensitiveType; | ||
|
||
SensitiveVarDecl() { this.getName().toLowerCase().regexpMatch(sensitiveType.getRegexp()) } | ||
|
||
predicate hasInfo(string label, SensitiveDataType type) { | ||
label = this.getName() and | ||
sensitiveType = type | ||
} | ||
} | ||
|
||
/** | ||
* An `AbstractFunctionDecl` that might be used to contain sensitive data. | ||
*/ | ||
private class SensitiveFunctionDecl extends AbstractFunctionDecl { | ||
SensitiveDataType sensitiveType; | ||
|
||
SensitiveFunctionDecl() { this.getName().toLowerCase().regexpMatch(sensitiveType.getRegexp()) } | ||
|
||
predicate hasInfo(string label, SensitiveDataType type) { | ||
label = this.getName() and | ||
sensitiveType = type | ||
} | ||
} | ||
|
||
/** | ||
* An `Argument` that might be used to contain sensitive data. | ||
*/ | ||
private class SensitiveArgument extends Argument { | ||
SensitiveDataType sensitiveType; | ||
|
||
SensitiveArgument() { this.getLabel().toLowerCase().regexpMatch(sensitiveType.getRegexp()) } | ||
|
||
predicate hasInfo(string label, SensitiveDataType type) { | ||
label = this.getLabel() and | ||
sensitiveType = type | ||
} | ||
} | ||
|
||
/** | ||
* An expression whose value might be sensitive data. | ||
*/ | ||
class SensitiveExpr extends Expr { | ||
string label; | ||
SensitiveDataType sensitiveType; | ||
|
||
SensitiveExpr() { | ||
// variable reference | ||
this.(DeclRefExpr).getDecl().(SensitiveVarDecl).hasInfo(label, sensitiveType) | ||
or | ||
// member variable reference | ||
this.(MemberRefExpr).getMember().(SensitiveVarDecl).hasInfo(label, sensitiveType) | ||
or | ||
// function call | ||
this.(ApplyExpr).getStaticTarget().(SensitiveFunctionDecl).hasInfo(label, sensitiveType) | ||
or | ||
// sensitive argument | ||
exists(SensitiveArgument a | | ||
a.hasInfo(label, sensitiveType) and | ||
a.getExpr() = this | ||
) | ||
} | ||
|
||
/** | ||
* Gets the label associated with this expression. | ||
*/ | ||
string getLabel() { result = label } | ||
|
||
/** | ||
* Gets the type of sensitive expression this is. | ||
*/ | ||
SensitiveDataType getSensitiveType() { result = sensitiveType } | ||
|
||
/** | ||
* Holds if this sensitive expression might be safe because it contains | ||
* hashed or encrypted data, or is only a reference to data that is stored | ||
* elsewhere. | ||
*/ | ||
predicate isProbablySafe() { label.toLowerCase().regexpMatch(regexpProbablySafe()) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
|
||
<p>Sensitive information that is stored unencrypted in a database is accessible to an attacker who gains access to that database. For example the information could be accessed by any process or user in a rooted device, or exposed through another vulnerability.</p> | ||
|
||
</overview> | ||
<recommendation> | ||
|
||
<p>Either encrypt the entire database, or ensure that each piece of sensitive information is encrypted before being stored. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Avoid storing sensitive information at all if you do not need to keep it.</p> | ||
mchammer01 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
</recommendation> | ||
<example> | ||
|
||
<p>The following example shows three cases of storing information using the Core Data library. In the 'BAD' case, the data that is stored is sensitive (a credit card number) and is not encrypted. In the 'GOOD' cases, the data is either not sensitive or is protected with encryption.</p> | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<sample src="CleartextStorageDatabase.swift" /> | ||
|
||
</example> | ||
<references> | ||
|
||
<li> | ||
OWASP Top 10:2021: | ||
<a href="https://owasp.org/Top10/A02_2021-Cryptographic_Failures/">A02:2021 � Cryptographic Failures</a> | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</li> | ||
|
||
</references> | ||
</qhelp> |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,109 @@ | ||||||
/** | ||||||
* @name Cleartext storage of sensitive information in a local database | ||||||
* @description Storing sensitive information in a non-encrypted | ||||||
* database can expose it to an attacker. | ||||||
* @kind path-problem | ||||||
* @problem.severity warning | ||||||
* @security-severity 7.5 | ||||||
* @precision medium | ||||||
* @id swift/cleartext-storage-database | ||||||
* @tags security | ||||||
* external/cwe/cwe-312 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be CWE 311?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, CWE-312 is a child of CWE-311. The relevant part of the hierarchy looks something like this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for clarifying 🙇🏻♀️ |
||||||
*/ | ||||||
|
||||||
import swift | ||||||
import codeql.swift.security.SensitiveExprs | ||||||
import codeql.swift.dataflow.DataFlow | ||||||
import codeql.swift.dataflow.TaintTracking | ||||||
import DataFlow::PathGraph | ||||||
|
||||||
/** | ||||||
* An `Expr` that is stored in a local database. | ||||||
*/ | ||||||
abstract class Stored extends Expr { } | ||||||
|
||||||
/** | ||||||
* An `Expr` that is stored with the Core Data library. | ||||||
*/ | ||||||
class CoreDataStore extends Stored { | ||||||
CoreDataStore() { | ||||||
// `content` arg to `NWConnection.send` is a sink | ||||||
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call | | ||||||
c.getName() = "NSManagedObject" and | ||||||
c.getAMember() = f and | ||||||
f.getName() = ["setValue(_:forKey:)", "setPrimitiveValue(_:forKey:)"] and | ||||||
call.getFunction().(ApplyExpr).getStaticTarget() = f and | ||||||
call.getArgument(0).getExpr() = this | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* An `Expr` that is stored with the Realm database library. | ||||||
*/ | ||||||
class RealmStore extends Stored { | ||||||
RealmStore() { | ||||||
// `object` arg to `Realm.add` is a sink | ||||||
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call | | ||||||
c.getName() = "Realm" and | ||||||
c.getAMember() = f and | ||||||
f.getName() = ["add(_:update:)"] and | ||||||
|
||||||
call.getFunction().(ApplyExpr).getStaticTarget() = f and | ||||||
call.getArgument(0).getExpr() = this | ||||||
) | ||||||
or | ||||||
// `value` arg to `Realm.create` is a sink | ||||||
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call | | ||||||
c.getName() = "Realm" and | ||||||
c.getAMember() = f and | ||||||
f.getName() = ["create(_:value:update:)"] and | ||||||
|
||||||
call.getFunction().(ApplyExpr).getStaticTarget() = f and | ||||||
call.getArgument(1).getExpr() = this | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* 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) { | ||||||
exists(SensitiveExpr e | | ||||||
node.asExpr() = e and | ||||||
not e.isProbablySafe() | ||||||
) | ||||||
} | ||||||
|
||||||
override predicate isSink(DataFlow::Node node) { node.asExpr() instanceof Stored } | ||||||
|
||||||
override predicate isSanitizerIn(DataFlow::Node node) { | ||||||
// make sources barriers so that we only report the closest instance | ||||||
isSource(node) | ||||||
} | ||||||
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { | ||||||
// TODO: the following special case flows are required to catch any of the Realm test | ||||||
// cases, I hope we'll be able to remove them once we have field flow??? | ||||||
// flow out from field accesses, i.e. `a.b` -> `a` | ||||||
exists(MemberRefExpr m | | ||||||
node1.asExpr() = m and // `a.b` | ||||||
node2.asExpr() = m.getBaseExpr() // `a` | ||||||
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved
Hide resolved
|
||||||
) | ||||||
or | ||||||
// flow through assignment (!) | ||||||
exists(AssignExpr ae | | ||||||
node1.asExpr() = ae.getSource() and | ||||||
node2.asExpr() = ae.getDest() | ||||||
) | ||||||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
} | ||||||
|
||||||
from CleartextStorageConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode | ||||||
where config.hasFlowPath(sourceNode, sinkNode) | ||||||
select sinkNode.getNode(), sourceNode, sinkNode, | ||||||
"This operation stores '" + sinkNode.getNode().toString() + | ||||||
"' in a database. It may contain unencrypted sensitive data from $@", sourceNode, | ||||||
sourceNode.getNode().toString() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
|
||
func storeMyData(databaseObject : NSManagedObject, faveSong : String, creditCardNo : String) { | ||
// ... | ||
|
||
// GOOD: not sensitive information | ||
databaseObject.setValue(faveSong, forKey: "myFaveSong") | ||
|
||
// BAD: sensitive information saved in cleartext | ||
databaseObject.setValue(creditCardNo, forKey: "myCreditCardNo") | ||
|
||
// GOOD: encrypted sensitive information saved | ||
databaseObject.setValue(encrypt(creditCardNo), forKey: "myCreditCardNo") | ||
|
||
// ... | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
|
||
<p>Sensitive information that is transmitted without encryption may be accessible to an attacker.</p> | ||
|
||
</overview> | ||
<recommendation> | ||
|
||
<p>Ensure that sensitive information is always encrypted before being transmitted over the network. In general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext. Avoid transmitting sensitive information when it is not necessary to.</p> | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
</recommendation> | ||
<example> | ||
|
||
<p>The following example shows three cases of transmitting information. In the 'BAD' case, the data transmitted is sensitive (a credit card number) and is not encrypted. In the 'GOOD' cases, the data is either not sensitive or is protected with encryption.</p> | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<sample src="CleartextTransmission.swift" /> | ||
|
||
</example> | ||
<references> | ||
|
||
<li> | ||
OWASP Top 10:2021: | ||
<a href="https://owasp.org/Top10/A02_2021-Cryptographic_Failures/">A02:2021 � Cryptographic Failures</a> | ||
geoffw0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</li> | ||
|
||
</references> | ||
</qhelp> |
Uh oh!
There was an error while loading. Please reload this page.