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.
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.
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.
Oh, nice to hear you are the one working on Texas! I am really looking forward to your talk at ElixirConf!
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.
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.
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.
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).
pretty much what I meant as well
The best explanation is here:
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?
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