### Summary
The `UploadsRewriter` does not validate the file name, allowing arbitrary files to be copied via directory traversal when moving an issue to a new project.
The pattern used to look for references is :
```
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}.freeze
```
This is used by the `UploadsRewriter` when copying an issue to also copy across the files:
```ruby
@text.gsub(@pattern) do |markdown|
file = find_file(@source_project, $~[:secret], $~[:file])
break markdown unless file.try(:exists?)
klass = target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader
moved = klass.copy_to(file, target_parent)
...
def find_file(project, secret, file)
uploader = FileUploader.new(project, secret: secret)
uploader.retrieve_from_store!(file)
uploader
end
```
As there is no restriction on what `file` can be, path traversal can be used to copy any file.
Demo
{F757226}
### Steps to reproduce
1. Create two projects
1. Add an issue with the following description:
```markdown

```
1. Move the issue to the second project
1. The file will have been copied to the project
### Impact
Allows an attacker to read arbitrary files on the server, including tokens, private data, configs, etc
### What is the current *bug* behavior?
The file name and path are not checked when copying an issue between projects
### What is the expected *correct* behavior?
The file or path should be validated before copying files.
### Output of checks
#### Results of GitLab environment info
```
System information
System: Ubuntu 18.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.7-ee
Revision: 2643fd87200
Directory: /opt/gitlab/embedded/service/gitlab-rails
DB Adapter: PostgreSQL
DB Version: 10.12
URL: http://gitlab-vm.local
HTTP Clone URL: http://gitlab-vm.local/some-group/some-project.git
SSH Clone URL: git@gitlab-vm.local: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
Allows an attacker to read arbitrary files on the server, including tokens, private data, configs, etc
{"id": "H1:827052", "vendorId": null, "type": "hackerone", "bulletinFamily": "bugbounty", "title": "GitLab: Arbitrary file read via the UploadsRewriter when moving and issue", "description": "### Summary\nThe `UploadsRewriter` does not validate the file name, allowing arbitrary files to be copied via directory traversal when moving an issue to a new project.\n\nThe pattern used to look for references is :\n```\n MARKDOWN_PATTERN = %r{\\!?\\[.*?\\]\\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\\)}.freeze\n```\n\nThis is used by the `UploadsRewriter` when copying an issue to also copy across the files:\n\n```ruby\n @text.gsub(@pattern) do |markdown|\n file = find_file(@source_project, $~[:secret], $~[:file])\n break markdown unless file.try(:exists?)\n\n klass = target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader\n moved = klass.copy_to(file, target_parent)\n...\n def find_file(project, secret, file)\n uploader = FileUploader.new(project, secret: secret)\n uploader.retrieve_from_store!(file)\n uploader\n end\n```\n\nAs there is no restriction on what `file` can be, path traversal can be used to copy any file.\n\nDemo\n{F757226}\n\n### Steps to reproduce\n\n1. Create two projects\n1. Add an issue with the following description:\n\n ```markdown\n\n```\n1. Move the issue to the second project\n1. The file will have been copied to the project\n\n### Impact\n\nAllows an attacker to read arbitrary files on the server, including tokens, private data, configs, etc\n\n### What is the current *bug* behavior?\n\nThe file name and path are not checked when copying an issue between projects\n\n### What is the expected *correct* behavior?\n\nThe file or path should be validated before copying files.\n\n### Output of checks\n#### Results of GitLab environment info\n\n```\nSystem information\nSystem:\t\tUbuntu 18.04\nProxy:\t\tno\nCurrent User:\tgit\nUsing RVM:\tno\nRuby Version:\t2.6.5p114\nGem Version:\t2.7.10\nBundler Version:1.17.3\nRake Version:\t12.3.3\nRedis Version:\t5.0.7\nGit Version:\t2.24.1\nSidekiq Version:5.2.7\nGo Version:\tunknown\n\nGitLab information\nVersion:\t12.8.7-ee\nRevision:\t2643fd87200\nDirectory:\t/opt/gitlab/embedded/service/gitlab-rails\nDB Adapter:\tPostgreSQL\nDB Version:\t10.12\nURL:\t\thttp://gitlab-vm.local\nHTTP Clone URL:\thttp://gitlab-vm.local/some-group/some-project.git\nSSH Clone URL:\tgit@gitlab-vm.local:some-group/some-project.git\nElasticsearch:\tno\nGeo:\t\tno\nUsing LDAP:\tno\nUsing Omniauth:\tyes\nOmniauth Providers:\n\nGitLab Shell\nVersion:\t11.0.0\nRepository storage paths:\n- default: \t/var/opt/gitlab/git-data/repositories\nGitLab Shell path:\t\t/opt/gitlab/embedded/service/gitlab-shell\nGit:\t\t/opt/gitlab/embedded/bin/git\n```\n\n## Impact\n\nAllows an attacker to read arbitrary files on the server, including tokens, private data, configs, etc", "published": "2020-03-23T10:54:31", "modified": "2020-04-27T16:15:59", "cvss": {"score": 0.0, "vector": "NONE"}, "cvss2": {}, "cvss3": {}, "href": "https://hackerone.com/reports/827052", "reporter": "vakzz", "references": [], "cvelist": [], "immutableFields": [], "lastseen": "2023-02-03T01:41:55", "viewCount": 629, "enchantments": {"dependencies": {}, "score": {"value": 0.4, "vector": "NONE"}, "backreferences": {"references": [{"type": "threatpost", "idList": ["THREATPOST:C249ACD6B53EBF0A2F149F42F6D9873D"]}]}, "exploitation": null, "vulnersScore": 0.4}, "_state": {"dependencies": 1675388583, "score": 1675388738}, "_internal": {"score_hash": "9bea10177d7563bfd48ef0a9eae94f63"}, "bounty": 20000.0, "bountyState": "resolved", "h1team": {"url": "https://hackerone.com/gitlab", "handle": "gitlab", "profile_picture_urls": {"small": "https://profile-photos.hackerone-user-content.com/variants/f0hovtq73f9ap815a0r1w42bocp4/363a6db6e9457491cded7d2812f94ec0b47eaf5e7109e90ea24882bcff798a49", "medium": "https://profile-photos.hackerone-user-content.com/variants/f0hovtq73f9ap815a0r1w42bocp4/e60fe2d979b041d2254f8a36a3d2d7a24d7c4a8ad33ea024d13fc56668c7c4f6"}}, "h1reporter": {"disabled": false, "username": "vakzz", "url": "/vakzz", "is_me?": false, "cleared": true, "hackerone_triager": false, "hacker_mediation": false}}