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

The current proxy handling breakes various common use cases #62

Open
kmod-midori opened this issue Oct 15, 2021 · 17 comments
Open

The current proxy handling breakes various common use cases #62

kmod-midori opened this issue Oct 15, 2021 · 17 comments

Comments

@kmod-midori
Copy link

kmod-midori commented Oct 15, 2021

As per https://crbug.com/1253239, the standards prevents HTTP sites from accessing any resource (be it HTTP or HTTPS) through a local (or private) proxy, which is a very common use case in China and other regions where people use proxies (that are alternatives of traditional VPNs) running locally (listening on 127.0.0.1 or 0.0.0.0) to access some parts of the internet. Some users use extensions such as SwitchyOmega, but more users do not use any extensions or PAC rules, instead they route all traffic to the proxy and let the proxy application decide if it needs to send the request to a remote server or simple forward it locally. In this case, HTTP sites are totally broken.

The current standard also breaks debugging a public HTTP site with Fiddler, but this particular use case should be fine since the user is expected to be a developer.

The problem is that the site itself might not be proxied because of extensions or PAC rules ("public"), while its resources (e.g. served from Google CDN, which is proxied) essentially become "private".

For examples, check Clash, V2Ray/V2Fly, and Shadowsocks.

@jschuh
Copy link

jschuh commented Oct 15, 2021

Can't proxy implementers resolve this by simply tagging any proxied hosts with the proper address partition via a header containing the treat-as-public-address CSP directive? At least, I think that's where we landed last time this came up.

@kmod-midori
Copy link
Author

That should work on HTTP(S) proxies (although I don't know the situation of CONNECT requests), but does not cover SOCKS5 proxies that do not involve headers in any way (some of the applications mentioned above are actually SOCKS5-only).

At the same time, Chrome is shipping PNA in 94 without this and already breaking sites (and apparently they do not even know that this 11-month old CSP directive exists, as seen in that issue), so I'm a bit concerned.

@jschuh
Copy link

jschuh commented Oct 15, 2021

This is the last discussion I remember on the subject. Your concern seems to be almost the opposite of the concern in that discussion, however the solution doesn't seem to be different. The browser just needs a way of knowing what partition the destination host is in, meaning either the browser has the IP (directly or by performing the name resolution itself) or the proxy provides the network partition somehow (e.g. via treat-as-public-address).

One additional thought is that the browser could expose a proxy configuration option for default handling of an unknown partition. I could imagine something like this:

  1. Default to partition of proxy's address
  2. Default to private
  3. Default to public

Obviously that would be a user-agent customization rather than something written in the spec, but does that sound like what you're asking for?

@kmod-midori
Copy link
Author

kmod-midori commented Oct 15, 2021 via email

@kmod-midori
Copy link
Author

Another consideration is user experience. Before this, setting up a proxy is a zero/one-click operation: the application automatically set up the proxy configuration in Windows using Windows APIs, and the user can just launch Chrome and get started.
Even if we were to add some setting in the user agent, it becomes "no matter you understand or not, check this advanced option or you will not be able to use some websites", as this is not a development or enterprise deployment, the user is not expected to understand your "public" and "private" options.

@letitz
Copy link
Collaborator

letitz commented Oct 18, 2021

Thanks for the feedback on this blind spot, and for examples of affected proxy software! This is very helpful.

I am curious to understand why such software only proxies part of some websites. Is it common for websites residing inside the GFW to rely on resources from CDNs outside?

This brings me to a second question: how hard is it to fix your setup by changing the rules according to which some websites are proxied and others are not?

it is true that SOCKS proxies would not be able to insert CSP headers into responses, as they do not necessarily have visibility into those - in case of HTTPS, this goes from complicated to impossible.

There should be:

  • An option to treat a proxy as public or private, which should not only be
    a group policy, which is not available in Home editions of Windows

This should be doable, though it is true that it involves futzing around with settings in a way that was not necessary before.

  • Some kind of API for extensions to do the same

For Chrome, it seems ProxyServer could be the place to stick an address space property. That seems like it would go nicely hand in hand with the above.

@kmod-midori
Copy link
Author

Sites inside GFW usually does not use resources from other CDNs, however sites outside China might load itself just fine (not inpacted by GFW) while rely on CDNs that is not available inside GFW. In that case the site itself is not proxied while its resources are. At the same time, sites inside China usually performs badly if accessed abroad.

GFW is a multi-level architecture and individual/regional ISPs might have extra rules (for example, China Mobile users in some regions have more difficulty access GitHub than other major ISPs), and CDNs like jsDelivrm which actually has servers in China, and is used by large sites, is also sometime affected.

The current situation described above makes it necessary to use complex rules (both PAC and rules inside proxy app) to decide which site to proxy or not. The local part of these proxy application usually forwards local (private) traffic directly out as this is the most desirable outcome, making the PNA protection unavailable unless they return that header conditionally. However, some user might choose to relay these private traffic to a remote server, acting like a enterprise VPN.

Due to the number of people using these proxy services and applications, not being able to automate this can quickly turn into a support nigtmare as they do not always automatically update like their Chrome is.

Current Solutions

Applications providing HTTP proxies should be updated to inject that CSP header, while SOCKS5-only proxies should be put behind softwares such as Privoxy.

BTW, is that CSP rule already supported in Chromium?

After all, HTTP sites are no longer that common now, but can be extremely confusing to normal users that does not understand technologies very well.

@letitz
Copy link
Collaborator

letitz commented Oct 20, 2021

Thanks for the explanation! That makes a lot more sense.

is that CSP rule already supported in Chromium?

Content-Security-Policy: treat-as-public-address works in Chromium. The hypothetical treat-as-private-address does not.

Applications providing HTTP proxies should be updated to inject that CSP header, while SOCKS5-only proxies should be put behind softwares such as Privoxy.

I believe that putting the SOCKS5-only proxy behind an HTTP proxy should allow a CSP: treat-as-public-address header to be injected indeed. That would improve the user's security by preventing proxied websites from poking at the local network.

However, now that I think of it some more, in the absence of a mechanism for treating proxies as belonging to another address space, this would prevent proxied websites from embedding any proxied resources (considered local). Indeed, the header only works to drop privileges on a document. PNA blocks fetches before a request is ever made, so resources have no opportunity to set a header indicating they really should be treated as public.

This means that the CSP header alone cannot be a solution. What we really need is a setting to control how proxies are handled, or to exempt proxies entirely.

@kmod-midori
Copy link
Author

kmod-midori commented Oct 20, 2021 via email

@letitz
Copy link
Collaborator

letitz commented Oct 20, 2021

Yeah, even if the header works, there is no opportunity for the proxy to
indicate that in the first place since no request is ever made.

Exactly.

I suppose that we are not able to ever fully protect users behind a proxy,
as the name resulotion is sometimes also delegated to a remote server and
the proxy client (i.e., proxy software running on the user’s device) never
knows if it is requesting a private address, for example
192.168.133.7.traefik.me.

That may be true! That said, even in the case of traefik.me, xip.io, sslip.io etc., the proxy does know the target IP address once it connects the socket at the very least.

@kmod-midori
Copy link
Author

kmod-midori commented Oct 20, 2021 via email

@letitz
Copy link
Collaborator

letitz commented Oct 21, 2021

If memory serves me right, Chromium sends ClientHello after it sees the
HTTP proxy's headers, but for obfuscation reasons, the underlying proxy
tunnel is not created until CH (or the first actual data block after
CONNECT header) is sent from the browser, and the proxy response header is
not sent until the actual server responds.

Interesting. I guess this behavior depends on the proxy? Or is it specified?

Also, does this not introduce a circular dependency?

  • browser waits for CONNECT response to send ClientHello
  • proxy waits for ClientHello to send CONNECT response

Anyway, it is interesting to me that we can do something better for CONNECT than for simple HTTP proxied requests.

Indeed, in the simple HTTP case, there is no way for the browser to get information about the target endpoint before sending the request - since the only response the browser gets is the proxied response, once the target endpoint has received the request and replied. This defeats the purpose of PNA - sending the request alone is enough for CSRF.

OTOH, in the CONNECT case, the proxy returns a response to the browser after establishing a TCP connection to the target endpoint. This is the point in time at which PNA checks are applied in the non-proxied case. The browser could then act on the information returned by the proxy (via CSP, which seems ill-suited, or rather some other X-Bikeshed-Remote-IP header) to determine the address space of the remote endpoint. Notably, this would also work for subresources.

@kmod-midori
Copy link
Author

It works like this:

  • Browser sends CONNECT
  • Proxy application immediately responds with OK, BEFORE any remote connection is made
  • Proxy application waits for any data
  • Browser sends ClientHello (or anything) to proxy
  • TCP connection to remote is made
  • ServerHello comes back with remote proxy header

This behavior is application dependent, but the proxy application always sends its protocol header along with the first bits of actual application data to reduce the chance of being detected and save RTT.

@letitz
Copy link
Collaborator

letitz commented Oct 21, 2021

Ah, I see. Without this, a timing attack can reveal proxy users, where TCP connections are opened, then left hanging for a bit before any bits are sent on the wire?

@kmod-midori
Copy link
Author

kmod-midori commented Oct 21, 2021 via email

@letitz
Copy link
Collaborator

letitz commented Oct 21, 2021

Makes sense.

@letitz
Copy link
Collaborator

letitz commented Dec 10, 2021

FYI, I've disabled this behavior in Chrome 98: https://crrev.com/c/3320317 fixed https://crbug.com/1253239.

Now the spec needs updating to reflect this state of affairs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants