Commiting directories containing LF character results in 500 errors in Web UI when viewing commit

Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #1937213 by cryptopone on 2023-04-06, assigned to @ottilia_westerlund:

Report | Attachments | 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).
  2. Create a new project (VictimProject) with Internal visibility and Initialize repository with a README checked.
  3. 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).
  2. The attacker will be redirected to http://gitlab.example.com/victim/victimproject/-/tree/AttackerBranch
  3. Click the + dropdown button and select New file (redirects to http://gitlab.example.com/victim/victimproject/-/new/AttackerBranch/).
  4. In BurpSuite use the Proxy -> Intercept tab and ensure Intercept is on is set.
  5. Set the file name to beforeafter/beforeafter/hiddenfile.txt. For the file contents put in Hidden attacker contents.. Target branch should be AttackerBranch.
  6. Press the Commit changes button and have BurpSuite intercept the POST request.
  7. 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  
  1. Forward the request to the server. Intercept can also be turned off at this point.
  2. 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.
  3. If you press F12 to open Developer Tools and access the console you will also see several 500 errors.
  4. 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).
  2. Click on the Changes tab to view the changes but note the Error: Couldn't load some or all of the changes. error message.
  3. 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:

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

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:
AttackerAddsLFtoDirectoriesFilePath.png

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

Victim attempting to view changes inside of a merge request:
VictimViewingMergeRequestChanges.png

==> /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:
VictimAttemptingToCompareAttackerBranchWithMain.png

==> /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:
VictimAttemptingToBrowseLFDirectory.png

==> /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!

How To Reproduce

Please add reproducibility information to this section: