/
PotentialTimeBomb.ql
180 lines (162 loc) · 6.5 KB
/
PotentialTimeBomb.ql
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/**
* @name Potential Timebomb
* @description If there is data flow from a file's last modification date and an offset to a condition statement, this could trigger a "time bomb".
* @kind path-problem
* @precision Low
* @problem.severity warning
* @id cs/backdoor/potential-time-bomb
* @tags security
* solorigate
*/
import csharp
import DataFlow
query predicate nodes = PathGraph::nodes/3;
query predicate edges(DataFlow::PathNode a, DataFlow::PathNode b) {
PathGraph::edges(a, b)
or
exists(
FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable conf1,
FlowsFromTimeSpanArithmeticToTimeComparisonCallable conf2
|
conf1 = a.getConfiguration() and
conf1.isSink(a.getNode()) and
conf2 = b.getConfiguration() and
b.isSource()
)
or
exists(
FlowsFromTimeSpanArithmeticToTimeComparisonCallable conf1,
FlowsFromTimeComparisonCallableToSelectionStatementCondition conf2
|
conf1 = a.getConfiguration() and
conf1.isSink(a.getNode()) and
conf2 = b.getConfiguration() and
b.isSource()
)
}
/**
* Class that will help to find the source for the trigger file-modification date.
*
* May be extended as new patterns for similar time bombs are found.
*/
class GetLastWriteTimeMethod extends Method {
GetLastWriteTimeMethod() {
this.getQualifiedName() in [
"System.IO.File.GetLastWriteTime", "System.IO.File.GetFileCreationTime",
"System.IO.File.GetCreationTimeUtc", "System.IO.File.GetLastAccessTimeUtc"
]
}
}
/**
* Abstracts `System.DateTime` structure
*/
class DateTimeStruct extends Struct {
DateTimeStruct() { this.getQualifiedName() = "System.DateTime" }
/**
* holds if the Callable is used for DateTime arithmetic operations
*/
Callable getATimeSpanArtithmeticCallable() {
(result = this.getAnOperator() or result = this.getAMethod()) and
result.getName() in [
"Add", "AddDays", "AddHours", "AddMilliseconds", "AddMinutes", "AddMonths", "AddSeconds",
"AddTicks", "AddYears", "+", "-"
]
}
/**
* Holds if the Callable is used for DateTime comparision
*/
Callable getAComparisonCallable() {
(result = this.getAnOperator() or result = this.getAMethod()) and
result.getName() in ["Compare", "CompareTo", "Equals", "==", "!=", "<", ">", "<=", ">="]
}
}
/**
* Dataflow configuration to find flow from a GetLastWriteTime source to a DateTime arithmetic operation
*/
private class FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable extends TaintTracking::Configuration {
FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable() {
this = "FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable"
}
override predicate isSource(DataFlow::Node source) {
exists(Call call, GetLastWriteTimeMethod m |
m.getACall() = call and
source.asExpr() = call
)
}
override predicate isSink(DataFlow::Node sink) {
exists(Call call, DateTimeStruct dateTime |
call.getAChild*() = sink.asExpr() and
call = dateTime.getATimeSpanArtithmeticCallable().getACall()
)
}
}
/**
* Dataflow configuration to find flow from a DateTime arithmetic operation to a DateTime comparison operation
*/
private class FlowsFromTimeSpanArithmeticToTimeComparisonCallable extends TaintTracking::Configuration {
FlowsFromTimeSpanArithmeticToTimeComparisonCallable() {
this = "FlowsFromTimeSpanArithmeticToTimeComparisonCallable"
}
override predicate isSource(DataFlow::Node source) {
exists(DateTimeStruct dateTime, Call call | source.asExpr() = call |
call = dateTime.getATimeSpanArtithmeticCallable().getACall()
)
}
override predicate isSink(DataFlow::Node sink) {
exists(Call call, DateTimeStruct dateTime |
call.getAnArgument().getAChild*() = sink.asExpr() and
call = dateTime.getAComparisonCallable().getACall()
)
}
}
/**
* Dataflow configuration to find flow from a DateTime comparison operation to a Selection Statement (such as an If)
*/
private class FlowsFromTimeComparisonCallableToSelectionStatementCondition extends TaintTracking::Configuration {
FlowsFromTimeComparisonCallableToSelectionStatementCondition() {
this = "FlowsFromTimeComparisonCallableToSelectionStatementCondition"
}
override predicate isSource(DataFlow::Node source) {
exists(DateTimeStruct dateTime, Call call | source.asExpr() = call |
call = dateTime.getAComparisonCallable().getACall()
)
}
override predicate isSink(DataFlow::Node sink) {
exists(SelectionStmt sel | sel.getCondition().getAChild*() = sink.asExpr())
}
}
/**
* Holds if the last file modification date from the call to getLastWriteTimeMethodCall will be used in a DateTime arithmetic operation timeArithmeticCall,
* which is then used for a DateTime comparison timeComparisonCall and the result flows to a Selection statement which is likely a TimeBomb trigger
*/
predicate isPotentialTimeBomb(
DataFlow::PathNode pathSource, DataFlow::PathNode pathSink, Call getLastWriteTimeMethodCall,
Call timeArithmeticCall, Call timeComparisonCall, SelectionStmt selStatement
) {
exists(
FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable config1, Node sink,
DateTimeStruct dateTime, FlowsFromTimeSpanArithmeticToTimeComparisonCallable config2,
Node sink2, FlowsFromTimeComparisonCallableToSelectionStatementCondition config3, Node sink3
|
pathSource.getNode() = exprNode(getLastWriteTimeMethodCall) and
config1.hasFlow(exprNode(getLastWriteTimeMethodCall), sink) and
timeArithmeticCall = dateTime.getATimeSpanArtithmeticCallable().getACall() and
timeArithmeticCall.getAChild*() = sink.asExpr() and
config2.hasFlow(exprNode(timeArithmeticCall), sink2) and
timeComparisonCall = dateTime.getAComparisonCallable().getACall() and
timeComparisonCall.getAnArgument().getAChild*() = sink2.asExpr() and
config3.hasFlow(exprNode(timeComparisonCall), sink3) and
selStatement.getCondition().getAChild*() = sink3.asExpr() and
pathSink.getNode() = sink3
)
}
from
DataFlow::PathNode source, DataFlow::PathNode sink, Call getLastWriteTimeMethodCall,
Call timeArithmeticCall, Call timeComparisonCall, SelectionStmt selStatement
where
isPotentialTimeBomb(source, sink, getLastWriteTimeMethodCall, timeArithmeticCall,
timeComparisonCall, selStatement)
select selStatement, source, sink,
"Possible TimeBomb logic triggered by $@ that takes into account $@ from the $@ as part of the potential trigger.",
timeComparisonCall, timeComparisonCall.toString(), timeArithmeticCall, "an offset",
getLastWriteTimeMethodCall, "last modification time of a file"