CVE-2022-4966: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in sequentech admin-console up to 6.1.7 and classified as problematic. Affected by this issue is some unknown functionality of the component Election Description Handler. The manipulation leads to cross site scripting. The attack may be launched remotely. Upgrading to version 7.0.0-beta.1 is able to address this issue. The patch is identified as 0043a6b1e6e0f5abc9557e73f9ffc524fc5d609d. It is recommended to upgrade the affected component. VDB-258782 is the identifier assigned to this vulnerability.
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.
Published: 2024-04-01T12:15Z
Repository: https://github.com/sequentech/admin-console
Language: HTML
Files changed: avAdmin/admin-directives/create/create.html, avAdmin/admin-directives/create/create.js, avAdmin/admin-directives/elections/elections.html
Commit date: 2022-06-16 15:22:02 +0200
Commit message: Fix some XSS (#292) (#293)

There was an issue with election description. During election creation,
we relied in ng-bind-html to sanitize the visualization of election
description before being sanitized by the backend (because it has not
yet been sent to the backend), but apparently it doesn't work for some
xss. The same issue happened in election list when showing the
description of the drafti election.

With this change, we always show the election description as plain text
so that we don't need to sanitize it. Also see related issue with
htmlToText in common-ui: https://github.com/sequentech/common-ui/pull/221
because sometimes we also called to our own htmlToText angular filter,
and sanitization was also needed to happen there.

Another fix is to use $sanitize more pervasively in the election
creation screen, so that any reply from the server is also not trusted
and properly sanitized.
Diff: diff --git a/avAdmin/admin-directives/create/create.html b/avAdmin/admin-directives/create/create.html
index 4f2b86f..c14c05c 100644
--- a/avAdmin/admin-directives/create/create.html
+++ b/avAdmin/admin-directives/create/create.html
@@ -7,11 +7,11 @@
     <div ng-repeat="election in elections">
       <h3 ng-i18next>[i18next]({'title': election.title, 'index': $index+1})avAdmin.basic.title.labelH3</h3>
       <table class="table table-bordered table-striped">
           <tr>
               <th ng-i18next> avAdmin.basic.description.label </th>
-              <td ng-bind-html="election.description | addTargetBlank"></td>
+              <td ng-bind="election.description | htmlToText | truncate:150"></td>
           </tr>
           <tr>
               <th ng-i18next> avAdmin.sidebar.questions </th>
               <td>
                   <ol>
diff --git a/avAdmin/admin-directives/create/create.js b/avAdmin/admin-directives/create/create.js
index 6e07850..6800029 100644
--- a/avAdmin/admin-directives/create/create.js
+++ b/avAdmin/admin-directives/create/create.js
@@ -24,10 +24,11 @@ angular.module('avAdmin')
       Authmethod,
       DraftElection,
       ElectionsApi,
       $state,
       $stateParams,
+      $sanitize,
       $i18next,
       $filter,
       $modal,
       ConfigService,
       ElectionLimits,
@@ -67,15 +68,15 @@ angular.module('avAdmin')
           scope.elections = ElectionsApi.currentElections;
           ElectionsApi.currentElections = [];
         }
 
         function logInfo(text) {
-          scope.log += "<p>" + text + "</p>";
+          scope.log += "<p>" + $sanitize(text) + "</p>";
         }
 
         function logError(text) {
-          scope.log += "<p class=\"text-brand-danger\">" + text + "</p>";
+          scope.log += "<p class=\"text-brand-danger\">" + $sanitize(text) + "</p>";
         }
         function validateEmail(email) {
           var re = /^[^\s@]+@[^\s@.]+\.[^\s@.]+$/;
           return re.test(email);
         }
@@ -1076,11 +1077,11 @@ angular.module('avAdmin')
 
             var data = {
               election: el,
               error: function (errorMsg) {
                   scope.errors.push({
-                    data: {message: errorMsg},
+                    data: {message: $sanitize($sanitize)(errorMsg)},
                     key: "election-census-createel-unknown"
                   });
                 },
               disableOk: false,
               cancel: function (error) {
@@ -1314,11 +1315,11 @@ angular.module('avAdmin')
                 CheckerService({
                   checks: checks,
                   data: scope.elections,
                   onError: function (errorKey, errorData) {
                     scope.errors.push({
-                      data: errorData,
+                      data: $sanitize(errorData),
                       key: errorKey
                     });
                   }
                 });
               }
diff --git a/avAdmin/admin-directives/elections/elections.html b/avAdmin/admin-directives/elections/elections.html
index da7f22d..fe58db0 100644
--- a/avAdmin/admin-directives/elections/elections.html
+++ b/avAdmin/admin-directives/elections/elections.html
@@ -82,11 +82,11 @@
             </td>
             <td class="td-7">
               <a ng-click="loadDraft()">
                 <strong>{{ draft.title }}</strong>
                 <div
-                  ng-bind-html="draft.description | htmlToText | truncate:150">
+                  ng-bind="draft.description | htmlToText | truncate:150">
                 </div>
               </a>
             </td>
             <td class="text-center td-2">
             </td>
@@ -120,11 +120,11 @@
             </td>
             <td class="main td-7">
               <a ui-sref="admin.dashboard({id: election.id})">
                 <strong>{{ election.title }}</strong>
                 <div
-                  ng-bind-html="election.description | htmlToText | truncate:150">
+                  ng-bind="election.description | htmlToText | truncate:150">
                 </div>
               </a>
               <!-- children elections-->
               <table 
               role="table" 
@@ -140,11 +140,11 @@
                     </td>
                     <td class="td-6">
                       <a ui-sref="admin.dashboard({id: childElection.id})">
                         <strong>{{ childElection.title }}</strong>
                         <div
-                          ng-bind-html="childElection.description | htmlToText | truncate:150">
+                          ng-bind="childElection.description | htmlToText | truncate:150">
                         </div>
                       </a>
                     </td>
                     <td class="text-center td-2">
                       <a ui-sref="admin.dashboard({id: childElection.id})">

Vulnerable code:               <td ng-bind-html="election.description | addTargetBlank"></td>
          scope.log += "<p>" + text + "</p>";
          scope.log += "<p class=\"text-brand-danger\">" + text + "</p>";
                    data: {message: errorMsg},
                      data: errorData,
                  ng-bind-html="draft.description | htmlToText | truncate:150">
                  ng-bind-html="election.description | htmlToText | truncate:150">
                          ng-bind-html="childElection.description | htmlToText | truncate:150">
Fixed code:               <td ng-bind="election.description | htmlToText | truncate:150"></td>
      $sanitize,
          scope.log += "<p>" + $sanitize(text) + "</p>";
          scope.log += "<p class=\"text-brand-danger\">" + $sanitize(text) + "</p>";
                    data: {message: $sanitize($sanitize)(errorMsg)},
                      data: $sanitize(errorData),
                  ng-bind="draft.description | htmlToText | truncate:150">
                  ng-bind="election.description | htmlToText | truncate:150">
                          ng-bind="childElection.description | htmlToText | truncate:150">