Class: WPScan::Target

Inherits:
WebSite show all
Includes:
Platform::WordPress, Server::Generic
Defined in:
lib/wpscan/target.rb,
lib/wpscan/target/scope.rb,
lib/wpscan/target/hashes.rb,
lib/wpscan/target/server/iis.rb,
lib/wpscan/target/platform/php.rb,
lib/wpscan/target/server/nginx.rb,
lib/wpscan/target/server/apache.rb,
lib/wpscan/target/server/generic.rb,
lib/wpscan/target/platform/wordpress.rb,
lib/wpscan/target/platform/wordpress/custom_directories.rb

Overview

Scope system logic

Defined Under Namespace

Modules: Platform, Server Classes: Scope

Constant Summary

Constants included from Platform::WordPress

Platform::WordPress::COOKIE_PATTERNS, Platform::WordPress::WORDPRESS_HOSTED_PATTERN, Platform::WordPress::WORDPRESS_PATTERN, Platform::WordPress::WP_ADMIN_AJAX_PATTERN, Platform::WordPress::WP_JSON_OEMBED_PATTERN

Constants included from Platform::PHP

Platform::PHP::DEBUG_LOG_PATTERN, Platform::PHP::ERROR_LOG_PATTERN, Platform::PHP::FPD_PATTERN

Instance Attribute Summary

Attributes included from Platform::WordPress

#mu_plugins, #multisite, #registration_enabled

Attributes inherited from WebSite

#homepage_res, #opts, #uri

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Platform::WordPress

#content_dir, #content_dir=, #content_uri, #content_url, #default_content_dir_exists?, #do_login, #login_request, #login_url, #maybe_add_cookies, #plugin_url, #plugins_dir, #plugins_dir=, #plugins_uri, #plugins_url, #registration_url, #sub_dir, #theme_url, #themes_dir, #themes_uri, #themes_url, #url, #wordpress?, #wordpress_from_meta_comments_or_scripts?, #wordpress_hosted?

Methods included from Platform::PHP

#debug_log?, #error_log?, #full_path_disclosure?, #full_path_disclosure_entries, #install_body_cap, #log_file?, #stream_capped_body

Methods included from Server::Generic

#directory_listing?, #directory_listing_entries, #headers, #server

Methods inherited from WebSite

#access_forbidden?, #error_404_res, #error_404_url, #head_and_get, #head_or_get_params, #homepage_url, #http_auth?, #ip, #online?, #proxy_auth?, #redirection, #reset_homepage_cache!, #url, #url=

Constructor Details

#initialize(url, opts = {}) ⇒ Target

Returns a new instance of Target.

Parameters:

  • url (String)
  • opts (Hash) (defaults to: {})

Options Hash (opts):



19
20
21
22
23
24
# File 'lib/wpscan/target.rb', line 19

def initialize(url, opts = {})
  super

  scope << uri.host
  Array(opts[:scope]).each { |s| scope << s }
end

Class Method Details

.page_hash(page) ⇒ String

Note:

Comments are deleted to avoid cache generation details

Returns The md5sum of the page.

Parameters:

Returns:

  • (String)

    The md5sum of the page



11
12
13
14
15
16
17
18
19
20
# File 'lib/wpscan/target/hashes.rb', line 11

def self.page_hash(page)
  page = WPScan::Browser.get(page, followlocation: true, maxredirs: 10) unless page.is_a?(Typhoeus::Response)

  # Removes comments and script tags before computing the hash
  # to remove any potential cached stuff
  html = Nokogiri::HTML(page.body)
  html.xpath('//script|//comment()').each(&:remove)

  Digest::MD5.hexdigest(html)
end

Instance Method Details

#backup_folders(opts = {}) ⇒ Array<BackupFolder>

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (Array<BackupFolder>)


205
206
207
# File 'lib/wpscan/target.rb', line 205

def backup_folders(opts = {})
  @backup_folders ||= Finders::BackupFolders::Base.find(self, opts)
end

#comments_from_page(pattern, page = nil) {|MatchData, Nokogiri::XML::Comment| ... } ⇒ Array<Array<MatchData, Nokogiri::XML::Comment>>

Parameters:

Yields:

  • (MatchData, Nokogiri::XML::Comment)

Returns:

  • (Array<Array<MatchData, Nokogiri::XML::Comment>>)


93
94
95
96
97
# File 'lib/wpscan/target.rb', line 93

def comments_from_page(pattern, page = nil)
  xpath_pattern_from_page('//comment()', pattern, page) do |match, node|
    yield match, node if block_given?
  end
end

#config_backups(opts = {}) ⇒ Array<ConfigBackup>

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (Array<ConfigBackup>)


191
192
193
# File 'lib/wpscan/target.rb', line 191

def config_backups(opts = {})
  @config_backups ||= Finders::ConfigBackups::Base.find(self, opts)
end

#db_exports(opts = {}) ⇒ Array<DBExport>

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (Array<DBExport>)


198
199
200
# File 'lib/wpscan/target.rb', line 198

def db_exports(opts = {})
  @db_exports ||= Finders::DbExports::Base.find(self, opts)
end

#error_404_hashString

Note:

This is used to detect potential custom 404 responding with a 200

Returns The hash of a 404.

Returns:

  • (String)

    The hash of a 404



29
30
31
# File 'lib/wpscan/target/hashes.rb', line 29

def error_404_hash
  @error_404_hash ||= self.class.page_hash(error_404_res)
end

#head_or_get_request_paramsHash

Returns:

  • (Hash)


27
28
29
30
31
32
33
# File 'lib/wpscan/target.rb', line 27

def head_or_get_request_params
  @head_or_get_request_params ||= if Browser.head(url).code == 405
                                    { method: :get, maxfilesize: 1 }
                                  else
                                    { method: :head }
                                  end
end

#homepage_hashString

Returns The hash of the homepage.

Returns:

  • (String)

    The hash of the homepage



23
24
25
# File 'lib/wpscan/target/hashes.rb', line 23

def homepage_hash
  @homepage_hash ||= self.class.page_hash(url)
end

#homepage_or_404?(page) ⇒ Boolean

Returns Wether or not the page is a the homepage or a 404 based on its md5sum.

Parameters:

Returns:

  • (Boolean)

    Wether or not the page is a the homepage or a 404 based on its md5sum



35
36
37
# File 'lib/wpscan/target/hashes.rb', line 35

def homepage_or_404?(page)
  homepage_and_404_hashes.include?(self.class.page_hash(page))
end

#in_scope?(url_or_uri) ⇒ Boolean

Returns true if the url given is in scope.

Parameters:

  • url (String, Addressable::URI)

    An absolute URL or URI

Returns:

  • (Boolean)

    true if the url given is in scope



14
15
16
17
18
19
20
# File 'lib/wpscan/target/scope.rb', line 14

def in_scope?(url_or_uri)
  url_or_uri = Addressable::URI.parse(url_or_uri.strip) unless url_or_uri.is_a?(Addressable::URI)

  scope.include?(url_or_uri.host)
rescue StandardError
  false
end

#in_scope_uris(res, xpath = '//@href|//@src|//@data-src') {|Addressable::URI, Nokogiri::XML::Element| ... } ⇒ Array<Addressable::URI>

Note:

It is highly recommended to use the xpath parameter to focus on the uris needed, as this method can be quite time consuming when there are a lof of uris to check

Returns The in scope absolute URIs detected in the response’s body.

Parameters:

Yields:

  • (Addressable::URI, Nokogiri::XML::Element)

    The in scope url and its associated tag

Returns:

  • (Array<Addressable::URI>)

    The in scope absolute URIs detected in the response’s body



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/wpscan/target/scope.rb', line 31

def in_scope_uris(res, xpath = '//@href|//@src|//@data-src')
  found = []

  uris_from_page(res, xpath) do |uri, tag|
    next unless in_scope?(uri)

    yield uri, tag if block_given?

    found << uri
  end

  found
end

#interesting_findings(opts = {}) ⇒ Findings

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (Findings)


52
53
54
# File 'lib/wpscan/target.rb', line 52

def interesting_findings(opts = {})
  @interesting_findings ||= WPScan::Finders::InterestingFindings::Base.find(self, opts)
end

#javascripts_from_page(pattern, page = nil) {|MatchData, Nokogiri::XML::Element| ... } ⇒ Array<Array<MatchData, Nokogiri::XML::Element>>

Parameters:

Yields:

  • (MatchData, Nokogiri::XML::Element)

Returns:

  • (Array<Array<MatchData, Nokogiri::XML::Element>>)


104
105
106
107
108
# File 'lib/wpscan/target.rb', line 104

def javascripts_from_page(pattern, page = nil)
  xpath_pattern_from_page('//script', pattern, page) do |match, node|
    yield match, node if block_given?
  end
end

#main_theme(opts = {}) ⇒ Theme

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (Theme)


157
158
159
160
161
# File 'lib/wpscan/target.rb', line 157

def main_theme(opts = {})
  @main_theme = Finders::MainTheme::Base.find(self, opts) if @main_theme.nil?

  @main_theme
end

#medias(opts = {}) ⇒ Array<Media>

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (Array<Media>)


212
213
214
# File 'lib/wpscan/target.rb', line 212

def medias(opts = {})
  @medias ||= Finders::Medias::Base.find(self, opts)
end

#plugins(opts = {}) {|Plugin| ... } ⇒ Array<Plugin>

Parameters:

  • opts (Hash) (defaults to: {})

Yields:

  • (Plugin)

    Optional block called as each plugin is first discovered (used to stream findings).

Returns:

  • (Array<Plugin>)


168
169
170
# File 'lib/wpscan/target.rb', line 168

def plugins(opts = {}, &)
  @plugins ||= Finders::Plugins::Base.find(self, opts, &)
end

#scopeArray<PublicSuffix::Domain, String>

Returns:



7
8
9
# File 'lib/wpscan/target/scope.rb', line 7

def scope
  @scope ||= Scope.new
end

#scope_url_patternRegexp

Similar to Target#url_pattern but considering the in scope domains as well

rubocop:disable Metrics/AbcSize

Returns:

  • (Regexp)

    The pattern related to the target url and in scope domains, it also matches escaped /, such as in JSON JS data: http://t.com/



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/wpscan/target/scope.rb', line 50

def scope_url_pattern
  return @scope_url_pattern if @scope_url_pattern

  domains = [uri.host + uri.path]

  domains += if scope.domains.empty?
               Array(scope.invalid_domains[1..])
             else
               Array(scope.domains[1..]).map(&:to_s) + scope.invalid_domains
             end

  domains.map! { |d| Regexp.escape(d.delete_suffix('/')).gsub('\*', '.*').gsub('/', '\\\\\?/') }

  domains[0].gsub!(Regexp.escape(uri.host), "#{Regexp.escape(uri.host)}(?::\\d+)?") if uri.port

  @scope_url_pattern = %r{https?:\\?/\\?/(?:#{domains.join('|')})\\?/?}i
end

#themes(opts = {}) {|Theme| ... } ⇒ Array<Theme>

Parameters:

  • opts (Hash) (defaults to: {})

Yields:

  • (Theme)

    Optional block called as each theme is first discovered (used to stream findings).

Returns:

  • (Array<Theme>)


177
178
179
# File 'lib/wpscan/target.rb', line 177

def themes(opts = {}, &)
  @themes ||= Finders::Themes::Base.find(self, opts, &)
end

#timthumbs(opts = {}) ⇒ Array<Timthumb>

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (Array<Timthumb>)


184
185
186
# File 'lib/wpscan/target.rb', line 184

def timthumbs(opts = {})
  @timthumbs ||= Finders::Timthumbs::Base.find(self, opts)
end

#uris_from_page(page = nil, xpath = '//@href|//@src|//@data-src') {|Addressable::URI, Nokogiri::XML::Element| ... } ⇒ Array<Addressable::URI>

Note:

It is highly recommended to use the xpath parameter to focus on the uris needed, as this method can be quite time consuming when there are a lof of uris to check

Returns The absolute URIs detected in the response’s body from the HTML tags.

Parameters:

  • page (Typhoeus::Response, String) (defaults to: nil)
  • xpath (String) (defaults to: '//@href|//@src|//@data-src')

Yields:

  • (Addressable::URI, Nokogiri::XML::Element)

    The url and its associated tag

Returns:

  • (Array<Addressable::URI>)

    The absolute URIs detected in the response’s body from the HTML tags



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/wpscan/target.rb', line 119

def uris_from_page(page = nil, xpath = '//@href|//@src|//@data-src')
  page  = WPScan::Browser.get(url(page)) unless page.is_a?(Typhoeus::Response)
  found = []

  page.html.xpath(xpath).each do |node|
    attr_value = node.text.to_s

    next unless attr_value && !attr_value.empty?

    node_uri = begin
      uri.join(attr_value.strip)
    rescue StandardError
      # Skip potential malformed URLs etc.
      next
    end

    next unless node_uri.host

    yield node_uri, node.parent if block_given? && !found.include?(node_uri)

    found << node_uri
  end

  found.uniq
end

#url_patternRegexp

Returns The pattern related to the target url, also matches escaped /, such as in JSON JS data: http://t.com/.

Returns:

  • (Regexp)

    The pattern related to the target url, also matches escaped /, such as in JSON JS data: http://t.com/



63
64
65
# File 'lib/wpscan/target.rb', line 63

def url_pattern
  @url_pattern ||= Regexp.new(Regexp.escape(url).gsub(/https?/i, 'https?').gsub('/', '\\\\\?/'), Regexp::IGNORECASE)
end

#users(opts = {}) {|User| ... } ⇒ Array<User>

Parameters:

  • opts (Hash) (defaults to: {})

Yields:

  • (User)

    Optional block called as each user is first discovered (used to stream findings).

Returns:

  • (Array<User>)


221
222
223
# File 'lib/wpscan/target.rb', line 221

def users(opts = {}, &)
  @users ||= Finders::Users::Base.find(self, opts, &)
end

#vulnerable?Boolean

Returns:

  • (Boolean)


36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/wpscan/target.rb', line 36

def vulnerable?
  [@wp_version, @main_theme, @plugins, @themes, @timthumbs].each do |e|
    Array(e).each { |ae| return true if ae && ae.vulnerable? } # rubocop:disable Style/SafeNavigation
  end

  return true unless Array(@config_backups).empty?
  return true unless Array(@db_exports).empty?

  Array(@users).each { |u| return true if u.password }

  false
end

#wp_version(opts = {}) ⇒ WpVersion, false

Returns The WpVersion found or false if not detected.

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (WpVersion, false)

    The WpVersion found or false if not detected



148
149
150
151
152
# File 'lib/wpscan/target.rb', line 148

def wp_version(opts = {})
  @wp_version = Finders::WpVersion::Base.find(self, opts) if @wp_version.nil?

  @wp_version
end

#xmlrpcXMLRPC?

Returns:

  • (XMLRPC, nil)


57
58
59
# File 'lib/wpscan/target.rb', line 57

def xmlrpc
  @xmlrpc ||= interesting_findings&.grep(Model::XMLRPC)&.first
end

#xpath_pattern_from_page(xpath, pattern, page = nil) {|MatchData, Nokogiri::XML::Element| ... } ⇒ Array<Array<MatchData, Nokogiri::XML::Element>>

Parameters:

Yields:

  • (MatchData, Nokogiri::XML::Element)

Returns:

  • (Array<Array<MatchData, Nokogiri::XML::Element>>)


73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/wpscan/target.rb', line 73

def xpath_pattern_from_page(xpath, pattern, page = nil)
  page    = WPScan::Browser.get(url(page)) unless page.is_a?(Typhoeus::Response)
  matches = []

  page.html.xpath(xpath).each do |node|
    next unless node.text.strip =~ pattern

    yield Regexp.last_match, node if block_given?

    matches << [Regexp.last_match, node]
  end

  matches
end