VYPR
Medium severity6.5NVD Advisory· Published Mar 10, 2026· Updated Apr 17, 2026

CVE-2026-1776

CVE-2026-1776

Description

Camaleon CMS versions 2.4.5.0 through 2.9.0, prior to commit f54a77e, contain a path traversal vulnerability in the AWS S3 uploader implementation that allows authenticated users to read arbitrary files from the web server’s filesystem. The issue occurs in the download_private_file functionality when the application is configured to use the CamaleonCmsAwsUploader backend. Unlike the local uploader implementation, the AWS uploader does not validate file paths with valid_folder_path?, allowing directory traversal sequences to be supplied via the file parameter. As a result, any authenticated user, including low-privileged registered users, can access sensitive files such as /etc/passwd. This issue represents a bypass of the incomplete fix for CVE-2024-46987 and affects deployments using the AWS S3 storage backend.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
camaleon_cmsRubyGems
>= 2.4.5.0, <= 2.9.1

Affected products

1

Patches

1
f54a77e2a7be

Fix path traversal in CamaleonCmsAwsUploader and add regression coverage

3 files changed · +123 1
  • app/uploaders/camaleon_cms_aws_uploader.rb+8 1 modified
    @@ -37,6 +37,8 @@ def objects(prefix = '/', sort = 'created_at')
       end
     
       def fetch_file(file_name)
    +    return { error: 'Invalid file path' } unless valid_folder_path?(file_name)
    +
         return file_name if file_exists?(file_name)
     
         return file_name if bucket.object(file_name).download_file(file_name) && file_exists?(file_name)
    @@ -84,8 +86,9 @@ def file_parse(s3_file)
       #   - same_name: false => avoid to overwrite an existent file with same key and search for an available key
       #   - is_thumb: true => if this file is a thumbnail of an uploaded file
       def add_file(uploaded_io_or_file_path, key, args = {})
    +    return { error: 'Invalid file path' } unless valid_folder_path?(key)
    +
         args = { same_name: false, is_thumb: false }.merge(args)
    -    res = nil
         key = "#{@aws_settings['inner_folder']}/#{key}" if @aws_settings['inner_folder'].present? && !args[:is_thumb]
         key = key.cama_fix_media_key
         key = search_new_key(key) unless args[:same_name]
    @@ -117,6 +120,8 @@ def add_folder(key)
     
       # delete a folder in AWS with :key
       def delete_folder(key)
    +    return { error: 'Invalid folder path' } unless valid_folder_path?(key)
    +
         key = "#{@aws_settings['inner_folder']}/#{key}" if @aws_settings['inner_folder'].present?
         key = key.cama_fix_media_key
         bucket.objects(prefix: key.slice(1..-1) << '/').delete
    @@ -125,6 +130,8 @@ def delete_folder(key)
     
       # delete a file in AWS with :key
       def delete_file(key)
    +    return { error: 'Invalid file path' } unless valid_folder_path?(key)
    +
         key = "#{@aws_settings['inner_folder']}/#{key}" if @aws_settings['inner_folder'].present?
         key = key.cama_fix_media_key
         begin
    
  • .github/copilot-instructions.md+1 0 added
    @@ -0,0 +1 @@
    +When running rspec tests, please ensure that the RAILS_ENV environment variable is always set to test. The command should be executed as RAILS_ENV=test bundle exec rspec.
    
  • spec/uploaders/aws_uploader_spec.rb+114 0 added
    @@ -0,0 +1,114 @@
    +# frozen_string_literal: true
    +
    +require 'rails_helper'
    +
    +RSpec.describe CamaleonCmsAwsUploader do
    +  init_site
    +
    +  let(:current_site) { Cama::Site.first.decorate }
    +  let(:hook_instance) { instance_double('UploaderHookInstance', hooks_run: nil) }
    +  let(:uploader) { described_class.new({ current_site: current_site, aws_settings: {} }, hook_instance) }
    +  let(:bucket) { instance_double('Aws::S3::Bucket') }
    +
    +  before { allow(uploader).to receive(:bucket).and_return(bucket) }
    +
    +  context 'with an invalid path containing path traversal characters' do
    +    describe '#add_file' do
    +      it 'returns an error' do
    +        expect(bucket).not_to receive(:object)
    +
    +        expect(uploader.add_file('/tmp/test.png', '../tmp/test.png')).to eql(error: 'Invalid file path')
    +      end
    +    end
    +
    +    describe '#delete_folder' do
    +      it 'returns an error' do
    +        expect(bucket).not_to receive(:objects)
    +
    +        expect(uploader.delete_folder('../tmp')).to eql(error: 'Invalid folder path')
    +      end
    +    end
    +
    +    describe '#delete_file' do
    +      it 'returns an error' do
    +        expect(bucket).not_to receive(:object)
    +        expect(hook_instance).not_to receive(:hooks_run)
    +
    +        expect(uploader.delete_file('../tmp/test.png')).to eql(error: 'Invalid file path')
    +      end
    +    end
    +  end
    +
    +  context 'with an invalid URI-like path' do
    +    describe '#add_file' do
    +      it 'returns an error' do
    +        expect(bucket).not_to receive(:object)
    +
    +        expect(uploader.add_file('/tmp/test.png', 'file:///tmp/test.png')).to eql(error: 'Invalid file path')
    +      end
    +    end
    +
    +    describe '#delete_folder' do
    +      it 'returns an error' do
    +        expect(bucket).not_to receive(:objects)
    +
    +        expect(uploader.delete_folder('s3://bucket/folder')).to eql(error: 'Invalid folder path')
    +      end
    +    end
    +
    +    describe '#delete_file' do
    +      it 'returns an error' do
    +        expect(bucket).not_to receive(:object)
    +        expect(hook_instance).not_to receive(:hooks_run)
    +
    +        expect(uploader.delete_file('https://example.com/file.txt')).to eql(error: 'Invalid file path')
    +      end
    +    end
    +  end
    +
    +  context 'with a valid file path' do
    +    describe '#add_file' do
    +      let(:s3_file) { instance_double('Aws::S3::Object') }
    +      let(:parsed_file) do
    +        {
    +          'name' => 'test.png',
    +          'folder_path' => '/safe',
    +          'url' => 'https://cdn.example.com/safe/test.png',
    +          'is_folder' => false,
    +          'file_size' => 123.45,
    +          'thumb' => '/safe/thumb/test-png.png',
    +          'file_type' => 'image',
    +          'created_at' => '2026-03-09T00:00:00Z',
    +          'dimension' => '100x100',
    +          'key' => '/safe/test.png'
    +        }
    +      end
    +
    +      before do
    +        allow(bucket).to receive(:object).and_return(s3_file)
    +        allow(s3_file).to receive(:upload_file).and_return(true)
    +        allow(uploader).to receive(:search_new_key).and_return('/safe/test.png')
    +        allow(uploader).to receive(:file_parse).with(s3_file).and_return(parsed_file)
    +        allow(uploader).to receive(:cache_item).with(parsed_file).and_return(parsed_file)
    +      end
    +
    +      it 'uploads the file and returns cached metadata' do
    +        file_path = "#{CAMALEON_CMS_ROOT}/spec/support/fixtures/rails.png"
    +        expect(hook_instance).to receive(:hooks_run).with(
    +          'uploader_aws_before_upload',
    +          hash_including(
    +            file: file_path, key: '/safe/test.png', args: hash_including(same_name: false, is_thumb: false)
    +          )
    +        )
    +
    +        result = uploader.add_file(file_path, 'safe/test.png')
    +
    +        expect(bucket).to have_received(:object).with('safe/test.png')
    +        expect(s3_file).to have_received(:upload_file).with(
    +          file_path, { acl: 'public-read' }
    +        )
    +        expect(result).to eql(parsed_file)
    +      end
    +    end
    +  end
    +end
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.