CVE-2021-41132 (JavaScript, published 2021-10-14T16:15Z)
Description: value: OMERO.web provides a web based client and plugin infrastructure. In versions prior to 5.11.0, a variety of templates do not perform proper sanitization through HTML escaping. Due to the lack of sanitization and use of ``jQuery.html()``, there are a whole host of cross-site scripting possibilities with specially crafted input to a variety of fields. This issue is patched in version 5.11.0. There are no known workarounds aside from upgrading.
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
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.
Repository: https://github.com/ome/omero-web
Files changed:
CHANGELOG.md
omeroweb/webadmin/templates/webadmin/includes/drivespaceStats.js
omeroweb/webclient/static/webclient/javascript/ome.chgrp.js
omeroweb/webclient/static/webclient/javascript/ome.chown.js
omeroweb/webclient/static/webclient/javascript/ome.right_panel_comments_pane.js
omeroweb/webclient/static/webclient/javascript/ome.right_panel_customanns_pane.js
omeroweb/webclient/static/webclient/javascript/ome.right_panel_fileanns_pane.js
omeroweb/webclient/static/webclient/javascript/ome.right_panel_tags_pane.js
omeroweb/webclient/static/webclient/javascript/ome.tagging_form.js
omeroweb/webclient/static/webclient/javascript/ome.thumbnail_figure.js
omeroweb/webclient/templates/webclient/annotations/comments_underscore.html
omeroweb/webclient/templates/webclient/annotations/customanns_underscore.html
omeroweb/webclient/templates/webclient/annotations/fileanns_underscore.html
omeroweb/webclient/templates/webclient/annotations/mapanns_underscore.html
omeroweb/webclient/templates/webclient/annotations/metadata_general.html
omeroweb/webclient/templates/webclient/annotations/tags_underscore.html
omeroweb/webclient/templates/webclient/data/icon_thumbnails_underscore.html
omeroweb/webclient/templates/webclient/data/includes/center_plugin.thumbs.js.html
omeroweb/webclient/templates/webclient/data/plate.html
omeroweb/webclient/templates/webclient/scripts/upload_script.html
omeroweb/webclient/views.py
omeroweb/webgateway/static/webgateway/js/ome.popup.js
omeroweb/webgateway/static/webgateway/js/ome.roidisplay.js
omeroweb/webgateway/static/webgateway/js/ome.spwgridview.js
omeroweb/webgateway/static/webgateway/js/omero_image.js
omeroweb/webgateway/templates/webgateway/viewport/omero_image.html
Commit message: Fix issues with inconsistency in input sanitisation leading to XSS vectors


Vulnerable code:
                    tableRows.push('<tr><td class="link">' + rowData.label + '(id:'+ rowId +')</td>');
            var headerTxt = "<p>Move data owned by " + ownernames.join(", ") + ".</p>" +
                html += g.name + "<hr></div>";
                namesList.push(u.name);
            <input name='<%= obj.split("=")[0] %>' value='<%= obj.split("=")[1] %>' hidden/>
                    <%= exp.FirstName%> <%= exp.LastName %> 
                    ann.textValue = _.escape(ann.textValue);
                            ann.value = _.escape(ann[a]);
                    ann.description = _.escape(ann.description);
                    tag.textValue = _.escape(tag.textValue);
                    tag.description = _.escape(tag.description);
                            summary[tagId] = {'textValue': tag.textValue,
                html += create_tag_html(tag.t, tag.d, owners[tag.o], tag.i,
                                        null, tag.s !== 0);
                                child.t, child.d, owners[child.o], child.i,
                                tag.i);
        $(".tag_selection div").on('click', tag_click).tooltip({
            track: false,
            show: false,
            hide: false,
            items: '[data-content]',
            content: function() {
                return $(this).data('content');
            }
        });
                tagset.text() + "</span> tag set and select it immediately:");
    var encode_html = function(text) {
        return $('<div/>').text(text).html();
    };

            title += "<b>Owner:</b> " + owner + "<br />";
            title += "<b>Linked by:</b> " + link_owner + "<br />";
            title += "<b>Description:</b> " + description + "<br />";
            title += "<b>Tag set:</b> " + tagset + "<br />";
    var create_tag_html = function(text, description, owner, id, parent_id,
        var tagset;
            tagset = all_tags[parent_id].t;
            html += " data-description='" + encode_html(description) + "'";
        var title = create_tag_title(description, owner, tagset);
        // Add content even if it is empty
        // and handle case for no tooltip in the tooltip code
        html += " data-content='" + title.replace(/'/g, "&#39;") + "'";
        html += ">" + encode_html(text) + "</div>";
            owners[me] = owners[me] || my_name;
                text, description, my_name, new_tag_counter,
                items: '[data-content]',
                content: function() {
                    return $(this).data('content');
                }
                        $tr = $('<tr><th><h2>' + topLevelTag + '</h2></th></tr>');
});
<div class="ann_comment_wrapper" data-added-by="<% print (ann.addedBy.join(',')) %>">
        <img src="<%= webindex %>avatar/<%= ann.owner.id %>/"
        alt="<%= ann.owner.firstName %> <%= ann.owner.lastName %>"
        title="<%= ann.owner.firstName %> <%= ann.owner.lastName %>" />
                <%= ann.owner.firstName %> <%= ann.owner.lastName %>
            </strong> 
    
            <img class='removeComment' id="<%= ann.id %>-comment"
                url='<%= webindex %>action/remove/comment/<%= ann.id %>/' 
        <div class='commentText'><%= ann.textValue %></div>
        <b>ID:</b> <%= ann.id %><br />
        <% if (ann.ns) { %><b>Namespace:</b> <%= ann.ns %><br /><% } %>
        <% if (ann.description) { %><b>Description:</b> <%= ann.description %><br /><% } %>
<tr data-added-by="<% print (ann.addedBy.join(',')) %>">
    <th><%= ann.type %></th>
            <div><% print((ann.value + "").slice(0, 20)) %>...</div>
            <div style="display: none"><%= ann.value %></div>
            <div><%= ann.value %></div>
        
            <b>ID:</b> <%= ann.id %><br />
            <% if (ann.ns) { %><b>Namespace:</b> <%= ann.ns %><br /><% } %>
            <% if (ann.description) { %><b>Description:</b> <%= ann.description %><br /><% } %>
            <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %><br />
            <b>Linked to:</b> <%= ann.parent.class %> <%= ann.parent.id %>
<% }) %>
    id="file_ann-<%= ann.id %>"
    data-added-by="<% print (ann.addedBy.join(',')) %>">
        <%= ann.file.name %>
            <span>(<%= ann.file.size %>)</span>
            Can remove File from <b><%= ann.canRemoveCount %>
                        <b><%= link.parent.class %> <%= link.parent.id %></b>
            <b>Annotation ID:</b> <%= ann.id %><br />
            <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %><br />
            <b>Linked by:</b> <%= ann.link.owner.firstName %> <%= ann.link.owner.lastName %><br />
            <b>Description:</b> <%= ann.description %>
                <br/><b>Namespace:</b> <%= ann.ns %>
                <br/><b>Mimetype:</b> <%= ann.file.mimetype %>
            <br/><b>File ID:</b> <%= ann.file.id %>
            href='<%= webindex %>omero_table/<%= ann.file.id %>/'>&nbsp;</a>
            <a class='removeFile action' id="<%= ann.id %>-file" title="Remove File"
            href='<%= webindex %>action/remove/file/<%= ann.id %>/'>&#8211</a>
            <a id="<%= ann.id %>-file" type="image" class="deleteFile action" title="Delete File"
                href="<%= webindex %>action/delete/file/<%= ann.id %>/"> &#215 </a>
<% }) %>
            data-annId="<%= ann.id %>"
            data-added-by="<% print (ann.addedBy.join(',')) %>"
      <tr title="<%= ann.ns %>">
              <%= ann.ns.slice(0, 50) %>
                Added by: <%= ann.owner.firstName %> <%= ann.owner.lastName %>
                    <%= ann.parentNames.length %> Annotations linked to:
                  <%= ann.parentNames ? (ann.parentNames.length + " objects") : ann.link.parent.name %>
                      <b><%= ann.parentNames.length %></b> identical Key-Value annotations:
                        <b><%= ann.link.parent.class.slice(0, ann.link.parent.class.length-1) %>
                            ID:</b> <%= ann.link.parent.id %><br />
                            &nbsp <%= pName %><br />
                        <b>Owner:</b> <%= ann.owner.firstName %> <%= ann.owner.lastName %>
                        <br /><b>Linked by:</b> <%= ann.link.owner.firstName %> <%= ann.link.owner.lastName %>
                Add annotations to <%= objCount %> objects
            <td><% print(_.escape(row['0'])) %></td>
            <td><% print(_.escape(row['1'])) %></td>
                            "<b>Owner:</b> " + data.owner + " <br />" +
                            "<b>Linked by:</b> " + data.addedBy + " <br />" +
    data-tag-id="<%= tag.id %>"
    data-added-by="<% print (tag.addedBy.join(',')) %>">
	
        <a class='tooltip tag_inner' href="?show=tag-<%= tag.id %>" target="_top">
            <%= tag.textValue %>
        <span class='removeTag' title="Remove Tag" id="<%= tag.id %>-tag"
                url='<%= webindex %>action/remove/tag/<%= tag.id %>/'>
            Can remove Tag from <b><%= tag.canRemoveCount %> object<% if(tag.canRemoveCount !== 1) {print('s')} %></b>:<br/>
                        <b><%= link.parent.class %> <%= link.parent.id %></b>
                        <% print(_.escape(link.parent.name).slice(0, 28)) %>
                        <% if (link.owner.id !== userId){
            <b>ID:</b> <%= tag.id %><br />
            <b>Owner:</b> <%= tag.owner.firstName %> <%= tag.owner.lastName %><br />
            <b>Linked by:</b> <%= tag.link.owner.firstName %> <%= tag.link.owner.lastName %><br />
            <b>Description:</b> <%= tag.description %>
    
<% }) %>
            id="image_icon-<%= img.id %>"
            data-fileset="<%= img.data.obj.filesetId %>"
            data-id="<%= img.id %>"
            <% if(img.shareId) { %> data-share="<%= img.shareId %>" <% }%>
                <a href="<%= webindex %><% if(img.shareId) {print( img.shareId + '/')} %>img_detail/<%= img.id %>/<% if (dataset) print('?dataset=' + dataset) %>" >
                 <%= img.name %>
                <span class="hidden_sort_text"> <%= img.name %> </span>
            <div class="date" valign="middle"><%= img.date %></div>
            <div class="sizeX" valign="middle"><%= img.data.obj.sizeX %></div>
            <div class="sizeY" valign="middle"><%= img.data.obj.sizeY %></div>
            <div class="sizeZ" valign="middle"><%= img.data.obj.sizeZ %></div>
<% } %>
                return "<option value='" + t.id + "'>" + t.text + "</option>";
                var tagText = usedTags[tagId].textValue;
                            html += '<li ' + cls + ' title="' + w.name + '" data-imageId="' + w.id + '">';
                    $("#targetMessage").html("File: " + chosen_file + " not valid.<br/>File must be Python script (.py)");
                "owner": r["owner"],
            case '"': return '\"';
                                    var text_string = formatShapeText(shape['textValue'].escapeHTML())
                                    txt = paper.text(textx, newY, formatShapeText(shape['textValue'].escapeHTML()))
                    return '<img style="left: ' + (x * 100) + '%; top: ' + (y * 100) + '%" title="' + ws.name + '" data-imageId="' + ws.id + '" />';
        $('#wblitz-image-name').html(tmp.imageName);
        $('#wblitz-image-description-content').html(tmp.imageDescription.replace(/\n/g, '<br />'));
        $('#wblitz-image-author').html(tmp.imageAuthor);
        $('#wblitz-image-pub').html(tmp.projectName);
                '" title="' + channels[i].label +
                '"><div class="lutBackground"></div><div class="btnLabel">'+channels[i].label+'</div></button>')
                .replace(/\$label/g, channels[i].label)
                .replace(/\$l/g, lbl)
                .replace(/\$cwl/g, channels[i].label) // Wavelength
              shapesHtml += "<td class='shape_cell shape_text' title=\""+ text +"\"><p>"+ truncate_text(text.escapeHTML()) + "</p></td>";
          roi_html += "<th class='shape_text' title=\""+ roiText +"\"><p>"+ truncate_text(roiText) +"</p></th>";

Fixed code:
5.11.0 (October 2021)
---------------------

- Security vulnerability fixes for
  [2021-SV3](https://www.openmicroscopy.org/security/advisories/2021-SV3)

                    tableRows.push('<tr><td class="link">' + rowData.label.escapeHTML() + '(id:'+ rowId +')</td>');
                        slice.label = slice.label.escapeHTML();
            var headerTxt = "<p>Move data owned by " + ownernames.join(", ").escapeHTML() + ".</p>" +
                html += g.name.escapeHTML() + "<hr></div>";
                namesList.push(u.name.escapeHTML());
            <input name='<%- obj.split("=")[0] %>' value='<%- obj.split("=")[1] %>' hidden/>
                    <%- exp.FirstName%> <%- exp.LastName %>
                            ann.value = ann[a];
                    tag.textValue = tag.textValue;
                    tag.description = tag.description;
                            summary[tagId] = {'textValue': _.escape(tag.textValue),
                html += create_tag_html(
                    tag.t, tag.d, tag.i, null, tag.s !== 0
                );
                                child.t, child.d, child.i, tag.i
                            );
        $(".tag_selection div").on('click', tag_click);
                tagset.text().escapeHTML() + "</span> tag set and select it immediately:");
            items: '[data-id]',  // Just needs an attribute that exists
            title += "<b>Owner:</b> " + owner.escapeHTML() + "<br />";
            title += "<b>Linked by:</b> " + link_owner.escapeHTML() + "<br />";
            title += "<b>Description:</b> " + description.escapeHTML() + "<br />";
            title += "<b>Tag set:</b> " + tagset.escapeHTML() + "<br />";
    var create_tag_html = function(text, description, id, parent_id,
            html += " data-description='" + description.escapeHTML() + "'";
        html += ">" + text.escapeHTML() + "</div>";
            owners[me] = owners[me] || _.unescape(my_name);
                text, description, new_tag_counter,
            var title = create_tag_title(
                description, _.unescape(my_name),
                tagset_id? all_tags[tagset_id].t : null
            );
                items: '[data-id]',  // Just needs an attribute that exists
                content: title
                        $tr = $('<tr><th><h2>' + topLevelTag.escapeHTML() + '</h2></th></tr>');
});
<div class="ann_comment_wrapper" data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
        <img src="<%= webindex %>avatar/<%- ann.owner.id %>/"
        alt="<%- ann.owner.firstName %> <%- ann.owner.lastName %>"
        title="<%- ann.owner.firstName %> <%- ann.owner.lastName %>" />
                <%- ann.owner.firstName %> <%- ann.owner.lastName %>
            </strong>

            <img class='removeComment' id="<%- ann.id %>-comment"
                url='<%= webindex %>action/remove/comment/<%- ann.id %>/'
        <div class='commentText'><%- ann.textValue %></div>
        <b>ID:</b> <%- ann.id %><br />
        <% if (ann.ns) { %><b>Namespace:</b> <%- ann.ns %><br /><% } %>
        <% if (ann.description) { %><b>Description:</b> <%- ann.description %><br /><% } %>
<tr data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
    <th><%- ann.type %></th>
            <div><% print(_.escape((ann.value + "").slice(0, 20))) %>...</div>
            <div style="display: none"><%- ann.value %></div>
            <div><%- ann.value %></div>

            <b>ID:</b> <%- ann.id %><br />
            <% if (ann.ns) { %><b>Namespace:</b> <%- ann.ns %><br /><% } %>
            <% if (ann.description) { %><b>Description:</b> <%- ann.description %><br /><% } %>
            <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %><br />
            <b>Linked to:</b> <%- ann.parent.class %> <%- ann.parent.id %>
<% }) %>
    id="file_ann-<%- ann.id %>"
    data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>">
        <%- ann.file.name %>
            <span>(<%- ann.file.size %>)</span>
            Can remove File from <b><%- ann.canRemoveCount %>
                        <b><%- link.parent.class %> <%- link.parent.id %></b>
            <b>Annotation ID:</b> <%- ann.id %><br />
            <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %><br />
            <b>Linked by:</b> <%- ann.link.owner.firstName %> <%- ann.link.owner.lastName %><br />
            <b>Description:</b> <%- ann.description %>
                <br/><b>Namespace:</b> <%- ann.ns %>
                <br/><b>Mimetype:</b> <%- ann.file.mimetype %>
            <br/><b>File ID:</b> <%- ann.file.id %>
            href='<%= webindex %>omero_table/<%- ann.file.id %>/'>&nbsp;</a>
            <a class='removeFile action' id="<%- ann.id %>-file" title="Remove File"
            href='<%= webindex %>action/remove/file/<%- ann.id %>/'>&#8211</a>
            <a id="<%- ann.id %>-file" type="image" class="deleteFile action" title="Delete File"
                href="<%= webindex %>action/delete/file/<%- ann.id %>/"> &#215 </a>
<% }) %>
            data-annId="<%- ann.id %>"
            data-added-by="<% print(_.escape(ann.addedBy.join(','))) %>"
      <tr title="<%- ann.ns %>">
              <%- ann.ns.slice(0, 50) %>
                Added by: <%- ann.owner.firstName %> <%- ann.owner.lastName %>
                    <%- ann.parentNames.length %> Annotations linked to:
                  <%- ann.parentNames ? (ann.parentNames.length + " objects") : ann.link.parent.name %>
                      <b><%- ann.parentNames.length %></b> identical Key-Value annotations:
                        <b><%- ann.link.parent.class.slice(0, ann.link.parent.class.length-1) %>
                            ID:</b> <%- ann.link.parent.id %><br />
                            &nbsp <%- pName %><br />
                        <b>Owner:</b> <%- ann.owner.firstName %> <%- ann.owner.lastName %>
                        <br /><b>Linked by:</b> <%- ann.link.owner.firstName %> <%- ann.link.owner.lastName %>
                Add annotations to <%- objCount %> objects
            <td><%- row['0'] %></td>
            <td><%- row['1'] %></td>
                            "<b>Owner:</b> " + data.owner.escapeHTML() + " <br />" +
                            "<b>Linked by:</b> " + data.addedBy.escapeHTML() + " <br />" +
    data-tag-id="<%- tag.id %>"
    data-added-by="<% print(_.escape(tag.addedBy.join(','))) %>">

        <a class='tooltip tag_inner' href="?show=tag-<%- tag.id %>" target="_top">
            <%- tag.textValue %>
        <span class='removeTag' title="Remove Tag" id="<%- tag.id %>-tag"
                url='<%= webindex %>action/remove/tag/<%- tag.id %>/'>
            Can remove Tag from <b><%- tag.canRemoveCount %> object<% if(tag.canRemoveCount !== 1) {print('s')} %></b>:<br/>
                        <b><%- link.parent.class %> <%- link.parent.id %></b>
                        <%- link.parent.name.slice(0, 28) %>
                        <% if (link.owner.id !== userId) {
            <b>ID:</b> <%- tag.id %><br />
            <b>Owner:</b> <%- tag.owner.firstName %> <%- tag.owner.lastName %><br />
            <b>Linked by:</b> <%- tag.link.owner.firstName %> <%- tag.link.owner.lastName %><br />
            <b>Description:</b> <%- tag.description %>

<% }) %>
            id="image_icon-<%- img.id %>"
            data-fileset="<%- img.data.obj.filesetId %>"
            data-id="<%- img.id %>"
            <% if(img.shareId) { %> data-share="<%- img.shareId %>" <% }%>
                <a href="<%= webindex %><% if(img.shareId) {print( img.shareId + '/')} %>img_detail/<%- img.id %>/<% if (dataset) print('?dataset=' + dataset) %>" >
                 <%- img.name %>
                <span class="hidden_sort_text"> <%- img.name %> </span>
            <div class="date" valign="middle"><%- img.date %></div>
            <div class="sizeX" valign="middle"><%- img.data.obj.sizeX %></div>
            <div class="sizeY" valign="middle"><%- img.data.obj.sizeY %></div>
            <div class="sizeZ" valign="middle"><%- img.data.obj.sizeZ %></div>
<% } %>
                return "<option value='" + t.id + "'>" + t.text.escapeHTML() + "</option>";
                var tagText = usedTags[tagId].textValue.escapeHTML();
                            html += '<li ' + cls + ' title="' + w.name.escapeHTML() + '" data-imageId="' + w.id + '">';
                    $("#targetMessage").html("File: " + chosen_file.escapeHTML() + " not valid.<br/>File must be Python script (.py)");
from django.utils.html import escape
                "owner": escape(r["owner"]),  # May be used unsafe later
            case "`": return '&#x60;';
            case "'": return '&#x27;';
            case '"': return '&quot;';
                                    var text_string = formatShapeText(shape['textValue'])
                                    txt = paper.text(textx, newY, formatShapeText(shape['textValue']))
                    return '<img style="left: ' + (x * 100) + '%; top: ' + (y * 100) + '%" title="' + ws.name.escapeHTML() + '" data-imageId="' + ws.id + '" />';
        $('#wblitz-image-name').html(tmp.imageName.escapeHTML());
        $('#wblitz-image-description-content').html(tmp.imageDescription.escapeHTML().replace(/\n/g, '<br />'));
        $('#wblitz-image-author').html(tmp.imageAuthor.escapeHTML());
        $('#wblitz-image-pub').html(tmp.projectName.escapeHTML());
                '" title="' + channels[i].label.escapeHTML() +
                '"><div class="lutBackground"></div><div class="btnLabel">'+channels[i].label.escapeHTML()+'</div></button>')
                .replace(/\$label/g, channels[i].label.escapeHTML())
                .replace(/\$l/g, lbl.escapeHTML())
                .replace(/\$cwl/g, channels[i].label.escapeHTML()) // Wavelength
              shapesHtml += "<td class='shape_cell shape_text' title=\""+ text.escapeHTML() +"\"><p>"+ truncate_text(text.escapeHTML()) + "</p></td>";
          roi_html += "<th class='shape_text' title=\""+ roiText.escapeHTML() +"\"><p>"+ truncate_text(roiText).escapeHTML() +"</p></th>";