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

[release-branch.go1.10] net/url, net/http: reject control characters in URLs #29926

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/net/http/fs_test.go
Expand Up @@ -583,16 +583,23 @@ func TestFileServerZeroByte(t *testing.T) {
ts := httptest.NewServer(FileServer(Dir(".")))
defer ts.Close()

res, err := Get(ts.URL + "/..\x00")
c, err := net.Dial("tcp", ts.Listener.Addr().String())
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadAll(res.Body)
defer c.Close()
_, err = fmt.Fprintf(c, "GET /..\x00 HTTP/1.0\r\n\r\n")
if err != nil {
t.Fatal(err)
}
var got bytes.Buffer
bufr := bufio.NewReader(io.TeeReader(c, &got))
res, err := ReadResponse(bufr, nil)
if err != nil {
t.Fatal("reading Body:", err)
t.Fatal("ReadResponse: ", err)
}
if res.StatusCode == 200 {
t.Errorf("got status 200; want an error. Body is:\n%s", string(b))
t.Errorf("got status 200; want an error. Body is:\n%s", got.Bytes())
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/net/http/http.go
Expand Up @@ -59,6 +59,12 @@ func isASCII(s string) bool {
return true
}

// isCTL reports whether r is an ASCII control character, including
// the Extended ASCII control characters included in Unicode.
func isCTL(r rune) bool {
return r < ' ' || 0x7f <= r && r <= 0x9f
}

func hexEscapeNonASCII(s string) string {
newLen := 0
for i := 0; i < len(s); i++ {
Expand Down
7 changes: 6 additions & 1 deletion src/net/http/request.go
Expand Up @@ -528,7 +528,12 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF
// CONNECT requests normally give just the host and port, not a full URL.
ruri = host
}
// TODO(bradfitz): escape at least newlines in ruri?
if strings.IndexFunc(ruri, isCTL) != -1 {
return errors.New("net/http: can't write control character in Request.URL")
}
// TODO: validate r.Method too? At least it's less likely to
// come from an attacker (more likely to be a constant in
// code).

// Wrap the writer in a bufio Writer if it's not already buffered.
// Don't always call NewWriter, as that forces a bytes.Buffer
Expand Down
11 changes: 11 additions & 0 deletions src/net/http/requestwrite_test.go
Expand Up @@ -512,6 +512,17 @@ var reqWriteTests = []reqWriteTest{
"User-Agent: Go-http-client/1.1\r\n" +
"\r\n",
},

21: {
Req: Request{
Method: "GET",
URL: &url.URL{
Host: "www.example.com",
RawQuery: "new\nline", // or any CTL
},
},
WantError: errors.New("net/http: can't write control character in Request.URL"),
},
}

func TestRequestWrite(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions src/net/url/url.go
Expand Up @@ -483,6 +483,10 @@ func parse(rawurl string, viaRequest bool) (*URL, error) {
var rest string
var err error

if strings.IndexFunc(rawurl, isCTL) != -1 {
return nil, errors.New("net/url: invalid control character in URL")
}

if rawurl == "" && viaRequest {
return nil, errors.New("empty url")
}
Expand Down Expand Up @@ -1102,3 +1106,9 @@ func validUserinfo(s string) bool {
}
return true
}

// isCTL reports whether r is an ASCII control character, including
// the Extended ASCII control characters included in Unicode.
func isCTL(r rune) bool {
return r < ' ' || 0x7f <= r && r <= 0x9f
}
17 changes: 16 additions & 1 deletion src/net/url/url_test.go
Expand Up @@ -1737,8 +1737,23 @@ func TestNilUser(t *testing.T) {
}

func TestInvalidUserPassword(t *testing.T) {
_, err := Parse("http://us\ner:pass\nword@foo.com/")
_, err := Parse("http://user^:passwo^rd@foo.com/")
if got, wantsub := fmt.Sprint(err), "net/url: invalid userinfo"; !strings.Contains(got, wantsub) {
t.Errorf("error = %q; want substring %q", got, wantsub)
}
}

func TestRejectControlCharacters(t *testing.T) {
tests := []string{
"http://foo.com/?foo\nbar",
"http\r://foo.com/",
"http://foo\x7f.com/",
}
for _, s := range tests {
_, err := Parse(s)
const wantSub = "net/url: invalid control character in URL"
if got := fmt.Sprint(err); !strings.Contains(got, wantSub) {
t.Errorf("Parse(%q) error = %q; want substring %q", s, got, wantSub)
}
}
}