Accessing cookies in Phoenix.Socket connect

Because if you are vulnerable to XSS, then you have already lost control over the socket, regardless if the token is stored in a cookie, meta or local storage.

1 Like

Exactly my point, hence why it should be stored in the cookie. ^.^

Storing in the cookie opens up the possibility for CSWSH if you mess up your check origin configuration. Storing it elsewhere prevents from CSWSH and does not make it any better or worse when it comes to XSS. So I don’t see a reason to keep the cookie around.

Has phoenix messed up the check origin configuration? As I recall it takes explicit site domains or to be entirely disabled (which absolutely should not be default of course).

It is trivial for people to break XSS as well just by, oh, not putting in the CSRF check (very easy to do if adding phoenix to an existing project), that doesn’t mean that useful functionality should be denied outright when there are useful uses of it that are still as safe or more so as otherwise.

Right, but you’ve lost control via a likely static asset - the attacker very likely doesn’t have arbitrary write access to your assets or else the attacker probably wouldn’t need a authenticated socket to do damage in the first place.

What’s much more likely is that a generic malicious ad or something runs a script on your website that crawls the page looking for sensitive information and passing that to a 3rd party that could later analyze that information and potentially use it later. In my case I want to use the same cookie for http auth and websocket auth - that means this highly generic malicious script could grab my cookie and at some generic point in the future use that cookie to hijack one of my users sessions.

Let’s compare that to an XSS vulnerability that would do malicious damage via a socket. First that script would have to be aware of how to connect to our server via a websocket. Secondly that script would have to know the websocket API in order to intelligently issue messages that would do harm. This isn’t something a generic script could do - that would either mean the application was specifically targeted or that the malicious user has write access to our codebase via some other vulnerability (maybe it’s sanitized params - maybe they have shell access as a user that has write privileges).

The difference here is that both of these attacks can happen now - whereas only the latter could happen if users were using http_only cookies to authenticate.

To be fair, I agree that exposing the cookie to developers does increase the attack surface for CSWSH, but I would personally be happy to go documentation hunting and changing everywhere that recommends using meta tags/tokens to an HTTP only cookie and explaining why it’s important not to mess up the server-side origin check for phoenix sockets.

I think it’s important to take advantage to closing off as much attack surface as possible in phoenix and I don’t think we can say that we’re doing that currently. My preference would be to allow access to http_only cookies and start educating everyone on how to make sure you aren’t vulnerable to CSWSH

This code appears to outline that it has safe defaults that make you whitelist allowed domains. I haven’t dug into the functionality, but the error message would lead me to believe that is the case.

1 Like

To be more transparent - I’m working on a library that could really utilize http-only cookies for both http auth and websocket auth.

There are heavier ways to ensure my library would still be safe to use, but having http_only cookie access within the socket connect would really keep things simple and easy to explain to new users.

Essentially the library is trying to bridge the request/response nature of http but give the server push access via websockets - in order to achieve more parity it would be very beneficial to have one secure way to authenticate for both http and websocket upgrades.

As you can imagine, people do not follow the spec accordingly. Some tools like Cordova send file:/// as the origin, which means that to support Cordova, you open up a vector attack to anyone who would open an .html file via the filesystem.

How is XSS related to CSRF?

If your concern is custom scripts stealing your information, then the custom scripts could simply send a malicious version of the .js to the client. Any attack that starts with the premise of remote code execution is already more vulnerable than CSWSH.

You can always build your own transports. We won’t help you shot yourself in the foot though. :slight_smile:

2 Likes

Oh, nice to hear you are the one working on Texas! I am really looking forward to your talk at ElixirConf! :smiley:

1 Like

I’ve never yet seen a non-browser websocket tool that doesn’t accept an origin request (in addition I’ve not seen any non-browser tool able to use the cookies from the browser either), and if something did not support stated an origin then I don’t see how it would work with any websocket conforming server implementation? Though I’ve never heard of Cordova so I’m unsure of how popular it is or what it’s use is either, but I still doubt it pulls the cookie from the browser as it is?

Eh just listing security examples, I was conflating form post injections and all together, rushing too fast due to lack of time to type here. ^.^

And right you aren’t by the origin check, shooting someone in the foot is their own doing and entirely outside of standard operating setup, but disallowing a standards based storage method is highly irritating regardless and I’m still not seeing the point in it.

Note that SameSite may make cookies safe once again but we are probably a couple years away still.

1 Like

As of November 2017 the SameSite attribute is implemented in Chrome, Firefox, and Opera.

Oh hey! That’s why I’ve been seeing lax and strict things on cookies on sites lately!

We’d definitely want a way to use different cookies, one lax and one strict for sure, different session types!!!

I does, though if you have a public WS API (and if you have native apps as clients, you have to) you must disable the origin check.

No you don’t, just specify an origin in the library you use, every native websocket client library I’ve touched yet (5 while I was testing) have an option to specify the origin. :slight_smile:

Hm, I used React Native with Phoenix Socket library (technically not a native ws lib) and didn’t see such an option, here are the ones the can be specified https://github.com/mspanc/phoenix_socket/blob/master/dist/socket.js#L590-L617 maybe it would work as a key in params?

Anyway the way I see it is an origin that we can freely control can also be controlled by a malicious 3rd party so it doesn’t add much in terms of security.

You aren’t allowed to control the origin header from inside the web browser sandbox as per the standard, hence why a web browser javascript library doesn’t expose such an option. :wink:

The origin header is to control web browser security, if you are in native code then you could easily just scrape a webpage to get a token (potentially even grabbing the web browsers cookie or whatever else) and then feed that in to the live websocket, so none of that security matters anyway for native code (hence why you should build a proper auth system if you need auth regardless, but the browser should be able to store that auth in a cookie backed by an origin as that is the way the standard was written).

1 Like

pretty much what I meant as well :slight_smile:

The best explanation is here:

1 Like

Exactly my point, hence why it should be stored in the cookie.

For example, for what JWT is designed for.

I’ve carefully read all the concerns about security (I agree with), BUT I think you are just considering scenarios in which the same Phoenix backend is both the authentication service AND the websocket endpoint.

I want to suggest you another scenario, for which the suggested workaround of using a csrf token from the Phoenix backend won’t be possible.

Let’s say we have these services:

A - login.somesite.com: Ruby on Rails (or whatever you prefer) backend that has the responsibility to Authenticate user (= to return a jwt token, in response to a login request, through a HttpOnly, secure, SameSite strict)
B - www.somesite.com: another (micro)service that renders tha app and manges the Business logics.
C - communications.somesite.com: the Phoenix (micro)service that handles WS Communications.

If you prefer, B & C might be the same service, no matter.

Service A sends a signed JWT HttpOnly cookie on domain “.somesite.com”, so that each services can exploit a decentralized authentication / verification, without the need of asking A (of course, that’s the way JWT has been designed for).
The wss protocol used to open a bidirectional channel, as you pointed out, allow us to send that jwt cookie to the C service (Phoenix) too, without the need to make the jwt accessible to the JS (it would open a way more dangerous security problem, if we woudn’t have that cookie “HttpOnly”).

Here, unfortunatly, comes the Phoenix backend with its design choices, that prevent us from reading that cookie (that wss protocol would let us send), making it useless.

I agree with the concerns that having a secure first design approach is in general a good choice, but in this case:

  • Developer has one secure way to prevent the security risk (CSWSH), simply by enforcing the Origin checks on server side.
  • Developer has no way to access the cookie, since the framework has chosen to deny its access.

I would have prefered - in these cases - a “smart config” approach, if you care so much about CSWSH risk, where developers can - through an opt-in choice in server configuration - choose to have the cookie available in the connection endpoint.

What do you think about?

1 Like

We can get access to the user_token natively by copying LiveView’s approach (CSRF):

# lib/web/endpoint.ex
@session_options [ ... ]

socket "/socket", Web.UserSocket, websocket: [connect_info: [{:session, @session_options}]]
// assets/app.js
let csrf_token = /* get via `csrf_meta_tag()` or `csrf_token_value()` */
let socket = new Socket("/socket", {params: {_csrf_token: csrf_token}});
# lib/web/channels/user_socket.ex
def connect(_params, socket, %{session: session}) do
  if user_token = Map.get(session, "user_token") do
    user = Account.get_user_by_session_token(user_token)
    {:ok, assign(socket, :user, user)}
  else
    :error
  end
end
2 Likes