Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebSockets + PHP (Ratchet) login system

I know how to make a secure login system with PHP including basics like hashing, salting and more complex security measures.

Recently I've been using Ratchet with WebSockets a lot. And I've been wondering if it is possible to create a secure register/login system using WebSockets.

  1. User enters his data in form.
  2. Data is validated and sanitized.
  3. Password is hashed.
  4. Everything is being sent to the server via WebSocket.
  5. PHP stores it in MySQL database and salts the password's hash.
  6. PHP returns a callback that registration finished via WebSocket.
  7. User is granted access to the website.

I see it might work. My doubts are here: how secure is ws:// protocol? How to make sure that after logging in the user is the user that was logged? Some sort of sessions? Tokens?

Are there any frameworks/libraries/implementations of such a thing?

Questions are more out of plain curiosity over a new (kinda) piece of tech. If it doesn't work I'll just go for AJAX POST request. :)

like image 567
Tomasz Gałkowski Avatar asked Dec 25 '22 17:12

Tomasz Gałkowski


1 Answers

No, it is not possible to set up a secure authentication using any of the current WebSocket servers written in PHP.

They all lack TLS support. (Even mine, though I do have an active development branch devoted to providing TLS support.)

Here is what is required for a secure authentication system with WebSockets:

Basic PHP security first:

  1. Always transmit via TLS or some other secure transport system.
  2. Use bcrypt or scrypt (or a future more secure system) to hash the password for storage in the database. Even a stretched SHA256 password with a salt generated from a cryptographically random source is now considered insecure.
  3. Authentication tokens (such as the session token created by PHP and stored in the session cookie) must be cryptographically unpredictable.
  4. Authentication tokens must not be available to non-TLS traffic or client scripts. (Strict enforcement of the Secure flag AND strict enforcement of the HttpOnly flag.) This explicitly means that a user can not be securely authenticated unless they are in https: and wss: mode.
  5. Session data must be stored in a secure directory or data storage on the server.
  6. Reset the session token as part of authentication. Completely delete the data associated with the old session ID.

PHP's built in algorithm for generating session IDs uses /dev/urandom if it is available on your system. Windows users do not get cryptographically random session IDs, though because most WebSockets implementations would require a Posix environment, this isn't an issue. If rolling your own session ID generator, use openssl_random_pseudo_bytes(), and remember that when it comes to security, it's a bad idea to roll your own.

Also, by default, the PHP session cookie does not have the Secure flag or the HttpOnly flag set. Change these in your php.ini, and yes, this means that people can not authenticate anyone who is not using a secure connection -- but this is true anyways, since without TLS, you can not ensure that anyone is the same user from request to request, even if they're using the same session ID and same IP.

And, by default, the PHP session save path is your system's temp directory. This is readable and writable by anyone. Thus, any rogue process, regardless of escalated privileges, can access and alter any session data. Least access means that only the web server user and, in the case of those of us using WebSockets, the WS server's user needs to access the session save path.

WebSockets specific authentication:

The session cookie is available to the WebSocket server during the handshake. This includes when the cookie has the HttpOnly flag set. (Tested on latest publicly available Chrome on Windows.) (Secure flag not testable yet. Give me a couple months as I get TLS working on my server. If secure cookies are not available, or are available in a non-secure context, I'll be personally raising a holy stink with the browser vendors on your behalf.)

This means that someone who has an authenticated session can also be pre-authenticated on their WebSocket connection without having to pass data through the web document body (no need to embed the session ID in the HTML or in data in a script tag, which would then be re-transmitted through the open WS connection).

As for securely authenticating someone using WebSockets... There is one huge caveat.

Because WebSocket servers have multiple users connected at the same time, the traditional sense of PHP sessions is broken. Which session do we mean?

Thus, the $_SESSION superglobal MUST be strictly off limits, and the session_...() functions should be considered highly dangerous. The only safe and meaningful session functions are the read-only version of session_save_path(), which tells you where to find the session files, session_encode(), and session_decode().

To get the session data, you will need to read the session file, running its contents through session_decode(), and associating the results with the specific connection for that specific user. You will also need to do this periodically, as the session data changes without warning. To persist any data to the session, session_encode() is your friend, of course. Since PHP doesn't care about race conditions on session data, you shouldn't either. Just read the file, change the data, and write to the file, and it'll be all good.

And this brings us to the first real problem: In order to have secure authentication, you need to reset the session token when the user is authenticated. Since client side Javascript can't write to an HttpOnly cookie, you can't change the session ID on the client side.

The status of whether the user is authenticated can easily be updated within the existing session data, of course. That's fairly trivial. However, because we need a new session ID before we persist the new authentication state, and that new session ID must be valid for all of the client's connections immediately, we can't completely perform secure authentication through a WebSocket connection.

A possible work-around to use as much WebSockets as possible (once a PHP WS server supports TLS, that is):

  • Client sends login information to the server through the WebSocket connection.
  • Server responds back with a cryptographically random token.
  • Client responds back with a salt through the WS connection, and with a hashed and salted version of the token through an AJAX request.
  • Server verifies the salt matches, and using the AJAX request's script, calls session_regenerate_id(true), and returns any appropriate response (just so long as the set-cookie: header is set).
  • Server updates its internal mapping to the user's new session ID.
  • Server discards the one-time token completely, and naturally logs any attempted use of any invalid one-time tokens.

(It might be worth it to implement a true Diffie-Hellman exchange to set up a true secure shared secret. I am not a security expert; the above algorithm may not be as secure as I think it is, though because it would already be in two TLS tunnels, it should be secure. (The https: tunnel is separate and distinct from the wss: tunnel, thus there is a need to confirm information sent through one tunnel matches information sent through the other, but should not require secrecy in addition to that already afforded by TLS.))

Bottom line:

Yes, security is hard to get right. But it's worth it.

like image 145
Ghedipunk Avatar answered Dec 27 '22 21:12

Ghedipunk