# Sending Arbitrary Requests through Jupyter Notebooks on gitlab.com and Self-Hosted GitLab Instances

GitLab's Jupyter notebook renderer keeps data-* attributes, letting jquery-ujs fire arbitrary API requests (e.g. set status, grant maintainer) on victim click.

- Payout: hidden
- Severity: medium
- Program: GitLab
- Found by: iwis
- Disclosed: 2022-02-10
- Write-up quality: 7/10
- Original: https://hackerone.com/reports/970869
- Library page: https://bugbountylibrary.com/r/h1%3A970869
- Tags: area:web domain:web impact:integrity impact:privesc kind:bounty platform:hackerone sev:medium tech:ruby technique:source-review vuln:access-control vuln:csrf vuln:privilege-escalation

**Target:** Jupyter notebook (.ipynb) rich HTML rendering in GitLab project Files view
**Root cause:** GitLab's notebook HTML sanitizer strips most tags/attributes but retains data-* attributes; jquery-ujs interprets data-method/data-remote/data-url/data-params as an unobtrusive AJAX request.
**Steps:**
1. Create/select a GitLab project.
2. Upload `exploit.ipynb` with `cells[0].outputs[0].data['text/html']` containing `<select data-method="put" data-params="message=p0wn3d" data-remote="true" data-url="/api/v4/user/status">...`
3. Open the file in the Files view and click an option in the rendered select.
4. Reload and observe victim status changed to `p0wn3d`.
5. For privesc: payload `data-url="/api/v4/projects/<PROJECT_ID>/members" data-params="user_id=<ATTACKER_ID>&access_level=40" data-method="put"` grants maintainer.
**How found:** Noticed notebook rich HTML output retains data-* attributes after sanitization, then recognized jquery-ujs (bundled by GitLab) as a gadget that converts those attributes into AJAX requests.
**Exploitation:** Attacker with project write access uploads a crafted .ipynb; victim opens it and clicks the rendered widget, causing jquery-ujs to issue an authenticated PUT/POST/DELETE to the GitLab API with attacker-controlled params. On gitlab.com CSP limits targets to same-origin API; self-hosted has no such limit.
**Escalation:** Chains a low-priv developer account to maintainer by targeting the members API with access_level=40.
**Insight:** When a sanitizer allows data-* attributes, check for JS libraries in the app (jquery-ujs, Stimulus, Alpine) that treat data-* as behavior directives — they turn inert markup into a request/action gadget.
