whatwg / fetch

Fetch Standard
https://fetch.spec.whatwg.org/
Other
2.12k stars 332 forks source link

Consider shifting the "bad port list" to an allowlist. #1189

Open mikewest opened 3 years ago

mikewest commented 3 years ago

Given the drip-by-drip expansion of the bad port list over time, I wonder whether it's reasonable to invert the list. Skimming through HTTP Archive, for example, ~99.94% of URLs requested are using the default port, and a substantial amount of the rest are specifying well-known ports like 8080, 8443, 444, 8090, 8081, 81, 8000, 443, and so on.

Given that we know there's real risk here, perhaps coming up with a feasible allowlist (with user-agent specific carveouts via devtools and/or enterprise policy) would be a good use of our time?

The following HTTP Archive query:

SELECT
  REGEXP_EXTRACT(url, CONCAT(NET.HOST(url), ":([0-9]+)")) as port,
  COUNT(*) as num,
  count(*) * 100.0 / sum(count(*)) over() as percent
FROM
  `httparchive.requests.2021_02_01_desktop`
GROUP BY
  port
ORDER BY
  num DESC
LIMIT 50

yields:

Port Percent
null 99.943
8080 0.0076206
8443 0.0038307
444 0.0018087
8090 0.0014097
8081 0.0012318
11103 0.00096948
3000 0.00090611
81 0.00090592
8000 0.00073031
4433 0.00068932
8901 0.00066261
4450 0.00062426
2083 0.00061899
44444 0.00059304
443 0.00054886
8082 0.00054190
8888 0.00049884
10010 0.00048211
9443 0.00046970
3001 0.00045334
8123 0.00044130
5000 0.00042758
4200 0.00041498
6001 0.00040445
7767 0.00038001
2096 0.00037004
4443 0.00033131
8083 0.00032980
446 0.00032924
442 0.00032172
8088 0.00031326
10001 0.00030630
82 0.00028016
61122 0.00027922
7777 0.00027659
10443 0.00027245
88 0.00026230
85 0.00024425
8181 0.00023729
9000 0.00022451
3002 0.00022394
8093 0.00022169
8099 0.00020758
2053 0.00020476
9090 0.00020232
8085 0.00020119
5001 0.00019875
1443 0.00019856
335 0.00019611

Based on this initial sampling, it might not be crazy to build an allowlist of the top XXX ports in actual usage today, and then cull it over time.

/cc @MattMenke2 @ricea @mozfreddyb @youennf to follow up on https://github.com/whatwg/fetch/pull/1148 and https://groups.google.com/a/chromium.org/g/blink-dev/c/kyVo08TtOp8/m/nu4B94LcCAAJ.

ricea commented 3 years ago

There's a danger of us missing important ports because they're not used on the public internet.

For example, 3128, a common proxy port, is not on the above list. I don't know off the top of my head whether the bad port list is applied to proxies, or even whether it should be.

mikewest commented 3 years ago

I'm assuming that the proxy configuration of a given user agent is outside the scope of Fetch, so I'm mostly concerned with requests triggered by websites.

I agree, though, that HTTP Archive data is going to miss both home networks and intranets. Perhaps we could add some telemetry to Chrome to get a more representative list? I think we could also handle the latter via enterprise policy and/or devtools carveouts for folks building websites (or an explicitly carved out range of ports we think are safe).

yutakahirano commented 3 years ago

Will private networks be covered by https://github.com/wicg/private-network-access? Is it reasonable to require CORS preflights for requests to non-allow-listed ports?

MattMenke2 commented 3 years ago

Given that proxy autoconfig remains enabled by default on Windows, I think we'll likely need to continue using the same blacklist for proxies, in practice.

mikewest commented 3 years ago

@yutakahirano:

Will private networks be covered by https://github.com/wicg/private-network-access?

Ideally, yes, but that seems somewhat orthogonal to the question here.

Is it reasonable to require CORS preflights for requests to non-allow-listed ports?

If we wanted to go this route, I think we could more simply require TLS for non-allowlisted ports. It seems to me that encryption would substantially mitigate the Slipstream style of attack.

@MattMenke2:

Given that proxy autoconfig remains enabled by default on Windows, I think we'll likely need to continue using the same blacklist for proxies, in practice.

I don't understand the risk here (because I know little to nothing about proxy configuration on Windows). Can a web-based attacker force a user to use a given proxy? That seems bad.

MattMenke2 commented 3 years ago

@MattMenke2:

Given that proxy autoconfig remains enabled by default on Windows, I think we'll likely need to continue using the same blacklist for proxies, in practice.

I don't understand the risk here (because I know little to nothing about proxy configuration on Windows). Can a web-based attacker force a user to use a given proxy? That seems bad.

If you're a man-in-the-middle attacker, you can respond to DNS lookups to wpad to get users to use your own PAC script. This feature that is only really targeted at enterprises is still enabled by default on all Windows machines, to the extent of my knowledge.

mikewest commented 3 years ago

If you're a man-in-the-middle attacker, you can respond to DNS lookups to wpad to get users to use your own PAC script. This feature that is only really targeted at enterprises is still enabled by default on all Windows machines, to the extent of my knowledge.

Interesting. That sounds bad, but somewhat distinct from the risks leading us to slowly add ports to the bad port list, right? It seems like it would be totally possible to prevent websites from fetch()ing a resource at port 3128, while at the same time allowing a proxy to be configured pointing at that same port. The contexts seem pretty distinct.

MattMenke2 commented 3 years ago

Once a PAC script is injected, it can make requests for http://some_host:80/ to http://local.domain: by setting that as a proxy for those requests. This would bypass both the port blacklist and any additional webby security features around connections to local IPs.

mikewest commented 3 years ago

Once a PAC script is injected, it can make requests for http://some_host:80/ to http://local.domain: by setting that as a proxy for those requests. This would bypass both the port blacklist and any additional webby security features around connections to local IPs.

Malicious proxies are, indeed, bad. I agree that we need to deal with them in some way. All I'm saying here is that I'm not sure we need to deal with them via the bad port list.

MattMenke2 commented 3 years ago

Quick clarification: That's not a malicious proxy, but a malicious PAC script. There doesn't need to be any actual proxy in that scenario, the browser just needs to think it's using a proxy.

ricea commented 3 years ago

A preflight could protect vulnerable endpoints, but it couldn't protect vulnerable middleboxes, because in the Slipstream attack the server is also malicious and so can handle the preflight correctly.

davidben commented 3 years ago

Regarding the table: between Alt-Svc and SVCB/HTTPS, the bad port list needs to be applied not just at URLs, but also at the endpoint we actually connect to. Otherwise any protocol-confusion attacks can just as easily apply after the redirect.

I don't think either feature invalidates the numbers in the original report, since they're fairly rare and, for now, merely alternate routes one can safely ignore. (Though, with ECH in SVCB/HTTP, that will later no longer be the case.) But if the intent is to bound the use of arbitrary ports, which I agree seems prudent given all this mess, that's probably some feedback to the IETF that to stop building unbounded port redirection into everything. (@bemasc @ericorth FYI)

The other wrinkle is Alt-Svc and SVCB/HTTP specify not just TCP ports but also UDP ports for QUIC (@DavidSchinazi FYI). Protocols that run on the two ports don't coincide as much, so we may want to treat them differently? (Fortunately, QUIC is a lot less malleable than cleartext HTTP. Unfortunately, it probably still is malleable enough under chosen plaintext scenarios like the web. Fortunately most random protocols are TCP anyway.)

annevk commented 3 years ago

@davidben see #889 about potentially moving/duplicating the bad port list into "obtain a connection" (WebTransport might need to bypass though?).

bemasc commented 3 years ago

I think we at least need to allow all the dynamic ports (49152-65535, RFC 6335). They're commonly used for short-lived servers, and shouldn't collide with existing semantics because they're explicitly non-registrable.

nightpool commented 3 years ago

I'm trying to understand what the point of enshrining such a blocklist or allowlist in the long term is—I understand that it's useful for the web platform to be part of a coordinated incident response to individual vulnerabilities in this area, but isn't the ultimate responsibility on the NAT firewalls in question to close the hole created by their improper parsing of of these protocols? We're never going to fix this problem by blacklisting or allow-listing specific ports if the broken NAT and ALG system remain in place and continue to be developed. In that way, this type of attack is very similar to HTTP Request smuggling—there just isn't enough information on the client side to be able to block it comprehensively.

ricea commented 3 years ago

@nightpool I agree that NAT firewalls should fix their bugs, but it will be a very long time before all the deployed devices have been replaced. In the meantime, new devices will introduce new bugs.

Browsers are one of the few applications that run untrusted code inside people's internal networks. Users have an expectation that they can browse the web in safety and we (browser developers) have a responsibility to live up to that. If we fail to do so then users will stop browsing the web. My personal view is that that would be a great loss. This is why we keep working around other people's bugs.

Once we've added a port to the blocklist we never know if it's safe to remove it again, so in practice it will only grow. Every time we add something to the blocklist we break people's applications, so it's desirable if we can stop doing that.

flackr commented 3 years ago

Once we've added a port to the blocklist we never know if it's safe to remove it again, so in practice it will only grow. Every time we add something to the blocklist we break people's applications, so it's desirable if we can stop doing that.

Would this mean that the allowlist couldn't be expanded since we could similarly never know if it's safe to allow any currently blocked port?

I'm worried about the myriad of servers which run web interfaces on non-standard ports so as not to conflict with a webserver running on the same machine, e.g. cups 631, selenium grid 4444, goma 8088, plex 32400, tools like code-server (defaults to 8080) or cloud9 (defaults to 8181) specifically need to avoid dev web server ports as well since they're often used for working on that dev web content, etc. It's common to configure routers with a non-standard port for remote web configuration for the same reason. I suspect some IoT devices may use non-standard configuration ports.

Would a devtools configured exception be possible to use while not keeping devtools open (i.e. similar to adding an exception to your firewall)? I've seen many cases where the presence of devtools significantly slows down the site, and it's awkward to lose that screen real estate when you're not working on the site.

Alternately, could we get most of the benefit without some of the pain by not restricting the main navigation, but only restricting sub-resource requests (explicitly allowing them to the ip:port used for the main navigation)?

ricea commented 3 years ago

Would this mean that the allowlist couldn't be expanded since we could similarly never know if it's safe to allow any currently blocked port?

Yes, I think we would never add new ports to the allowlist, only remove them.

Would a devtools configured exception be possible to use while not keeping devtools open (i.e. similar to adding an exception to your firewall)? I've seen many cases where the presence of devtools significantly slows down the site, and it's awkward to lose that screen real estate when you're not working on the site.

So essentially it would be a normal browser setting, but hidden in devtools rather than on the usual setting page? It might work.

Alternately, could we get most of the benefit without some of the pain by not restricting the main navigation, but only restricting sub-resource requests (explicitly allowing them to the ip:port used for the main navigation)?

I think the security benefits of this are not that great, since you can perform many attacks just by using a form with POST. However, it might be a good transitional stage to get web developers used the idea of there being an allowlist.

davidben commented 1 year ago

Got reminded of this recently and noticed there was this discussion point that is worth touching on:

Is it reasonable to require CORS preflights for requests to non-allow-listed ports?

If we wanted to go this route, I think we could more simply require TLS for non-allowlisted ports. It seems to me that encryption would substantially mitigate the Slipstream style of attack.

CORS preflights and TLS limit the space significantly, but they don't fully solve the problem. They still assume that you and the other side are speaking the same protocol. When we send a preflight, we'll send OPTIONS /attacker/controlled/path HTTP/1.1, or a TLS ClientHello with attacker-controlled server_name or session ticket. That those are attacker-controlled works because the syntaxes have defined meaning and we can assume the other side will correctly interpret those bytes as the path or server_name or whatever.

But when we don't even know we're speaking the same protocol as the other side, that breaks down. E.g. maybe your protocol is just newline-separated and you ignore unrecognized commands. While goofy, that's a perfectly well-defined protocol. (AIUI that's what memcached does.) Then any attacker-controlled field that can fit a newline (e.g. TLS session tickets) is an avenue for attack. Or if you use space-separated commands, OPTIONS /attacker/controlled/path HTTP/1.1 is a problem

Essentially the meaning of the port allowlist/blocklist is: anyone deploying anything on those ports must use protocols that cannot be confused with an HTTP-related protocol (that is, TLS and HTTP/1.1) in a cross-protocol attack. Browsers have squatted practically the whole port space, so we've rather constrained everyone else. An allowlist means we relinquish part of that space so other applications don't need to worry about HTTP.

flackr commented 1 year ago

Alternately, could we get most of the benefit without some of the pain by not restricting the main navigation, but only restricting sub-resource requests (explicitly allowing them to the ip:port used for the main navigation)?

I think the security benefits of this are not that great, since you can perform many attacks just by using a form with POST. However, it might be a good transitional stage to get web developers used the idea of there being an allowlist.

What if links and POST requests were only allowed so long as they matched the top-level port? I.e. allow the user to navigate to any port manually but content can only link to other resources served from the same port or allow listed ports?

pnorman commented 9 months ago

I came across this via port 10080 and amanda for my prometheus exporter. Prometheus exporters speak HTTP/HTTPS, and when developing them it's common to use a web browser to see what's being returned.

Ports 9100-10013 are used by a prometheus exporter. Any one server will be only using a small fraction of those since each port is a different piece of software being monitored. I assume each one has had users setting it up who visited the exporter in a web browser to test, and developers will of done the same.

I also want to note that I know of several pieces of software that put up a HTTP server for users to use the software and don't have the port appear on the list in the first post.