Re: [hybi] A WebSocket handshake

Willy Tarreau <w@1wt.eu> Wed, 06 October 2010 05:33 UTC

Return-Path: <w@1wt.eu>
X-Original-To: hybi@core3.amsl.com
Delivered-To: hybi@core3.amsl.com
Received: from localhost (localhost [127.0.0.1]) by core3.amsl.com (Postfix) with ESMTP id 1687A3A6BF4 for <hybi@core3.amsl.com>; Tue, 5 Oct 2010 22:33:08 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -1.926
X-Spam-Level:
X-Spam-Status: No, score=-1.926 tagged_above=-999 required=5 tests=[AWL=-1.683, BAYES_00=-2.599, HELO_IS_SMALL6=0.556, J_CHICKENPOX_31=0.6, J_CHICKENPOX_51=0.6, J_CHICKENPOX_81=0.6]
Received: from mail.ietf.org ([64.170.98.32]) by localhost (core3.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id AWt-uuBtZPBl for <hybi@core3.amsl.com>; Tue, 5 Oct 2010 22:33:06 -0700 (PDT)
Received: from 1wt.eu (1wt.eu [62.212.114.60]) by core3.amsl.com (Postfix) with ESMTP id 8F6A53A6D91 for <hybi@ietf.org>; Tue, 5 Oct 2010 22:33:04 -0700 (PDT)
Received: (from willy@localhost) by mail.home.local (8.14.4/8.14.4/Submit) id o965Y0Cq022382; Wed, 6 Oct 2010 07:34:00 +0200
Date: Wed, 06 Oct 2010 07:34:00 +0200
From: Willy Tarreau <w@1wt.eu>
To: Adam Barth <ietf@adambarth.com>
Message-ID: <20101006053400.GC20095@1wt.eu>
References: <AANLkTimQ5x-v+Mz_OHrNDdtVd94E+HOBWwo3_f1ktEeg@mail.gmail.com>
Mime-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Disposition: inline
In-Reply-To: <AANLkTimQ5x-v+Mz_OHrNDdtVd94E+HOBWwo3_f1ktEeg@mail.gmail.com>
User-Agent: Mutt/1.4.2.3i
Cc: Hybi <hybi@ietf.org>
Subject: Re: [hybi] A WebSocket handshake
X-BeenThere: hybi@ietf.org
X-Mailman-Version: 2.1.9
Precedence: list
List-Id: Server-Initiated HTTP <hybi.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/listinfo/hybi>, <mailto:hybi-request@ietf.org?subject=unsubscribe>
List-Archive: <http://www.ietf.org/mail-archive/web/hybi>
List-Post: <mailto:hybi@ietf.org>
List-Help: <mailto:hybi-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/hybi>, <mailto:hybi-request@ietf.org?subject=subscribe>
X-List-Received-Date: Wed, 06 Oct 2010 05:33:08 -0000

Hello Adam,

First, thanks for your work on this handshake. I found several interesting
points and a few concerning ones in your proposal.

Comments :
1) subsequent HTTP requests.

> Client -> Server:
> POST /path/of/attackers/choice HTTP/1.1
> Host: host-of-attackers-choice.com
> Sec-WebSocket-Key: <connection-key>
> 
> Server -> Client:
> HTTP/1.1 200 OK
> Sec-WebSocket-Accept: <connection-key>
> 
> The idea behind this protocol is that by echoing back the
> connection-key, the server has agreed to establish a WebSocket
> connection.  Unfortunately, this handshake has serious problems.  If
> the attacker can host an htaccess file at any location a target HTTP
> server, the attacker can opt the server into using WebSockets.  The
> server will believe the first HTTP request is complete and is
> expecting another HTTP request on the socket.  However, the attacker
> can now send (roughly) arbitrary bytes on the socket, spoofing HTTP
> requests and reading back the response.

That's exactly why I've been advocating for several months to add
"Connection: close" in requests and responses. It's simple, efficient,
well defined by the HTTP protocol to achieve exactly what we want, which
is :
  - for the client to tell the next point (intermediaries or server)
    that it is the last request and that it expects the server to do
    so in response ;

  - for the server to tell the previous intermediary or client that
    this is the last response over this connection and that it will
    not process any other one.

Additionally, I suggested that we add "Content-length: 0" in the
response so that intermediaries that don't understand the upgrade
won't blindly dump all the bytes on the connection until the close.

These points are really important, because without the connection: close,
we're actively telling the other side that we expect to reuse the connection
for sending other requests. Thus we must not be surprized by the nasty side
effects of our wrong use of the protocol !

(...)
> To attempt to repair this vulnerability, we remove the attacker?s
> ability to designate a PHP script on the server:
> 
> Client -> Server:
> OPTIONS * HTTP/1.1
> Host: host-of-attackers-choice.com
> Sec-WebSocket-Key: <connection-key>
> 
> Server -> Client:
> HTTP/1.1 200 OK
> Sec-WebSocket-Accept: HMAC(<connection-key>, ?...?)
> 
> This handshake still has problems in more sophisticated virtual
> hosting scenarios, but let?s put those aside for the moment to
> consider how this handshake interacts with transparent HTTP proxies.
> Recall that the browser will not use the proxy version of the
> handshake because the proxy is transparent.
> 
> Unfortunately, this handshake is likely to confuse a transparent
> proxy.  After seeing these messages exchanged, a transparent proxy
> will likely believe that the next bytes emitted by the browser will be
> another HTTP request.

Same here, this would not be the case with "connection: close".
However, the "OPTIONS *" request may fail on a number of intermediaries,
some of which will take it for them and reply with their own options
instead of forwarding it. Well, at least the response will not contain
the WS headers so that might be a clean failure we may want to exploit
in the end.

> It seems entirely likely that some number of transparent proxies will
> be oblivious to the HTTP upgrade mechanism.

The two types of incorrect handling of the Upgrade mechanism I've already
seen (and naively implemented myself) :

  - 101 is considered as any valid response (such as 200). Data flow
    back until the connection closes. Most of the time nothing flows
    from the client to the server, but that may work depending on the
    implementation. Advertising "connection: close" and "content-length: 0"
    in the response will definitely help making them fail cleanly if we
    don't want to rely on them.

  - 101 is considered as any 1xx response and is handled by default as
    a "100 Continue", which means the response is skipped and the
    intermediary waits for another response from the server. The "next"
    response does not exist here, it's just raw data. Some will consider
    those data as an invalid response (does not look like HTTP), others
    may consider those data as an HTTP/0.9 response and forward them to
    the client until the close.


2) use of CONNECT

> Rather than relying upon the rarely used HTTP upgrade mechanism to
> inform network intermediaries that the remainder of the socket is not
> HTTP, we propose using the RFC 2817 CONNECT mechanism.  This mechanism
> is widely used on the Internet to tunnel TLS connections through
> proxies.  Proxy implementations that lack support for the CONNECT
> mechanism will likely discover and repair that oversight quickly.

That's interesting that you suggest using CONNECT. When I did so in
january or february, I got some negative responses concerning the fear
that some servers also contain proxy parts and that for this reason the
CONNECT method could be dangerous. I don't completely agree with that
because in my opinion, it's no different than sending a proxy-like
request to those servers. And the CONNECT has all the semantics we're
looking for, since it establishes a tunnel the intermediaries should
not look into.

> === Handshake Request ===
> 
> To establish a WebSocket connection, the browser sends an RFC 2817
> CONNECT request:
> 
> Client -> Server:
> CONNECT 1C1BCE63-1DF8-455C-8235-08C2646A4F21.invalid:443 HTTP/1.1
> Host: 1C1BCE63-1DF8-455C-8235-08C2646A4F21.invalid:443
> Sec-WebSocket-Key: <connection-key1>
> 
> where <connection-key1> is a 128-bit random number encoded in base64.
> This initial message has several desirable properties:
> 
> 1) The attacker cannot influence any of the bytes included in the
> message.  Instead of using the attacker?s host name, we use an invalid
> host name (per RFC 2606).  Although we could use any invalid host
> name, we use this host name as a globally unique identifier for the
> WebSocket protocol.

I have a problem with the invalid host name here. It breaks the ability
to support virtual hosting, which really is critical to web hosting
companies. If we used the correct target host's name, 4 possible things
could happen :

  - a transparent intermediary simply forwards it to the original destination
    IP without interpreting it. That's what we expect from a client point of
    view, so the intermediary does not cause any issue.

  - a transparent intermediary interprets it and forwards it to the
    requested host : great, that's precisely what we're looking for, so
    the intermediary does not cause any issue either.

  - the target receives it and handles it correctly. It finds its host
    name and knows what internal component to forward it to. That's
    what we're looking for.

  - the target mis-interprets it as a proxy request and forwards it to
    itself in loops. After a few hundreds to thousands iterations, the
    target is deadlocked and the issue is spotted by the admin. Anyway,
    such broken targets on the net (if they exist) are already vulnerable
    so we can't rely on a new protocol to protect them.

> 2) Any intermediaries that understand this message according to its
> HTTP semantics with route the request to a non-existent domain and
> fail the request.  In particular, they will not route the
> Sec-WebSocket-Key to the attacker, making it difficult for the
> attacker to perform actions based on the key.

If we had the proper URL in the request, it would not be routed to the
attacker either.

> 3) Transparent proxies are likely to interpret this request as an
> HTTPS connect request and assume the remainder of the socket is
> unintelligible.  Because the remainder of the bytes on the socket are
> encrypted (see below), the attacker is unlikely to be able to trick
> the transparent proxy into taking further action.
> 
> 4) This message cannot be generated by a web attacker in today?s browsers.

I wholeheartly agree with that. That's also why I preferred the CONNECT
or OPTIONS request vs the GET.

> 5) A server that wishes to multiplex HTTP and WebSockets on the same
> port can use the request-line to distinguish the two protocols.
> 
> The client can also include additional information in the first
> handshake message by encrypting that information in AES-128-CTR using
> the key HMAC-SHA1(<connection-key1>,
> ?C1BA787A-0556-49F3-B6AE-32E5376F992B?) and a counter block that is
> the byte number represented in 128-bit network byte order
> (big-endian).  We expect browsers to use this additional information
> to include additional meta-data about the connection (e.g., the origin
> of the web site that created the WebSocket) rather than
> application-layer messages.

This possibly reduces the number of round trips during the handshake,
which is good.

> Encrypting the additional information makes it difficult for the
> attacker to predict the bytes that appear on the wire.  Without the
> ability to predict on-the-wire bytes, the attacker will have
> difficulty crafting a network message that confuses a non-WebSocket
> server or an intermediary.

What I like with a payload-only encryption vs a connection encryption
is that it still maintains the ability to install dedicated filtering
components in schools. Such components will technically work as transparent
proxies and will just be MITM. They will have two encrypted channels,
one with the browser and one with the server and they'll be able to
see the exchanged data in clear text and be able to filter based on
that.

Adam, could you please recheck the importance for this handshake to
have the invalid hostname ? It really is the only blocking issue I
can think of. If it is absolutely needed, maybe we could use something
like "<valid-hostname>.websocket.invalid" instead so that at least the
server-side components can route the request to the proper location ?

Thanks,
Willy