Accessing cookies in Phoenix.Socket connect

I’d like to send my authentication token as a HttpOnly cookie to add a layer of defense against XSS. The problem is that I don’t see a way to get the cookie in Phoenix.Socket’s connect/2. Has anyone figured out how to do this? Maybe there’s a way to write a plug to extract the cookie and assign it before Phoenix.Socket runs?

1 Like

It looks like Socket doesn’t have access to cookies because Transport.connect doesn’t accept the conn:

It also looks like the socket stuff is hooked directly into Cowboy, and doesn’t go through the normal plug stack, so there’s nothing I can do on my end to fudge the cookies into params.

Does this sound correct?

Is this worthy of opening an issue with Phoenix?

In general you should pass it to the socket constructor in javascript, and I’m still not sure of a good way. I currently create a token on the phoenix side holding the information I need then dump that into the generated html itself (Ick ick ICK) and grab that from javascript to stuff in to the socket. I really really wish we had access to the cookie information in a websocket to verify a token straight that way…

1 Like

No, since this is an intentional design decision. WebSockets are not restrained by the same-origin policy, so using cookies could actually leave folks vulnerable to the xss you are wanting to avoid. We also don’t support them because channels are transport agnostic, and not all transports would support cookies. The recommended approach is to use Phoenix.Token to sign data into a token, then verify it on the server as a replacement for cookies. This works across any transport and client.

6 Likes

Hear hear, though there is one thing that would be really nice, if the javascript library could pull the token out of something in the page that is not HTML, like a cookie that is not htmlonly but is only a token and enforced that way or so? ^.^

I should do that to mine, I really really hate putting tokens ‘in’ the html, a lot of people like to save a page I’ve found and that can work around…

Is CSWSH the attack that blocking cookies on websockets prevents?

1 Like

Looks like it, good read.

Apologies for the zombie thread, but I’m not sure I understand the rationale for not providing the option of reading cookies in the socket connect handshake.

It’s my understanding that a considerably safer method for storing and sending an auth token is via a HttpOnly cookie (provided you have CORS policies to prevent CSRF / CSWSH). With this, JS cannot access the tokens, and the browser does the storing / sending.

I understand the CSWSH concern due to lack of default CORS policy, but that sounds more like a strong warning to developers to check the origin, rather than just not supporting it at all.

On the flip side, we could pass a Phoenix.Token / JWT as a socket connection param, but the problem is that you have to get it to the socket constructor somehow. A <meta /> tag, localStorage, window.__INITIAL_STATE__, etc., are all easily accessible via JS (and therefore in a XSS attack).

So some clarification on this would be helpful.

4 Likes

I agree with this assessment. HttpOnly is vulnerable to CSRF as it always is, but it should be used complemented with a CSRF token or an Origin check. The current approach (write a token as a meta tag) would work well for CSRF in concert with an HttpOnly cookie, but I agree that on it’s own it’s vulnerable to XSS attacks. Any cross site JS script that runs on the page can access the meta tag and be used to initiate an authenticated web socket connection no?

2 Likes

oops, didn’t realize I was necro’ing such an old thread - sorry!

I don’t understand the security concerns here. The browser will send the cookie regardless of what phoenix decides to do with it, so I’m guessing the reason the cookie isn’t exposed on the socket connection is simply a matter of aiming to be transport agnostic, right?

If that’s the case what transports would you think might be unaware of HTTP? Because right now both implementations (long-polling and websocket) require an HTTP request to setup the channel.

Further I think that it’s a security concern that we aren’t exposing the cookie to developers. Without the ability to use HTTPOnly cookies you necessarily open the attack surface that a user might unintentionally send their clients malicious javascript, that javascript now has the ability to hijack your cookie or JWT or whatever else is exposed to the clients JS including tokens put in tags, correct?

Because I believe it to be increased attack surface, I would think a better and recommended authentication method would be to send unique HTTPOnly cookies to clients on any prior responses, so that when they are ready to upgrade to a websocket they can use that HTTPOnly cookie on the server to associate some authenticating data to their unique HTTPOnly cookie, thus ensuring the connection made was made with a trusted client.

2 Likes

Actually the issue is that you are logged in to Website A that uses a websocket that auths based on WebSite A’s cookie, then Website B sets up a websocket connection to Website A, which uses Website A’s cookie to authenticate as you, but then Website B can do whatever it wants to on that websocket now while using your credentials, all without you doing anything but just browsing and loading Website B. It really is a misdesign in the spec that websockets bypass cookie protention like that…

I think you’re mistaken OvermindDL1. When website A opens a connection, that doesn’t give any other website access to that connection. Now website B could open its own connection with the same server, but, unless I’m mistake, that would not give it access to cookies scoped to website A. Cookies aren’t passed in any part of a websockets payload, only the HTTP request that sets up the websocket would have access to cookies and just like any other HTTP request, the cookie would be scoped to the domain that issued the cookie.

I never said it did.

I never said it would give Website B access to Website A’s cookies, I said it would give it access to a websocket that was initialized with those cookies, which based on if the server reads cookies then it could give Website B authenticated access to do stuff via that user, but it never exposes the cookies to Website B, just to the webserver via Website B.

Except starting a websocket connection to Website A from Website B javascript will send the HTTP request to webserver A, Website A’s cookies and all, which if webserver A decided to auth a user based on the cookies then Website B now has a fully authenticated websocket connection to do with what it pleases.

EDIT: It’s similar to a CSRF attack, where a ‘different’ site has a form that submits to ‘your’ site to do something (like say delete your bank information or whatever) that is then submitted with your users credentials, even though it was Website B that initialized the request, except with websockets it could be a lot worse without that token.

A websocket and a connection are the same thing in this context. I don’t think I’m understanding you.

If website B does not have access to website A’s cookies and website A authenticates based on that cookie - then how would website B ever authenticate on website A?

Because the browser itself sends the cookie along, just like via a CSRF attack, especially worse because websockets don’t have same-origin cookie security:

As well as:

Now phoenix ‘could’ potentially fix it and allow access to cookies again if it validates the Origin header only.

1 Like

Oh, thanks for clearing that up for me - I didn’t realize website B would send website A’s cookies!

With that said, phoenix does currently validate against allowed origins - so this still should be secure to allow cookies to be used to authenticate, no?

1 Like

Supposedly there are other issues that I don’t remember of off-hand, maybe @josevalim or @chrismccord remember?

This article explains the issue: Cross-Site WebSocket Hijacking (CSWSH)

If website A opens up a websocket to website B, then the browser will send all cookies and authentication headers that belong to website B, even though the request was made from A.

This is orders of magnitude worse than CSRF. CSRF is a blind attack, you can trigger it but you can’t read the result. On the other hand, CSWSH gives the attacker full control of the socket, you can read from the socket, write to the socket, and perform any other operation available through the socket.

Note Phoenix does validate the Origin by default. Still I don’t think we should allow cookies to be read. Messing this up opens up a very big vulnerability and Phoenix is correct in making it double sure it can’t happen. All of the information you want to pass as a cookie can be passed in a safer way.

If your site is exposed to XSS, then it is game over anyway, because with XSS you can directly open up a websocket connection without relying on CSWSH. So you shouldn’t worry about storing those in meta or local storage since XSS allows you to cause much more damage than what the token is meant to prevent.

4 Likes

The thing that I really exceptionally don’t like though is that almost all examples show doing this by stuffing the token into the HTML/javascript source somewhere, which loses a LOT of the natural security of cookies, and yet still other examples stuff it into the cookie that javascript then pulls out of, but then that means you can’t have the cookie be httponly, also losing some of its natural security, thus sure you can pass in the information via other ways, just none with as high of security as a cookie.

2 Likes

I don’t think I understand why we wouldn’t prevent both XSS and CSWSH when we can. I agree that it’s possible to mess up and that phoenix should have safe defaults (and it does), but why would we expose ourselves to increased XSS attack surface when we could prevent both?

Phoenix already has safe defaults for preventing CSWSH and while it’s true that a compromised script could create a websocket connection and compromise you with a very phoenix-centric attack - I think it’s much more likely that a malicious script just leaks high attack surface details such as non-http cookies, meta tags, headers, etc.

My use-case is that I would like to be able to use the cookie for both http auth and websocket auth…I can’t do that confidently if I know I’m exposing that cookie to XSS and I don’t think we have to expose it to XSS.

I think the scenario where malicious JS is copy/pasting information to a 3rd party is much more likely than a malicious script sneaking in that creates websocket connections and executes domain specific logic.

I guess tl;dr; - we already have safe defaults preventing cswsh so why wouldn’t we be encourging people to use http only cookies to decrease xss attack surface?

2 Likes