mirror of
				https://github.com/therootcompany/greenlock-express.js.git
				synced 2024-11-16 17:28:59 +00:00 
			
		
		
		
	
		
			
	
	
		
			134 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			134 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | "use strict"; | ||
|  | 
 | ||
|  | var SanitizeHost = module.exports; | ||
|  | var HttpMiddleware = require("./http-middleware.js"); | ||
|  | 
 | ||
|  | SanitizeHost.create = function(gl, app) { | ||
|  | 	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; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			/* | ||
|  |       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;
 | ||
|  | 			} | ||
|  |       */ | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// carry on
 | ||
|  | 		realNext(); | ||
|  | 	}; | ||
|  | }; | ||
|  | 
 | ||
|  | var warnDomainFronting = true; | ||
|  | var warnUnexpectedError = true; | ||
|  | SanitizeHost._checkServername = function(safeHost, tlsSocket) { | ||
|  | 	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?
 | ||
|  | 		if ( | ||
|  | 			(cert.subject.CN || "").toLowerCase() !== safeHost && | ||
|  | 			!(cert.subjectaltname || "").split(/,\s+/).some(function(name) { | ||
|  | 				// always prefixed with "DNS:"
 | ||
|  | 				return safeHost === name.slice(4).toLowerCase(); | ||
|  | 			}) | ||
|  | 		) { | ||
|  | 			return false; | ||
|  | 		} | ||
|  | 	} 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; | ||
|  | }; |