Mitigating the favicon bug through LineRate scripting
A recent report (June 2015) revealed a web browser's problem with a large favicon [1]: The browser crashes when the size of a favicon is larger than what it expects. A favicon is a small image, say 16 x 16 pixels, that is displayed next to a browser's address bar or in the bookmark [2]. Because it is downloaded automatically by the browser, users can't have any clue to the browser's abrupt crash.
LineRate can protect the web clients in your private/corporate network by rejecting the large favicons.
In this solution, a LineRate instance is configured as a forward-proxy as shown in the figure below. The LineRate proxies HTTP requests from clients in the corporate/private network to servers on the public Intenet, and relays the responses from the servers to the clients. Please refer to our Product Documentation for the configuration guide [3].
Here is the script. When a client in the corporate/private network sends a request, the LineRate checks if the requested resource is a favicon (line 3 and 10). When the response comes back from the server on the Internet, it checks the Content-Length response header value [4]. The LineRate passes the favicon smaller than a predefined threshold (line 4 and 13): Otherwise, it discards the data and sends back 403 error message to the client.
'use strict'; var fpm = require('lrs/forwardProxyModule'); var favicon = /favicon\.ico|gif|png/i; var maxSize = 1000; // # of bytes var vsname = 'fp50'; var errorMessage = 'Sorry, the favicon you requested is too large for you.' fpm.on('exist', vsname, function(fpo) { fpo.on('request', function(servReq, servResp, cliReq) { if (favicon.test(servReq.url)) { cliReq.on('response', function(cliResp) { var len = cliResp.headers['content-length']; if (len > maxSize) { console.log('favicon ' + servReq.url + ' rejected. ' + len + ' bytes.'); servResp.writeHead(403, { 'Content-type': 'text/plain', 'Content-Length': errorMessage.length}); servResp.end(errorMessage); } else { cliResp.bindHeaders(servResp); cliResp.fastPipe(servResp); } }); } cliReq(); }); }); console.log('favicon filter started (fw version).');
The Content-Length header may not be present in the response header (e.g., Transfer-Encoding). In that case, you need to determine its size by checking the amount of data read.
The code below is essentially the same as the Content-Length version. Because the favicon data is binary, the Buffer object should be used to read the incoming data chunks (line 14). If you use var ico += ico_chunk, the data chunk is interpreted as UTF-8 characters, hence the image will be corrupted.
'use strict'; var fpm = require('lrs/forwardProxyModule'); var favicon = /favicon\.ico|gif|png/i; var maxSize = 1000; // # of bytes var vsname = 'fp50'; var errorMessage = 'Sorry, the favicon you requested is too large for you.' fpm.on('exist', vsname, function(fpo) { fpo.on('request', function(servReq, servResp, cliReq) { if (favicon.test(servReq.url)) { cliReq.on('response', function(cliResp) { var ico_chunk = []; cliResp.on('data', function(chunk) { ico_chunk.push(chunk); }); cliResp.on('end', function() { var ico = Buffer.concat(ico_chunk); if (ico.length > maxSize) { console.log('favicon ' + servReq.url + ' rejected. ' + ico.length + ' bytes.'); servResp.writeHead(403, { 'Content-type': 'text/plain', 'Content-Length': errorMessage.length}); servResp.end(errorMessage); } else { cliResp.bindHeaders(servResp); servResp.end(ico); } }); }); } cliReq(); }); }); console.log('favicon filter started.');
You may think storing the entire data and flushing at the end is performance-wise not optimal. In that case, you can change the above code (line 11-30) to stream back the chunks as they come in until the size exceeds the threshold. When it reaches the maximum size, the script aborts the session (
). This script won't explicitly send the 403 error to the client.cliReq.abort()
cliReq.on('response', function(cliResp) { var ico_length = 0; var aborted = false; cliResp.on('data', function(chunk) { if(aborted) { return; } // Handles outstanding chunks coming at us before the server cancels the request; this should never happen, but it's here just in case iso_length += chunk.length; // Track length only, not the entire buffer in memory if(iso_length < maxSize) { servResp.write(chunk); // Performance: writes chunks out immediately } else { // Cancel request aborted = true; cliReq.abort(); servResp.end(); } }); cliResp.on('end', function() { if (!aborted && ico_length < maxSize) { servResp.end(); } }); });
You do not need to implement this solution once the browser bug is fixed. Yet, something similar may happen in future, and the fix may not come in timely manner. LineRate can provide solutions for timely security vulnerability mitigation as discussed in this article and others [5, 6]. if you wish to respond to incidents by manipulating HTTP transactions, it is time to introduce LineRate.
Please leave a comment or reach out to us with any questions or suggestions and if you're not a LineRate user yet, remember you can try it out for free.
References:
[1] benjamingr: Favicon Download Bug, GitHub.
[3] LineRate Product Documentation. See the "Configuring a Forward Proxy" section.
[4] Fielding & Reschke, Eds.: Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing, RFC 7230 (June 2014). See Section 3.3.2 for the Content-Length definition.
[5] Talley: LineRate: Range header attack mitigation, DevCentral (April 2015).
[6] Rajagopal: CVE-2014-3566: Removing SSLv3 from LineRate, DevCentral (October 2014).