Reputation:
I would like to inspect network traffic going through web sockets, I have no control over the networking code as this is a binary lib for which I do not have the source code, so I cannot do any log/breakpoint in the networking part of the code.
I have tried using the latest version of CharlesProxy which claim to be able to sniff websockets however when I tried the url and apis using websockets were not even mentionned in the list of endpoints called from my iPhone.
I have verified that CharlesProxy is configured correctly as I am able to inspect non-websocket traffic even under SSL.
So my question is: did anyone find a solution to inspect traffic going through websockets with CharlesProxy?
Note: I have ATS disabled when using iOS9
Thanks!
Upvotes: 43
Views: 54573
Reputation: 3614
All above answer doesn't work with new APIs for WebSocket: URLSessionWebSocketTask
New Solution for iOS 17/18 with URLSessionWebSocketTask.
Make sure you setup Charles Proxy's certificate correctly on your Simulator or devices. It's quite tricky, you can find a lot tutorials on the Internet
Go to Proxy Menu -> Proxy Settings -> Enable SOCKS Proxy at port 8889
On your iOS app, use this code when initializing the URLSession. Make sure to replace your current IP with SOCKS Port
private static var urlSession: URLSession = {
let config = URLSessionConfiguration.default
if #available(iOS 17.0, *) {
let socksv5Proxy = NWEndpoint.hostPort(host: "192.168.1.9", port: 8889) // Replace with your Proxyman SOCKS Proxy Server IP address
let proxyConfig = ProxyConfiguration.init(socksv5Proxy: socksv5Proxy)
config.proxyConfigurations = [proxyConfig]
}
return URLSession(configuration: config, delegate: nil, delegateQueue: nil)}()
At this point, Charles Proxy can capture and decrypt WSS.
On Proxyman, Go to Tools menu -> Proxy Settings -> SOCKS Proxy Setting -> Enable it and save
Use the same code, and replace your current IP and port (You can find the current IP on the top of the Proxyman Toolbar)
private static var urlSession: URLSession = {
let config = URLSessionConfiguration.default
if #available(iOS 17.0, *) {
let socksv5Proxy = NWEndpoint.hostPort(host: "192.168.1.9", port: 8889) // Replace with your Proxyman SOCKS Proxy Server IP address
let proxyConfig = ProxyConfiguration.init(socksv5Proxy: socksv5Proxy)
config.proxyConfigurations = [proxyConfig]
}
return URLSession(configuration: config, delegate: nil, delegateQueue: nil)}()
Done, Proxyman will capture and decrypt websocket
P/S: I'm a creator of Proxyman. I built this app to make it's easier to debug traffic on iOS.
Upvotes: 0
Reputation: 34471
CharlesProxy with iOS WebSocket connection
var rd: InputStream
var wr: OutputStream
let dict: NSDictionary = [
StreamSOCKSProxyConfiguration.hostKey.rawValue : <ip>,
StreamSOCKSProxyConfiguration.portKey.rawValue : <port>
]
rd.setProperty(dict, forKey: Stream.PropertyKey.socksProxyConfigurationKey)
wr.setProperty(dict, forKey: Stream.PropertyKey.socksProxyConfigurationKey)
or
let proxyDict = CFNetworkCopySystemProxySettings()
let prop = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
rd.setProperty(prop, forKey: Stream.PropertyKey(rawValue: kCFStreamPropertySOCKSProxy as String as String))
wr.setProperty(prop, forKey: Stream.PropertyKey(rawValue: kCFStreamPropertySOCKSProxy as String as String))
CharlesProxy setup
Proxy -> Proxy Settings... -> Proxies -> Enable SOCKS Proxy
also you can check/review socket connection using websocket-tester
Please note that it should be used only in debug mode for security reasons and there is can be additional logic(e.g. checking signature...) which prevents intercept a traffic
Upvotes: 0
Reputation: 5479
UPDATE JUNE 2022: Whilst socket.io-client-swift has an API to enableSOCKSProxy
, it seems Starscream v4 has actually removed the built-in support for SOCKS proxying, so this option doesn't actually do anything!
So it looks like we're back to manually patching Starscream to enable SOCKS proxying.
Just to recap: we've gone from Starscream supporting SOCKS proxy but socket.io-client-swift not having an API to enable it, to now socket.io-client-swift having an API to enable SOCKS proxying, which Starscream no longer supports 🤣🙄.
UPDATE JUNE 2019: Apparently socket.io-client-swift v15.1.0 now properly supports SOCKS proxy. I have not yet tried it, but it would mean that these manual edits to Starscream are no longer required.
The accepted answer does not seem to work with Socket.IO on iOS devices.
The latest version of Socket.IO-Client-Swift (15.0.0 at the time of writing) uses Starscream for WebSockets on iOS/OS X.
The good news is that Starscream supports SOCKS proxying however:
Socket.IO does not expose the Starscream websocket or provide any API for enabling the SOCKS proxying behaviour.
The SOCKS proxying built into Starscream uses the OS SOCKS proxy settings which are cumbersome to setup (at least for iOS).
If I get some time I might propose a PR to address this more thoroughly, but given that it requires work to both Starscream and Socket.IO-Client-Swift, this is not entirely straightforward.
The easiest way to hack around this for temporary debugging purposes (which is the use case for Charles!), is to edit the WebSocket.swift
file as part of Starscream, and replace this code:
if enableSOCKSProxy {
let proxyDict = CFNetworkCopySystemProxySettings()
let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
CFWriteStreamSetProperty(outputStream, propertyKey, socksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, socksConfig)
}
with this code:
let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, CFNetworkCopySystemProxySettings()!.takeRetainedValue()) as! [String: Any]
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
let ip = socksConfig["HTTPSProxy"]
let proxySocksConfig = ["SOCKSProxy": ip, "SOCKSPort": 8889, "SOCKSEnable": true] as CFDictionary // Where 8889 is the SOCKS proxy port in Charles
CFWriteStreamSetProperty(outputStream, propertyKey, proxySocksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, proxySocksConfig)
This will ensure the SOCKS proxy is enabled by default, and will route all the websockets traffic via Charles.
You then need to ensure that the HTTP proxy settings are configured in iOS (since the same IP will be used for both HTTP and SOCKS), SOCKS proxy is enabled in Charles, and that the port matches the port in the code above (by default 8889).
Upvotes: 13
Reputation: 79
Found a workaround solution:
After trying, I conclude that although iOS simulators follow system proxy settings for HTTP, WebSocket is not followed. Socks5 proxy settings is not followed, either.
However, we can use Reverse Proxies
in Charles to force the simulator to use it.
In Charles, click Proxy -> Reverse Proxies
, set up a local address to be your WebSocket server's transparent proxy, then use that address in your new WebSocket(<address>)
(Well in the case of React Native), then you can see your WebSocket connections appear in Charles
Upvotes: 1
Reputation: 4523
Thanks for your very very helpful answer Jonathan Ellis! I'm using Pusher and this worked great!
However, I found socksConfig
to not always contain valid data and didn't work or would crash the app when I pulled the IP from there. Since the only thing we are getting from there is the localhost IP I just replaced the following in WebSocket.swift
if enableSOCKSProxy {
let proxyDict = CFNetworkCopySystemProxySettings()
let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
CFWriteStreamSetProperty(outputStream, propertyKey, socksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, socksConfig)
}
with this:
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
let proxySocksConfig = ["SOCKSProxy": "127.0.0.1", "SOCKSPort": 8889, "SOCKSEnable": true] as CFDictionary // Where 8889 is the SOCKS proxy port in Charles
CFWriteStreamSetProperty(outputStream, propertyKey, proxySocksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, proxySocksConfig)
And then enabled the socks proxy in Charles as you described.
Thanks Again!
Upvotes: 3
Reputation: 25011
I finally found the answer.
Charles 3.11.2 works perfectly with WebSocket.
I use socketIO, so I've already seen http requests sent during the negotiation phase, but I missed websockets traffic.
In the beginning, socketIO try to use polling then switches to use websockets.
The websocket traffic is visible when you go to the request with status: "Sending request body" which is actually wss:// request.
You even have a dedicated tab for this kind of traffic. The rest of messages will appear right there.
PS1. Ensure you're connected to socket properly then it appears in Charles.
PS2. I suggest using socketIO it's a great enhancement for full-duplex traffic like websockets.
Upvotes: 26