Arbitrary API PUT requests via HTML injection in user's name
HackerOne report #2225710 by yvvdwf
on 2023-10-25, assigned to @kmorrison1:
Report
Hello,
Full name of a user is sanitized by the following function:
def sanitize_name
return unless self.name
self.name = self.name.gsub(%r{</?[^>]*>}, '')
end
The name can be any HTML tag without closing bracket.
When it is displayed in the navigation bar of gitlab, it is sanitized once more times using simple_sanitize
function which allows a, span
tags and class, href, ...
attributes.
Thus, a user's fullname can be <a class=x href=y
In a merge request page, we got this gadget:
const draftToggles = document.querySelectorAll('.js-draft-toggle-button');
if (draftToggles.length) {
draftToggles.forEach((draftToggle) => {
draftToggle.addEventListener('click', (e) => {
...
axios
.put(draftToggle.href, null, { params: { format: 'json' } })
This means that if a user's fullname is <a class=js-draft-toggle-button href=xxx
and this fullname is shown in a merge request page, then we can trick victim to execute any RESTful API PUT requests if the victim clicks on the fullname.
To easily capture victim's clicks, we can add fixed-top
class to create a transparent topmost layer, and tree-list-holder
class to hide this topmost layer on other pages (but the topmost shows only on a merge request page), for example, a<a class="fixed-top tree-list-holder js-draft-toggle-button" href=xxxxxx
Reproduce
The vulnerability allows attackers to execute any RESTful API PUT requests on behalf of victim. The following scenario is to execute PUT /projects/:id/transfer
, thus to allow attacker to steal a victim's project by escalating to the owner of the project.
-
step 0: as victim
- create a new, private project and note its ID, e.g.,
11111111
(we will steal this project: e.g., to be owner of the project)
- create a new, private project and note its ID, e.g.,
-
step 1: as attacker
- create a new group, note its ID, e.g.,
99999999
- invite victim as
Maintainer
of the group
- create a new group, note its ID, e.g.,
-
step 2: as attacker
- create a new project, edit its readme then commit to another branch, then create a new merge request from the new branch to the default one. The objective is to have a merge request page, e.g.,
https://gitlab.com/attacker/project-a/-/merge_requests/1
- goto attacker profile page (https://gitlab.com/-/profile), change "Full name" to:
a<a class="fixed-top tree-list-holder js-draft-toggle-button" href=/api/v4/projects/111111111/transfer?namespace=999999999
. Then clickUpdate profile settings
button. Important note: there must be no closed-bracket character>
in the full name. - go back to the merge request page,
https://gitlab.com/attacker/project-a/-/merge_requests/1
, you should notice a topmost transparent layer. - change the project to public or add victim as a project member so that victim can view the merge request page
- create a new project, edit its readme then commit to another branch, then create a new merge request from the new branch to the default one. The objective is to have a merge request page, e.g.,
-
step 3: as victim
- visit the merge request page, then click any where
You might now notice that the project 111111111
has been transfer to the group 999999999
and attacker becomes owner of the project.
Best regards,
yvvdwf
Impact
The vulnerability allows attackers to execute any RESTful API PUT requests on behalf of victim.
How To Reproduce
Please add reproducibility information to this section: