Skip to content

Commit 53ea65b

Browse files
committed
Swift: Implement query.
1 parent 2d76d6d commit 53ea65b

File tree

3 files changed

+164
-9
lines changed

3 files changed

+164
-9
lines changed

swift/ql/src/queries/Security/CWE-095/UnsafeWebViewFetch.ql

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @name Unsafe WebView fetch
33
* @description TODO
4-
* @kind problem
4+
* @kind path-problem
55
* @problem.severity warning
66
* @security-severity TODO
77
* @precision high
@@ -13,5 +13,116 @@
1313
*/
1414

1515
import swift
16+
import codeql.swift.dataflow.DataFlow
17+
import codeql.swift.dataflow.TaintTracking
18+
import codeql.swift.dataflow.FlowSources
19+
import DataFlow::PathGraph
20+
import codeql.swift.frameworks.StandardLibrary.String
1621

17-
select "TODO"
22+
/**
23+
* A taint source that is `String(contentsOf:)`.
24+
* TODO: this shouldn't be needed when `StringSource` in `String.qll` is working.
25+
*/
26+
class StringContentsOfURLSource extends RemoteFlowSource {
27+
StringContentsOfURLSource() {
28+
exists(CallExpr call, AbstractFunctionDecl f |
29+
call.getFunction().(ApplyExpr).getStaticTarget() = f and
30+
f.getName() = "init(contentsOf:)" and
31+
f.getParam(0).getType().getName() = "URL" and
32+
this.asExpr() = call
33+
)
34+
}
35+
36+
override string getSourceType() { result = "" }
37+
}
38+
39+
/**
40+
* A sink that is a candidate result for this query, such as certain arguments
41+
* to `UIWebView.loadHTMLString`.
42+
*/
43+
class Sink extends DataFlow::Node {
44+
Expr baseURL;
45+
46+
Sink() {
47+
exists(
48+
AbstractFunctionDecl funcDecl, CallExpr call, string funcName, string paramName, int arg,
49+
int baseURLarg
50+
|
51+
// arguments to method calls...
52+
exists(string className, ClassDecl c |
53+
(
54+
// `loadHTMLString`
55+
className = ["UIWebView", "WKWebView"] and
56+
funcName = "loadHTMLString(_:baseURL:)" and
57+
paramName = "string"
58+
or
59+
// `UIWebView.load`
60+
className = "UIWebView" and
61+
funcName = "load(_:mimeType:textEncodingName:baseURL:)" and
62+
paramName = "data"
63+
or
64+
// `WKWebView.load`
65+
className = "WKWebView" and
66+
funcName = "load(_:mimeType:characterEncodingName:baseURL:)" and
67+
paramName = "data"
68+
) and
69+
c.getName() = className and
70+
c.getAMember() = funcDecl and
71+
call.getFunction().(ApplyExpr).getStaticTarget() = funcDecl
72+
) and
73+
// match up `funcName`, `paramName`, `arg`, `node`.
74+
funcDecl.getName() = funcName and
75+
funcDecl.getParam(pragma[only_bind_into](arg)).getName() = paramName and
76+
call.getArgument(pragma[only_bind_into](arg)).getExpr() = this.asExpr() and
77+
// match up `baseURLArg`
78+
funcDecl.getParam(pragma[only_bind_into](baseURLarg)).getName() = "baseURL" and
79+
call.getArgument(pragma[only_bind_into](baseURLarg)).getExpr() = baseURL
80+
)
81+
}
82+
83+
/**
84+
* Gets the `baseURL` argument associated with this sink.
85+
*/
86+
Expr getBaseURL() { result = baseURL }
87+
}
88+
89+
/**
90+
* Taint configuration from taint sources to sinks (and `baseURL` arguments)
91+
* for this query.
92+
*/
93+
class UnsafeWebViewFetchConfig extends TaintTracking::Configuration {
94+
UnsafeWebViewFetchConfig() { this = "UnsafeWebViewFetchConfig" }
95+
96+
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
97+
98+
override predicate isSink(DataFlow::Node node) {
99+
node instanceof Sink
100+
}
101+
102+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
103+
// allow flow through `try!` and similar constructs
104+
// TODO: this should probably be part of DataFlow / TaintTracking.
105+
node1.asExpr() = node2.asExpr().(AnyTryExpr).getSubExpr()
106+
or
107+
// allow flow through `!`
108+
// TODO: this should probably be part of DataFlow / TaintTracking.
109+
node1.asExpr() = node2.asExpr().(ForceValueExpr).getSubExpr()
110+
or
111+
// allow flow through string concatenation.
112+
// TODO: this should probably be part of TaintTracking.
113+
node2.asExpr().(AddExpr).getAnOperand() = node1.asExpr()
114+
}
115+
}
116+
117+
from
118+
UnsafeWebViewFetchConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode,
119+
Sink sink, string message
120+
where
121+
config.hasFlowPath(sourceNode, sinkNode) and
122+
sink = sinkNode.getNode() and
123+
(
124+
// base URL is nil
125+
sink.getBaseURL() instanceof NilLiteralExpr and
126+
message = "Tainted data is used in a WebView fetch without restricting the base URL."
127+
)
128+
select sinkNode, sourceNode, sinkNode, message
Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,45 @@
1-
| TODO |
1+
edges
2+
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : |
3+
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:120:25:120:39 | call to getRemoteData() |
4+
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : |
5+
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:167:25:167:39 | call to getRemoteData() |
6+
| UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:94:10:94:37 | try ... : |
7+
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:121:25:121:25 | remoteString |
8+
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:124:25:124:51 | ... call to +(_:_:) ... |
9+
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:135:25:135:25 | remoteString |
10+
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:137:25:137:25 | remoteString |
11+
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:139:25:139:25 | remoteString |
12+
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:141:25:141:25 | remoteString |
13+
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:168:25:168:25 | remoteString |
14+
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:171:25:171:51 | ... call to +(_:_:) ... |
15+
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:182:25:182:25 | remoteString |
16+
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:184:25:184:25 | remoteString |
17+
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:186:25:186:25 | remoteString |
18+
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:188:25:188:25 | remoteString |
19+
nodes
20+
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | semmle.label | try ... : |
21+
| UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | semmle.label | call to ... : |
22+
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | semmle.label | call to getRemoteData() : |
23+
| UnsafeWebViewFetch.swift:120:25:120:39 | call to getRemoteData() | semmle.label | call to getRemoteData() |
24+
| UnsafeWebViewFetch.swift:121:25:121:25 | remoteString | semmle.label | remoteString |
25+
| UnsafeWebViewFetch.swift:124:25:124:51 | ... call to +(_:_:) ... | semmle.label | ... call to +(_:_:) ... |
26+
| UnsafeWebViewFetch.swift:135:25:135:25 | remoteString | semmle.label | remoteString |
27+
| UnsafeWebViewFetch.swift:137:25:137:25 | remoteString | semmle.label | remoteString |
28+
| UnsafeWebViewFetch.swift:139:25:139:25 | remoteString | semmle.label | remoteString |
29+
| UnsafeWebViewFetch.swift:141:25:141:25 | remoteString | semmle.label | remoteString |
30+
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | semmle.label | call to getRemoteData() : |
31+
| UnsafeWebViewFetch.swift:167:25:167:39 | call to getRemoteData() | semmle.label | call to getRemoteData() |
32+
| UnsafeWebViewFetch.swift:168:25:168:25 | remoteString | semmle.label | remoteString |
33+
| UnsafeWebViewFetch.swift:171:25:171:51 | ... call to +(_:_:) ... | semmle.label | ... call to +(_:_:) ... |
34+
| UnsafeWebViewFetch.swift:182:25:182:25 | remoteString | semmle.label | remoteString |
35+
| UnsafeWebViewFetch.swift:184:25:184:25 | remoteString | semmle.label | remoteString |
36+
| UnsafeWebViewFetch.swift:186:25:186:25 | remoteString | semmle.label | remoteString |
37+
| UnsafeWebViewFetch.swift:188:25:188:25 | remoteString | semmle.label | remoteString |
38+
subpaths
39+
#select
40+
| UnsafeWebViewFetch.swift:120:25:120:39 | call to getRemoteData() | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:120:25:120:39 | call to getRemoteData() | Tainted data is used in a WebView fetch without restricting the base URL. |
41+
| UnsafeWebViewFetch.swift:121:25:121:25 | remoteString | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:121:25:121:25 | remoteString | Tainted data is used in a WebView fetch without restricting the base URL. |
42+
| UnsafeWebViewFetch.swift:124:25:124:51 | ... call to +(_:_:) ... | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:124:25:124:51 | ... call to +(_:_:) ... | Tainted data is used in a WebView fetch without restricting the base URL. |
43+
| UnsafeWebViewFetch.swift:167:25:167:39 | call to getRemoteData() | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:167:25:167:39 | call to getRemoteData() | Tainted data is used in a WebView fetch without restricting the base URL. |
44+
| UnsafeWebViewFetch.swift:168:25:168:25 | remoteString | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:168:25:168:25 | remoteString | Tainted data is used in a WebView fetch without restricting the base URL. |
45+
| UnsafeWebViewFetch.swift:171:25:171:51 | ... call to +(_:_:) ... | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:171:25:171:51 | ... call to +(_:_:) ... | Tainted data is used in a WebView fetch without restricting the base URL. |

swift/ql/test/query-tests/Security/CWE-095/UnsafeWebViewFetch.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ func testUIWebView() {
117117
let remoteString = getRemoteData()
118118

119119
webview.loadHTMLString(localString, baseURL: nil) // GOOD: the HTML data is local
120-
webview.loadHTMLString(getRemoteData(), baseURL: nil) // BAD: HTML contains remote input, may access local secrets [NOT DETECTED]
121-
webview.loadHTMLString(remoteString, baseURL: nil) // BAD [NOT DETECTED]
120+
webview.loadHTMLString(getRemoteData(), baseURL: nil) // BAD: HTML contains remote input, may access local secrets
121+
webview.loadHTMLString(remoteString, baseURL: nil) // BAD
122122

123123
webview.loadHTMLString("<html>" + localStringFragment + "</html>", baseURL: nil) // GOOD: the HTML data is local
124-
webview.loadHTMLString("<html>" + remoteString + "</html>", baseURL: nil) // BAD [NOT DETECTED]
124+
webview.loadHTMLString("<html>" + remoteString + "</html>", baseURL: nil) // BAD
125125

126126
webview.loadHTMLString("<html>\(localStringFragment)</html>", baseURL: nil) // GOOD: the HTML data is local
127127
webview.loadHTMLString("<html>\(remoteString)</html>", baseURL: nil) // BAD [NOT DETECTED]
@@ -164,11 +164,11 @@ func testWKWebView() {
164164
let remoteString = getRemoteData()
165165

166166
webview.loadHTMLString(localString, baseURL: nil) // GOOD: the HTML data is local
167-
webview.loadHTMLString(getRemoteData(), baseURL: nil) // BAD [NOT DETECTED]
168-
webview.loadHTMLString(remoteString, baseURL: nil) // BAD [NOT DETECTED]
167+
webview.loadHTMLString(getRemoteData(), baseURL: nil) // BAD
168+
webview.loadHTMLString(remoteString, baseURL: nil) // BAD
169169

170170
webview.loadHTMLString("<html>" + localStringFragment + "</html>", baseURL: nil) // GOOD: the HTML data is local
171-
webview.loadHTMLString("<html>" + remoteString + "</html>", baseURL: nil) // BAD [NOT DETECTED]
171+
webview.loadHTMLString("<html>" + remoteString + "</html>", baseURL: nil) // BAD
172172

173173
webview.loadHTMLString("<html>\(localStringFragment)</html>", baseURL: nil) // GOOD: the HTML data is local
174174
webview.loadHTMLString("<html>\(remoteString)</html>", baseURL: nil) // BAD [NOT DETECTED]

0 commit comments

Comments
 (0)