Merge pull request #376 from wpscanteam/parent_theme
Detect parent theme
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ log.txt
|
|||||||
.yardoc
|
.yardoc
|
||||||
debug.log
|
debug.log
|
||||||
wordlist.txt
|
wordlist.txt
|
||||||
|
rspec_results.html
|
||||||
|
|||||||
40
dev/pre-commit-hook.rb
Executable file
40
dev/pre-commit-hook.rb
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# ln -sf /Users/xxx/wpscan/dev/pre-commit-hook.rb /Users/xxx/wpscan/.git/hooks/pre-commit
|
||||||
|
|
||||||
|
require 'pty'
|
||||||
|
html_path = 'rspec_results.html'
|
||||||
|
|
||||||
|
begin
|
||||||
|
PTY.spawn( "rspec spec --format h > #{html_path}" ) do |stdin, stdout, pid|
|
||||||
|
begin
|
||||||
|
stdin.each { |line| print line }
|
||||||
|
rescue Errno::EIO => e
|
||||||
|
puts "Error: #{e.to.s}"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue PTY::ChildExited
|
||||||
|
puts 'Child process exit!'
|
||||||
|
end
|
||||||
|
|
||||||
|
# find out if there were any errors
|
||||||
|
html = open(html_path).read
|
||||||
|
examples = html.match(/(\d+) examples/)[0].to_i rescue 0
|
||||||
|
errors = html.match(/(\d+) errors/)[0].to_i rescue 0
|
||||||
|
if errors == 0 then
|
||||||
|
errors = html.match(/(\d+) failure/)[0].to_i rescue 0
|
||||||
|
end
|
||||||
|
pending = html.match(/(\d+) pending/)[0].to_i rescue 0
|
||||||
|
|
||||||
|
if errors.zero?
|
||||||
|
puts "0 failed! #{examples} run, #{pending} pending"
|
||||||
|
sleep 1
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
puts "\aCOMMIT FAILED!!"
|
||||||
|
puts "View your rspec results at #{File.expand_path(html_path)}"
|
||||||
|
puts
|
||||||
|
puts "#{errors} failed! #{examples} run, #{pending} pending"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
class Vulnerabilities < Array
|
class Vulnerabilities < Array
|
||||||
module Output
|
module Output
|
||||||
|
|
||||||
def output
|
def output(verbose = false)
|
||||||
self.each do |v|
|
self.each do |v|
|
||||||
v.output
|
v.output(verbose)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
class WpItems < Array
|
class WpItems < Array
|
||||||
module Output
|
module Output
|
||||||
|
|
||||||
def output
|
def output(verbose = false)
|
||||||
self.each { |item| item.output }
|
self.each { |item| item.output(verbose) }
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -177,3 +177,12 @@ end
|
|||||||
def count_file_lines(file)
|
def count_file_lines(file)
|
||||||
`wc -l #{file.shellescape}`.split[0].to_i
|
`wc -l #{file.shellescape}`.split[0].to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Truncates a string to a specific length and adds ... at the end
|
||||||
|
def truncate(input, size, trailing = '...')
|
||||||
|
size = size.to_i
|
||||||
|
trailing ||= ''
|
||||||
|
return input if input.nil? or size <= 0 or input.length <= size or
|
||||||
|
trailing.length >= input.length or size-trailing.length-1 >= input.length
|
||||||
|
return "#{input[0..size-trailing.length-1]}#{trailing}"
|
||||||
|
end
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class Vulnerability
|
|||||||
module Output
|
module Output
|
||||||
|
|
||||||
# output the vulnerability
|
# output the vulnerability
|
||||||
def output
|
def output(verbose = false)
|
||||||
puts ' |'
|
puts ' |'
|
||||||
puts ' | ' + red("* Title: #{title}")
|
puts ' | ' + red("* Title: #{title}")
|
||||||
references.each do |key, urls|
|
references.each do |key, urls|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class WpItem
|
|||||||
module Output
|
module Output
|
||||||
|
|
||||||
# @return [ Void ]
|
# @return [ Void ]
|
||||||
def output
|
def output(verbose = false)
|
||||||
puts
|
puts
|
||||||
puts " | Name: #{self}" #this will also output the version number if detected
|
puts " | Name: #{self}" #this will also output the version number if detected
|
||||||
puts " | Location: #{url}"
|
puts " | Location: #{url}"
|
||||||
@@ -13,6 +13,10 @@ class WpItem
|
|||||||
puts " | Readme: #{readme_url}" if has_readme?
|
puts " | Readme: #{readme_url}" if has_readme?
|
||||||
puts " | Changelog: #{changelog_url}" if has_changelog?
|
puts " | Changelog: #{changelog_url}" if has_changelog?
|
||||||
|
|
||||||
|
if respond_to?(:additional_output)
|
||||||
|
additional_output(verbose)
|
||||||
|
end
|
||||||
|
|
||||||
vulnerabilities.output
|
vulnerabilities.output
|
||||||
|
|
||||||
if has_error_log?
|
if has_error_log?
|
||||||
|
|||||||
@@ -3,16 +3,28 @@
|
|||||||
require 'wp_theme/findable'
|
require 'wp_theme/findable'
|
||||||
require 'wp_theme/versionable'
|
require 'wp_theme/versionable'
|
||||||
require 'wp_theme/vulnerable'
|
require 'wp_theme/vulnerable'
|
||||||
|
require 'wp_theme/info'
|
||||||
|
require 'wp_theme/output'
|
||||||
|
require 'wp_theme/childtheme'
|
||||||
|
|
||||||
class WpTheme < WpItem
|
class WpTheme < WpItem
|
||||||
extend WpTheme::Findable
|
extend WpTheme::Findable
|
||||||
include WpTheme::Versionable
|
include WpTheme::Versionable
|
||||||
include WpTheme::Vulnerable
|
include WpTheme::Vulnerable
|
||||||
|
include WpTheme::Info
|
||||||
|
include WpTheme::Output
|
||||||
|
include WpTheme::Childtheme
|
||||||
|
|
||||||
attr_writer :style_url
|
attr_writer :style_url
|
||||||
|
|
||||||
def allowed_options; super << :style_url end
|
def allowed_options; super << :style_url end
|
||||||
|
|
||||||
|
def initialize(*args)
|
||||||
|
super(*args)
|
||||||
|
|
||||||
|
parse_style
|
||||||
|
end
|
||||||
|
|
||||||
# Sets the @uri
|
# Sets the @uri
|
||||||
#
|
#
|
||||||
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
# @param [ URI ] target_base_uri The URI of the wordpress blog
|
||||||
|
|||||||
33
lib/common/models/wp_theme/childtheme.rb
Normal file
33
lib/common/models/wp_theme/childtheme.rb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# encoding: UTF-8
|
||||||
|
|
||||||
|
class WpTheme < WpItem
|
||||||
|
module Childtheme
|
||||||
|
|
||||||
|
def is_child_theme?
|
||||||
|
return true unless @theme_template.nil?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_parent_theme_style_url
|
||||||
|
if is_child_theme?
|
||||||
|
return style_url.sub("/#{name}/style.css", "/#@theme_template/style.css")
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_parent_theme
|
||||||
|
if is_child_theme?
|
||||||
|
base_url = @uri.clone
|
||||||
|
base_url.path = base_url.path.sub(/(?<url>.*\/)#{Regexp.escape(@wp_content_dir)}\/.+/, '\k<url>')
|
||||||
|
return WpTheme.new(base_url,
|
||||||
|
{
|
||||||
|
name: @theme_template,
|
||||||
|
style_url: get_parent_theme_style_url,
|
||||||
|
wp_content_dir: @wp_content_dir
|
||||||
|
})
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
34
lib/common/models/wp_theme/info.rb
Normal file
34
lib/common/models/wp_theme/info.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# encoding: UTF-8
|
||||||
|
|
||||||
|
class WpTheme < WpItem
|
||||||
|
module Info
|
||||||
|
|
||||||
|
attr_reader :theme_name, :theme_uri, :theme_description,
|
||||||
|
:theme_author, :theme_author_uri, :theme_template,
|
||||||
|
:theme_license, :theme_license_uri, :theme_tags,
|
||||||
|
:theme_text_domain
|
||||||
|
|
||||||
|
def parse_style
|
||||||
|
style = Browser.get(style_url).body
|
||||||
|
@theme_name = parse_style_tag(style, 'Theme Name')
|
||||||
|
@theme_uri = parse_style_tag(style, 'Theme URI')
|
||||||
|
@theme_description = parse_style_tag(style, 'Description')
|
||||||
|
@theme_author = parse_style_tag(style, 'Author')
|
||||||
|
@theme_author_uri = parse_style_tag(style, 'Author URI')
|
||||||
|
@theme_template = parse_style_tag(style, 'Template')
|
||||||
|
@theme_license = parse_style_tag(style, 'License')
|
||||||
|
@theme_license_uri = parse_style_tag(style, 'License URI')
|
||||||
|
@theme_tags = parse_style_tag(style, 'Tags')
|
||||||
|
@theme_text_domain = parse_style_tag(style, 'Text Domain')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse_style_tag(style, tag)
|
||||||
|
value = style[/^\s*#{Regexp.escape(tag)}:\s*(.*)/i, 1]
|
||||||
|
return value.strip if value
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
23
lib/common/models/wp_theme/output.rb
Normal file
23
lib/common/models/wp_theme/output.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# encoding: UTF-8
|
||||||
|
|
||||||
|
class WpTheme
|
||||||
|
module Output
|
||||||
|
|
||||||
|
# @return [ Void ]
|
||||||
|
def additional_output(verbose = false)
|
||||||
|
puts " | Style URL: #{style_url}"
|
||||||
|
puts " | Theme Name: #@theme_name" if @theme_name
|
||||||
|
puts " | Theme URI: #@theme_uri" if @theme_uri
|
||||||
|
theme_desc = verbose ? @theme_description : truncate(@theme_description, 100)
|
||||||
|
puts " | Description: #{theme_desc}"
|
||||||
|
puts " | Author: #@theme_author" if @theme_author
|
||||||
|
puts " | Author URI: #@theme_author_uri" if @theme_author_uri
|
||||||
|
puts " | Template: #@theme_template" if @theme_template and verbose
|
||||||
|
puts " | License: #@theme_license" if @theme_license and verbose
|
||||||
|
puts " | License URI: #@theme_license_uri" if @theme_license_uri and verbose
|
||||||
|
puts " | Tags: #@theme_tags" if @theme_tags and verbose
|
||||||
|
puts " | Text Domain: #@theme_text_domain" if @theme_text_domain and verbose
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
class WpTimthumb < WpItem
|
class WpTimthumb < WpItem
|
||||||
module Output
|
module Output
|
||||||
|
|
||||||
def output
|
def output(verbose = false)
|
||||||
puts ' | ' + red('[!]') + " #{self}"
|
puts ' | ' + red('[!]') + " #{self}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class WpVersion < WpItem
|
class WpVersion < WpItem
|
||||||
module Output
|
module Output
|
||||||
|
|
||||||
def output
|
def output(verbose = false)
|
||||||
puts green('[+]') + " WordPress version #{self.number} identified from #{self.found_from}"
|
puts green('[+]') + " WordPress version #{self.number} identified from #{self.found_from}"
|
||||||
|
|
||||||
vulnerabilities = self.vulnerabilities
|
vulnerabilities = self.vulnerabilities
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe WpThemes do
|
describe WpThemes do
|
||||||
|
before { stub_request(:get, /.+\/style.css$/).to_return(status: 200) }
|
||||||
|
|
||||||
it_behaves_like 'WpItems::Detectable' do
|
it_behaves_like 'WpItems::Detectable' do
|
||||||
subject(:wp_themes) { WpThemes }
|
subject(:wp_themes) { WpThemes }
|
||||||
let(:item_class) { WpTheme }
|
let(:item_class) { WpTheme }
|
||||||
|
|||||||
@@ -88,4 +88,83 @@ describe 'common_helper' do
|
|||||||
@expected = @html
|
@expected = @html
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#truncate' do
|
||||||
|
after :each do
|
||||||
|
output = truncate(@input, @length, @trailing)
|
||||||
|
output.should == @expected
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil on no input' do
|
||||||
|
@input = nil
|
||||||
|
@length = 1
|
||||||
|
@expected = nil
|
||||||
|
@trailing = '...'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns input when length > input' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = 13
|
||||||
|
@expected = @input
|
||||||
|
@trailing = '...'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'truncates the input' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = 6
|
||||||
|
@expected = '123...'
|
||||||
|
@trailing = '...'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds own trailing' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = 7
|
||||||
|
@expected = '123xxxx'
|
||||||
|
@trailing = 'xxxx'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'accepts strings as length' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = '6'
|
||||||
|
@expected = '123...'
|
||||||
|
@trailing = '...'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'checks if trailing is longer than input' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = 1
|
||||||
|
@expected = @input
|
||||||
|
@trailing = 'A' * 20
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns input on negative length' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = -1
|
||||||
|
@expected = @input
|
||||||
|
@trailing = '...'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns input on length == input.length' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = '10'
|
||||||
|
@expected = @input
|
||||||
|
@trailing = '...'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns cut string on nil trailing' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = 9
|
||||||
|
@expected = '123456789'
|
||||||
|
@trailing = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'trailing.length > length' do
|
||||||
|
@input = '1234567890'
|
||||||
|
@length = 1
|
||||||
|
@expected = @input
|
||||||
|
@trailing = 'A' * 20
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -7,6 +7,10 @@ describe 'WpTheme::Findable' do
|
|||||||
let(:uri) { URI.parse('http://example.com/') }
|
let(:uri) { URI.parse('http://example.com/') }
|
||||||
|
|
||||||
describe '::find_from_css_link' do
|
describe '::find_from_css_link' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.+\/style.css$/).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
@body ||= File.new(fixtures_dir + '/css_link/' + @file)
|
@body ||= File.new(fixtures_dir + '/css_link/' + @file)
|
||||||
stub_request(:get, uri.to_s).to_return(status: 200, body: @body)
|
stub_request(:get, uri.to_s).to_return(status: 200, body: @body)
|
||||||
@@ -51,6 +55,10 @@ describe 'WpTheme::Findable' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '::find_from_wooframework' do
|
describe '::find_from_wooframework' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.+\/style.css$/).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
@body ||= File.new(fixtures_dir + '/wooframework/' + @file)
|
@body ||= File.new(fixtures_dir + '/wooframework/' + @file)
|
||||||
stub_request(:get, uri.to_s).to_return(status: 200, body: @body)
|
stub_request(:get, uri.to_s).to_return(status: 200, body: @body)
|
||||||
@@ -119,6 +127,7 @@ describe 'WpTheme::Findable' do
|
|||||||
context 'when the theme is found' do
|
context 'when the theme is found' do
|
||||||
it 'returns it, with the :found_from set' do
|
it 'returns it, with the :found_from set' do
|
||||||
stub_all_to_nil()
|
stub_all_to_nil()
|
||||||
|
stub_request(:get, /.+\/the-oracle\/style.css$/).to_return(status: 200)
|
||||||
expected = WpTheme.new(uri, name: 'the-oracle')
|
expected = WpTheme.new(uri, name: 'the-oracle')
|
||||||
|
|
||||||
WpTheme.stub(:find_from_css_link).and_return(expected)
|
WpTheme.stub(:find_from_css_link).and_return(expected)
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe WpTheme do
|
describe WpTheme do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.+\/style.css$/).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
it_behaves_like 'WpTheme::Versionable'
|
it_behaves_like 'WpTheme::Versionable'
|
||||||
it_behaves_like 'WpTheme::Vulnerable'
|
it_behaves_like 'WpTheme::Vulnerable'
|
||||||
it_behaves_like 'WpItem::Vulnerable' do
|
it_behaves_like 'WpItem::Vulnerable' do
|
||||||
|
|||||||
22
wpscan.rb
22
wpscan.rb
@@ -186,14 +186,24 @@ def main
|
|||||||
}
|
}
|
||||||
|
|
||||||
if wp_version = wp_target.version(WP_VERSIONS_FILE)
|
if wp_version = wp_target.version(WP_VERSIONS_FILE)
|
||||||
wp_version.output
|
wp_version.output(wpscan_options.verbose)
|
||||||
end
|
end
|
||||||
|
|
||||||
if wp_theme = wp_target.theme
|
if wp_theme = wp_target.theme
|
||||||
puts
|
puts
|
||||||
# Theme version is handled in #to_s
|
# Theme version is handled in #to_s
|
||||||
puts green('[+]') + " WordPress theme in use: #{wp_theme}"
|
puts green('[+]') + " WordPress theme in use: #{wp_theme}"
|
||||||
wp_theme.output
|
wp_theme.output(wpscan_options.verbose)
|
||||||
|
|
||||||
|
# Check for parent Themes
|
||||||
|
while wp_theme.is_child_theme?
|
||||||
|
parent = wp_theme.get_parent_theme
|
||||||
|
puts
|
||||||
|
puts green('[+]') + " Detected parent theme: #{parent}"
|
||||||
|
parent.output(wpscan_options.verbose)
|
||||||
|
wp_theme = parent
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil
|
if wpscan_options.enumerate_plugins == nil and wpscan_options.enumerate_only_vulnerable_plugins == nil
|
||||||
@@ -204,7 +214,7 @@ def main
|
|||||||
if !wp_plugins.empty?
|
if !wp_plugins.empty?
|
||||||
puts " | #{wp_plugins.size} plugins found:"
|
puts " | #{wp_plugins.size} plugins found:"
|
||||||
|
|
||||||
wp_plugins.output
|
wp_plugins.output(wpscan_options.verbose)
|
||||||
else
|
else
|
||||||
puts 'No plugins found'
|
puts 'No plugins found'
|
||||||
end
|
end
|
||||||
@@ -226,7 +236,7 @@ def main
|
|||||||
if !wp_plugins.empty?
|
if !wp_plugins.empty?
|
||||||
puts green('[+]') + " We found #{wp_plugins.size} plugins:"
|
puts green('[+]') + " We found #{wp_plugins.size} plugins:"
|
||||||
|
|
||||||
wp_plugins.output
|
wp_plugins.output(wpscan_options.verbose)
|
||||||
else
|
else
|
||||||
puts 'No plugins found'
|
puts 'No plugins found'
|
||||||
end
|
end
|
||||||
@@ -248,7 +258,7 @@ def main
|
|||||||
if !wp_themes.empty?
|
if !wp_themes.empty?
|
||||||
puts green('[+]') + " We found #{wp_themes.size} themes:"
|
puts green('[+]') + " We found #{wp_themes.size} themes:"
|
||||||
|
|
||||||
wp_themes.output
|
wp_themes.output(wpscan_options.verbose)
|
||||||
else
|
else
|
||||||
puts 'No themes found'
|
puts 'No themes found'
|
||||||
end
|
end
|
||||||
@@ -270,7 +280,7 @@ def main
|
|||||||
puts green('[+]') + " We found #{wp_timthumbs.size} timthumb file/s:"
|
puts green('[+]') + " We found #{wp_timthumbs.size} timthumb file/s:"
|
||||||
puts
|
puts
|
||||||
|
|
||||||
wp_timthumbs.output
|
wp_timthumbs.output(wpscan_options.verbose)
|
||||||
|
|
||||||
puts
|
puts
|
||||||
puts red(' * Reference: http://www.exploit-db.com/exploits/17602/')
|
puts red(' * Reference: http://www.exploit-db.com/exploits/17602/')
|
||||||
|
|||||||
Reference in New Issue
Block a user