diff --git a/frontend/express/app.js b/frontend/express/app.js index 2389ebb7f66..142368d7b2f 100644 --- a/frontend/express/app.js +++ b/frontend/express/app.js @@ -75,6 +75,10 @@ var versionInfo = require('./version.info'), { validateCreate } = require('../../api/utils/rights.js'), tracker = require('../../api/parts/mgmt/tracker.js'); +//Language codes as the dashboard uses them, e.g. "en", "pt-br", "zh_CN". Anything +//else is refused rather than sanitized, so a bad value never reaches storage. +var LANG_CODE_RE = /^[A-Za-z]{2,3}(?:[-_][A-Za-z0-9]{2,8})?$/; + console.log("Starting Countly", "version", versionInfo.version, "package", pack.version); var COUNTLY_NAMED_TYPE = "Countly Lite v" + COUNTLY_VERSION; @@ -1849,8 +1853,12 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ var updatedUser = {}; - if (req.body.lang) { - updatedUser.lang = req.body.lang; + //The stored lang reaches file paths in several places, for example + //api/utils/localization.js and the plugin localization lookups, so keep it to a + //language code here rather than relying on every reader to sanitize it. The + //dashboard only ever sends codes from its own localization list. + if (req.body.lang && LANG_CODE_RE.test(req.body.lang + "")) { + updatedUser.lang = req.body.lang + ""; countlyDb.collection('members').update({"_id": countlyDb.ObjectID(req.session.uid + "")}, {'$set': updatedUser}, {safe: true}, function(err, member) { if (member && !err) { diff --git a/plugins/plugins/api/api.js b/plugins/plugins/api/api.js index 02165726e8a..90fb7095094 100644 --- a/plugins/plugins/api/api.js +++ b/plugins/plugins/api/api.js @@ -152,7 +152,8 @@ var plugin = {}, if (!resultObj.enabled) { var local_path = fullpath + "/frontend/public/localization/" + resultObj.code + ".properties"; if (params.member.lang && params.member.lang !== "en") { - local_path = fullpath + "/frontend/public/localization/" + resultObj.code + "_" + params.member.lang + ".properties"; + //sanitized the same way api/utils/localization.js does it + local_path = fullpath + "/frontend/public/localization/" + resultObj.code + "_" + common.sanitizeFilename(params.member.lang) + ".properties"; } if (fs.existsSync(local_path)) { var local_properties = fs.readFileSync(local_path); @@ -379,7 +380,8 @@ var plugin = {}, var fullpath = path.resolve(__dirname, "../"); var local_path = fullpath + "/frontend/public/localization/plugins.properties"; if (params.member.lang && params.member.lang !== "en") { - path = fullpath + "/frontend/public/localization/plugins" + "_" + params.member.lang + ".properties"; + //sanitized the same way api/utils/localization.js does it + path = fullpath + "/frontend/public/localization/plugins" + "_" + common.sanitizeFilename(params.member.lang) + ".properties"; if (fs.existsSync(path)) { local_path = path; }