RepositoryPipeline allows importing of local git repos
HackerOne report #1685822 by vakzz
on 2022-08-30, assigned to @dcouture:
Report | Attachments | How To Reproduce
Report
Summary
When importing a project via the BulkImports, the response field httpUrlToRepo
from the client is used to fetch the repo:
def load(context, data)
url = data['httpUrlToRepo']
return unless url.present?
url = url.sub("://", "://oauth2:#{context.configuration.access_token}@")
project = context.portable
Gitlab::UrlBlocker.validate!(url, allow_local_network: allow_local_requests?, allow_localhost: allow_local_requests?)
project.ensure_repository
project.repository.fetch_as_mirror(url)
end
Gitlab::UrlBlocker.validate
is called, but since no schemas are passed in it allows any (such as file) so long as the rest of the url is valid.
This means that if a url such as file://aw.rs/var/opt/gitlab/git-data/repositories/[@]hashed/b1/74/b174103b399555239923697fbe124faa61de4d441bd5c5678275eb0a5a27a562.git
is supplied, this will end up being used by git fetch, eg:
$ git fetch file://aw.rs/var/opt/gitlab/git-data/repositories/[@]hashed/b1/74/b174103b399555239923697fbe124faa61de4d441bd5c5678275eb0a5a27a562.git
fatal: '/var/opt/gitlab/git-data/repositories/[@]hashed/b1/74/b174103b399555239923697fbe124faa61de4d441bd5c5678275eb0a5a27a562.git' does not appear to be a git repository
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
This allows an attacker to import any local repository that the current machine has access to if the path is known.
The storage path for projects in gitlab is just based on a configurable folder combined with a bucketed sha2 hash of the id, eg for project 38006449 the Digest::SHA2.hexdigest("38006449")
is b174103b399555239923697fbe124faa61de4d441bd5c5678275eb0a5a27a562
so the path will be at [@]hashed/b1/74/b174103b399555239923697fbe124faa61de4d441bd5c5678275eb0a5a27a562.git
.
This can then be used to import any gitlab repository via the project id by calculating the path, such as the gitlab ctf project!
{gitlab-bounty-flag-7a3f26698d2ef146843d7209e5efc8ec}
Steps to reproduce
- Create a private project with User A and edit the readme file, make note of the project id
- Download and edit line 99 with the new path to the repository above (replace
b1/74/b174103b399555239923697fbe124faa61de4d441bd5c5678275eb0a5a27a562
with the newsha2[0:2]/sha2[2:4]/sha2
) - Run the server with
FLASK_APP=fake_server.py FLASK_ENV=development flask run
- Start ngrok with
ngrok http 500
- User B, visit https://gitlab.com/groups/new#import-group-pane and enter your ngrok url, any token and hit connect
- In the browser console, replace
"destination_namespace":"vakzz"
with your gitlab username (or a group you have access too) in the code below and run it:
await fetch("/import/bulk_imports.json", { method: "POST", headers: { "X-CSRF-Token": document.querySelector("[name=csrf-token]").content, "Content-Type": "application/json" }, body: `{"bulk_import":[{"source_type":"project_entity","source_full_path":"group1/project1","destination_namespace":"vakzz","destination_slug":"some_project_z_${Math.floor(Math.random() * 10000)}"}]}` });
- After a few minutes you should see a new project appear
- Initially it will just show
No repository
- After another minute or so the project will either show
The repository for this project is empty
or it will be a clone of the project from User A - If you see
The repository for this project is empty
then just repeat the fetch call again, it can take a few tries to end up on the same server as the victim (I think that's what is happening)
This can also be done with a Helm install of gitlab using the base path of /home/git/repositories
or using the omnibus edition, but you will need to check where the repositories are located on disk and use that as the base path.
Impact
Allows an attacked to clone any repo on gitlab with just the project id
Examples
Example of me cloning the gitlab ctf project - https://gitlab.com/vakzz-h1/secret_ctf_5401/-/blob/main/you/found/id/flag.txt
What is the current bug behavior?
The RepositoryPipeline
allows for arbitrary url protocols to be passed to project.repository.fetch_as_mirror(url)
What is the expected correct behavior?
It should be restricted to https/git/ssh
Relevant logs and/or screenshots
Output of checks
This bug happens on GitLab.com
Impact
Allows an attacked to clone any repo on gitlab with just the project id
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
Please add reproducibility information to this section: