CVE-2024-34061 (HTML, published 2024-05-02T14:15Z)
Description: value: changedetection.io is a free open source web page change detection, website watcher, restock monitor and notification service. In affected versions Input in parameter notification_urls is not processed resulting in javascript execution in the application. A reflected XSS vulnerability happens when the user input from a URL or POST data is reflected on the page without being stored, thus allowing the attacker to inject malicious content. This issue has been addressed in version 0.45.22. Users are advised to upgrade. There are no known workarounds for this vulnerability.
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/dgtlmoon/changedetection.io
Files changed:
changedetectionio/blueprint/tags/templates/edit-tag.html
changedetectionio/blueprint/tags/templates/groups-overview.html
changedetectionio/templates/IMPORTANT.md
changedetectionio/templates/_common_fields.html
changedetectionio/templates/diff.html
changedetectionio/templates/edit.html
changedetectionio/templates/import.html
changedetectionio/templates/settings.html
changedetectionio/templates/watch-overview.html
changedetectionio/tests/test_security.py
Commit message: Merge pull request from GHSA-pwgc-w4x9-gw67

* Auto-escape was not enabled GHSA-pwgc-w4x9-gw67

* Auto-escape was not enabled because the filenames were not something jinja2 enables it for.

Vulnerable code:
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
{% from '_common_fields.jinja' import render_common_settings_form %}
{% from '_helpers.jinja' import render_simple_field, render_field %}
{% from '_helpers.jinja' import render_field %}
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
{% from '_common_fields.jinja' import render_common_settings_form %}
{% from '_helpers.jinja' import render_field %}
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
{% from '_common_fields.jinja' import render_common_settings_form %}
{% from '_helpers.jinja' import render_simple_field, render_field, render_nolabel_field, sort_by_title %}
    live_server_setup(live_server)
    assert b'file:// type access is denied for security reasons.' in res.data

Fixed code:
{% from '_helpers.html' import render_field, render_checkbox_field, render_button %}
{% from '_common_fields.html' import render_common_settings_form %}
{% from '_helpers.html' import render_simple_field, render_field %}
# Important notes about templates

Template names should always end in ".html", ".htm", ".xml", ".xhtml", ".svg", even the `import`'ed templates.

Jinja2's `def select_jinja_autoescape(self, filename: str) -> bool:` will check the filename extension and enable autoescaping

{% from '_helpers.html' import render_field %}
{% from '_helpers.html' import render_field, render_checkbox_field, render_button %}
{% from '_helpers.html' import render_field, render_checkbox_field, render_button %}
{% from '_common_fields.html' import render_common_settings_form %}
{% from '_helpers.html' import render_field %}
{% from '_helpers.html' import render_field, render_checkbox_field, render_button %}
{% from '_common_fields.html' import render_common_settings_form %}
{% from '_helpers.html' import render_simple_field, render_field, render_nolabel_field, sort_by_title %}
def test_setup(client, live_server):
    live_server_setup(live_server)
    #live_server_setup(live_server)
    assert b'file:// type access is denied for security reasons.' in res.data

def test_xss(client, live_server):
    #live_server_setup(live_server)
    from changedetectionio.notification import (
        default_notification_format
    )
    # the template helpers were named .jinja which meant they were not having jinja2 autoescape enabled.
    res = client.post(
        url_for("settings_page"),
        data={"application-notification_urls": '"><img src=x onerror=alert(document.domain)>',
              "application-notification_title": '"><img src=x onerror=alert(document.domain)>',
              "application-notification_body": '"><img src=x onerror=alert(document.domain)>',
              "application-notification_format": default_notification_format,
              "requests-time_between_check-minutes": 180,
              'application-fetch_backend': "html_requests"},
        follow_redirects=True
    )

    assert b"<img src=x onerror=alert(" not in res.data
    assert b"&lt;img" in res.data