From 3525fb87e2b4ba311a294f9f725443a7feeef583 Mon Sep 17 00:00:00 2001 From: erwanlr Date: Tue, 9 Apr 2013 17:43:15 +0200 Subject: [PATCH] Browser::Actions (no specs) --- Gemfile | 1 + lib/common/browser.rb | 42 +++++----------- lib/common/browser/actions.rb | 49 +++++++++++++++++++ lib/common/browser/options.rb | 7 +++ lib/common/collections/wp_items/detectable.rb | 2 +- lib/common/hacks.rb | 14 ------ lib/common/models/wp_item/existable.rb | 2 +- lib/common/models/wp_item/infos.rb | 8 +-- lib/common/models/wp_item/versionable.rb | 2 +- lib/common/models/wp_theme/findable.rb | 4 +- lib/common/models/wp_theme/versionable.rb | 2 +- lib/common/models/wp_timthumb/versionable.rb | 2 +- lib/common/models/wp_user/existable.rb | 2 +- lib/common/models/wp_version/findable.rb | 4 +- lib/wpscan/web_site.rb | 16 +++--- lib/wpscan/wp_target.rb | 26 +++++----- lib/wpscan/wp_target/malwares.rb | 2 +- lib/wpscan/wp_target/wp_custom_directories.rb | 6 +-- .../wp_target/wp_full_path_disclosure.rb | 2 +- lib/wpscan/wp_target/wp_login_protection.rb | 14 +++--- lib/wpscan/wp_target/wp_readme.rb | 2 +- lib/wpscan/wp_target/wp_registrable.rb | 5 +- wpscan.rb | 2 +- 23 files changed, 121 insertions(+), 95 deletions(-) create mode 100644 lib/common/browser/actions.rb create mode 100644 lib/common/browser/options.rb diff --git a/Gemfile b/Gemfile index 26d9a044..b27aafd4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source "https://rubygems.org" gem "typhoeus", ">=0.6.2" +gem "ethon", :git => "https://github.com/typhoeus/ethon.git" gem "nokogiri" gem "json" diff --git a/lib/common/browser.rb b/lib/common/browser.rb index eb5e8349..767aa492 100644 --- a/lib/common/browser.rb +++ b/lib/common/browser.rb @@ -1,8 +1,11 @@ # encoding: UTF-8 require 'common/typhoeus_cache' +require 'common/browser/actions' class Browser + extend Browser::Actions + @@instance = nil USER_AGENT_MODES = %w{ static semi-static random } @@ -122,26 +125,6 @@ class Browser end end - def get(url, params = {}) - run_request( - forge_request(url, params.merge(method: :get)) - ) - end - - def post(url, params = {}) - run_request( - forge_request(url, params.merge(method: :post)) - ) - end - - def get_and_follow_location(url, params = {}) - params[:maxredirs] ||= 2 - - run_request( - forge_request(url, params.merge(method: :get, followlocation: true)) - ) - end - def forge_request(url, params = {}) Typhoeus::Request.new( url.to_s, @@ -181,12 +164,19 @@ class Browser params.merge!(ssl_verifypeer: false) params.merge!(ssl_verifyhost: 0) - params.merge!(cookie_jar: @cache_dir + '/cookie-jar') - params.merge!(cookie_file: @cache_dir + '/cookie-jar') + params.merge!(cookiejar: @cache_dir + '/cookie-jar') + params.merge!(cookiefile: @cache_dir + '/cookie-jar') params end + # return the response + def run_request(request) + @hydra.queue request + @hydra.run + request.response + end + private # return Array @@ -199,13 +189,6 @@ class Browser params end - # return the response - def run_request(request) - @hydra.queue request - @hydra.run - request.response - end - # Override with the options if they are set def override_config_with_options(options) options.each do |option, value| @@ -214,4 +197,5 @@ class Browser end end end + end diff --git a/lib/common/browser/actions.rb b/lib/common/browser/actions.rb new file mode 100644 index 00000000..bb3ddde2 --- /dev/null +++ b/lib/common/browser/actions.rb @@ -0,0 +1,49 @@ +# encoding: UTF-8 + +class Browser + module Actions + + # @param [ String ] url + # @param [ Hash ] params + # + # @return [ Typhoeus::Response ] + def get(url, params = {}) + #Typhoeus.get(url, Browser.instance.merge_request_params(params)) + process(url, params.merge(method: :get)) + end + + # @param [ String ] url + # @param [ Hash ] params + # + # @return [ Typhoeus::Response ] + def post(url, params = {}) + #Typhoeus.post(url, Browser.instance.merge_request_params(params)) + process(url, params.merge(method: :post)) + end + + # @param [ String ] url + # @param [ Hash ] params + # + # @return [ Typhoeus::Response ] + def get_and_follow_location(url, params = {}) + params[:maxredirs] ||= 2 + + get(url, params.merge(followlocation: true)) + end + + protected + + # @param [ String ] url + # @param [ Hash ] params + # + # @return [ Typhoeus::Response ] + def process(url, params) + browser = Browser.instance + + browser.run_request( + browser.forge_request(url, params) + ) + end + + end +end diff --git a/lib/common/browser/options.rb b/lib/common/browser/options.rb new file mode 100644 index 00000000..a663802a --- /dev/null +++ b/lib/common/browser/options.rb @@ -0,0 +1,7 @@ +# encoding: UTF-8 + +class Browser + module Options + + end +end diff --git a/lib/common/collections/wp_items/detectable.rb b/lib/common/collections/wp_items/detectable.rb index 4a56bf9a..f53353c6 100755 --- a/lib/common/collections/wp_items/detectable.rb +++ b/lib/common/collections/wp_items/detectable.rb @@ -67,7 +67,7 @@ class WpItems < Array results = new item_class = self.item_class type = self.to_s.gsub(/Wp/, '').downcase - response = Browser.instance.get(wp_target.url) + response = Browser.get(wp_target.url) item_options = { wp_content_dir: wp_target.wp_content_dir, wp_plugins_dir: wp_target.wp_plugins_dir, diff --git a/lib/common/hacks.rb b/lib/common/hacks.rb index 98dcfbbb..c4717d90 100644 --- a/lib/common/hacks.rb +++ b/lib/common/hacks.rb @@ -47,20 +47,6 @@ module Typhoeus end end -module Ethon - class Easy - module Options - def cookie_jar=(value) - Curl.set_option(:cookiejar, value_for(value, :string), handle) - end - - def cookie_file=(value) - Curl.set_option(:cookiefile, value_for(value, :string), handle) - end - end - end -end - # Override for puts to enable logging def puts(o = '') # remove color for logging diff --git a/lib/common/models/wp_item/existable.rb b/lib/common/models/wp_item/existable.rb index 0daa45d0..372607a2 100755 --- a/lib/common/models/wp_item/existable.rb +++ b/lib/common/models/wp_item/existable.rb @@ -13,7 +13,7 @@ class WpItem # @return [ Boolean ] def exists?(options = {}, response = nil) unless response - response = Browser.instance.get(url) + response = Browser.get(url) end exists_from_response?(response, options) end diff --git a/lib/common/models/wp_item/infos.rb b/lib/common/models/wp_item/infos.rb index e4c1f819..dce401a3 100644 --- a/lib/common/models/wp_item/infos.rb +++ b/lib/common/models/wp_item/infos.rb @@ -7,7 +7,7 @@ class WpItem # @return [ Boolean ] def has_readme? - Browser.instance.get(readme_url).code == 200 ? true : false + Browser.get(readme_url).code == 200 ? true : false end # @return [ String ] The url to the readme file @@ -17,7 +17,7 @@ class WpItem # @return [ Boolean ] def has_changelog? - Browser.instance.get(changelog_url).code == 200 ? true : false + Browser.get(changelog_url).code == 200 ? true : false end # @return [ String ] The url to the changelog file @@ -27,7 +27,7 @@ class WpItem # @return [ Boolean ] def has_directory_listing? - Browser.instance.get(@uri.to_s).body[%r{Index of}] ? true : false + Browser.get(@uri.to_s).body[%r{<title>Index of}] ? true : false end # Discover any error_log files created by WordPress @@ -41,7 +41,7 @@ class WpItem # # @return [ Boolean ] def has_error_log? - response_body = Browser.instance.get(error_log_url, headers: {'range' => 'bytes=0-700'}).body + response_body = Browser.get(error_log_url, headers: {'range' => 'bytes=0-700'}).body response_body[%r{PHP Fatal error}i] ? true : false end diff --git a/lib/common/models/wp_item/versionable.rb b/lib/common/models/wp_item/versionable.rb index d1b70595..6370e550 100755 --- a/lib/common/models/wp_item/versionable.rb +++ b/lib/common/models/wp_item/versionable.rb @@ -10,7 +10,7 @@ class WpItem # @return [ String ] The version number def version unless @version - response = Browser.instance.get(readme_url) + response = Browser.get(readme_url) @version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1] end @version diff --git a/lib/common/models/wp_theme/findable.rb b/lib/common/models/wp_theme/findable.rb index 1ef6c32e..8ebc6056 100755 --- a/lib/common/models/wp_theme/findable.rb +++ b/lib/common/models/wp_theme/findable.rb @@ -27,7 +27,7 @@ class WpTheme < WpItem # # @return [ WpTheme ] def find_from_css_link(target_uri) - response = Browser.instance.get_and_follow_location(target_uri.to_s) + response = Browser.get_and_follow_location(target_uri.to_s) # https + domain is optional because of relative links matches = %r{(?:https?://[^"']+)?/([^/]+)/themes/([^"']+)/style.css}i.match(response.body) @@ -49,7 +49,7 @@ class WpTheme < WpItem # # @return [ WpTheme ] def find_from_wooframework(target_uri) - body = Browser.instance.get(target_uri.to_s).body + body = Browser.get(target_uri.to_s).body regexp = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?" />\s+<meta name="generator" content="WooFramework\s?([^"]+)?" />} diff --git a/lib/common/models/wp_theme/versionable.rb b/lib/common/models/wp_theme/versionable.rb index 5c548747..d0d07d25 100755 --- a/lib/common/models/wp_theme/versionable.rb +++ b/lib/common/models/wp_theme/versionable.rb @@ -5,7 +5,7 @@ class WpTheme < WpItem def version unless @version - @version = Browser.instance.get(style_url).body[%r{Version:\s([^\s]+)}i, 1] + @version = Browser.get(style_url).body[%r{Version:\s([^\s]+)}i, 1] # Get Version from readme.txt @version ||= super diff --git a/lib/common/models/wp_timthumb/versionable.rb b/lib/common/models/wp_timthumb/versionable.rb index 95f5a9b4..d570966f 100755 --- a/lib/common/models/wp_timthumb/versionable.rb +++ b/lib/common/models/wp_timthumb/versionable.rb @@ -9,7 +9,7 @@ class WpTimthumb < WpItem # @return [ String ] The version def version unless @version - response = Browser.instance.get(url) + response = Browser.get(url) @version = response.body[%r{TimThumb version\s*: ([^<]+)} , 1] end @version diff --git a/lib/common/models/wp_user/existable.rb b/lib/common/models/wp_user/existable.rb index 924f3981..9fafcdd4 100755 --- a/lib/common/models/wp_user/existable.rb +++ b/lib/common/models/wp_user/existable.rb @@ -24,7 +24,7 @@ class WpUser < WpItem @login = Existable.login_from_author_pattern(location) @display_name = Existable.display_name_from_body( - Browser.instance.get(location).body + Browser.get(location).body ) elsif response.code == 200 # login in body? @login = Existable.login_from_body(response.body) diff --git a/lib/common/models/wp_version/findable.rb b/lib/common/models/wp_version/findable.rb index df71f65c..87c29c36 100755 --- a/lib/common/models/wp_version/findable.rb +++ b/lib/common/models/wp_version/findable.rb @@ -45,7 +45,7 @@ class WpVersion < WpItem # @return [ String ] def scan_url(target_uri, pattern, path = nil) url = path ? target_uri.merge(path).to_s : target_uri.to_s - response = Browser.instance.get_and_follow_location(url) + response = Browser.get_and_follow_location(url) response.body[pattern, 1] end @@ -163,7 +163,7 @@ class WpVersion < WpItem xml.xpath('//file').each do |node| wp_item.path = node.attribute('src').text - response = Browser.instance.get(wp_item.url) + response = Browser.get(wp_item.url) md5sum = Digest::MD5.hexdigest(response.body) node.search('hash').each do |hash| diff --git a/lib/wpscan/web_site.rb b/lib/wpscan/web_site.rb index bce5b41f..a0c03cc8 100644 --- a/lib/wpscan/web_site.rb +++ b/lib/wpscan/web_site.rb @@ -18,11 +18,11 @@ class WebSite # Checks if the remote website is up. def online? - Browser.instance.get(@uri.to_s).code != 0 + Browser.get(@uri.to_s).code != 0 end def has_basic_auth? - Browser.instance.get(@uri.to_s).code == 401 + Browser.get(@uri.to_s).code == 401 end def has_xml_rpc? @@ -38,7 +38,7 @@ class WebSite end def xml_rpc_url_from_headers - headers = Browser.instance.get(@uri.to_s).headers_hash + headers = Browser.get(@uri.to_s).headers_hash xmlrpc_url = nil unless headers.nil? @@ -51,7 +51,7 @@ class WebSite end def xml_rpc_url_from_body - body = Browser.instance.get(@uri.to_s).body + body = Browser.get(@uri.to_s).body body[%r{<link rel="pingback" href="([^"]+)" ?\/?>}, 1] end @@ -62,7 +62,7 @@ class WebSite def redirection(url = nil) redirection = nil url ||= @uri.to_s - response = Browser.instance.get(url) + response = Browser.get(url) if response.code == 301 || response.code == 302 redirection = response.headers_hash['location'] @@ -78,7 +78,7 @@ class WebSite # Return the MD5 hash of the page given by url def self.page_hash(url) - Digest::MD5.hexdigest(Browser.instance.get(url).body) + Digest::MD5.hexdigest(Browser.get(url).body) end def homepage_hash @@ -100,13 +100,13 @@ class WebSite # Will try to find the rss url in the homepage # Only the first one found iw returned def rss_url - homepage_body = Browser.instance.get(@uri.to_s).body + homepage_body = Browser.get(@uri.to_s).body homepage_body[%r{<link .* type="application/rss\+xml" .* href="([^"]+)" />}, 1] end # Checks if a robots.txt file exists def has_robots? - Browser.instance.get(robots_url).code == 200 + Browser.get(robots_url).code == 200 end # Gets a robots.txt URL diff --git a/lib/wpscan/wp_target.rb b/lib/wpscan/wp_target.rb index 4dd7a33e..21d41a28 100644 --- a/lib/wpscan/wp_target.rb +++ b/lib/wpscan/wp_target.rb @@ -11,14 +11,14 @@ require 'wp_target/wp_custom_directories' require 'wp_target/wp_full_path_disclosure' class WpTarget < WebSite - include Malwares - include WpReadme - include BruteForce - include WpRegistrable - include WpConfigBackup - include WpLoginProtection - include WpCustomDirectories - include WpFullPathDisclosure + include WpTarget::Malwares + include WpTarget::WpReadme + include WpTarget::BruteForce + include WpTarget::WpRegistrable + include WpTarget::WpConfigBackup + include WpTarget::WpLoginProtection + include WpTarget::WpCustomDirectories + include WpTarget::WpFullPathDisclosure attr_reader :verbose @@ -38,17 +38,17 @@ class WpTarget < WebSite def wordpress? wordpress = false - response = Browser.instance.get_and_follow_location(@uri.to_s) + response = Browser.get_and_follow_location(@uri.to_s) if response.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i wordpress = true else - response = Browser.instance.get_and_follow_location(xml_rpc_url) + response = Browser.get_and_follow_location(xml_rpc_url) if response.body =~ %r{XML-RPC server accepts POST requests only}i wordpress = true else - response = Browser.instance.get_and_follow_location(login_url) + response = Browser.get_and_follow_location(login_url) if response.code == 200 && response.body =~ %r{WordPress}i wordpress = true @@ -104,7 +104,7 @@ class WpTarget < WebSite def has_debug_log? # We only get the first 700 bytes of the file to avoid loading huge file (like 2Go) - response_body = Browser.instance.get(debug_log_url(), headers: {'range' => 'bytes=0-700'}).body + response_body = Browser.get(debug_log_url(), headers: {'range' => 'bytes=0-700'}).body response_body[%r{\[[^\]]+\] PHP (?:Warning|Error|Notice):}] ? true : false end @@ -120,7 +120,7 @@ class WpTarget < WebSite end def search_replace_db_2_exists? - resp = Browser.instance.get(search_replace_db_2_url) + resp = Browser.get(search_replace_db_2_url) resp.code == 200 && resp.body[%r{by interconnect}i] end end diff --git a/lib/wpscan/wp_target/malwares.rb b/lib/wpscan/wp_target/malwares.rb index dd554393..0fcb5223 100644 --- a/lib/wpscan/wp_target/malwares.rb +++ b/lib/wpscan/wp_target/malwares.rb @@ -17,7 +17,7 @@ class WpTarget < WebSite unless @malwares malwares_found = [] malwares_file = Malwares.malwares_file(malwares_file_path) - index_page_body = Browser.instance.get(@uri.to_s).body + index_page_body = Browser.get(@uri.to_s).body File.open(malwares_file, 'r') do |file| file.readlines.collect do |url| diff --git a/lib/wpscan/wp_target/wp_custom_directories.rb b/lib/wpscan/wp_target/wp_custom_directories.rb index c864ccdb..1e60791a 100644 --- a/lib/wpscan/wp_target/wp_custom_directories.rb +++ b/lib/wpscan/wp_target/wp_custom_directories.rb @@ -6,7 +6,7 @@ class WpTarget < WebSite # @return [ String ] The wp-content directory def wp_content_dir unless @wp_content_dir - index_body = Browser.instance.get(@uri.to_s).body + index_body = Browser.get(@uri.to_s).body uri_path = @uri.path # Only use the path because domain can be text or an IP if index_body[/\/wp-content\/(?:themes|plugins)\//i] || default_wp_content_dir_exists? @@ -22,7 +22,7 @@ class WpTarget < WebSite # @return [ Boolean ] def default_wp_content_dir_exists? - response = Browser.instance.get(@uri.merge('wp-content').to_s) + response = Browser.get(@uri.merge('wp-content').to_s) hash = Digest::MD5.hexdigest(response.body) if WpTarget.valid_response_codes.include?(response.code) @@ -42,7 +42,7 @@ class WpTarget < WebSite # @return [ Boolean ] def wp_plugins_dir_exists? - Browser.instance.get(@uri.merge(wp_plugins_dir)).code != 404 + Browser.get(@uri.merge(wp_plugins_dir).to_s).code != 404 end end diff --git a/lib/wpscan/wp_target/wp_full_path_disclosure.rb b/lib/wpscan/wp_target/wp_full_path_disclosure.rb index dcad94ab..2e97b404 100644 --- a/lib/wpscan/wp_target/wp_full_path_disclosure.rb +++ b/lib/wpscan/wp_target/wp_full_path_disclosure.rb @@ -7,7 +7,7 @@ class WpTarget < WebSite # # @return [ Boolean ] def has_full_path_disclosure? - response = Browser.instance.get(full_path_disclosure_url()) + response = Browser.get(full_path_disclosure_url()) response.body[%r{Fatal error}i] ? true : false end diff --git a/lib/wpscan/wp_target/wp_login_protection.rb b/lib/wpscan/wp_target/wp_login_protection.rb index fda54fe1..4aeac5d0 100644 --- a/lib/wpscan/wp_target/wp_login_protection.rb +++ b/lib/wpscan/wp_target/wp_login_protection.rb @@ -38,17 +38,17 @@ class WpTarget < WebSite # Thanks to Alip Aswalid for providing this method. # http://wordpress.org/extend/plugins/login-lockdown/ def has_login_lockdown_protection? - Browser.instance.get(login_url).body =~ %r{Login LockDown}i ? true : false + Browser.get(login_url).body =~ %r{Login LockDown}i ? true : false end # http://wordpress.org/extend/plugins/login-lock/ def has_login_lock_protection? - Browser.instance.get(login_url).body =~ %r{LOGIN LOCK} ? true : false + Browser.get(login_url).body =~ %r{LOGIN LOCK} ? true : false end # http://wordpress.org/extend/plugins/better-wp-security/ def has_better_wp_security_protection? - Browser.instance.get(better_wp_security_url).code != 404 + Browser.get(better_wp_security_url).code != 404 end def plugin_url(plugin_name) @@ -66,7 +66,7 @@ class WpTarget < WebSite # http://wordpress.org/extend/plugins/simple-login-lockdown/ def has_simple_login_lockdown_protection? - Browser.instance.get(simple_login_lockdown_url).code != 404 + Browser.get(simple_login_lockdown_url).code != 404 end def simple_login_lockdown_url @@ -75,7 +75,7 @@ class WpTarget < WebSite # http://wordpress.org/extend/plugins/login-security-solution/ def has_login_security_solution_protection? - Browser.instance.get(login_security_solution_url()).code != 404 + Browser.get(login_security_solution_url()).code != 404 end def login_security_solution_url @@ -84,7 +84,7 @@ class WpTarget < WebSite # http://wordpress.org/extend/plugins/limit-login-attempts/ def has_limit_login_attempts_protection? - Browser.instance.get(limit_login_attempts_url).code != 404 + Browser.get(limit_login_attempts_url).code != 404 end def limit_login_attempts_url @@ -93,7 +93,7 @@ class WpTarget < WebSite # http://wordpress.org/extend/plugins/bluetrait-event-viewer/ def has_bluetrait_event_viewer_protection? - Browser.instance.get(bluetrait_event_viewer_url).code != 404 + Browser.get(bluetrait_event_viewer_url).code != 404 end def bluetrait_event_viewer_url diff --git a/lib/wpscan/wp_target/wp_readme.rb b/lib/wpscan/wp_target/wp_readme.rb index 9ff9619a..b3b7fae8 100644 --- a/lib/wpscan/wp_target/wp_readme.rb +++ b/lib/wpscan/wp_target/wp_readme.rb @@ -10,7 +10,7 @@ class WpTarget < WebSite # # @return [ Boolean ] def has_readme? - response = Browser.instance.get(readme_url()) + response = Browser.get(readme_url()) unless response.code == 404 return response.body =~ %r{wordpress}i ? true : false diff --git a/lib/wpscan/wp_target/wp_registrable.rb b/lib/wpscan/wp_target/wp_registrable.rb index c8aaf344..72c4c307 100644 --- a/lib/wpscan/wp_target/wp_registrable.rb +++ b/lib/wpscan/wp_target/wp_registrable.rb @@ -7,7 +7,7 @@ class WpTarget < WebSite # # @return [ Boolean ] def registration_enabled? - resp = Browser.instance.get(registration_url) + resp = Browser.get(registration_url) # redirect only on non multi sites if resp.code == 302 and resp.headers_hash['location'] =~ /wp-login\.php\?registration=disabled/i enabled = false @@ -34,8 +34,7 @@ class WpTarget < WebSite unless @multisite # when multi site, there is no redirection or a redirect to the site itself # otherwise redirect to wp-login.php - url = @uri.merge('wp-signup.php') - resp = Browser.instance.get(url) + resp = Browser.get(@uri.merge('wp-signup.php').to_s) if resp.code == 302 and resp.headers_hash['location'] =~ /wp-login\.php\?action=register/ @multisite = false diff --git a/wpscan.rb b/wpscan.rb index 952d8c27..9078601e 100755 --- a/wpscan.rb +++ b/wpscan.rb @@ -48,7 +48,7 @@ def main end if wpscan_options.proxy - proxy_response = Browser.instance.get(wp_target.url) + proxy_response = Browser.get(wp_target.url) unless WpTarget::valid_response_codes.include?(proxy_response.code) raise "Proxy Error :\r\n#{proxy_response.headers}"