HTML injection in code navigation leads to ATO

⚠️ 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 #3297413 by joaxcar on 2025-08-13, assigned to @katwu:

Report | Attachments | How To Reproduce

Report

Summary

Hi I saw that there is a patch for #3247096 in version 18.2.2. See MR here https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/5203 (private)
and commit here: 61f1c480

The fix only sanitizes the data using the Gitlab default configuration for DOMPurify, which I have proven before can be bypassed. This fix will prevent full XSS that was previously possible but still allow for ATO using script gadgets through data attributes

See my comments in #3247096 on how the root cause for the issue here is that you take the textContent of one element and put this into innerHTML of another element

wrapNodes(elm.textContent ...)  
...  
 wrapper.innerHTML = wrapSpacesWithSpans(text);  

This is problematic as textContent will convert HTML entities to HTML. You should probably just do elm.innerHTML in the first step as well, this will keep all entities as entities

(NOTE see the previous report for technical info on how this could be abused without the "override" in the POC, I can copy paste it over here if needed, but I think this keeps it cleaner)

Steps to reproduce

see #3247096 for details on how the full flow works and why we need to use an override in devtools. This is just to be able to prove the point here

Do this in Chrome!

  1. Import this project xss_bypas.tar.gz
  2. Go to the project and trigger a new pipeline manually
  3. Go to the project and click on the xss.py file.
  4. now open devtools and go to the Network tab
  5. Refresh the page
  6. You will see a call to *something*....?file_type=lsif right click this call and pick Overwrite Content
  7. There will be a small pop-up asking you where to store the Chrome override files, pick any directory
  8. Now in the small editor that opens, delete all text that is there and replace it with
[{"start_line":0,"start_char":1,"definition_path":"hej.py#L5","hover":[{"value":""}]}]  

Click ctrl-s to save
9. Refresh the page again (with devtools still open)
The string in the Python file should now disappear and be replaced by a big gray square.
10.Click the gray square
11. A new page will open, wait for about 15 seconds for the page to close again
12. Now click the gray square again, nothing will happen
13. Go to https://gitlab.com/-/profile/emails and see that you have a new email on your account.
14. Remove the email so that others can test it

<video redacted>

Impact

HTML injection leads to full ATO

What is the current bug behavior?

The fix use sanatize from DOMPurify which can still be bypassed in some situations

What is the expected correct behavior?

There is point in using textContent an the text should never contain HTML. Thus its better to use innerHTML to also get the text out of the element. This will prevent all injections

Output of checks

This bug happens on GitLab.com

Impact

HTML injection leads to full ATO

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section:

3.

Edited by ADandy