Résumé

Gitea: Repository Migration Follows Git HTTP Redirects After URL Allow/Block Validation, Enabling Internal Git Repository Exfiltration

Détails de l’avis

Repository Migration Follows Git HTTP Redirects After URL Allow/Block Validation, Enabling Internal Git Repository Exfiltration

Summary

Gitea validates the user-supplied repository migration URL, but the actual clone and later mirror fetch operations are performed by the Git command-line client without disabling HTTP redirects. Git's default http.followRedirects=initial follows the first redirect and then uses the redirected URL as the base for later repository object requests.

This creates a URL-policy bypass, SSRF, and repository exfiltration primitive. A low-privileged authenticated user can submit an allowed public Git URL that redirects the Gitea server to an otherwise blocked or internal Git HTTP(S) endpoint. Local validation confirmed that Git followed a first-hop redirect to 127.0.0.1, fetched Git objects, and completed the clone; git -c http.followRedirects=false clone blocked the same path.

The main impact is internal Git repository exfiltration into an attacker-controlled Gitea repository. Pull mirrors increase risk because scheduled git fetch --tags operations can keep following the redirect and collect future internal commits.

This is High severity by default for internet-accessible instances with migrations enabled, and can become Critical where internal repositories contain deploy keys, CI/CD secrets, cloud credentials, Terraform state, kubeconfigs, signing material, or production configuration. Direct unauthenticated exploitation, direct Gitea server RCE, arbitrary file:// import, and default Actions runner execution are not confirmed.

Technical Root Cause Analysis

The root cause is a validation/enforcement mismatch across a trust boundary. This should be treated as a product security issue rather than a pure deployment misconfiguration: deployment choices influence reachability and impact, but Gitea applies policy to the originally submitted URL while the Git subprocess is allowed to reach a different effective URL after redirection.

Gitea validates the original migration URL:

  • C:\Users\One\Desktop\gitea_new\gitea\routers\web\repo\migrate.go:180 parses the submitted web migration clone address.
  • C:\Users\One\Desktop\gitea_new\gitea\routers\web\repo\migrate.go:182 calls migrations.IsMigrateURLAllowed.
  • C:\Users\One\Desktop\gitea_new\gitea\routers\api\v1\repo\migrate.go:101 parses the submitted API migration clone address.
  • C:\Users\One\Desktop\gitea_new\gitea\routers\api\v1\repo\migrate.go:103 calls migrations.IsMigrateURLAllowed.

The URL validator resolves and checks the initially supplied host:

  • C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:44 defines IsMigrateURLAllowed.
  • C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:75 rejects unsupported schemes.
  • C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:79 extracts the host.
  • C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:85 performs DNS resolution.
  • C:\Users\One\Desktop\gitea_new\gitea\services\migrations\migrate.go:86 enforces the allow/block list on the originally resolved host.

After this validation, the actual repository data transfer is delegated to Git:

  • C:\Users\One\Desktop\gitea_new\gitea\services\repository\migrate.go:93 calls gitrepo.CloneExternalRepo with opts.CloneAddr.
  • C:\Users\One\Desktop\gitea_new\gitea\modules\gitrepo\clone.go:13 delegates to git.Clone.
  • C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:123 constructs a git clone command.
  • C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:125 conditionally sets only http.sslVerify=false.
  • C:\Users\One\Desktop\gitea_new\gitea\modules\git\repo.go:154 appends the source and destination arguments.

No equivalent network policy is applied to the final URL reached by Git after HTTP redirection. The protected migration HTTP client exists for service-specific migration API calls, but it is not used for the raw Git clone/fetch operation:

  • C:\Users\One\Desktop\gitea_new\gitea\services\migrations\http_client.go:16 defines NewMigrationHTTPClient.
  • C:\Users\One\Desktop\gitea_new\gitea\services\migrations\http_client.go:27 applies a host-matching dialer.

That protection does not wrap git clone or git fetch.

The behavior depends on Git's documented redirect handling. The current Git documentation states that http.followRedirects=initial follows the initial request redirect and uses the redirected URL as the base for follow-up requests. The default value is initial: https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpfollowRedirects

This means the effective enforcement point is not the same as the actual network sink. Gitea checks https://attacker.example/repo.git; Git later retrieves http://127.0.0.1:PORT/internal.git/... or another internal target selected by the redirector.

For mirrors, the issue extends beyond initial migration:

  • C:\Users\One\Desktop\gitea_new\gitea\services\repository\migrate.go:177 creates a mirror record when migration is requested as a mirror.
  • C:\Users\One\Desktop\gitea_new\gitea\services\repository\migrate.go:188 stores the sanitized original remote address.
  • C:\Users\One\Desktop\gitea_new\gitea\services\mirror\mirror_pull.go:120 later performs mirror synchronization with git fetch --tags.
  • C:\Users\One\Desktop\gitea_new\gitea\services\mirror\mirror_pull.go:126 fetches from the configured remote.

Because the mirror sync path also delegates to Git, a redirecting remote can continue to redirect scheduled fetches into an internal target.

Affected Assets & Attack Surface

Affected features are URL-based repository migration, API repository migration, pull mirror creation through migration, and scheduled pull mirror synchronization. The relevant entrypoints are POST /repo/migrate for signed-in web users (C:\Users\One\Desktop\gitea_new\gitea\routers\web\web.go:1062) and POST /api/v1/repos/migrate for API-token users (C:\Users\One\Desktop\gitea_new\gitea\routers\api\v1\api.go:1170).

Important preconditions are:

  • Repository migrations are enabled, which is the default in code and example configuration (C:\Users\One\Desktop\gitea_new\gitea\modules\setting\repository.go:181, C:\Users\One\Desktop\gitea_new\gitea\custom\conf\app.example.ini:1078).
  • The attacker has a low-privileged account or the instance permits self-registration. Default/example service settings do not force registration confirmation by default.
  • For persistent exfiltration, pull mirrors must be enabled (C:\Users\One\Desktop\gitea_new\gitea\modules\setting\mirror.go:20).
  • The Gitea server can reach internal Git HTTP(S) services that the attacker cannot reach directly.

High-value targets include internal Gitea, GitLab, GitHub Enterprise, Bitbucket, cgit, git-http-backend, and static bare Git repositories, especially repositories containing CI/CD definitions, deployment manifests, IaC, credentials, or production configuration.

The key trust boundary is that low-privileged user input influences server-side Git network access. Gitea validates the initially supplied URL, then hands control to a Git subprocess whose redirected destination is not constrained by the same allow/block policy. An attacker can discover exposure by using a controlled redirector, observing outbound Git requests, and testing whether scheduled mirror requests continue to arrive.

Exploitation Walkthrough

Scenario 1: One-time internal repository exfiltration

  1. The attacker obtains a low-privileged Gitea account. On instances with open registration, this may require only creating a user.
  2. The attacker hosts a public HTTP endpoint that appears to be a Git remote, for example https://attacker.example/public.git.
  3. The attacker configures the endpoint to respond to the initial Git discovery request with an HTTP redirect to an internal target, for example `http://internal-git.company.local/team/private.git/i

Références