CVE-2024-36110 (Python, published 2024-05-28T19:15Z)
Description: value: ansibleguy-webui is an open source WebUI for using Ansible. Multiple forms in versions < 0.0.21 allowed injection of HTML elements. These are returned to the user after executing job actions and thus evaluated by the browser. These issues have been addressed in version 0.0.21 (0.0.21.post2 on pypi). Users are advised to upgrade. There are no known workarounds for these issues.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/ansibleguy/webui
Files changed:
CHANGELOG.md
src/ansibleguy-webui/aw/api_endpoints/alert.py
src/ansibleguy-webui/aw/api_endpoints/base.py
src/ansibleguy-webui/aw/api_endpoints/credentials.py
src/ansibleguy-webui/aw/api_endpoints/job.py
src/ansibleguy-webui/aw/api_endpoints/permission.py
src/ansibleguy-webui/aw/api_endpoints/repository.py
src/ansibleguy-webui/aw/static/js/aw.js
src/ansibleguy-webui/aw/static/js/jobs/logs.js
src/ansibleguy-webui/aw/static/js/jobs/manage.js
Commit message: added validation against XSS (#44)


Vulnerable code:
* ...
    api_docs_post
    api_docs_put, api_docs_post
    LogDownloadResponse, api_docs_put, api_docs_delete, api_docs_post
    api_docs_post
    api_docs_delete, api_docs_post
        resultDiv.innerHTML = 'Success: ' + result.msg;
        errorHTML += ' - ' + result.responseJSON.msg;
function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

function replaceAll(str, search, replace) {
    return str.replace(new RegExp(escapeRegExp(search), 'g'), replace);
}


Fixed code:
* Added [validation against XSS](https://github.com/ansibleguy/webui/issues/44)
    api_docs_post, validate_no_xss
    def validate(self, attrs: dict):
        for field in AlertUser.api_fields_write:
            if field in attrs:
                validate_no_xss(value=attrs[field], field=field)

        return attrs

    def validate(self, attrs: dict):
        for field in AlertGlobal.api_fields_write:
            if field in attrs:
                validate_no_xss(value=attrs[field], field=field)

        return attrs

    def validate(self, attrs: dict):
        for field in AlertGroup.api_fields_write:
            if field in attrs:
                validate_no_xss(value=attrs[field], field=field)

        return attrs

from django.utils.html import escape as escape_html
from rest_framework.exceptions import ValidationError
from aw.utils.util import is_set


def validate_no_xss(value: str, field: str):
    if is_set(value) and isinstance(value, str) and value != escape_html(value):
        raise ValidationError(f"Found illegal characters in field '{field}'")
    api_docs_put, api_docs_post, validate_no_xss
    def validate(self, attrs: dict):
        for field in JobGlobalCredentials.api_fields_write:
            if field in attrs and field not in BaseJobCredentials.SECRET_ATTRS:
                validate_no_xss(value=attrs[field], field=field)

        return attrs

    def validate(self, attrs: dict):
        for field in JobUserCredentials.api_fields_write:
            if field in attrs and field not in BaseJobCredentials.SECRET_ATTRS:
                validate_no_xss(value=attrs[field], field=field)

        return attrs

    LogDownloadResponse, api_docs_put, api_docs_delete, api_docs_post, validate_no_xss
    def validate(self, attrs: dict):
        for field in Job.api_fields_write:
            if field in attrs:
                validate_no_xss(value=attrs[field], field=field)

        return attrs

    api_docs_post, validate_no_xss
    def validate(self, attrs: dict):
        for field in JobPermission.api_fields_write:
            if field in attrs:
                validate_no_xss(value=attrs[field], field=field)

        return attrs

    api_docs_delete, api_docs_post, validate_no_xss
    def validate(self, attrs: dict):
        for field in Repository.api_fields_write:
            if field in attrs:
                validate_no_xss(value=attrs[field], field=field)

        return attrs

function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

function replaceAll(str, search, replace) {
    return str.replace(new RegExp(escapeRegExp(search), 'g'), replace);
}

// see: https://docs.djangoproject.com/en/5.0/ref/templates/language/#automatic-html-escaping
function escapeHTML(data) {
    if (!is_set(data)) {
        return data;
    }
    data = data.replaceAll('<', '&lt;');
    data = data.replaceAll('>', '&gt;');
    data = data.replaceAll('&', '&amp;');
    return data;
}

        resultDiv.innerHTML = 'Success: ' + escapeHTML(result.msg);
        errorHTML += ' - ' + escapeHTML(result.responseJSON.msg);
    } else {
        actionsTemplate = actionsTemplate.replaceAll('${EXEC_ID_1}', 0);