Yasin Soliman

Yasin Soliman

I'm Yasin, a security analyst from the UK, interested in web application testing and red team operations.

Yasin Soliman



Lightweight markup: a trio of persistent XSS in GitLab

Yasin SolimanYasin Soliman

Earlier this morning, the GitLab team issued a Security Release with updated versions 8.16.5, 8.15.6, and 8.14.9 for GitLab Community Edition (CE) and Enterprise Edition (EE).

No workarounds are available for the vulnerabilities patched in this Security Release. As such, it is recommended that all GitLab instances be upgraded immediately.

I identified three vulnerabilities – using the RubyDoc, reStructuredText, and Textile markup formats.

GitLab logo

GitLab is an application to code, test, and deploy code together. It provides Git repository management with fine grained access controls, code reviews, issue tracking, activity feeds, wikis, and continuous integration.

Inspired by HackerOne co-founder Jobert's quality string of high-severity reports, I spun up a Digital Ocean instance to experiment with the latest GitLab build.

Digital Ocean one-click apps

Finding persistent cross-site scripting vectors always presents an interesting challenge. Persistent (or stored) XSS is classified as OTG-INPVAL-002 in the OWASP Testing Guide, and is considered a high-severity vulnerability.

Having previously gained success with Markdown – the same markup language behind this blogpost and my HackerOne reports – I started by committing JavaScript payloads in README.md files.

Shubham provides an excellent set of Proof of Concept payloads here. However, I soon discovered that the Markdown parser used by GitLab was sanitising my input – was it game over for this type of bug?

Not to be defeated, I dived headfirst into the world of lightweight markup languages and doc syntax.


My first report focused on RubyDoc, or more specifically, the RDoc::Markup class, which parses plaintext documents and attempts to decompose them into their constituent parts.

For each report, I provided a custom Proof of Concept payload and steps to reproduce the vector. In the case of RubyDoc, the outline looked like this:

  1. Create a new GitLab project from the web UI
  2. Initialise the project by creating a README file
  3. Set the title to README.rdoc
  4. Paste the below payload into the file
  5. Commit the file to the project and click the link
XSS[JaVaScriPt:alert(document.domain)] <-- click to test


A few days ago, I revisited my GitLab instance and decided to try Textile – one of the many LMLs developed before Markdown. Lo and behold, it worked with a basic JavaScript link – here's the payload:

"Test link":javascript:alert(document.domain)


Finally, I turned my attention to reStructuredText and found that the link feature – albeit with some very strange syntax – could be used to store a persistent JavaScript payload.

`Test link`__.

__ javascript:alert(document.domain)


This post marks the launch of my new technical research blog – thanks for reading! I hope to share other interesting reports and public disclosures with the community in the weeks ahead.

Also: during the remediation period, GitLab experienced some issues with their production database cluster.

In addition to the excellent handling of these reports, I'd like to commend Brian and the entire GitLab team for the professionalism and transparency displayed throughout the database outage and postmortem process.

Research timeline

The following timestamps are provided in GMT. A comprehensive timeline can be found on the applicable HackerOne reports.

  • 2017-01-24 07:48 – RubyDoc XSS reported
  • 2017-01-24 15:05 – RubyDoc XSS triaged
  • 2017-02-11 12:40 – Textile and RST XSS reported
  • 2017-02-11 17:31 – Textile and RST XSS triaged
  • 2017-02-15 03:37 – reports resolved
  • 2017-02-15 05:17 – public disclosure requested
  • 2017-02-15 05:28 – public disclosure approved
Yasin Soliman

Yasin Soliman