CVE-2021-21377 (Python, published 2021-03-23T16:15Z)
Description: value: OMERO.web is open source Django-based software for managing microscopy imaging. OMERO.web before version 5.9.0 supports redirection to a given URL after performing login or switching the group context. These URLs are not validated, allowing redirection to untrusted sites. OMERO.web 5.9.0 adds URL validation before redirecting. External URLs are not considered valid, unless specified in the omero.web.redirect_allowed_hosts setting.
Severity: MEDIUM (CVSS v3 base score: 5.4)
Weakness: CWE-601 \u2014 URL Redirection to Untrusted Site ('Open Redirect')
A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
Repository: https://github.com/ome/omero-web
Files changed:
CHANGELOG.md
omeroweb/decorators.py
omeroweb/settings.py
omeroweb/webclient/decorators.py
omeroweb/webclient/forms.py
omeroweb/webclient/static/webclient/javascript/ome.tree.js
omeroweb/webclient/templates/webclient/base/base_container.html
omeroweb/webclient/templates/webclient/base/includes/toolbar_forms.html
omeroweb/webclient/templates/webclient/data/containers.html
omeroweb/webclient/templates/webclient/public/public.html
omeroweb/webclient/views.py
omeroweb/webgateway/templates/webgateway/core_html.html
omeroweb/webgateway/views.py
Commit message: SV commits


Vulnerable code:
        pass
from omeroweb.webgateway.marshal import eventContextMarshal
        context["ome"]["eventContext"] = eventContextMarshal(conn.getEventContext())
from omeroweb.webadmin.custom_forms import GroupModelMultipleChoiceField
class BasketShareForm(ShareForm):
    def __init__(self, *args, **kwargs):
        super(BasketShareForm, self).__init__(*args, **kwargs)

        try:
            self.fields["image"] = GroupModelMultipleChoiceField(
                queryset=kwargs["initial"]["images"],
                initial=kwargs["initial"]["selected"],
                widget=forms.SelectMultiple(attrs={"size": 10}),
            )
        except Exception:
            self.fields["image"] = GroupModelMultipleChoiceField(
                queryset=kwargs["initial"]["images"],
                widget=forms.SelectMultiple(attrs={"size": 10}),
            )


                    memberOfGroup = WEBCLIENT.eventContext.memberOfGroups.indexOf(WEBCLIENT.active_group_id) > -1,
                    writeOwned = WEBCLIENT.eventContext.adminPrivileges.indexOf("WriteOwned") > -1,
        WEBCLIENT.eventContext = {{ ome.eventContext|json_dumps|safe }};
  Copyright (C) 2011 University of Dundee & Open Microscopy Environment.

        var productListQuery = [];

        // we do inst.get_selected() here, since we then get objects
        // instead of ids for some reason?
        var inst = $.jstree.reference('#dataTree');
        data = inst.get_selected(true);
        data.forEach(function(node){
            productListQuery.push(node.type + "=" + node.data.id);
        });

        var query = '{% url 'manage_action_containers' "add" "share" %}' + "?"+productListQuery.join("&");
        $("#create_share_form").attr("action", query)
        $("#create_share_form").load(query);
        // AJAX handling of create-discussion form
        $("#create_share_form").ajaxForm({
            success: function(html) {
                if (html.indexOf("shareId") > -1) {
                    var shareId = html.replace("shareId:", "");
                    $("#create_share_form").dialog( "close" );
                    $("#shareCreatedId").text(shareId);
                    $("#share_dialog_form").dialog("open").show();
                } else {
                    $("#create_share_form").html(html);
                }
            },
        });

        $("#share_dialog_form").dialog({
            autoOpen: false,
            resizable: true,
            height: 150,
            width:300,
            modal: true,
            buttons: {
                "OK": function() {
                    $( this ).dialog( "close" );
                }
            }
        });

            height: 600,
                "Accept": function() {
                    // simply submit the form
                    $("#create_share_form").submit();
                },
                "Cancel": function() {



<!-- hidden form for creating share - shown in dialog & loaded by AJAX -->
<form id="create_share_form" action="#" method="post" title="Create Share" class="standard_form">{% csrf_token %}
</form>

<form id="share_dialog_form" action="#" title="Create Share" style="display:none">
    <p style="font-size: 120%; font-weight: bold">
        Share <span id="shareCreatedId"></span> was created successfully.
</form>
            var writeOwned = WEBCLIENT.eventContext.adminPrivileges.indexOf("WriteOwned") > -1;
            var memberOfGroup = WEBCLIENT.eventContext.memberOfGroups.indexOf(WEBCLIENT.active_group_id) > -1;
                memberOfGroup = WEBCLIENT.eventContext.memberOfGroups.indexOf(WEBCLIENT.active_group_id) > -1,
                writeOwned = WEBCLIENT.eventContext.adminPrivileges.indexOf("WriteOwned") > -1,
    <div id="tree_details" class="left_panel_tree">
from .forms import ShareForm, BasketShareForm
    elif action == "add":
        template = "webclient/public/share_form.html"
        experimenters = list(conn.getExperimenters())
        experimenters.sort(key=lambda x: x.getOmeName().lower())
        if o_type == "share":
            img_ids = request.GET.getlist("image", request.POST.getlist("image"))
            if request.method == "GET" and len(img_ids) == 0:
                return HttpResponse("No images specified")
            images_to_share = list(conn.getObjects("Image", img_ids))
            if request.method == "POST":
                form = BasketShareForm(
                    initial={"experimenters": experimenters, "images": images_to_share},
                    data=request.POST.copy(),
                )
                if form.is_valid():
                    images = form.cleaned_data["image"]
                    message = form.cleaned_data["message"]
                    expiration = form.cleaned_data["expiration"]
                    members = form.cleaned_data["members"]
                    # guests = request.POST['guests']
                    enable = form.cleaned_data["enable"]
                    host = "%s?server=%i" % (
                        request.build_absolute_uri(
                            reverse("load_template", args=["public"])
                        ),
                        int(conn.server_id),
                    )
                    shareId = manager.createShare(
                        host, images, message, members, enable, expiration
                    )
                    return HttpResponse("shareId:%s" % shareId)
            else:
                initial = {
                    "experimenters": experimenters,
                    "images": images_to_share,
                    "enable": True,
                    "selected": request.GET.getlist("image"),
                }
                form = BasketShareForm(initial=initial)
        template = "webclient/public/share_form.html"
        context = {"manager": manager, "form": form}
                else None
        range_size = kwargs.get("limit", rows)

Fixed code:
- Security vulnerability fixes for
  [2021-SV1](https://www.openmicroscopy.org/security/advisories/2021-SV1-user-context/),
  [2021-SV2](https://www.openmicroscopy.org/security/advisories/2021-SV2-url-validation/)

        context["html"] = context.get("html", {})
        context["html"]["meta_referrer"] = settings.HTML_META_REFERRER
    "omero.web.session_cookie_path": [
        "SESSION_COOKIE_PATH",
        None,
        leave_none_unset,
        "The path to use for session cookies",
    ],
    "omero.web.redirect_allowed_hosts": [
        "REDIRECT_ALLOWED_HOSTS",
        "[]",
        json.loads,
        (
            "If you wish to allow redirects to an external site, "
            "the domains must be listed here. "
            'For example ["openmicroscopy.org"].'
        ),
    ],
    "omero.web.html_meta_referrer": [
        "HTML_META_REFERRER",
        "origin-when-crossorigin",
        str,
        (
            "Default content for the HTML Meta referrer tag. "
            "See https://www.w3.org/TR/referrer-policy/#referrer-policies for "
            "allowed values and https://caniuse.com/#feat=referrer-policy for "
            "browser compatibility. "
            "Warning: Internet Explorer 11 does not support the default value "
            'of this setting, you may want to change this to "origin" after '
            "reviewing the linked documentation."
        ),
    ],
        super(render_response, self).prepare_context(request, context, *args, **kwargs)

                    memberOfGroup = WEBCLIENT.member_of_groups.indexOf(WEBCLIENT.active_group_id) > -1,
                    writeOwned = WEBCLIENT.current_admin_privileges.indexOf("WriteOwned") > -1,
        WEBCLIENT.member_of_groups = {{ member_of_groups|json_dumps|safe }};
  Copyright (C) 2011-2021 University of Dundee & Open Microscopy Environment.
            title: "Shares not supported",
            height: 250,
                "OK": function() {
<!-- hidden dialog -->
<div id="create_share_form" style="display:none">
    <p>Share functionality is no longer supported.</p>
    <p>Please see <a target="_blank" href="https://www.openmicroscopy.org/omero/features/share/">Sharing your data in OMERO</a>
        for alternative workflows.
</div>
            var writeOwned = WEBCLIENT.current_admin_privileges.indexOf("WriteOwned") > -1;
            var memberOfGroup = WEBCLIENT.member_of_groups.indexOf(WEBCLIENT.active_group_id) > -1;
                memberOfGroup = WEBCLIENT.member_of_groups.indexOf(WEBCLIENT.active_group_id) > -1,
                writeOwned = WEBCLIENT.current_admin_privileges.indexOf("WriteOwned") > -1,
    <div style="height: 110px; padding: 15px; box-sizing: border-box;">
        <p>Creating new shares is no longer supported. Previously created shares are shown below.</p>
        <p>Please see <a target="_blank" href="https://www.openmicroscopy.org/omero/features/share/">Sharing your data in
                OMERO</a>
            for alternative workflows.
        </p>
    </div>

    <div id="tree_details" class="left_panel_tree" style="height: calc(100% - 110px)">
from django.utils.http import is_safe_url
from .forms import ShareForm
def validate_redirect_url(url):
    """
    Returns a URL is safe to redirect to.
    If url is a different host, not in settings.REDIRECT_ALLOWED_HOSTS
    we return webclient index URL.
    """
    if not is_safe_url(url, allowed_hosts=settings.REDIRECT_ALLOWED_HOSTS):
        url = reverse("webindex")
    return url


        else:
            url = validate_redirect_url(url)
    url = validate_redirect_url(url)
    context["member_of_groups"] = conn.getEventContext().memberOfGroups
    <meta name="referrer" content="{{ html.meta_referrer|default:"origin-when-crossorigin" }}">
                else rows
        range_size = limit