Re: [hybi] Experiment comparing Upgrade and CONNECT handshakes

Willy Tarreau <w@1wt.eu> Sat, 27 November 2010 07:15 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 318B53A6B43 for <hybi@core3.amsl.com>; Fri, 26 Nov 2010 23:15:44 -0800 (PST)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -1.453
X-Spam-Level:
X-Spam-Status: No, score=-1.453 tagged_above=-999 required=5 tests=[AWL=-1.269, BAYES_20=-0.74, HELO_IS_SMALL6=0.556]
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 RpTqZEqGMKRR for <hybi@core3.amsl.com>; Fri, 26 Nov 2010 23:15:43 -0800 (PST)
Received: from 1wt.eu (1wt.eu [62.212.114.60]) by core3.amsl.com (Postfix) with ESMTP id 844303A697E for <hybi@ietf.org>; Fri, 26 Nov 2010 23:15:42 -0800 (PST)
Received: (from willy@localhost) by mail.home.local (8.14.4/8.14.4/Submit) id oAR7GiP1027979; Sat, 27 Nov 2010 08:16:44 +0100
Date: Sat, 27 Nov 2010 08:16:44 +0100
From: Willy Tarreau <w@1wt.eu>
To: Eric Rescorla <ekr@rtfm.com>
Message-ID: <20101127071644.GB26428@1wt.eu>
References: <AANLkTim_8g-Cb01si00EkvCK5BtXUx3zHsUee1F6JqsD@mail.gmail.com> <AANLkTimSu1fOGCg0gqX2EFh4v-MkpZuY_-onm3+TO_Z0@mail.gmail.com> <AANLkTimYpdp-75BQSmhAUfyrQv19LvzF1ouznst+ANUG@mail.gmail.com> <AANLkTikbycTS51Ein9ybbZ52zcrViFCNBjCmpRGD3yCk@mail.gmail.com> <AANLkTim=_Ey_7tSJ0H8OKzip-UcwtJ=YMG5wf_f_qnty@mail.gmail.com>
Mime-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Disposition: inline
In-Reply-To: <AANLkTim=_Ey_7tSJ0H8OKzip-UcwtJ=YMG5wf_f_qnty@mail.gmail.com>
User-Agent: Mutt/1.4.2.3i
Cc: Hybi <hybi@ietf.org>
Subject: Re: [hybi] Experiment comparing Upgrade and CONNECT handshakes
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: Sat, 27 Nov 2010 07:15:44 -0000

Hi Eric,

On Fri, Nov 26, 2010 at 10:16:45PM -0800, Eric Rescorla wrote:
> Well, in every existing proposal, it proposes to give them control over the
> vast majority of
> bytes on the socket, with some uncontrollable, but predictable framing. In
> order for this to
> be exploitable, you primarily need a proxy which is willing to ignore
> malformed requests
> enough to allow the attacker to resynchronize.

I can't agree with you on this Eric, because your study relies on broken
components that act as you want depending on the test. For instance, there
is the following paragraph concerning the POST-based handshake which is
wrong :

  Consider an intermediary examining packets exchanged
  between the browser and the attacker's server. As above,
  the client requests WebSockets and the server agrees. At
  this point, the client can send any traffic it wants on the
  channel. Unfortunately, the intermediary does not know about
  WebSockets, so the initial WebSockets handshake just looks
  like a standard HTTP request/response pair, with the request
  being terminated, as usual, by an empty line. Thus, the client
  program can inject new data which looks like an HTTP request
  and the proxy may treat it as such. So, for instance, he might
  inject the following sequence of bytes:

It assumes that an intermediary can accept a new HTTP request on
a non-keep-alive response. This does not work, this is not even
remotely looking like HTTP. The intermediary needs a content-length,
a transfer-encoding or a method or status code which implies an
empty body to be able to accept a second request. Here we have a
POST and a 200. Without either a content-length or a tranfer-encoding,
it is the last response and the response goes till the end of the
connection.

This is not even implementation-specific, such a compontent cannot
work without that.

It's important to keep the basic semantics of the HTTP protocol in
mind when addressing the handshake issues, because I once again feel
like we're trying to invent imaginary components for every type of
handshake that has to be dismissed. We could as well invent an imaginary
proxy that badly fails on the CONNECT handshake.

> It's true that we don't have a demonstrated exploit against such a proxy and
> exactly
> what's required in order to mount such an exploit is probably somewhat
> implementation-
> specific. However, I'm unaware of any convincing security analysis that
> demonstrates
> that the framing is a serious obstacle.

Please keep in mind that both the handshake and the framing are important.
Without a proper handshake, we could easily trick an existing proxy into
caching the first response as a wrong one, or perform cross-protocol
connections. The framing is important to prevent the situations where
intermediaries could have been mistaken by the handshake from confusing
raw data with some protocol they speak.

It has been said several times, but designing a keep-alive compatible
proxy really is tough work and requires to respect some key points of
HTTP, particularly the messaging. In order to be deployed, the component
does not have to be safe, but it has to work for the most common situations.
This means that we're sure to find proxies that don't work with 101 responses,
that do not correclty handle multiple content-length, that accept any raw data
in header names etc... (eg: apache has all these flaws but it's one of the
most commonly deployed reverse-proxies). But the component must flawlessly
work on the most common traffic. Basically, handling a POST with a 200 and
supporting correct messaging (ie respecting a missing or a correctly-formed
content-length header) is essential.

This means that if the framing ensures that :
  - initial data sent from the client to the server cannot be parsed as a
    valid HTTP request
  - initial data sent from the server to the client cannot be parsed as a
    valid HTTP response

then we have something that cannot fool these components. For instance, we
could very well have a POST and a 200 both containing a Content-length: 1 gig
and have a safe channel for us for the first gig of data. This would be stupid
because many proxies will not necessarily let the client-to-server data pass
once the server responds. But still it shows why HTTP messaging is important.

We could also ensure that an exchanged hello frame right after the handshake
looks like an un parsable HTTP request/response or a parsable end of HTTP
request/response to intermediaries. One such easy solutions would be to
prevent any CR or LF byte from being sent in the whole framing. In this case,
whatever the handshake, it's really the framing that's protecting you.

That's why I'm saying that both handshake and framing are important and
that we must not ignore reality. It's nice to invent imaginary components
to see how an issue or another could lead to an exploitable situation, but
at one point, it is important to weigh the degree of imagination when drawing
to a conclusion.

I'm not dismissing your work nor am I against the use of the CONNECT method,
indeed I was one of the first to ask for it several months ago. I'm just
trying to ensure that we focus on real world tests with both handshake and
framing trying to do the right thing and being usable by intermediaries as
appropriate.

For example, in your case, the whole security of the CONNECT-based method
relies on the fact that "websocket.invalid" cannot be resolved. Have you
imagined that future malware will try to add their own IP address in front
of "websocket.invalid" in every etc/hosts so that they can try to divert
most of the leaking requests ?

  root@pcw:~# echo "127.0.0.1 websocket.invalid" >> /etc/hosts
  root@pcw:~# ping websocket.invalid
  PING websocket.invalid (127.0.0.1) 56(84) bytes of data.
  64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.022 ms
  ^C

It would even be enough to couple that with DNS poisoning attacks, because
surely some proxies will try to resolve it via the DNS :

  willy@pcw:~$ host -a websocket.invalid
  Trying "websocket.invalid"
  ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52408
  ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

  ;; QUESTION SECTION:
  ;websocket.invalid.             IN      ANY

  ;; ANSWER SECTION:
  websocket.invalid.      3600    IN      CNAME   www.attacker.com.


I'd rather have something which works by protocol construction than by
pure luck because hopefully nobody resolves websocket.invalid.

Regards,
Willy