skip to Main Content

I have a big legacy web app that needs to be made CSP (content security policy) compliant. It is full of inline event handlers, eg

onclick="alert(‘hello’)"

I am trying to write some js which will scan the page on load and replace all these inline handlers with dynamically assigned handlers, eg

var oc = $("#btn").attr("onclick");

$("#btn").on("click", function(){oc});

This obviously wont work, I don’t know what each inline event is ahead of time, so it needs to be added as a string. Trouble is there is no way of doing this I can find which is CSP compliant. I tried replace all the real inline handlers with pseudo events, eg onclick becomes zclick , then extracting these plsurdo handlers and creating real dynamic handlers:

var oc = $("#btn").attr("zclick");
var func = Function(oc);
btn.on("click", func);

but CSP caught this as "unsafe eval". I tried discovering how angular does it, as in how it converts ng-click to real onclick events but it seems to extreme low level custom compiling or some such. Does any one have any ideas as to how I may achieve this?

Thanks!

2

Answers


  1. Since you say we can’t use eval() we can instead create a string of JS with all our event listeners and dynamically load it as script…

    const Events=[
     'abort','afterprint','animationcancel','animationend','animationiteration','animationstart','audioprocess','auxclick','beforeprint','beforeunload','blur','broadcast','canplay','canplaythrough','change',
     'CheckboxStateChange','checking','click','close','complete','compositionend','compositionstart','compositionupdate','contextmenu','copy','cut','dblclick','downloading','drag','dragend','dragenter',
     'dragleave','dragover','dragstart','drop','durationchange','emptied','ended','error','focus','focusin','focusout','fullscreenchange','fullscreenerror','hashchange','input','invalid','keydown',
     'keypress','keyup','load','loadeddata','loadedmetadata','loadend','loadstart','message','mousedown','mouseenter','mouseleave','mousemove','mouseout','mouseover','mouseup','noupdate','obsolete',
     'offline','online','open','pagehide','pageshow','paste','pause','play','playing','pointerlockchange','pointerlockerror','popstate','progress','RadioStateChange','ratechange','readystatechange','reset',
     'resize','scroll','seeked','seeking','select','show','stalled','storage','submit','suspend','timeout','timeupdate','transitioncancel','transitionend','transitionrun','transitionstart','unhandledrejection',
     'unload','updateready','ValueChange','volumechange','waiting','wheel'];
    
    
    var E=document.querySelectorAll('*'), A='', Z='', S='';
    
    
    for(let x=0; x<E.length; x++){
    
      // In the next line we need to create an ID if not found, using a random number
    
     if(E[x].hasAttribute('id')===false){E[x].id='e'+RandomNum(99999);} 
    
     for(let y=0; y<Events.length; y++){
    
      A='on'+Events[y];
    
      if(E[x].hasAttribute(A)){
    
       Z=E[x].getAttribute(A);
    
       E[x].removeAttribute(A);
    
       S+=E[x].id.toString()+'.addEventListener(''+A.slice(2)+'',function(e){'+Z.replace(/'/g,'"')+'},false);';
    
    }}}
    
    
    var N=document.createElement('script');
    
    N.type='text/javascript';
    
    // N.onload=function(){alert('Loaded!');}; // We don't actually need this
    
    N.text=S;
    
    document.getElementsByTagName('HEAD')[0].appendChild(N);
    

    Notes:

    1: Random number generating function not supplied.

    2: The whole thing is totally untested code but it does compile.

    3: An interesting challenge, thanks for the opportunity to exercise my brain.

    Login or Signup to reply.
  2. Firstly, I agree 100% that this job should be automated so keep persevering in this direction.

    However, eval() would be useless (actually catastrophic) because it doesn’t convert strings to code… it only EXECUTES strings as code, so you’d have tons of code suddenly firing away like Chinese firecrackers! 🙂

    But anyway, we don’t need eval() for this job… we can always assemble a string of JS (lots of event listeners basically in string format) and load it dynamically as script as the code below does.

    In the worst case scenario, if the loading was to fail we can still extract the string of all the event listeners (by saving it in a textarea) to save us from all that typing in the event that it has to be done manually… so keep going it’s a win-win!

    Alright, so here’s an updated version that eliminates the need for a random number function and moves the ID creation line further down where it should be…

    const Events=[
     'abort','afterprint','animationcancel','animationend','animationiteration','animationstart','audioprocess','auxclick','beforeprint','beforeunload','blur','broadcast','canplay','canplaythrough','change',
     'CheckboxStateChange','checking','click','close','complete','compositionend','compositionstart','compositionupdate','contextmenu','copy','cut','dblclick','downloading','drag','dragend','dragenter',
     'dragleave','dragover','dragstart','drop','durationchange','emptied','ended','error','focus','focusin','focusout','fullscreenchange','fullscreenerror','hashchange','input','invalid','keydown',
     'keypress','keyup','load','loadeddata','loadedmetadata','loadend','loadstart','message','mousedown','mouseenter','mouseleave','mousemove','mouseout','mouseover','mouseup','noupdate','obsolete',
     'offline','online','open','pagehide','pageshow','paste','pause','play','playing','pointerlockchange','pointerlockerror','popstate','progress','RadioStateChange','ratechange','readystatechange','reset',
     'resize','scroll','seeked','seeking','select','show','stalled','storage','submit','suspend','timeout','timeupdate','transitioncancel','transitionend','transitionrun','transitionstart','unhandledrejection',
     'unload','updateready','ValueChange','volumechange','waiting','wheel'];
    
    
    var E=document.querySelectorAll('*'), A='', Z='', S='';
    
    
    for(let x=0; x<E.length; x++){
    
     for(let y=0; y<Events.length; y++){
    
      A='on'+Events[y];
    
      if(E[x].hasAttribute(A)){
    
       if(E[x].hasAttribute('id')===false){E[x].id='e'+(x*y);} 
    
       Z=E[x].getAttribute(A);
    
       E[x].removeAttribute(A);
    
       S+=E[x].id.toString()+'.addEventListener(''+A.slice(2)+'',function(e){'+Z.replace(/'/g,'"')+'},false);';
    
    }}}
    
    
    var N=document.createElement('script');
    
    N.type='text/javascript'; N.text=S;
    
    document.head.appendChild(N);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search