Skip to content

Commit 5c905c4

Browse files
committed
Swift: Initial UnsafeJsEval query
1 parent 8502939 commit 5c905c4

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* @name JavaScript Injection
3+
* @description Evaluating JavaScript code containing a substring from a remote source may lead to remote code execution.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @security-severity 6.1
7+
* @precision high
8+
* @id swift/unsafe-js-eval
9+
* @tags security
10+
* external/cwe/cwe-094
11+
* external/cwe/cwe-095
12+
* external/cwe/cwe-749
13+
*/
14+
15+
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+
21+
/**
22+
* A source of untrusted, user-controlled data.
23+
* TODO: Extend to more (non-remote) sources in the future.
24+
*/
25+
class Source = RemoteFlowSource;
26+
27+
/**
28+
* A sink that evaluates a string of JavaScript code.
29+
*/
30+
abstract class Sink extends DataFlow::Node { }
31+
32+
class WKWebView extends Sink {
33+
WKWebView() {
34+
any(CallExpr ce |
35+
ce.getStaticTarget() =
36+
getMethodWithQualifiedName("WKWebView",
37+
[
38+
"evaluateJavaScript(_:completionHandler:)",
39+
"evaluateJavaScript(_:in:in:completionHandler:)",
40+
"evaluateJavaScript(_:in:contentWorld:)",
41+
"callAsyncJavaScript(_:arguments:in:in:completionHandler:)",
42+
"callAsyncJavaScript(_:arguments:in:contentWorld:)"
43+
])
44+
).getArgument(0).getExpr() = this.asExpr()
45+
}
46+
}
47+
48+
class WKUserContentController extends Sink {
49+
WKUserContentController() {
50+
any(CallExpr ce |
51+
ce.getStaticTarget() =
52+
getMethodWithQualifiedName("WKUserContentController", "addUserScript(_:)")
53+
).getArgument(0).getExpr() = this.asExpr()
54+
}
55+
}
56+
57+
class UIWebView extends Sink {
58+
UIWebView() {
59+
any(CallExpr ce |
60+
ce.getStaticTarget() =
61+
getMethodWithQualifiedName(["UIWebView", "WebView"], "stringByEvaluatingJavaScript(from:)")
62+
).getArgument(0).getExpr() = this.asExpr()
63+
}
64+
}
65+
66+
class JSContext extends Sink {
67+
JSContext() {
68+
any(CallExpr ce |
69+
ce.getStaticTarget() =
70+
getMethodWithQualifiedName("JSContext",
71+
["evaluateScript(_:)", "evaluateScript(_:withSourceURL:)"])
72+
).getArgument(0).getExpr() = this.asExpr()
73+
}
74+
}
75+
76+
class JSEvaluateScript extends Sink {
77+
JSEvaluateScript() {
78+
any(CallExpr ce |
79+
ce.getStaticTarget() = getFunctionWithQualifiedName("JSEvaluateScript(_:_:_:_:_:_:)")
80+
).getArgument(1).getExpr() = this.asExpr()
81+
}
82+
}
83+
84+
// TODO: Consider moving the following to the library, e.g.
85+
// - Decl.hasQualifiedName(moduleName?, declaringDeclName?, declName)
86+
// - parentDecl = memberDecl.getDeclaringDecl() <=> parentDecl.getAMember() = memberDecl
87+
IterableDeclContext getDeclaringDeclOf(Decl member) { result.getAMember() = member }
88+
89+
MethodDecl getMethodWithQualifiedName(string className, string methodName) {
90+
result.getName() = methodName and
91+
getDeclaringDeclOf(result).(NominalTypeDecl).getName() = className
92+
}
93+
94+
AbstractFunctionDecl getFunctionWithQualifiedName(string funcName) {
95+
result.getName() = funcName and
96+
not result.hasSelfParam()
97+
}
98+
99+
/**
100+
* A taint configuration from taint sources to sinks for this query.
101+
*/
102+
class UnsafeJsEvalConfig extends TaintTracking::Configuration {
103+
UnsafeJsEvalConfig() { this = "UnsafeJsEvalConfig" }
104+
105+
override predicate isSource(DataFlow::Node node) { node instanceof Source }
106+
107+
override predicate isSink(DataFlow::Node node) { node instanceof Sink }
108+
109+
override predicate isSanitizer(DataFlow::Node node) {
110+
none() // TODO: A conversion to a primitive type or an enum
111+
}
112+
}
113+
114+
from
115+
UnsafeJsEvalConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode, Sink sink
116+
where
117+
config.hasFlowPath(sourceNode, sinkNode) and
118+
sink = sinkNode.getNode()
119+
select sink, sourceNode, sinkNode, "Evaluation of uncontrolled JavaScript from a remote source."

0 commit comments

Comments
 (0)