CVE-2022-27311: Server-Side Request Forgery (SSRF) (CWE-918)
value: Gibbon v3.4.4 and below allows attackers to execute a Server-Side Request Forgery (SSRF) via a crafted URL.
CWE description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
CVSS v3 base score: 9.8
Published: 2022-04-25T13:15Z
Repository: https://github.com/amro/gibbon
Language: Ruby
Files changed: CHANGELOG.md, lib/gibbon/api_request.rb, lib/gibbon/gibbon_helpers.rb, lib/gibbon/version.rb, spec/gibbon/gibbon_spec.rb
Commit date: 2022-02-24 16:10:06 -0500
Commit message: bump version, handle invalid API key DC

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index c844e80..8d65653 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,9 @@
 ## [Unreleased][unreleased]
 
+## [3.4.4] - 2022-02-24
+- Remove non-alpha characters when parsing datacenter from API keys to prevent potential attackers from injecting a domain via the API key. This would only be possible if one were using user-provided API keys (e.g. from a form, etc.).
 
 ## [3.4.3] - 2022-01-19
 - Support for Faraday 2.0, which requires new syntax for basic auth
 
 ## [3.4.2] - 2021-09-21
diff --git a/lib/gibbon/api_request.rb b/lib/gibbon/api_request.rb
index 7e376d1..b48a7a3 100644
--- a/lib/gibbon/api_request.rb
+++ b/lib/gibbon/api_request.rb
@@ -192,11 +192,9 @@ module Gibbon
       base_api_url + @request_builder.path
     end
 
     def base_api_url
       computed_api_endpoint = "https://#{get_data_center_from_api_key(self.api_key)}api.mailchimp.com"
-      raise Gibbon::GibbonError, "SSRF attempt" unless URI(computed_api_endpoint).host.include?("api.mailchimp.com")
-
       "#{self.api_endpoint || computed_api_endpoint}/3.0/"
     end
   end
 end
diff --git a/lib/gibbon/gibbon_helpers.rb b/lib/gibbon/gibbon_helpers.rb
index e229575..e7a614d 100644
--- a/lib/gibbon/gibbon_helpers.rb
+++ b/lib/gibbon/gibbon_helpers.rb
@@ -3,12 +3,16 @@ module Gibbon
     def get_data_center_from_api_key(api_key)
       # Return an empty string for invalid API keys so Gibbon hits the main endpoint
       data_center = ""
 
       if api_key && api_key["-"]
-        # Add a period since the data_center is a subdomain and it keeps things dry
-        data_center = "#{api_key.split('-').last}."
+        # Remove all non-alphanumberic characters in case someone attempts to inject
+        # a different domain into the API key (e.g. when consuming user form-provided keys)
+        # This approach avoids assuming a 3 letter prefix (e.g. is MC were to create 
+        # a us10 DC, this would continue to work), and will continue to hit MC's server
+        # rather than a would-be attacker's servers.
+        data_center = "#{api_key.split('-').last.gsub(/[^0-9a-z ]/i, '')}."
       end
 
       data_center
     end
   end
diff --git a/lib/gibbon/version.rb b/lib/gibbon/version.rb
index 35b89f5..6c9fc55 100644
--- a/lib/gibbon/version.rb
+++ b/lib/gibbon/version.rb
@@ -1,3 +1,3 @@
 module Gibbon
-  VERSION = "3.4.3"
+  VERSION = "3.4.4"
 end
diff --git a/spec/gibbon/gibbon_spec.rb b/spec/gibbon/gibbon_spec.rb
index 9234f8b..9b5e1e3 100644
--- a/spec/gibbon/gibbon_spec.rb
+++ b/spec/gibbon/gibbon_spec.rb
@@ -12,10 +12,11 @@ describe Gibbon do
 
     it "have no API by default" do
       @gibbon = Gibbon::Request.new
       expect(@gibbon.api_key).to be_nil
     end
+  
     it "sets an API key in the constructor" do
       @gibbon = Gibbon::Request.new(api_key: @api_key)
       expect(@gibbon.api_key).to eq(@api_key)
     end
 
@@ -161,14 +162,16 @@ describe Gibbon do
       @gibbon.api_endpoint = "https://us6.api.mailchimp.com"
       @request = Gibbon::APIRequest.new(builder: @gibbon)
       expect {@request.validate_api_key}.not_to raise_error
     end
 
-    it "raises with a valid SSRF attack" do
-      @api_key = "-attacker.net/test/?"
+    it "removes non-alpha characters from datacenter prefix" do
+      @api_key = "123-attacker.net/test/?"
       @gibbon.api_key = @api_key
-      expect {@gibbon.try.retrieve}.to raise_error(Gibbon::MailChimpError, /SSRF attempt/)
+      @gibbon.try
+      @request = Gibbon::APIRequest.new(builder: @gibbon)
+      expect(@request.api_url).to eq("https://attackernettest.api.mailchimp.com/3.0/try")
     end
   end
 
   describe "class variables" do
     let(:logger) { double(:logger) }

Vulnerable code:       raise Gibbon::GibbonError, "SSRF attempt" unless URI(computed_api_endpoint).host.include?("api.mailchimp.com")

        # Add a period since the data_center is a subdomain and it keeps things dry
        data_center = "#{api_key.split('-').last}."
  VERSION = "3.4.3"
    it "raises with a valid SSRF attack" do
      @api_key = "-attacker.net/test/?"
      expect {@gibbon.try.retrieve}.to raise_error(Gibbon::MailChimpError, /SSRF attempt/)
Fixed code: ## [3.4.4] - 2022-02-24
- Remove non-alpha characters when parsing datacenter from API keys to prevent potential attackers from injecting a domain via the API key. This would only be possible if one were using user-provided API keys (e.g. from a form, etc.).
        # Remove all non-alphanumberic characters in case someone attempts to inject
        # a different domain into the API key (e.g. when consuming user form-provided keys)
        # This approach avoids assuming a 3 letter prefix (e.g. is MC were to create 
        # a us10 DC, this would continue to work), and will continue to hit MC's server
        # rather than a would-be attacker's servers.
        data_center = "#{api_key.split('-').last.gsub(/[^0-9a-z ]/i, '')}."
  VERSION = "3.4.4"
  
    it "removes non-alpha characters from datacenter prefix" do
      @api_key = "123-attacker.net/test/?"
      @gibbon.try
      @request = Gibbon::APIRequest.new(builder: @gibbon)
      expect(@request.api_url).to eq("https://attackernettest.api.mailchimp.com/3.0/try")