Subject: [hybi] More on Payload Masking

From: Zhong Yu <[email-masked]>
Date: Wed, 10 Nov 2010 19:30:51 -0600

Continuation of
http://www.ietf.org/mail-archive/web/hybi/current/msg04685.html

I think I understand Bjoern Hoehrmann's point. I'll give a plausible
example where plaintext WebSocket could be used to attack an HTTP
server. I'll also show that Adam Barth's current masking strategy is
flawed and cannot prevent the attack.

Example: In a virtual host farm, a front dispatcher dispatches HTTP
requests based on Host headers to different HTTP servers. The
dispatcher does not understand WebSocket, yet WebSocket connections
can pass through it (due to our ingenious design). An attacker sets up
server A in the farm in order to attack an HTTP server B in the same
farm. The attacker leads users to a web page hosted on A, which opens
a WS connection to server A. This works because the attacker controls
server A, and the dispatcher leaks WS connections through. The
attacker then writes to the WS connection some HTTP-like data, with
header "Host: B". The dispatcher could be confused and think that the
data is a valid HTTP request, and dispatch the request to server B.
Therefore, using WS client, the attacker can talk to and read from
HTTP server B, from any browser which visits a web page on domain A.
(User's cookies for domain B are *not* sent to server B in this
attack.)

Plausibility: The attack depends on the dispatcher's flaw of parsing
non-http stream into HTTP requests. Such flaws are quite plausible.
There is even a slogan: "Be conservative in what you send; be liberal
in what you accept." The real motives are often just laziness. It is
*harder* to program to be strict and reject all non-conforming data.
For the dispatcher which has very limited functionality, there is
little reason to be fully HTTP savvy. It may have a crude way to
demarcate requests and extract Host headers. Everything works
perfectly if connections are from genuine HTTP clients. That was a
legitimate assumption when the device was deployed. The introduction
of WebSocket breaks that assumption.

Real Chance: How many HTTP components do have such flaws that can
convert WS stream into valid HTTP requests?

Adam Barth's Masking: By masking WS stream, it seems that the attacker
cannot inject data to make the stream look like HTTP at all.
Unfortunately in this attack scenario, he can, given the current
proposed masking. The attacker knows everything about the handshake,
he knows the reverse function of the masking function. This problem
can be fixed by introducing more random keys during the communication.

- Zhong Yu

---

From: John Tamplin <[email-masked]>
Date: Wed, 10 Nov 2010 20:37:35 -0500

> can be fixed by introducing more random keys during the communication.

This is true only because XOR is invertible -- if we went back to the
original case of AES which is not forgeable, then the attack could not
generate any chosen ciphertext by controlling the plaintext.

-- 
John A. Tamplin
Software Engineer (GWT), Google

---

From: Bjoern Hoehrmann <[email-masked]>
Date: Thu, 11 Nov 2010 03:00:51 +0100

* Zhong Yu wrote:
>[...]

I think your understanding is quite correct.

>convert WS stream into valid HTTP requests?

This is not necessarily a flaw if you can arrange the frames so they
come out as a valid HTTP request. I know that with some open source
HTTP servers you'd have little chance to succeed if you cannot make
the first frame start with a known or at least valid-looking method,
as they will generate and error and close the connection. But that
does not say much.

>can be fixed by introducing more random keys during the communication.

If the server understands that after the header of a CONNECT request
all incoming data is non-HTTP data, then there is no problem. But so
far nobody submitted data on how well that can be relied upon. In the
HTTPBis group http://trac.tools.ietf.org/wg/httpbis/trac/ticket/251
we have bugs that suggest this is a problematic area...
-- 
Björn Höhrmann · mailto:[email-masked] · http://bjoern.hoehrmann.de
Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de
25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/

---

From: Bjoern Hoehrmann <[email-masked]>
Date: Thu, 11 Nov 2010 03:38:29 +0100

>generate any chosen ciphertext by controlling the plaintext.

As I understood the proposal, the input to the encryption algorithm is
<connection-key1>, <connection-key2>, and the number of bytes that have
been sent; the attacker in this scenario knows the number of bytes, it
knows connection-key2 because it can freely choose it, and the browser
sends connection-key1 in the handshake, so there would be no input to
the algorithm that the attacker is unaware of, meaning it cannot produce
anything the attacker could not predict.

It was rather my understanding that Adam's proposal mitigates this by
obscuring the HTTP routing information (the attacker is unlikely to
control what the server defaults to) and by using CONNECT (anything
after the CONNECT request header is supposedly to be considered not
HTTP by a server that supports CONNECT correctly, though it's unclear
how well-founded that assumption is).
-- 
Björn Höhrmann · mailto:[email-masked] · http://bjoern.hoehrmann.de
Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de
25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/

---

From: John Tamplin <[email-masked]>
Date: Wed, 10 Nov 2010 22:09:10 -0500

> anything the attacker could not predict.

Even if I know the key, it is computationally infeasible to produce
plaintext which encrypts to a given ciphertext for an unforgeable
encryption algorithm.  I don't know off the top of my head if AES-CTR
is unforgeable and didn't find any obvious results, but I would be
surprised if it were forgeable considering the effort that went into
selecting it.

---

From: Bjoern Hoehrmann <[email-masked]>
Date: Thu, 11 Nov 2010 04:20:18 +0100

>surprised if it were forgeable considering the effort that went into

Going by <http://tools.ietf.org/html/draft-moskowitz-aes128-ctr-00>,

  To encrypt a message M, the sender partitions M into B-bit blocks
  M = M[1] M[2] ... M[n-1] M[n]. Each block of M is then XOR'd with
  an encrypted counter value: ...

(the draft uses non-ASCII octets, I hope I replaced them correctly)
so this does not seem any better than XORing without involving AES.
-- 
Björn Höhrmann · mailto:[email-masked] · http://bjoern.hoehrmann.de
Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de
25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/

---

From: Bjoern Hoehrmann <[email-masked]>
Date: Thu, 11 Nov 2010 04:24:54 +0100

Ah, never mind, I wasn't thinking.
-- 
Björn Höhrmann · mailto:[email-masked] · http://bjoern.hoehrmann.de
Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de
25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/

---

From: John Tamplin <[email-masked]>
Date: Wed, 10 Nov 2010 22:26:28 -0500

> so this does not seem any better than XORing without involving AES.

Unless you can produce a particular encrypted counter value, the
presence of XOR doesn't change the unforgeability.

---

From: Greg Wilkins <[email-masked]>
Date: Thu, 11 Nov 2010 15:13:26 +1100

Zhong,

I think there are a number of factors that significantly reduce the
plausibility of the attack you describe.

Firstly, intermediaries typically are going to be one of two types
when it comes to forwarding requests.  Either they will form a route
based on the first HTTP request seen and forward all subsequent bytes
to the same destination, or they will parse individual HTTP messages
on the connection and route each one individually.

In the former case, a WS stream will be routed to server A and stay
routed to A.   In the second case, the stream of WS messages would not
be able to be parsed into a stream of HTTP messages, thus an
intermediary would not be able to deframe the stream and forward any
"HTTP" requests at all.   Note that some of the proposed changes to
the framing mechanism (FIN bit ) are intended to make it even less
likely that a WS frame could be mistaken for a HTTP request.

For the attack to succeed, the B server would also have to be
susceptible to interpreting a stream of WS frames as HTTP requests,
with sufficient credentials to trigger sensitive behaviour.   It is
also highly unlikely that any HTTP responses received from the B
server would be acceptable to the WS client and passed onto the
attacker.

regards

On 11 November 2010 12:30, Zhong Yu <[email-masked]> wrote:
> _______________________________________________
> hybi mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/hybi
>

---

From: Willy Tarreau <[email-masked]>
Date: Thu, 11 Nov 2010 07:29:37 +0100

On Thu, Nov 11, 2010 at 03:13:26PM +1100, Greg Wilkins wrote:
> "HTTP" requests at all.

In fact, the second case could happen for those who don't consider the
101+upgrade response or the CONNECT+200. However, the following data
which would make the new "request" would not look like HTTP at all and
could not be magically routed, in part thanks to the point below :

> Note that some of the proposed changes to

I'd like to add that it is important to understand that designing a
keep-alive compatible intermediary involves much stricter protocol
compliance than just being able to process a first HTTP request in
a connection, because you are forced to find some required headers
(connection, content-length, transfer-encoding, ...) and have to always
remain synced with the data that flow. The slightest approximation in
the protocol can very quickly get you out of sync and make the component
totally unreliable, hanging on downloads, randomly spewing errors, etc...
So while it is possible to design a working intermediary which does not
support Upgrade nor CONNECT, it's almost not possible to design one that
magically works through invalid framing.

For this reason, I consider that it's the initial framing that protects
us from the risks exposed by Zhong and Adam, and not the handshake. The
handshake must be reliable, the initial framing must make it secure.

Regards,
Willy

---

From: Scott Ferguson <[email-masked]>
Date: Thu, 11 Nov 2010 09:34:48 -0800

> magically works through invalid framing.

+1

Repeating Willy's point for emphasis: a keepalive server/proxy must 
parse all framing content for keepalives to work, including http 
headers, chunking, etc. for both the request and the response. It cannot 
skip invalid bytes and continue the keepalive. Because there's no way to 
recover from the framing errors, the server/proxy needs to close the 
connection after the error if the HTTP framing breaks.

For example, in the case of NPH scripts, a server has to close the 
connection when the NPH script completes because the server doesn't know 
if the script responded with Connection: close or if the script even had 
a valid Content-Length/chunking.

-- Scott

---

From: Zhong Yu <[email-masked]>
Date: Thu, 11 Nov 2010 13:15:53 -0600

> presence of XOR doesn't change the unforgeability.

I assume we need a new counter for each WS frame?

Isn't it equivalent, but simpler, if each WS frame payload is XOR-ed
by a random 128 bits mask. The mask is different from frame to frame,
generated by the sender, and carried within the frame.

- Zhong Yu

---

From: Zhong Yu <[email-masked]>
Date: Thu, 11 Nov 2010 13:29:01 -0600

> with sufficient credentials to trigger sensitive behaviour.   It is

Yes, I was being very generous in assuming flaws in implementation to
make the attack possible. As you guys pointed out, the actual
plausibility is far lower.

> server would be acceptable to the WS client and passed onto the

That is probably a death blow to my attack example. Even with minimal
frame design, it is highly unlikely that a WS client can convert HTTP
responses into WS frames. We do have much more confidence in WS
clients than WS servers, somehow.

Therefore attacker cannot read server B's responses. The WS connection
will be prompted closed. The attacker might be able to deliver some
HTTP requests to server B, if we grant the now-less-plausible flaws
that can make the trick. The attacker can not deliver B's cookies
either.

Then this attack becomes very lame. The attacker is much better off to
use HTTP client to attack B.

- Zhong Yu

---

From: John Tamplin <[email-masked]>
Date: Thu, 11 Nov 2010 14:30:20 -0500

> I assume we need a new counter for each WS frame?

No, as it the encrypted counter is not predictable.

> generated by the sender, and carried within the frame.

The point was that if the attacker knows the key, they can trivially
produce arbitrary data after masking.  If the UI chose a new random
key for every frame, I think that would solve it but now we just added
16 bytes per frame after we worked so hard to keep the frame sizes
down.

If we are going to go that route, I would prefer to just say WebSocket
doesn't support unencrypted connections (given the growing attacks
against unencrypted connections maybe it isnt a bad idea anyway).

---

From: Zhong Yu <[email-masked]>
Date: Thu, 11 Nov 2010 14:16:51 -0600

> No, as it the encrypted counter is not predictable.

I don't understand. In the attack scenario I described(now considered
less plausible), the attacker have full knowledge of the handshake and
some beginning WS frames. If the counter sequence used for these
beginning WS frames will continue to be used for some following WS
frames, attacker can craft some cybertext in the following bytes.

- Zhong Yu

---

From: Brodie Thiesfield <[email-masked]>
Date: Fri, 12 Nov 2010 08:33:22 +0900

On Fri, Nov 12, 2010 at 2:34 AM, Scott Ferguson <[email-masked]> wrote:
> Repeating Willy's point for emphasis: a keepalive server/proxy must parse
> all framing content for keepalives to work, including http headers,
> chunking, etc. for both the request and the response. It cannot skip invalid
> bytes and continue the keepalive. Because there's no way to recover from the
> framing errors, the server/proxy needs to close the connection after the
> error if the HTTP framing breaks.

Since there is concern that a server may continuing to process the
HTTP stream after an Upgrade, then just include data in the stream
specifically for that possibility. Immediately following the CRLF of
the upgrade request, require the client to send a fixed string that is
basically valid HTTP and requests connection close.

WEBSOCKET / HTTP/1.1
...
CRLF

"WS !" CR LF "Connection: close" CR LF CR LF

The string is a HTTP 0.9 request with nothing but a connection close
header. Any server actually processing it is encouraged to disconnect
via the unknown method, unknown URL, use of HTTP 0.9 and the
Connection close header. It costs 27 bytes.

In return for this overhead, any server which continues to process the
stream *should* consider this the last message on it. It relies on
these servers to support closing the connection after bad requests and
when requested, but surely at some stage we need to trust the server
to do something right.

Intermediatries supporting Upgrade should not be acting on the stream
after the 101 response. Servers implementing WebSockets know to ignore
it. I don't believe that it is a panacea, but it would add
encouragement to non-supporting servers to disconnect.

Regards,
Brodie