SSRF protection: Handle IP ranges in the IP validation

What does this MR do and why?

In https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4137+ we've introduced the SSRF filtering for the virtual registry and dependency proxy. Later in Add outbound allowlist to allowed URIs for SSRF... (!188915 - merged) we've added support for allowed URIs. However, the IP validation was missing the case when the allowed URI is a IP range.

This MR adds the changes to the IP validation to handle properly IP ranges.

References

Dependency proxy / Virtual registry SSRF check ... (#562702 - closed)

Screenshots or screen recordings

No.

How to set up and validate locally

  1. To simplify the verification allow local IP addresses in the virtual registries settings and not allow local IP addressed for SSRF filtering in the workhorse.
diff --git a/ee/app/models/virtual_registries/packages/maven/upstream.rb b/ee/app/models/virtual_registries/packages/maven/upstream.rb
index 709d860e6d23..a5a3074883a6 100644
--- a/ee/app/models/virtual_registries/packages/maven/upstream.rb
+++ b/ee/app/models/virtual_registries/packages/maven/upstream.rb
@@ -23,8 +23,8 @@ class Upstream < ApplicationRecord
         validates :group, top_level_group: true, presence: true
         validates :url,
           addressable_url: {
-            allow_localhost: false,
-            allow_local_network: false,
+            allow_localhost: true,
+            allow_local_network: true,
             dns_rebind_protection: true,
             enforce_sanitization: true
           },
diff --git a/ee/lib/api/concerns/virtual_registries/packages/endpoint.rb b/ee/lib/api/concerns/virtual_registries/packages/endpoint.rb
index 62bb17fc692a..7f9ed15ad939 100644
--- a/ee/lib/api/concerns/virtual_registries/packages/endpoint.rb
+++ b/ee/lib/api/concerns/virtual_registries/packages/endpoint.rb
@@ -70,8 +70,8 @@ def send_error_response_from!(service_response:)
               end

               def workhorse_upload_url(url:, upstream:)
-                allow_localhost = Gitlab.dev_or_test_env? ||
-                  Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+                allow_localhost = false # Gitlab.dev_or_test_env? ||
+                  # Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
                 # rubocop:disable Naming/InclusiveLanguage -- existing setting
                 allowed_endpoints = ObjectStoreSettings.enabled_endpoint_uris +
                   Gitlab::CurrentSettings.outbound_local_requests_whitelist
  1. Prepare the third party server

    $ mkdir simple-server
    $ cd simple-server
    $ touch Gemfile
    $ touch server.rb
    # Gemfile
    source 'https://rubygems.org'
    gem 'rackup', '~> 2.1'
    gem 'sinatra', '~> 4.0'
    
    # server.rb
    require 'sinatra'
    
    get '/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar' do
      'Hello world!'
    end
    $ bundle install
    $ ruby server.rb

    Copy the server URL with port

  2. Setup the virtual registry

    g = Group.first
    r = ::VirtualRegistries::Packages::Maven::Registry.create!(group: g, name: 'test')
    u = ::VirtualRegistries::Packages::Maven::Upstream.create!(group: g, url: '<Simple server URL and PORT>', name: 'test')
    VirtualRegistries::Packages::Maven::RegistryUpstream.create!(group: g, registry: r, upstream: u)
  3. Create the request to fetch a package

    $ curl --header "Private-Token: <PAT>" "http://gdk.test:3000/api/v4/virtual_registries/packages/maven/<r.id>/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar"

    It should fail with 403 Forbidden and the log entry should be similar to this:

    {"correlation_id":"01K4Q9PWXKE1ZFXJ8RQXNYBE25","error":"Get \"http://127.0.0.1:4567/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar\": dial tcp 127.0.0.1:4567: IP 127.0.0.1 is not allowed: loopback IPs are not allowed","level":"error","method":"GET","msg":"","time":"2025-09-09T15:12:44+02:00","uri":"/api/v4/virtual_registries/packages/maven/10/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar"}
  4. Add new IP range that covers the third party server to the list of allowed outbound IPs

    Visit http://gdk.test:3000/admin/application_settings/network#js-outbound-settings
    Add the IP range to the list of allowed outbound IPs. In my case the server has IP `127.0.0.1` and I added `127.0.0.0/8`
  5. Create the request to fetch a package

    $ curl --header "Private-Token: <PAT>" "http://gdk.test:3000/api/v4/virtual_registries/packages/maven/<r.id>/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar"

    This time it should succeed with 200 and the log entry should be similar to this:

    {"backend_id":"rails","body_limit":104857600,"content_type":"text/html;charset=utf-8","correlation_id":"01K4Q9Z2BN9JWQY4KQSBJF6ZMQ","duration_ms":142,"host":"gdk.test:3000","level":"info","method":"HEAD","msg":"access","proto":"HTTP/1.1","read_bytes":263,"referrer":"","remote_addr":"172.16.123.1:59372","remote_ip":"172.16.123.1","route":"^/api/","route_id":"api","status":200,"system":"http","time":"2025-09-09T15:17:11+02:00","ttfb_ms":61,"uri":"/api/v4/virtual_registries/packages/maven/10/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar","user_agent":"curl/8.13.0","written_bytes":12}
  6. Now, remove the cached entries from the upstream

    u.cache_entries.destroy_all
  7. Switch to master branch and create the request to fetch a package again

    $ curl --header "Private-Token: <PAT>" "http://gdk.test:3000/api/v4/virtual_registries/packages/maven/<r.id>/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar"

    The request fails with 502 Bad Gateway (IP validator can't handle the IP range correctly) and the log entry should be similar to this:

    {"correlation_id":"01K4QABEK1SDFY2CBJWV4R8QHH","error":"Get \"http://127.0.0.1:4567/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar\": dial tcp 127.0.0.1:4567: error resolving IP address for 127.0.0.0/8: lookup 127.0.0.0/8: no such host","level":"error","method":"HEAD","msg":"","time":"2025-09-09T15:23:57+02:00","uri":"/api/v4/virtual_registries/packages/maven/10/org/codehaus/mojo/exec-maven-plugin/3.0.0/exec-maven-plugin-3.0.0.jar"}

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #562702 (closed)

Edited by Dzmitry (Dima) Meshcharakou

Merge request reports

Loading