diff --git a/lib/wpscan/modules/wp_item.rb b/lib/wpscan/modules/wp_item.rb new file mode 100644 index 00000000..ae3e25b3 --- /dev/null +++ b/lib/wpscan/modules/wp_item.rb @@ -0,0 +1,66 @@ +#-- +# WPScan - WordPress Security Scanner +# Copyright (C) 2012 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +#++ + +module WpItem + attr_accessor :path, :base_url, :wp_content_dir + @version = nil + + def get_url + URI.parse("#{@base_url.to_s}#@wp_content_dir/#@path") + end + + def version + unless @version + response = Browser.instance.get(get_url.merge("readme.txt").to_s) + @version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1] + end + @version + end + + # Is directory listing enabled? + def directory_listing? + # Need to remove to file part from the url + Browser.instance.get(location_uri_from_file_url(get_url.to_s)).body[%r{Index of}] ? true : false + end + + def extract_name_from_url(url) + url.to_s[%r{^(https?://.*/([^/]+)/)}i, 2] + end + + def to_s + item_version = version + "#@name#{' v' + item_version if item_version}" + end + + def ==(item) + item.name == @name + end + + def <=>(item) + item.name <=> @name + end + + def location_uri_from_file_url(location_url) + valid_location_url = location_url[%r{^(https?://.*/)[^.]+\.[^/]+$}, 1] + unless valid_location_url + valid_location_url = add_trailing_slash(location_url) + end + URI.parse(valid_location_url) + end + +end diff --git a/lib/wpscan/modules/wp_login_protection.rb b/lib/wpscan/modules/wp_login_protection.rb index 282c2617..7ef10739 100644 --- a/lib/wpscan/modules/wp_login_protection.rb +++ b/lib/wpscan/modules/wp_login_protection.rb @@ -37,11 +37,8 @@ module WpLoginProtection plugin_name = symbol_to_call[@@login_protection_method_pattern, 1].gsub('_', '-') return @login_protection_plugin = WpPlugin.new( - WpPlugin::create_location_url_from_name( - plugin_name, - @uri.to_s - ), - :name => plugin_name + :name => plugin_name, + :base_url => @uri.to_s ) end end diff --git a/lib/wpscan/modules/wp_plugins.rb b/lib/wpscan/modules/wp_plugins.rb index bd5000db..3533439c 100644 --- a/lib/wpscan/modules/wp_plugins.rb +++ b/lib/wpscan/modules/wp_plugins.rb @@ -19,92 +19,18 @@ module WpPlugins # Enumerate installed plugins. - # Available options : see #targets_url - # :show_progress_bar - default false # # return array of WpPlugin - def plugins_from_aggressive_detection(options = {}) - browser = Browser.instance - hydra = browser.hydra - found_plugins = options[:only_vulnerable_ones] ? [] : plugins_from_passive_detection() - request_count = 0 - queue_count = 0 - local_404_hash = error_404_hash() - valid_response_codes = WpPlugins.valid_response_codes() - targets_url = plugins_targets_url(options) - show_progress_bar = options[:show_progress_bar] || false - - targets_url.each do |target_url| - request = browser.forge_request(target_url, :cache_timeout => 0, :follow_location => true) - request_count += 1 - - request.on_complete do |response| - print "\rChecking for #{targets_url.size} total plugins... #{(request_count * 100) / targets_url.size}% complete." if show_progress_bar - - if valid_response_codes.include?(response.code) - if Digest::MD5.hexdigest(response.body) != local_404_hash - found_plugins << WpPlugin.new(target_url) - end - end - end - - hydra.queue(request) - queue_count += 1 - - if queue_count == browser.max_threads - hydra.run - queue_count = 0 - end - end - - hydra.run - - found_plugins + def plugins_from_aggressive_detection(options) + options[:file] = "#{DATA_DIR}/plugins.txt" + options[:vulns_file] = "#{DATA_DIR}/plugin_vulns.xml" + options[:vulns_xpath] = "//plugin[@name='#{@name}']/vulnerability" + options[:type] = "plugins" + result = WpDetector.aggressive_detection(options) + result end - def self.valid_response_codes - [200, 403, 301, 302] - end - - # Available options : - # :only_vulnerable_ones - default false - # :plugins_file - default DATA_DIR/plugins.txt - # :plugin_vulns_file - default DATA_DIR/plugin_vulns.xml - # - # @return Array of String - def plugins_targets_url(options = {}) - only_vulnerable = options[:only_vulnerable_ones] || false - plugins_file = options[:plugins_file] || "#{DATA_DIR}/plugins.txt" - plugin_vulns_file = options[:plugin_vulns_file] || "#{DATA_DIR}/plugin_vulns.xml" - targets_url = [] - - if only_vulnerable == false - # Open and parse the 'most popular' plugin list... - File.open(plugins_file, 'r') do |file| - file.readlines.collect do |line| - targets_url << WpPlugin.create_url_from_raw(line.chomp, @uri) - end - end - end - - xml = Nokogiri::XML(File.open(plugin_vulns_file)) do |config| - config.noblanks - end - - # We check if the plugin name from the plugin_vulns_file is already in targets, otherwise we add it - xml.xpath("//plugin").each do |node| - plugin_name = node.attribute('name').text - - if targets_url.grep(%r{/#{plugin_name}/}).empty? - targets_url << WpPlugin.create_location_url_from_name(plugin_name, url()) - end - end - - targets_url.flatten! - targets_url.uniq! - # randomize the plugins array to *maybe* help in some crappy IDS/IPS/WAF detection - targets_url.sort_by! { rand } - end + private # http://code.google.com/p/wpscan/issues/detail?id=42 # plugins can be found in the source code : @@ -112,18 +38,16 @@ module WpPlugins # <link rel='stylesheet' href='http://example.com/wp-content/plugins/wp-minify/..' type='text/css' media='screen'/> # ... # return array of WpPlugin - def plugins_from_passive_detection - plugins = [] - response = Browser.instance.get(url()) - plugins_names = response.body.scan(%r{(?:[^=:]+)\s?(?:=|:)\s?(?:"|')[^"']+\\?/wp-content\\?/plugins\\?/([^/\\"']+)\\?(?:/|"|')}i) + def plugins_from_passive_detection(wp_content_dir) + plugins = [] + temp = WpDetector.passive_detection(url(), "plugins", wp_content_dir) - plugins_names.flatten! - plugins_names.uniq! - - plugins_names.each do |plugin_name| + temp.each do |item| plugins << WpPlugin.new( - WpPlugin.create_location_url_from_name(plugin_name, url()), - :name => plugin_name + :base_url => item[:base_url], + :name => item[:name], + :path => item[:path], + :wp_content_dir => wp_content_dir ) end plugins diff --git a/lib/wpscan/wp_detector.rb b/lib/wpscan/wp_detector.rb new file mode 100644 index 00000000..f21ee7fa --- /dev/null +++ b/lib/wpscan/wp_detector.rb @@ -0,0 +1,57 @@ +#-- +# WPScan - WordPress Security Scanner +# Copyright (C) 2012 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#++ + +class WpDetector + + def self.aggressive_detection(options, items = []) + WpOptions.check_options(options) + + result = items + unless items == nil or items.length == 0 + result = passive_detection(options[:url], options[:type], options[:wp_content_dir]) + end + + enum_results = WpEnumerator.enumerate(options) + enum_results.each do |enum_result| + result << enum_result + end + result + end + + # plugins and themes can be found in the source code : + # <script src='http://example.com/wp-content/plugins/s2member/...' /> + # <link rel='stylesheet' href='http://example.com/wp-content/plugins/wp-minify/..' type='text/css' media='screen'/> + # ... + def self.passive_detection(url, type, wp_content_dir) + items = [] + response = Browser.instance.get(url) + regex1 = %r{(?:[^=:]+)\s?(?:=|:)\s?(?:"|')[^"']+\\?/} + regex2 = %r{\\?/} + regex3 = %r{\\?/([^/\\"']+)\\?(?:/|"|')} + # Custom wp-content dir is now used in this regex + names = response.body.scan(/#{regex1}#{wp_content_dir}#{regex2}#{type}#{regex3}/i) + + names.flatten! + names.uniq! + + names.each do |item| + items << { :base_url => url, :name => item, :path => "#{type}/#{item}" } + end + items + end +end \ No newline at end of file diff --git a/lib/wpscan/wp_enumerator.rb b/lib/wpscan/wp_enumerator.rb new file mode 100644 index 00000000..32115428 --- /dev/null +++ b/lib/wpscan/wp_enumerator.rb @@ -0,0 +1,118 @@ +#-- +# WPScan - WordPress Security Scanner +# Copyright (C) 2012 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#++ + +# Enumerate over a given set of items and check if they exist +class WpEnumerator + + # Enumerate the given Targets + # + # ==== Attributes + # + # * +targets+ - targets to enumerate + # * * +:base_url+ - Base URL + # * * +:wp_content+ - wp-content directory + # * * +:path+ - Path to plugin + # * +type+ - "plugins" or "themes", item to enumerate + # * +filename+ - filename in the data directory with paths + # * +show_progress_bar+ - Show a progress bar during enumeration + def self.enumerate(options = {}) + + WpOptions.check_options(options) + + targets = self.generate_items(options) + + found = [] + queue_count = 0 + request_count = 0 + enum_browser = Browser.instance + enum_hydra = enum_browser.hydra + enumerate_size = targets.size + + targets.each do |target| + url = target.get_url + request = enum_browser.forge_request(url, :cache_timeout => 0, :follow_location => true) + request_count += 1 + + request.on_complete do |response| + if options[:show_progress_bar] + print "\rChecking for #{enumerate_size} total #{options[:type]}... #{(request_count * 100) / enumerate_size}% complete." + end + if WpTarget.valid_response_codes.include?(response.code) + if Digest::MD5.hexdigest(response.body) != options[:error_404_hash] + found << target + end + end + end + + enum_hydra.queue(request) + queue_count += 1 + + if queue_count == enum_browser.max_threads + enum_hydra.run + queue_count = 0 + end + end + + enum_hydra.run + found + end + + private + + def self.generate_items(options = {}) + only_vulnerable = options[:only_vulnerable_ones] + plugins_file = options[:file] || "#{DATA_DIR}/plugins.txt" + plugin_vulns_file = options[:vulns_file] || "#{DATA_DIR}/plugin_vulns.xml" + wp_content_dir = options[:wp_content_dir] + url = options[:base_url] + type = options[:type] + targets_url = [] + + if only_vulnerable == false + # Open and parse the 'most popular' plugin list... + File.open(plugins_file, 'r') do |file| + file.readlines.collect do |line| + targets_url << WpPlugin.new(:base_url => url, :path => line.strip, :wp_content_dir => wp_content_dir) + end + end + end + + xml = Nokogiri::XML(File.open(plugin_vulns_file)) do |config| + config.noblanks + end + + # We check if the plugin name from the plugin_vulns_file is already in targets, otherwise we add it + xml.xpath("//plugin").each do |node| + plugin_name = node.attribute('name').text + + if targets_url.grep(%r{/#{plugin_name}/}).empty? + targets_url << WpPlugin.new( + :base_url => url, + :path => "#{type}/#{plugin_name}", + :wp_content_dir => wp_content_dir, + :name => plugin_name + ) + end + end + + targets_url.flatten! + targets_url.uniq! + # randomize the plugins array to *maybe* help in some crappy IDS/IPS/WAF detection + targets_url.sort_by! { rand } + end +end diff --git a/lib/wpscan/wp_options.rb b/lib/wpscan/wp_options.rb new file mode 100644 index 00000000..5a748ac3 --- /dev/null +++ b/lib/wpscan/wp_options.rb @@ -0,0 +1,50 @@ +#-- +# WPScan - WordPress Security Scanner +# Copyright (C) 2012 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#++ + +class WpOptions + def self.get_empty_options + options = { + :url => "", + :only_vulnerable_ones => true, + :file => "", + :vulns_file => "", + :vulns_xpath => "", + :wp_content_dir => "", + :show_progress_bar => true, + :error_404_hash => "", + :type => "" + } + options + end + + def self.check_options(options) + raise("url must be set") unless options[:url] + raise("only_vulnerable_ones must be set") unless options[:only_vulnerable_ones] + raise("file must be set") unless options[:file] + raise("vulns_file must be set") unless options[:vulns_file] + raise("vulns_xpath must be set") unless options[:vulns_xpath] + raise("wp_content_dir must be set") unless options[:wp_content_dir] + raise("show_progress_bar must be set") unless options[:show_progress_bar] + raise("error_404_hash must be set") unless options[:error_404_hash] + raise("type must be set") unless options[:type] + unless options[:type] =~ /plugins/i or options[:type] =~ /themes/i + raise("Unknown type #{options[:type]}") + end + end + +end \ No newline at end of file diff --git a/lib/wpscan/wp_plugin.rb b/lib/wpscan/wp_plugin.rb index dfdecd7c..0e5177df 100644 --- a/lib/wpscan/wp_plugin.rb +++ b/lib/wpscan/wp_plugin.rb @@ -19,38 +19,22 @@ require "#{WPSCAN_LIB_DIR}/vulnerable" class WpPlugin < Vulnerable - @@location_url_pattern = %r{^(https?://.*/([^/]+)/)}i + include WpItem - attr_reader :name + def initialize(options = {}) + @base_url = options[:base_url] + @path = options[:path] + @wp_content_dir = options[:wp_content_dir] + @name = options[:name] || extract_name_from_url(get_url) + @vulns_xml = options[:vulns_xml] || DATA_DIR + '/plugin_vulns.xml' + @vulns_xpath = "//plugin[@name='#@name']/vulnerability" + @version = nil - def initialize(location_url, options = {}) - @location_uri = WpPlugin.location_uri_from_url(location_url) - @name = options[:name] || WpPlugin.extract_name_from_location_url(location_url) - @vulns_xml = options[:vulns_xml] || DATA_DIR + '/plugin_vulns.xml' - @vulns_xpath = "//plugin[@name='#{@name}']/vulnerability" - end - - def location_url - @location_uri.to_s - end - - def ==(plugin) - plugin.name == @name - end - - def <=>(plugin) - plugin.name <=> @name - end - - # http://code.google.com/p/wpscan/issues/detail?id=97 - def version - response = Browser.instance.get(@location_uri.merge("readme.txt").to_s) - response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1] - end - - def to_s - version = version() - "#{@name}#{' v' + version if version}" + raise("base_url not set") unless @base_url + raise("path not set") unless @path + raise("wp_content_dir not set") unless @wp_content_dir + raise("name not set") unless @name + raise("vulns_xml not set") unless @vulns_xml end # Discover any error_log files created by WordPress @@ -64,39 +48,7 @@ class WpPlugin < Vulnerable end def error_log_url - @location_uri.merge("error_log").to_s + get_url.merge("error_log").to_s end - # Is directory listing enabled? - # WordPress denies directory listing however, - # forgets about the plugin directory. - def directory_listing? - Browser.instance.get(location_url()).body[%r{<title>Index of}] ? true : false - end - - def self.create_location_url_from_name(name, target_uri) - if target_uri.is_a?(String) - target_uri = URI.parse(target_uri) - end - target_uri.merge(URI.escape("$wp-plugins$/#{name}/")).to_s - end - - def self.create_url_from_raw(raw, target_uri) - target_uri.merge(URI.escape("$wp-plugins$/#{raw}")).to_s - end - - protected - def self.extract_name_from_location_url(location_url) - location_url[@@location_url_pattern, 2] - end - - def self.location_uri_from_url(location_url) - valid_location_url = location_url[%r{^(https?://.*/)[^.]+\.[^/]+$}, 1] - - unless valid_location_url - valid_location_url = add_trailing_slash(location_url) - end - - URI.parse(valid_location_url) - end end diff --git a/lib/wpscan/wp_target.rb b/lib/wpscan/wp_target.rb index 9e8ba8c1..d68c94c1 100644 --- a/lib/wpscan/wp_target.rb +++ b/lib/wpscan/wp_target.rb @@ -50,7 +50,7 @@ class WpTarget url = @uri.merge("wp-login.php").to_s # Let's check if the login url is redirected (to https url for example) - if redirection = redirection(url) + if redirection == redirection(url) url = redirection end @@ -70,6 +70,11 @@ class WpTarget @error_404_hash end + # Valid HTTP return codes + def self.valid_response_codes + [200, 403, 301, 302] + end + # return WpTheme def theme WpTheme.find(@uri) diff --git a/spec/lib/wpscan/wp_plugin_spec.rb b/spec/lib/wpscan/wp_plugin_spec.rb index e922cc7e..2759ea23 100644 --- a/spec/lib/wpscan/wp_plugin_spec.rb +++ b/spec/lib/wpscan/wp_plugin_spec.rb @@ -44,6 +44,11 @@ describe WpPlugin do @expected_uri_string = "http://example.com/wp-content/plugins/example/" end + it "should return the uri without the file" do + @url = "https://sub.example.com/path/to/dir/wp-content/plugins/example/readme.txt" + @expected_uri_string = "https://sub.example.com/path/to/dir/wp-content/plugins/example/" + end + it "should return the same uri" do @url = "http://example.com/wp-content/plugins/hello-world/" @expected_uri_string = @url @@ -65,6 +70,10 @@ describe WpPlugin do it "should return 'example-plugin'" do WpPlugin.extract_name_from_location_url('http://example.com/wp-content/plugins/example-plugin/').should === 'example-plugin' end + + it "should return 'example-plugin'" do + WpPlugin.extract_name_from_location_url('https://sub.example.com/path/to/a/wp-content/plugins/example-plugin/').should === 'example-plugin' + end end describe "#create_location_url_from_name" do diff --git a/wpscan.rb b/wpscan.rb index a2ea24e0..aac4028a 100755 --- a/wpscan.rb +++ b/wpscan.rb @@ -78,9 +78,7 @@ begin end end - if wp_content_dir = wp_target.wp_content_dir() - Browser.instance.variables_to_replace_in_url = {"$wp-content$" => wp_content_dir, "$wp-plugins$" => wp_target.wp_plugins_dir()} - else + unless wp_target.wp_content_dir raise "The wp_content_dir has not been found, please supply it with --wp-content-dir" end @@ -89,7 +87,7 @@ begin puts "| Started on #{Time.now.asctime}" puts - if wp_theme = wp_target.theme + if wp_theme == wp_target.theme theme_version = wp_theme.version puts "[!] The WordPress theme in use is #{wp_theme}" @@ -98,8 +96,8 @@ begin puts "[+] We have identified #{theme_vulnerabilities.size} vulnerabilities for this theme :" theme_vulnerabilities.each do |vulnerability| puts - puts " | * Title: " + vulnerability.title - puts " | * Reference: " + vulnerability.reference + puts " | * Title: #{vulnerability.title}" + puts " | * Reference: #{vulnerability.reference}" end puts end @@ -132,7 +130,7 @@ begin puts end - if wp_version = wp_target.version + if wp_version == wp_target.version puts "[!] WordPress version #{wp_version.number} identified from #{wp_version.discovery_method}" version_vulnerabilities = wp_version.vulnerabilities @@ -142,33 +140,33 @@ begin puts "[+] We have identified #{version_vulnerabilities.size} vulnerabilities from the version number :" version_vulnerabilities.each do |vulnerability| puts - puts " | * Title: " + vulnerability.title - puts " | * Reference: " + vulnerability.reference + puts " | * Title: #{vulnerability.title}" + puts " | * Reference: #{vulnerability.reference}" end end end if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil puts - print "[+] Enumerating plugins from passive detection ... " + puts "[+] Enumerating plugins from passive detection ... " plugins = wp_target.plugins_from_passive_detection unless plugins.empty? - print "#{plugins.size} found :\n" + puts "#{plugins.size} found :" plugins.each do |plugin| puts - puts " | Name: " + plugin.name - puts " | Location: " + plugin.location_url.gsub("$wp-plugins$", wp_target.wp_plugins_dir()) #Hotfix + puts " | Name: #{plugin.name}" + puts " | Location: #{plugin.get_url}" plugin.vulnerabilities.each do |vulnerability| puts " |" - puts " | [!] " + vulnerability.title - puts " | * Reference: " + vulnerability.reference + puts " | [!] #{vulnerability.title}" + puts " | * Reference: #{vulnerability.reference}" end end else - print "No plugins found :(\n" + puts "No plugins found :(" end end @@ -178,20 +176,22 @@ begin puts "[+] Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ..." puts - plugins = wp_target.plugins_from_aggressive_detection( - :only_vulnerable_ones => wpscan_options.enumerate_only_vulnerable_plugins, - :show_progress_bar => true - ) + options = WpOptions.get_empty_options + options[:base_url] = wp_target.uri + options[:only_vulnerable_ones] = wpscan_options.enumerate_only_vulnerable_plugins, + options[:show_progress_bar] = true, + options[:wp_content_dir] = wp_target.wp_content_dir + + plugins = wp_target.plugins_from_aggressive_detection(options) unless plugins.empty? puts puts - puts "[+] We found " + plugins.size.to_s + " plugins:" + puts "[+] We found #{plugins.size.to_s} plugins:" plugins.each do |plugin| puts puts " | Name: #{plugin}" #this will also output the version number if detected - puts " | Location: " + plugin.location_url.gsub("$wp-plugins$", wp_target.wp_plugins_dir()) #Hotfix - + puts " | Location: #{plugin.get_url}" puts " | Directory listing enabled? #{plugin.directory_listing? ? "Yes." : "No."}" plugin.vulnerabilities.each do |vulnerability| @@ -199,8 +199,8 @@ begin #vulnerability['vulnerability'][0]['postdata'] == nil ? "" : postdata = CGI.unescapeHTML(vulnerability['vulnerability'][0]['postdata']) # postdata puts " |" - puts " | [!] " + vulnerability.title - puts " | * Reference: " + vulnerability.reference + puts " | [!] #{vulnerability.title}" + puts " | * Reference: #{vulnerability.reference}" # This has been commented out as MSF are moving from # XML-RPC to MessagePack. @@ -212,7 +212,7 @@ begin end if plugin.error_log? - puts " | [!] A WordPress error_log file has been found : " + plugin.error_log_url + puts " | [!] A WordPress error_log file has been found : #{plugin.error_log_url}" end end else @@ -230,11 +230,11 @@ begin timthumbs = wp_target.timthumbs puts - puts "[+] We found " + timthumbs.size.to_s + " timthumb file/s :" + puts "[+] We found #{timthumbs.size.to_s} timthumb file/s :" puts timthumbs.each do |file_url| - puts " | [!] " + file_url + puts " | [!] #{file_url}" end puts puts " * Reference: http://www.exploit-db.com/exploits/17602/" @@ -259,10 +259,10 @@ begin exit(1) else puts - puts "We found the following " + usernames.length.to_s + " username/s :" + puts "We found the following #{usernames.length.to_s} username/s :" puts - usernames.each {|username| puts " " + username} + usernames.each {|username| puts " #{username}"} end else @@ -296,7 +296,7 @@ begin end puts - puts '[+] Finished at ' + Time.now.asctime + puts "[+] Finished at #{Time.now.asctime}" exit() # must exit! rescue => e puts "[ERROR] #{e.message}"