Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proxy: provide support for WebSockets with MITM #31

Open
admtnnr opened this issue Aug 24, 2015 · 12 comments
Open

proxy: provide support for WebSockets with MITM #31

admtnnr opened this issue Aug 24, 2015 · 12 comments
Labels

Comments

@admtnnr
Copy link
Contributor

admtnnr commented Aug 24, 2015

Some prelimary investigation reveals several problems with supporting WebSockets, both secure and insecure. When using a proxy, browsers will send a CONNECT request to the destination when attempting to open a WebSocket, regardless of whether it is secure (ws:// or wss://).

Martian assumes CONNECT requests are requests to upgrade to TLS for HTTPS. In the non MITM case, Martian will blindly make a TCP connection by default for CONNECT requests which means that both secure and insecure WebSocket requests work.

Everything starts to break when MITM is enabled. Here's how:

To start, we'll look at an example request that is made to the proxy when attempting to open a WebSocket to echo.websocket.org.

CONNECT echo.websocket.org:80 HTTP/1.1
Host: echo.websocket.org:80
Proxy-Connection: keep-alive
Content-Length: 0
User-Agent: ...

Insecure WebSockets with MITM

Martian will wrap the connection with a TLS connection after sending a 200 OK response to the client. In the case of an insecure WebSocket request, the connection will then have a normal HTTP request sent to it for the WebSocket handshake. This fails with a TLS handshake error: tls: first record does not look like a TLS handshake.

How to Fix

This is the tricky case. We actually need to inspect the first couple bytes of traffic from the connection to be able to make a guess whether the connection is TLS or something else. The current idea is to inspect the bytes for:

  • A TLS version as part of the handshake and then wrap the connection in TLS.
  • The start of an HTTP request so we can also support that case and run any modifiers.
  • Anything unknown will be sent as a normal CONNECT tunnel and not modified.

Secure WebSockets with MITM

GET https://echo.websocket.org/?encoding=text HTTP/1.1
Host: echo.websocket.org
Upgrade: websocket
Connection: Upgrade
Content-Length: 0
Origin: http://www.websocket.org
Sec-Websocket-Extensions: permessage-deflate
Sec-Websocket-Key: 5/02dRzDuskoI7ZWhnvL1w==
Sec-Websocket-Version: 13

The TLS handshake succeeds in the secure WebSockets case, but fails with the following response to the prior request. This is because Martian will remove the Upgrade header (because it is listed in the Connection header) and thus prevent the downstream server from recognizing it as a WebSocket request.

HTTP/1.1 400 WebSocket Upgrade Failure
Content-Length: 77
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Headers: x-websocket-extensions
Access-Control-Allow-Headers: x-websocket-version
Access-Control-Allow-Headers: x-websocket-protocol
Access-Control-Allow-Origin: http://www.websocket.org
Content-Type: text/html
Date: Tue, 24 Nov 2015 21:59:21 GMT

How to Fix

I expect this case will be slightly easier to handle with a custom modifier that checks for the Upgrade header, and if it finds websocket specified, will issue context.Hijack() and stitch the connections together.

context.Hijack()

The idea is that session.Context provides a Hijack() method similar to ResponseWriter.Hijack() that will return net.Conn, bufio.ReadWriter, error.

This will allow a modifier aware of WebSockets to take control of the connection while allowing modifiers before it to run.

A sub modification system (such as modifiers specifically designed to manipulate Websocket messages) could be created to further enhance the system.

To deal with the default case that is currently broken, we can create a modifier that will by default take any request with WebSocket headers and hijack the connection and stitch the two connections together reusing behavior similar to the CONNECT tunnel, send a 101 Switching Protocols response, and then waiting until the connection is closed.

@admtnnr admtnnr changed the title proxy: provide API to handle "Upgrade" requests [WIP] proxy: provide API to handle "Upgrade" requests Aug 24, 2015
@admtnnr admtnnr added this to the v2.1.0 milestone Aug 24, 2015
@admtnnr admtnnr changed the title [WIP] proxy: provide API to handle "Upgrade" requests [WIP] proxy: provide support for WebSockets Nov 24, 2015
@admtnnr admtnnr changed the title [WIP] proxy: provide support for WebSockets proxy: provide support for WebSockets Nov 24, 2015
@admtnnr admtnnr changed the title proxy: provide support for WebSockets proxy: provide support for WebSockets with MITM Nov 25, 2015
@berkant
Copy link

berkant commented Jul 24, 2018

It would be perfect if this were implemented.

@bramhaghosh bramhaghosh removed the bug label Sep 28, 2018
@bramhaghosh bramhaghosh removed this from the v2.1.0 milestone Sep 28, 2018
@bramhaghosh bramhaghosh added the p3 Priority 3 label Sep 28, 2018
@trickpattyFH20
Copy link

@bramhaghosh @admtnnr Is this feature currently being worked on?

@bramhaghosh
Copy link
Member

bramhaghosh commented Dec 11, 2018 via email

@trickpattyFH20
Copy link

I'm following the "how to fix" section described above, and helping to work on a custom modifier with @fcjr. The upgrade header is checked for, and then the connection hijacked, but I'm having trouble with the "stitch the connections together" part

I think it should be something like make anet.dial to the original req.url, but after that I'm unclear how to read and write data between the original connection and the new connection.

Does anyone have any tips on how to achieve this?
@admtnnr @bramhaghosh

@vtolstov
Copy link

gentle ping on this issue, ow to workaround absent websocket wss support with mitm proxy

@nobody5050
Copy link

going to bump this

@bramhaghosh
Copy link
Member

bramhaghosh commented Oct 12, 2020 via email

@LukasPukenis
Copy link

@trickpattyFH20 @fcjr did you work out a solution?

@berkant
Copy link

berkant commented Feb 1, 2021

Hello! Does Martian support MITM'ing WebSockets connections yet?

@bramhaghosh
Copy link
Member

bramhaghosh commented Feb 1, 2021 via email

@abdullah-lt
Copy link

Does anyone have a workaround to support WebSocket with MITM

@mmatczuk
Copy link

If anyone is interested I just submitted a PR to our fork implementing protocol upgrades.
This enables running WS with MITM.

saucelabs#22

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants