Module: WPScan::Target::Platform::WordPress

Includes:
CMSScanner::Target::Platform::PHP
Included in:
WPScan::Target
Defined in:
lib/wpscan/target/platform/wordpress.rb,
lib/wpscan/target/platform/wordpress/custom_directories.rb

Overview

wp-content & plugins directory implementation

Constant Summary collapse

WORDPRESS_PATTERN =
%r{/(?:(?:wp-content/(?:themes|(?:mu\-)?plugins|uploads))|wp-includes)/}i.freeze
{
  'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#mu_pluginsObject Also known as: mu_plugins?

These methods are used in the associated interesting_findings finders to keep the boolean state of the finding rather than re-check the whole thing again



18
19
20
# File 'lib/wpscan/target/platform/wordpress.rb', line 18

def mu_plugins
  @mu_plugins
end

#multisiteObject Also known as: multisite?

These methods are used in the associated interesting_findings finders to keep the boolean state of the finding rather than re-check the whole thing again



18
19
20
# File 'lib/wpscan/target/platform/wordpress.rb', line 18

def multisite
  @multisite
end

#registration_enabledObject Also known as: registration_enabled?

These methods are used in the associated interesting_findings finders to keep the boolean state of the finding rather than re-check the whole thing again



18
19
20
# File 'lib/wpscan/target/platform/wordpress.rb', line 18

def registration_enabled
  @registration_enabled
end

Instance Method Details

#content_dir(detection_mode = :mixed) ⇒ String

Returns The wp-content directory.

Parameters:

  • detection_mode (Symbol) (defaults to: :mixed)

Returns:

  • (String)

    The wp-content directory



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 18

def content_dir(detection_mode = :mixed)
  unless @content_dir
    # scope_url_pattern is from CMSScanner::Target
    pattern = %r{#{scope_url_pattern}([\w\s\-/]+)\\?/(?:themes|plugins|uploads|cache)\\?/}i

    in_scope_uris(homepage_res) do |uri|
      return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
    end

    # Checks for the pattern in raw JS code, as well as @content attributes of meta tags
    xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, homepage_res) do |match|
      return @content_dir = match[1]
    end

    unless detection_mode == :passive
      return @content_dir = 'wp-content' if default_content_dir_exists?
    end
  end

  @content_dir
end

#content_dir=(dir) ⇒ Object



8
9
10
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 8

def content_dir=(dir)
  @content_dir = dir.chomp('/')
end

#content_uriAddressable::URI

Returns:

  • (Addressable::URI)


47
48
49
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 47

def content_uri
  uri.join("#{content_dir}/")
end

#content_urlString

Returns:

  • (String)


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

def content_url
  content_uri.to_s
end

#default_content_dir_exists?Boolean

Returns:

  • (Boolean)


40
41
42
43
44
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 40

def default_content_dir_exists?
  # url('wp-content') can't be used here as the folder has not yet been identified
  # and the method would try to replace it by nil which would raise an error
  [200, 401, 403].include?(Browser.forge_request(uri.join('wp-content/').to_s, head_or_get_params).run.code)
end

#do_login(username, password) ⇒ Typhoeus::Response

Parameters:

  • username (String)
  • password (String)

Returns:



100
101
102
# File 'lib/wpscan/target/platform/wordpress.rb', line 100

def (username, password)
  (username, password).run
end

#login_request(username, password) ⇒ Typhoeus::Request

Parameters:

  • username (String)
  • password (String)

Returns:

  • (Typhoeus::Request)


108
109
110
111
112
113
114
115
# File 'lib/wpscan/target/platform/wordpress.rb', line 108

def (username, password)
  Browser.instance.forge_request(
    ,
    method: :post,
    cache_ttl: 0,
    body: { log: username, pwd: password }
  )
end

#login_urlString

The login page is checked for a potential redirection (from http to https) the first time the method is called, and the effective_url is then used if suitable, otherwise the default wp-login will be.

Returns:

  • (String)

    The URL to the login page



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/wpscan/target/platform/wordpress.rb', line 122

def 
  return @login_url if @login_url

  @login_url = url('wp-login.php')

  res = Browser.get_and_follow_location(@login_url)

  @login_url = res.effective_url if res.effective_url =~ /wp\-login\.php\z/i && in_scope?(res.effective_url)

  @login_url
end

#maybe_add_cookiesObject

Sometimes there is a mechanism in place on the blog, which requires a specific cookie and value to be added to requests. Lets try to detect and add them



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/wpscan/target/platform/wordpress.rb', line 54

def maybe_add_cookies
  COOKIE_PATTERNS.each do |cookie_key, pattern|
    next unless homepage_res.body =~ pattern

    browser = Browser.instance

    cookie_string = "#{cookie_key}=#{Regexp.last_match[:c_value]}"

    cookie_string += "; #{browser.cookie_string}" if browser.cookie_string

    browser.cookie_string = cookie_string

    # Force recheck of the homepage when retying wordpress?
    # No need to clear the cache, as the request (which will contain the cookies)
    # will be different
    @homepage_res = nil
    @homepage_url = nil

    break
  end
end

#plugin_url(slug) ⇒ String

Parameters:

  • slug (String)

Returns:

  • (String)


74
75
76
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 74

def plugin_url(slug)
  plugins_uri.join("#{URI.encode(slug)}/").to_s
end

#plugins_dirString

Returns:

  • (String)


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

def plugins_dir
  @plugins_dir ||= "#{content_dir}/plugins"
end

#plugins_dir=(dir) ⇒ Object



12
13
14
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 12

def plugins_dir=(dir)
  @plugins_dir = dir.chomp('/')
end

#plugins_uriAddressable::URI

Returns:

  • (Addressable::URI)


62
63
64
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 62

def plugins_uri
  uri.join("#{plugins_dir}/")
end

#plugins_urlString

Returns:

  • (String)


67
68
69
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 67

def plugins_url
  plugins_uri.to_s
end

#registration_urlString

Returns:

  • (String)


77
78
79
# File 'lib/wpscan/target/platform/wordpress.rb', line 77

def registration_url
  multisite? ? url('wp-signup.php') : url('wp-login.php?action=register')
end

#sub_dirString, False

@note: nil can not be returned here, otherwise if there is no sub_dir

the check would be done each time, which would make enumeration of
long list of items very slow to generate

Returns:

  • (String, False)

    String of the sub_dir found, false otherwise



104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 104

def sub_dir
  return @sub_dir unless @sub_dir.nil?

  # url_pattern is from CMSScanner::Target
  pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i

  in_scope_uris(homepage_res) do |uri|
    return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
  end

  @sub_dir = false
end

#theme_url(slug) ⇒ String

Parameters:

  • slug (String)

Returns:

  • (String)


96
97
98
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 96

def theme_url(slug)
  themes_uri.join("#{URI.encode(slug)}/").to_s
end

#themes_dirString

Returns:

  • (String)


79
80
81
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 79

def themes_dir
  @themes_dir ||= "#{content_dir}/themes"
end

#themes_uriAddressable::URI

Returns:

  • (Addressable::URI)


84
85
86
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 84

def themes_uri
  uri.join("#{themes_dir}/")
end

#themes_urlString

Returns:

  • (String)


89
90
91
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 89

def themes_url
  themes_uri.to_s
end

#url(path = nil) ⇒ String

Override of the WebSite#url to consider the custom WP directories

Parameters:

  • path (String) (defaults to: nil)

    Optional path to merge with the uri

Returns:

  • (String)


122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/wpscan/target/platform/wordpress/custom_directories.rb', line 122

def url(path = nil)
  return @uri.to_s unless path

  if %r{wp\-content/plugins}i.match?(path)
    path = +path.gsub('wp-content/plugins', plugins_dir)
  elsif /wp\-content/i.match?(path)
    path = +path.gsub('wp-content', content_dir)
  elsif path[0] != '/' && sub_dir
    path = "#{sub_dir}/#{path}"
  end

  super(path)
end

#wordpress?(detection_mode) ⇒ Boolean

Parameters:

  • detection_mode (Symbol)

Returns:

  • (Boolean)


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/wpscan/target/platform/wordpress.rb', line 26

def wordpress?(detection_mode)
  in_scope_uris(homepage_res) do |uri|
    return true if uri.path.match(WORDPRESS_PATTERN)
  end

  homepage_res.html.css('meta[name="generator"]').each do |node|
    return true if /wordpress/i.match?(node['content'])
  end

  return true unless comments_from_page(/wordpress/i, homepage_res).empty?

  if %i[mixed aggressive].include?(detection_mode)
    %w[wp-admin/install.php wp-login.php].each do |path|
      in_scope_uris(Browser.get_and_follow_location(url(path))).each do |uri|
        return true if uri.path.match(WORDPRESS_PATTERN)
      end
    end
  end

  false
end

#wordpress_hosted?Boolean

Returns Whether or not the target is hosted on wordpress.com.

Returns:

  • (Boolean)

    Whether or not the target is hosted on wordpress.com



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/wpscan/target/platform/wordpress.rb', line 82

def wordpress_hosted?
  return true if /\.wordpress\.com$/i.match?(uri.host)

  unless content_dir(:passive)
    pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze

    uris_from_page(homepage_res) do |uri|
      return true if uri.to_s.match?(pattern)
    end
  end

  false
end