27 ms·
Stealing secrets from developers using WebSockets
- tmpfs 6y agoThis 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?
- cjbprime 6y agoThe Host fix sounds right to me, local TCP web servers already have to do the same thing to avoid DNS rebinding attacks from external websites.
- MajesticHobo2 6y ago> Also would a CORS "Access-Control-Allow-Origin: localhost" prevent the connections being established? WebSocket isn't bound by CORS, AFAIK.
- rndgermandude 6y agoIt is not subject to CORS, same as any regular img load isn't, but the browser will send an Origin header with websocket handshakes, which you're supposed to check server-side.
- deleted 6y ago[deleted]
- nkozyra 6y agoGiven 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.
- scoot_718 6y agoWhy the actual fuck will a browser allow traffic to localhost from anywhere else?
- cjbprime 6y agoSuper 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".
- edwintorok 6y agoDNS rebinding can be fixed at the DNS server level. OpenWRT has an option for it. But this websocket thing in browsers can't easily be turned off/mitigated AFAICT.
- bawolff 6y agoWell if you are going to use custom software to alter how protocols work, you could just change your web browser.
- gruez 6y ago>DNS rebinding can be fixed at the DNS server level You can't always depend on that. eg. when you're on public/enterprise wifi that intercepts DNS requests.
- Avamander 6y agoThis is why a local stub is a very good idea.
- inetknght 6y ago> DNS rebinding can be fixed at the DNS server level. Let me know how that works with DNS over HTTP
- 6y ago
- nkozyra 6y agoThe server didn't work for me, not sure if it's a traffic issue or what. 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 6y agoI 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.
- deleted 6y ago[deleted]
- bawolff 6y agoAnd this is why you are supposed to check the origin and host headers before sending sensitive data to a web socket
- remram 6y agoOr 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.
- tasogare 6y agoImplementing any of those require more work. The issue lies in the fact security is an afterthought for the Web.
- remram 6y agoSo much work was put into the design of HTTP and Websockets in particular to avoid so many problems. Like how Websockets were made incapable to talk to any non-websocket TCP endpoint, to avoid exactly this class of attack where your browser would connect to your local SSH, FTP, ... server. There is a built-in Origin validation mechanism, and every websocket connection is going to come with its Origin and Cookies clearly marked. The browser will even disallow cross-origin requests that can modify data (e.g. non-GET) by default. If you go out of your way to build something like Webpack's websocket endpoint and forget to validate anything, it seems a bit dishonest to blame this on "security of the Web".
- brlewis 6y agocreate-react-app may already be doing so according to https://news.ycombinator.com/item?id=23259803 https://news.ycombinator.com/item?id=23259803 EDIT: Nope, exploit worked for me against webpack-dev 3.10.3 used by react-scripts 3.4.1
- gambler 6y agoYeah, 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.
- blakesterz 6y agoThis 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."
- qppo 6y agoThis is one Show HN post away from an exploit in the wild
- armchairchair 6y agoThis issues isn't endemic to websockets. I've done this with iframes as well to portscan machines on my LAN. Additionally, the portscan capabilities are even worse than the article states: you can scan any machine reachable from the visitor's machine. Any 192.* address, anything behind your VPN, so long as the time for actively refusing the connection and failing to route are different. I don't know if you can time connections to known hosts to infer things about Tor circuits. Simply call Date.now() when adding the iframe and when that iframe's onerror event fires then diff the two. I think you can do this with img tags, frames, and anything backed by a network call that lets you observe load failures. CORS doesn't save you because you aren't trying to reach into that iframe and run Javascript or access the DOM. A CSP doesn't save you because the site you're visiting is opting to do this and can put whatever they want in their CSP.
- parliament32 6y agoExactly. This isn't websockets specific, you can portscan happily with pure JS. The issue is that browsers are allowing connections (from untrusted code) to private/loopback address space. This should really be behind a permission.
- armchairchair 6y agoPOC https://jsfiddle.net/s9vzxctd/3/ https://jsfiddle.net/s9vzxctd/3/ Tested in Firefox ESR on Linux. Anything with about 3000ms time isn't a routable network address. Anything with a significantly longer or shorter time responds to a ping on my network. Timings vary from browser to browser. NoScript does block the requests before they ever leave your browser, reminding me why I use it.
- amelius 6y agoIs 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?
- est31 6y agoYou'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. [1]: https://youtu.be/pRlh8LX4kQI?t=954 https://youtu.be/pRlh8LX4kQI?t=954 [2]: https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/vm_tools#chunnel https://chromium.googlesource.com/chromiumos/platform2/+/HEA...
- ShaneMcGowan 6y agoI too like to hardcode my AWS secret keys in my frontend application
- stestagg 6y agoAdmittedly the example was a bit fake :) 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
- ShaneMcGowan 6y agoI completely understand friend, have done the very same
- enahs-sf 6y agoFake though the example may be, I wouldn’t underestimate its ability to stumble upon something useful if you could garner enough traffic. - you would probably only need a handful of ports - it really only takes one person pasting that AWS key into their file to get pwned and I’m sure someone has those keys committed to GitHub right now. - how many tabs do you have open of random tech blogs right now? Excluding HN, my guess is the average dev has at least one. Not a super plausible attack, but over a long period of time with decent SEO, could probably deliver some interesting results.
- ChuckMcM 6y agoYou do realize that your evil server could in fact send something back to your exploit to ask it to send something back to the server it connected to right? evil-server (looks at data from client) (recognizes well known server app) (launches exploit!) The first one that comes to mind is built in "package updaters" where the front end server has a well defined way of updating its packages. Have your evil server send it "get a new version of fetch_user_passwords from here..."
- bitwize 6y 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." 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.
- codazoda 6y agoExactly. 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.
- bryanrasmussen 6y agoSo the blog should have articles on how to set up and log into Service X using React! This explains why I see so many of these!
- earthboundkid 6y agoThe average post quality on Proggit has gone down a lot in the last year. It would be funny if this were why.
- pheme1 6y agoAnother idea: an online json editor
- deleted 6y ago[deleted]
- FridgeSeal 6y agoAre these not already phishing sites of some kind? I used to have some co-workers who would dump json docs containing sensitive information into these sites all the time, and despite showing them how to format stuff in VS Code.
- fabian2k 6y agoI get the following output when trying this with Create React App running: {"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 6y agoSeems to be related to this: https://github.com/webpack/webpack-dev-server/issues/1604 https://github.com/webpack/webpack-dev-server/issues/1604 It's not clear (without a lot more digging) what impact the sockjs changes have on this issue.
- wrkronmiller 6y agoI 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).
- btown 6y agoThis is why you don't let @wongmjane inject code into websites. Imagine what features she'd learn about with tracebacks from developer machines! /s In seriousness, this is all because websockets aren't bound by CORS, for good reason. https://blog.securityevaluators.com/websockets-not-bound-by-cors-does-this-mean-2e7819374acc 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.
- lstamour 6y agoThat’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 https://tools.ietf.org/html/rfc6455#section-10.7 Example, and note: https://news.ycombinator.com/item?id=23261309 https://news.ycombinator.com/item?id=23261309
- vbezhenar 6y agoWebsocket 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).
- lstamour 6y agoIt might not be localhost or a local IP if users use a different hostname, common for some environments, at which point it would have to be configurable. But yes, that could also work, if all browsers send Origin headers as expected.
- Ajedi32 6y ago> In seriousness, this is all because websockets aren't bound by CORS, for good reason. https://blog.securityevaluators.com/websockets-not-bound-by-cors-does-this-mean-2e7819374acc https://blog.securityevaluators.com/websockets-not-bound-by-... 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.
- winrid 6y agoForget port scanning for a sec. Couldn't you just scan the whole local network for common vulnerabilities, like any old virus would?
- deleted 6y ago[deleted]
- ff7c11 6y agoYou 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.
- rndgermandude 6y agoI can still attack your local non-http-servers with a regular <form> post, e.g. something like http://bugs.proftpd.org/show_bug.cgi?id=4143 http://bugs.proftpd.org/show_bug.cgi?id=4143 With Websockets something like this is effectively not possible, because WebSockets were designed with this in mind - A browser will only start transmitting data over the ws once the handshake is done. So just making a request has very limited ways for an attacker to transmit user defined data (basically the Host header/Origin header and cookies... which will not really work as an attack vector for newline-delimited or binary protocols) - The handshake itself works by the client sending a nonce to the server which the server then has to hash together with a special uuid. Only actual websocket servers know how to do this step correctly, and thus the browser will refuse to even open connections to servers which aren't actual websocket servers. So the attacker will not be able to send truly arbitrary data or read any responses. - Even after the handshake, browser-to-server data is masked by XORing the data with browser-picked keys. The attacker therefore cannot control what the data will end up looking like when it is sent to the server. And unaware servers will certainly not try to reverse the XORing. What you're left with regarding websockets are timing attacks to do some port and network scanning, and attacking actual websocket servers, which do not check the Origin or use some kind of token to verify incoming connections, analog to attack-able regular http endpoints that do not do auth and csrf tokens properly. I'll readily admit tho, that a lot of developers forget about verifying incoming websocket connections. I have fucked this up myself in the past, and I have found such issues in other websites, including one problem that let me take over user accounts via an unsecured websocket if I was able to get such users to open an attack website (or ad).
- ff7c11 6y agoNode 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.
- taviso 6y agoThis is true, although until recently it was possible to use DNS rebinding to get the list of guids! 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 https://github.com/taviso/cefdebug
- ff7c11 6y agoThanks that's really interesting, as I see from your reports you could call /json/list with rebinding to get the guid. For the past 2 years it now validates the Host header.
- matham 6y agoIf you use uBlock Origin, you can prevent any connections to localhost by default.
- oriettaxx 6y agointeresting, but I do not get it: "you can", or is it blocked by default?
- penguat 6y agoIt strikes me that this is most likely to be used as part of an exfiltration mechanism for a malicious JS package or similar.
- danShumway 6y agoHeads 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. [0]: https://github.com/gorhill/uMatrix https://github.com/gorhill/uMatrix [1]: https://github.com/gorhill/uMatrix/wiki/The-popup-panel#the-type-cells 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 https://bugzilla.mozilla.org/show_bug.cgi?id=967895
- ASalazarMX 6y agoI 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.
- nerdponx 6y ago
- zelly 6y agoThis is why you use VMs with their own network interfaces to do development in
- toupeira 6y agoOh well. I ended up adding these rules to uBlock Origin, suggestions for improvement welcome: ||localhost^$important,third-party ||127.*^$important,third-party ||10.*^$important,third-party ||192.168.*^$important,third-party ||172.16.*^$important,third-party ||172.17.*^$important,third-party ||172.18.*^$important,third-party ||172.19.*^$important,third-party ||172.20.*^$important,third-party ||172.21.*^$important,third-party ||172.22.*^$important,third-party ||172.23.*^$important,third-party ||172.24.*^$important,third-party ||172.25.*^$important,third-party ||172.26.*^$important,third-party ||172.27.*^$important,third-party ||172.28.*^$important,third-party ||172.29.*^$important,third-party ||172.30.*^$important,third-party ||172.31.*^$important,third-party
- lstamour 6y agoThat 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...
- enkrs 6y agoGiven 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)
- afturner 6y agoNot 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?
- dpacmittal 6y agoYou can achieve the same with http server as well. You'll just have to setup cors headers.
- fendy3002 6y agoCMIIW, 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". Unless I misunderstood your use case. Also, obligatory xkcd 1172
- deleted 6y ago[deleted]
- aarong11 6y agoI'm assuming this can be mitigated by using SSL/TLS. Have a read over at https://crossbar.io/docs/Secure-WebSocket-and-HTTPS/ https://crossbar.io/docs/Secure-WebSocket-and-HTTPS/ - Not sure how you would do certificate pinning though.
- fenwick67 6y agoI 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?
- aarong11 6y agoThat's why I mentioned certificate pinning. I figure you could generate a keypair for WSS communications between the nice programs and then when a nice client tried to connect to a naughty server he would know he had connected to a different host program.
- thisisnot 6y agowhen I disable websocket with network.websocket.max-connections = 0, then whatsappweb doesn't work, so perhaps someone can develop a related attack here?
- fenwick67 6y agoIs webpack-dev-server planning on fixing this?
- deleted 6y ago[deleted]
- _bxg1 6y agoIt's worth noting that secrets rarely live in front-end code, specifically because it's impossible to prevent people from extracting them.
- austincheney 6y agoI 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. TypeScript code: const sock:WebSocketLocal = (function local_socket():WebSocketLocal { // A minor security circumvention. const socket:WebSocketLocal = <WebSocketLocal>WebSocket; WebSocket = null; return socket; }()); TypeScript definitions (index.d.ts): 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.
- VWWHFSfQ 6y agothis is trivially defeated by a script that enumerates globals looking for something that extends or implements WebSocket
- odensc 6y agoThe `sock` variable wouldn't be global in this case though, so there's nothing to look for. However there are still so many different ways to defeat this (e.g. creating a web worker, creating a new window that handles the WebSocket and posting messages to it, etc.) that it's basically pointless to try.
- austincheney 6y agoIf you are opening a new window you are pretty limited. Clearly you are alerting the user that you are spawning new tabs or forcing a new popup if you provide a width or height dimension to your window.open method. Yes, I am aware of the popunder by blurring the new window the moment its created, but that is still not very clever. Even still modern browsers block popups by default, so you have to convince the user to crawl into their browser settings and turn that off, which seems like a hard sell. Then the window.open allows you to specify an address, but not page contents. If you open the same address as the current page the global WebSocket name is still null. You can open to a malicious location though, but that is a good way to get the primary domain blacklisted. You can open to about:blank, which Firefox sends restricted messaging about, but you would have to inject code into that blank page. Perhaps there are other ways to spawn new windows with greater access control that I am not aware and don't require access to the global window object. The global WebSocket is really window.WebSocket so anything that is reliant upon the window object or inherited from the window object will continue to see that window.WebSocket is null.
- mehrdadn 6y agoAre there any extensions to block connections to localhost from code on other interfaces or origins?
- oriettaxx 6y agoI 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/ https://nullsweep.com/why-is-this-website-port-scanning-me/
- EE84M3i 6y agoIs the TL;DR here just "webpack-dev server doesn't verify Origin headers for hot reloading?" Is there a whole group of people that are just learning about Websockets for the first time?
- kerng 6y agoInteresting, 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.
- fortran77 6y agoCan't you use CSRF to protect against this? https://dev.solita.fi/2018/11/07/securing-websocket-endpoints.html https://dev.solita.fi/2018/11/07/securing-websocket-endpoint...
- unnouinceput 6y agoAfter ~10 minutes I get: "Connected to 0 servers" Ahh, feels so good
- LockAndLol 6y ago> 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?