Persistent XSS at Merge Request "Resolve conflicts" page using source branch name as payload
HackerOne report #486772 by valis_
on 2019-01-26, assigned to dappelt
:
Summary:
The vulnerability should be technically classified as a Vue client-side template injection, but since it's trivial to gain full Javascript execution from Vue expressions, it leads straight to a persistent XSS.
Description:
Vue template expression is injected in app/views/projects/merge_requests/conflicts/_submit_form.html.haml:
- branch_name = link_to @merge_request.source_branch, project_tree_path(@merge_request.project, @merge_request.source_branch), class: "ref-name"
- translation =_('You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}') % { use_ours: '<code>Use Ours</code>', use_theirs: '<code>Use Theirs</code>', branch_name: branch_name }
..
.resolve-info
= translation.html_safe
The whole #conflicts div is parsed as Vue template because there is no 'template' property and no render() method specified when creating a Vue object in app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
link_to correctly escapes html entities, but does not protect against template injection.
Putting following payload in the source branch name will execute js code:
{{toString.constructor('alert(/xss/)')()}}
Steps To Reproduce:
- Login to Gitlab
- Create a new project.
- Create a text file, e.g. README on a master branch.
- Create a new branch with a following name:
fix-critical-issue-long-name-{{toString.constructor('alert(/xss/)')()}}
- Commit a change to the first line of the README file in a newly created branch.
- Commit a different change to the first line of the README file in a master branch to create a conflict.
- Create a merge request for our new branch
- Click 'Resolve conflicts'
- You'll see a javascript alert from the executed payload:
Tested against my own gitlab instance: gitlab-ee version 11.7.0-ee.0 (Ubuntu package from packages.gitlab.com) with Chrome 71 and Firefox 64.
I did not try it at gitlab.com, but it's most likely vulnerable as well.
The payload provided above uses single quotes instead of backticks shown in the screenshot - it works exactly the same, but backticks are problematic in Markdown.
Above steps use just one account with full project access for simplicity, playing a role of both attacker and victim.
You'll find a more realistic scenario below.
Example attack scenario:
- Attacker has a developer-level access to a project owned by the victim.
- Attacker creates a new branch with the xss payload as described above:
- Attacker creates a merge request with an intentional conflict:
- Attacker sends the victim (project owner) a link to the the conflict resolution page, e.g.
https://gitlabhost/group1/project1/merge_requests/1/conflicts
(the link seems completely innocent)
or just waits for the victim to examine the new merge request by him or herself.
Note that thanks to a truncation of the branch name at the merge request page victim is less likely to
notice anything suspicious about the merge request, especially if the request's name, description and
branch prefix are carefully prepared to match routine requests from the target project.
- Victim visits conflict resolution page and xss payload executes:
- Attacker can now perform all the actions that victim is authorized to perform.
See 'Impact' for more details.
Impact
Attacker can perform all the actions that victim (person reviewing merge request) is authorized to perform, including:
- full access to all victim's repositories
- adding API keys to achieve persistence
- changing victim's password through the 'Forgot my password' feature (the fact that this is possible is IMO a weakness by itself and I'll submit a separate report about that)
etc
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
Links
- Security issue: https://dev.gitlab.org/gitlab/gitlabhq/issues/2819