fix(portproxy): Enforce TLS handshake and SNI validation on port 443 by blocking non-TLS connections and terminating session resumption attempts without SNI when allowSessionTicket is disabled.
This commit is contained in:
		| @@ -1,5 +1,13 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-03-12 - 3.41.5 - fix(portproxy) | ||||||
|  | Enforce TLS handshake and SNI validation on port 443 by blocking non-TLS connections and terminating session resumption attempts without SNI when allowSessionTicket is disabled. | ||||||
|  |  | ||||||
|  | - Added explicit check to block non-TLS connections on port 443 to ensure proper TLS usage. | ||||||
|  | - Enhanced logging for TLS ClientHello to include details on SNI extraction and session resumption status. | ||||||
|  | - Terminate connections with missing SNI by setting termination reasons ('session_ticket_blocked' or 'no_sni_blocked'). | ||||||
|  | - Ensured consistent rejection of non-TLS handshakes on standard HTTPS port. | ||||||
|  |  | ||||||
| ## 2025-03-12 - 3.41.4 - fix(tls/sni) | ## 2025-03-12 - 3.41.4 - fix(tls/sni) | ||||||
| Improve logging for TLS session resumption by extracting and logging SNI values from ClientHello messages. | Improve logging for TLS session resumption by extracting and logging SNI values from ClientHello messages. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@push.rocks/smartproxy', |   name: '@push.rocks/smartproxy', | ||||||
|   version: '3.41.4', |   version: '3.41.5', | ||||||
|   description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.' |   description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1578,28 +1578,45 @@ export class PortProxy { | |||||||
|           initialDataReceived = true; |           initialDataReceived = true; | ||||||
|           connectionRecord.hasReceivedInitialData = true; |           connectionRecord.hasReceivedInitialData = true; | ||||||
|            |            | ||||||
|  |           // Block non-TLS connections on port 443 | ||||||
|  |           // Always enforce TLS on standard HTTPS port | ||||||
|  |           if (!SniHandler.isTlsHandshake(chunk) && localPort === 443) { | ||||||
|  |             console.log( | ||||||
|  |               `[${connectionId}] Non-TLS connection detected on port 443. ` + | ||||||
|  |               `Terminating connection - only TLS traffic is allowed on standard HTTPS port.` | ||||||
|  |             ); | ||||||
|  |             if (connectionRecord.incomingTerminationReason === null) { | ||||||
|  |               connectionRecord.incomingTerminationReason = 'non_tls_blocked'; | ||||||
|  |               this.incrementTerminationStat('incoming', 'non_tls_blocked'); | ||||||
|  |             } | ||||||
|  |             socket.end(); | ||||||
|  |             this.cleanupConnection(connectionRecord, 'non_tls_blocked'); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |  | ||||||
|           // Check if this looks like a TLS handshake |           // Check if this looks like a TLS handshake | ||||||
|           if (SniHandler.isTlsHandshake(chunk)) { |           if (SniHandler.isTlsHandshake(chunk)) { | ||||||
|             connectionRecord.isTLS = true; |             connectionRecord.isTLS = true; | ||||||
|              |              | ||||||
|             // Check for session tickets if allowSessionTicket is disabled |             // Check for TLS ClientHello with either no SNI or session tickets | ||||||
|             if (this.settings.allowSessionTicket === false && SniHandler.isClientHello(chunk)) { |             if (this.settings.allowSessionTicket === false && SniHandler.isClientHello(chunk)) { | ||||||
|  |               // Extract SNI first | ||||||
|  |               const extractedSNI = SniHandler.extractSNI(chunk, this.settings.enableTlsDebugLogging); | ||||||
|  |               const hasSNI = !!extractedSNI; | ||||||
|  |                | ||||||
|               // Analyze for session resumption attempt |               // Analyze for session resumption attempt | ||||||
|               const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging); |               const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging); | ||||||
|                |                | ||||||
|               if (resumptionInfo.isResumption) { |               // Always log for debugging purposes | ||||||
|                 // Always log resumption attempt for easier debugging |  | ||||||
|                 // Try to extract SNI for logging |  | ||||||
|                 const extractedSNI = SniHandler.extractSNI(chunk, this.settings.enableTlsDebugLogging); |  | ||||||
|               console.log( |               console.log( | ||||||
|                   `[${connectionId}] Session resumption detected in initial ClientHello. ` + |                 `[${connectionId}] TLS ClientHello detected with allowSessionTicket=false. ` + | ||||||
|                   `Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` + |                 `Has SNI: ${hasSNI ? 'Yes' : 'No'}, ` + | ||||||
|                 `SNI value: ${extractedSNI || 'None'}, ` + |                 `SNI value: ${extractedSNI || 'None'}, ` + | ||||||
|                   `allowSessionTicket: ${this.settings.allowSessionTicket}` |                 `Has session resumption: ${resumptionInfo.isResumption ? 'Yes' : 'No'}` | ||||||
|               ); |               ); | ||||||
|                |                | ||||||
|                 // Block if there's session resumption without SNI |               // Block if this is a connection with session resumption but no SNI | ||||||
|                 if (!resumptionInfo.hasSNI) { |               if (resumptionInfo.isResumption && !hasSNI) { | ||||||
|                 console.log( |                 console.log( | ||||||
|                   `[${connectionId}] Session resumption detected in initial ClientHello without SNI and allowSessionTicket=false. ` + |                   `[${connectionId}] Session resumption detected in initial ClientHello without SNI and allowSessionTicket=false. ` + | ||||||
|                   `Terminating connection to force new TLS handshake.` |                   `Terminating connection to force new TLS handshake.` | ||||||
| @@ -1611,14 +1628,22 @@ export class PortProxy { | |||||||
|                 socket.end(); |                 socket.end(); | ||||||
|                 this.cleanupConnection(connectionRecord, 'session_ticket_blocked'); |                 this.cleanupConnection(connectionRecord, 'session_ticket_blocked'); | ||||||
|                 return; |                 return; | ||||||
|                 } else { |               } | ||||||
|                   if (this.settings.enableDetailedLogging) { |                | ||||||
|  |               // Also block if this is a TLS connection without SNI when allowSessionTicket is false | ||||||
|  |               // This forces clients to send SNI which helps with routing | ||||||
|  |               if (!hasSNI && localPort === 443) { | ||||||
|                 console.log( |                 console.log( | ||||||
|                       `[${connectionId}] Session resumption with SNI detected in initial ClientHello. ` + |                   `[${connectionId}] TLS ClientHello detected on port 443 without SNI and allowSessionTicket=false. ` + | ||||||
|                       `Allowing connection since SNI is present.` |                   `Terminating connection to force proper SNI in handshake.` | ||||||
|                 ); |                 ); | ||||||
|  |                 if (connectionRecord.incomingTerminationReason === null) { | ||||||
|  |                   connectionRecord.incomingTerminationReason = 'no_sni_blocked'; | ||||||
|  |                   this.incrementTerminationStat('incoming', 'no_sni_blocked'); | ||||||
|                 } |                 } | ||||||
|                 } |                 socket.end(); | ||||||
|  |                 this.cleanupConnection(connectionRecord, 'no_sni_blocked'); | ||||||
|  |                 return; | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|              |              | ||||||
| @@ -1956,6 +1981,22 @@ export class PortProxy { | |||||||
|  |  | ||||||
|             initialDataReceived = true; |             initialDataReceived = true; | ||||||
|              |              | ||||||
|  |             // Block non-TLS connections on port 443 | ||||||
|  |             // Always enforce TLS on standard HTTPS port | ||||||
|  |             if (!SniHandler.isTlsHandshake(chunk) && localPort === 443) { | ||||||
|  |               console.log( | ||||||
|  |                 `[${connectionId}] Non-TLS connection detected on port 443 in SNI handler. ` + | ||||||
|  |                 `Terminating connection - only TLS traffic is allowed on standard HTTPS port.` | ||||||
|  |               ); | ||||||
|  |               if (connectionRecord.incomingTerminationReason === null) { | ||||||
|  |                 connectionRecord.incomingTerminationReason = 'non_tls_blocked'; | ||||||
|  |                 this.incrementTerminationStat('incoming', 'non_tls_blocked'); | ||||||
|  |               } | ||||||
|  |               socket.end(); | ||||||
|  |               this.cleanupConnection(connectionRecord, 'non_tls_blocked'); | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // Try to extract SNI |             // Try to extract SNI | ||||||
|             let serverName = ''; |             let serverName = ''; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user