Proxyman icon indicating copy to clipboard operation
Proxyman copied to clipboard

Proxyman interferes with WebSocket upgrade request & causes race condition

Open liambutler-lawrence opened this issue 10 months ago • 40 comments

Description

When using Proxyman to trace a WS connection with SSL Proxying enabled, Proxyman sends an HTTP 101 response back to the client immediately (within 5ms), before the server has actually returned its HTTP response. This causes a race condition in clients that need to wait for the upgrade (101) response before doing additional logic.

Steps to Reproduce

  1. Start Proxyman and enable SSL Proxying for a WS API
  2. Using any local client, open a WS connection at that URL

Current Behavior

Proxyman receives the HTTP upgrade request from the client and forwards it to the server, but does not wait for the server response before responding to the client. Instead, it immediately sends a fake success (HTTP 101) response to the client:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Accept: pIF19n7SVsW0u4/t7tCDGOzUNnY=
Connection: upgrade

Since the Sec-WebSocket-Accept header is correctly computed, this causes the client to erroneously believe the upgrade process has already completed. If the client waits for the upgrade process to complete before doing additional logic, this causes a race condition and all sorts of bad behavior.

Expected Behavior

Proxyman should wait for the HTTP response (whether 101 or not) and forward that response to the client, just like the behavior for other HTTP requests.

Environment

  • App version: 5.16.0
  • macOS version: 15.3.2 (24D81)

liambutler-lawrence avatar Mar 19 '25 18:03 liambutler-lawrence

@liambutler-lawrence we're aware of it. Sorry I tried to fix it because it's how we currently design the Websocket Mitm proxy. It will return 101 status code first, then make a real connection to a WS socket server. Then it exchanges the data.

If the client waits for the upgrade process to complete before doing additional logic, this causes a race condition and all sorts of bad behavior.

Not sure why it causes the race condition? Does your WS Server return any new Headers that your client needs to read?

From what I know 99% WS client just needs the Sec-WebSocket-Accept header to start exchange data

NghiaTranUIT avatar Mar 21 '25 07:03 NghiaTranUIT

Our WS server is stateful- during the connection process it saves the WS connection ID to a DB. On the client side, as soon as it receives the 101 response, it assumes the DB has been updated and begins making other REST calls to our backend, which fail if the DB has actually not been updated yet. Hence the race condition.

More generally though, this behavior simply violates the basic expectation of a proxy—that it should inspect and log inbound/outbound traffic, but not interfere with it. I don't understand why your WS implementation is special: until the upgrade handshake is completed, it's just a normal HTTP flow.

liambutler-lawrence avatar Mar 21 '25 15:03 liambutler-lawrence

thanks, we understand. The reason is we're using SwiftNIO with a built-in websocket upgrade for server/client.

We will refactor it by manually handling the upgrader. It will fix your problem 👍

NghiaTranUIT avatar Mar 22 '25 01:03 NghiaTranUIT

Makes sense, thanks!

liambutler-lawrence avatar Mar 23 '25 01:03 liambutler-lawrence

Hi, can you provide an ETA on this?

liambutler-lawrence avatar Apr 22 '25 03:04 liambutler-lawrence

@liambutler-lawrence not yet because it takes a full re-implementation of how Proxyman works with Websocket.

NghiaTranUIT avatar Apr 24 '25 01:04 NghiaTranUIT

To workaround, you can use Proxyman + Atlantis framework (develop by Proxyman team): https://github.com/ProxymanApp/atlantis

It can capture all WSS from URLSessionWebsocket, and send to Proxyman to preview. It's not a Proxy, so your WSS logic isn't interfered

NghiaTranUIT avatar Apr 24 '25 01:04 NghiaTranUIT

We use NIO not URLSession to make our WS calls, so Atlantis wouldn't help.

Understood that it's a large change, but I will note that this issue makes Proxyman largely useless to my team and we have been forced to explore other options, even though we like Proxyman a lot. This issue also has made us lose a lot of confidence in the integrity of the proxy, since as I mentioned before, the first job of a proxy is to not interfere.

liambutler-lawrence avatar Apr 24 '25 01:04 liambutler-lawrence

I will create a ticket and put it on the high-priority list 👍

We want to rewrite how Proxyman handles the WS, so that it enables us to use Map Local or Breakpoint to modify the headers. Currently, it's not possible because the websocket upgrader is handled deeply by SwiftNIO lib

NghiaTranUIT avatar Apr 24 '25 02:04 NghiaTranUIT

@liambutler-lawrence if you don't mind, please try this BETA build: https://download.proxyman.io/beta/Proxyman_5.19.0_New_websocket_logic.dmg

Changelogs

  • Completely rewrite how Proxyman handles the websocket. Basically, it reads your Request (with upgrade header) and forwards it to the server. Then, returning your server's Response with 101 status code => Fix the race condition.

Screenshots

Capture websocket traffic with Proxyman

NghiaTranUIT avatar Apr 29 '25 13:04 NghiaTranUIT

@liambutler-lawrence new update: https://download.proxyman.io/beta/Proxyman_5.19.0_Rewrite_websocket_v3.dmg

  • Fix some internal issues with websocket
  • A lot of WS/WSS Test
  • Works with Map Remote + Websocket
  • Fix websocket extension

NghiaTranUIT avatar May 01 '25 05:05 NghiaTranUIT

Great! We will check it out, thanks.

liambutler-lawrence avatar May 01 '25 17:05 liambutler-lawrence

We tried the build you posted (5.19.0). It does fix the race condition (🙏) but seems to have some serious regressions, specifically only inbound string/data frames show up in the Websocket tab. Outbound, ping & pong frames do not show up at all.

liambutler-lawrence avatar May 13 '25 01:05 liambutler-lawrence

@liambutler-lawrence are you sure? We tested, and ping/pong shows up on the screenshot too

Capture websocket traffic with Proxyman

NghiaTranUIT avatar May 13 '25 02:05 NghiaTranUIT

Maybe you accidentally hide the ping/pong message.

Proxyman captures WS

NghiaTranUIT avatar May 13 '25 02:05 NghiaTranUIT

I checked this "beta" version, and it works totally wrong with socket.io Image

ganeles avatar May 14 '25 12:05 ganeles

Screenshot of the same traffic from the current "master" version:

Image

ganeles avatar May 14 '25 12:05 ganeles

Thanks, this Beta build hasn't been tested with socket.io yet, let me fix it 👍

NghiaTranUIT avatar May 15 '25 01:05 NghiaTranUIT

@ganeles can you share with me your SocketIO Response's Header? I guess it's encoded by some compression algo

I tested with socketio 4, and Proxyman can capture well

https://github.com/user-attachments/assets/7e76bbac-302a-4480-b699-d33d219231c4

NghiaTranUIT avatar May 15 '25 02:05 NghiaTranUIT

sent it to you by email [email protected]

ganeles avatar May 15 '25 06:05 ganeles

I see, socketIO encodes the message again with this header: Sec-WebSocket-Extensions: permessage-deflate even though I remove this header from the Request.

It works with normal websocket, but not socketio

NghiaTranUIT avatar May 15 '25 08:05 NghiaTranUIT

@liambutler-lawrence new update: https://download.proxyman.io/beta/Proxyman_5.19.0_Rewrite_websocket_v3.dmg

  • Fix some internal issues with websocket
  • A lot of WS/WSS Test
  • Works with Map Remote + Websocket
  • Fix websocket extension

@ganeles can you confirm that you;re using this Beta build ? THe first beta build has a problem with websocket extension -> Causes your issue

NghiaTranUIT avatar May 15 '25 13:05 NghiaTranUIT

Checked it. Hmm. I don't see corrupted messages - this is good side of this update.

But the reason is I can't send the socket.io messages via Proxyman ^)) So, please, do not implement this branch into the master

ganeles avatar May 16 '25 10:05 ganeles

Do you mean that the Client can't send a socket.io message to your Server when it's intercepting by Proxyman?

From your previous screenshot, it shows that your client -> server can communicate via websocket.

NghiaTranUIT avatar May 16 '25 10:05 NghiaTranUIT

Yeah, I can't send WS messages with the latest beta https://download.proxyman.io/beta/Proxyman_5.19.0_Rewrite_websocket_v3.dmg.

With the previous beta, I could send messages (but ProxyMan shows it as in my screenshot)

And with the current "Master" version, I can send WS messages, but sometimes responses are trimmed (I see only the first letters, but they are correct - I mean no garbage symbols are there)

ganeles avatar May 16 '25 10:05 ganeles

Would you like an example of traffic? PCAP or HAR?

By the way - if I import the HAR into ProxyMan, it shows the data correctly, so, maybe PCAP will be better to detect the cause of the problem.

ganeles avatar May 16 '25 10:05 ganeles

Saw this got shipped in the last release–appreciate it. However, there still seems to be a pretty bad regression in 5.21.0–specifically, sent messages show up as "received", both visually (the green down arrow) and in the filters.

liambutler-lawrence avatar Jun 11 '25 21:06 liambutler-lawrence

@liambutler-lawrence can you try this Beta build? https://download.proxyman.io/beta/Proxyman_5.21.0_Try_fixing_websocket_direction.dmg

I improved the logic to detect whether it's from a Client or Server.

I tested with many scenarios, but I hanve't reproduced your issue. Tried to compare the order of the websocket event, with Google Dev Tools and Proxyman. It's the correct order (Sent / Received)

NghiaTranUIT avatar Jun 12 '25 01:06 NghiaTranUIT

Unlucky for me, I have the same problem that I wrote about before with this build (and last release build as well): For my "socket.io" connections, I see only "close" messages for the WebSocket flow

PS Build "Proxyman_5.14.0_Filter_empty_comment.dmg" works fine!

ganeles avatar Jun 12 '25 13:06 ganeles

@ganeles if you don't mind, can you share with me your socket.io sample code?

I need the name of your socket.io library, so I can write a sample code and test it.

NghiaTranUIT avatar Jun 12 '25 13:06 NghiaTranUIT