Commiting directories containing LF character results in 500 errors in Web UI when viewing commit
:warning: **Please read [the process](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/developer.md) on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.**
**[HackerOne report #1937213](https://hackerone.com/reports/1937213)** by `cryptopone` on 2023-04-06, assigned to @ottilia_westerlund:
[Report](#report) | [Attachments](#attachments) | [How To Reproduce](#how-to-reproduce)
## Report
##### Summary
An attacker is able to add new directories containing the line feed character (`%0a` or `\n`). Once committed to a repository, attempting to view the details of the commit via the GitLab website will result in various 500 Internal Server Errors. Some of these activities include:
* Viewing changes inside of a merge request containing the malicious directory
* Viewing the malicious directory in the project/repository file browser
* Viewing the commit details containing the malicious directory
* Comparing branch revisions when one of the branches contain the malicious directory
##### Steps to reproduce
Note: Two accounts will be needed (Attacker and Victim).
###### Prerequisites (Victim Account):
1. Have the victim user log in and navigate to (http://gitlab.example.com/projects/new).
1. Create a new project (VictimProject) with `Internal` visibility and `Initialize repository with a README` checked.
1. Navigate to the project members page (http://gitlab.example.com/victim/victimproject/-/project_members) and use `Invite members` to invite the attacker as a developer for the project.
###### Reproduction Steps (Attacker with BurpSuite):
1. Have the attacker login, navigate to the VictimProject and create a new branch called `AttackerBranch` (via http://gitlab.example.com/victim/victimproject/-/branches/new).
1. The attacker will be redirected to http://gitlab.example.com/victim/victimproject/-/tree/AttackerBranch
1. Click the `+` dropdown button and select `New file` (redirects to http://gitlab.example.com/victim/victimproject/-/new/AttackerBranch/).
1. In BurpSuite use the Proxy -> Intercept tab and ensure `Intercept is on` is set.
1. Set the file name to `beforeafter/beforeafter/hiddenfile.txt`. For the file contents put in `Hidden attacker contents.`. Target branch should be `AttackerBranch`.
1. Press the `Commit changes` button and have BurpSuite intercept the POST request.
1. The POST request will use a url encoded body. Change the `file_name` from:
```
file_name=beforeafter%2Fbeforeafter%2Fhiddenfile.txt
```
by adding `%0a` between `before` and `after` for the two directories:
```
file_name=before%0aafter%2Fbefore%0aafter%2Fhiddenfile.txt
```
8. Forward the request to the server. Intercept can also be turned off at this point.
9. The page will reload and if done successfully you'll see a new message `"before after/before after/hiddenfile.txt" did not exist on "AttackerBranch"` and the commit message area will display a loading indicator.
10. If you press F12 to open Developer Tools and access the console you will also see several 500 errors.
11. Have the attacker create a new merge request by clicking the `Create merge request` in the green message `You pushed to AttackerBranch just now` (or by navigating to http://gitlab.example.com/victim/victimproject/-/merge_requests/new?merge_request%5Bsource_branch%5D=AttackerBranch). Then click `Create merge request` at the bottom of the page.
###### Reproduction Steps (Victim):
1. Navigate to the merge request created by the attacker (http://gitlab.example.com/victim/victimproject/-/merge_requests/1).
1. Click on the `Changes` tab to view the changes but note the `Error: Couldn't load some or all of the changes.` error message.
1. Try clicking on the commits tab, then on `Add new file` to view the commit changes and again on `Add new file` and note the resulting 500 error.
At this point the web UI will throw 500 Internal Server Errors on a variety of tasks, as seen when attempting to view the merge request changes:
* Compare Revisions (between AttackerBranch and main) - http://gitlab.example.com/victim/victimproject/-/compare?from=main&to=AttackerBranch when clicking the `Compare` button.
* Attempting to view the AttackerBranch and clicking on the `before after` directory to view its contents (http://gitlab.example.com/victim/victimproject/-/tree/AttackerBranch)
##### Impact
An attacker could disrupt regular processes such as reviewing merge requests or viewing portions of a project via the web UI.
My initial report uses the following reasoning for the CVSS score:
`AC:L`: Attacker can use the web UI to add a directory and modify the POST request before it reaches the server.
`PR:L`: Attacker requires an authenticated account but does not require elevated roles within a project.
`UI:N`: Attack can be accomplished without any user interaction.
`S:U`: Impact is localized to the exploitable component.
`C:N`: No confidential information is disclosed.
`I:N`: No integrity loss.
`A:L`: The commit details cannot be viewed in the Web UI but could still be reviewed using a local git repo.
##### Examples
Attached is a copy of the project I created on my self-hosted instance running 15.10.2 following these repro steps. It's possible to import this project file as a "GitLab Export" to see the merge request and resulting 500 errors.
[directories_with_lf_victim_victimproject_export.tar.gz](https://h1.sec.gitlab.net/a/d1861ae9-6b75-438c-a573-e67d2ec38e54/directories_with_lf_victim_victimproject_export.tar.gz)
Additionally, I created a project on GitLab.com and setup a similar project (though I only used a single account) to demonstrate the 500 errors as well. The project is currently Private but please let me know if you would like access.
https://gitlab.com/Cryptopone/crlf-directory-repro/-/tree/AttackerBranch?ref_type=heads
##### What is the current *bug* behavior?
If a directory containing a line feed character is committed to a project's repository, the web UI will throw a 500 Internal Server Error when any attempts are made to view the contents of the commit.
##### What is the expected *correct* behavior?
The web page should be displayed normally, without throwing a 500 error.
##### Relevant logs and/or screenshots
Attacker adds linefeed characters to the directory paths to the hidden file:

Response when attacker pushes the malicious directories and hidden file to the project repo:

Victim attempting to view changes inside of a merge request:

```
==> /var/log/gitlab/gitlab-rails/production.log <==
Gitlab::Git::CommandError (13:error streaming commits: cat-file get commit "6280e1a0a6ffab99ab5ea8b9ff21ea442fa953a1": object not found.):
lib/gitlab/git/wraps_gitaly_errors.rb:15:in `rescue in wrapped_gitaly_errors'
lib/gitlab/git/wraps_gitaly_errors.rb:6:in `wrapped_gitaly_errors'
lib/gitlab/git/repository.rb:377:in `log'
lib/gitlab/git/commit.rb:47:in `where'
app/models/repository.rb:168:in `commits'
lib/gitlab/code_navigation_path.rb:28:in `block in build'
lib/gitlab/utils/strong_memoize.rb:34:in `strong_memoize'
lib/gitlab/code_navigation_path.rb:26:in `build'
lib/gitlab/code_navigation_path.rb:16:in `full_json_path_for'
app/serializers/diff_file_entity.rb:66:in `block in <class:DiffFileEntity>'
<trimmed>
```
Victim attempting to compare revisions:

```
==> /var/log/gitlab/gitlab-rails/production.log <==
ActionView::Template::Error (undefined method `lines' for nil:NilClass):
1: - diff_file = viewer.diff_file
2: - blob = diff_file.blob
3: - total_lines = blob.lines.size
4: - total_lines -= 1 if total_lines > 0 && blob.lines.last.blank?
5: - if diff_view == :parallel
6: = render "projects/diffs/parallel_view", diff_file: diff_file, total_lines: total_lines
app/views/projects/diffs/viewers/_text.html.haml:3
app/views/projects/diffs/_viewer.html.haml:14
app/views/projects/diffs/_content.html.haml:11
app/views/projects/diffs/_file.html.haml:42
app/views/projects/diffs/_diffs.html.haml:39
app/views/projects/compare/show.html.haml:12
app/controllers/application_controller.rb:142:in `render'
app/controllers/projects/compare_controller.rb:37:in `show'
<trimmed>
```
Attempting to view the project repository or navigating into one of the malicious folders:

```
==> /var/log/gitlab/gitlab-rails/production.log <==
Gitlab::Git::CommandError (13:error streaming commits: cat-file get commit "035a86f727a8236a721ae4bccdd22ce1a5b54be5": object not found.):
lib/gitlab/git/wraps_gitaly_errors.rb:15:in `rescue in wrapped_gitaly_errors'
lib/gitlab/git/wraps_gitaly_errors.rb:6:in `wrapped_gitaly_errors'
lib/gitlab/git/repository.rb:377:in `log'
lib/gitlab/git/commit.rb:47:in `where'
lib/gitlab/git/commit.rb:97:in `last_for_path'
app/graphql/resolvers/last_commit_resolver.rb:14:in `resolve'
lib/gitlab/graphql/present/field_extension.rb:18:in `resolve'
lib/gitlab/graphql/tracers/timer_tracer.rb:20:in `trace'
<trimmed>
```
##### Output of checks
This bug happens on GitLab.com
###### Results of GitLab environment info
```
System information
System:
Proxy: no
Current User: git
Using RVM: no
Ruby Version: 3.0.5p211
Gem Version: 3.2.33
Bundler Version:2.3.15
Rake Version: 13.0.6
Redis Version: 6.2.11
Sidekiq Version:6.5.7
Go Version: unknown
GitLab information
Version: 15.10.2-ee
Revision: a54d6973eae
Directory: /opt/gitlab/embedded/service/gitlab-rails
DB Adapter: PostgreSQL
DB Version: 13.8
URL: http://gitlab.example.com
HTTP Clone URL: http://gitlab.example.com/some-group/some-project.git
SSH Clone URL: git@gitlab.example.com:some-group/some-project.git
Elasticsearch: no
Geo: no
Using LDAP: no
Using Omniauth: yes
Omniauth Providers:
GitLab Shell
Version: 14.18.0
Repository storages:
- default: unix:/var/opt/gitlab/gitaly/gitaly.socket
GitLab Shell path: /opt/gitlab/embedded/service/gitlab-shell
```
#### Impact
##### Impact
An attacker could disrupt regular processes such as reviewing merge requests or viewing portions of a project via the web UI.
My initial report uses the following reasoning for the CVSS score:
`AC:L`: Attacker can use the web UI to add a directory and modify the POST request before it reaches the server.
`PR:L`: Attacker requires an authenticated account but does not require elevated roles within a project.
`UI:N`: Attack can be accomplished without any user interaction.
`S:U`: Impact is localized to the exploitable component.
`C:N`: No confidential information is disclosed.
`I:N`: No integrity loss.
`A:L`: The commit details cannot be viewed in the Web UI but could still be reviewed using a local git repo.
## Attachments
**Warning:** Attachments received through HackerOne, please exercise caution!
* [directories_with_lf_victim_victimproject_export.tar.gz](https://h1.sec.gitlab.net/a/d1861ae9-6b75-438c-a573-e67d2ec38e54/directories_with_lf_victim_victimproject_export.tar.gz)
* [AttackerAddsLFtoDirectoriesFilePath.png](https://h1.sec.gitlab.net/a/0acc8e3f-ac9a-4e78-8067-395ac216f346/AttackerAddsLFtoDirectoriesFilePath.png)
* [AfterAttackerPushesFileToRepo.png](https://h1.sec.gitlab.net/a/784854ff-038c-4a96-8efa-8463d82ec03f/AfterAttackerPushesFileToRepo.png)
* [VictimViewingMergeRequestChanges.png](https://h1.sec.gitlab.net/a/807aa023-1ac0-4ee5-a274-57d7a6e647c5/VictimViewingMergeRequestChanges.png)
* [VictimAttemptingToCompareAttackerBranchWithMain.png](https://h1.sec.gitlab.net/a/f38301de-966f-4d8a-908c-9ea20ac9d24c/VictimAttemptingToCompareAttackerBranchWithMain.png)
* [VictimAttemptingToBrowseLFDirectory.png](https://h1.sec.gitlab.net/a/c64c424b-1655-4c3a-aafd-49971f286f13/VictimAttemptingToBrowseLFDirectory.png)
## How To Reproduce
Please add [reproducibility information] to this section:
1.
1.
1.
[reproducibility information]: https://about.gitlab.com/handbook/engineering/security/#reproducibility-on-security-issues
issue