| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var SanitizeHost = module.exports; | 
					
						
							|  |  |  | var HttpMiddleware = require("./http-middleware.js"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SanitizeHost.create = function(gl, app) { | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     return function(req, res, next) { | 
					
						
							|  |  |  |         function realNext() { | 
					
						
							|  |  |  |             if ("function" === typeof app) { | 
					
						
							|  |  |  |                 app(req, res); | 
					
						
							|  |  |  |             } else if ("function" === typeof next) { | 
					
						
							|  |  |  |                 next(); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 res.statusCode = 500; | 
					
						
							|  |  |  |                 res.end("Error: no middleware assigned"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var hostname = HttpMiddleware.getHostname(req); | 
					
						
							|  |  |  |         // Replace the hostname, and get the safe version
 | 
					
						
							|  |  |  |         var safehost = HttpMiddleware.sanitizeHostname(req); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // if no hostname, move along
 | 
					
						
							|  |  |  |         if (!hostname) { | 
					
						
							|  |  |  |             realNext(); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // if there were unallowed characters, complain
 | 
					
						
							|  |  |  |         if (safehost.length !== hostname.length) { | 
					
						
							|  |  |  |             res.statusCode = 400; | 
					
						
							|  |  |  |             res.end("Malformed HTTP Header: 'Host: " + hostname + "'"); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
 | 
					
						
							|  |  |  |         if (req.socket.encrypted) { | 
					
						
							|  |  |  |             if (req.socket && "string" === typeof req.socket.servername) { | 
					
						
							|  |  |  |                 // Workaround for https://github.com/nodejs/node/issues/22389
 | 
					
						
							|  |  |  |                 if (!SanitizeHost._checkServername(safehost, req.socket)) { | 
					
						
							|  |  |  |                     res.statusCode = 400; | 
					
						
							|  |  |  |                     res.setHeader("Content-Type", "text/html; charset=utf-8"); | 
					
						
							|  |  |  |                     res.end( | 
					
						
							|  |  |  |                         "<h1>Domain Fronting Error</h1>" + | 
					
						
							|  |  |  |                             "<p>This connection was secured using TLS/SSL for '" + | 
					
						
							|  |  |  |                             (req.socket.servername || "").toLowerCase() + | 
					
						
							|  |  |  |                             "'</p>" + | 
					
						
							|  |  |  |                             "<p>The HTTP request specified 'Host: " + | 
					
						
							|  |  |  |                             safehost + | 
					
						
							|  |  |  |                             "', which is (obviously) different.</p>" + | 
					
						
							|  |  |  |                             "<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>" | 
					
						
							|  |  |  |                     ); | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             /* | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  |       else if (safehost && !gl._skip_fronting_check) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// We used to print a log message here, but it turns out that it's
 | 
					
						
							|  |  |  | 				// really common for IoT devices to not use SNI (as well as many bots
 | 
					
						
							|  |  |  | 				// and such).
 | 
					
						
							|  |  |  | 				// It was common for the log message to pop up as the first request
 | 
					
						
							|  |  |  | 				// to the server, and that was confusing. So instead now we do nothing.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
 | 
					
						
							|  |  |  | 				//gl._skip_fronting_check = true;
 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  |       */ | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |         // carry on
 | 
					
						
							|  |  |  |         realNext(); | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var warnDomainFronting = true; | 
					
						
							|  |  |  | var warnUnexpectedError = true; | 
					
						
							|  |  |  | SanitizeHost._checkServername = function(safeHost, tlsSocket) { | 
					
						
							| 
									
										
										
										
											2019-11-01 15:14:07 -06:00
										 |  |  |     var servername = (tlsSocket.servername || "").toLowerCase(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // acceptable: older IoT devices may lack SNI support
 | 
					
						
							|  |  |  |     if (!servername) { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // acceptable: odd... but acceptable
 | 
					
						
							|  |  |  |     if (!safeHost) { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (safeHost === servername) { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ("function" !== typeof tlsSocket.getCertificate) { | 
					
						
							|  |  |  |         // domain fronting attacks allowed
 | 
					
						
							|  |  |  |         if (warnDomainFronting) { | 
					
						
							|  |  |  |             // https://github.com/nodejs/node/issues/24095
 | 
					
						
							|  |  |  |             console.warn( | 
					
						
							|  |  |  |                 "Warning: node " + | 
					
						
							|  |  |  |                     process.version + | 
					
						
							|  |  |  |                     " is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater." | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |             warnDomainFronting = false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // connection established with servername and session is re-used for allowed name
 | 
					
						
							|  |  |  |     // See https://github.com/nodejs/node/issues/24095
 | 
					
						
							|  |  |  |     var cert = tlsSocket.getCertificate(); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |         // TODO optimize / cache?
 | 
					
						
							|  |  |  |         // *should* always have a string, right?
 | 
					
						
							|  |  |  |         // *should* always be lowercase already, right?
 | 
					
						
							|  |  |  |         //console.log(safeHost, cert.subject.CN, cert.subjectaltname);
 | 
					
						
							|  |  |  |         var isSubject = (cert.subject.CN || "").toLowerCase() === safeHost; | 
					
						
							|  |  |  |         if (isSubject) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var dnsnames = (cert.subjectaltname || "").split(/,\s+/); | 
					
						
							|  |  |  |         var inSanList = dnsnames.some(function(name) { | 
					
						
							|  |  |  |             // always prefixed with "DNS:"
 | 
					
						
							|  |  |  |             return safeHost === name.slice(4).toLowerCase(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (inSanList) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |         // not sure what else to do in this situation...
 | 
					
						
							|  |  |  |         if (warnUnexpectedError) { | 
					
						
							|  |  |  |             console.warn("Warning: encoutered error while performing domain fronting check: " + e.message); | 
					
						
							|  |  |  |             warnUnexpectedError = false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return false; | 
					
						
							| 
									
										
										
										
											2019-10-26 23:52:19 -06:00
										 |  |  | }; |