From 87224d2bb650812c2055ace24fee9df82259c7f8 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Tue, 26 Apr 2022 23:05:02 +0000 Subject: [PATCH] Add some protection against bad requests. Also bail if we can't start properly. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3870 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- core/core.js | 2 ++ core/httpd.js | 79 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/core/core.js b/core/core.js index cee3790b..537eb50e 100644 --- a/core/core.js +++ b/core/core.js @@ -603,6 +603,8 @@ loadSettings().then(function() { httpd.registerSocketHandler("/app/socket", app.socket); }).catch(function(error) { print('Failed to load settings.'); + print(error); + exit(1); }); exports.getSessionProcessBlob = getSessionProcessBlob; diff --git a/core/httpd.js b/core/httpd.js index 111529c2..210c1b2a 100644 --- a/core/httpd.js +++ b/core/httpd.js @@ -2,9 +2,13 @@ var gHandlers = []; var gSocketHandlers = []; +var gBadRequests = {}; function logError(error) { print("ERROR " + error); + if (error.stackTrace) { + print(error.stackTrace); + } } function addHandler(handler) { @@ -48,7 +52,7 @@ function Request(method, uri, version, headers, body, client) { this.uri = uri; this.query = undefined; } - this.version = version; + this.version = version || ''; this.headers = headers; this.client = {peerName: client.peerName, tls: client.tls}; this.body = body; @@ -127,7 +131,7 @@ function Response(request, client) { } headerString += "\r\n"; _started = true; - client.write(headerString); + client.write(headerString).catch(function() {}); }, end: function(data) { if (_finished) { @@ -135,25 +139,24 @@ function Response(request, client) { } if (data) { if (_chunked) { - client.write(data.length.toString(16) + "\r\n" + data + "\r\n" + "0\r\n\r\n"); + client.write(data.length.toString(16) + "\r\n" + data + "\r\n" + "0\r\n\r\n").catch(function() {}); } else { - client.write(data); + client.write(data).catch(function() {}); } } else if (_chunked) { - client.write("0\r\n\r\n"); + client.write("0\r\n\r\n").catch(function() {}); } _finished = true; if (!_keepAlive) { - client.shutdown(); + client.shutdown().catch(function() {}); } }, reportError: function(error) { if (!_started) { - client.write("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n"); + client.write("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n").catch(function() {}); } if (!_finished) { - client.write("500 Internal Server Error\r\n\r\n" + error.stackTrace); - client.shutdown(); + client.write("500 Internal Server Error\r\n\r\n" + error?.stackTrace).catch(function() {}); } logError(client.peerName + " - - [" + new Date() + "] " + error); }, @@ -175,7 +178,6 @@ function handleRequest(request, response) { }); } } catch (error) { - print(error); response.reportError(error); } } else { @@ -344,7 +346,46 @@ function webSocketAcceptResponse(key) { return binary; } +function badRequest(client, reason) { + var now = new Date(); + var count = 0; + var old = gBadRequests[client.peerName]; + if (!old) { + gBadRequests[client.peerName] = { + expire: new Date(now.getTime() + 10 * 60 * 1000), + count: 1, + }; + count = 1; + } else { + old.count++; + count = old.count; + } + new Response({version: '1.0'}, client).reportError(reason + ': ' + count); + client.close(); +} + +function allowRequest(client) { + var old = gBadRequests[client.peerName]; + if (old) { + var now = new Date(); + if (old.expire < now) { + delete gBadRequests[client.peerName]; + return true; + } else { + return old.count < 3; + } + } else { + return true; + } +} + function handleConnection(client) { + if (!allowRequest(client)) { + print('Rejecting client for too many bad requests: ', client.peerName); + client.close(); + return; + } + var inputBuffer = new Uint8Array(0); var request; var headers = {}; @@ -378,7 +419,16 @@ function handleConnection(client) { if (bodyToRead == -1) { line = utf8Decode(line); if (!request) { + if (!line) { + badRequest(client, 'Empty request.'); + return false; + } request = line.split(' '); + if (request.length != 3 || !request[2].startsWith('HTTP/1.')) { + badRequest(client, 'Bad request.'); + request = null; + return false; + } return true; } else if (line) { var colon = line.indexOf(':'); @@ -390,6 +440,10 @@ function handleConnection(client) { if (headers["content-length"] != undefined) { bodyToRead = parseInt(headers["content-length"]); lineByLine = false; + if (bodyToRead > 16 * 1024 * 1024) { + badRequest(client, 'Reuqest too large: ' + bodyToRead + '.'); + return false; + } body = new Uint8Array(bodyToRead); return true; } else if (headers["connection"] @@ -426,6 +480,7 @@ function handleConnection(client) { client.read(function(data) { if (data) { + const kMaxLineLength = 4096; var newBuffer = new Uint8Array(inputBuffer.length + data.length); newBuffer.set(inputBuffer, 0); newBuffer.set(data, inputBuffer.length); @@ -443,6 +498,10 @@ function handleConnection(client) { if (end > 0 && inputBuffer[end - 1] == carriageReturn) { --end; } + if (end > kMaxLineLength || end == -1 && inputBuffer.length > kMaxLineLength) { + badRequest(client, 'Request too long.'); + return; + } if (end != -1) { var line = inputBuffer.slice(0, end); inputBuffer = inputBuffer.slice(realEnd + 1);