Aerosol Posted March 10, 2015 Report Posted March 10, 2015 Ruben Ventura (@tr3w_) found a pretty cool bypass of MentalJS. He used insertBefore with a null second argument which allows you to insert a node into the dom and bypass my sandboxing restrictions. The vector is below:-_=documentx =_.createElement('script');s =_.createElement('style')s.innerHTML = '*/alert(location)//'t=_.createElement('b')t.textContent = '/*'x.insertBefore(t.firstChild, null);x.insertBefore(s, null)_.body.appendChild(x)x =_.createElement('script');s =_.createElement('style')s.innerHTML = _.getElementsByTagName('script')[2].textContentx.insertBefore(s.firstChild, null)_.body.appendChild(x)It can actually be compressed to the following:s=document.createElement('script');s.insertBefore(document.createTextNode('alert(location)'),null);document.body.appendChild(s);The fix was to check if the second argument is null and the parent node is a script. Clean the script and then sandbox the code. Hopefully that will fix the attack, I couldn’t see a way to use insertBefore without a null argument to cause another bypass.@@ -5621,7 +5621,7 @@ } }; - exports.version = "0.1.15";+ exports.version = "0.1.16"; exports.parse = function(){ var js = MentalJS(); }; @@ -5873,9 +5873,7 @@ if(this.tagName && this.tagName.toUpperCase() == 'SCRIPT') { while(this.firstChild) { this.removeChild(this.firstChild);- }- } - if(this.tagName && this.tagName.toUpperCase() === 'SCRIPT') { + } js = MentalJS(); code = document.createTextNode(js.parse({options:{eval:false},code:node.textContent})); script = document.createElement('script'); @@ -5895,7 +5893,18 @@ 'lastChild$': {configurable:true, get:function(){return this.lastChild;}}, 'nextSibling$': {configurable:true, get:function(){return this.nextSibling;}}, 'parentNode$': {configurable:true, get:function(){return this.parentNode;}},- 'insertBefore$': {configurable:true, writable:false, value:function(){return this.insertBefore.apply(this, arguments);}}, + 'insertBefore$': {configurable:true, writable:false, value:function(newElement, referenceElement){+ var js, script;+ if(this.tagName && this.tagName.toUpperCase() == 'SCRIPT' && referenceElement === null) {+ while(this.firstChild) {+ this.removeChild(this.firstChild);+ } + js = MentalJS();+ code = document.createTextNode(js.parse({options:{eval:false},code:newElement.textContent})); + return this.insertBefore(code, null); + } + return this.insertBefore.apply(this, arguments);}+ }, 'cloneNode$': {configurable:true, writable:false, value:function(){return this.cloneNode.apply(this, arguments);}}, 'removeChild$': {configurable:true, writable:false, value:function(){return this.removeChild.apply(this, arguments);}}, 'removeAttribute$': {configurable:true, writable:false, value:function(name){ this.removeAttribute(name); }}, @@ -6175,7 +6184,8 @@ Object.defineProperties(HTMLStyleElement.prototype, { 'innerText$': {configurable:true, get:function(){return this.innerText;},set:function(innerText){ this.innerText = innerText; }}, 'textContent$': {configurable:true, get:function(){return this.textContent;},set:function(textContent){this.textContent=textConent;}},- 'text$': {configurable:true, get:function(){return this.text;},set:function(text){ this.text=text; }} + 'text$': {configurable:true, get:function(){return this.text;},set:function(text){ this.text=text; }},+ 'innerHTML$': {configurable:true, get:function(){return this.innerHTML;},set:function(){ }} }); Object.defineProperties(document, {Source Quote