Enforcing HSTS (HTTP Strict Transport Security) in LineRate

HTTP Strict Transport Security is a policy between your customer's browsers and your servers to increase security. It forces the browser to always use HTTPS when connecting to your site. The server or proxy needs to set the 

Strict-Transport-Security
 header. If the client connects sometime in the future and isn't offered a valid SSL cert, it should show an error to the user. Also, if the client is somehow directed to a plaintext URL at your site, for instance from the address bar, it should turn it into an HTTPS URL before connecting.

This policy can prevent simple attacks where an attacker is positioned in the network temporarily between a client and your site. If they can connect to the client plaintext, and the user isn't carefully checking for the green browser lock symbol, they can act as a man in the middle and read all the data flowing between clients and servers. See Moxie Marlinspike's presentation.

HSTS also saves you if you're worried about some piece of infrastructure accidentally emitting a non-secure link. Modern sites are a hybrid of client-side and server-side code; browser facing content and APIs; core applications and peripheral systems like analytics, support, or advertising. What are the odds that one of these systems will someday use a URL that's not HTTPS? With HSTS, you can prevent attacks that take advantage of this accident. To make HSTS effective in this case, you should place it in a proxy outside of all of these systems.

First I'll show a simple script to enable HSTS on LineRate; next I'll show an enhancement to detect the plaintext URLs that are leaking on clients that don't obey HSTS.

Simple: Add HSTS Header to Responses

To enable HSTS for your site, simply catch responses and add the "Strict-Transport-Security" header:

var vsm = require('lrs/virtualServerModule');

// Set this to the amount of time a browser should obey HSTS for your site
var maxAge = 365*24*3600;  // One year, in seconds

function setHsts(servReq, servRes, cliReq) {
  cliReq.on('response', function (cliRes) {
    cliRes.bindHeaders(servRes);
    servRes.setHeader('Strict-Transport-Security', 'max-age=' + maxAge);
    cliRes.fastPipe(servRes);
  });
  cliReq();
}

vsm.on('exist', 'yourVirtualServerName', function (vs) {
  vs.on('request', setHsts);
});

For any requests that come through a virtual server named 

yourVirtualServerName
, LineRate will add the header to the response.

In this example, the maxAge variable means that the browser should enforce HTTPS for your site for the next year from when it saw this response header. As long as users visit your site at least once a year, their browser will enforce that all URLs are HTTPS.

Advanced: Detect plaintext leaks and HSTS issues

In a more advanced script, you can also detect requests to URLs that aren't HTTPS. Note that HSTS requires the browser to enforce the policy; some browsers don't support it (Internet Explorer does not as of this writing; Safari didn't until Mavericks). For those users, you'll still need to detect any plaintext "leaks". Or, maybe you're a belt-and-suspenders kind of person, and you want to tell your servers to add HSTS, but also detect a failure to do so in your proxy. In these cases, the script below will detect the problem, collect information, record it, and workaround by redirecting to the HTTPS URL.

var vsm = require('lrs/virtualServerModule');
var util = require('util');

// Set this to the domain name to redirect to
var yourDomain = 'www.yoursite.com';

// Set this to the amount of time a browser should obey HSTS for your site
var maxAge = 365*24*3600;  // One year, in seconds

var stsValParser = /max-age=([0-9]+);?/;

function detectAndFixHsts(servReq, servRes, cliReq) {
  cliReq.on('response', function (cliRes) {
    cliRes.bindHeaders(servRes);
    var stsVal = cliRes.headers['strict-transport-security'];
    var stsMatch = stsVal ? stsValParser.match(stsVal) : [];
    if (stsMatch.length !== 1) {
      // Strict-Transport-Security header not valid.
      console.log('[WARNING] Strict-Transport-Security header not set ' +
                  'properly for URL %s.  Value: %s.  Request Headers: %s' +
                  ', response headers: %s', servReq.url, stsVal,
                  util.inspect(servReq.headers),
                  util.inspect(cliRes.headers));
      servRes.setHeader('Strict-Transport-Security', 'max-age=' + maxAge);
    }
    cliRes.fastPipe(servRes);
  });
  cliReq();
}

function redirectToHttps(servReq, servRes, cliReq) {
  // This is attached to the non-SSL VIP.
  var referer = servReq.headers['referer'];
  if (referer === undefined ||
      (referer.lastIndexOf('http://' + yourDomain, 0) == -1)) {
    // Referred from another site on the net; not a leak in your site.
  } else {
    // Leaked a plaintext URL or user is using a deprecated client
    console.log('[WARNING] Client requested non-HTTPS URL %s. ' +
                'User-Agent: %s, headers: %s', servReq.url,
                servReq.headers['user-agent'],
                util.inspect(servReq.headers));
  }
  var httpsUrl = 'https://' + yourDomain + servReq.url;
  var redirectBody = '<html><head><title> ' + httpsUrl + ' Moved</title>' +
                     '</head><body><p>This page has moved to <a href="' +
                     httpsUrl + '">' + httpsUrl + '</a></p></body></html>';
  servRes.writeHead(302, { 'Location': httpsUrl,
                           'Content-Type' : 'text/html',
                           'Content-Length' : redirectBody.length });
  servRes.end(redirectBody);
}

vsm.on('exist', 'yourHttpsVirtualServer', function (vs) {
  vs.on('request', detectAndFixHsts);
});
vsm.on('exist', 'yourPlainHttpVirtualServer', function (vs) {
  vs.on('request', redirectToHttps);
});

Note that logging every non-HTTPS request can limit performance and fill up disk.  Alternatives include throttled logging (try googling "npm log throttling"), or recording URLs to a database, or keeping a cache of URLs that we've already reported.  If you're interested, let me know in the comments and I can cover some of these topics in future blog posts.

Updated Jun 06, 2023
Version 2.0
No CommentsBe the first to comment