Skip to content

Commit

Permalink
[release-branch.go1.11] net/http, net/url: reject control characters …
Browse files Browse the repository at this point in the history
…in URLs

Cherry pick of combined CL 159157 + CL 160178.

Fixes #29923
Updates #27302
Updates #22907

Change-Id: I6de92c14284595a58321a4b4d53229285979b872
Reviewed-on: https://go-review.googlesource.com/c/160798
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
  • Loading branch information
bradfitz committed Feb 1, 2019
1 parent ddfb74e commit eb0f2b3
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 6 deletions.
15 changes: 11 additions & 4 deletions src/net/http/fs_test.go
Original file line number Diff line number Diff line change
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
11 changes: 11 additions & 0 deletions src/net/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ func isASCII(s string) bool {
return true
}

// stringContainsCTLByte reports whether s contains any ASCII control character.
func stringContainsCTLByte(s string) bool {
for i := 0; i < len(s); i++ {
b := s[i]
if b < ' ' || b == 0x7f {
return true
}
}
return false
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,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 stringContainsCTLByte(ruri) {
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
Original file line number Diff line number Diff line change
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
15 changes: 15 additions & 0 deletions src/net/url/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,10 @@ func parse(rawurl string, viaRequest bool) (*URL, error) {
var rest string
var err error

if stringContainsCTLByte(rawurl) {
return nil, errors.New("net/url: invalid control character in URL")
}

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

// stringContainsCTLByte reports whether s contains any ASCII control character.
func stringContainsCTLByte(s string) bool {
for i := 0; i < len(s); i++ {
b := s[i]
if b < ' ' || b == 0x7f {
return true
}
}
return false
}
23 changes: 22 additions & 1 deletion src/net/url/url_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1738,8 +1738,29 @@ 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)
}
}

// But don't reject non-ASCII CTLs, at least for now:
if _, err := Parse("http://foo.com/ctl\x80"); err != nil {
t.Errorf("error parsing URL with non-ASCII control byte: %v", err)
}

}

0 comments on commit eb0f2b3

Please sign in to comment.