ffdd057f10
-) Added Wordpress checks
321 lines
9.1 KiB
Ruby
321 lines
9.1 KiB
Ruby
# -*- coding: binary -*-
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# This module provides a way of interacting with wordpress installations
|
|
#
|
|
###
|
|
module Exploit::Remote::Wordpress
|
|
include Exploit::Remote::HttpClient
|
|
include Msf::Auxiliary::Report
|
|
|
|
def initialize(info = {})
|
|
super
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']),
|
|
], Exploit::Remote::Wordpress
|
|
)
|
|
end
|
|
|
|
#
|
|
# Checks if the site is online and running wordpress
|
|
# @return [Boolean] Returns true if the site is online and running wordpress
|
|
#
|
|
def wp_wordpress_and_online?
|
|
begin
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri)
|
|
}, 20)
|
|
if res and res.code == 200
|
|
if res.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i or
|
|
res.body =~ /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i or
|
|
res.body =~ /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'] \/>/i
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
|
|
rescue ::Timeout::Error, ::Errno::EPIPE
|
|
print_error("Error connecting to #{target_uri}")
|
|
return false
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
#
|
|
# Returns the Wordpress Login URL
|
|
# @return [String] Wordpress Login URL
|
|
#
|
|
def wp_uri_login
|
|
normalize_uri(target_uri.path, 'wp-login.php')
|
|
end
|
|
|
|
#
|
|
# Returns the Wordpress Post URL
|
|
# @param post_id Post ID
|
|
# @return [String] Wordpress Post URL
|
|
#
|
|
def wp_url_post(post_id)
|
|
normalize_uri(target_uri.path) + "/?p=#{post_id}"
|
|
end
|
|
|
|
#
|
|
# Returns the Wordpress Author URL
|
|
# @param author_id Author ID
|
|
# @return [String] Wordpress Author URL
|
|
#
|
|
def wp_url_author(author_id)
|
|
normalize_uri(target_uri.path) + "/?author=#{author_id}"
|
|
end
|
|
|
|
#
|
|
# performs a wordpress login
|
|
# @param user Username
|
|
# @param pass Password
|
|
# @return [String] the session cookie on successful login, nil otherwise
|
|
#
|
|
def wp_login(user, pass)
|
|
redirect = "#{target_uri}#{Rex::Text.rand_text_alpha(8)}"
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => wp_uri_login,
|
|
'data' => _wp_login_post_data(user, pass, redirect),
|
|
}, 20)
|
|
|
|
if res and res.code == 302 and res.headers['Location'] == redirect
|
|
match = res.get_cookies.match(/(wordpress(?:_sec)?_logged_in_[^=]+=[^;]+);/i)
|
|
if match
|
|
# return wordpress login cookie
|
|
return match[0]
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
#
|
|
# Checks if the given user exists
|
|
# @param user Username
|
|
# @return [Boolean] true if the user exists
|
|
#
|
|
def wp_user_exists?(user)
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => wp_uri_login,
|
|
'data' => _wp_login_post_data(user, 'x'),
|
|
}, 20)
|
|
|
|
exists = false
|
|
if res and res.code == 200
|
|
if res.body.to_s =~ /Incorrect password/ or
|
|
res.body.to_s =~ /document\.getElementById\('user_pass'\)/
|
|
exists = true
|
|
else
|
|
exists = false
|
|
end
|
|
end
|
|
return exists
|
|
end
|
|
|
|
#
|
|
# Checks if the given userid exists
|
|
# @param user_id user_id
|
|
# @return [String] the Username if it exists, nil otherwise
|
|
#
|
|
def wp_userid_exists?(user_id)
|
|
url = wp_url_author(user_id)
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => url
|
|
})
|
|
|
|
if res and res.code == 301
|
|
uri = URI(res.headers['Location'])
|
|
# try to extract username from location
|
|
if uri.to_s =~ /\/author\/([^\/\b]+)\/?/i
|
|
return $1
|
|
end
|
|
uri = "#{uri.path}?#{uri.query}"
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => uri
|
|
})
|
|
end
|
|
|
|
if res.nil?
|
|
print_error("#{target_uri} - Error getting response.")
|
|
elsif res.code == 200 and
|
|
(res.body =~ /href="http[s]*:\/\/.*\/\?*author.+title="([[:print:]]+)" /i or
|
|
res.body =~ /<body class="archive author author-(?:[^\s]+) author-(?:\d+)/i)
|
|
return $1
|
|
end
|
|
return nil
|
|
end
|
|
|
|
#
|
|
# Posts a comment as an authenticated user
|
|
# @param comment The comment
|
|
# @param comment_post_id The Post ID to post the comment to
|
|
# @param login_cookie The valid login_cookie
|
|
# @return [String] The location of the new comment/post
|
|
#
|
|
def wp_post_comment_auth(comment, comment_post_id, login_cookie)
|
|
_wp_post_comment(comment, comment_post_id, login_cookie, nil, nil, nil)
|
|
end
|
|
|
|
#
|
|
# Posts a comment as an unauthenticated user
|
|
# @param comment The comment
|
|
# @param comment_post_id The Post ID to post the comment to
|
|
# @param author The author name
|
|
# @param email The author email
|
|
# @param url The author url
|
|
# @return [String] The location of the new comment/post
|
|
#
|
|
def wp_post_comment_no_auth(comment, comment_post_id, author, email, url)
|
|
_wp_post_comment(comment, comment_post_id, nil, author, email, url)
|
|
end
|
|
|
|
#
|
|
# Tries to bruteforce a valid post_id
|
|
# @param login_cookie If set perform the bruteforce as an authenticated user
|
|
# @return [Integer] The post id, nil when nothing found
|
|
#
|
|
def wp_get_valid_post_id(login_cookie=nil)
|
|
_wp_get_valid_post_id(false, login_cookie)
|
|
end
|
|
|
|
#
|
|
# Tries to bruteforce a valid post_id with comments enabled
|
|
# @param login_cookie If set perform the bruteforce as an authenticated user
|
|
# @return [Integer] The post id, nil when nothing found
|
|
#
|
|
def wp_get_valid_post_id_with_comments_enabled(login_cookie=nil)
|
|
_wp_get_valid_post_id(true, login_cookie)
|
|
end
|
|
|
|
#
|
|
# Checks if the provided post has comments enabled
|
|
# @param post_id The post ID to check
|
|
# @param login_cookie If set perform the check as an authenticated user
|
|
# @return [String] the HTTP response body of the post, nil otherwise
|
|
#
|
|
def wp_post_comments_enabled?(post_id, login_cookie=nil)
|
|
_wp_check_post_id(wp_url_post(post_id), true, login_cookie)
|
|
end
|
|
|
|
private
|
|
|
|
#
|
|
# Returns the POST data for a Wordpress login request
|
|
# @param user Usernam
|
|
# @param pass Password
|
|
# @param redirect URL to redirect after successful login
|
|
# @return [String] The post data
|
|
#
|
|
def _wp_login_post_data(user, pass, redirect=nil)
|
|
post_data = "log=#{Rex::Text.uri_encode(user.to_s)}"
|
|
post_data << "&pwd=#{Rex::Text.uri_encode(pass.to_s)}"
|
|
post_data << "&redirect_to=#{Rex::Text.uri_encode(redirect.to_s)}"
|
|
post_data << '&wp-submit=Login'
|
|
post_data
|
|
end
|
|
|
|
#
|
|
# Helper method to post a comment to Wordpress
|
|
# @param comment The comment
|
|
# @param comment_post_id The Post ID to post the comment to
|
|
# @param login_cookie The valid login_cookie
|
|
# @param author The author name
|
|
# @param email The author email
|
|
# @param url The author url
|
|
# @return [String] The location of the new comment/post
|
|
#
|
|
def _wp_post_comment(comment, comment_post_id, login_cookie, author, email, url)
|
|
vars_post = {
|
|
'comment' => comment,
|
|
'submit' => 'Post+Comment',
|
|
'comment_post_ID' => comment_post_id.to_s,
|
|
'comment_parent' => '0'
|
|
}
|
|
vars_post.merge!({
|
|
'author' => author,
|
|
'email' => email,
|
|
'url' => url,
|
|
}) unless login_cookie
|
|
|
|
options = {
|
|
'uri' => normalize_uri(target_uri.path, 'wp-comments-post.php'),
|
|
'method' => 'POST'
|
|
}
|
|
options.merge!({'vars_post' => vars_post})
|
|
options.merge!({'cookie' => login_cookie}) if login_cookie
|
|
res = send_request_cgi(options)
|
|
if res and res.code == 302
|
|
location = URI(res.headers['Location'])
|
|
return location
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
#
|
|
# Helper method for bruteforcing a valid post id
|
|
# @param comments_enabled If true try to find a post id with comments enabled, otherwise return the first found
|
|
# @param login_cookie A valid login cookie to perform the bruteforce as an authenticated user
|
|
# @return [Integer] The post id, nil when nothing found
|
|
#
|
|
def _wp_get_valid_post_id(comments_enabled=false, login_cookie=nil)
|
|
(1..1000).each { |id|
|
|
vprint_status("#{rhost}:#{rport} - Checking POST ID #{id}...") if (id % 100) == 0
|
|
body = _wp_check_post_id(wp_url_post(id), comments_enabled, login_cookie)
|
|
return id if body
|
|
}
|
|
# no post found
|
|
return nil
|
|
end
|
|
|
|
#
|
|
# Helper method to check if a post is valid an has comments enabled
|
|
# @param uri the Post URI
|
|
# @param comments_enabled Check if comments are enabled on this post
|
|
# @param login_cookie A valid login cookie to perform the check as an authenticated user
|
|
# @return [String] the HTTP response body of the post, nil otherwise
|
|
#
|
|
def _wp_check_post_id(uri, comments_enabled=false, login_cookie=nil)
|
|
options = {
|
|
'method' => 'GET',
|
|
'uri' => uri
|
|
}
|
|
options.merge!({'cookie' => login_cookie}) if login_cookie
|
|
res = send_request_cgi(options)
|
|
# post exists
|
|
if res and res.code == 200
|
|
# also check if comments are enabled
|
|
if comments_enabled
|
|
if res.body =~ /form.*action.*wp-comments-post\.php/
|
|
return res.body
|
|
else
|
|
return nil
|
|
end
|
|
# valid post found, not checking for comments
|
|
else
|
|
return res.body
|
|
end
|
|
elsif res and (res.code == 301 or res.code == 302) and res.headers['Location']
|
|
location = URI(res.headers['Location'])
|
|
uri = location.path
|
|
uri << "?#{location.query}" unless location.query.nil? or location.query.empty?
|
|
return _wp_check_post_id(uri, comments_enabled)
|
|
end
|
|
return nil
|
|
end
|
|
|
|
end
|
|
end
|