"In all seriousness, this attack vector is pretty slim. You’ve got to tempt unwitting users to visit your site, and to stay on it while they’re developing JS code."
Wrap the exploit up in a blog post about Rust -- or an article about gut bacteria -- and submit it to Hackernews. Boom, a virtual feast of secrets.
Exactly. I've got a blog with dozens of technical documents about JS and other topics. That would be an ideal place to harvest this type of information, from developers actively looking for a solution to a particular problem.
What is stopping facebook, Reddit, or another popular site you have open while you're developing add this kind of thing and get user info? Or am I misunderstanding something?
There's a simple fix though - hot reload websocket listeners like Webpack should only consider the connection valid if they first receive a shared secret that's loaded into the initial dev bundle, which itself would never be transmitted over a websocket and could be set via CORS to not be accessible to non-whitelisted origins. It's a dead-simple protocol with no ongoing performance impacts. But understandable it hasn't been implemented yet.
As far as I can tell, that article only explains that WebSockets aren't bound by CORS. It doesn't provide a reason (good or otherwise) why WebSockets were designed that way. Personally, I consider that feature to be a design flaw. If WebSockets handshakes respected the Same-Origin-Policy and CORS headers the same way every other HTTP request on the web does, none of these vulnerabilities with poorly implemented WebSockets servers would exist today, as they would be secure by default rather than "insecure unless the server properly validates the origin header on every handshake".
Probably too late to do anything about that anymore though. Changing WebSockets to respect the Same Origin Policy now would break a ton of websites.
That’s exactly the solution I was thinking of. No end-user visible changes required, just change websocket to require a secret on initial connection. An easy way of doing this might be to use the web socket URL path or a query variable. Note that we’re relying on the websocket library code to do the right thing: https://tools.ietf.org/html/rfc6455#section-10.7
Websocket protocol defines Origin header to indicate which website tries to establish connection. Hot reload websocket server must check it and allow localhost connections only (at least by default).
That won’t help if someone sets up public DNS to point to localhost or 127.0.0.1 though. Unless you check after DNS is resolved?
It’s also possible someone might bind to an IPv6 address.
Better to rely on fixes mentioned elsewhere for web socket servers running on the local machine, including inserting a secret key into web socket path or query param, ensuring the web socket validates the path or query, and ensuring there are no web socket endpoints that could be used to get the secret from the websocket when not passed in. (Like an index of paths.) The Node debugger is mentioned elsewhere here as an example and cautionary tale.
Paranoid folks could maybe trick their everyday browser into never connecting to localhost via various means, and there’s an argument that websockets deserve localhost third-party restrictions or prompts, but if I were an attacker, publishing a malicious package via the web is significantly easier and higher value. Also, websockets require JS so disabling JS is another workaround. But then the site could encourage you to enable it for other reasons...
Heads up to anyone who doesn't already know, uMatrix[0] can be set up to block websockets by default from 3rd-party and/or first-party domains. In the UI, websockets are grouped under the "xhr" column[1].
I'm a pretty big Javascript advocate, but I do recommend advanced users run uMatrix and consider disabling at least 3rd-party JS by default. uMatrix is a fantastic tool and it really doesn't take long to get used to. And honestly, a relatively large portion of the web works with only 1st party Javascript, and a surprising chunk of the web still works just fine with no Javascript at all.
This is also why I advise advanced users to run Firefox. uMatrix isn't available for Safari, and it's looking extremely likely that it'll be at least underpowered in Chrome once Manifest v3 comes out. Or I guess run Brave or Vivaldi or whatever. Dang kids running around with their hipster browsers, I can't keep track of them all.
The point is, even though I'm extremely bullish on the web as a secure application platform, part of the reason I'm bullish is because the web makes it relatively easy to take simple security measures like disabling scripts by default. You should absolutely take advantage of that, you should absolutely be disabling at least some Javascript features when you browse.
You can even globally turn off fingerprinting vectors like WebGL[2]/Canvas[3] in Firefox, and just swap to a different profile whenever you want to visit the rare game/app that requires them. Although with more and more people trying to embed their own DOM models in Canvas, maybe that'll be harder in the future.
I really like uMatrix, but I don't want to spend my time tweaking every page I visit before I can use it, that's why I compromise with uBlock Origin. uMatrix is safer but impractical for most people.
I'd be happier if Firefox itself asked for permission before allowing web servers an websockets, but even this wouldn't be terribly helpful, as any authorized website (like agar.io) could then scan you.
Given the news this has made, I sure hope browser vendors don't overreact with blocking this too hard:
I genuinely have a use-case for this. We have an internal company wide business app, that works in any browser. The usual create-read-update-delete stuff, reports, factory forms etc.
With websockets we solve communication with local devices on the shopfloor - some computers have serial-port attached thermal printers, others have usb attached notification lights. We have small python scripts that listen for commands with websockets on 127.0.0.1 and control the printers and lights.
That way we can control each users local devices from the web app - without configuring internal firewalls or installing special browser add-ons (an in-house browser add-on is a bigger security risk, than a websocket on 127.0.0.1)
Not sure of the best implementation, but couldn't it be behind a permissions dialog like the ones users have to accept for webcam access or notifications?
CMIIW, this is doable without exploiting web socket. Make usual client traffic comes to room "A", then the rest (printer, etc) to room "B". Whatever message comes from "A" is rebroadcasted again to "B".
I /have/ put other secrets into frontend code before, strictly for small temporary projects where the cost of implementing secret management outweighs the size of the project. And obviously not in code that was anywhere close to being deployed outside my own box.
Unfortunately the method outlined in the article allows access to environments that would otherwise be considered trusted and not-accessible over the internet, hence the problem
Super bad news about that: even if it didn't allow the `localhost` string, DNS rebinding allows the domain name of the site you visited to become 127.0.0.1.
The answer to why browsers allow connections to 127.0.0.1 from external sites is probably something like "legacy reasons".
Because the web is supposed to be a web of multiple sites, built my multiple people, sharing a web of resources.
Localhost is just another site. If you want to make it secure, make it secure.
You realize that anybody on your coffeeshop wifi can also connect to your localhost server, don't you? Just because a server is running on your laptop doesn't mean it's not a server, running on the internet.
Node debug mode runs a websocket, but the address is something like ws://0.0.0.0:9229/1cda98c5-9ae8-4f9a-805a-f36d0a8cdbe8 - without the correct guid at the end, you can't open the websocket and communicate. You can only detect the port being open by timing.
Yeah, keep pimping these "mitigations" instead of a better security model that doesn't require everyone perfectly jumping through hoops. When you get fucked over by one of such security exploits it will be a great relief to know that it could have been prevented if only the software vendor did the right security voodoo dance (which gets more elaborate by the month).
Edit: can't wait for the usual replies with "what is your solution?"
The obvious flaw in modern web security is that the domain isolation model does not make any sense today. It's an outdated hack. Software communication should be done thorough something resembling actor model where code running locally is thought of as completely separate entity from the web server. It shouldn't have anything to do with domains. Communication from any actor to any other actor should be subject to the same security model, regardless of where their code was loaded from. Escalating privileges between actors should be a universal and well-established process with known guarantees, not a bloody mess of ad-hoc conventions, headers and "best practices" that change with every browser, app and year.
Or use cookies, a token in the URL, or any of the existing CSRF mitigation strategies. This is not a new problem. Sensitive and destructive HTTP endpoints open to third-party origins is a bug with many existing solutions.
Is anybody keeping a list of potential security threats so browser vendors can check them off and the community can verify that they are correctly dealt with?
I just tested an approach to deny access to WebSockets in the browser. This only applies if the JavaScript and the page comes from a location you both control and your goal is to limit access from third party scripts and you don't have access to the page's server to add a Content Security Policy (CSP) rule restricting web socket addresses/ports to specified rules.
interface WebSocketLocal extends WebSocket {
new (address:string): WebSocket;
}
If the 'sock' variable is not globally scoped it cannot be globally accessed. This means third party scripts must know the name of the variable and be able to access the scope where the variable is declared, because the global variable name "WebSockets" is reassigned to null and any attempts to access it will break those third party scripts.
This websockets thing is getting more interesting very fast. I wonder how long it'll be before someone finds something truly scary? This is the 3rd post this week, and each one has found a little bit more. Nothing that seems panic worthy yet. From this one:
"In all seriousness, this attack vector is pretty slim. You’ve got to tempt unwitting users to visit your site, and to stay on it while they’re developing JS code."
I wonder if this could be used to grab more sensitive data from apps that support browser extensions (e.g. from password managers that use websockets to communicate with browser extensions).
You're likely not even safe from this if you are using Chrome OS. It does sandbox the localhost web server [1] [2], but it does not restrict access to it from the host.
This is interesting, thanks for sharing. I wonder if a remediation for the moment would be for local websocket servers to check the Host header before sending the 101 switch protocol response. Also would a CORS "Access-Control-Allow-Origin: localhost" prevent the connections being established?
Given this is largely talking about sniffing development platforms, it could also require a nonce registered in the app and the frontend and only respond if that's sent via a header.
This would prevent having to worry about people who use other hostnames for host even in localdev.
I don't think I changed any significant settings in CRA, this is pretty close to the default. Not sure what exactly determines whether this works or not.
I threw the code together last night. It's running on cloudflare backed by an S3 static file, so shouldn't be capacity issues
It was only tested on Firefox, as a basic proof-of-concept. AIUI, chrome et al offer similar functionality but maybe the API is different
It may also take a few minutes to find and connect to the websocket, I think CRA webserver maybe only binds to one client at a time, so maybe it would pick up the connection after a webpack-dev-server reload or two.
Interesting, history repeats. Didn't browsers implement firewalls a while ago to prevent arbitrary requests? Remember doing things like CSRF attacks on SMTP, POP (any text based protocol basically) and stuff like that long ago, but Firefox added mitigations to prevent connections to certain ports - I guess that browser Firewall feature can be used as mitigation to prevent these attacks.
You can only make a websockets request. The javascript call will fail if either the port is closed, or the port is open but doesn't act like a websockets server. So you can tell if a port is open by the time it takes for the connection to fail. If it's actually a websockets server you hit, then you might get a useable bidirectional communication channel to it.
> browsers allow websockets from public origins to open websockets connections to localhost without many protections
Excuse me, but what in the world? XHR has all kinds of cross-site request protections that even make developing apps locally a pain. How come websockets don't come with such protections?
Are there apps that take over this responsibility?
I don't see what WSS would do to stop the local websockets dev server from serving a remote client. A remote client could just accept the connection without verifying the signature, yes?
I was wondering the same: I would be interested in an extension that tells me when a website try to connect to localhost (it should not be hard).
Then, once I know it, I would just react myself, as in the cited https://nullsweep.com/why-is-this-website-port-scanning-me/
when I disable websocket with network.websocket.max-connections = 0, then whatsappweb doesn't work, so perhaps someone can develop a related attack here?
bitwize|5 years ago
Wrap the exploit up in a blog post about Rust -- or an article about gut bacteria -- and submit it to Hackernews. Boom, a virtual feast of secrets.
spookyuser|5 years ago
codazoda|5 years ago
wlesieutre|5 years ago
pheme1|5 years ago
throwaway2048|5 years ago
api|5 years ago
Could that be the highest voted link in HN history?
talmr|5 years ago
bryanrasmussen|5 years ago
zwetan|5 years ago
how convenient to consider that "pretty slim" now
skoskie|5 years ago
btown|5 years ago
In seriousness, this is all because websockets aren't bound by CORS, for good reason. https://blog.securityevaluators.com/websockets-not-bound-by-...
There's a simple fix though - hot reload websocket listeners like Webpack should only consider the connection valid if they first receive a shared secret that's loaded into the initial dev bundle, which itself would never be transmitted over a websocket and could be set via CORS to not be accessible to non-whitelisted origins. It's a dead-simple protocol with no ongoing performance impacts. But understandable it hasn't been implemented yet.
Ajedi32|5 years ago
As far as I can tell, that article only explains that WebSockets aren't bound by CORS. It doesn't provide a reason (good or otherwise) why WebSockets were designed that way. Personally, I consider that feature to be a design flaw. If WebSockets handshakes respected the Same-Origin-Policy and CORS headers the same way every other HTTP request on the web does, none of these vulnerabilities with poorly implemented WebSockets servers would exist today, as they would be secure by default rather than "insecure unless the server properly validates the origin header on every handshake".
Probably too late to do anything about that anymore though. Changing WebSockets to respect the Same Origin Policy now would break a ton of websites.
lstamour|5 years ago
Example, and note: https://news.ycombinator.com/item?id=23261309
vbezhenar|5 years ago
toupeira|5 years ago
lstamour|5 years ago
It’s also possible someone might bind to an IPv6 address.
Better to rely on fixes mentioned elsewhere for web socket servers running on the local machine, including inserting a secret key into web socket path or query param, ensuring the web socket validates the path or query, and ensuring there are no web socket endpoints that could be used to get the secret from the websocket when not passed in. (Like an index of paths.) The Node debugger is mentioned elsewhere here as an example and cautionary tale.
Paranoid folks could maybe trick their everyday browser into never connecting to localhost via various means, and there’s an argument that websockets deserve localhost third-party restrictions or prompts, but if I were an attacker, publishing a malicious package via the web is significantly easier and higher value. Also, websockets require JS so disabling JS is another workaround. But then the site could encourage you to enable it for other reasons...
danShumway|5 years ago
I'm a pretty big Javascript advocate, but I do recommend advanced users run uMatrix and consider disabling at least 3rd-party JS by default. uMatrix is a fantastic tool and it really doesn't take long to get used to. And honestly, a relatively large portion of the web works with only 1st party Javascript, and a surprising chunk of the web still works just fine with no Javascript at all.
This is also why I advise advanced users to run Firefox. uMatrix isn't available for Safari, and it's looking extremely likely that it'll be at least underpowered in Chrome once Manifest v3 comes out. Or I guess run Brave or Vivaldi or whatever. Dang kids running around with their hipster browsers, I can't keep track of them all.
The point is, even though I'm extremely bullish on the web as a secure application platform, part of the reason I'm bullish is because the web makes it relatively easy to take simple security measures like disabling scripts by default. You should absolutely take advantage of that, you should absolutely be disabling at least some Javascript features when you browse.
You can even globally turn off fingerprinting vectors like WebGL[2]/Canvas[3] in Firefox, and just swap to a different profile whenever you want to visit the rare game/app that requires them. Although with more and more people trying to embed their own DOM models in Canvas, maybe that'll be harder in the future.
[0]: https://github.com/gorhill/uMatrix
[1]: https://github.com/gorhill/uMatrix/wiki/The-popup-panel#the-...
[2]: about:config -> `webgl.disabled` -> true
[3]: https://bugzilla.mozilla.org/show_bug.cgi?id=967895
ASalazarMX|5 years ago
I'd be happier if Firefox itself asked for permission before allowing web servers an websockets, but even this wouldn't be terribly helpful, as any authorized website (like agar.io) could then scan you.
jfkebwjsbx|5 years ago
The average user will never learn to configure and use software like uMatrix.
enkrs|5 years ago
I genuinely have a use-case for this. We have an internal company wide business app, that works in any browser. The usual create-read-update-delete stuff, reports, factory forms etc.
With websockets we solve communication with local devices on the shopfloor - some computers have serial-port attached thermal printers, others have usb attached notification lights. We have small python scripts that listen for commands with websockets on 127.0.0.1 and control the printers and lights.
That way we can control each users local devices from the web app - without configuring internal firewalls or installing special browser add-ons (an in-house browser add-on is a bigger security risk, than a websocket on 127.0.0.1)
afturner|5 years ago
dpacmittal|5 years ago
fendy3002|5 years ago
Unless I misunderstood your use case.
Also, obligatory xkcd 1172
unknown|5 years ago
[deleted]
ShaneMcGowan|5 years ago
stestagg|5 years ago
I /have/ put other secrets into frontend code before, strictly for small temporary projects where the cost of implementing secret management outweighs the size of the project. And obviously not in code that was anywhere close to being deployed outside my own box.
Unfortunately the method outlined in the article allows access to environments that would otherwise be considered trusted and not-accessible over the internet, hence the problem
koprulusector|5 years ago
im3w1l|5 years ago
scoot_718|5 years ago
cjbprime|5 years ago
The answer to why browsers allow connections to 127.0.0.1 from external sites is probably something like "legacy reasons".
jasonkester|5 years ago
Custom hostnames are such a better solution, but for some reason developers don't use them.
toomim|5 years ago
Localhost is just another site. If you want to make it secure, make it secure.
You realize that anybody on your coffeeshop wifi can also connect to your localhost server, don't you? Just because a server is running on your laptop doesn't mean it's not a server, running on the internet.
ff7c11|5 years ago
taviso|5 years ago
I actually saw people leaving this enabled so much in shipping products, I wrote a little utility to test for it.
https://github.com/taviso/cefdebug
bawolff|5 years ago
gambler|5 years ago
Edit: can't wait for the usual replies with "what is your solution?"
The obvious flaw in modern web security is that the domain isolation model does not make any sense today. It's an outdated hack. Software communication should be done thorough something resembling actor model where code running locally is thought of as completely separate entity from the web server. It shouldn't have anything to do with domains. Communication from any actor to any other actor should be subject to the same security model, regardless of where their code was loaded from. Escalating privileges between actors should be a universal and well-established process with known guarantees, not a bloody mess of ad-hoc conventions, headers and "best practices" that change with every browser, app and year.
remram|5 years ago
brlewis|5 years ago
EDIT: Nope, exploit worked for me against webpack-dev 3.10.3 used by react-scripts 3.4.1
amelius|5 years ago
austincheney|5 years ago
TypeScript code:
TypeScript definitions (index.d.ts): If the 'sock' variable is not globally scoped it cannot be globally accessed. This means third party scripts must know the name of the variable and be able to access the scope where the variable is declared, because the global variable name "WebSockets" is reassigned to null and any attempts to access it will break those third party scripts.VWWHFSfQ|5 years ago
blakesterz|5 years ago
"In all seriousness, this attack vector is pretty slim. You’ve got to tempt unwitting users to visit your site, and to stay on it while they’re developing JS code."
qppo|5 years ago
grimjack00|5 years ago
So, something like evil counterparts to HN, reddit, StackOverflow, or latestcatvideos.com.
wrkronmiller|5 years ago
est31|5 years ago
[1]: https://youtu.be/pRlh8LX4kQI?t=954
[2]: https://chromium.googlesource.com/chromiumos/platform2/+/HEA...
tmpfs|5 years ago
MajesticHobo2|5 years ago
WebSocket isn't bound by CORS, AFAIK.
nkozyra|5 years ago
This would prevent having to worry about people who use other hostnames for host even in localdev.
unknown|5 years ago
[deleted]
cjbprime|5 years ago
fabian2k|5 years ago
{"type":"error","data":"Invalid Host/Origin header"}
I don't think I changed any significant settings in CRA, this is pretty close to the default. Not sure what exactly determines whether this works or not.
stestagg|5 years ago
It's not clear (without a lot more digging) what impact the sockjs changes have on this issue.
nkozyra|5 years ago
I have at least 3 create-react-app and one next app running. I even ran a quick websocket server on port 3000 just to see but nada.
stestagg|5 years ago
It was only tested on Firefox, as a basic proof-of-concept. AIUI, chrome et al offer similar functionality but maybe the API is different
It may also take a few minutes to find and connect to the websocket, I think CRA webserver maybe only binds to one client at a time, so maybe it would pick up the connection after a webpack-dev-server reload or two.
kerng|5 years ago
unknown|5 years ago
[deleted]
winrid|5 years ago
ff7c11|5 years ago
unknown|5 years ago
[deleted]
LockAndLol|5 years ago
Excuse me, but what in the world? XHR has all kinds of cross-site request protections that even make developing apps locally a pain. How come websockets don't come with such protections?
Are there apps that take over this responsibility?
penguat|5 years ago
aarong11|5 years ago
fenwick67|5 years ago
fortran77|5 years ago
https://dev.solita.fi/2018/11/07/securing-websocket-endpoint...
unknown|5 years ago
[deleted]
matham|5 years ago
oriettaxx|5 years ago
"you can", or is it blocked by default?
_bxg1|5 years ago
mehrdadn|5 years ago
oriettaxx|5 years ago
unnouinceput|5 years ago
Ahh, feels so good
thisisnot|5 years ago
fenwick67|5 years ago
zelly|5 years ago
EE84M3i|5 years ago
Is there a whole group of people that are just learning about Websockets for the first time?