CVE-2023-37901: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Indico is an open source a general-purpose, web based event management tool. There is a Cross-Site-Scripting vulnerability in confirmation prompts commonly used when deleting content from Indico. Exploitation requires someone with at least submission privileges (such as a speaker) and then someone else to attempt to delete this content. Considering that event organizers may want to delete suspicious-looking content when spotting it, there is a non-negligible risk of such an attack to succeed. The risk of this could be further increased when combined with some some social engineering pointing the victim towards this content. Users need to update to Indico 3.2.6 as soon as possible. See the docs for instructions on how to update. Users who cannot upgrade should only let trustworthy users manage categories, create events or upload materials ("submission" privileges on a contribution/event). This should already be the case in a properly-configured setup when it comes to category/event management. Note that a conference doing a Call for Abstracts actively invites external speakers (who the organizers may not know and thus cannot fully trust) to submit content, hence the need to update to a a fixed version ASAP in particular when using such workflows.
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-07-21T19:15Z
Repository: https://github.com/indico/indico
Language: HTML
Files changed: CHANGES.rst, indico/modules/attachments/templates/_attachments.html, indico/modules/categories/templates/management/base.html, indico/modules/events/abstracts/templates/reviewing/public.html, indico/modules/events/layout/templates/_menu.html, indico/modules/events/management/templates/_action_menu.html, indico/modules/events/static/templates/static_sites.html, indico/modules/events/surveys/templates/management/survey.html, indico/modules/oauth/templates/user_apps.html, indico/modules/oauth/templates/user_tokens.html, indico/modules/users/templates/personal_data.html, indico/web/client/js/jquery/utils/misc.js
Commit date: 2023-07-20 15:18:50 +0200
Commit message: Fix XSS in confirmation prompts

Also update some confirmation prompts to not use HTML in the message as
this is no longer supported.

Diff: diff --git a/CHANGES.rst b/CHANGES.rst
index a057109338..224c625104 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,10 +5,21 @@ Changelog
 Version 3.2.6
 -------------
 
 *Unreleased*
 
+Security fixes
+^^^^^^^^^^^^^^
+
+- Fix an XSS vulnerability in various confirmation prompts commonly used when deleting
+  things. Exploitation requires someone with at least submission privileges (such as a
+  speaker) and then rely on someone else to attempt to delete this content. However,
+  considering that event organizers may indeed delete suspicious-looking content when
+  encountering it, there is a non-negligible risk of such an attack to succeed. Because
+  of this it is strongly recommended to upgrade as soon as possible (:pr:`5862`,
+  :cve:`CVE-2023-37901`)
+
 Internationalization
 ^^^^^^^^^^^^^^^^^^^^
 
 - New translation: Czech
 
diff --git a/indico/modules/attachments/templates/_attachments.html b/indico/modules/attachments/templates/_attachments.html
index e30e12af40..2392d36a93 100644
--- a/indico/modules/attachments/templates/_attachments.html
+++ b/indico/modules/attachments/templates/_attachments.html
@@ -52,11 +52,11 @@
                    data-method="DELETE"
                    data-href="{{ url_for('attachments.delete_attachment', attachment) }}"
                    data-title="{% trans name=attachment.title %}Remove {{ name }}?{% endtrans %}"
                    title="{% trans %}Remove{% endtrans %}"
                    data-confirm="{% trans name=attachment.title -%}
-                       Are you sure you want to remove &quot;{{ name }}&quot;?<br>
+                       Are you sure you want to remove &quot;{{ name }}&quot;?
                        This will remove the attachment permanently.
                        {%- endtrans %}">
                 </a>
             </td>
             <td class="date">{{ attachment.modified_dt|format_datetime('short') }}</td>
@@ -92,11 +92,11 @@
                    data-method="DELETE"
                    data-href="{{ url_for('attachments.delete_folder', folder) }}"
                    data-title="{% trans name=folder.title %}Remove {{ name }}?{% endtrans %}"
                    title="{% trans %}Remove{% endtrans %}"
                    data-confirm="{% trans name=folder.title -%}
-                       Are you sure you want to remove &quot;{{ name }}&quot;?<br>
+                       Are you sure you want to remove &quot;{{ name }}&quot;?
                        This will remove the folder and its contents permanently.
                    {%- endtrans %}">
                 </a>
             </td>
             <td class="date"></td>
diff --git a/indico/modules/categories/templates/management/base.html b/indico/modules/categories/templates/management/base.html
index 1d1d4c42a3..886c8451a1 100644
--- a/indico/modules/categories/templates/management/base.html
+++ b/indico/modules/categories/templates/management/base.html
@@ -1,10 +1,12 @@
 {% extends 'layout/management_page.html' %}
 
 {% macro delete_category_button(category, classes="i-button") %}
     {% set confirmation_message -%}
-        {% trans category=category.title %}Do you really want to delete the category "{{ category }}"?{% endtrans %}
+        {% trans category=category.title %}
+            Do you really want to delete the category &quot;{{ category }}&quot;?
+        {% endtrans %}
     {%- endset %}
     {% if category.is_empty %}
         {% set tooltip -%}
             {% trans %}Delete category{% endtrans %}
         {%- endset %}
@@ -14,11 +16,11 @@
             {% trans %}This category cannot be deleted because it is not empty.{% endtrans %}
         {%- endset %}
     {% endif %}
     <a class="{{ classes }} icon-remove js-delete-category"
        title="{{ tooltip|forceescape }}"
-       data-confirm="{{ confirmation_message|forceescape }}"
+       data-confirm="{{ confirmation_message }}"
        data-title="{% trans %}Delete category{% endtrans %}"
        data-href="{{ url_for('categories.delete', category) }}"
        data-method="POST"></a>
 {% endmacro %}
 
diff --git a/indico/modules/events/abstracts/templates/reviewing/public.html b/indico/modules/events/abstracts/templates/reviewing/public.html
index 45cb07c4bd..1abbeafb4b 100644
--- a/indico/modules/events/abstracts/templates/reviewing/public.html
+++ b/indico/modules/events/abstracts/templates/reviewing/public.html
@@ -17,16 +17,14 @@
                 <div class="group">
                     {% if abstract.can_withdraw(session.user, check_state=true) %}
                         {% set message %}
                             {% trans %}Do you really want to withdraw this abstract?{% endtrans %}
                             {% if abstract.contribution %}
-                                <br>
                                 {%- trans -%}
                                     Withdrawing this abstract will permanently delete its corresponding contribution.
                                 {%- endtrans -%}
                             {% elif not can_manage %}
-                                <br>
                                 {% trans %}This operation is irreversible.{% endtrans %}
                             {% endif %}
                         {% endset %}
                         <button class="i-button text-color color-on-hover danger hide-if-locked"
                                 title="{% trans %}Withdraw abstract{% endtrans %}"
diff --git a/indico/modules/events/layout/templates/_menu.html b/indico/modules/events/layout/templates/_menu.html
index db8c0f51f6..e7f4642c8d 100644
--- a/indico/modules/events/layout/templates/_menu.html
+++ b/indico/modules/events/layout/templates/_menu.html
@@ -35,11 +35,11 @@
                          data-href="{{ url_for('.menu_delete_entry', entry) }}"
                          data-title="{% trans %}Remove the menu entry?{% endtrans %}"
                          title="{% trans %}Remove the menu entry{% endtrans %}"
                          data-confirm="
                             {%- if not entry.is_separator -%}
-                                {%- trans name='&quot;%s&quot;'|format(entry.localized_title) -%}
+                                {%- trans name='"%s"'|format(entry.localized_title) -%}
                                     Are you sure you want to remove {{ name }}?
                                 {%- endtrans -%}
                             {% else %}
                                 {%- trans %}Are you sure you want to remove this spacer?{% endtrans -%}
                             {% endif %}">
diff --git a/indico/modules/events/management/templates/_action_menu.html b/indico/modules/events/management/templates/_action_menu.html
index 4dcf1d1ed9..ca73cf7d67 100644
--- a/indico/modules/events/management/templates/_action_menu.html
+++ b/indico/modules/events/management/templates/_action_menu.html
@@ -1,8 +1,8 @@
 {% macro _change_type_confirmation(type) %}
     {% trans -%}
-        Changing the event type to <strong>{{ type }}</strong> will change the look of the event page
+        Changing the event type to {{ type }} will change the look of the event page
         and may disable some features not available for the new event type.
     {%- endtrans %}
 {% endmacro %}
 
 
diff --git a/indico/modules/events/static/templates/static_sites.html b/indico/modules/events/static/templates/static_sites.html
index 39e79e82f6..53047bd744 100644
--- a/indico/modules/events/static/templates/static_sites.html
+++ b/indico/modules/events/static/templates/static_sites.html
@@ -9,11 +9,11 @@
                 <div class="i-box-title">{%- trans %}New Offline Copy{% endtrans -%}</div>
                 <button class="i-button icon-wrench right" type="button"
                         data-method="post"
                         data-href="{{ url_for('static_site.build', event) }}"
                         data-title="{% trans %}Build an offline copy{% endtrans %}"
-                        data-confirm="{% trans %}Are you sure you want to build an offline copy of this event?<br>Beware that it is a heavy operation, especially for large events, and it might take some time to finish.{% endtrans %}">
+                        data-confirm="{% trans %}Are you sure you want to build an offline copy of this event? Beware that it is a heavy operation, especially for large events, and it might take some time to finish.{% endtrans %}">
                     {%- trans %}Build offline copy{% endtrans -%}
                 </button>
             </div>
             <div class="i-box-content">
                 {% trans %}This will build an offline copy of the event. This is useful if you want to make it available where no Internet connection is available.{% endtrans %}
diff --git a/indico/modules/events/surveys/templates/management/survey.html b/indico/modules/events/surveys/templates/management/survey.html
index f0cb93dfb1..0dc49612a4 100644
--- a/indico/modules/events/surveys/templates/management/survey.html
+++ b/indico/modules/events/surveys/templates/management/survey.html
@@ -192,12 +192,14 @@
         {% endif %}
         <td>
             {% set data_confirm %}
                 {% trans %}Are you sure you want to remove this submission?{% endtrans %}
                 {% if submission.is_anonymous %}
-                    <br><br>
-                    {% trans %}Note: Authenticated users will not be able to submit an anonymous survey again even if you delete their submission.{% endtrans %}
+                    {% trans %}
+                        Note: Authenticated users will not be able to submit an anonymous survey again
+                        even if you delete their submission.
+                    {% endtrans %}
                 {% endif %}
             {% endset %}
             <a href="#" class="icon-remove right js-delete-submission hide-if-locked"
                data-href="{{ url_for('.delete_submissions', survey) }}"
                data-method="DELETE"
diff --git a/indico/modules/oauth/templates/user_apps.html b/indico/modules/oauth/templates/user_apps.html
index 017210a66c..5b9441d953 100644
--- a/indico/modules/oauth/templates/user_apps.html
+++ b/indico/modules/oauth/templates/user_apps.html
@@ -34,11 +34,11 @@
                                 </span>
                             </span>
                             <button class="ui negative small button"
                                     data-href="{{ url_for('.user_app_revoke', auth.application) }}"
                                     data-method="POST"
-                                    data-confirm="{% trans app_name=auth.application.name %}{{ app_name }} will no longer have access to your Indico data.<br>
+                                    data-confirm="{% trans app_name=auth.application.name %}{{ app_name }} will no longer have access to your Indico data.
                                     Are you sure you want to revoke its access?{% endtrans %}">
                                 {%- trans %}Revoke{% endtrans -%}
                             </button>
                         </span>
                     </li>
diff --git a/indico/modules/oauth/templates/user_tokens.html b/indico/modules/oauth/templates/user_tokens.html
index 22b70755fb..5e16bfb0b7 100644
--- a/indico/modules/oauth/templates/user_tokens.html
+++ b/indico/modules/oauth/templates/user_tokens.html
@@ -107,17 +107,14 @@
                                             Any applications or scripts using this token will no longer be able to access
                                             the Indico API.
                                             You cannot undo this action.
                                             {% endtrans %}
                                             {% if not can_manage %}
-                                                <br><br>
-                                                <strong>
                                                 {% trans %}
                                                 Warning: You will not be able to create a new API token since only Indico
                                                 admins can create API tokens.
                                                 {% endtrans %}
-                                                </strong>
                                             {% endif %}
                                             ">
                                         <i aria-hidden="true" class="trash icon"></i>
                                     </button>
                                 </div>
diff --git a/indico/modules/users/templates/personal_data.html b/indico/modules/users/templates/personal_data.html
index da396806a7..3f5742247a 100644
--- a/indico/modules/users/templates/personal_data.html
+++ b/indico/modules/users/templates/personal_data.html
@@ -12,22 +12,22 @@
                         {% if not user.is_blocked %}
                             <div {%- if user == session.user %} title="{% trans %}You cannot block yourself{% endtrans %}"{% endif %}>
                                 <button class="ui negative small button"
                                         data-href="{{ url_for('.user_block') }}"
                                         data-method="PUT"
-                                        data-confirm="{% trans %}This user will no longer have access to Indico.<br>
+                                        data-confirm="{% trans %}This user will no longer have access to Indico.
                                         Are you sure you want to revoke their access?{% endtrans %}"
                                         data-reload-after
                                         {% if user == session.user %}disabled{% endif %}>
                                     {% trans %}Block User{% endtrans %}
                                 </button>
                             </div>
                         {% else %}
                             <button class="ui small button"
                                     data-href="{{ url_for('.user_block') }}"
                                     data-method="DELETE"
-                                    data-confirm="{% trans %}This user will regain access to Indico.<br>
+                                    data-confirm="{% trans %}This user will regain access to Indico.
                                     Are you sure you want to restore their access?{% endtrans %}"
                                     data-reload-after>
                                 {% trans %}Unblock User{% endtrans %}
                             </button>
                         {% endif %}
diff --git a/indico/web/client/js/jquery/utils/misc.js b/indico/web/client/js/jquery/utils/misc.js
index 57293e9cc8..573928dafe 100644
--- a/indico/web/client/js/jquery/utils/misc.js
+++ b/indico/web/client/js/jquery/utils/misc.js
@@ -10,11 +10,11 @@
 import Palette from '../../utils/palette';
 
 (function(global) {
   global.confirmPrompt = function confirmPrompt(message, title) {
     var dfd = $.Deferred();
-    message = $('<div>', {width: 400, html: message});
+    message = $('<div>', {width: 400, text: message});
     new ConfirmPopup(title || $T('Please confirm'), message, function(confirmed) {
       if (confirmed) {
         dfd.resolve();
       } else {
         dfd.reject();

Vulnerable code:                        Are you sure you want to remove &quot;{{ name }}&quot;?<br>
                       Are you sure you want to remove &quot;{{ name }}&quot;?<br>
        {% trans category=category.title %}Do you really want to delete the category "{{ category }}"?{% endtrans %}
       data-confirm="{{ confirmation_message|forceescape }}"
                                <br>
                                <br>
                                {%- trans name='&quot;%s&quot;'|format(entry.localized_title) -%}
        Changing the event type to <strong>{{ type }}</strong> will change the look of the event page
                        data-confirm="{% trans %}Are you sure you want to build an offline copy of this event?<br>Beware that it is a heavy operation, especially for large events, and it might take some time to finish.{% endtrans %}">
                    <br><br>
                    {% trans %}Note: Authenticated users will not be able to submit an anonymous survey again even if you delete their submission.{% endtrans %}
                                    data-confirm="{% trans app_name=auth.application.name %}{{ app_name }} will no longer have access to your Indico data.<br>
                                                <br><br>
                                                <strong>
                                                </strong>
                                        data-confirm="{% trans %}This user will no longer have access to Indico.<br>
                                    data-confirm="{% trans %}This user will regain access to Indico.<br>
    message = $('<div>', {width: 400, html: message});
Fixed code: Security fixes
^^^^^^^^^^^^^^

- Fix an XSS vulnerability in various confirmation prompts commonly used when deleting
  things. Exploitation requires someone with at least submission privileges (such as a
  speaker) and then rely on someone else to attempt to delete this content. However,
  considering that event organizers may indeed delete suspicious-looking content when
  encountering it, there is a non-negligible risk of such an attack to succeed. Because
  of this it is strongly recommended to upgrade as soon as possible (:pr:`5862`,
  :cve:`CVE-2023-37901`)

                       Are you sure you want to remove &quot;{{ name }}&quot;?
                       Are you sure you want to remove &quot;{{ name }}&quot;?
        {% trans category=category.title %}
            Do you really want to delete the category &quot;{{ category }}&quot;?
        {% endtrans %}
       data-confirm="{{ confirmation_message }}"
                                {%- trans name='"%s"'|format(entry.localized_title) -%}
        Changing the event type to {{ type }} will change the look of the event page
                        data-confirm="{% trans %}Are you sure you want to build an offline copy of this event? Beware that it is a heavy operation, especially for large events, and it might take some time to finish.{% endtrans %}">
                    {% trans %}
                        Note: Authenticated users will not be able to submit an anonymous survey again
                        even if you delete their submission.
                    {% endtrans %}
                                    data-confirm="{% trans app_name=auth.application.name %}{{ app_name }} will no longer have access to your Indico data.
                                        data-confirm="{% trans %}This user will no longer have access to Indico.
                                    data-confirm="{% trans %}This user will regain access to Indico.
    message = $('<div>', {width: 400, text: message});