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 | ||||
|  | ||||
| ## 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) | ||||
| Improve logging for TLS session resumption by extracting and logging SNI values from ClientHello messages. | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   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.' | ||||
| } | ||||
|   | ||||
| @@ -1578,47 +1578,72 @@ export class PortProxy { | ||||
|           initialDataReceived = 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 | ||||
|           if (SniHandler.isTlsHandshake(chunk)) { | ||||
|             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)) { | ||||
|               // Extract SNI first | ||||
|               const extractedSNI = SniHandler.extractSNI(chunk, this.settings.enableTlsDebugLogging); | ||||
|               const hasSNI = !!extractedSNI; | ||||
|                | ||||
|               // Analyze for session resumption attempt | ||||
|               const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging); | ||||
|                | ||||
|               if (resumptionInfo.isResumption) { | ||||
|                 // Always log resumption attempt for easier debugging | ||||
|                 // Try to extract SNI for logging | ||||
|                 const extractedSNI = SniHandler.extractSNI(chunk, this.settings.enableTlsDebugLogging); | ||||
|                 console.log( | ||||
|                   `[${connectionId}] Session resumption detected in initial ClientHello. ` + | ||||
|                   `Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` + | ||||
|                   `SNI value: ${extractedSNI || 'None'}, ` + | ||||
|                   `allowSessionTicket: ${this.settings.allowSessionTicket}` | ||||
|                 ); | ||||
|               // Always log for debugging purposes | ||||
|               console.log( | ||||
|                 `[${connectionId}] TLS ClientHello detected with allowSessionTicket=false. ` + | ||||
|                 `Has SNI: ${hasSNI ? 'Yes' : 'No'}, ` + | ||||
|                 `SNI value: ${extractedSNI || 'None'}, ` + | ||||
|                 `Has session resumption: ${resumptionInfo.isResumption ? 'Yes' : 'No'}` | ||||
|               ); | ||||
|                | ||||
|                 // Block if there's session resumption without SNI | ||||
|                 if (!resumptionInfo.hasSNI) { | ||||
|                   console.log( | ||||
|                     `[${connectionId}] Session resumption detected in initial ClientHello without SNI and allowSessionTicket=false. ` + | ||||
|                     `Terminating connection to force new TLS handshake.` | ||||
|                   ); | ||||
|                   if (connectionRecord.incomingTerminationReason === null) { | ||||
|                     connectionRecord.incomingTerminationReason = 'session_ticket_blocked'; | ||||
|                     this.incrementTerminationStat('incoming', 'session_ticket_blocked'); | ||||
|                   } | ||||
|                   socket.end(); | ||||
|                   this.cleanupConnection(connectionRecord, 'session_ticket_blocked'); | ||||
|                   return; | ||||
|                 } else { | ||||
|                   if (this.settings.enableDetailedLogging) { | ||||
|                     console.log( | ||||
|                       `[${connectionId}] Session resumption with SNI detected in initial ClientHello. ` + | ||||
|                       `Allowing connection since SNI is present.` | ||||
|                     ); | ||||
|                   } | ||||
|               // Block if this is a connection with session resumption but no SNI | ||||
|               if (resumptionInfo.isResumption && !hasSNI) { | ||||
|                 console.log( | ||||
|                   `[${connectionId}] Session resumption detected in initial ClientHello without SNI and allowSessionTicket=false. ` + | ||||
|                   `Terminating connection to force new TLS handshake.` | ||||
|                 ); | ||||
|                 if (connectionRecord.incomingTerminationReason === null) { | ||||
|                   connectionRecord.incomingTerminationReason = 'session_ticket_blocked'; | ||||
|                   this.incrementTerminationStat('incoming', 'session_ticket_blocked'); | ||||
|                 } | ||||
|                 socket.end(); | ||||
|                 this.cleanupConnection(connectionRecord, 'session_ticket_blocked'); | ||||
|                 return; | ||||
|               } | ||||
|                | ||||
|               // 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( | ||||
|                   `[${connectionId}] TLS ClientHello detected on port 443 without SNI and allowSessionTicket=false. ` + | ||||
|                   `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; | ||||
|              | ||||
|             // 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 | ||||
|             let serverName = ''; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user