diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d06f453..02accf8cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# 1.6.4 + * SECURITY: exploitable /admin access - CVE-2018-9845 + * SECURITY: DoS with pad exports - CVE-2018-9327 + * SECURITY: Remote Code Execution - CVE-2018-9326 + * SECURITY: Pad data leak - CVE-2018-9325 + * Fix: Admin redirect URL + * Fix: Various script Fixes + * Fix: Various CSS/Style/Layout fixes + * NEW: Improved Pad contents readability + * NEW: Hook: onAccessCheck + * NEW: SESSIONKEY and APIKey customizable path + * NEW: checkPads script + * NEW: Support "cluster mode" + # 1.6.3 * SECURITY: Update ejs * SECURITY: xss vulnerability when reading window.location.href @@ -56,7 +70,7 @@ * NEW: Allow LibreOffice to be used when exporting a pad * NEW: Create hook exportHtmlAdditionalTagsWithData * NEW: Improve DB migration performance - * NEW: allow settings to be applied from the filesystem + * NEW: allow settings to be applied from the filesystem * NEW: remove applySettings hook and allow credentials.json to be part of core * NEW: Use exec to switch to node process * NEW: Validate incoming color codes @@ -85,7 +99,7 @@ * Fix: switchToPad method * Fix: Dead keys * Fix: Preserve new lines in copy-pasted text - * Fix: Compatibility mode on IE + * Fix: Compatibility mode on IE * Fix: Content Collector to get the class of the DOM-node * Fix: Timeslider export links * Fix: Double prompt on file upload @@ -212,7 +226,7 @@ * Fix: Session Deletion error * Fix: Allow browser tabs to be cycled when focus is in editor * Fix: Various Editor issues with Easysync potentially entering forever loop on bad changeset - + # 1.4 * NEW: Disable toolbar items through settings.json * NEW: Internal stats/metrics engine @@ -244,7 +258,7 @@ # 1.3 * NEW: We now follow the semantic versioning scheme! * NEW: Option to disable IP logging - * NEW: Localisation updates from http://translatewiki.net. + * NEW: Localisation updates from http://translatewiki.net. * Fix: Fix readOnly group pads * Fix: don't fetch padList on every request @@ -337,7 +351,7 @@ * NEW: Add authorId to chat and userlist as a data attribute * NEW: Refactor and fix our frontend tests * NEW: Localisation updates - + # 1.2.81 * Fix: CtrlZ-Y for Undo Redo @@ -377,7 +391,7 @@ * Other: Change loading message asking user to please wait on first build * Other: Allow etherpad to use global npm installation (Safe since node 6.3) * Other: Better documentation for log rotation and log message handling - + # 1.2.7 diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index 17910e4b2..48dcf56cb 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -25,6 +25,10 @@ exports.createServer = function () { else{ console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json"); } + var env = process.env.NODE_ENV || 'development'; + if(env !== 'production'){ + console.warn("Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production"); + } } exports.restartServer = function () { diff --git a/src/node/hooks/express/apicalls.js b/src/node/hooks/express/apicalls.js index 009a93d72..e07bbb0be 100644 --- a/src/node/hooks/express/apicalls.js +++ b/src/node/hooks/express/apicalls.js @@ -3,7 +3,7 @@ var apiLogger = log4js.getLogger("API"); var clientLogger = log4js.getLogger("client"); var formidable = require('formidable'); var apiHandler = require('../../handler/APIHandler'); -var isVarName = require('is-var-name'); +var isValidJSONPName = require('./isValidJsonPName'); //This is for making an api call, collecting all post information and passing it to the apiHandler var apiCaller = function(req, res, fields) { @@ -19,7 +19,7 @@ var apiCaller = function(req, res, fields) { apiLogger.info("RESPONSE, " + req.params.func + ", " + response); //is this a jsonp call, if yes, add the function call - if(req.query.jsonp && isVarName(req.query.jsonp)) + if(req.query.jsonp && isValidJSONPName.check(req.query.jsonp)) response = req.query.jsonp + "(" + response + ")"; res._____send(response); @@ -46,7 +46,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { //The Etherpad client side sends information about how a disconnect happened args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) { - new formidable.IncomingForm().parse(req, function(err, fields, files) { + new formidable.IncomingForm().parse(req, function(err, fields, files) { clientLogger.info("DIAGNOSTIC-INFO: " + fields.diagnosticInfo); res.end("OK"); }); @@ -54,7 +54,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { //The Etherpad client side sends information about client side javscript errors args.app.post('/jserror', function(req, res) { - new formidable.IncomingForm().parse(req, function(err, fields, files) { + new formidable.IncomingForm().parse(req, function(err, fields, files) { try { var data = JSON.parse(fields.errorInfo) }catch(e){ @@ -64,7 +64,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { res.end("OK"); }); }); - + //Provide a possibility to query the latest available API version args.app.get('/api', function (req, res) { res.json({"currentVersion" : apiHandler.latestApiVersion}); diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.js index 5ebac1db0..a62942cc0 100644 --- a/src/node/hooks/express/importexport.js +++ b/src/node/hooks/express/importexport.js @@ -2,6 +2,7 @@ var hasPadAccess = require("../../padaccess"); var settings = require('../../utils/Settings'); var exportHandler = require('../../handler/ExportHandler'); var importHandler = require('../../handler/ImportHandler'); +var padManager = require("../../db/PadManager"); exports.expressCreateServer = function (hook_name, args, cb) { args.app.get('/p/:pad/:rev?/export/:type', function(req, res, next) { @@ -22,14 +23,29 @@ exports.expressCreateServer = function (hook_name, args, cb) { res.header("Access-Control-Allow-Origin", "*"); hasPadAccess(req, res, function() { - exportHandler.doExport(req, res, req.params.pad, req.params.type); + console.log('req.params.pad', req.params.pad); + padManager.doesPadExists(req.params.pad, function(err, exists) + { + if(!exists) { + return next(); + } + + exportHandler.doExport(req, res, req.params.pad, req.params.type); + }); }); }); //handle import requests args.app.post('/p/:pad/import', function(req, res, next) { hasPadAccess(req, res, function() { - importHandler.doImport(req, res, req.params.pad); + padManager.doesPadExists(req.params.pad, function(err, exists) + { + if(!exists) { + return next(); + } + + importHandler.doImport(req, res, req.params.pad); + }); }); }); } diff --git a/src/node/hooks/express/isValidJSONPName.js b/src/node/hooks/express/isValidJSONPName.js new file mode 100644 index 000000000..47755ef86 --- /dev/null +++ b/src/node/hooks/express/isValidJSONPName.js @@ -0,0 +1,83 @@ +const RESERVED_WORDS = [ + 'abstract', + 'arguments', + 'await', + 'boolean', + 'break', + 'byte', + 'case', + 'catch', + 'char', + 'class', + 'const', + 'continue', + 'debugger', + 'default', + 'delete', + 'do', + 'double', + 'else', + 'enum', + 'eval', + 'export', + 'extends', + 'false', + 'final', + 'finally', + 'float', + 'for', + 'function', + 'goto', + 'if', + 'implements', + 'import', + 'in', + 'instanceof', + 'int', + 'interface', + 'let', + 'long', + 'native', + 'new', + 'null', + 'package', + 'private', + 'protected', + 'public', + 'return', + 'short', + 'static', + 'super', + 'switch', + 'synchronized', + 'this', + 'throw', + 'throws', + 'transient', + 'true', + 'try', + 'typeof', + 'var', + 'void', + 'volatile', + 'while', + 'with', + 'yield' +]; + +const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/; + +module.exports.check = function(inputStr) { + var isValid = true; + inputStr.split(".").forEach(function(part) { + if (!regex.test(part)) { + isValid = false; + } + + if (RESERVED_WORDS.indexOf(part) !== -1) { + isValid = false; + } + }); + + return isValid; +} diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index e0b35831c..4cb4b9d3e 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -20,7 +20,7 @@ exports.basicAuth = function (req, res, next) { // Do not require auth for static paths and the API...this could be a bit brittle if (req.path.match(/^\/(static|javascripts|pluginfw|api)/)) return cb(true); - if (req.path.indexOf('/admin') != 0) { + if (req.path.toLowerCase().indexOf('/admin') != 0) { if (!settings.requireAuthentication) return cb(true); if (!settings.requireAuthorization && req.session && req.session.user) return cb(true); } @@ -38,7 +38,7 @@ exports.basicAuth = function (req, res, next) { var password = userpass.join(':'); var fallback = function(success) { if (success) return cb(true); - if (settings.users[username] != undefined && settings.users[username].password == password) { + if (settings.users[username] != undefined && settings.users[username].password === password) { settings.users[username].username = username; req.session.user = settings.users[username]; return cb(true); @@ -129,4 +129,3 @@ exports.expressConfigure = function (hook_name, args, cb) { args.app.use(exports.basicAuth); } - diff --git a/src/node/utils/ExportEtherpad.js b/src/node/utils/ExportEtherpad.js index 46ae0d7af..a68ab0b2a 100644 --- a/src/node/utils/ExportEtherpad.js +++ b/src/node/utils/ExportEtherpad.js @@ -22,25 +22,18 @@ var ERR = require("async-stacktrace"); exports.getPadRaw = function(padId, callback){ async.waterfall([ function(cb){ - - // Get the Pad - db.findKeys("pad:"+padId, null, function(err,padcontent){ - if(!err){ - cb(err, padcontent); - } - }) + db.get("pad:"+padId, cb); }, function(padcontent,cb){ + var records = ["pad:"+padId]; + for (var i = 0; i <= padcontent.head; i++) { + records.push("pad:"+padId+":revs:" + i); + } + + for (var i = 0; i <= padcontent.chatHead; i++) { + records.push("pad:"+padId+":chat:" + i); + } - // Get the Pad available content keys - db.findKeys("pad:"+padId+":*", null, function(err,records){ - if(!err){ - for (var key in padcontent) { records.push(padcontent[key]);} - cb(err, records); - } - }) - }, - function(records, cb){ var data = {}; async.forEachSeries(Object.keys(records), function(key, r){ @@ -69,7 +62,7 @@ exports.getPadRaw = function(padId, callback){ } r(null); // callback; }); - }, function(err){ + }, function(err){ cb(err, data); }) } diff --git a/src/package.json b/src/package.json index 189d8a7b7..bf2f8858c 100644 --- a/src/package.json +++ b/src/package.json @@ -24,7 +24,7 @@ "async" : "0.9.0", "clean-css" : "3.4.19", "uglify-js" : "2.6.2", - "formidable" : "1.0.17", + "formidable" : "1.2.1", "log4js" : "0.6.35", "cheerio" : "0.20.0", "async-stacktrace" : "0.0.2", @@ -42,13 +42,12 @@ "channels" : "0.0.4", "jsonminify" : "0.4.1", "measured" : "1.1.0", - "mocha" : "2.4.5", - "supertest" : "1.2.0", - "is-var-name" : "1.0.0" - }, + "mocha" : "5.0.5", + "supertest" : "3.0.0" + }, "bin": { "etherpad-lite": "./node/server.js" }, "devDependencies": { - "wd" : "0.3.11" + "wd" : "1.6.1" }, "engines" : { "node" : ">=0.10.0", "npm" : ">=1.0" @@ -56,6 +55,6 @@ "repository" : { "type" : "git", "url" : "http://github.com/ether/etherpad-lite.git" }, - "version" : "1.6.3", + "version" : "1.6.4", "license" : "Apache-2.0" } diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index 0d0fa1ed5..7cb4b1bdc 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -124,7 +124,7 @@ exports.getPackages = function (cb) { var tmp = {}; tmp[data.name] = data; - flatten(tmp[undefined].dependencies); + flatten(tmp[data.name].dependencies); cb(null, packages); }); };