[GH-ISSUE #1581] Proxyman does not use SNI when generating certificates for use with SSL Proxying #1574

Open
opened 2026-03-03 19:52:35 +03:00 by kerem · 1 comment
Owner

Originally created by @meermanr on GitHub (Mar 22, 2023).
Original GitHub issue: https://github.com/ProxymanApp/Proxyman/issues/1581

Originally assigned to: @NghiaTranUIT on GitHub.

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:

  • proxychains4 installed via brew install proxychains-ng (version: stable 4.16 (bottled), HEAD)
  • socat installed via brew install socat (version: stable 1.7.4.4 (bottled))
  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

  • App version: Version 4.5.0 (45000)
  • macOS version: Ventura 13.2.1 (22D68) (Intel)
Originally created by @meermanr on GitHub (Mar 22, 2023). Original GitHub issue: https://github.com/ProxymanApp/Proxyman/issues/1581 Originally assigned to: @NghiaTranUIT on GitHub. ## 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: - `proxychains4` installed via `brew install proxychains-ng` (version: stable 4.16 (bottled), HEAD) - `socat` installed via `brew install socat` (version: stable 1.7.4.4 (bottled)) 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 ``` 4. Run `proxychains4 -f ~/.proxychains.conf socat -d -d FILE:/dev/null OPENSSL:192.168.111.30:443,snihost=example.com` 5. Locate the request in Proxyman.app, and enable SSL Proxying 6. Repeat command 7. Proxyman.app records a request with status "Internal Error" 8. Preview the request's certificate (click request line, click summary in content area, scroll down, click "Certificate: Preview") 9. 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: 10. 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. 11. Run `socat -d -d FILE:/dev/null OPENSSL:127.0.0.1:11223,snihost=example.com` to make a request 12. Observe that the SNI hostname, `example.com`, appears in the output from `xxd` 13. 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 - App version: Version 4.5.0 (45000) - macOS version: Ventura 13.2.1 (22D68) (Intel)
Author
Owner

@NghiaTranUIT commented on GitHub (Mar 23, 2023):

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:
github.com/apple/swift-nio-ssl@845a97cbd5/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
<!-- gh-comment-id:1480472939 --> @NghiaTranUIT commented on GitHub (Mar 23, 2023): 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](https://github.com/apple/swift-nio-ssl/blob/845a97cbd5516ea9a5ed0f2788c4e38b3ee270dd/Sources/NIOSSL/NIOSSLClientHandler.swift#L68) 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> ``` 3. Proxyman constructs the new server certificate with real certificate information (`commonName` and `alternative_names`) and signs by Proxyman self-signed Root Certificate. 4. 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 ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/Proxyman#1574
No description provided.