Skip to content

Commit 96752f0

Browse files
authored
Merge pull request #10061 from geoffw0/cleartext
Swift: Queries for CWE-311 (originally CWE-200)
2 parents d5200ef + d3250a7 commit 96752f0

17 files changed

+812
-0
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* Provides classes for heuristically identifying expressions that contain
3+
* 'sensitive' data, meaning that they contain or return a password or other
4+
* credential, or sensitive private information.
5+
*/
6+
7+
import swift
8+
9+
private newtype TSensitiveDataType =
10+
TCredential() or
11+
TPrivateInfo()
12+
13+
/**
14+
* A type of sensitive expression.
15+
*/
16+
abstract class SensitiveDataType extends TSensitiveDataType {
17+
abstract string toString();
18+
19+
/**
20+
* Gets a regexp for identifying expressions of this type.
21+
*/
22+
abstract string getRegexp();
23+
}
24+
25+
/**
26+
* The type of sensitive expression for passwords and other credentials.
27+
*/
28+
class SensitiveCredential extends SensitiveDataType, TCredential {
29+
override string toString() { result = "credential" }
30+
31+
override string getRegexp() {
32+
result = ".*(password|passwd|accountid|account.?key|accnt.?key|license.?key|trusted).*"
33+
}
34+
}
35+
36+
/**
37+
* The type of sensitive expression for private information.
38+
*/
39+
class SensitivePrivateInfo extends SensitiveDataType, TPrivateInfo {
40+
override string toString() { result = "private information" }
41+
42+
override string getRegexp() {
43+
result =
44+
".*(" +
45+
// Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
46+
// Government identifiers, such as Social Security Numbers
47+
"social.?security|national.?insurance|" +
48+
// Contact information, such as home addresses
49+
"post.?code|zip.?code|home.?address|" +
50+
// and telephone numbers
51+
"telephone|home.?phone|mobile|fax.?no|fax.?number|" +
52+
// Geographic location - where the user is (or was)
53+
"latitude|longitude|" +
54+
// Financial data - such as credit card numbers, salary, bank accounts, and debts
55+
"credit.?card|debit.?card|salary|bank.?account|" +
56+
// Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
57+
"email|" +
58+
// Health - medical conditions, insurance status, prescription records
59+
"birthday|birth.?date|date.?of.?birth|medical|" +
60+
// Relationships - work and family
61+
"employer|spouse" +
62+
// ---
63+
").*"
64+
}
65+
}
66+
67+
/**
68+
* A regexp string to identify variables etc that might be safe because they
69+
* contain hashed or encrypted data, or are only a reference to data that is
70+
* actually stored elsewhere.
71+
*/
72+
private string regexpProbablySafe() { result = ".*(hash|crypt|file|path|invalid).*" }
73+
74+
/**
75+
* A `VarDecl` that might be used to contain sensitive data.
76+
*/
77+
private class SensitiveVarDecl extends VarDecl {
78+
SensitiveDataType sensitiveType;
79+
80+
SensitiveVarDecl() { this.getName().toLowerCase().regexpMatch(sensitiveType.getRegexp()) }
81+
82+
predicate hasInfo(string label, SensitiveDataType type) {
83+
label = this.getName() and
84+
sensitiveType = type
85+
}
86+
}
87+
88+
/**
89+
* An `AbstractFunctionDecl` that might be used to contain sensitive data.
90+
*/
91+
private class SensitiveFunctionDecl extends AbstractFunctionDecl {
92+
SensitiveDataType sensitiveType;
93+
94+
SensitiveFunctionDecl() { this.getName().toLowerCase().regexpMatch(sensitiveType.getRegexp()) }
95+
96+
predicate hasInfo(string label, SensitiveDataType type) {
97+
label = this.getName() and
98+
sensitiveType = type
99+
}
100+
}
101+
102+
/**
103+
* An `Argument` that might be used to contain sensitive data.
104+
*/
105+
private class SensitiveArgument extends Argument {
106+
SensitiveDataType sensitiveType;
107+
108+
SensitiveArgument() { this.getLabel().toLowerCase().regexpMatch(sensitiveType.getRegexp()) }
109+
110+
predicate hasInfo(string label, SensitiveDataType type) {
111+
label = this.getLabel() and
112+
sensitiveType = type
113+
}
114+
}
115+
116+
/**
117+
* An expression whose value might be sensitive data.
118+
*/
119+
class SensitiveExpr extends Expr {
120+
string label;
121+
SensitiveDataType sensitiveType;
122+
123+
SensitiveExpr() {
124+
// variable reference
125+
this.(DeclRefExpr).getDecl().(SensitiveVarDecl).hasInfo(label, sensitiveType)
126+
or
127+
// member variable reference
128+
this.(MemberRefExpr).getMember().(SensitiveVarDecl).hasInfo(label, sensitiveType)
129+
or
130+
// function call
131+
this.(ApplyExpr).getStaticTarget().(SensitiveFunctionDecl).hasInfo(label, sensitiveType)
132+
or
133+
// sensitive argument
134+
exists(SensitiveArgument a |
135+
a.hasInfo(label, sensitiveType) and
136+
a.getExpr() = this
137+
)
138+
}
139+
140+
/**
141+
* Gets the label associated with this expression.
142+
*/
143+
string getLabel() { result = label }
144+
145+
/**
146+
* Gets the type of sensitive expression this is.
147+
*/
148+
SensitiveDataType getSensitiveType() { result = sensitiveType }
149+
150+
/**
151+
* Holds if this sensitive expression might be safe because it contains
152+
* hashed or encrypted data, or is only a reference to data that is stored
153+
* elsewhere.
154+
*/
155+
predicate isProbablySafe() { label.toLowerCase().regexpMatch(regexpProbablySafe()) }
156+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
5+
<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>
6+
7+
</overview>
8+
<recommendation>
9+
10+
<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>
11+
12+
</recommendation>
13+
<example>
14+
15+
<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>
16+
17+
<sample src="CleartextStorageDatabase.swift" />
18+
19+
</example>
20+
<references>
21+
22+
<li>
23+
OWASP Top 10:2021:
24+
<a href="https://owasp.org/Top10/A02_2021-Cryptographic_Failures/">A02:2021 � Cryptographic Failures</a>.
25+
</li>
26+
27+
</references>
28+
</qhelp>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* @name Cleartext storage of sensitive information in a local database
3+
* @description Storing sensitive information in a non-encrypted
4+
* database can expose it to an attacker.
5+
* @kind path-problem
6+
* @problem.severity warning
7+
* @security-severity 7.5
8+
* @precision medium
9+
* @id swift/cleartext-storage-database
10+
* @tags security
11+
* external/cwe/cwe-312
12+
*/
13+
14+
import swift
15+
import codeql.swift.security.SensitiveExprs
16+
import codeql.swift.dataflow.DataFlow
17+
import codeql.swift.dataflow.TaintTracking
18+
import DataFlow::PathGraph
19+
20+
/**
21+
* An `Expr` that is stored in a local database.
22+
*/
23+
abstract class Stored extends Expr { }
24+
25+
/**
26+
* An `Expr` that is stored with the Core Data library.
27+
*/
28+
class CoreDataStore extends Stored {
29+
CoreDataStore() {
30+
// `content` arg to `NWConnection.send` is a sink
31+
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call |
32+
c.getName() = "NSManagedObject" and
33+
c.getAMember() = f and
34+
f.getName() = ["setValue(_:forKey:)", "setPrimitiveValue(_:forKey:)"] and
35+
call.getStaticTarget() = f and
36+
call.getArgument(0).getExpr() = this
37+
)
38+
}
39+
}
40+
41+
/**
42+
* An `Expr` that is stored with the Realm database library.
43+
*/
44+
class RealmStore extends Stored {
45+
RealmStore() {
46+
// `object` arg to `Realm.add` is a sink
47+
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call |
48+
c.getName() = "Realm" and
49+
c.getAMember() = f and
50+
f.getName() = "add(_:update:)" and
51+
call.getStaticTarget() = f and
52+
call.getArgument(0).getExpr() = this
53+
)
54+
or
55+
// `value` arg to `Realm.create` is a sink
56+
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call |
57+
c.getName() = "Realm" and
58+
c.getAMember() = f and
59+
f.getName() = "create(_:value:update:)" and
60+
call.getStaticTarget() = f and
61+
call.getArgument(1).getExpr() = this
62+
)
63+
}
64+
}
65+
66+
/**
67+
* A taint configuration from sensitive information to expressions that are
68+
* transmitted over a network.
69+
*/
70+
class CleartextStorageConfig extends TaintTracking::Configuration {
71+
CleartextStorageConfig() { this = "CleartextStorageConfig" }
72+
73+
override predicate isSource(DataFlow::Node node) {
74+
exists(SensitiveExpr e |
75+
node.asExpr() = e and
76+
not e.isProbablySafe()
77+
)
78+
}
79+
80+
override predicate isSink(DataFlow::Node node) { node.asExpr() instanceof Stored }
81+
82+
override predicate isSanitizerIn(DataFlow::Node node) {
83+
// make sources barriers so that we only report the closest instance
84+
isSource(node)
85+
}
86+
87+
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
88+
// flow out from fields of a `RealmSwiftObject` at the sink, for example in `obj.var = tainted; sink(obj)`.
89+
isSink(node) and
90+
exists(ClassDecl cd |
91+
c.getAReadContent().(DataFlow::Content::FieldContent).getField() = cd.getAMember() and
92+
cd.getType().(NominalType).getABaseType*().getName() = "RealmSwiftObject"
93+
)
94+
or
95+
// any default implicit reads
96+
super.allowImplicitRead(node, c)
97+
}
98+
}
99+
100+
from CleartextStorageConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode
101+
where config.hasFlowPath(sourceNode, sinkNode)
102+
select sinkNode.getNode(), sourceNode, sinkNode,
103+
"This operation stores '" + sinkNode.getNode().toString() +
104+
"' in a database. It may contain unencrypted sensitive data from $@", sourceNode,
105+
sourceNode.getNode().toString()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
func storeMyData(databaseObject : NSManagedObject, faveSong : String, creditCardNo : String) {
3+
// ...
4+
5+
// GOOD: not sensitive information
6+
databaseObject.setValue(faveSong, forKey: "myFaveSong")
7+
8+
// BAD: sensitive information saved in cleartext
9+
databaseObject.setValue(creditCardNo, forKey: "myCreditCardNo")
10+
11+
// GOOD: encrypted sensitive information saved
12+
databaseObject.setValue(encrypt(creditCardNo), forKey: "myCreditCardNo")
13+
14+
// ...
15+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
5+
<p>Sensitive information that is transmitted without encryption may be accessible to an attacker.</p>
6+
7+
</overview>
8+
<recommendation>
9+
10+
<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>
11+
12+
</recommendation>
13+
<example>
14+
15+
<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>
16+
17+
<sample src="CleartextTransmission.swift" />
18+
19+
</example>
20+
<references>
21+
22+
<li>
23+
OWASP Top 10:2021:
24+
<a href="https://owasp.org/Top10/A02_2021-Cryptographic_Failures/">A02:2021 � Cryptographic Failures</a>.
25+
</li>
26+
27+
</references>
28+
</qhelp>

0 commit comments

Comments
 (0)