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

Commit 03bbef7

Browse files
committed
Add models for the read side of golang.org/x/net/html
This covers cases where an HTML document is retrieved and then parts of its structure are output without proper escaping.
1 parent e4aa252 commit 03bbef7

File tree

9 files changed

+283
-0
lines changed

9 files changed

+283
-0
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Added partial support for the `golang.org/x/net/html` package, modelling tainted data flow from a retrieved HTML document to its attributes and other data.

ql/src/go.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,6 @@ import semmle.go.frameworks.Stdlib
4747
import semmle.go.frameworks.SystemCommandExecutors
4848
import semmle.go.frameworks.Testing
4949
import semmle.go.frameworks.WebSocket
50+
import semmle.go.frameworks.XNetHtml
5051
import semmle.go.frameworks.XPath
5152
import semmle.go.security.FlowSources
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `golang.org/x/net/html` subpackage.
3+
*
4+
* 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.
7+
*/
8+
9+
import go
10+
11+
/** Provides models of commonly used functions in the `golang.org/x/net/html` subpackage. */
12+
module XNetHtml {
13+
/** Gets the package name `golang.org/x/net/html`. */
14+
string packagePath() { result = "golang.org/x/net/html" }
15+
16+
private class EscapeString extends HtmlEscapeFunction, TaintTracking::FunctionModel {
17+
EscapeString() { this.hasQualifiedName(packagePath(), "EscapeString") }
18+
19+
override predicate hasTaintFlow(DataFlow::FunctionInput input, DataFlow::FunctionOutput output) {
20+
input.isParameter(0) and output.isResult()
21+
}
22+
}
23+
24+
private class FunctionModels extends TaintTracking::FunctionModel {
25+
FunctionModels() { hasQualifiedName(packagePath(), _) }
26+
27+
override predicate hasTaintFlow(DataFlow::FunctionInput input, DataFlow::FunctionOutput output) {
28+
getName() =
29+
["UnescapeString", "Parse", "ParseFragment", "ParseFragmentWithOptions", "ParseWithOptions",
30+
"NewTokenizer", "NewTokenizerFragment"] and
31+
input.isParameter(0) and
32+
output.isResult(0)
33+
}
34+
}
35+
36+
private class TokenizerMethodModels extends Method, TaintTracking::FunctionModel {
37+
TokenizerMethodModels() { this.hasQualifiedName(packagePath(), "Tokenizer", _) }
38+
39+
override predicate hasTaintFlow(DataFlow::FunctionInput input, DataFlow::FunctionOutput output) {
40+
getName() = ["Buffered", "Raw", "Text", "Token"] and input.isReceiver() and output.isResult(0)
41+
or
42+
getName() = "TagAttr" and input.isReceiver() and output.isResult(1)
43+
}
44+
}
45+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
edges
2+
| test.go:10:15:10:42 | call to Cookie : tuple type | test.go:14:15:14:55 | type conversion |
3+
| test.go:10:15:10:42 | call to Cookie : tuple type | test.go:14:42:14:47 | implicit dereference : Cookie |
4+
| test.go:14:42:14:47 | implicit dereference : Cookie | test.go:14:15:14:55 | type conversion |
5+
| test.go:14:42:14:47 | implicit dereference : Cookie | test.go:14:42:14:47 | implicit dereference : Cookie |
6+
| test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:17:15:17:31 | type conversion |
7+
| test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:17:22:17:25 | implicit dereference : Node |
8+
| test.go:17:22:17:25 | implicit dereference : Node | test.go:17:15:17:31 | type conversion |
9+
| test.go:17:22:17:25 | implicit dereference : Node | test.go:17:22:17:25 | implicit dereference : Node |
10+
| test.go:19:36:19:47 | selection of Body : ReadCloser | test.go:20:15:20:32 | type conversion |
11+
| test.go:19:36:19:47 | selection of Body : ReadCloser | test.go:20:22:20:26 | implicit dereference : Node |
12+
| test.go:20:22:20:26 | implicit dereference : Node | test.go:20:15:20:32 | type conversion |
13+
| test.go:20:22:20:26 | implicit dereference : Node | test.go:20:22:20:26 | implicit dereference : Node |
14+
| test.go:22:33:22:44 | selection of Body : ReadCloser | test.go:23:15:23:35 | type conversion |
15+
| test.go:22:33:22:44 | selection of Body : ReadCloser | test.go:23:22:23:29 | implicit dereference : Node |
16+
| test.go:23:22:23:29 | implicit dereference : Node | test.go:23:15:23:35 | type conversion |
17+
| test.go:23:22:23:29 | implicit dereference : Node | test.go:23:22:23:29 | implicit dereference : Node |
18+
| test.go:25:45:25:56 | selection of Body : ReadCloser | test.go:26:15:26:36 | type conversion |
19+
| test.go:25:45:25:56 | selection of Body : ReadCloser | test.go:26:22:26:30 | implicit dereference : Node |
20+
| test.go:26:22:26:30 | implicit dereference : Node | test.go:26:15:26:36 | type conversion |
21+
| 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 |
29+
nodes
30+
| test.go:10:15:10:42 | call to Cookie : tuple type | semmle.label | call to Cookie : tuple type |
31+
| test.go:14:15:14:55 | type conversion | semmle.label | type conversion |
32+
| test.go:14:42:14:47 | implicit dereference : Cookie | semmle.label | implicit dereference : Cookie |
33+
| test.go:16:24:16:35 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
34+
| test.go:17:15:17:31 | type conversion | semmle.label | type conversion |
35+
| test.go:17:22:17:25 | implicit dereference : Node | semmle.label | implicit dereference : Node |
36+
| test.go:19:36:19:47 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
37+
| test.go:20:15:20:32 | type conversion | semmle.label | type conversion |
38+
| test.go:20:22:20:26 | implicit dereference : Node | semmle.label | implicit dereference : Node |
39+
| test.go:22:33:22:44 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
40+
| test.go:23:15:23:35 | type conversion | semmle.label | type conversion |
41+
| test.go:23:22:23:29 | implicit dereference : Node | semmle.label | implicit dereference : Node |
42+
| test.go:25:45:25:56 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
43+
| test.go:26:15:26:36 | type conversion | semmle.label | type conversion |
44+
| 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 |
52+
#select
53+
| 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 |
54+
| 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 |
55+
| 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 |
56+
| 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 |
57+
| 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 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-079/ReflectedXss.ql
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module example.com/m
2+
3+
go 1.14
4+
5+
require (
6+
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb
7+
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package test
2+
3+
import (
4+
"golang.org/x/net/html"
5+
"net/http"
6+
)
7+
8+
func test(request *http.Request, writer http.ResponseWriter) {
9+
10+
cookie, _ := request.Cookie("SomeCookie")
11+
12+
writer.Write([]byte(html.EscapeString(cookie.Value))) // GOOD: escaped.
13+
14+
writer.Write([]byte(html.UnescapeString(cookie.Value))) // BAD: unescaped.
15+
16+
node, _ := html.Parse(request.Body)
17+
writer.Write([]byte(node.Data)) // BAD: writing unescaped HTML data
18+
19+
node2, _ := html.ParseWithOptions(request.Body)
20+
writer.Write([]byte(node2.Data)) // BAD: writing unescaped HTML data
21+
22+
nodes, _ := html.ParseFragment(request.Body, nil)
23+
writer.Write([]byte(nodes[0].Data)) // BAD: writing unescaped HTML data
24+
25+
nodes2, _ := html.ParseFragmentWithOptions(request.Body, nil)
26+
writer.Write([]byte(nodes2[0].Data)) // BAD: writing unescaped HTML data
27+
28+
tokenizer := html.NewTokenizer(request.Body)
29+
writer.Write(tokenizer.Buffered()) // BAD: writing unescaped HTML data
30+
writer.Write(tokenizer.Raw()) // BAD: writing unescaped HTML data
31+
_, value, _ := tokenizer.TagAttr()
32+
writer.Write(value) // BAD: writing unescaped HTML data
33+
writer.Write(tokenizer.Text()) // BAD: writing unescaped HTML data
34+
writer.Write([]byte(tokenizer.Token().Data)) // BAD: writing unescaped HTML data
35+
36+
}

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

Lines changed: 126 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# golang.org/x/net v0.0.0-20201010224723-4f7140c49acb
2+
## explicit
3+
golang.org/x/net

0 commit comments

Comments
 (0)