diff --git a/lib/common/common_helper.rb b/lib/common/common_helper.rb index 98185ce4..e2ff9112 100644 --- a/lib/common/common_helper.rb +++ b/lib/common/common_helper.rb @@ -304,3 +304,11 @@ end def url_encode(str) CGI.escape(str).gsub("+", "%20") end + +# Check valid JSON? +def valid_json?(json) + JSON.parse(json) + return true + rescue JSON::ParserError => e + return false +end \ No newline at end of file diff --git a/lib/wpscan/wp_target.rb b/lib/wpscan/wp_target.rb index 9fa0325e..32399056 100644 --- a/lib/wpscan/wp_target.rb +++ b/lib/wpscan/wp_target.rb @@ -1,22 +1,24 @@ # encoding: UTF-8 require 'web_site' -require 'wp_target/wp_readme' -require 'wp_target/wp_registrable' +require 'wp_target/wp_api' require 'wp_target/wp_config_backup' -require 'wp_target/wp_must_use_plugins' -require 'wp_target/wp_login_protection' require 'wp_target/wp_custom_directories' require 'wp_target/wp_full_path_disclosure' +require 'wp_target/wp_login_protection' +require 'wp_target/wp_must_use_plugins' +require 'wp_target/wp_readme' +require 'wp_target/wp_registrable' class WpTarget < WebSite - include WpTarget::WpReadme - include WpTarget::WpRegistrable + include WpTarget::WpAPI include WpTarget::WpConfigBackup - include WpTarget::WpMustUsePlugins - include WpTarget::WpLoginProtection include WpTarget::WpCustomDirectories include WpTarget::WpFullPathDisclosure + include WpTarget::WpLoginProtection + include WpTarget::WpMustUsePlugins + include WpTarget::WpReadme + include WpTarget::WpRegistrable attr_reader :verbose diff --git a/lib/wpscan/wp_target/wp_api.rb b/lib/wpscan/wp_target/wp_api.rb new file mode 100644 index 00000000..f4c9dc15 --- /dev/null +++ b/lib/wpscan/wp_target/wp_api.rb @@ -0,0 +1,66 @@ +# encoding: UTF-8 + +class WpTarget < WebSite + module WpAPI + + # Checks to see if the REST API is enabled + # + # This by default in a WordPress installation since 4.5+ + # @return [ Boolean ] + def has_api?(url) + # Make the request + response = Browser.get(url) + + # Able to view the output? + if valid_json?(response.body) + # Read in JSON + data = JSON.parse(response.body) + + # If there is nothing there, return false + return false if data.empty? + + # WAF/API disabled response + return false if data.include?('message') and data['message'] =~ /Only authenticated users can access the REST API/ + + # Success! + return true if response.code == 200 + end + + # Something went wrong + return false + end + + # @return [ String ] The API/JSON URL + def json_url + @uri.merge('/wp-json/').to_s + end + + # @return [ String ] The API/JSON URL to show users + def json_users_url + @uri.merge('/wp-json/wp/v2/users').to_s + end + + # @return [ String ] The API/JSON URL to show users + def json_get_users(url) + # Make the request + response = Browser.get(url) + + # Able to view the output? + return false if not valid_json?(response.body) + + # Read in JSON + data = JSON.parse(response.body) + + # If there is nothing there, return false + return false if data.empty? + + # If not HTTP 200, return false + return false if response.code != 200 + + data.each do |child| + puts notice("ID: #{child['id']} | Name: #{child['name']}") + end + end + + end +end diff --git a/wpscan.rb b/wpscan.rb index a389561f..1a512e85 100755 --- a/wpscan.rb +++ b/wpscan.rb @@ -299,6 +299,21 @@ def main puts info("XML-RPC Interface available under: #{wp_target.xml_rpc_url}") end + if wp_target.has_api?(wp_target.json_url) + puts info("API exposed: #{wp_target.json_url}") + + if wp_target.has_api?(wp_target.json_users_url) + puts warning("Users exposed via API: #{wp_target.json_users_url}") + + # Print users from JSON + wp_target.json_get_users(wp_target.json_users_url) + end + end + + if wp_target.has_full_path_disclosure? + puts warning("Full Path Disclosure (FPD) in '#{wp_target.full_path_disclosure_url}': #{wp_target.full_path_disclosure_data}") + end + if wp_target.upload_directory_listing_enabled? puts warning("Upload directory has directory listing enabled: #{wp_target.upload_dir_url}") end