From c9d55c81a3d6b7685fff86f042db06bd63decfe4 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 4 Apr 2020 20:39:33 +0000 Subject: [PATCH] import/export: always rate limit import and exports This is a departure from previous versions, which did not limit import/export requests. Now such requests are ALWAYS rate limited. The default is 10 requests per IP each 90 seconds, and also applies to old instances upgraded to 1.8.3. Administrators can tune the parameters via settings.importExportRateLimiting. --- settings.json.docker | 17 +++++++++++++++++ settings.json.template | 17 +++++++++++++++++ src/node/hooks/express/importexport.js | 10 ++++++++++ src/node/utils/Settings.js | 17 +++++++++++++++++ src/package-lock.json | 5 +++++ src/package.json | 1 + 6 files changed, 67 insertions(+) diff --git a/settings.json.docker b/settings.json.docker index c4dcc78ea..56bc69649 100644 --- a/settings.json.docker +++ b/settings.json.docker @@ -408,6 +408,23 @@ "indentationOnNewLine": false, */ + /* + * From Etherpad 1.8.3 onwards, import and export of pads is always rate + * limited. + * + * The default is to allow at most 10 requests per IP in a 90 seconds window. + * After that the import/export request is rejected. + * + * See https://github.com/nfriedly/express-rate-limit for more options + */ + "importExportRateLimiting": { + // duration of the rate limit window (milliseconds) + "windowMs": 90000, + + // maximum number of requests per IP to allow during the rate limit window + "max": 10 + }, + /* * From Etherpad 1.8.3 onwards, the maximum allowed size for a single imported * file is always bounded. diff --git a/settings.json.template b/settings.json.template index 97ed7844b..0e61c7c67 100644 --- a/settings.json.template +++ b/settings.json.template @@ -413,6 +413,23 @@ "indentationOnNewLine": false, */ + /* + * From Etherpad 1.8.3 onwards, import and export of pads is always rate + * limited. + * + * The default is to allow at most 10 requests per IP in a 90 seconds window. + * After that the import/export request is rejected. + * + * See https://github.com/nfriedly/express-rate-limit for more options + */ + "importExportRateLimiting": { + // duration of the rate limit window (milliseconds) + "windowMs": 90000, + + // maximum number of requests per IP to allow during the rate limit window + "max": 10 + }, + /* * From Etherpad 1.8.3 onwards, the maximum allowed size for a single imported * file is always bounded. diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.js index 5e27e940e..95d02775d 100644 --- a/src/node/hooks/express/importexport.js +++ b/src/node/hooks/express/importexport.js @@ -4,10 +4,19 @@ var exportHandler = require('../../handler/ExportHandler'); var importHandler = require('../../handler/ImportHandler'); var padManager = require("../../db/PadManager"); var authorManager = require("../../db/AuthorManager"); +const rateLimit = require("express-rate-limit"); + +settings.importExportRateLimiting.onLimitReached = function(req, res, options) { + // when the rate limiter triggers, write a warning in the logs + console.warn(`Import/Export rate limiter triggered on "${req.originalUrl}" for IP address ${req.ip}`); +} + +var limiter = rateLimit(settings.importExportRateLimiting); exports.expressCreateServer = function (hook_name, args, cb) { // handle export requests + args.app.use('/p/:pad/:rev?/export/:type', limiter); args.app.get('/p/:pad/:rev?/export/:type', async function(req, res, next) { var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"]; //send a 404 if we don't support this filetype @@ -40,6 +49,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { }); // handle import requests + args.app.use('/p/:pad/import', limiter); args.app.post('/p/:pad/import', async function(req, res, next) { if (await hasPadAccess(req, res)) { let exists = await padManager.doesPadExists(req.params.pad); diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 4c808620b..91fe5d7c2 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -305,6 +305,23 @@ exports.scrollWhenFocusLineIsOutOfViewport = { */ exports.exposeVersion = false; +/* + * From Etherpad 1.8.3 onwards, import and export of pads is always rate + * limited. + * + * The default is to allow at most 10 requests per IP in a 90 seconds window. + * After that the import/export request is rejected. + * + * See https://github.com/nfriedly/express-rate-limit for more options + */ +exports.importExportRateLimiting = { + // duration of the rate limit window (milliseconds) + "windowMs": 90000, + + // maximum number of requests per IP to allow during the rate limit window + "max": 10 +}; + /* * From Etherpad 1.8.3 onwards, the maximum allowed size for a single imported * file is always bounded. diff --git a/src/package-lock.json b/src/package-lock.json index 48ad650d6..ebd75d4bd 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -1633,6 +1633,11 @@ } } }, + "express-rate-limit": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.1.tgz", + "integrity": "sha512-puA1zcCx/quwWUOU6pT6daCt6t7SweD9wKChKhb+KSgFMKRwS81C224hiSAUANw/gnSHiwEhgozM/2ezEBZPeA==" + }, "express-session": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz", diff --git a/src/package.json b/src/package.json index 29a7cbec9..9cd9abd44 100644 --- a/src/package.json +++ b/src/package.json @@ -40,6 +40,7 @@ "etherpad-require-kernel": "1.0.9", "etherpad-yajsml": "0.0.2", "express": "4.17.1", + "express-rate-limit": "5.1.1", "express-session": "1.17.0", "find-root": "1.1.0", "formidable": "1.2.1",