CVE-2024-23345 (Python, published 2024-01-23T00:15Z)
Description: value: Nautobot is a Network Source of Truth and Network Automation Platform built as a web application.  All users of Nautobot versions earlier than 1.6.10 or 2.1.2 are potentially impacted by a cross-site scripting vulnerability. Due to inadequate input sanitization, any user-editable fields that support Markdown rendering, including are potentially susceptible to cross-site scripting (XSS) attacks via maliciously crafted data. This issue is fixed in Nautobot versions 1.6.10 and 2.1.2.
Severity: nan (CVSS v3 base score: 5.4)
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/nautobot/nautobot
Files changed:
changes/5133.added
changes/5133.dependencies
changes/5133.security
nautobot/core/constants.py
nautobot/core/forms/fields.py
nautobot/core/settings.py
nautobot/core/templatetags/helpers.py
nautobot/core/tests/test_templatetags_helpers.py
nautobot/core/tests/test_views.py
nautobot/core/utils/logging.py
nautobot/docs/development/jobs/index.md
nautobot/docs/user-guide/administration/configuration/optional-settings.md
nautobot/docs/user-guide/platform-functionality/note.md
nautobot/docs/user-guide/platform-functionality/template-filters.md
nautobot/extras/forms/forms.py
nautobot/extras/models/jobs.py
poetry.lock
pyproject.toml
Commit message: Sanitize `render_markdown()` output with `nh3` library (#5133)

* Fix GHSA-v4xv-795h-rv4h.

* Renumber change fragments

* Address review feedback

* Test fix

* Ruff

* Review feedback

* Update nautobot/core/forms/fields.py

Vulnerable code:
    # TODO: Port Markdown cheat sheet to internal documentation
    default_helptext = (
        '<i class="mdi mdi-information-outline"></i> '
        '<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank">'
        "Markdown</a> syntax is supported"
    )
        help_text="Help message to include on 4xx and 5xx error pages. Markdown is supported.\n"
from django.utils.html import format_html, strip_tags
from nautobot.core.utils import color, config, data, lookup
    # Strip HTML tags
    value = strip_tags(value)

    # Sanitize Markdown links
    schemes = "|".join(settings.ALLOWED_URL_SCHEMES)
    pattern = rf"\[(.+)\]\((?!({schemes})).*:(.+)\)"
    value = re.sub(pattern, "[\\1](\\3)", value, flags=re.IGNORECASE)

        self.assertEqual(
            '<p><a href="https://www.example.com">I am a link</a></p>',
                    'on <a href="https://slack.networktocode.com/">Network to Code\'s Slack community</a> '
                    "and post your question.</p>",
            "If further assistance is required, please join the <code>#nautobot</code> channel "
            'on <a href="https://slack.networktocode.com/">Network to Code\'s Slack community</a> '
            "and post your question.",
            "If further assistance is required, please join the <code>#nautobot</code> channel "
            'on <a href="https://slack.networktocode.com/">Network to Code\'s Slack community</a> '
            "and post your question.",
        """Nautobot's custom 500 page should be used and should include a default support message."""
"""Utilities for working with log messages and similar features."""
This can accept either plain text or Markdown-formatted text. It can also be multiple lines:
Markdown rendering is supported for log messages.
A message to include on error pages (status code 403, 404, 500, etc.) when an error occurs. You can configure this to direct users to the appropriate contact(s) within your organization that provide support for Nautobot. Markdown formatting is supported within this message (raw HTML is not).
The note field supports [Markdown Basic Syntax](https://www.markdownguide.org/cheat-sheet/#basic-syntax).
Render text as Markdown.
    description = forms.CharField(
        help_text="Also used as the help text when editing models using this custom field.<br>"
        '<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank">'
        "Markdown</a> syntax is supported.",
    note = CommentField
    description = models.TextField(blank=True, help_text="Markdown formatting is supported")
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
    {file = "lxml-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cfbac9f6149174f76df7e08c2e28b19d74aed90cad60383ad8671d3af7d0502f"},
    {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
    {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
    {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
content-hash = "8138e21579db269494e43aa441e2690b0bf838b58c6cce161dcb9f4cd1852640"

Fixed code:
Enhanced Markdown-supporting fields (`comments`, `description`, Notes, Job log entries, etc.) to also permit the use of a limited subset of "safe" HTML tags and attributes.
Added `nh3` HTML sanitization library as a dependency.
Fixed an XSS vulnerability ([GHSA-v4xv-795h-rv4h](https://github.com/nautobot/nautobot/security/advisories/GHSA-v4xv-795h-rv4h)) in the `render_markdown()` utility function used to render comments, notes, job log entries, etc.
from copy import deepcopy

import nh3

#
# User input sanitization
#

# Subset of the HTML tags allowed by default by ammonia:
# https://github.com/rust-ammonia/ammonia/blob/master/src/lib.rs
HTML_ALLOWED_TAGS = nh3.ALLOWED_TAGS - {
    # no image maps at present
    "area",
    "map",
    # no document-level markup at present
    "article",
    "aside",
    "footer",
    "header",
    "nav",
    # miscellaneous out-of-scope for now
    "data",
    "dfn",
    "figcaption",
    "figure",
}

# Variant of the HTML attributes allowed by default by ammonia:
# https://github.com/rust-ammonia/ammonia/blob/master/src/lib.rs
# at present we just copy nh3.ALLOWED_ATTRIBUTES but we can modify this later as desired and appropriate
HTML_ALLOWED_ATTRIBUTES = deepcopy(nh3.ALLOWED_ATTRIBUTES)


from django.templatetags.static import static
from django.utils.html import format_html

    @property
    def default_helptext(self):
        # TODO: Port Markdown cheat sheet to internal documentation
        return format_html(
            '<i class="mdi mdi-information-outline"></i> '
            '<a href="https://www.markdownguide.org/cheat-sheet/#basic-syntax" rel="noopener noreferrer">Markdown</a> '
            'syntax is supported, as well as <a href="{}#render_markdown">a limited subset of HTML</a>.',
            static("docs/user-guide/platform-functionality/template-filters.html"),
        )
        help_text="Help message to include on 4xx and 5xx error pages. "
        "Markdown is supported, as are some HTML tags and attributes.\n"
from django.utils.html import format_html
from nautobot.core.utils import color, config, data, logging as nautobot_logging, lookup
    # Sanitize rendered HTML
    html = nautobot_logging.clean_html(html)

        self.assertHTMLEqual(
            '<p><a href="https://www.example.com" rel="noopener noreferrer">I am a link</a></p>',
        )

    def test_render_markdown_security(self):
        self.assertEqual(helpers.render_markdown('<script>alert("XSS")</script>'), "")
        self.assertHTMLEqual(
            helpers.render_markdown('[link](javascript:alert("XSS"))'),
            '<p><a title="XSS" rel="noopener noreferrer">link</a>)</p>',  # the trailing ) seems weird to me, but...
        )
        self.assertHTMLEqual(
            helpers.render_markdown(
                "[link\nJS]"
                "(&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A"  # '(javascript:'
                "&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29)"  # 'alert("XSS"))'
            ),
            '<p><a rel="noopener noreferrer">link JS</a></p>',
                    'on <a href="https://slack.networktocode.com/" rel="noopener noreferrer">Network to Code\'s '
                    "Slack community</a> and post your question.</p>",
            "If further assistance is required, please join the <code>#nautobot</code> channel on "
            '<a href="https://slack.networktocode.com/" rel="noopener noreferrer">Network to Code\'s '
            "Slack community</a> and post your question.",
            "If further assistance is required, please join the <code>#nautobot</code> channel on "
            '<a href="https://slack.networktocode.com/" rel="noopener noreferrer">Network to Code\'s '
            "Slack community</a> and post your question.",
        """Nautobot's custom 500 page should be used and should include a custom support message if defined."""
"""Utilities for working with log messages and similar user-authored data."""
import nh3

from nautobot.core import constants


def clean_html(html):
    """Use nh3/ammonia to strip out all HTML tags and attributes except those explicitly permitted."""
    return nh3.clean(
        html,
        tags=constants.HTML_ALLOWED_TAGS,
        attributes=constants.HTML_ALLOWED_ATTRIBUTES,
        url_schemes=set(settings.ALLOWED_URL_SCHEMES),
    )
This can accept either plain text, Markdown-formatted text, or [a limited subset of HTML](../../user-guide/platform-functionality/template-filters.md#render_markdown). It can also be multiple lines:
Markdown rendering is supported for log messages, as well as [a limited subset of HTML](../../user-guide/platform-functionality/template-filters.md#render_markdown).
A message to include on error pages (status code 403, 404, 500, etc.) when an error occurs. You can configure this to direct users to the appropriate contact(s) within your organization that provide support for Nautobot. Markdown formatting is supported within this message, as well as [a limited subset of HTML](../../platform-functionality/template-filters.md#render_markdown).
The note field supports [Markdown Basic Syntax](https://www.markdownguide.org/cheat-sheet/#basic-syntax) as well as [a limited subset of HTML](template-filters.md#render_markdown).
Render and sanitize Markdown text into HTML. A limited subset of HTML tags and attributes are permitted in the text as well; non-permitted HTML will be stripped from the output for security.
#### Permitted HTML Tags and Attributes


The set of permitted HTML tags is defined in `nautobot.core.constants.HTML_ALLOWED_TAGS`, and their permitted attributes are defined in `nautobot.core.constants.HTML_ALLOWED_ATTRIBUTES`. As of Nautobot 2.1.2 the following are permitted:

??? info "Full list of HTML tags and attributes"
    | Tag            | Attributes                                                           |
    | -------------- | -------------------------------------------------------------------- |
    | `<a>`          | `href`, `hreflang`                                                   |
    | `<abbr>`       |                                                                      |
    | `<acronym>`    |                                                                      |
    | `<b>`          |                                                                      |
    | `<bdi>`        |                                                                      |
    | `<bdo>`        | `dir`                                                                |
    | `<blockquote>` | `cite`                                                               |
    | `<br>`         |                                                                      |
    | `<caption>`    |                                                                      |
    | `<center>`     |                                                                      |
    | `<cite>`       |                                                                      |
    | `<code>`       |                                                                      |
    | `<col>`        | `align`, `char`, `charoff`, `span`                                   |
    | `<colgroup>`   | `align`, `char`, `charoff`, `span`                                   |
    | `<dd>`         |                                                                      |
    | `<del>`        | `cite`, `datetime`                                                   |
    | `<details>`    |                                                                      |
    | `<div>`        |                                                                      |
    | `<dl>`         |                                                                      |
    | `<dt>`         |                                                                      |
    | `<em>`         |                                                                      |
    | `<h1>`         |                                                                      |
    | `<h2>`         |                                                                      |
    | `<h3>`         |                                                                      |
    | `<h4>`         |                                                                      |
    | `<h5>`         |                                                                      |
    | `<h6>`         |                                                                      |
    | `<hgroup>`     |                                                                      |
    | `<hr>`         | `align`, `size`, `width`                                             |
    | `<i>`          |                                                                      |
    | `<img>`        | `align`, `alt`, `height`, `src`, `width`                             |
    | `<ins>`        | `cite`, `datetime`                                                   |
    | `<kbd>`        |                                                                      |
    | `<li>`         |                                                                      |
    | `<mark>`       |                                                                      |
    | `<ol>`         | `start`                                                              |
    | `<p>`          |                                                                      |
    | `<pre>`        |                                                                      |
    | `<q>`          | `cite`                                                               |
    | `<rp>`         |                                                                      |
    | `<rt>`         |                                                                      |
    | `<rtc>`        |                                                                      |
    | `<ruby>`       |                                                                      |
    | `
`          |                                                                      |
    | `<samp>`       |                                                                      |
    | `<small>`      |                                                                      |
    | `<span>`       |                                                                      |
    | `<strike>`     |                                                                      |
    | `<strong>`     |                                                                      |
    | `<sub>`        |                                                                      |
    | `<summary>`    |                                                                      |
    | `<sup>`        |                                                                      |
    | `<table>`      | `align`, `char`, `charoff`, `summary`                                |
    | `<tbody>`      | `align`, `char`, `charoff`                                           |
    | `<td>`         | `align`, `char`, `charoff`, `colspan`, `headers`, `rowspan`          |
    | `<th>`         | `align`, `char`, `charoff`, `colspan`, `headers`, `rowspan`, `scope` |
    | `<thead>`      | `align`, `char`, `charoff`                                           |
    | `<time>`       |                                                                      |
    | `<tr>`         | `align`, `char`, `charoff`                                           |
    | `<tt>`         |                                                                      |
    | `<u>`          |                                                                      |
    | `<ul>`         |                                                                      |
    | `<var>`        |                                                                      |
    | `<wbr>`        |                                                                      |

class CustomFieldDescriptionField(CommentField):
    @property
    def default_helptext(self):
        return "Also used as the help text when editing models using this custom field.<br>" + super().default_helptext


    description = CustomFieldDescriptionField(
        label="Description",
    note = CommentField()
    description = models.TextField(
        blank=True,
        help_text="Markdown formatting and a limited subset of HTML are supported",
    )
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
    {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"},
    {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"},
    {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"},
    {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"},
    {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"},
[[package]]
name = "nh3"
version = "0.2.15"
description = "Python bindings to the ammonia HTML sanitization library."
optional = false
python-versions = "*"
files = [
    {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0"},
    {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3"},
    {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97"},
    {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28"},
    {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf"},
    {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911"},
    {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf"},
    {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7"},
    {file = "nh3-0.2.15-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305"},
    {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770"},
    {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"},
    {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d"},
    {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5"},
    {file = "nh3-0.2.15-cp37-abi3-win32.whl", hash = "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601"},
    {file = "nh3-0.2.15-cp37-abi3-win_amd64.whl", hash = "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e"},
    {file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"},
]

content-hash = "136fa4c518d4206074a6f43319274580a9fd1c9ccc7e32b8045e767a0f8bcd12"
# HTML sanitization
nh3 = "~0.2.15"