From e337bcdb8fd78fe4bd556c2eb5fa72360196f8de Mon Sep 17 00:00:00 2001 From: Lam Wei Li Date: Tue, 17 Jan 2023 17:12:18 +0800 Subject: [PATCH] feat: new crlfFilter appender to prevent log forging --- docs/appenders.md | 1 + docs/crlfFilter.md | 36 ++++++++++++++++++++++++++++ lib/appenders/crlfFilter.js | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 docs/crlfFilter.md create mode 100644 lib/appenders/crlfFilter.js diff --git a/docs/appenders.md b/docs/appenders.md index 1619952..5f4b050 100644 --- a/docs/appenders.md +++ b/docs/appenders.md @@ -23,6 +23,7 @@ The following appenders are included with log4js. Some require extra dependencie - [categoryFilter](categoryFilter.md) - [console](console.md) +- [crlfFilter](crlfFilter.md) - [dateFile](dateFile.md) - [file](file.md) - [fileSync](fileSync.md) diff --git a/docs/crlfFilter.md b/docs/crlfFilter.md new file mode 100644 index 0000000..fd75aa7 --- /dev/null +++ b/docs/crlfFilter.md @@ -0,0 +1,36 @@ +# CRLF Filter + +This is not strictly an appender - it wraps around another appender and to prevent log forging by replacing starting the line with a `>` prefix after line-breaks. + +## Configuration + +- `type` - `"crlfFilter"` +- `appender` - `string | Array` - the name of the appender to filter. (exists for backward compatibility) +- `appenders` - `string | Array` - the names of the appenders to filter. + +## Example + +```javascript +log4js.configure({ + appenders: { + everything: { type: "file", filename: "all-the-logs.log" }, + crlfFilter: { + type: "crlfFilter", + appenders: ["everything"], + }, + }, + categories: { + default: { appenders: ["crlfFilter"], level: "debug" }, + }, +}); + +const logger = log4js.getLogger(); +logger.info( + "Some debug messages\n[2023-01-17T11:58:38.150] [INFO] default - Log forging" +); +``` + +``` +[2023-01-17T11:58:38.150] [INFO] default - Some debug messages +> [2023-01-17T11:58:38.150] [INFO] default - Log forging +``` diff --git a/lib/appenders/crlfFilter.js b/lib/appenders/crlfFilter.js new file mode 100644 index 0000000..6bda1a4 --- /dev/null +++ b/lib/appenders/crlfFilter.js @@ -0,0 +1,47 @@ +const debug = require('debug')('log4js:crlfFilter'); + +function crlfFilter(appenders) { + return (logEvent) => { + const filteredData = logEvent.data.map((i) => { + if (typeof i.replace === 'function') return i.replace(/\n/g, '\n> '); // add a > prefix + return i; + }); + logEvent.data = filteredData; + appenders.forEach((appender) => { + appender(logEvent); + }); + }; +} + +function configure(config, layouts, findAppender) { + const addToSet = function (input, set = new Set()) { + const addAppender = function (appenderName) { + debug(`actual appender is ${appenderName}`); + const appender = findAppender(appenderName); + if (!appender) { + debug(`actual appender "${config.appender}" not found`); + throw new Error(`crlfFilter appender "${config.appender}" not defined`); + } else { + set.add(appender); + } + }; + + if (input && !Array.isArray(input)) input = [input]; + if (input) input.forEach((appenderName) => addAppender(appenderName)); + + return set; + }; + + const appenders = new Set(); + addToSet(config.appender, appenders); + addToSet(config.appenders, appenders); + + if (appenders.size === 0) { + debug(`no appender found in config ${config}`); + throw new Error('crlfFilter appender must have an "appender" defined'); + } + + return crlfFilter(appenders); +} + +module.exports.configure = configure;