module Wpxf::WordPress::Fingerprint
Provides functionality for fingerprinting WordPress
and its components.
Constants
- WORDPRESS_ATOM_VERSION_PATTERN
- WORDPRESS_GENERATOR_VERSION_PATTERN
- WORDPRESS_OPML_VERSION_PATTERN
- WORDPRESS_RDF_VERSION_PATTERN
- WORDPRESS_README_VERSION_PATTERN
- WORDPRESS_RSS_VERSION_PATTERN
- WORDPRESS_SITEMAP_VERSION_PATTERN
- WORDPRESS_VERSION_PATTERN
Public Instance Methods
Checks a plugin's changelog for a vulnerable version. @param plugin_name [String] the name of the plugin. @param file_name [String] the name of the file that contains the changelog. @param fixed [String] the version the vulnerability was fixed in. @param introduced [String] the version the vulnerability was introduced in. @return [Symbol] :unknown, :vulnerable or :safe.
# File lib/wpxf/wordpress/fingerprint.rb, line 65 def check_plugin_version_from_changelog(plugin_name, file_name, fixed = nil, introduced = nil) changelog = normalize_uri(wordpress_url_plugins, plugin_name, file_name) check_version_from_custom_file(changelog, /=\s([\d\.]+)\s=/, fixed, introduced) end
Checks a plugin's readme for a vulnerable version. @param name [String] the name of the plugin. @param fixed [String] the version the vulnerability was fixed in. @param introduced [String] the version the vulnerability was introduced in. @return [Symbol] :unknown, :vulnerable or :safe.
# File lib/wpxf/wordpress/fingerprint.rb, line 55 def check_plugin_version_from_readme(name, fixed = nil, introduced = nil) _check_version_from_readme(:plugin, name, fixed, introduced) end
Checks a theme's readme for a vulnerable version. @param name [String] the name of the theme. @param fixed [String] the version the vulnerability was fixed in. @param introduced [String] the version the vulnerability was introduced in. @return [Symbol] :unknown, :vulnerable or :safe.
# File lib/wpxf/wordpress/fingerprint.rb, line 46 def check_theme_version_from_readme(name, fixed = nil, introduced = nil) _check_version_from_readme(:theme, name, fixed, introduced) end
Checks the style.css file for a vulnerable version. @param name [String] the name of the theme. @param fixed [String] the version the vulnerability was fixed in. @param introduced [String] the version the vulnerability was introduced in. @return [Symbol] :unknown, :vulnerable or :safe.
# File lib/wpxf/wordpress/fingerprint.rb, line 30 def check_theme_version_from_style(name, fixed = nil, introduced = nil) style_uri = normalize_uri(wordpress_url_themes, name, 'style.css') res = execute_get_request(url: style_uri) # No style.css file present return :unknown if res.nil? || res.code != 200 pattern = _extension_version_pattern(:style) _extract_and_check_version(res.body, pattern, fixed, introduced) end
Checks a custom file for a vulnerable version. @param url [String] the relative path of the file. @param regex [Regexp] the regular expression to extract the version. @param fixed [String] the version the vulnerability was fixed in. @param introduced [String] the version the vulnerability was introduced. @return [Symbol] :unknown, :vulnerable or :safe.
# File lib/wpxf/wordpress/fingerprint.rb, line 76 def check_version_from_custom_file(url, regex, fixed = nil, introduced = nil) res = execute_get_request(url: url) return :unknown unless res && res.code == 200 _extract_and_check_version(res.body, regex, fixed, introduced) end
Check if the host is online and running WordPress
. @return [Boolean] true if the host is online and running WordPress
.
# File lib/wpxf/wordpress/fingerprint.rb, line 7 def wordpress_and_online? res = execute_get_request(url: full_uri) return false unless res && res.code == 200 return true if _wordpress_fingerprint_regexes.any? { |r| res.body =~ r } false end
Extract the WordPress
version information from various sources. @return [Version, nil] the version if found, nil otherwise.
# File lib/wpxf/wordpress/fingerprint.rb, line 16 def wordpress_version _wordpress_version_fingerprint_sources.each do |url, pattern| res = execute_get_request(url: url) match = res.body.match(pattern) if res && res.code == 200 return Gem::Version.new(match[1]) if match end nil end
Private Instance Methods
# File lib/wpxf/wordpress/fingerprint.rb, line 128 def _check_version_from_readme(type, name, fixed = nil, introduced = nil) readme = _get_first_readme(name, type) if readme.nil? # No readme present for plugin return :unknown if type == :plugin # Try again using the style.css file, if it is a theme. return check_theme_version_from_style(name, fixed, introduced) if type == :theme end # If all versions are vulnerable and we found a file, end the process here # and return a vulnerable state, as some readmes will not have a version. return :vulnerable if fixed.nil? && introduced.nil? state = _extension_is_vulnerable(type, readme, fixed, introduced) if state == :no_version_found # If no version could be found in readme.txt for a theme, try style.css return check_theme_version_from_style(name, fixed, introduced) end state end
# File lib/wpxf/wordpress/fingerprint.rb, line 190 def _content_directory_name(type) case type when :plugin 'plugins' when :theme 'themes' else raise("Unknown readme type #{type}") end end
# File lib/wpxf/wordpress/fingerprint.rb, line 151 def _extension_is_vulnerable(type, readme, fixed, introduced) pattern = _extension_version_pattern(:readme) vuln = _extract_and_check_version(readme, pattern, fixed, introduced) return :no_version_found if vuln == :unknown && type == :theme vuln end
# File lib/wpxf/wordpress/fingerprint.rb, line 224 def _extension_version_pattern(type) case type when :readme # Example line: # Stable tag: 2.6.6 /(?:stable tag):\s*(?!trunk)([0-9a-z.-]+)/i when :style # Example line: # Version: 1.5.2 /(?:Version):\s*([0-9a-z.-]+)/i else raise("Unknown file type #{type}") end end
# File lib/wpxf/wordpress/fingerprint.rb, line 212 def _extract_and_check_version(body, pattern, fixed = nil, introduced = nil) version = _extract_highest_version(body, pattern) return :unknown if version.nil? version = Gem::Version.new(version) fixed = Gem::Version.new(fixed) unless fixed.nil? introduced = Gem::Version.new(introduced) unless introduced.nil? emit_info "Found version #{version}", true _version_vulnerable?(version, fixed, introduced) end
# File lib/wpxf/wordpress/fingerprint.rb, line 201 def _extract_highest_version(body, pattern) version = nil body.scan(pattern) do |match| match_version = Gem::Version.new(match[0]) version = match_version if version.nil? || match_version > version end version end
# File lib/wpxf/wordpress/fingerprint.rb, line 158 def _get_first_readme(name, type) res = nil folder = _content_directory_name(type) readmes = ['readme.txt', 'Readme.txt', 'README.txt'] readmes.each do |readme| readme_url = normalize_uri(wordpress_url_wp_content, folder, name, readme) res = execute_get_request(url: readme_url) break if res && res.code == 200 end return res.body if res && res.code == 200 nil end
# File lib/wpxf/wordpress/fingerprint.rb, line 172 def _version_vulnerable?(version, fixed, introduced) return :vulnerable if fixed.nil? && introduced.nil? if fixed && !introduced return :vulnerable if version < fixed end if !fixed && introduced return :vulnerable if version >= introduced end if fixed && introduced return :vulnerable if version >= introduced && version < fixed end :safe end
# File lib/wpxf/wordpress/fingerprint.rb, line 119 def _wordpress_fingerprint_regexes [ %r{["'][^"']*\/#{Regexp.escape(wp_content_dir)}\/[^"']*["']}i, %r{<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/ wlwmanifest\.xml["'] \/>}i, %r{<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'](?: \/)*>}i ] end
# File lib/wpxf/wordpress/fingerprint.rb, line 107 def _wordpress_version_fingerprint_sources { full_uri => WORDPRESS_GENERATOR_VERSION_PATTERN, wordpress_url_readme => WORDPRESS_README_VERSION_PATTERN, wordpress_url_rss => WORDPRESS_RSS_VERSION_PATTERN, wordpress_url_rdf => WORDPRESS_RDF_VERSION_PATTERN, wordpress_url_atom => WORDPRESS_ATOM_VERSION_PATTERN, wordpress_url_sitemap => WORDPRESS_SITEMAP_VERSION_PATTERN, wordpress_url_opml => WORDPRESS_OPML_VERSION_PATTERN } end