CVE-2021-25972: Server-Side Request Forgery (SSRF) (CWE-918)
value: In Camaleon CMS, versions 2.1.2.0 to 2.6.0, are vulnerable to Server-Side Request Forgery (SSRF) in the media upload feature, which allows admin users to fetch media files from external URLs but fails to validate URLs referencing to localhost or other internal servers. This allows attackers to read files stored in the internal server.
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: 4.9
Published: 2021-10-20T12:15Z
Repository: https://github.com/owen2345/camaleon-cms
Language: Ruby
Files changed: app/controllers/camaleon_cms/admin/media_controller.rb, config/locales/camaleon_cms/admin/en.yml
Commit date: 2021-10-12 00:02:51 -0500
Commit message: Disallow uploads from localhost

Diff: diff --git a/app/controllers/camaleon_cms/admin/media_controller.rb b/app/controllers/camaleon_cms/admin/media_controller.rb
index ae3240705..9f86988dd 100644
--- a/app/controllers/camaleon_cms/admin/media_controller.rb
+++ b/app/controllers/camaleon_cms/admin/media_controller.rb
@@ -2,10 +2,19 @@ require 'will_paginate/array'
 class CamaleonCms::Admin::MediaController < CamaleonCms::AdminController
   skip_before_action :admin_logged_actions, except: [:index, :download_private_file], raise: false
   skip_before_action :verify_authenticity_token, only: :upload, raise: false
   before_action :init_media_vars, except: :download_private_file
 
+  LOCALHOST_DOMAIN_MATCHER = %r{
+    localhost|
+    127\.0\.0\.1|
+    0\.0\.0\.0|
+    0x7f\.0x0\.0x0\.0x1| # hex encoding
+    0177\.0\.0\.01| # octal encoding
+    2130706433 # dword encoding
+  }x
+
   # render media section
   def index
     authorize! :manage, :media
     @show_file_actions = true
     @files = @tree.paginate(page: params[:page], per_page: 100)
@@ -65,11 +74,15 @@ class CamaleonCms::Admin::MediaController < CamaleonCms::AdminController
         render inline: ""
       when 'crop_url'
         unless params[:url].start_with?('data:')
           params[:url] = (params[:url].start_with?('http') ? '' : current_site.the_url(locale: nil)) + params[:url]
         end
-        r = cama_tmp_upload( params[:url], formats: params[:formats], name: params[:name])
+        r = if local_url?(params[:url])
+              { error: t("camaleon_cms.admin.media.local_upload_denied") }
+            else
+              cama_tmp_upload( params[:url], formats: params[:formats], name: params[:name])
+            end
         unless r[:error].present?
           params[:file_upload] = r[:file_path]
           sett = {remove_source: true}
           sett[:same_name] = true if params[:same_name].present?
           sett[:name] = params[:name] if params[:name].present?
@@ -78,10 +91,14 @@ class CamaleonCms::Admin::MediaController < CamaleonCms::AdminController
           render inline: r[:error]
         end
     end
   end
 
+  def local_url?(url)
+    url.try :match?, LOCALHOST_DOMAIN_MATCHER
+  end
+
   # upload files from media uploader
   def upload(settings = {})
     params[:dimension] = nil if params[:skip_auto_crop].present?
     f = {error: "File not found."}
     if params[:file_upload].present?
diff --git a/config/locales/camaleon_cms/admin/en.yml b/config/locales/camaleon_cms/admin/en.yml
index 0526000aa..c18310e3a 100644
--- a/config/locales/camaleon_cms/admin/en.yml
+++ b/config/locales/camaleon_cms/admin/en.yml
@@ -210,10 +210,11 @@ en:
         folder_name: 'Folder Name'
         folder_file_name: 'Folder or File Name'
         reload: 'Reload'
         clear_cache: 'Clear Cache'
         name_required: 'File name is required'
+        local_upload_denied: 'Cannot upload from localhost'
       menus:
         menus: Menus
         link_url: 'Link URL'
         target: 'Target'
         targets:

Vulnerable code:         r = cama_tmp_upload( params[:url], formats: params[:formats], name: params[:name])
Fixed code:   LOCALHOST_DOMAIN_MATCHER = %r{
    localhost|
    127\.0\.0\.1|
    0\.0\.0\.0|
    0x7f\.0x0\.0x0\.0x1| # hex encoding
    0177\.0\.0\.01| # octal encoding
    2130706433 # dword encoding
  }x

        r = if local_url?(params[:url])
              { error: t("camaleon_cms.admin.media.local_upload_denied") }
            else
              cama_tmp_upload( params[:url], formats: params[:formats], name: params[:name])
            end
  def local_url?(url)
    url.try :match?, LOCALHOST_DOMAIN_MATCHER
  end

        local_upload_denied: 'Cannot upload from localhost'