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
- 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
-
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.rbCopy the server URL with port
-
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) -
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 Forbiddenand 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"} -
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` -
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
200and 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} -
Now, remove the cached entries from the upstream
u.cache_entries.destroy_all -
Switch to
masterbranch 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)