From 8b0558063ed9a6c9eef937a92a27f2588cff0d34 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Thu, 13 Sep 2012 00:06:50 +0200 Subject: [PATCH] generate list of popular or all themes --- lib/environment.rb | 2 +- lib/wpstools/generate_plugin_list.rb | 141 --------------------------- lib/wpstools/parse_svn.rb | 128 ++++++++++++++++++++++++ lib/wpstools/wpstools_helper.rb | 11 +++ wpscan.rb | 5 +- wpstools.rb | 36 ++++++- 6 files changed, 175 insertions(+), 148 deletions(-) delete mode 100644 lib/wpstools/generate_plugin_list.rb create mode 100644 lib/wpstools/parse_svn.rb diff --git a/lib/environment.rb b/lib/environment.rb index 76a669ec..f4e63b67 100644 --- a/lib/environment.rb +++ b/lib/environment.rb @@ -23,7 +23,7 @@ rescue LoadError => e puts "[ERROR] #{e}" if missing_gem = e.to_s[%r{ -- ([^\s]+)}, 1] - puts "[TIP] Try to run 'gem install #{missing_gem}' or 'gem install --user-install #{missing_gem}'. If you still get an error, Please see README file or http://code.google.com/p/wpscan/" + puts "[TIP] Try to run 'gem install #{missing_gem}' or 'gem install --user-install #{missing_gem}'. If you still get an error, Please see README file or https://github.com/wpscanteam/wpscan" end exit(1) end diff --git a/lib/wpstools/generate_plugin_list.rb b/lib/wpstools/generate_plugin_list.rb deleted file mode 100644 index a8ea0d1b..00000000 --- a/lib/wpstools/generate_plugin_list.rb +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env ruby - -# -# WPScan - WordPress Security Scanner -# Copyright (C) 2011 Ryan Dewhurst AKA ethicalhack3r -# -# 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 . -# -# ryandewhurst at gmail -# - -# This tool generates a plugin list to use for plugin enumeration - -class Generate_Plugin_List - - attr_accessor :pages, :verbose - - def initialize(pages, verbose) - @pages = pages.to_i - @verbose = verbose - @browser = Browser.instance - @hydra = @browser.hydra - end - - # Send a HTTP request to the WordPress most popular plugins webpage - # parse the response for the plugin names. - - def parse_popular_plugins - - found_plugins = [] - page_count = 1 - queue_count = 0 - - (1...@pages).each do |page| - - request = @browser.forge_request('http://wordpress.org/extend/plugins/browse/popular/page/'+page.to_s+'/') - - queue_count += 1 - - request.on_complete do |response| - puts "[+] Parsing page " + page_count.to_s if @verbose - page_count += 1 - response.body.scan(%r{

.+

}i).each do |plugin| - found_plugins << plugin[0] - end - end - - @hydra.queue(request) - - if queue_count == @browser.max_threads - @hydra.run - queue_count = 0 - end - - end - - @hydra.run - - found_plugins.uniq - end - - def parse_full_plugins - found_plugins = [] - index = @browser.get('http://plugins.svn.wordpress.org/').body - index.scan(%r{
  • (.*)/
  • }i).each do |plugin| - found_plugins << plugin[0] - end - found_plugins.uniq - end - - # Use the WordPress plugin SVN repo to find a - # valid plugin file. This will cut down on - # false positives. See issue 39. - - def parse_plugin_files(plugins) - - plugins_with_paths = "" - queue_count = 0 - - plugins.each do |plugin| - - request = @browser.forge_request('http://plugins.svn.wordpress.org/' + plugin + '/trunk/') - - request.on_complete do |response| - - puts "[+] Parsing plugin " + plugin + " [" + response.code.to_s + "]" if @verbose - file = response.body[%r{
  • .+
  • }i, 1] - if file - # Only count Plugins with contents - plugin += "/" + file - plugins_with_paths << plugin + "\n" - end - end - - queue_count += 1 - @hydra.queue(request) - - # the wordpress server stops - # responding if we dont use this. - if queue_count == @browser.max_threads - @hydra.run - queue_count = 0 - end - - end - - @hydra.run - - plugins_with_paths - end - - # Save the file - - def save_file(full=false) - begin - if (full) - plugins = parse_full_plugins - else - plugins = parse_popular_plugins - end - puts "[*] We have parsed " + plugins.size.to_s - plugins_with_paths = parse_plugin_files(plugins) - File.open(DATA_DIR + '/plugins.txt', 'w') { |f| f.write(plugins_with_paths) } - puts "New data/plugin.txt file created with " + plugins_with_paths.scan(/\n/).size.to_s + " entries." - rescue => e - puts "ERROR: Something went wrong :( " + e.inspect - end - end - -end diff --git a/lib/wpstools/parse_svn.rb b/lib/wpstools/parse_svn.rb new file mode 100644 index 00000000..2e5d9027 --- /dev/null +++ b/lib/wpstools/parse_svn.rb @@ -0,0 +1,128 @@ +#!/usr/bin/env ruby + +# +# 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 . + +# This Class Parses SVN Repositories via HTTP +class Svn_Parser + + attr_accessor :verbose, :svn_root, :keep_empty_dirs + + def initialize(svn_root, verbose, keep_empty_dirs = false) + @svn_root = svn_root + @verbose = verbose + @keep_empty_dirs = keep_empty_dirs + @svn_browser = Browser.instance + @svn_hydra = @svn_browser.hydra + end + + def parse(dirs = nil) + if dirs == nil + dirs = get_root_directories + end + urls = get_svn_project_urls(dirs) + entries = get_svn_file_entries(urls) + return entries + end + + # Gets all directories in the SVN root + def get_root_directories + dirs = [] + rootindex = @svn_browser.get(@svn_root).body + rootindex.scan(%r{
  • (.*)/
  • }i).each do |dir| + dirs << dir[0] + end + dirs.uniq + dirs.sort + return dirs + end + + def get_svn_project_urls(dirs) + urls = [] + queue_count = 0 + # First get all trunk or version directories + dirs.each do |dir| + svnurl = @svn_root + dir + "/" + request = @svn_browser.forge_request(svnurl) + request.on_complete do |response| + # trunk folder present + if contains_trunk(response) + puts "[+] Adding trunk on #{dir}" if @verbose + urls << (svnurl << "trunk/") + # no trunk folder. This is true on theme svn repos + else + folders = response.body.scan(%r{^\s*
  • .*/
  • $}i) + if folders != nil and folders.length > 0 + last_version = folders.last[0] + puts "[+] Adding #{last_version} on #{dir}" if @verbose + urls << (svnurl + last_version + "/") + else + puts "[+] No content in #{dir}" if @verbose + end + end + end + queue_count += 1 + @svn_hydra.queue(request) + # the wordpress server stops + # responding if we dont use this. + if queue_count == @svn_browser.max_threads + @svn_hydra.run + queue_count = 0 + end + end + @svn_hydra.run + return urls + end + + # Get a file in each directory + def get_svn_file_entries(urls) + entries = [] + queue_count = 0 + urls.each do |url| + request = @svn_browser.forge_request(url) + request.on_complete do |response| + puts "[+] Parsing url #{url} [#{response.code.to_s}]" if @verbose + file = response.body[%r{
  • .*
  • }i, 1] + # TODO: recursive parsing of subdirectories if there is no file in the root directory + if file + url += "/" + file + entries << url + elsif @keep_empty_dirs + entries << url + end + end + queue_count += 1 + @svn_hydra.queue(request) + # the wordpress server stops + # responding if we dont use this. + if queue_count == @svn_browser.max_threads + @svn_hydra.run + queue_count = 0 + end + end + @svn_hydra.run + return entries + end + + def contains_trunk(body) + contains = false + if !!(body =~ %r[
  • trunk/
  • ]i) + contains = true + end + return contains + end +end diff --git a/lib/wpstools/wpstools_helper.rb b/lib/wpstools/wpstools_helper.rb index 373145f8..68823652 100644 --- a/lib/wpstools/wpstools_helper.rb +++ b/lib/wpstools/wpstools_helper.rb @@ -15,6 +15,12 @@ def usage() puts "- Generate a new full plugin list" puts "ruby " + script_name + " --generate_full_plugin_list" puts + puts "- Generate a new 'most popular' theme list, up to 150 pages ..." + puts "ruby " + script_name + " --generate_theme_list 150" + puts + puts "- Generate a new full theme list" + puts "ruby " + script_name + " --generate_full_theme_list" + puts puts "See README for further information." puts end @@ -29,5 +35,10 @@ def help() puts "--gpl Alias for --generate_plugin_list" puts "--generate_full_plugin_list Generate a new full data/plugins.txt file" puts "--gfpl Alias for --generate_full_plugin_list" + + puts "--generate_theme_list [number of pages] Generate a new data/themes.txt file. (supply number of *pages* to parse, default : 150)" + puts "--gtl Alias for --generate_theme_list" + puts "--generate_full_theme_list Generate a new full data/themes.txt file" + puts "--gftl Alias for --generate_full_theme_list" puts end diff --git a/wpscan.rb b/wpscan.rb index 728ecb37..672601cc 100755 --- a/wpscan.rb +++ b/wpscan.rb @@ -301,6 +301,7 @@ begin puts '[+] Finished at ' + Time.now.asctime exit() # must exit! rescue => e - puts "[ERROR] #{e}" - puts "Trace : #{e.backtrace}" + puts "[ERROR] #{e.message}" + puts "Trace :" + puts e.backtrace.join("\n") end diff --git a/wpstools.rb b/wpstools.rb index 2ac22c3b..f610691f 100755 --- a/wpstools.rb +++ b/wpstools.rb @@ -38,8 +38,12 @@ begin ["--verbose", "-v", GetoptLong::NO_ARGUMENT], ["--generate_plugin_list", GetoptLong::OPTIONAL_ARGUMENT], ["--generate_full_plugin_list", GetoptLong::NO_ARGUMENT], + ["--generate_theme_list", GetoptLong::OPTIONAL_ARGUMENT], + ["--generate_full_theme_list", GetoptLong::NO_ARGUMENT], ["--gpl", GetoptLong::OPTIONAL_ARGUMENT], ["--gfpl", GetoptLong::OPTIONAL_ARGUMENT], + ["--gtl", GetoptLong::OPTIONAL_ARGUMENT], + ["--gftl", GetoptLong::OPTIONAL_ARGUMENT], ["--update", "-u", GetoptLong::NO_ARGUMENT] ) @@ -59,23 +63,46 @@ begin end @generate_plugin_list = true + when "--generate_theme_list", "--gtl" + if argument == '' + puts "Number of pages not supplied, defaulting to 150 pages ..." + @number_of_pages = 150 + else + @number_of_pages = argument.to_i + end + + @generate_theme_list = true when "--update" @update = true when "--generate_full_plugin_list", "--gfpl" @generate_full_plugin_list = true + when "--generate_full_theme_list", "--gftl" + @generate_full_theme_list = true end end if @generate_plugin_list puts "[+] Generating new most popular plugin list" puts - Generate_Plugin_List.new(@number_of_pages, @verbose).save_file(false) + Generate_List.new('plugins', @verbose).generate_popular_list(@number_of_pages) end if @generate_full_plugin_list puts "[+] Generating new full plugin list" puts - Generate_Plugin_List.new(-1, @verbose).save_file(true) + Generate_List.new('plugins', @verbose).generate_full_list + end + + if @generate_theme_list + puts "[+] Generating new most popular theme list" + puts + Generate_List.new('themes', @verbose).generate_popular_list(@number_of_pages) + end + + if @generate_full_theme_list + puts "[+] Generating new full theme list" + puts + Generate_List.new('themes', @verbose).generate_full_list end if @update @@ -88,6 +115,7 @@ begin end rescue => e - puts "[ERROR] #{e}" - puts "Trace : #{e.backtrace}" + puts "[ERROR] #{e.message}" + puts "Trace :" + puts e.backtrace.join("\n") end