diff --git a/CREDITS b/CREDITS
index aa48164e..02088591 100644
--- a/CREDITS
+++ b/CREDITS
@@ -15,3 +15,4 @@ michee08 - Reported and gave potential solutions to bugs.
Callum Pember - Implemented proxy support - callumpember at gmail.com
g0tmi1k - Additional timthumb checks + bug reports.
Melvin Lammerts - Reported a couple of fake vulnerabilities - melvin at 12k.nl
+Christian Mehlmauer - @_FireFart_ - Theme enumeration
\ No newline at end of file
diff --git a/lib/wpscan/wp_item.rb b/lib/wpscan/wp_item.rb
index 8793da2c..588ef53d 100644
--- a/lib/wpscan/wp_item.rb
+++ b/lib/wpscan/wp_item.rb
@@ -17,23 +17,37 @@
#++
class WpItem < Vulnerable
- attr_accessor :path, :url, :wp_content_dir
+ attr_accessor :path, :url, :wp_content_dir, :name
@version = nil
+ def initialize(options = {})
+ @wp_content_dir = options[:wp_content_dir]
+ @url = options[:url]
+ @path = options[:path]
+ @name = options[:name] || extract_name_from_url
+ end
+
+ # Get the full url for this item
def get_url
- URI.parse("#{@url.to_s}#@wp_content_dir/#@path")
+ url = @url.to_s.end_with?("/") ? @url.to_s : "#@url/"
+ # remove first and last /
+ wp_content_dir = @wp_content_dir.sub(/^\//, "").sub(/\/$/, "")
+ # remove first /
+ path = @path.sub(/^\//, "")
+ URI.parse("#{url}#{wp_content_dir}/#{path}")
end
+ # Gets the full url for this item without filenames
def get_url_without_filename
- matches = @path.match(%r{^(.*/).*$})
- if matches == nil or matches.length < 2
- dirname = @path
- else
- dirname = matches[1]
+ location_url = get_url.to_s
+ valid_location_url = location_url[%r{^(https?://.*/)[^.]+\.[^/]+$}, 1]
+ unless valid_location_url
+ valid_location_url = add_trailing_slash(location_url)
end
- URI.parse("#{@url.to_s}#@wp_content_dir/#{dirname}")
+ URI.parse(valid_location_url)
end
+ # Returns version number from readme.txt if it exists
def version
unless @version
response = Browser.instance.get(get_url.merge("readme.txt").to_s)
@@ -45,42 +59,36 @@ class WpItem < Vulnerable
# 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
+ Browser.instance.get(get_url_without_filename).body[%r{Index of}] ? true : false
end
- def extract_name_from_url(url)
- url.to_s[%r{^(https?://.*/([^/]+)/)}i, 2]
+ # Extract item name from a url
+ def extract_name_from_url
+ get_url.to_s[%r{^(https?://.*/([^/]+)/)}i, 2]
end
+ # To string. Adds a version number if detected
def to_s
item_version = version
"#@name#{' v' + item_version.strip if item_version}"
end
+ # Object comparer
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
-
+ # Url for readme.txt
def readme_url
get_url_without_filename.merge("readme.txt")
end
+ # Url for changelog.txt
def changelog_url
get_url_without_filename.merge("changelog.txt")
end
+ # readme.txt present?
def has_readme?
unless @readme
status = Browser.instance.get(readme_url).code
@@ -89,6 +97,7 @@ class WpItem < Vulnerable
@readme
end
+ # changelog.txt present?
def has_changelog?
unless @changelog
status = Browser.instance.get(changelog_url).code
diff --git a/lib/wpscan/wp_plugin.rb b/lib/wpscan/wp_plugin.rb
index f344c082..e2764d6b 100644
--- a/lib/wpscan/wp_plugin.rb
+++ b/lib/wpscan/wp_plugin.rb
@@ -26,7 +26,6 @@ class WpPlugin < WpItem
@url = options[: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
@@ -36,6 +35,8 @@ class WpPlugin < WpItem
raise("wp_content_dir not set") unless @wp_content_dir
raise("name not set") unless @name
raise("vulns_xml not set") unless @vulns_xml
+
+ super(:wp_content_dir => @wp_content_dir, :url => @url, :path => @path)
end
# Discover any error_log files created by WordPress
diff --git a/lib/wpscan/wp_theme.rb b/lib/wpscan/wp_theme.rb
index 991809dc..94352ad9 100644
--- a/lib/wpscan/wp_theme.rb
+++ b/lib/wpscan/wp_theme.rb
@@ -24,7 +24,6 @@ class WpTheme < WpItem
def initialize(options = {})
@url = options[:url]
- @name = options[:name] || extract_name_from_url(get_url)
@path = options[:path]
@wp_content_dir = options[:wp_content_dir]
@vulns_xml = options[:vulns_xml] || DATA_DIR + '/wp_theme_vulns.xml'
@@ -38,6 +37,8 @@ class WpTheme < WpItem
raise("wp_content_dir not set") unless @wp_content_dir
raise("name not set") unless @name
raise("vulns_xml not set") unless @vulns_xml
+
+ super(:wp_content_dir => @wp_content_dir, :url => @url, :path => @path)
end
def version
@@ -49,7 +50,6 @@ class WpTheme < WpItem
@version
end
-
def self.find(target_uri)
self.methods.grep(/find_from_/).each do |method_to_call|
theme = self.send(method_to_call, target_uri)
diff --git a/spec/lib/wpscan/wp_item_spec.rb b/spec/lib/wpscan/wp_item_spec.rb
new file mode 100644
index 00000000..8b6d900e
--- /dev/null
+++ b/spec/lib/wpscan/wp_item_spec.rb
@@ -0,0 +1,228 @@
+#--
+# 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 .
+#++
+
+require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper')
+
+describe WpPlugin do
+ before :all do
+ @browser = Browser.instance(:config_file => SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf.json')
+ end
+
+ before :each do
+ @instance = WpItem.new(:wp_content_dir => "wp-content",
+ :url => "http://sub.example.com/path/to/wordpress/",
+ :path => "plugins/test/asdf.php")
+ end
+
+ describe "#initialize" do
+ it "should create a correct instance" do
+ @instance.wp_content_dir.should == "wp-content"
+ @instance.url.should == "http://sub.example.com/path/to/wordpress/"
+ @instance.path.should == "plugins/test/asdf.php"
+ end
+ end
+
+ describe "#get_url" do
+ it "should return the correct url" do
+ @instance.get_url.to_s.should == "http://sub.example.com/path/to/wordpress/wp-content/plugins/test/asdf.php"
+ end
+
+ it "should return the correct url (custom wp_content_dir)" do
+ @instance.wp_content_dir = "custom"
+ @instance.get_url.to_s.should == "http://sub.example.com/path/to/wordpress/custom/plugins/test/asdf.php"
+ end
+
+ it "should trim / and add missing / before concatenating url" do
+ @instance.wp_content_dir = "/custom/"
+ @instance.url = "http://sub.example.com/path/to/wordpress"
+ @instance.path = "plugins/test/asdf.php"
+ @instance.get_url.to_s.should == "http://sub.example.com/path/to/wordpress/custom/plugins/test/asdf.php"
+ end
+ end
+
+ describe "#get_url_without_filename" do
+ it "should return the correct url" do
+ @instance.get_url_without_filename.to_s.should == "http://sub.example.com/path/to/wordpress/wp-content/plugins/test/"
+ end
+
+ it "should return the correct url (custom wp_content_dir)" do
+ @instance.wp_content_dir = "custom"
+ @instance.get_url_without_filename.to_s.should == "http://sub.example.com/path/to/wordpress/custom/plugins/test/"
+ end
+
+ it "should trim / and add missing / before concatenating url" do
+ @instance.wp_content_dir = "/custom/"
+ @instance.url = "http://sub.example.com/path/to/wordpress"
+ @instance.path = "plugins/test/asdf.php"
+ @instance.get_url_without_filename.to_s.should == "http://sub.example.com/path/to/wordpress/custom/plugins/test/"
+ end
+
+ it "should not remove the last foldername" do
+ @instance.path = "plugins/test/"
+ @instance.get_url_without_filename.to_s.should == "http://sub.example.com/path/to/wordpress/wp-content/plugins/test/"
+ end
+ end
+
+ describe "#version" do
+ it "should return a version number" do
+ stub_request(:get, @instance.readme_url.to_s).to_return(:status => 200, :body => "Stable tag: 1.2.4.3.2.1")
+ @instance.version.should == "1.2.4.3.2.1"
+ end
+
+ it "should not return a version number" do
+ stub_request(:get, @instance.readme_url.to_s).to_return(:status => 200, :body => "Stable tag: trunk")
+ @instance.version.should be nil
+ end
+ end
+
+ describe "#directory_listing?" do
+ it "should return true" do
+ stub_request(:get, @instance.get_url_without_filename.to_s).to_return(:status => 200, :body => "Index of asdf")
+ @instance.directory_listing?.should == true
+ end
+
+ it "should return false" do
+ stub_request(:get, @instance.get_url_without_filename.to_s).to_return(:status => 200, :body => "My Wordpress Site")
+ @instance.directory_listing?.should == false
+ end
+ end
+
+ describe "#extract_name_from_url" do
+ it "should extract the correct name" do
+ @instance.extract_name_from_url.should == "test"
+ end
+
+ it "should extract the correct name (custom wp_content_dir)" do
+ @instance.wp_content_dir = "custom"
+ @instance.extract_name_from_url.should == "test"
+ end
+
+ it "should extract the correct name" do
+ @instance.wp_content_dir = "/custom/"
+ @instance.url = "http://sub.example.com/path/to/wordpress"
+ @instance.path = "plugins/test2/asdf.php"
+ @instance.extract_name_from_url.should == "test2"
+ end
+
+ it "should extract the correct plugin name" do
+ @instance.path = "plugins/testplugin/"
+ @instance.extract_name_from_url.should == "testplugin"
+ end
+
+ it "should extract the correct theme name" do
+ @instance.path = "themes/testtheme/"
+ @instance.extract_name_from_url.should == "testtheme"
+ end
+ end
+
+ describe "#to_s" do
+ it "should return the name including a version number" do
+ stub_request(:get, @instance.readme_url.to_s).to_return(:status => 200, :body => "Stable tag: 1.2.4.3.2.1")
+ @instance.to_s.should == "test v1.2.4.3.2.1"
+ end
+
+ it "should not return the name without a version number" do
+ stub_request(:get, @instance.readme_url.to_s).to_return(:status => 200, :body => "Stable tag: trunk")
+ @instance.to_s.should == "test"
+ end
+ end
+
+ describe "#==" do
+ it "should return false" do
+ instance2 = WpItem.new(:wp_content_dir => "wp-content",
+ :url => "http://sub.example.com/path/to/wordpress/",
+ :path => "plugins/newname/asdf.php")
+ (@instance==instance2).should == false
+ end
+
+ it "should return true" do
+ instance2 = WpItem.new(:wp_content_dir => "wp-content",
+ :url => "http://sub.example.com/path/to/wordpress/",
+ :path => "plugins/test/asdf.php")
+ (@instance==instance2).should == true
+ end
+ end
+
+ describe "#readme_url" do
+ it "should return the corrent plugin readme url" do
+ @instance.readme_url.to_s.should == "http://sub.example.com/path/to/wordpress/wp-content/plugins/test/readme.txt"
+ end
+
+ it "should return the corrent plugin readme url (custom wp_content)" do
+ @instance.wp_content_dir = "custom"
+ @instance.readme_url.to_s.should == "http://sub.example.com/path/to/wordpress/custom/plugins/test/readme.txt"
+ end
+
+ it "should return the corrent theme readme url" do
+ @instance.path = "themes/test/asdf.php"
+ @instance.readme_url.to_s.should == "http://sub.example.com/path/to/wordpress/wp-content/themes/test/readme.txt"
+ end
+
+ it "should return the corrent theme readme url (custom wp_content)" do
+ @instance.wp_content_dir = "custom"
+ @instance.path = "themes/test/asdf.php"
+ @instance.readme_url.to_s.should == "http://sub.example.com/path/to/wordpress/custom/themes/test/readme.txt"
+ end
+ end
+
+ describe "#changelog_url" do
+ it "should return the corrent plugin changelog url" do
+ @instance.changelog_url.to_s.should == "http://sub.example.com/path/to/wordpress/wp-content/plugins/test/changelog.txt"
+ end
+
+ it "should return the corrent plugin changelog url (custom wp_content)" do
+ @instance.wp_content_dir = "custom"
+ @instance.changelog_url.to_s.should == "http://sub.example.com/path/to/wordpress/custom/plugins/test/changelog.txt"
+ end
+
+ it "should return the corrent theme changelog url" do
+ @instance.path = "themes/test/asdf.php"
+ @instance.changelog_url.to_s.should == "http://sub.example.com/path/to/wordpress/wp-content/themes/test/changelog.txt"
+ end
+
+ it "should return the corrent theme changelog url (custom wp_content)" do
+ @instance.wp_content_dir = "custom"
+ @instance.path = "themes/test/asdf.php"
+ @instance.changelog_url.to_s.should == "http://sub.example.com/path/to/wordpress/custom/themes/test/changelog.txt"
+ end
+ end
+
+ describe "#has_readme?" do
+ it "should return true" do
+ stub_request(:get, @instance.readme_url.to_s).to_return(:status => 200)
+ @instance.has_readme?.should == true
+ end
+
+ it "should return false" do
+ stub_request(:get, @instance.readme_url.to_s).to_return(:status => 403)
+ @instance.has_readme?.should == false
+ end
+ end
+
+ describe "#has_changelog?" do
+ it "should return true" do
+ stub_request(:get, @instance.changelog_url.to_s).to_return(:status => 200)
+ @instance.has_changelog?.should == true
+ end
+
+ it "should return false" do
+ stub_request(:get, @instance.changelog_url.to_s).to_return(:status => 403)
+ @instance.has_changelog?.should == false
+ end
+ end
+end
\ No newline at end of file