Bug#1010383: bullseye-pu: package node-ejs/2.5.7-3+deb11u1

2022-05-28 Thread Adam D. Barratt
Control: tags -1 + confirmed

On Sat, 2022-04-30 at 09:11 +0200, Yadd wrote:
> node-ejs is vulnerable to server-side template injection
> (CVE-2022-29078, #1010359) and probably to prototype pollution.
> 

Please go ahead.

Regards,

Adam



Bug#1010383: bullseye-pu: package node-ejs/2.5.7-3+deb11u1

2022-04-30 Thread Yadd
Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian@packages.debian.org
Usertags: pu

[ Reason ]
node-ejs is vulnerable to server-side template injection
(CVE-2022-29078, #1010359) and probably to prototype pollution.

[ Impact ]
Medium security issue

[ Tests ]
New test added, confirms that issue is fixed

[ Risks ]
Low risk, code is trivial

[ Checklist ]
  [X] *all* changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in (old)stable
  [X] the issue is verified as fixed in unstable

[ Changes ]
 * Replace {} by `new Object`
 * check localsName value

Cheers,
Yadd
diff --git a/debian/changelog b/debian/changelog
index a40e2ed..1409436 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+node-ejs (2.5.7-3+deb11u1) bullseye; urgency=medium
+
+  * Team upload
+  * Sanitize options and new objects (Closes: #1010359, CVE-2022-29078)
+
+ -- Yadd   Sat, 30 Apr 2022 06:59:25 +0200
+
 node-ejs (2.5.7-3) unstable; urgency=medium
 
   * Team upload
diff --git a/debian/patches/CVE-2022-29078.patch 
b/debian/patches/CVE-2022-29078.patch
new file mode 100644
index 000..cd413ae
--- /dev/null
+++ b/debian/patches/CVE-2022-29078.patch
@@ -0,0 +1,174 @@
+Description: sanitize localsName option and fix prototype pollution
+ This patch fixes CVE-2022-29078 but I also apply prototype pollution fixes,
+ even if there are no CVE associated with it
+Author: Nicolas Dumazet 
+Origin: upstream, https://github.com/mde/ejs/commit/15ee6985
+Bug: https://eslam.io/posts/ejs-server-side-template-injection-rce/
+Bug-Debian: https://bugs.debian.org/1010359
+Forwarded: not-needed
+Reviewed-By: Yadd 
+Last-Update: 2022-04-30
+
+--- a/lib/ejs.js
 b/lib/ejs.js
+@@ -61,6 +61,7 @@
+ // so we make an exception for `renderFile`
+ var _OPTS_EXPRESS = _OPTS.concat('cache');
+ var _BOM = /^\uFEFF/;
++var _JS_IDENTIFIER = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
+ 
+ /**
+  * EJS template function cache. This can be a LRU object from lru-cache NPM
+@@ -254,7 +255,7 @@
+  */
+ 
+ function includeFile(path, options) {
+-  var opts = utils.shallowCopy({}, options);
++  var opts = utils.shallowCopy(utils.createNullProtoObjWherePossible(), 
options);
+   opts.filename = getIncludePath(path, opts);
+   return handleCache(opts);
+ }
+@@ -270,7 +271,7 @@
+  */
+ 
+ function includeSource(path, options) {
+-  var opts = utils.shallowCopy({}, options);
++  var opts = utils.shallowCopy(utils.createNullProtoObjWherePossible(), 
options);
+   var includePath;
+   var template;
+   includePath = getIncludePath(path, opts);
+@@ -372,8 +373,8 @@
+  */
+ 
+ exports.render = function (template, d, o) {
+-  var data = d || {};
+-  var opts = o || {};
++  var data = d || utils.createNullProtoObjWherePossible();
++  var opts = o || utils.createNullProtoObjWherePossible();
+ 
+   // No options object -- if there are optiony names
+   // in the data, copy them to options
+@@ -431,7 +432,7 @@
+ opts.filename = filename;
+   }
+   else {
+-data = {};
++data = utils.createNullProtoObjWherePossible();
+   }
+ 
+   return tryHandleCache(opts, data, cb);
+@@ -447,8 +448,8 @@
+ };
+ 
+ function Template(text, opts) {
+-  opts = opts || {};
+-  var options = {};
++  opts = opts || utils.createNullProtoObjWherePossible();
++  var options = utils.createNullProtoObjWherePossible();
+   this.templateText = text;
+   this.mode = null;
+   this.truncate = false;
+@@ -466,6 +467,9 @@
+   options.cache = opts.cache || false;
+   options.rmWhitespace = opts.rmWhitespace;
+   options.root = opts.root;
++  if (opts.localsName && !_JS_IDENTIFIER.test(opts.localsName)) {
++throw new Error('localsName is not a valid JS identifier.');
++  }
+   options.localsName = opts.localsName || exports.localsName || 
_DEFAULT_LOCALS_NAME;
+   options.views = opts.views;
+ 
+@@ -571,13 +575,13 @@
+ // Adds a local `include` function which allows full recursive include
+ var returnedFn = function (data) {
+   var include = function (path, includeData) {
+-var d = utils.shallowCopy({}, data);
++var d = utils.shallowCopy(utils.createNullProtoObjWherePossible(), 
data);
+ if (includeData) {
+   d = utils.shallowCopy(d, includeData);
+ }
+ return includeFile(path, opts)(d);
+   };
+-  return fn.apply(opts.context, [data || {}, escapeFn, include, rethrow]);
++  return fn.apply(opts.context, [data || 
utils.createNullProtoObjWherePossible(), escapeFn, include, rethrow]);
+ };
+ returnedFn.dependencies = this.dependencies;
+ return returnedFn;
+--- a/lib/utils.js
 b/lib/utils.js
+@@ -114,8 +114,10 @@
+  */
+ exports.shallowCopy = function (to, from) {
+   from = from || {};
+-  for (var p in from) {
+-to[p] = from[p];
++  if ((to !== null) && (to !== undefined)) {
++for (var p in from) {
++  to[p] = from[p];
++}
+   }
+   return to;
+ };
+@@ -133,12 +135,16 @@
+  *