Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is my websocket's server origin checking safe?

I'm using https://github.com/lemmingzshadow/php-websocket/

I can allow some domains, and I have allowed localhost and a domain which points to my local server. But I wonder if someone else which has a server on his computer can connect to my websocket (through my domain) using an script in his localhost server.

Here is the relevant code:

-> server/server.php

$server->setAllowedOrigin('localhost');
$server->setAllowedOrigin('mydomain.com');

-> server/lib/WebSocket/Connection.php

// check origin:
if($this->server->getCheckOrigin() === true)
{
    $origin = (isset($headers['Sec-WebSocket-Origin'])) ? $headers['Sec-WebSocket-Origin'] : false;
    $origin = (isset($headers['Origin'])) ? $headers['Origin'] : $origin;
    if($origin === false)
    {
        $this->log('No origin provided.');
        $this->sendHttpResponse(401);
        stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
        $this->server->removeClientOnError($this);
        return false;
    }

    if(empty($origin))
    {
        $this->log('Empty origin provided.');
        $this->sendHttpResponse(401);
        stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
        $this->server->removeClientOnError($this);
        return false;
    }

    if($this->server->checkOrigin($origin) === false)
    {
        $this->log('Invalid origin provided.');
        $this->sendHttpResponse(401);
        stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
        $this->server->removeClientOnError($this);
        return false;
    }
}

-> server/lib/WebSocket/Server.php

public function checkOrigin($domain)
{
    $domain = str_replace('http://', '', $domain);
    $domain = str_replace('https://', '', $domain);
    $domain = str_replace('www.', '', $domain);
    $domain = str_replace('/', '', $domain);

    return isset($this->_allowedOrigins[$domain]);
}

public function setAllowedOrigin($domain)
{
    $domain = str_replace('http://', '', $domain);
    $domain = str_replace('www.', '', $domain);
    $domain = (strpos($domain, '/') !== false) ? substr($domain, 0, strpos($domain, '/')) : $domain;
    if(empty($domain))
    {
        return false;
    }
    $this->_allowedOrigins[$domain] = true;     
    return true;
}

Edit:

Maybe I wasn't clear enough. I want that everybody can connect to the websocket but only if they are at my domain (or my localhost), something like Same Origin Policy in AJAX.

My worry is that if I allow localhost, maybe all other localhost in other computers will be allowed too.

like image 908
Oriol Avatar asked Dec 27 '12 17:12

Oriol


People also ask

Can WebSockets be hacked?

In fact, the Cross-Site WebSocket Hijacking attack is possible when the WebSocket handshake is vulnerable to CSRF. Indeed, the communication channel between the two parties (client/server) is created according to the origin of the opening request.

How do I protect my WebSocket connection?

You should strongly prefer the secure wss:// protocol over the insecure ws:// transport. Like HTTPS, WSS (WebSockets over SSL/TLS) is encrypted, thus protecting against man-in-the-middle attacks. A variety of attacks against WebSockets become impossible if the transport is secured.

Is WSS protocol secure?

WSS is secure, so it prevents things like man-in-the-middle attacks. A secure transport prevents many attacks from the start. In conclusion, WebSockets aren't your standard socket implementation. WebSockets are versatile, the established connection is always open, and messages can be sent and received continuously.

Are WebSockets vulnerable?

Some WebSockets security vulnerabilities arise when an attacker makes a cross-domain WebSocket connection from a web site that the attacker controls. This is known as a cross-site WebSocket hijacking attack, and it involves exploiting a cross-site request forgery (CSRF) vulnerability on a WebSocket handshake.


2 Answers

If you want to be sure, you should add an IP check, analyzing $_SERVER["REMOTE_ADDR"]. The origin that is being checked is a text value provided by the client which can easily be forged.

if (!in_array($_SERVER["REMOTE_ADDR"], array("127.0.0.1", "ip.of.dom.ain")) exit;

If you don't want to hardcode the IP address or if that changes frequently, you can use gethostbyname to fetch the IP address for the domain like this. Be aware that this will add a delay to each request when the DNS must be queried for the domain(s) and this way will cause timeouts when your DNS goes down. (the following code could be optimized, of course)

$allowed_origins = array('localhost', 'mydomain.com');
$allowed_ips = array();
foreach ($allowed_origins as $domain) {
    $server->setAllowedOrigin($domain);
    $allowed_ips[] = gethostbyname($domain);
}
if (!in_array($_SERVER["REMOTE_ADDR"], $allowed_ips)) exit; 

Probably the proper way to do this would be to protect your resource through your web server so that it only accepts requests from the IP addresses you wish to allow (see deny in Apache or deny in nginx).

like image 151
akirk Avatar answered Sep 17 '22 11:09

akirk


Although I was worried about allowing connections from all localhost when I wanted to allow only my localhost, i have found that in another question, kanaka said:

To clarify, Javascript running in an uncompromised and well-behaved browser cannot affect the value of Sec-WebSocket-Origin which holds the value of the original site the page was loaded from (allowing your server to only allow certain origination points). However, if you had Javascript running in Node.js as a WebSocket client, it could set the origin value to whatever it wants. The CORS security is for the safety of browser users, not for your server. You need other mechanisms to protect your server.

Then, my origin checking code is not safe at all; and it doesn't matter if all allow all localhosts because if someone has enough knowledge for connecting to my websocket using his own localhost, it is likely that he could modify the Origin header.

like image 26
Oriol Avatar answered Sep 18 '22 11:09

Oriol