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.
This commit is contained in:
Chris Smowton
2020-10-12 12:23:57 +01:00
parent e4aa252d6b
commit 03bbef7286
9 changed files with 283 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* 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.

View File

@@ -47,5 +47,6 @@ import semmle.go.frameworks.Stdlib
import semmle.go.frameworks.SystemCommandExecutors
import semmle.go.frameworks.Testing
import semmle.go.frameworks.WebSocket
import semmle.go.frameworks.XNetHtml
import semmle.go.frameworks.XPath
import semmle.go.security.FlowSources

View File

@@ -0,0 +1,45 @@
/**
* Provides classes modeling security-relevant aspects of the `golang.org/x/net/html` subpackage.
*
* Currently we support the unmarshalling aspect of this package, conducting taint from an untrusted
* reader to an untrusted `Node` tree or `Tokenizer` instance. We do not yet model adding a child
* `Node` to a tree then calling `Render` yielding an untrustworthy string.
*/
import go
/** Provides models of commonly used functions in the `golang.org/x/net/html` subpackage. */
module XNetHtml {
/** Gets the package name `golang.org/x/net/html`. */
string packagePath() { result = "golang.org/x/net/html" }
private class EscapeString extends HtmlEscapeFunction, TaintTracking::FunctionModel {
EscapeString() { this.hasQualifiedName(packagePath(), "EscapeString") }
override predicate hasTaintFlow(DataFlow::FunctionInput input, DataFlow::FunctionOutput output) {
input.isParameter(0) and output.isResult()
}
}
private class FunctionModels extends TaintTracking::FunctionModel {
FunctionModels() { hasQualifiedName(packagePath(), _) }
override predicate hasTaintFlow(DataFlow::FunctionInput input, DataFlow::FunctionOutput output) {
getName() =
["UnescapeString", "Parse", "ParseFragment", "ParseFragmentWithOptions", "ParseWithOptions",
"NewTokenizer", "NewTokenizerFragment"] and
input.isParameter(0) and
output.isResult(0)
}
}
private class TokenizerMethodModels extends Method, TaintTracking::FunctionModel {
TokenizerMethodModels() { this.hasQualifiedName(packagePath(), "Tokenizer", _) }
override predicate hasTaintFlow(DataFlow::FunctionInput input, DataFlow::FunctionOutput output) {
getName() = ["Buffered", "Raw", "Text", "Token"] and input.isReceiver() and output.isResult(0)
or
getName() = "TagAttr" and input.isReceiver() and output.isResult(1)
}
}
}

View File

@@ -0,0 +1,62 @@
edges
| test.go:10:15:10:42 | call to Cookie : tuple type | test.go:14:15:14:55 | type conversion |
| test.go:10:15:10:42 | call to Cookie : tuple type | test.go:14:42:14:47 | implicit dereference : Cookie |
| test.go:14:42:14:47 | implicit dereference : Cookie | test.go:14:15:14:55 | type conversion |
| test.go:14:42:14:47 | implicit dereference : Cookie | test.go:14:42:14:47 | implicit dereference : Cookie |
| test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:17:15:17:31 | type conversion |
| test.go:16:24:16:35 | selection of Body : ReadCloser | test.go:17:22:17:25 | implicit dereference : Node |
| test.go:17:22:17:25 | implicit dereference : Node | test.go:17:15:17:31 | type conversion |
| test.go:17:22:17:25 | implicit dereference : Node | test.go:17:22:17:25 | implicit dereference : Node |
| test.go:19:36:19:47 | selection of Body : ReadCloser | test.go:20:15:20:32 | type conversion |
| test.go:19:36:19:47 | selection of Body : ReadCloser | test.go:20:22:20:26 | implicit dereference : Node |
| test.go:20:22:20:26 | implicit dereference : Node | test.go:20:15:20:32 | type conversion |
| test.go:20:22:20:26 | implicit dereference : Node | test.go:20:22:20:26 | implicit dereference : Node |
| test.go:22:33:22:44 | selection of Body : ReadCloser | test.go:23:15:23:35 | type conversion |
| test.go:22:33:22:44 | selection of Body : ReadCloser | test.go:23:22:23:29 | implicit dereference : Node |
| test.go:23:22:23:29 | implicit dereference : Node | test.go:23:15:23:35 | type conversion |
| test.go:23:22:23:29 | implicit dereference : Node | test.go:23:22:23:29 | implicit dereference : Node |
| test.go:25:45:25:56 | selection of Body : ReadCloser | test.go:26:15:26:36 | type conversion |
| test.go:25:45:25:56 | selection of Body : ReadCloser | test.go:26:22:26:30 | implicit dereference : Node |
| test.go:26:22:26:30 | implicit dereference : Node | test.go:26:15:26:36 | type conversion |
| test.go:26:22:26:30 | implicit dereference : Node | test.go:26:22:26:30 | implicit dereference : Node |
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:29:15:29:34 | call to Buffered |
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:30:15:30:29 | call to Raw |
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:32:15:32:19 | value |
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:33:15:33:30 | call to Text |
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:34:15:34:44 | type conversion |
| test.go:28:33:28:44 | selection of Body : ReadCloser | test.go:34:22:34:38 | call to Token : Token |
| test.go:34:22:34:38 | call to Token : Token | test.go:34:15:34:44 | type conversion |
nodes
| test.go:10:15:10:42 | call to Cookie : tuple type | semmle.label | call to Cookie : tuple type |
| test.go:14:15:14:55 | type conversion | semmle.label | type conversion |
| test.go:14:42:14:47 | implicit dereference : Cookie | semmle.label | implicit dereference : Cookie |
| test.go:16:24:16:35 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
| test.go:17:15:17:31 | type conversion | semmle.label | type conversion |
| test.go:17:22:17:25 | implicit dereference : Node | semmle.label | implicit dereference : Node |
| test.go:19:36:19:47 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
| test.go:20:15:20:32 | type conversion | semmle.label | type conversion |
| test.go:20:22:20:26 | implicit dereference : Node | semmle.label | implicit dereference : Node |
| test.go:22:33:22:44 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
| test.go:23:15:23:35 | type conversion | semmle.label | type conversion |
| test.go:23:22:23:29 | implicit dereference : Node | semmle.label | implicit dereference : Node |
| test.go:25:45:25:56 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
| test.go:26:15:26:36 | type conversion | semmle.label | type conversion |
| test.go:26:22:26:30 | implicit dereference : Node | semmle.label | implicit dereference : Node |
| test.go:28:33:28:44 | selection of Body : ReadCloser | semmle.label | selection of Body : ReadCloser |
| test.go:29:15:29:34 | call to Buffered | semmle.label | call to Buffered |
| test.go:30:15:30:29 | call to Raw | semmle.label | call to Raw |
| test.go:32:15:32:19 | value | semmle.label | value |
| test.go:33:15:33:30 | call to Text | semmle.label | call to Text |
| test.go:34:15:34:44 | type conversion | semmle.label | type conversion |
| test.go:34:22:34:38 | call to Token : Token | semmle.label | call to Token : Token |
#select
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |

View File

@@ -0,0 +1 @@
Security/CWE-079/ReflectedXss.ql

View File

@@ -0,0 +1,7 @@
module example.com/m
go 1.14
require (
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb
)

View File

@@ -0,0 +1,36 @@
package test
import (
"golang.org/x/net/html"
"net/http"
)
func test(request *http.Request, writer http.ResponseWriter) {
cookie, _ := request.Cookie("SomeCookie")
writer.Write([]byte(html.EscapeString(cookie.Value))) // GOOD: escaped.
writer.Write([]byte(html.UnescapeString(cookie.Value))) // BAD: unescaped.
node, _ := html.Parse(request.Body)
writer.Write([]byte(node.Data)) // BAD: writing unescaped HTML data
node2, _ := html.ParseWithOptions(request.Body)
writer.Write([]byte(node2.Data)) // BAD: writing unescaped HTML data
nodes, _ := html.ParseFragment(request.Body, nil)
writer.Write([]byte(nodes[0].Data)) // BAD: writing unescaped HTML data
nodes2, _ := html.ParseFragmentWithOptions(request.Body, nil)
writer.Write([]byte(nodes2[0].Data)) // BAD: writing unescaped HTML data
tokenizer := html.NewTokenizer(request.Body)
writer.Write(tokenizer.Buffered()) // BAD: writing unescaped HTML data
writer.Write(tokenizer.Raw()) // BAD: writing unescaped HTML data
_, value, _ := tokenizer.TagAttr()
writer.Write(value) // BAD: writing unescaped HTML data
writer.Write(tokenizer.Text()) // BAD: writing unescaped HTML data
writer.Write([]byte(tokenizer.Token().Data)) // BAD: writing unescaped HTML data
}

View File

@@ -0,0 +1,126 @@
// Code generated by depstubber. DO NOT EDIT.
// This is a simple stub for golang.org/x/net/html, strictly for use in testing.
// See the LICENSE file for information about the licensing of the original library.
// Source: golang.org/x/net/html (exports: Node,Token,Tokenizer; functions: EscapeString,UnescapeString,Parse,ParseWithOptions,ParseFragment,ParseFragmentWithOptions,NewTokenizer)
// Package html is a stub of golang.org/x/net/html, generated by depstubber.
package html
import (
io "io"
)
type Attribute struct {
Namespace string
Key string
Val string
}
func EscapeString(_ string) string {
return ""
}
type Node struct {
Parent *Node
FirstChild *Node
LastChild *Node
PrevSibling *Node
NextSibling *Node
Type NodeType
DataAtom interface{}
Data string
Namespace string
Attr []Attribute
}
func (_ *Node) AppendChild(_ *Node) {}
func (_ *Node) InsertBefore(_ *Node, _ *Node) {}
func (_ *Node) RemoveChild(_ *Node) {}
type NodeType uint32
func Parse(_ io.Reader) (*Node, error) {
return nil, nil
}
func ParseFragment(_ io.Reader, _ *Node) ([]*Node, error) {
return nil, nil
}
func ParseFragmentWithOptions(_ io.Reader, _ *Node, _ ...ParseOption) ([]*Node, error) {
return nil, nil
}
type ParseOption func(interface{})
func ParseWithOptions(_ io.Reader, _ ...ParseOption) (*Node, error) {
return nil, nil
}
type Token struct {
Type TokenType
DataAtom interface{}
Data string
Attr []Attribute
}
func (_ Token) String() string {
return ""
}
type TokenType uint32
func (_ TokenType) String() string {
return ""
}
type Tokenizer struct{}
func (_ *Tokenizer) AllowCDATA(_ bool) {}
func (_ *Tokenizer) Buffered() []byte {
return nil
}
func (_ *Tokenizer) Err() error {
return nil
}
func (_ *Tokenizer) Next() TokenType {
return 0
}
func (_ *Tokenizer) NextIsNotRawText() {}
func (_ *Tokenizer) Raw() []byte {
return nil
}
func (_ *Tokenizer) SetMaxBuf(_ int) {}
func (_ *Tokenizer) TagAttr() ([]byte, []byte, bool) {
return nil, nil, false
}
func (_ *Tokenizer) TagName() ([]byte, bool) {
return nil, false
}
func (_ *Tokenizer) Text() []byte {
return nil
}
func (_ *Tokenizer) Token() Token {
return Token{}
}
func UnescapeString(_ string) string {
return ""
}
func NewTokenizer(r io.Reader) *Tokenizer {
return nil
}

View File

@@ -0,0 +1,3 @@
# golang.org/x/net v0.0.0-20201010224723-4f7140c49acb
## explicit
golang.org/x/net