-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathHTMLTemplateEscapingPassthrough.ql
More file actions
153 lines (133 loc) · 5.59 KB
/
HTMLTemplateEscapingPassthrough.ql
File metadata and controls
153 lines (133 loc) · 5.59 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
/**
* @name HTML template escaping passthrough
* @description If a user-provided value is converted to a special type that avoids escaping when fed into a HTML
* template, it may result in XSS.
* @kind path-problem
* @problem.severity warning
* @id go/html-template-escaping-passthrough
* @tags security
* experimental
* external/cwe/cwe-079
*/
import go
/**
* Holds if the provided `untrusted` node flows into a conversion to a PassthroughType.
* The `targetType` parameter gets populated with the name of the PassthroughType,
* and `conversionSink` gets populated with the node where the conversion happens.
*/
predicate flowsFromUntrustedToConversion(
DataFlow::Node untrusted, PassthroughTypeName targetType, DataFlow::Node conversionSink
) {
exists(DataFlow::Node source |
UntrustedToPassthroughTypeConversionFlow::flow(source, conversionSink) and
source = untrusted and
UntrustedToPassthroughTypeConversionConfig::isSinkToPassthroughType(conversionSink, targetType)
)
}
/**
* A name of a type that will not be escaped when passed to
* a `html/template` template.
*/
class PassthroughTypeName extends string {
PassthroughTypeName() { this = ["HTML", "HTMLAttr", "JS", "JSStr", "CSS", "Srcset", "URL"] }
}
module UntrustedToPassthroughTypeConversionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
additional predicate isSinkToPassthroughType(DataFlow::TypeCastNode sink, PassthroughTypeName name) {
exists(Type typ |
typ = sink.getResultType() and
typ.getUnderlyingType*().hasQualifiedName("html/template", name)
)
}
predicate isSink(DataFlow::Node sink) { isSinkToPassthroughType(sink, _) }
predicate isBarrier(DataFlow::Node node) {
node instanceof SharedXss::Sanitizer or node.getType() instanceof NumericType
}
}
/**
* Tracks taint flow for reasoning about when a `ActiveThreatModelSource` is
* converted into a special "passthrough" type which will not be escaped by the
* template generator; this allows the injection of arbitrary content (html,
* css, js) into the generated output of the templates.
*/
module UntrustedToPassthroughTypeConversionFlow =
TaintTracking::Global<UntrustedToPassthroughTypeConversionConfig>;
/**
* Holds if the provided `conversion` node flows into the provided `execSink`.
*/
predicate flowsFromConversionToExec(
DataFlow::Node conversion, PassthroughTypeName targetType, DataFlow::Node execSink
) {
PassthroughTypeConversionToTemplateExecutionCallFlow::flow(conversion, execSink) and
PassthroughTypeConversionToTemplateExecutionCallConfig::isSourceConversionToPassthroughType(conversion,
targetType)
}
module PassthroughTypeConversionToTemplateExecutionCallConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { isSourceConversionToPassthroughType(source, _) }
additional predicate isSourceConversionToPassthroughType(
DataFlow::TypeCastNode source, PassthroughTypeName name
) {
exists(Type typ |
typ = source.getResultType() and
typ.getUnderlyingType*().hasQualifiedName("html/template", name)
)
}
predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) }
}
/**
* Tracks taint flow for reasoning about when the result of a conversion to a
* PassthroughType flows to a template execution call.
*/
module PassthroughTypeConversionToTemplateExecutionCallFlow =
TaintTracking::Global<PassthroughTypeConversionToTemplateExecutionCallConfig>;
/**
* Holds if the sink is a data value argument of a template execution call.
*/
predicate isSinkToTemplateExec(DataFlow::Node sink, DataFlow::CallNode call) {
exists(Method fn, string methodName |
fn.hasQualifiedName("html/template", "Template", methodName) and
call = fn.getACall()
|
methodName = "Execute" and sink = call.getArgument(1)
or
methodName = "ExecuteTemplate" and sink = call.getArgument(2)
)
}
module FromUntrustedToTemplateExecutionCallConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) }
}
/**
* Tracks taint flow from a `ActiveThreatModelSource` into a template executor
* call.
*/
module FromUntrustedToTemplateExecutionCallFlow =
TaintTracking::Global<FromUntrustedToTemplateExecutionCallConfig>;
import FromUntrustedToTemplateExecutionCallFlow::PathGraph
/**
* Holds if the provided `untrusted` node flows into the provided `execSink`.
*/
predicate flowsFromUntrustedToExec(
FromUntrustedToTemplateExecutionCallFlow::PathNode untrusted,
FromUntrustedToTemplateExecutionCallFlow::PathNode execSink
) {
FromUntrustedToTemplateExecutionCallFlow::flowPath(untrusted, execSink)
}
from
FromUntrustedToTemplateExecutionCallFlow::PathNode untrustedSource,
FromUntrustedToTemplateExecutionCallFlow::PathNode templateExecCall,
PassthroughTypeName targetTypeName, DataFlow::Node conversion
where
// A = untrusted remote flow source
// B = conversion to PassthroughType
// C = template execution call
// Flows:
// A -> B
flowsFromUntrustedToConversion(untrustedSource.getNode(), targetTypeName, conversion) and
// B -> C
flowsFromConversionToExec(conversion, targetTypeName, templateExecCall.getNode()) and
// A -> C
flowsFromUntrustedToExec(untrustedSource, templateExecCall)
select templateExecCall.getNode(), untrustedSource, templateExecCall,
"Data from an $@ will not be auto-escaped because it was $@ to template." + targetTypeName,
untrustedSource.getNode(), "untrusted source", conversion, "converted"