CVE-2023-32685: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Kanboard is project management software that focuses on the Kanban methodology. Due to improper handling of elements under the `contentEditable` element, maliciously crafted clipboard content can inject arbitrary HTML tags into the DOM. A low-privileged attacker with permission to attach a document on a vulnerable Kanboard instance can trick the victim into pasting malicious screenshot data and achieve cross-site scripting if CSP is improperly configured. This issue has been patched in version 1.2.29.

CWE description: 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.
CVSS v3 base score: 5.4
Published: 2023-05-30T05:15Z
Repository: https://github.com/kanboard/kanboard
Language: JavaScript
Files changed: assets/js/app.min.js, assets/js/components/screenshot.js
Commit date: 2023-05-18 19:55:47 -0700
Commit message: Remove content editable element

Non longer necessary. It works on Safari, Chrome and Firefox

Diff: diff --git a/assets/js/app.min.js b/assets/js/app.min.js
index 7cd6254a0a..e1b56e2f81 100644
--- a/assets/js/app.min.js
+++ b/assets/js/app.min.js
@@ -161,21 +161,15 @@ function getAriaLabelValue(){if(options.ariaLabel){return options.ariaLabel}
 return''}
 function buildComponentElement(){var roles=[];var container=KB.dom('div');for(var role in options.roles){if(options.roles.hasOwnProperty(role)){var item={value:role,text:options.roles[role]};if(options.role===role){item.selected='selected'}
 roles.push(item)}}
 container.add(KB.dom('select').attr('aria-label',getAriaLabelValue()).change(onChange).for('option',roles).build());if(isLoading){container.text(' ');container.add(KB.dom('i').attr('class','fa fa-spinner fa-pulse fa-fw').build())}else if(isSuccess){container.text(' ');container.add(KB.dom('i').attr('class','fa fa-check fa-fw icon-fade-out icon-success').build())}else if(isError){container.text(' ');container.add(KB.dom('i').attr('class','fa fa-check fa-fw icon-fade-out icon-error').build())}
 return container.build()}
-this.render=function(){componentElement=buildComponentElement();containerElement.appendChild(componentElement)}});KB.component('screenshot',function(containerElement){var pasteCatcher=null;var inputElement=null;function onFileLoaded(e){createImage(e.target.result)}
-function onPaste(e){if(e.clipboardData&&e.clipboardData.items){var items=e.clipboardData.items;if(items){for(var i=0;i<items.length;i++){if(items[i].type.indexOf("image")!==-1){var blob=items[i].getAsFile();var reader=new FileReader();reader.onload=onFileLoaded;reader.readAsDataURL(blob)}}}}else{setTimeout(checkInput,100)}}
-function initialize(){destroy();if(!window.Clipboard){pasteCatcher=document.createElement('template');pasteCatcher.id='screenshot-pastezone';pasteCatcher.contentEditable=!0;pasteCatcher.style.opacity=0;pasteCatcher.style.position='fixed';pasteCatcher.style.top=0;pasteCatcher.style.right=0;pasteCatcher.style.width=0;document.body.insertBefore(pasteCatcher,document.body.firstChild);pasteCatcher.focus();document.addEventListener('click',setFocus);document.getElementById('screenshot-zone').addEventListener('click',setFocus)}
-window.addEventListener('paste',onPaste,!1)}
-function destroy(){if(KB.exists('#screenshot-pastezone')){KB.find('#screenshot-pastezone').remove()}
-document.removeEventListener('click',setFocus);pasteCatcher=null}
-function setFocus(){if(pasteCatcher!==null){pasteCatcher.focus()}}
-function checkInput(){var child=pasteCatcher.childNodes[0];if(child){if(child.tagName==='IMG'){createImage(child.src)}}
-pasteCatcher.innerHTML=''}
-function createImage(blob){var pastedImage=new Image();pastedImage.src=blob;pastedImage.onload=function(){var sourceSplit=blob.split('base64,');inputElement.value=sourceSplit[1]};var zone=document.getElementById('screenshot-zone');zone.innerHTML='';zone.className='screenshot-pasted';zone.appendChild(pastedImage);destroy();initialize()}
-KB.on('modal.close',function(){destroy()});this.render=function(){inputElement=KB.dom('input').attr('type','hidden').attr('name','screenshot').build();containerElement.appendChild(inputElement);initialize()}});KB.component('select-dropdown-autocomplete',function(containerElement,options){var componentElement,inputElement,inputHiddenElement,chevronIconElement,loadingIconElement;function onLoadingStart(){KB.dom(loadingIconElement).show();KB.dom(chevronIconElement).hide()}
+this.render=function(){componentElement=buildComponentElement();containerElement.appendChild(componentElement)}});KB.component('screenshot',function(containerElement){var inputElement=null;function onFileLoaded(e){createImage(e.target.result)}
+function onPaste(e){if(e.clipboardData&&e.clipboardData.items){var items=e.clipboardData.items;if(items){for(var i=0;i<items.length;i++){if(items[i].type.indexOf("image")!==-1){var blob=items[i].getAsFile();var reader=new FileReader();reader.onload=onFileLoaded;reader.readAsDataURL(blob)}}}}}
+function initialize(){window.addEventListener('paste',onPaste,!1)}
+function createImage(blob){var pastedImage=new Image();pastedImage.src=blob;pastedImage.onload=function(){var sourceSplit=blob.split('base64,');inputElement.value=sourceSplit[1]};var zone=document.getElementById('screenshot-zone');zone.innerHTML='';zone.className='screenshot-pasted';zone.appendChild(pastedImage);initialize()}
+this.render=function(){inputElement=KB.dom('input').attr('type','hidden').attr('name','screenshot').build();containerElement.appendChild(inputElement);initialize()}});KB.component('select-dropdown-autocomplete',function(containerElement,options){var componentElement,inputElement,inputHiddenElement,chevronIconElement,loadingIconElement;function onLoadingStart(){KB.dom(loadingIconElement).show();KB.dom(chevronIconElement).hide()}
 function onLoadingStop(){KB.dom(loadingIconElement).hide();KB.dom(chevronIconElement).show()}
 function onScroll(){var menuElement=KB.find('#select-dropdown-menu');if(menuElement){var componentPosition=componentElement.getBoundingClientRect();menuElement.style('top',(document.body.scrollTop+componentPosition.bottom)+'px')}}
 function onKeyDown(e){switch(KB.utils.getKey(e)){case 'Escape':inputElement.value='';destroyDropdownMenu();break;case 'ArrowUp':e.preventDefault();e.stopImmediatePropagation();moveUp();break;case 'ArrowDown':e.preventDefault();e.stopImmediatePropagation();moveDown();break;case 'Enter':e.preventDefault();e.stopImmediatePropagation();insertSelectedItem();break}}
 function onInputChanged(){destroyDropdownMenu();renderDropdownMenu()}
 function onItemMouseOver(element){if(KB.dom(element).hasClass('select-dropdown-menu-item')){KB.find('.select-dropdown-menu-item.active').removeClass('active');KB.dom(element).addClass('active')}}
diff --git a/assets/js/components/screenshot.js b/assets/js/components/screenshot.js
index 97cd086c6d..1130bd22ca 100644
--- a/assets/js/components/screenshot.js
+++ b/assets/js/components/screenshot.js
@@ -1,15 +1,13 @@
 KB.component('screenshot', function (containerElement) {
-    var pasteCatcher = null;
     var inputElement = null;
 
     function onFileLoaded(e) {
         createImage(e.target.result);
     }
 
     function onPaste(e) {
-        // Firefox doesn't have the property e.clipboardData.items (only Chrome)
         if (e.clipboardData && e.clipboardData.items) {
             var items = e.clipboardData.items;
 
             if (items) {
                 for (var i = 0; i < items.length; i++) {
@@ -22,73 +20,17 @@ KB.component('screenshot', function (containerElement) {
                         reader.onload = onFileLoaded;
                         reader.readAsDataURL(blob);
                     }
                 }
             }
-        } else {
-
-            // Handle Firefox
-            setTimeout(checkInput, 100);
         }
     }
 
     function initialize() {
-        destroy();
-
-        if (! window.Clipboard) {
-            // Insert the content editable at the top to avoid scrolling down in the board view
-            pasteCatcher = document.createElement('template');
-            pasteCatcher.id = 'screenshot-pastezone';
-            pasteCatcher.contentEditable = true;
-            pasteCatcher.style.opacity = 0;
-            pasteCatcher.style.position = 'fixed';
-            pasteCatcher.style.top = 0;
-            pasteCatcher.style.right = 0;
-            pasteCatcher.style.width = 0;
-            document.body.insertBefore(pasteCatcher, document.body.firstChild);
-
-            pasteCatcher.focus();
-
-            // Set the focus when clicked anywhere in the document
-            document.addEventListener('click', setFocus);
-
-            // Set the focus when clicked in screenshot dropzone
-            document.getElementById('screenshot-zone').addEventListener('click', setFocus);
-        }
-
         window.addEventListener('paste', onPaste, false);
     }
 
-    function destroy() {
-        if (KB.exists('#screenshot-pastezone')) {
-            KB.find('#screenshot-pastezone').remove();
-        }
-
-        document.removeEventListener('click', setFocus);
-        pasteCatcher = null;
-    }
-    
-    function setFocus() {
-        if (pasteCatcher !== null) {
-            pasteCatcher.focus();
-        }
-    }
-    
-    function checkInput() {
-        var child = pasteCatcher.childNodes[0];
-
-        if (child) {
-            // If the user pastes an image, the src attribute
-            // will represent the image as a base64 encoded string.
-            if (child.tagName === 'IMG') {
-                createImage(child.src);
-            }
-        }
-
-        pasteCatcher.innerHTML = '';
-    }
-    
     function createImage(blob) {
         var pastedImage = new Image();
         pastedImage.src = blob;
 
         // Send the image content to the form variable
@@ -100,18 +42,13 @@ KB.component('screenshot', function (containerElement) {
         var zone = document.getElementById('screenshot-zone');
         zone.innerHTML = '';
         zone.className = 'screenshot-pasted';
         zone.appendChild(pastedImage);
 
-        destroy();
         initialize();
     }
 
-    KB.on('modal.close', function () {
-        destroy();
-    });
-
     this.render = function () {
         inputElement = KB.dom('input')
             .attr('type', 'hidden')
             .attr('name', 'screenshot')
             .build();

Vulnerable code: this.render=function(){componentElement=buildComponentElement();containerElement.appendChild(componentElement)}});KB.component('screenshot',function(containerElement){var pasteCatcher=null;var inputElement=null;function onFileLoaded(e){createImage(e.target.result)}
function onPaste(e){if(e.clipboardData&&e.clipboardData.items){var items=e.clipboardData.items;if(items){for(var i=0;i<items.length;i++){if(items[i].type.indexOf("image")!==-1){var blob=items[i].getAsFile();var reader=new FileReader();reader.onload=onFileLoaded;reader.readAsDataURL(blob)}}}}else{setTimeout(checkInput,100)}}
function initialize(){destroy();if(!window.Clipboard){pasteCatcher=document.createElement('template');pasteCatcher.id='screenshot-pastezone';pasteCatcher.contentEditable=!0;pasteCatcher.style.opacity=0;pasteCatcher.style.position='fixed';pasteCatcher.style.top=0;pasteCatcher.style.right=0;pasteCatcher.style.width=0;document.body.insertBefore(pasteCatcher,document.body.firstChild);pasteCatcher.focus();document.addEventListener('click',setFocus);document.getElementById('screenshot-zone').addEventListener('click',setFocus)}
window.addEventListener('paste',onPaste,!1)}
function destroy(){if(KB.exists('#screenshot-pastezone')){KB.find('#screenshot-pastezone').remove()}
document.removeEventListener('click',setFocus);pasteCatcher=null}
function setFocus(){if(pasteCatcher!==null){pasteCatcher.focus()}}
function checkInput(){var child=pasteCatcher.childNodes[0];if(child){if(child.tagName==='IMG'){createImage(child.src)}}
pasteCatcher.innerHTML=''}
function createImage(blob){var pastedImage=new Image();pastedImage.src=blob;pastedImage.onload=function(){var sourceSplit=blob.split('base64,');inputElement.value=sourceSplit[1]};var zone=document.getElementById('screenshot-zone');zone.innerHTML='';zone.className='screenshot-pasted';zone.appendChild(pastedImage);destroy();initialize()}
KB.on('modal.close',function(){destroy()});this.render=function(){inputElement=KB.dom('input').attr('type','hidden').attr('name','screenshot').build();containerElement.appendChild(inputElement);initialize()}});KB.component('select-dropdown-autocomplete',function(containerElement,options){var componentElement,inputElement,inputHiddenElement,chevronIconElement,loadingIconElement;function onLoadingStart(){KB.dom(loadingIconElement).show();KB.dom(chevronIconElement).hide()}
    var pasteCatcher = null;
        // Firefox doesn't have the property e.clipboardData.items (only Chrome)
        } else {

            // Handle Firefox
            setTimeout(checkInput, 100);
        destroy();

        if (! window.Clipboard) {
            // Insert the content editable at the top to avoid scrolling down in the board view
            pasteCatcher = document.createElement('template');
            pasteCatcher.id = 'screenshot-pastezone';
            pasteCatcher.contentEditable = true;
            pasteCatcher.style.opacity = 0;
            pasteCatcher.style.position = 'fixed';
            pasteCatcher.style.top = 0;
            pasteCatcher.style.right = 0;
            pasteCatcher.style.width = 0;
            document.body.insertBefore(pasteCatcher, document.body.firstChild);

            pasteCatcher.focus();

            // Set the focus when clicked anywhere in the document
            document.addEventListener('click', setFocus);

            // Set the focus when clicked in screenshot dropzone
            document.getElementById('screenshot-zone').addEventListener('click', setFocus);
        }

    function destroy() {
        if (KB.exists('#screenshot-pastezone')) {
            KB.find('#screenshot-pastezone').remove();
        }

        document.removeEventListener('click', setFocus);
        pasteCatcher = null;
    }
    
    function setFocus() {
        if (pasteCatcher !== null) {
            pasteCatcher.focus();
        }
    }
    
    function checkInput() {
        var child = pasteCatcher.childNodes[0];

        if (child) {
            // If the user pastes an image, the src attribute
            // will represent the image as a base64 encoded string.
            if (child.tagName === 'IMG') {
                createImage(child.src);
            }
        }

        pasteCatcher.innerHTML = '';
    }
    
        destroy();
    KB.on('modal.close', function () {
        destroy();
    });

Fixed code: this.render=function(){componentElement=buildComponentElement();containerElement.appendChild(componentElement)}});KB.component('screenshot',function(containerElement){var inputElement=null;function onFileLoaded(e){createImage(e.target.result)}
function onPaste(e){if(e.clipboardData&&e.clipboardData.items){var items=e.clipboardData.items;if(items){for(var i=0;i<items.length;i++){if(items[i].type.indexOf("image")!==-1){var blob=items[i].getAsFile();var reader=new FileReader();reader.onload=onFileLoaded;reader.readAsDataURL(blob)}}}}}
function initialize(){window.addEventListener('paste',onPaste,!1)}
function createImage(blob){var pastedImage=new Image();pastedImage.src=blob;pastedImage.onload=function(){var sourceSplit=blob.split('base64,');inputElement.value=sourceSplit[1]};var zone=document.getElementById('screenshot-zone');zone.innerHTML='';zone.className='screenshot-pasted';zone.appendChild(pastedImage);initialize()}
this.render=function(){inputElement=KB.dom('input').attr('type','hidden').attr('name','screenshot').build();containerElement.appendChild(inputElement);initialize()}});KB.component('select-dropdown-autocomplete',function(containerElement,options){var componentElement,inputElement,inputHiddenElement,chevronIconElement,loadingIconElement;function onLoadingStart(){KB.dom(loadingIconElement).show();KB.dom(chevronIconElement).hide()}