ProxymanApp / Proxyman

Modern. Native. Delightful Web Debugging Proxy for macOS, iOS, and Android ⚡️
https://proxyman.io
5.53k stars 183 forks source link

Proxyman does not use SNI when generating certificates for use with SSL Proxying #1581

Open meermanr opened 1 year ago

meermanr commented 1 year ago

Description

Proxyman does not appear to use to Server Name Indication (SNI) to determine the Common Name (or Subject Alternate Names (SANs)) of the certificate it generates when performing SSL Proxying.

I'm attempting to debug a legacy application which does not use system roots of trust, but instead uses a baked-in self-signed certificate for which I have the CA and private key. The application is unusual in that always sends a fixed SNI, irrespective of the server's address.

Steps to Reproduce

I've replicated this using standard tools:

  1. Launch Proxyman.app and note its address: 127.0.0.1:9090 in my case
  2. Create ~/.proxychains.conf and populate it with:
[ProxyList]
http  127.0.0.1   9090
  1. Run proxychains4 -f ~/.proxychains.conf socat -d -d FILE:/dev/null OPENSSL:192.168.111.30:443,snihost=example.com
  2. Locate the request in Proxyman.app, and enable SSL Proxying
  3. Repeat command
  4. Proxyman.app records a request with status "Internal Error"
  5. Preview the request's certificate (click request line, click summary in content area, scroll down, click "Certificate: Preview")
  6. Note that the common name of the certificate is the IP address 192.168.111.30, and not the SNI example.com

For completeness, you can verify that socat is indeed emitting an SNI request:

  1. Run socat -d TCP-LISTEN:11223,fork,reuseaddr SYSTEM:'xxd >&2', which will listen for traffic on TCP port 11223 and write it to a subprocess, xxd >&2, effectively dumping the request on STDERR.
  2. Run socat -d -d FILE:/dev/null OPENSSL:127.0.0.1:11223,snihost=example.com to make a request
  3. Observe that the SNI hostname, example.com, appears in the output from xxd
  4. The socat processes are now waiting for something. Kill the TCP-LISTEN version of socat to get your shells back.

Current Behavior

Auto-generated certificate's common name is set to network address (i.e. 192.168.111.30) specified by CONNECT

Expected Behavior

Auto-generated certificate's common name should be set to the value given by the request's Server Name Indication (SNI), i.e. example.com.

Environment

NghiaTranUIT commented 1 year ago

Hey @meermanr thanks for the detailed reproducible test.

It's hard to say, but I would explain how Proxyman constructs the self-signed certificate.

  1. During the SSL Handshake from the client to Proxyman, Proxyman would use NIOSSLClientHandler to make a connection to the destination server.
  2. This class would return the real certificate from the server, including the commonName and alternative_names

For example: Connect to https://www.producthunt.com/

<NIOSSLCertificate;serial_number=5f94d6b623d326554e87bb18d395ee2;common_name=sni.cloudflaressl.com;alternative_names=*.producthunt.com,sni.cloudflaressl.com,producthunt.com>
  1. Proxyman constructs the new server certificate with real certificate information (commonName and alternative_names) and signs by Proxyman self-signed Root Certificate.
  2. Then, Proxyman uses this certificate to perform the SSL Handshake with the client.

If Proxyman could not connect to the destination server (in the 1st step), Proxyman would generates a fake certificate, with the SNI is the host name, or the IP address.

From what I see, you're using the IP, so NIOSSLClientHandler doesn't accept it -> Proxyman generates a fake certificate with your IP instead of the real SNI.

Code: https://github.com/apple/swift-nio-ssl/blob/845a97cbd5516ea9a5ed0f2788c4e38b3ee270dd/Sources/NIOSSL/NIOSSLClientHandler.swift#L39


I suppose that we can fix it using a real new hostname.

proxychains4 -f ~/.proxychains.conf socat -d -d FILE:/dev/null OPENSSL:my_server.com:443