CVE-2023-3086: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository nilsteampassnet/teampass prior to 3.0.9.
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: 9
Published: 2023-06-03T12:15Z
Repository: https://github.com/nilsteampassnet/teampass
Language: PHP
Files changed: includes/js/functions.js, pages/profile.js.php, sources/main.functions.php
Commit date: 2023-06-03 12:11:48 +0200
Commit message: 3.0.9

Fix xss in user profile form

Diff: diff --git a/includes/js/functions.js b/includes/js/functions.js
index 6f395f954e..c820cd7d3f 100755
--- a/includes/js/functions.js
+++ b/includes/js/functions.js
@@ -401,16 +401,31 @@ function fieldSanitizeStep1(
     if (field === undefined ||field === '') {
         return false;
     }
     let string = '';
     text = (text === '') ? $(field).val() : text;
-    
+/*
+    // Sanitize string
+    var tagsToReplace = {
+        '&': '&amp;',
+        '<': '&lt;',
+        '>': '&gt;',
+        "'" : '&#39;',
+        '"' : '&quot;'
+    };
+    text = text.replace(/[&<>'"]/g, function(tag) {
+        return tagsToReplace[tag] || tag;
+    });
+    */
     // Purify string
     string = DOMPurify.sanitize(
         text
             .replaceAll('&lt;', '<')
-            .replaceAll('&gt;', '>'),
+            .replaceAll('&gt;', '>')
+            .replaceAll('&amp;', '&')
+            .replaceAll('&quot;', '"')
+            .replaceAll('&#39;', "'"),
         {USE_PROFILES: {html:bHtml, svg:bSvg, svgFilters: bSvgFilters}}
     );
     
     // Clear field if string is empty and warn user
     if (string === '' && text !== '') {
diff --git a/pages/profile.js.php b/pages/profile.js.php
index 478ba30694..c846c2aa8c 100755
--- a/pages/profile.js.php
+++ b/pages/profile.js.php
@@ -162,20 +162,40 @@ if (checkUser($_SESSION['user_id'], $_SESSION['key'], 'profile', $SETTINGS) ===
     uploader_photo.init();
 
 
     // Save user settings
     $('#profile-user-save-settings').click(function() {
+        // Sanitize text fields
+        let formName = fieldSanitizeStep1('#profile-user-name', false, false, false),
+            formLastname = fieldSanitizeStep1('#profile-user-lastname', false, false, false),
+            formEmail = fieldSanitizeStep1('#profile-user-email', false, false, false);
+        if (formName === false || formLastname === false || formEmail === false) {
+            // Label is empty
+            toastr.remove();
+            toastr.warning(
+                'XSS attempt detected. Field has been emptied.',
+                'Error', {
+                    timeOut: 5000,
+                    progressBar: true
+                }
+            );
+            return false;
+        }
+
+        // Prepare data
         var data = {
-            'name': DOMPurify.sanitize($('#profile-user-name').val()),
-            'lastname': DOMPurify.sanitize($('#profile-user-lastname').val()),
-            'email': DOMPurify.sanitize($('#profile-user-email').val()),
+            'name': formName,
+            'lastname': formLastname,
+            'email': formEmail,
             'timezone': $('#profile-user-timezone').val(),
             'language': $('#profile-user-language').val().toLowerCase(),
             'treeloadstrategy': $('#profile-user-treeloadstrategy').val().toLowerCase(),
             'agsescardid': $('#profile-user-agsescardid').length > 0 ? $('#profile-user-agsescardid').val() : '',
         }
-        //console.log(data)
+        console.log(data);
+        //return false;
+        // " onmouseover="confirm(document.cookie)"
         // Inform user
         toastr.remove();
         toastr.info('<i class="fas fa-cog fa-spin fa-2x"></i>');
 
         //Send query
diff --git a/sources/main.functions.php b/sources/main.functions.php
index 24f90443f6..c650cf34d8 100755
--- a/sources/main.functions.php
+++ b/sources/main.functions.php
@@ -3537,14 +3537,20 @@ function dataSanitizer(
     require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Trim.php';
     require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/Cast.php';
     require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EscapeHTML.php';
     require_once $path . '/includes/libraries/Elegant/sanitizer/Filters/EmptyStringToNull.php';
     require_once $path . '/includes/libraries/Elegant/sanitizer/Sanitizer.php';
+    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
+
+    // Load AntiXSS
+    include_once $path. '/includes/libraries/anti-xss-master/src/voku/helper/ASCII.php';
+    include_once $path . '/includes/libraries/anti-xss-master/src/voku/helper/UTF8.php';
+    include_once $path . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
+    $antiXss = new voku\helper\AntiXSS();
 
     // Sanitize post and get variables
-    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
-    return $sanitizer->sanitize();
+    return $antiXss->xss_clean($sanitizer->sanitize());
 }
 
 /**
  * Permits to manage the cache tree for a user
  *

Vulnerable code:     
            .replaceAll('&gt;', '>'),
            'name': DOMPurify.sanitize($('#profile-user-name').val()),
            'lastname': DOMPurify.sanitize($('#profile-user-lastname').val()),
            'email': DOMPurify.sanitize($('#profile-user-email').val()),
        //console.log(data)
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);
    return $sanitizer->sanitize();
Fixed code: /*
    // Sanitize string
    var tagsToReplace = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'" : '&#39;',
        '"' : '&quot;'
    };
    text = text.replace(/[&<>'"]/g, function(tag) {
        return tagsToReplace[tag] || tag;
    });
    */
            .replaceAll('&gt;', '>')
            .replaceAll('&amp;', '&')
            .replaceAll('&quot;', '"')
            .replaceAll('&#39;', "'"),
        // Sanitize text fields
        let formName = fieldSanitizeStep1('#profile-user-name', false, false, false),
            formLastname = fieldSanitizeStep1('#profile-user-lastname', false, false, false),
            formEmail = fieldSanitizeStep1('#profile-user-email', false, false, false);
        if (formName === false || formLastname === false || formEmail === false) {
            // Label is empty
            toastr.remove();
            toastr.warning(
                'XSS attempt detected. Field has been emptied.',
                'Error', {
                    timeOut: 5000,
                    progressBar: true
                }
            );
            return false;
        }

        // Prepare data
            'name': formName,
            'lastname': formLastname,
            'email': formEmail,
        console.log(data);
        //return false;
        // " onmouseover="confirm(document.cookie)"
    $sanitizer = new Elegant\sanitizer\Sanitizer($data, $filters);

    // Load AntiXSS
    include_once $path. '/includes/libraries/anti-xss-master/src/voku/helper/ASCII.php';
    include_once $path . '/includes/libraries/anti-xss-master/src/voku/helper/UTF8.php';
    include_once $path . '/includes/libraries/anti-xss-master/src/voku/helper/AntiXSS.php';
    $antiXss = new voku\helper\AntiXSS();
    return $antiXss->xss_clean($sanitizer->sanitize());