CVE-2019-12313 (JavaScript, published 2019-05-24T14:29Z)
Description: value: XSS exists in Shave before 2.5.3 because output encoding is mishandled during the overwrite of an HTML element.
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/dollarshaveclub/shave
Files changed:
dist/jquery.shave.min.js
dist/shave.es.js
dist/shave.js
dist/shave.min.js
package.json
src/shave.js
Commit message: [version] patch; @digitalcraft createTextNode() 🚀 (#146)



Vulnerable code:
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t():"function"==typeof define&&define.amd?define(t):t()}(0,function(){"use strict";if("undefined"!=typeof window){var e=window.$||window.jQuery||window.Zepto;e&&(e.fn.shave=function(e,t){return function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};if(!t)throw Error("maxHeight is required");var i="string"==typeof e?document.querySelectorAll(e):e;if(i){var o=n.character||"…",a=n.classname||"js-shave",s="boolean"!=typeof n.spaces||n.spaces,r='<span class="js-shave-char">'.concat(o,"</span>");"length"in i||(i=[i]);for(var c=0;c<i.length;c+=1){var f=i[c],h=f.style,l=f.querySelector(".".concat(a)),d=void 0===f.textContent?"innerText":"textContent";l&&(f.removeChild(f.querySelector(".js-shave-char")),f[d]=f[d]);var v=f[d],g=s?v.split(" "):v;if(!(g.length<2)){var u=h.height;h.height="auto";var p=h.maxHeight;if(h.maxHeight="none",f.offsetHeight<=t)h.height=u,h.maxHeight=p;else{for(var y=g.length-1,j=0,m=void 0;j<y;)m=j+y+1>>1,f[d]=s?g.slice(0,m).join(" "):g.slice(0,m),f.insertAdjacentHTML("beforeend",r),f.offsetHeight>t?y=s?m-1:m-2:j=m;f[d]=s?g.slice(0,y).join(" "):g.slice(0,y),f.insertAdjacentHTML("beforeend",r);var H=s?" ".concat(g.slice(y).join(" ")):g.slice(y);f.insertAdjacentHTML("beforeend",'<span class="'.concat(a,'" style="display:none;">').concat(H,"</span>")),h.height=u,h.maxHeight=p}}}}}(this,e,t),this})}});
  @version v2.5.2
    el.insertAdjacentHTML('beforeend', "<span class=\"".concat(classname, "\" style=\"display:none;\">").concat(diff, "</span>"));
  @version v2.5.2
      el.insertAdjacentHTML('beforeend', "<span class=\"".concat(classname, "\" style=\"display:none;\">").concat(diff, "</span>"));
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.shave=t()}(this,function(){"use strict";return function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};if(!t)throw Error("maxHeight is required");var i="string"==typeof e?document.querySelectorAll(e):e;if(i){var o=n.character||"…",a=n.classname||"js-shave",s="boolean"!=typeof n.spaces||n.spaces,r='<span class="js-shave-char">'.concat(o,"</span>");"length"in i||(i=[i]);for(var c=0;c<i.length;c+=1){var h=i[c],l=h.style,f=h.querySelector(".".concat(a)),d=void 0===h.textContent?"innerText":"textContent";f&&(h.removeChild(h.querySelector(".js-shave-char")),h[d]=h[d]);var v=h[d],g=s?v.split(" "):v;if(!(g.length<2)){var p=l.height;l.height="auto";var u=l.maxHeight;if(l.maxHeight="none",h.offsetHeight<=t)l.height=p,l.maxHeight=u;else{for(var m=g.length-1,y=0,j=void 0;y<m;)j=y+m+1>>1,h[d]=s?g.slice(0,j).join(" "):g.slice(0,j),h.insertAdjacentHTML("beforeend",r),h.offsetHeight>t?m=s?j-1:j-2:y=j;h[d]=s?g.slice(0,m).join(" "):g.slice(0,m),h.insertAdjacentHTML("beforeend",r);var x=s?" ".concat(g.slice(m).join(" ")):g.slice(m);h.insertAdjacentHTML("beforeend",'<span class="'.concat(a,'" style="display:none;">').concat(x,"</span>")),l.height=p,l.maxHeight=u}}}}}});
  "version": "2.5.2",
    // https://stackoverflow.com/questions/476821/is-a-dom-text-node-guaranteed-to-not-be-interpreted-as-html

Fixed code:
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t():"function"==typeof define&&define.amd?define(t):t()}(0,function(){"use strict";if("undefined"!=typeof window){var e=window.$||window.jQuery||window.Zepto;e&&(e.fn.shave=function(e,t){return function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};if(!t)throw Error("maxHeight is required");var i="string"==typeof e?document.querySelectorAll(e):e;if(i){var o=n.character||"…",a=n.classname||"js-shave",r="boolean"!=typeof n.spaces||n.spaces,s='<span class="js-shave-char">'.concat(o,"</span>");"length"in i||(i=[i]);for(var c=0;c<i.length;c+=1){var d=i[c],h=d.style,f=d.querySelector(".".concat(a)),l=void 0===d.textContent?"innerText":"textContent";f&&(d.removeChild(d.querySelector(".js-shave-char")),d[l]=d[l]);var u=d[l],v=r?u.split(" "):u;if(!(v.length<2)){var g=h.height;h.height="auto";var p=h.maxHeight;if(h.maxHeight="none",d.offsetHeight<=t)h.height=g,h.maxHeight=p;else{for(var m=v.length-1,y=0,j=void 0;y<m;)j=y+m+1>>1,d[l]=r?v.slice(0,j).join(" "):v.slice(0,j),d.insertAdjacentHTML("beforeend",s),d.offsetHeight>t?m=r?j-1:j-2:y=j;d[l]=r?v.slice(0,m).join(" "):v.slice(0,m),d.insertAdjacentHTML("beforeend",s);var x=r?" ".concat(v.slice(m).join(" ")):v.slice(m),w=document.createTextNode(x),H=document.createElement("span");H.classList.add(a),H.style.display="none",H.appendChild(w),d.insertAdjacentElement("beforeend",H),h.height=g,h.maxHeight=p}}}}}(this,e,t),this})}});
  @version v2.5.3
    var shavedText = document.createTextNode(diff);
    var elWithShavedText = document.createElement('span');
    elWithShavedText.classList.add(classname);
    elWithShavedText.style.display = 'none';
    elWithShavedText.appendChild(shavedText);
    el.insertAdjacentElement('beforeend', elWithShavedText);
  @version v2.5.3
      var shavedText = document.createTextNode(diff);
      var elWithShavedText = document.createElement('span');
      elWithShavedText.classList.add(classname);
      elWithShavedText.style.display = 'none';
      elWithShavedText.appendChild(shavedText);
      el.insertAdjacentElement('beforeend', elWithShavedText);
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.shave=t()}(this,function(){"use strict";return function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};if(!t)throw Error("maxHeight is required");var i="string"==typeof e?document.querySelectorAll(e):e;if(i){var o=n.character||"…",a=n.classname||"js-shave",r="boolean"!=typeof n.spaces||n.spaces,s='<span class="js-shave-char">'.concat(o,"</span>");"length"in i||(i=[i]);for(var c=0;c<i.length;c+=1){var h=i[c],l=h.style,d=h.querySelector(".".concat(a)),f=void 0===h.textContent?"innerText":"textContent";d&&(h.removeChild(h.querySelector(".js-shave-char")),h[f]=h[f]);var v=h[f],g=r?v.split(" "):v;if(!(g.length<2)){var u=l.height;l.height="auto";var p=l.maxHeight;if(l.maxHeight="none",h.offsetHeight<=t)l.height=u,l.maxHeight=p;else{for(var m=g.length-1,x=0,y=void 0;x<m;)y=x+m+1>>1,h[f]=r?g.slice(0,y).join(" "):g.slice(0,y),h.insertAdjacentHTML("beforeend",s),h.offsetHeight>t?m=r?y-1:y-2:x=y;h[f]=r?g.slice(0,m).join(" "):g.slice(0,m),h.insertAdjacentHTML("beforeend",s);var j=r?" ".concat(g.slice(m).join(" ")):g.slice(m),H=document.createTextNode(j),b=document.createElement("span");b.classList.add(a),b.style.display="none",b.appendChild(H),h.insertAdjacentElement("beforeend",b),l.height=u,l.maxHeight=p}}}}}});
  "version": "2.5.3",