-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathExternalAPIs.qll
More file actions
155 lines (137 loc) · 5.73 KB
/
Copy pathExternalAPIs.qll
File metadata and controls
155 lines (137 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
* Definitions for reasoning about untrusted data used in APIs defined outside the
* database.
*/
overlay[local?]
module;
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
/**
* A `Method` that is considered a "safe" external API from a security perspective.
*/
abstract class SafeExternalApiMethod extends Method { }
/** The default set of "safe" external APIs. */
private class DefaultSafeExternalApiMethod extends SafeExternalApiMethod {
DefaultSafeExternalApiMethod() {
this instanceof EqualsMethod
or
this.hasName(["size", "length", "compareTo", "getClass", "lastIndexOf"])
or
this.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "Validate")
or
this.hasQualifiedName("java.util", "Objects", "equals")
or
this.getDeclaringType() instanceof TypeString and this.getName() = "equals"
or
this.getDeclaringType().hasQualifiedName("com.google.common.base", "Preconditions")
or
this.getDeclaringType().getPackage().getName().matches("org.junit%")
or
this.getDeclaringType().hasQualifiedName("com.google.common.base", "Strings") and
this.getName() = "isNullOrEmpty"
or
this.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "StringUtils") and
this.getName() = "isNotEmpty"
or
this.getDeclaringType().hasQualifiedName("java.lang", "Character") and
this.getName() = "isDigit"
or
this.getDeclaringType().hasQualifiedName("java.lang", "String") and
this.hasName(["equalsIgnoreCase", "regionMatches"])
or
this.getDeclaringType().hasQualifiedName("java.lang", "Boolean") and
this.getName() = "parseBoolean"
or
this.getDeclaringType().hasQualifiedName("org.apache.commons.io", "IOUtils") and
this.getName() = "closeQuietly"
or
this.getDeclaringType().hasQualifiedName("org.springframework.util", "StringUtils") and
this.hasName(["hasText", "isEmpty"])
}
}
/** A node representing data being passed to an external API. */
class ExternalApiDataNode extends DataFlow::Node {
Call call;
int i;
ExternalApiDataNode() {
(
// Argument to call to a method
this.asExpr() = call.getArgument(i)
or
// Qualifier to call to a method which returns non trivial value
this.asExpr() = call.getQualifier() and
i = -1 and
not call.getCallee().getReturnType() instanceof VoidType and
not call.getCallee().getReturnType() instanceof BooleanType
) and
// Defined outside the source archive
not call.getCallee().fromSource() and
// Not a call to an method which is overridden in source
not exists(Method m |
m.getASourceOverriddenMethod() = call.getCallee().getSourceDeclaration() and
m.fromSource()
) and
// Not already modeled as a taint step (we need both of these to handle `AdditionalTaintStep` subclasses as well)
not TaintTracking::localTaintStep(this, _) and
not TaintTracking::defaultAdditionalTaintStep(this, _, _) and
// Not a call to a known safe external API
not call.getCallee() instanceof SafeExternalApiMethod
}
/** Gets the called API `Method`. */
Method getMethod() { result = call.getCallee() }
/** Gets the index which is passed untrusted data (where -1 indicates the qualifier). */
int getIndex() { result = i }
/** Gets the description of the method being called. */
string getMethodDescription() { result = this.getMethod().getQualifiedName() }
}
/**
* Taint tracking configuration for flow from `ActiveThreatModelSource`s to `ExternalApiDataNode`s.
*/
module UntrustedDataToExternalApiConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
predicate observeDiffInformedIncrementalMode() {
any() // Simple use in UntrustedDataToExternalAPI.ql; also used through ExternalApiUsedWithUntrustedData in ExternalAPIsUsedWithUntrustedData.ql
}
}
/**
* Tracks flow from untrusted data to external APIs.
*/
module UntrustedDataToExternalApiFlow = TaintTracking::Global<UntrustedDataToExternalApiConfig>;
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flowTo(this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) }
}
/** An external API which is used with untrusted data. */
private newtype TExternalApi =
/** An untrusted API method `m` where untrusted data is passed at `index`. */
TExternalApiParameter(Method m, int index) {
exists(UntrustedExternalApiDataNode n |
m = n.getMethod() and
index = n.getIndex()
)
}
/** An external API which is used with untrusted data. */
class ExternalApiUsedWithUntrustedData extends TExternalApi {
/** Gets a possibly untrusted use of this external API. */
UntrustedExternalApiDataNode getUntrustedDataNode() {
this = TExternalApiParameter(result.getMethod(), result.getIndex())
}
/** Gets the number of untrusted sources used with this external API. */
int getNumberOfUntrustedSources() {
result = count(this.getUntrustedDataNode().getAnUntrustedSource())
}
/** Gets a textual representation of this element. */
string toString() {
exists(Method m, int index, string indexString |
if index = -1 then indexString = "qualifier" else indexString = "param " + index
|
this = TExternalApiParameter(m, index) and
result =
m.getDeclaringType().getQualifiedName() + "." + m.getSignature() + " [" + indexString + "]"
)
}
}