Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 3817ae8

Browse files
committed
Add support for html.Render method.
This entails generalising Http::ResponseBody to account for any modelled function writing to a ResponseWriter.
1 parent 02f353e commit 3817ae8

File tree

6 files changed

+57
-41
lines changed

6 files changed

+57
-41
lines changed

change-notes/2020-10-12-x-net-html.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
lgtm,codescanning
22
* Added partial support for the `golang.org/x/net/html` package, modeling tainted data flow from a retrieved HTML document to its attributes and other data.
3+
* Modeled more ways of writing data to an `net/http.ResponseWriter`. This may produce more results from queries such as `go/reflected-xss` which look for data flowing to an HTTP response.

ql/src/semmle/go/frameworks/XNetHtml.qll

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
* Provides classes modeling security-relevant aspects of the `golang.org/x/net/html` subpackage.
33
*
44
* Currently we support the unmarshalling aspect of this package, conducting taint from an untrusted
5-
* reader to an untrusted `Node` tree or `Tokenizer` instance. We do not yet model adding a child
6-
* `Node` to a tree then calling `Render` yielding an untrustworthy string.
5+
* reader to an untrusted `Node` tree or `Tokenizer` instance, as well as simple remarshalling of `Node`s
6+
* that were already untrusted. We do not yet model adding a child `Node` to a tree then calling `Render`
7+
* yielding an untrustworthy string.
78
*/
89

910
import go
@@ -34,12 +35,18 @@ module XNetHtml {
3435
getName() = ["AppendChild", "InsertBefore"] and
3536
input.isParameter(0) and
3637
output.isReceiver()
38+
or
39+
getName() = "Render" and
40+
input.isParameter(1) and
41+
output.isParameter(0)
3742
}
3843
}
3944

4045
private class TokenizerMethodModels extends Method, TaintTracking::FunctionModel {
4146
TokenizerMethodModels() { this.hasQualifiedName(packagePath(), "Tokenizer", _) }
4247

48+
// Note that `TagName` and the key part of `TagAttr` are not sources by default under the assumption
49+
// that their character-set restrictions usually rule them out as useful attack routes.
4350
override predicate hasTaintFlow(DataFlow::FunctionInput input, DataFlow::FunctionOutput output) {
4451
getName() = ["Buffered", "Raw", "Text", "Token"] and input.isReceiver() and output.isResult(0)
4552
or

ql/src/semmle/go/frameworks/stdlib/NetHttp.qll

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -135,33 +135,31 @@ module NetHttp {
135135
}
136136

137137
private class ResponseBody extends HTTP::ResponseBody::Range, DataFlow::ArgumentNode {
138-
int arg;
138+
DataFlow::Node responseWriter;
139139

140140
ResponseBody() {
141141
exists(DataFlow::CallNode call |
142+
// A direct call to ResponseWriter.Write, conveying taint from the argument to the receiver
142143
call.getTarget().(Method).implements("net/http", "ResponseWriter", "Write") and
143-
arg = 0
144-
or
145-
(
146-
call.getTarget().hasQualifiedName("fmt", "Fprintf")
147-
or
148-
call.getTarget().hasQualifiedName("io", "WriteString")
149-
) and
150-
call.getArgument(0).getType().hasQualifiedName("net/http", "ResponseWriter") and
151-
arg >= 1
144+
this = call.getArgument(0) and
145+
responseWriter = call.(DataFlow::MethodCallNode).getReceiver()
146+
)
147+
or
148+
exists(
149+
TaintTracking::FunctionModel model, FunctionOutput modelOutput, FunctionInput modelInput,
150+
DataFlow::CallNode call
152151
|
153-
this = call.getArgument(arg)
152+
// A modelled function conveying taint from some input to the response writer,
153+
// e.g. `io.Copy(responseWriter, someTaintedReader)`
154+
call = model.getACall() and
155+
model.hasTaintFlow(modelInput, modelOutput) and
156+
this = modelInput.getNode(call) and
157+
responseWriter = modelOutput.getNode(call).(DataFlow::PostUpdateNode).getPreUpdateNode() and
158+
responseWriter.getType().implements("net/http", "ResponseWriter")
154159
)
155160
}
156161

157-
override HTTP::ResponseWriter getResponseWriter() {
158-
// the response writer is the receiver of this call
159-
result.getANode() = this.getCall().(DataFlow::MethodCallNode).getReceiver()
160-
or
161-
// the response writer is an argument to Fprintf or WriteString
162-
arg >= 1 and
163-
result.getANode() = this.getCall().getArgument(0)
164-
}
162+
override HTTP::ResponseWriter getResponseWriter() { result.getANode() = responseWriter }
165163
}
166164

167165
private class RedirectCall extends HTTP::Redirect::Range, DataFlow::CallNode {

ql/test/library-tests/semmle/go/frameworks/XNetHtml/ReflectedXss.expected

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ edges
55
| test.go:14:42:14:47 | implicit dereference : Cookie | test.go:14:42:14:47 | implicit dereference : Cookie |
66
| test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:17:15:17:31 | type conversion |
77
| test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:17:22:17:25 | implicit dereference : Node |
8+
| test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:28:22:28:25 | node |
89
| test.go:17:22:17:25 | implicit dereference : Node | test.go:17:15:17:31 | type conversion |
910
| test.go:17:22:17:25 | implicit dereference : Node | test.go:17:22:17:25 | implicit dereference : Node |
11+
| test.go:17:22:17:25 | implicit dereference : Node | test.go:28:22:28:25 | node |
1012
| test.go:19:36:19:47 | selection of Body : ReadCloser | test.go:20:15:20:32 | type conversion |
1113
| test.go:19:36:19:47 | selection of Body : ReadCloser | test.go:20:22:20:26 | implicit dereference : Node |
1214
| test.go:20:22:20:26 | implicit dereference : Node | test.go:20:15:20:32 | type conversion |
@@ -19,13 +21,13 @@ edges
1921
| test.go:25:45:25:56 | selection of Body : ReadCloser | test.go:26:22:26:30 | implicit dereference : Node |
2022
| test.go:26:22:26:30 | implicit dereference : Node | test.go:26:15:26:36 | type conversion |
2123
| test.go:26:22:26:30 | implicit dereference : Node | test.go:26:22:26:30 | implicit dereference : Node |
22-
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:29:15:29:34 | call to Buffered |
23-
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:30:15:30:29 | call to Raw |
24-
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:32:15:32:19 | value |
25-
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:33:15:33:30 | call to Text |
26-
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:34:15:34:44 | type conversion |
27-
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:34:22:34:38 | call to Token : Token |
28-
| test.go:34:22:34:38 | call to Token : Token | test.go:34:15:34:44 | type conversion |
24+
| test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:31:15:31:34 | call to Buffered |
25+
| test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:32:15:32:29 | call to Raw |
26+
| test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:34:15:34:19 | value |
27+
| test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:35:15:35:30 | call to Text |
28+
| test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:36:15:36:44 | type conversion |
29+
| test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:36:22:36:38 | call to Token : Token |
30+
| test.go:36:22:36:38 | call to Token : Token | test.go:36:15:36:44 | type conversion |
2931
nodes
3032
| test.go:10:15:10:42 | call to Cookie : tuple type | semmle.label | call to Cookie : tuple type |
3133
| test.go:14:15:14:55 | type conversion | semmle.label | type conversion |
@@ -42,21 +44,23 @@ nodes
4244
| test.go:25:45:25:56 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
4345
| test.go:26:15:26:36 | type conversion | semmle.label | type conversion |
4446
| test.go:26:22:26:30 | implicit dereference : Node | semmle.label | implicit dereference : Node |
45-
| test.go:28:33:28:44 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
46-
| test.go:29:15:29:34 | call to Buffered | semmle.label | call to Buffered |
47-
| test.go:30:15:30:29 | call to Raw | semmle.label | call to Raw |
48-
| test.go:32:15:32:19 | value | semmle.label | value |
49-
| test.go:33:15:33:30 | call to Text | semmle.label | call to Text |
50-
| test.go:34:15:34:44 | type conversion | semmle.label | type conversion |
51-
| test.go:34:22:34:38 | call to Token : Token | semmle.label | call to Token : Token |
47+
| test.go:28:22:28:25 | node | semmle.label | node |
48+
| test.go:30:33:30:44 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
49+
| test.go:31:15:31:34 | call to Buffered | semmle.label | call to Buffered |
50+
| test.go:32:15:32:29 | call to Raw | semmle.label | call to Raw |
51+
| test.go:34:15:34:19 | value | semmle.label | value |
52+
| test.go:35:15:35:30 | call to Text | semmle.label | call to Text |
53+
| test.go:36:15:36:44 | type conversion | semmle.label | type conversion |
54+
| test.go:36:22:36:38 | call to Token : Token | semmle.label | call to Token : Token |
5255
#select
5356
| test.go:14:15:14:55 | type conversion | test.go:10:15:10:42 | call to Cookie : tuple type | test.go:14:15:14:55 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:10:15:10:42 | call to Cookie | user-provided value |
5457
| test.go:17:15:17:31 | type conversion | test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:17:15:17:31 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:16:24:16:35 | selection of Body | user-provided value |
5558
| test.go:20:15:20:32 | type conversion | test.go:19:36:19:47 | selection of Body : ReadCloser | test.go:20:15:20:32 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:19:36:19:47 | selection of Body | user-provided value |
5659
| test.go:23:15:23:35 | type conversion | test.go:22:33:22:44 | selection of Body : ReadCloser | test.go:23:15:23:35 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:22:33:22:44 | selection of Body | user-provided value |
5760
| test.go:26:15:26:36 | type conversion | test.go:25:45:25:56 | selection of Body : ReadCloser | test.go:26:15:26:36 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:25:45:25:56 | selection of Body | user-provided value |
58-
| test.go:29:15:29:34 | call to Buffered | test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:29:15:29:34 | call to Buffered | Cross-site scripting vulnerability due to $@. | test.go:28:33:28:44 | selection of Body | user-provided value |
59-
| test.go:30:15:30:29 | call to Raw | test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:30:15:30:29 | call to Raw | Cross-site scripting vulnerability due to $@. | test.go:28:33:28:44 | selection of Body | user-provided value |
60-
| test.go:32:15:32:19 | value | test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:32:15:32:19 | value | Cross-site scripting vulnerability due to $@. | test.go:28:33:28:44 | selection of Body | user-provided value |
61-
| test.go:33:15:33:30 | call to Text | test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:33:15:33:30 | call to Text | Cross-site scripting vulnerability due to $@. | test.go:28:33:28:44 | selection of Body | user-provided value |
62-
| test.go:34:15:34:44 | type conversion | test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:34:15:34:44 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:28:33:28:44 | selection of Body | user-provided value |
61+
| test.go:28:22:28:25 | node | test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:28:22:28:25 | node | Cross-site scripting vulnerability due to $@. | test.go:16:24:16:35 | selection of Body | user-provided value |
62+
| test.go:31:15:31:34 | call to Buffered | test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:31:15:31:34 | call to Buffered | Cross-site scripting vulnerability due to $@. | test.go:30:33:30:44 | selection of Body | user-provided value |
63+
| test.go:32:15:32:29 | call to Raw | test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:32:15:32:29 | call to Raw | Cross-site scripting vulnerability due to $@. | test.go:30:33:30:44 | selection of Body | user-provided value |
64+
| test.go:34:15:34:19 | value | test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:34:15:34:19 | value | Cross-site scripting vulnerability due to $@. | test.go:30:33:30:44 | selection of Body | user-provided value |
65+
| test.go:35:15:35:30 | call to Text | test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:35:15:35:30 | call to Text | Cross-site scripting vulnerability due to $@. | test.go:30:33:30:44 | selection of Body | user-provided value |
66+
| test.go:36:15:36:44 | type conversion | test.go:30:33:30:44 | selection of Body : ReadCloser | test.go:36:15:36:44 | type conversion | Cross-site scripting vulnerability due to $@. | test.go:30:33:30:44 | selection of Body | user-provided value |

ql/test/library-tests/semmle/go/frameworks/XNetHtml/test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ func test(request *http.Request, writer http.ResponseWriter) {
2525
nodes2, _ := html.ParseFragmentWithOptions(request.Body, nil)
2626
writer.Write([]byte(nodes2[0].Data)) // BAD: writing unescaped HTML data
2727

28+
html.Render(writer, node) // BAD: rendering untrusted HTML to `writer`
29+
2830
tokenizer := html.NewTokenizer(request.Body)
2931
writer.Write(tokenizer.Buffered()) // BAD: writing unescaped HTML data
3032
writer.Write(tokenizer.Raw()) // BAD: writing unescaped HTML data

ql/test/library-tests/semmle/go/frameworks/XNetHtml/vendor/golang.org/x/net/html/stub.go

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)