Skip to content

Arbitrary API PUT requests via HTML injection in user's name

Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #2225710 by yvvdwf on 2023-10-25, assigned to @kmorrison1:

Report | How To Reproduce

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)
  • step 1: as attacker

    • create a new group, note its ID, e.g., 99999999
    • invite victim as Maintainer of the group
  • 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 click Update 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
  • 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: