Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

html/template: reserved character "()" in href attribute is autoescaped. #63586

Open
gyuber opened this issue Oct 17, 2023 · 4 comments
Open
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Security
Milestone

Comments

@gyuber
Copy link

gyuber commented Oct 17, 2023

I think reservedcharacters percent encoding is not performed according to the RFC 3986 spec.

What version of Go are you using (go version)?

1.21

What did you do?

dict := make(map[string]interface{})
dict["link"] = `https://example.com/()"`
tag := `<a href="{{ $.link }}"></a>`
t, _ := template.New("tag").Parse(tag)

var tpl bytes.Buffer
e := t.Execute(&tpl, dict)
if e != nil {
    fmt.Println(e)
}

fmt.Println(tpl.String())

What did you expect to see?

<a href="https://example.com/()"></a>

What did you see instead?

<a href="https://example.com/%28%29%22"></a>
@gyuber gyuber changed the title html/template: reserved character in href attribute is autoescaped. html/template: reserved character "()" in href attribute is autoescaped. Oct 17, 2023
@cagedmantis cagedmantis added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Oct 19, 2023
@cagedmantis cagedmantis added this to the Unreleased milestone Oct 19, 2023
@cagedmantis
Copy link
Contributor

cc @golang/security

@rolandshoemaker
Copy link
Member

From html/template/url.go:

// Single quote and parens are sub-delims in RFC 3986, but we
// escape them so the output can be embedded in single
// quoted attributes and unquoted CSS url(...) constructs.

@gospider007
Copy link

Format (webp) is incorrectly encoded to format%28webp%29, and the browser will not encode Format (webp)

package main

import (
	"fmt"
	"net/url"
)

func main() {
	href := "https://img.asuracomics.com/unsafe/fit-in/330x450/filters:format(webp)/https://asuratoon.com/wp-content/uploads/2023/12/¸A°×AC_½AA¼AY´A_AµAcAuc_A¸AIAE²_AOA¾.jpg"
	CorrectURL := "https://img.asuracomics.com/unsafe/fit-in/330x450/filters:format(webp)/https://asuratoon.com/wp-content/uploads/2023/12/%C2%B8A%C2%B0%C3%97AC_%C2%BDAA%C2%BCAY%C2%B4A_A%C2%B5AcAuc_A%C2%B8AIAE%C2%B2_AOA%C2%BE.jpg"
	url, _ := url.Parse(href)
	fmt.Println("Correct URL: ", CorrectURL)
	fmt.Println("net/url URL: ", url.String())
}
Correct URL:  https://img.asuracomics.com/unsafe/fit-in/330x450/filters:format(webp)/https://asuratoon.com/wp-content/uploads/2023/12/%C2%B8A%C2%B0%C3%97AC_%C2%BDAA%C2%BCAY%C2%B4A_A%C2%B5AcAuc_A%C2%B8AIAE%C2%B2_AOA%C2%BE.jpg
net/url URL:  https://img.asuracomics.com/unsafe/fit-in/330x450/filters:format%28webp%29/https://asuratoon.com/wp-content/uploads/2023/12/%C2%B8A%C2%B0%C3%97AC_%C2%BDAA%C2%BCAY%C2%B4A_A%C2%B5AcAuc_A%C2%B8AIAE%C2%B2_AOA%C2%BE.jpg

@JonasUnderscore
Copy link

JonasUnderscore commented Dec 26, 2023

I believe the server is rightfully using ')' as a delimiter, and the escaped URI is for another (non-existing) resource.

https://www.rfc-editor.org/%72%66%63/%72%66%63%33%39%38%36#%73%65%63%74%69%6F%6E%2D%32.%32

2.2

Thus, characters in the reserved
set are protected from normalization and are therefore safe to be
used by scheme-specific and producer-specific algorithms for
delimiting data subcomponents within a URI.

Changing the encoding of delimiters may not be undone by normalization.

2.3

URIs that differ in the replacement of an unreserved character with
its corresponding percent-encoded US-ASCII octet are equivalent: they
identify the same resource.

Only unreserved mentioned.

3.3

Aside from dot-segments in hierarchical paths, a path segment is
considered opaque by the generic syntax. URI producing applications
often use the reserved characters allowed in a segment to delimit
scheme-specific or dereference-handler-specific subcomponents

'(' can be a delimiter (path, sub-delims, reserved) or an octet/character.
'%28' can be a percent encoded octet without special meaning.

'(' and '&' are in the same class, percent-encoding '&' would break most queries.

It seems '(' is not the same as '%28', although many implementations do not use '(' as a delimiter.

The go http library escapes '(' into '%28' and unescapes '%3B' into ';' when parsing URLs. Both are sub-delimiters.

edit:
The issue seems to be related to (*URL).String which calls (*URL).EscapedPath .
When it needs to escape some characters (e.g. non-ascii), it uses the unescaped path which removed encoding differences.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Security
Projects
None yet
Development

No branches or pull requests

6 participants