Skip to content

Inspecting Network Traffic on macOS

Tags: macos, software • Categories: Web Development

Table of Contents

I ran into a scenario where I wanted to sniff out exactly what was being communicated by a macOS app. It’s been a long time since I’ve done any http sniffing so it great to try out some new tools.

Packet Sniffing

Packet sniffing is the easiest way to see what is happening on your network device. No sudo, no proxies. However, you can only see the domain being contacted for SSL requests.

brew install sniffglue

sniffglue en0

Great for high level activity, not great for understanding the details of what an application is doing on your computer.

Using a Proxy to Inspect HTTPS Traffic

In order to view HTTP traffic, you need to route all network traffic on your device through a proxy. mitmproxy is exactly what you need. I’ve never used wireshark, but I bet it’s pretty similar and seems pretty easy to set up.

SSL can still cause you problems. Mitmproxy employs an interesting mechanism that generates SSL certificates on the fly based on the domain being requests. However, a server can implement certificate pinning, which effectively ensures that the root certificate matches a specific hash. This has been officially deprecated, but websites, especially apps, can use this to prevent exactly what we are doing—a man-in-the-middle attack.

After running brew install mitmproxy you can use this handy shell script to (a) start mitmproxy and (b) set your macOS network configuration to use the proxy. This script also checks to ensure you have the mitmproxy CA installed locally.

start_network_proxy() {
    SERVICE="Ethernet"
    PROXY_ADDRESS="localhost"
    # I often use 8084 for other services, otherwise the default could be used
    PROXY_PORT="8084"

    # Function to disable the proxy
    disable_proxy() {
        echo "Disabling proxies for $SERVICE..."
        sudo networksetup -setsocksfirewallproxystate "$SERVICE" off
        echo "Proxies disabled for $SERVICE"
    }

    echo "Starting mitmproxy on port $PROXY_PORT..."

    # Set SOCKS5 proxy
    sudo networksetup -setsocksfirewallproxy "$SERVICE" "$PROXY_ADDRESS" "$PROXY_PORT"
    sudo networksetup -setsocksfirewallproxystate "$SERVICE" on

    echo "SOCKS5 proxy set for $SERVICE"

    # Start mitmproxy in SOCKS5 mode with TLS version adjustment
    mitmproxy --mode socks5 --listen-port $PROXY_PORT --set tls_version_client_min=SSL3

    # Clean up proxy settings after mitmproxy exits
    disable_proxy
}

I’ve also added this to my dotfiles, so I can quickly setup a proxy in the future.

Re-requesting Stale Content

In my case, the request I wanted to inspect wasn’t returning anything! It returned a 304 status code and 0 bytes of content.

Strange.

mitmproxy makes it easy to replay a command. It will even generate a CLI command to rerun the request in my favorite curl replacement, httpie. Here’s what it returned:

http GET 'https://www.example.com/api/users?search=' 'Connection: keep-alive' 'If-None-Match: W/"2af6267f4de8a639e1564c740524cddc"' 'Accept: application/json' 'User-Agent: App/85 CFNetwork/1496.0.7 Darwin/23.5.0' 'Accept-Language: en-US,en;q=0.9' 'Authorization: Bearer <REDACTED_TOKEN>' 'Accept-Encoding: gzip, deflate, br'

I’ve never seen the If-None-Match header before. It allows the server to return no content if the hash of the content matches the header value. I had to remove this header in order to see the request content.

Keep in Touch

Subscribe to my email list to keep in touch. I’ll send you new blog posts and other thoughts.