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

net/http: HTTP requests sent via HTTP proxy are forwarded to the wrong host #30775

Closed
noneymous opened this issue Mar 12, 2019 · 7 comments
Closed
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.

Comments

@noneymous
Copy link

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

go version go1.11.2 windows/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GOARCH=amd64
set GOHOSTARCH=amd64
set GOHOSTOS=windows

What did you do?

I tried to send an HTTP request with a custom host header via an HTTP proxy. Sample code:

func Test(t *testing.T) {
	proxy, errParse := url.Parse("http://127.0.0.1:8080")
	if errParse != nil {
		t.Errorf("could not parse proxy URL '%s'", errParse)
	}

	// Define custom client allowing insecure SSL connections and any SSL ciphers (in case of HTTPS)
	client := &http.Client{
		Transport: &http.Transport{
			Proxy:                 http.ProxyURL(proxy),
		},
		// CheckRedirect: -> default is nil, which tells it to stop after 10 consecutive requests
	}

	// Build request
	req, errNew := http.NewRequest("GET", "http://192.168.0.01", nil)
	if errNew != nil {
		t.Errorf("could not parse proxy URL '%s'", errParse)
	}

	// Set virtual host
	req.Host = "mynotexistinghostheader1589.com"

	// Set request headers
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0")


	// Send request
	resp, errDo := client.Do(req)
	if errDo != nil {
		t.Errorf(errDo.Error())
	}

	fmt.Println(resp)
	return
}

What did you expect to see?

The sent request should be sent (by the proxy on behalf of me) to "http://192.168.0.01" along with the host header "mynotexistinghostheader1589.com".

When I define the web server host as "xxx" with a vhost "yyy", the proxy should do the same.

What did you see instead?

The proxy did NOT connect to 192.168.0.01 in order to send the HTTP request, but it directly tried to connect to "mynotexistinghostheader1589.com" taken from the request's host header. This is wrong, "mynotexistinghostheader1589.com" is not the server, it is just the vhost, so the request obviously could not be delivered.

Where is the bug?

I have already traced the issue. net/http/request.go:545 (line number from current master) takes data from the host header:
grafik

It should take it from the original URL, like this:
grafik

Proof

Here is a screenshot form BurpSutite, showing, that it receives the request but forwards it to mynotexistinghostheader1589.com instead of 192.168.0.01:
grafik

With my suggested fix, it is correct, like this:
grafik

@noneymous noneymous changed the title net/http HTTP requests sent via HTTP proxy are forwarded to the wrong host net/http: HTTP requests sent via HTTP proxy are forwarded to the wrong host Mar 12, 2019
@julieqiu
Copy link
Member

cc: @bradfitz

@julieqiu julieqiu added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Mar 12, 2019
@noneymous
Copy link
Author

I asked myself why this was not discovered before, because it seems to be a quite common use-case. Two reason I can come up with:

  • It's only affecting HTTP requests. For HTTPS, a "CONNECT" request is created, and there it is done correctly
  • In most production environments the server has a hostname pointing to it, equal to the vhost. Then it wouldn't be a problem, you can use either vhost or hostname to establish a connection, becasue they are equal. However, in none-production environments (on the hosting side) it is quite common to have a server without hostname or DNS running multiple applications. Anyways, independent of the hosting setup, technically, the vhost should just be the header sent along and not the address to connect to.

@noneymous
Copy link
Author

Hi all! This is a year old by now. I think the fix should be quite straight forward... Any opinions?

@noneymous
Copy link
Author

I still cannot set a proxy in between to validate my HTTP client, because of this bug :/

What do you suggest? Do you need a merge request? I mean it's just one variable to be switched, as suggested in the screenshots above...

@seankhliao
Copy link
Member

Proxies use absolute URIs and RFC 7230 Section 5.4 says

A client MUST send a Host header field in all HTTP/1.1 request
messages. If the target URI includes an authority component, then a
client MUST send a field-value for Host that is identical to that
authority component, excluding any userinfo subcomponent and its "@"
delimiter (Section 2.7.1).

See also #16265 (comment)

Closing as working as intended

@noneymous
Copy link
Author

It's still not working as intended. The connection information ("server" and "port") should not be taken from the host header, because the "port" might not be defined there. The host header is just a flag for the web server to pick the desired application, in a multi application (vhost) environment.

When you issue a request, you first connect to a server, e.g.:

  • 10.10.10.1 on port 345

then, you send it an HTTP request, including a host header to define the desired application, e.g.:

  • domain.tld

That host header value (domain.tld) does not necessarily need to contain the port 345 and (to my knowledge) usually doesn't.

Now, in net/http/request.go:545 the connection information is taken from the host header value (domain.tld) and therefore missing the port information (345). Subsequently, the request it is falling back connecting to the default HTTP port (80) where nothing might be listening.

A URL defines http://server:port. Not http://vhost:port. In production environments (and with working DNS) they are equal, so modern browsers are just setting "server" as "vhost", but in testing environments, or if you don't have a nameserver the situation may be different.

@seankhliao
Copy link
Member

By spec (RFC 7230), HTTP requests should either be:

relative URI

GET / HTTP/1.1
Host: www.example.org

or absolute URI with Host the same as in the URI

GET http://www.example.org/ HTTP/1.1
Host: www.example.org

There is no room for controlling which server you connect to.
If you want to override DNS resolution, you should be looking at net.Dialer

@golang golang locked and limited conversation to collaborators Sep 13, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

4 participants