Re: [hybi] WebSocket -76 is incompatible with HTTP reverse proxies

Micheil Smith <micheil@brandedcode.com> Wed, 07 July 2010 02:44 UTC

Return-Path: <micheil@brandedcode.com>
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 5B62E3A6974 for <hybi@core3.amsl.com>; Tue, 6 Jul 2010 19:44:44 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: 0.001
X-Spam-Level:
X-Spam-Status: No, score=0.001 tagged_above=-999 required=5 tests=[BAYES_50=0.001]
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 KMLtFcwGo+BD for <hybi@core3.amsl.com>; Tue, 6 Jul 2010 19:44:41 -0700 (PDT)
Received: from mail-pw0-f44.google.com (mail-pw0-f44.google.com [209.85.160.44]) by core3.amsl.com (Postfix) with ESMTP id 4B2653A6967 for <hybi@ietf.org>; Tue, 6 Jul 2010 19:44:40 -0700 (PDT)
Received: by pwj1 with SMTP id 1so1728138pwj.31 for <hybi@ietf.org>; Tue, 06 Jul 2010 19:44:40 -0700 (PDT)
Received: by 10.142.142.1 with SMTP id p1mr6645696wfd.10.1278470680732; Tue, 06 Jul 2010 19:44:40 -0700 (PDT)
Received: from [192.168.46.181] (124-170-233-189.dyn.iinet.net.au [124.170.233.189]) by mx.google.com with ESMTPS id l40sm6599198rvb.6.2010.07.06.19.44.36 (version=TLSv1/SSLv3 cipher=RC4-MD5); Tue, 06 Jul 2010 19:44:39 -0700 (PDT)
Mime-Version: 1.0 (Apple Message framework v1081)
Content-Type: text/plain; charset="us-ascii"
From: Micheil Smith <micheil@brandedcode.com>
In-Reply-To: <20100706210039.GA12167@1wt.eu>
Date: Wed, 07 Jul 2010 12:44:32 +1000
Content-Transfer-Encoding: quoted-printable
Message-Id: <B709B846-2A8C-4B84-8F4D-B06B81D91A7B@brandedcode.com>
References: <20100706210039.GA12167@1wt.eu>
To: hybi@ietf.org
X-Mailer: Apple Mail (2.1081)
Subject: Re: [hybi] WebSocket -76 is incompatible with HTTP reverse proxies
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, 07 Jul 2010 02:44:44 -0000

Willy,

Thanks for raising this point. I'm an author of a node.js powered websocket 
server, and this was actually a really awkward part of the websocket spec to 
comply to when writing my server.

Without having a content-length header, the http parser in node doesn't know 
how to handle the extra data it receives in that initial handshake. What we 
ended up doing as a fix, was just to provide an upgradeHead as a slice of the
buffer from where the headers ended to the end of the packet.

In short, I'd be all for having a content-length header sent to declare the body 
as a body, or to declare it as just extra data that should be handled post upgrade.
From what I can tell from what Ian is telling me over irc, he'd be more inclined to 
add in content-length: 0 as a header, then content-length: 8.

Hopefully this reply does make somewhat sense. 

Yours,
Micheil Smith
--
BrandedCode.com

ps: I'm hoping this does actually reply to the mailing list, rather then the individual.


On 07/07/2010, at 7:00 AM, Willy Tarreau wrote:

> Hi,
> 
> I was having a private discussion with Ian last week about a recent
> issue introduced in draft 76, but I realized it would be more useful
> and constructive to bring the issue to the list than to privately
> discuss possible fixes.
> 
> Last week, it was reported to me that a site that was running fine
> on draft 75 could not get the draft 76 handshake to complete via a
> HAProxy load balancer, which runs as an HTTP reverse proxy. The
> connection would remain open between the client and haproxy, and
> between haproxy and the server, with the server never responding.
> The same client (Chromium 6.0.414.0) directly connected to the
> server worked fine.
> 
> The guy was kind enough to send me some network captures which show
> an obvious problem : the 8-bytes nonce from the client is not advertised
> as a content-length, so it is not forwarded by the reverse proxy as
> it is either part of a next request or pending data for when the
> handshake completes. Unfortunately, the server wants those bytes to
> complete the handshake, so we have a dirty deadlock not even detectable
> by the end user.
> 
> Ian proposed to upgrade the reverse proxy to detect the WebSocket
> handshake in the request (before it completes) and that it accepts
> to forward those 8 bytes.
> 
> I can't agree with that because until the handshake completes, the
> proxy does not know whether the server will handle the request as
> a WS handshake or anything else, and it must absolutely not accept
> to blindly trust any random client who sets an Upgrade header that
> any server is free to ignore. Doing so would make the reverse-proxy
> vulnerable to HTTP request smuggling attacks [1] or even to any type
> of filtering bypass depending on the length of the data it lets go.
> Even with 8 bytes it is possible to send a "GET /xx\n" which is a
> valid HTTP/0.9 request and is accepted by some servers in a keep-alive
> connection (including Apache).
> 
> Example :
> 
>       GET /index.html HTTP/1.1
>       Host: example.com
>       Connection: Upgrade
>       Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
>       Sec-WebSocket-Protocol: sample
>       Upgrade: WebSocket
>       Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
>       Origin: http://example.com
> 
>       GET /..
> 
> If the server does not handle WebSocket for this ressource, it will
> happily return the index and/or a 404 and proceed with next request
> contained in the 8 bytes it received.
> 
> Conversely, having no Content-Length header in the request means that
> we don't know what a reverse proxy will do if it receives a valid one.
> For instance, we could very well imagine that some reverse proxies
> which will assume that Content-Length == 8 for any request containing
> "Upgrade: WebSocket" will have trouble when receiving a different
> Content-Length header. This could be used to pass larger amounts of
> data than what is allowed by the protocol to a second reverse-proxy,
> which, if it is able to parallelize pipelined requests, will forward
> the first one to the server and the second one (embedded in the apparent
> data) to another server.
> 
> The first obvious solution that comes to mind is to comply with the
> HTTP protocol which will be implemented along the whole chain and to
> simply add a "Content-Length: 8" header in the request. This fixes
> the dirty hang, this fixes the fact that reverse proxies have to blindly
> trust the client, this fixes the case with the different content length,
> and it even makes it possible for WebSocket aware reverse proxies to
> refuse requests which don't have exactly "Content-Length: 8".
> 
> But this raises a second point : shouldn't we switch to POST instead of
> GET then ? After all, GET + content-length is not well defined, httpbis
> p1 says :
> 
>  The presence of a message-body in a request is signaled by the
>  inclusion of a Content-Length or Transfer-Encoding header field in
>  the request's header fields.  When a request message contains both a
>  message-body of non-zero length and a method that does not define any
>  semantics for that request message-body, then an origin server SHOULD
>  either ignore the message-body or respond with an appropriate error
>  message (e.g., 413).  A proxy or gateway, when presented the same
>  request, SHOULD either forward the request inbound with the message-
>  body or ignore the message-body when determining a response.
> 
> So that means that we're not certain again that the data will pass
> through all reverse proxies. That said, we could consider that WebSocket
> aware reverse proxies will let the data flow, but the problem of the hung
> connection remains if the reverse proxy eats the data before forwarding
> to the server.
> 
> The POST solves all that. POST + content-length is normal and well
> supported everywhere. The POST also has the nice advantage that we're
> sure we won't get a reply from a cache (and the POST method is even
> one of the 3 defined methods which must invalidate caches).
> 
> The POST also has a nice advantage for various client implementations
> that it is easier to implement than GET with some data.
> 
> After some thinking, I'm wondering why we want to pass the nonce as
> data in the request instead of passing it as headers. After all, we're
> interested in data in the response to ensure we're able to let data
> flow and that the whole chain understands the Upgrade request. In fact,
> if any one intermediate ignores the 101 and takes it as a 100 (as is
> explicitly permitted by httpbis-p2), the response will be aborted
> because the pending data does not look like an HTTP response, and this
> is perfect, it's what we're looking for. But in the request ? I fail
> to see the added value of having it in the data. Probably that it would
> be easier to put it in a header and keep the GET.
> 
> Anyway, we have to do something now because we've reached the point Ian
> tried to ensure we would avoid a long time ago : the deadlock which is
> undetectable by the client. And it already happens through a common
> reverse proxy since draft 76, and will do through load balancers, IDS
> and HTTP-aware firewalls for the same reasons, without any simple way
> to fix it without breaking HTTP security on those components. And it's
> not like if we could imagine those components will not be in use where
> WebSocket is deployed !
> 
> Best regards,
> Willy
> 
> [1]   http://www.owasp.org/index.php/HTTP_Request_Smuggling
> 
> 
> _______________________________________________
> hybi mailing list
> hybi@ietf.org
> https://www.ietf.org/mailman/listinfo/hybi


Micheil Smith
--
BrandedCode.com

On 07/07/2010, at 7:00 AM, Willy Tarreau wrote:

> Hi,
> 
> I was having a private discussion with Ian last week about a recent
> issue introduced in draft 76, but I realized it would be more useful
> and constructive to bring the issue to the list than to privately
> discuss possible fixes.
> 
> Last week, it was reported to me that a site that was running fine
> on draft 75 could not get the draft 76 handshake to complete via a
> HAProxy load balancer, which runs as an HTTP reverse proxy. The
> connection would remain open between the client and haproxy, and
> between haproxy and the server, with the server never responding.
> The same client (Chromium 6.0.414.0) directly connected to the
> server worked fine.
> 
> The guy was kind enough to send me some network captures which show
> an obvious problem : the 8-bytes nonce from the client is not advertised
> as a content-length, so it is not forwarded by the reverse proxy as
> it is either part of a next request or pending data for when the
> handshake completes. Unfortunately, the server wants those bytes to
> complete the handshake, so we have a dirty deadlock not even detectable
> by the end user.
> 
> Ian proposed to upgrade the reverse proxy to detect the WebSocket
> handshake in the request (before it completes) and that it accepts
> to forward those 8 bytes.
> 
> I can't agree with that because until the handshake completes, the
> proxy does not know whether the server will handle the request as
> a WS handshake or anything else, and it must absolutely not accept
> to blindly trust any random client who sets an Upgrade header that
> any server is free to ignore. Doing so would make the reverse-proxy
> vulnerable to HTTP request smuggling attacks [1] or even to any type
> of filtering bypass depending on the length of the data it lets go.
> Even with 8 bytes it is possible to send a "GET /xx\n" which is a
> valid HTTP/0.9 request and is accepted by some servers in a keep-alive
> connection (including Apache).
> 
> Example :
> 
>        GET /index.html HTTP/1.1
>        Host: example.com
>        Connection: Upgrade
>        Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
>        Sec-WebSocket-Protocol: sample
>        Upgrade: WebSocket
>        Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
>        Origin: http://example.com
> 
>        GET /..
> 
> If the server does not handle WebSocket for this ressource, it will
> happily return the index and/or a 404 and proceed with next request
> contained in the 8 bytes it received.
> 
> Conversely, having no Content-Length header in the request means that
> we don't know what a reverse proxy will do if it receives a valid one.
> For instance, we could very well imagine that some reverse proxies
> which will assume that Content-Length == 8 for any request containing
> "Upgrade: WebSocket" will have trouble when receiving a different
> Content-Length header. This could be used to pass larger amounts of
> data than what is allowed by the protocol to a second reverse-proxy,
> which, if it is able to parallelize pipelined requests, will forward
> the first one to the server and the second one (embedded in the apparent
> data) to another server.
> 
> The first obvious solution that comes to mind is to comply with the
> HTTP protocol which will be implemented along the whole chain and to
> simply add a "Content-Length: 8" header in the request. This fixes
> the dirty hang, this fixes the fact that reverse proxies have to blindly
> trust the client, this fixes the case with the different content length,
> and it even makes it possible for WebSocket aware reverse proxies to
> refuse requests which don't have exactly "Content-Length: 8".
> 
> But this raises a second point : shouldn't we switch to POST instead of
> GET then ? After all, GET + content-length is not well defined, httpbis
> p1 says :
> 
>   The presence of a message-body in a request is signaled by the
>   inclusion of a Content-Length or Transfer-Encoding header field in
>   the request's header fields.  When a request message contains both a
>   message-body of non-zero length and a method that does not define any
>   semantics for that request message-body, then an origin server SHOULD
>   either ignore the message-body or respond with an appropriate error
>   message (e.g., 413).  A proxy or gateway, when presented the same
>   request, SHOULD either forward the request inbound with the message-
>   body or ignore the message-body when determining a response.
> 
> So that means that we're not certain again that the data will pass
> through all reverse proxies. That said, we could consider that WebSocket
> aware reverse proxies will let the data flow, but the problem of the hung
> connection remains if the reverse proxy eats the data before forwarding
> to the server.
> 
> The POST solves all that. POST + content-length is normal and well
> supported everywhere. The POST also has the nice advantage that we're
> sure we won't get a reply from a cache (and the POST method is even
> one of the 3 defined methods which must invalidate caches).
> 
> The POST also has a nice advantage for various client implementations
> that it is easier to implement than GET with some data.
> 
> After some thinking, I'm wondering why we want to pass the nonce as
> data in the request instead of passing it as headers. After all, we're
> interested in data in the response to ensure we're able to let data
> flow and that the whole chain understands the Upgrade request. In fact,
> if any one intermediate ignores the 101 and takes it as a 100 (as is
> explicitly permitted by httpbis-p2), the response will be aborted
> because the pending data does not look like an HTTP response, and this
> is perfect, it's what we're looking for. But in the request ? I fail
> to see the added value of having it in the data. Probably that it would
> be easier to put it in a header and keep the GET.
> 
> Anyway, we have to do something now because we've reached the point Ian
> tried to ensure we would avoid a long time ago : the deadlock which is
> undetectable by the client. And it already happens through a common
> reverse proxy since draft 76, and will do through load balancers, IDS
> and HTTP-aware firewalls for the same reasons, without any simple way
> to fix it without breaking HTTP security on those components. And it's
> not like if we could imagine those components will not be in use where
> WebSocket is deployed !
> 
> Best regards,
> Willy
> 
> [1]   http://www.owasp.org/index.php/HTTP_Request_Smuggling
> 
> 
> _______________________________________________
> hybi mailing list
> hybi@ietf.org
> https://www.ietf.org/mailman/listinfo/hybi