wordstress 0.50.0 → 0.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changelog.md +15 -0
- data/README.md +40 -7
- data/Rakefile +0 -34
- data/bin/wordstress +59 -56
- data/lib/wordstress/site.rb +28 -135
- data/lib/wordstress/utils.rb +46 -20
- data/lib/wordstress/version.rb +1 -1
- metadata +4 -5
- data/lib/wordstress/models/plugins.rb +0 -60
- data/lib/wordstress/models/themes.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 671f7b006dbdafbbb6121ceef8e02955cfc7e911
|
4
|
+
data.tar.gz: b7cff46bb7c7450ddc56616df38b73a2b3010f72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccdd7c979267c9d39ff5de0b403dcf11f47f27e832f0732ee0b47b636ed423b3fea6a4117d9ce0d458474d21f9d6df9d28b4c585ea55f52b539c901742ef714d
|
7
|
+
data.tar.gz: 9f9421817bb5b75fac260bafd1fcdf21c6d7dc2d82f6747c2cac1fd52f16b99898da233ea141478d48afb277c44e537dc7b2b54809eba8a8dfdcdb1364278a86
|
data/Changelog.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Wordstress - changelog
|
2
|
+
|
3
|
+
Wordstress is a whitebox security scanner for wordpress powered blogs. It needs
|
4
|
+
[the wordstress](https://wordpress.org/plugins/wordstress/) plugin to be
|
5
|
+
installed on the target server and an access key (stored server side) in order
|
6
|
+
to works.
|
7
|
+
|
8
|
+
It uses [wpvulndb.com](http://wpvulndb.com) vulnerability database.
|
9
|
+
|
10
|
+
_latest update: Tue Mar 3 08:09:56 CET 2015_
|
11
|
+
|
12
|
+
## Version 0.60 (2015-xx-xx) - First release with a Changelog
|
13
|
+
|
14
|
+
* major cleanup for all greybox/blackbox scanning facilities. wordpress now
|
15
|
+
works only in whitebox mode
|
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# Wordstress
|
2
2
|
|
3
|
-
wordstress is
|
4
|
-
websites
|
5
|
-
|
3
|
+
wordstress is an opensource whitebox security scanner for wordpress powered
|
4
|
+
websites.
|
6
5
|
|
7
6
|
## Description
|
8
7
|
|
@@ -64,8 +63,8 @@ wordpress](https://wordpress.org/plugins/wordstress/) you may must:
|
|
64
63
|
generated, to increase entropy you may want to reload the page a couple of
|
65
64
|
times. When you're comfortable with the generated key, press the "Save Changes"
|
66
65
|
button.
|
67
|
-
The virtual page is now available at the url http://youblogurl/wordstress?
|
68
|
-
* from the command line, use wordstress security scanner this way: `
|
66
|
+
The virtual page is now available at the url http://youblogurl/wordstress?wordstress-key=the_key
|
67
|
+
* from the command line, use wordstress security scanner this way: `wordstress -u http://yourblogurl/wordstress -k the_key`
|
69
68
|
* enjoy results
|
70
69
|
|
71
70
|
## Why another tool?
|
@@ -93,6 +92,40 @@ themes and plugins and their version. Since an authenticated check is necessary
|
|
93
92
|
to match scan results with installed plugin (or theme) version, I tought it was
|
94
93
|
a better idea to start authenticated from the beginning.
|
95
94
|
|
95
|
+
## Usage
|
96
|
+
|
97
|
+
Using wordstress from command line is pretty easy. There are 2 mandatory
|
98
|
+
arguments, the key to use to query the wordpress plugin and the target url.
|
99
|
+
|
100
|
+
`$ wordstress -k d4a34e43b5d74c822830b5c4690eccbb621aa372 http://mywordpressblog.com`
|
101
|
+
|
102
|
+
By default, wordstress doesn't look for inactive themes or inactive plugins
|
103
|
+
vulnerabilities. This means that if `foobar_plugin` installed version is
|
104
|
+
vulnerable to privilege escalation, wordstress scanner by default won't raise
|
105
|
+
an alarm if the `foobar_plugin` **is not active**.
|
106
|
+
|
107
|
+
If you want to include vulnerabilities for all themes and vulnerabilities you
|
108
|
+
can use -T and -P flags.
|
109
|
+
|
110
|
+
`$ wordstress -k d4a34e43b5d74c822830b5c4690eccbb621aa372 -T -P http://mywordpressblog.com`
|
111
|
+
|
112
|
+
Examples:
|
113
|
+
$ wordstress -k d4a34e43b5d74c822830b5c4690eccbb621aa372 -B basic_user:basic_password http://mywordpressblog.com
|
114
|
+
|
115
|
+
-k, --key uses the key to access wordstress plugin content on target website
|
116
|
+
-B, --basic-auth user:pwd uses 'user' and 'pwd' as basic auth credentials to target website
|
117
|
+
|
118
|
+
Plugins and themes specific flags
|
119
|
+
|
120
|
+
-T, --fetch-all-themes-vulns retrieves vulnerabilities also for inactive themes
|
121
|
+
-P, --fetch-all-plugins-vulns retrieves vulnerabilities also for inactive plugins
|
122
|
+
|
123
|
+
Service flags
|
124
|
+
|
125
|
+
-D, --debug enters dawn debug mode
|
126
|
+
-v, --version shows version information
|
127
|
+
-h, --help shows this help
|
128
|
+
|
96
129
|
## Online resource
|
97
130
|
|
98
131
|
[Wordstress homepage](http://wordstress.org)
|
@@ -114,6 +147,6 @@ a better idea to start authenticated from the beginning.
|
|
114
147
|
|
115
148
|
1. Fork it ( https://github.com/[my-github-username]/wordstress/fork )
|
116
149
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
117
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
150
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
118
151
|
4. Push to the branch (`git push origin my-new-feature`)
|
119
|
-
5. Create a new Pull Request
|
152
|
+
5. Create a new Pull Request
|
data/Rakefile
CHANGED
@@ -1,35 +1 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
|
3
|
-
namespace :update do
|
4
|
-
desc 'Update themes'
|
5
|
-
task :themes, :name do |t,args|
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
desc 'Update plugins'
|
10
|
-
task :plugins, :name do |t, args|
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
namespace :import do
|
16
|
-
desc 'Import themes'
|
17
|
-
task :themes, :name do |t,args|
|
18
|
-
require 'wordstress/models/themes'
|
19
|
-
name = args.name
|
20
|
-
puts "reading themes from #{name}"
|
21
|
-
t = Wordstress::Models::Themes.new({:dbname=>"themes.db"})
|
22
|
-
t.import_from_file(name)
|
23
|
-
end
|
24
|
-
|
25
|
-
desc 'Import plugins'
|
26
|
-
task :plugins, :name do |t, args|
|
27
|
-
require 'wordstress/models/plugins'
|
28
|
-
name = args.name
|
29
|
-
puts "reading themes from #{name}"
|
30
|
-
p = Wordstress::Models::Plugins.new({:dbname=>"plugins.db"})
|
31
|
-
p.import_from_file(name)
|
32
|
-
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
data/bin/wordstress
CHANGED
@@ -7,38 +7,35 @@ require 'codesake-commons'
|
|
7
7
|
|
8
8
|
require 'wordstress'
|
9
9
|
|
10
|
-
# Scanning modes for plugins and themes
|
11
|
-
# + gentleman: wordstress will try to fetch plugins and themes only using
|
12
|
-
# info in the HTML page (this is very polite but also very inaccurate).
|
13
|
-
# + whitebox: wordstress will use a target installed plugin to fetch
|
14
|
-
# installed plugins and themes with their version
|
15
|
-
# + aggressive: wordstress will use enumeration to find installed plugins and
|
16
|
-
# themes. This will lead to false positives.
|
17
|
-
MODES = [:gentleman,:whitebox,:aggressive]
|
18
10
|
APPNAME = File.basename($0)
|
11
|
+
trap("INT") { $logger.die('[INTERRUPTED]') }
|
19
12
|
|
20
13
|
$logger = Codesake::Commons::Logging.instance
|
21
|
-
$logger.debug = false
|
22
|
-
|
23
|
-
@output_root = File.join(Dir.home, '/wordstress')
|
14
|
+
# $logger.debug = false
|
15
|
+
@output_root = File.join(Dir.home, 'wordstress')
|
24
16
|
|
25
|
-
|
26
|
-
|
27
|
-
basic_auth = {:user=>"", :pwd=>""}
|
17
|
+
whitebox = {:url=>"", :key=>""}
|
18
|
+
basic_auth = {:user=>"", :pwd=>""}
|
28
19
|
|
29
20
|
opts = GetoptLong.new(
|
30
|
-
|
31
|
-
[ '--
|
32
|
-
|
33
|
-
[ '--
|
34
|
-
|
35
|
-
[ '--
|
36
|
-
[ '--
|
37
|
-
[ '--
|
38
|
-
[ '--
|
21
|
+
#deprecated
|
22
|
+
[ '--gentleman', '-G', GetoptLong::NO_ARGUMENT],
|
23
|
+
#deprecated
|
24
|
+
[ '--whitebox' , '-W', GetoptLong::NO_ARGUMENT],
|
25
|
+
#deprecated
|
26
|
+
[ '--wordstress-url', '-u', GetoptLong::REQUIRED_ARGUMENT],
|
27
|
+
[ '--fetch-all-themes-vulns', '-T', GetoptLong::NO_ARGUMENT],
|
28
|
+
[ '--fetch-all-plugins-vulns','-P', GetoptLong::NO_ARGUMENT],
|
29
|
+
[ '--basic-auth', '-B', GetoptLong::REQUIRED_ARGUMENT],
|
30
|
+
[ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
|
31
|
+
[ '--csv', '-C', GetoptLong::NO_ARGUMENT],
|
32
|
+
[ '--debug', '-D', GetoptLong::NO_ARGUMENT],
|
33
|
+
[ '--version', '-v', GetoptLong::NO_ARGUMENT],
|
34
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT]
|
39
35
|
)
|
40
36
|
|
41
37
|
opts.quiet=true
|
38
|
+
deprecation_warning = false
|
42
39
|
|
43
40
|
begin
|
44
41
|
opts.each do |opt, val|
|
@@ -47,10 +44,11 @@ begin
|
|
47
44
|
basic_auth[:user] = val.split(':')[0]
|
48
45
|
basic_auth[:pwd] = val.split(':')[1]
|
49
46
|
when '--whitebox'
|
50
|
-
|
47
|
+
deprecation_warning = true
|
51
48
|
when '--wordstress-url'
|
49
|
+
deprecation_warning = true
|
52
50
|
whitebox[:url] = val
|
53
|
-
when '--
|
51
|
+
when '--key'
|
54
52
|
whitebox[:key] = val
|
55
53
|
when '--version'
|
56
54
|
puts "#{Wordstress::VERSION}"
|
@@ -58,6 +56,7 @@ begin
|
|
58
56
|
when '--debug'
|
59
57
|
$logger.debug = true
|
60
58
|
when '--help'
|
59
|
+
Wordstress::Utils.help
|
61
60
|
Kernel.exit(0)
|
62
61
|
end
|
63
62
|
end
|
@@ -67,66 +66,70 @@ rescue GetoptLong::InvalidOption => e
|
|
67
66
|
Kernel.exit(-1)
|
68
67
|
end
|
69
68
|
|
70
|
-
target=ARGV.shift unless scanning_mode == :whitebox
|
71
|
-
target=whitebox[:url] if scanning_mode == :whitebox
|
72
|
-
|
73
|
-
|
74
69
|
$logger.helo APPNAME, Wordstress::VERSION
|
70
|
+
$logger.warn "-G, -W, -u flags are deprecated and they will be soon removed. whitebox scanning will be the only supported option" if deprecation_warning
|
71
|
+
|
72
|
+
if whitebox[:url].empty?
|
73
|
+
whitebox[:url]="#{ARGV.shift}/wordstress"
|
74
|
+
$logger.warn "assuming wordstress virtual page to be found at #{whitebox[:url]}"
|
75
|
+
end
|
75
76
|
|
76
77
|
unless Dir.exists?(@output_root)
|
77
78
|
$logger.log "creating output dir #{@output_root}"
|
78
79
|
Dir.mkdir @output_root
|
79
80
|
end
|
80
81
|
|
81
|
-
@output_dir = Wordstress::Utils.build_output_dir(@output_root,
|
82
|
+
@output_dir = Wordstress::Utils.build_output_dir(@output_root, whitebox[:url])
|
82
83
|
$logger.log "storing results to #{@output_dir}"
|
83
84
|
FileUtils::mkdir_p(@output_dir)
|
84
85
|
|
85
|
-
|
86
|
-
$logger.die("missing target") if target.nil?
|
86
|
+
$logger.die("missing target") if whitebox[:url].nil?
|
87
87
|
|
88
|
-
$logger.log "scanning #{
|
89
|
-
site = Wordstress::Site.new({:
|
88
|
+
$logger.log "scanning #{whitebox[:url]}"
|
89
|
+
site = Wordstress::Site.new({:whitebox=>whitebox,:basic_auth=>basic_auth, :output_dir=>@output_dir})
|
90
90
|
|
91
|
-
|
92
|
-
$logger.err "can't detect wordpress version running on #{target}. Giving up!"
|
93
|
-
Kernel.exit(-2)
|
94
|
-
end
|
95
|
-
|
96
|
-
$logger.ok "#{target} is a wordpress version #{site.version[:version]} with #{site.themes.count} themes and #{site.plugins.count} plugins"
|
97
|
-
$logger.warn "scan mode is set to 'gentleman'. We are using only information found on resulting HTML. This can be lead to undetected plugins or themes" if site.scanning_mode == :gentleman
|
91
|
+
$logger.ok "#{Wordstress::Utils.url_to_target(whitebox[:url])} is a wordpress version #{site.version[:version]} with #{site.themes.count} themes and #{site.plugins.count} plugins"
|
98
92
|
|
99
93
|
if site.online?
|
100
94
|
site.wp_vuln["wordpress"]["vulnerabilities"].each do |v|
|
101
95
|
$logger.err "#{v["title"]}. Detected: #{site.version[:version]}. Safe: #{v["fixed_in"]}" if Gem::Version.new(site.version[:version]) <= Gem::Version.new(v["fixed_in"])
|
102
96
|
end
|
103
97
|
site.themes.each do |t|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
98
|
+
|
99
|
+
if t[:status] == "inactive"
|
100
|
+
$logger.debug "Theme #{t[:name]} is inactive no looking for vulns. Use '--fetch-all-themes-vulns' flag to fetch inactive theme vulnerabilities"
|
101
|
+
else
|
102
|
+
v = site.get_theme_vulnerabilities(t[:name])
|
103
|
+
unless v["theme"].nil?
|
104
|
+
v["theme"]["vulnerabilities"].each do |vv|
|
105
|
+
if Gem::Version.new(t[:version]) <= Gem::Version.new(vv["fixed_in"])
|
106
|
+
$logger.err "Theme #{vv["title"]} is vulnerable. Detected: #{t[:version]}. Safe: #{vv["fixed_in"]}"
|
107
|
+
site.theme_vulns << {:title=>vv["title"], :cve=>vv["cve"], :url=>vv["url"], :detected=>t[:version], :fixed_in=>vv["fixed_in"]}
|
108
|
+
end
|
110
109
|
end
|
111
110
|
end
|
112
|
-
end
|
111
|
+
end # if t[:status] == "inactive"
|
113
112
|
end
|
114
113
|
|
115
114
|
site.plugins.each do |t|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
115
|
+
if t[:status] == "inactive"
|
116
|
+
$logger.debug "Plugin #{t[:name]} is inactive no looking for vulns. Use '--fetch-all-plugins-vulns' flag to fetch inactive plugin vulnerabilities"
|
117
|
+
else
|
118
|
+
v = site.get_plugin_vulnerabilities(t[:name])
|
119
|
+
unless v["plugin"].nil?
|
120
|
+
v["plugin"]["vulnerabilities"].each do |vv|
|
121
|
+
if Gem::Version.new(t[:version]) <= Gem::Version.new(vv["fixed_in"])
|
122
|
+
$logger.err "Plugin #{vv["title"]} is vulnerable. Detected: #{t[:version]}. Safe: #{vv["fixed_in"]}"
|
123
|
+
site.plugin_vulns << {:title=>vv["title"], :cve=>vv["cve"], :url=>vv["url"], :detected=>t[:version], :fixed_in=>vv["fixed_in"]}
|
124
|
+
end
|
122
125
|
end
|
123
126
|
end
|
124
|
-
end
|
127
|
+
end # if t[:status] == "inactive"
|
125
128
|
end
|
126
129
|
else
|
127
130
|
site.online = false
|
128
131
|
$logger.err "it seems we are offline. wordstress can't reach https://wpvulndb.com"
|
129
|
-
$logger.err "
|
132
|
+
$logger.err "wordstress can't enumerate vulnerabilities"
|
130
133
|
end
|
131
134
|
|
132
135
|
site.stop_scan
|
data/lib/wordstress/site.rb
CHANGED
@@ -4,19 +4,17 @@ require 'terminal-table'
|
|
4
4
|
module Wordstress
|
5
5
|
class Site
|
6
6
|
|
7
|
-
attr_reader :version, :
|
7
|
+
attr_reader :version, :wp_vuln, :plugins, :themes
|
8
8
|
attr_accessor :theme_vulns, :plugin_vulns, :online
|
9
9
|
|
10
|
-
def initialize(options={:
|
11
|
-
@target = options[:target]
|
10
|
+
def initialize(options={:whitebox=>{:url=>"http://localhost/wordstress", :key=>""}, :basic_auth=>{:user=>"", :pwd=>""}, :output_dir=>"./"})
|
12
11
|
begin
|
13
|
-
@uri = URI(options[:
|
14
|
-
@raw_name = options[:target]
|
12
|
+
@uri = URI(options[:whitebox][:url])
|
15
13
|
@valid = true
|
16
14
|
rescue
|
17
15
|
@valid = false
|
18
16
|
end
|
19
|
-
@
|
17
|
+
@target = Wordstress::Utils.url_to_target(options[:whitebox][:url])
|
20
18
|
|
21
19
|
@basic_auth_user = options[:basic_auth][:user]
|
22
20
|
@basic_auth_pwd = options[:basic_auth][:pwd]
|
@@ -25,16 +23,8 @@ module Wordstress
|
|
25
23
|
@start_time = Time.now
|
26
24
|
@end_time = Time.now # I hate init variables to nil...
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
@robots_txt = get(@raw_name + "/robots.txt")
|
31
|
-
@readme_html = get(@raw_name + "/readme.html")
|
32
|
-
@homepage = get(@raw_name)
|
33
|
-
@version = detect_version(@homepage, false)
|
34
|
-
else
|
35
|
-
@wordstress_page = get("#{options[:whitebox][:url]}?wordstress-key=#{options[:whitebox][:key]}") if options[:scanning_mode] == :whitebox
|
36
|
-
@version = detect_version(@wordstress_page, true)
|
37
|
-
end
|
26
|
+
@wordstress_page = get("#{options[:whitebox][:url]}?wordstress-key=#{options[:whitebox][:key]}")
|
27
|
+
@version = detect_version(@wordstress_page)
|
38
28
|
|
39
29
|
@online = true
|
40
30
|
|
@@ -58,7 +48,7 @@ module Wordstress
|
|
58
48
|
return JSON.parse(json)
|
59
49
|
rescue => e
|
60
50
|
$logger.err e.message
|
61
|
-
@online = false
|
51
|
+
@online = false unless e.message.include?"403"
|
62
52
|
return JSON.parse("{}")
|
63
53
|
end
|
64
54
|
end
|
@@ -70,7 +60,7 @@ module Wordstress
|
|
70
60
|
return JSON.parse(json)
|
71
61
|
rescue => e
|
72
62
|
$logger.err e.message
|
73
|
-
@online = false
|
63
|
+
@online = false unless e.message.include?"403"
|
74
64
|
return JSON.parse("{}")
|
75
65
|
end
|
76
66
|
end
|
@@ -82,7 +72,7 @@ module Wordstress
|
|
82
72
|
return JSON.parse("{\"wordpress\":{\"vulnerabilities\":[]}}") if page.class == Net::HTTPNotFound
|
83
73
|
rescue => e
|
84
74
|
$logger.err e.message
|
85
|
-
@online = false
|
75
|
+
@online = false unless e.message.include?"403"
|
86
76
|
return JSON.parse("{}")
|
87
77
|
end
|
88
78
|
end
|
@@ -92,12 +82,7 @@ module Wordstress
|
|
92
82
|
# return version.gsub('.', '')+'0' if version.split('.').count == 2
|
93
83
|
end
|
94
84
|
|
95
|
-
def detect_version(page
|
96
|
-
detect_version_blackbox(page) unless whitebox
|
97
|
-
detect_version_whitebox(page) if whitebox
|
98
|
-
end
|
99
|
-
|
100
|
-
def detect_version_whitebox(page)
|
85
|
+
def detect_version(page)
|
101
86
|
v_meta = '0.0.0'
|
102
87
|
doc = Nokogiri::HTML(page.body)
|
103
88
|
doc.css('#wp_version').each do |link|
|
@@ -107,50 +92,6 @@ module Wordstress
|
|
107
92
|
return {:version => v_meta, :accuracy => 1.0}
|
108
93
|
end
|
109
94
|
|
110
|
-
def detect_version_blackbox(page)
|
111
|
-
|
112
|
-
#
|
113
|
-
# 1. trying to detect wordpress version from homepage body meta generator
|
114
|
-
# tag
|
115
|
-
|
116
|
-
v_meta = ""
|
117
|
-
doc = Nokogiri::HTML(page.body)
|
118
|
-
doc.xpath("//meta[@name='generator']/@content").each do |attr|
|
119
|
-
v_meta = attr.value.split(' ')[1]
|
120
|
-
end
|
121
|
-
|
122
|
-
#
|
123
|
-
# 2. trying to detect wordpress version from readme.html in the root
|
124
|
-
# directory
|
125
|
-
#
|
126
|
-
# Not available if scanning
|
127
|
-
|
128
|
-
unless whitebox
|
129
|
-
v_readme = ""
|
130
|
-
doc = Nokogiri::HTML(@readme_html.body)
|
131
|
-
v_readme = doc.at_css('h1').children.last.text.chop.lstrip.split(' ')[1]
|
132
|
-
end
|
133
|
-
|
134
|
-
#
|
135
|
-
# 3. Detect from RSS link
|
136
|
-
#
|
137
|
-
v_rss = ""
|
138
|
-
rss_doc = Nokogiri::HTML(page.body)
|
139
|
-
begin
|
140
|
-
rss = Nokogiri::HTML(get(rss_doc.css('link[type="application/rss+xml"]').first.attr('href')).body) unless l.nil?
|
141
|
-
v_rss= rss.css('generator').text.split('=')[1]
|
142
|
-
rescue => e
|
143
|
-
v_rss = "0.0.0"
|
144
|
-
end
|
145
|
-
|
146
|
-
|
147
|
-
return {:version => v_meta, :accuracy => 1.0} if v_meta == v_readme && v_meta == v_rss
|
148
|
-
return {:version => v_meta, :accuracy => 0.8} if v_meta == v_readme || v_meta == v_rss
|
149
|
-
|
150
|
-
# we failed detecting wordpress version
|
151
|
-
return {:version => "0.0.0", :accuracy => 0}
|
152
|
-
end
|
153
|
-
|
154
95
|
def get(page)
|
155
96
|
return get_http(page) if @uri.scheme == "http"
|
156
97
|
return get_https(page) if @uri.scheme == "https"
|
@@ -163,22 +104,26 @@ module Wordstress
|
|
163
104
|
return @online
|
164
105
|
end
|
165
106
|
|
166
|
-
def find_themes
|
167
|
-
return find_themes_gentleman if @scanning_mode == :gentleman
|
168
|
-
return find_themes_whitebox if @scanning_mode == :whitebox
|
169
|
-
return []
|
170
|
-
end
|
171
107
|
def find_plugins
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
108
|
+
ret = []
|
109
|
+
doc = Nokogiri::HTML(@wordstress_page.body)
|
110
|
+
doc.css('#all_plugin').each do |link|
|
111
|
+
l=link.text.split(',')
|
112
|
+
ret << {:name=>l[2].split('/')[0], :version=>l[1], :status=>l[3]} unless is_already_detected?(ret, l[2])
|
113
|
+
end
|
114
|
+
ret
|
115
|
+
end
|
116
|
+
def find_themes
|
117
|
+
ret = []
|
118
|
+
doc = Nokogiri::HTML(@wordstress_page.body)
|
119
|
+
doc.css('#all_theme').each do |link|
|
120
|
+
l=link.text.split(',')
|
121
|
+
ret << {:name=>l[2], :version=>l[1], :status=>l[3]} unless is_already_detected?(ret, l[2])
|
122
|
+
end
|
123
|
+
ret
|
180
124
|
end
|
181
125
|
|
126
|
+
|
182
127
|
def ascii_report
|
183
128
|
# 0_Executive summary
|
184
129
|
rows = []
|
@@ -271,36 +216,6 @@ module Wordstress
|
|
271
216
|
return (!a.nil?)
|
272
217
|
end
|
273
218
|
|
274
|
-
def find_plugins_whitebox
|
275
|
-
ret = []
|
276
|
-
doc = Nokogiri::HTML(@wordstress_page.body)
|
277
|
-
doc.css('#all_plugin').each do |link|
|
278
|
-
l=link.text.split(',')
|
279
|
-
ret << {:name=>l[2].split('/')[0], :version=>l[1], :status=>l[3]} unless is_already_detected?(ret, l[2])
|
280
|
-
end
|
281
|
-
ret
|
282
|
-
end
|
283
|
-
def find_themes_whitebox
|
284
|
-
ret = []
|
285
|
-
doc = Nokogiri::HTML(@wordstress_page.body)
|
286
|
-
doc.css('#all_theme').each do |link|
|
287
|
-
l=link.text.split(',')
|
288
|
-
ret << {:name=>l[2], :version=>l[1], :status=>l[3]} unless is_already_detected?(ret, l[2])
|
289
|
-
end
|
290
|
-
ret
|
291
|
-
end
|
292
|
-
|
293
|
-
def find_themes_gentleman
|
294
|
-
ret = []
|
295
|
-
doc = Nokogiri::HTML(@homepage.body)
|
296
|
-
doc.css('link').each do |link|
|
297
|
-
if link.attr('href').include?("wp-content/themes")
|
298
|
-
theme = theme_name(link.attr('href'))
|
299
|
-
ret << {:name=>theme, :version=>""} unless is_already_detected?(ret, theme)
|
300
|
-
end
|
301
|
-
end
|
302
|
-
ret
|
303
|
-
end
|
304
219
|
|
305
220
|
def theme_name(url)
|
306
221
|
url.match(/\/wp-content\/themes\/(\w)+/)[0].split('/').last
|
@@ -309,28 +224,6 @@ module Wordstress
|
|
309
224
|
url.match(/\/wp-content\/plugins\/(\w)+/)[0].split('/').last
|
310
225
|
end
|
311
226
|
|
312
|
-
def find_plugins_gentleman
|
313
|
-
ret = []
|
314
|
-
doc = Nokogiri::HTML(@homepage.body)
|
315
|
-
doc.css('script').each do |link|
|
316
|
-
if ! link.attr('src').nil?
|
317
|
-
if link.attr('src').include?("wp-content/plugins")
|
318
|
-
plugin = plugin_name(link.attr('src'))
|
319
|
-
ret << {:name=>plugin, :version=>"", :status=>"active"} unless is_already_detected?(ret, plugin)
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
323
|
-
doc.css('link').each do |link|
|
324
|
-
if link.attr('href').include?("wp-content/plugins")
|
325
|
-
plugin = plugin_name(link.attr('href'))
|
326
|
-
ret << plugin if ret.index(plugin).nil?
|
327
|
-
end
|
328
|
-
|
329
|
-
end
|
330
|
-
|
331
|
-
ret
|
332
|
-
end
|
333
|
-
|
334
227
|
def get_http(page, use_ssl=false)
|
335
228
|
uri = URI(page)
|
336
229
|
req = Net::HTTP::Get.new(uri)
|
@@ -349,7 +242,7 @@ module Wordstress
|
|
349
242
|
when Net::HTTPRedirection then
|
350
243
|
location = res['location']
|
351
244
|
$logger.debug "redirected to #{location}"
|
352
|
-
get_http(location)
|
245
|
+
get_http(location, use_ssl)
|
353
246
|
when Net::HTTPNotFound
|
354
247
|
return res
|
355
248
|
else
|
data/lib/wordstress/utils.rb
CHANGED
@@ -1,32 +1,58 @@
|
|
1
1
|
module Wordstress
|
2
2
|
class Utils
|
3
|
+
def self.help
|
4
|
+
|
5
|
+
puts "wordstress v#{Wordstress::VERSION} (http://wordstress.org)"
|
6
|
+
|
7
|
+
puts "Usage: wordstress [options] url"
|
8
|
+
printf "\nExamples:\n"
|
9
|
+
puts "\t$ wordstress -k d4a34e43b5d74c822830b5c4690eccbb621aa372 http://mywordpressblog.com"
|
10
|
+
puts "\t$ wordstress -k d4a34e43b5d74c822830b5c4690eccbb621aa372 -B basic_user:basic_password http://mywordpressblog.com"
|
11
|
+
puts "\t$ wordstress -k d4a34e43b5d74c822830b5c4690eccbb621aa372 -T -P http://mywordpressblog.com"
|
12
|
+
printf "\n -k, --key\t\t\t\tuses the key to access wordstress plugin content on target website"
|
13
|
+
printf "\n -B, --basic-auth user:pwd\t\tuses 'user' and 'pwd' as basic auth credentials to target website"
|
14
|
+
printf "\n\nPlugins and themes specific flags\n"
|
15
|
+
printf "\n -T, --fetch-all-themes-vulns\t\tretrieves vulnerabilities also for inactive themes"
|
16
|
+
printf "\n -P, --fetch-all-plugins-vulns\tretrieves vulnerabilities also for inactive plugins"
|
17
|
+
printf "\n\nService flags\n"
|
18
|
+
printf "\n -D, --debug\t\t\t\tenters dawn debug mode"
|
19
|
+
printf "\n -v, --version\t\t\tshows version information"
|
20
|
+
printf "\n -h, --help\t\t\t\tshows this help\n"
|
21
|
+
|
22
|
+
true
|
23
|
+
end
|
3
24
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
25
|
+
def self.url_to_target(url)
|
26
|
+
uri = URI.parse(url)
|
27
|
+
"#{uri.scheme}://#{uri.host}#{uri.request_uri.gsub("/wordstress", "")}" if uri.port == 80
|
28
|
+
"#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.request_uri.gsub("/wordstress", "")}" unless uri.port == 80
|
29
|
+
end
|
30
|
+
# Transform a given URL into a directory name to be used to store data
|
31
|
+
def self.target_to_dirname(target)
|
32
|
+
uri = URI.parse(target)
|
33
|
+
path = uri.request_uri.split('/')
|
34
|
+
blog_path = ""
|
35
|
+
blog_path = "_#{path[1]}" if path.count >= 2
|
36
|
+
return "#{uri.host}_#{uri.port}#{blog_path}"
|
37
|
+
end
|
16
38
|
|
17
|
-
|
39
|
+
def self.build_output_dir(root, target)
|
40
|
+
attempt=0
|
41
|
+
today=Time.now.strftime("%Y%m%d")
|
18
42
|
|
19
|
-
|
20
|
-
if attempt != 0
|
21
|
-
proposed += "_#{attempt}"
|
22
|
-
end
|
43
|
+
while 1 do
|
23
44
|
|
24
|
-
|
25
|
-
|
45
|
+
proposed = File.join(root, Wordstress::Utils.target_to_dirname(target), today)
|
46
|
+
if attempt != 0
|
47
|
+
proposed += "_#{attempt}"
|
26
48
|
end
|
27
49
|
|
28
|
-
|
50
|
+
return proposed unless Dir.exists?(proposed)
|
51
|
+
attempt +=1 if Dir.exists?(proposed)
|
29
52
|
end
|
30
53
|
|
54
|
+
|
31
55
|
end
|
56
|
+
|
57
|
+
end
|
32
58
|
end
|
data/lib/wordstress/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wordstress
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.70.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paolo Perego
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -105,14 +105,13 @@ extensions: []
|
|
105
105
|
extra_rdoc_files: []
|
106
106
|
files:
|
107
107
|
- ".gitignore"
|
108
|
+
- Changelog.md
|
108
109
|
- Gemfile
|
109
110
|
- LICENSE.txt
|
110
111
|
- README.md
|
111
112
|
- Rakefile
|
112
113
|
- bin/wordstress
|
113
114
|
- lib/wordstress.rb
|
114
|
-
- lib/wordstress/models/plugins.rb
|
115
|
-
- lib/wordstress/models/themes.rb
|
116
115
|
- lib/wordstress/site.rb
|
117
116
|
- lib/wordstress/utils.rb
|
118
117
|
- lib/wordstress/version.rb
|
@@ -137,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
136
|
version: '0'
|
138
137
|
requirements: []
|
139
138
|
rubyforge_project:
|
140
|
-
rubygems_version: 2.
|
139
|
+
rubygems_version: 2.4.8
|
141
140
|
signing_key:
|
142
141
|
specification_version: 4
|
143
142
|
summary: wordstress is a security scanner for wordpress powered websites
|
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'data_mapper'
|
2
|
-
require 'dm-sqlite-adapter'
|
3
|
-
|
4
|
-
module Wordstress
|
5
|
-
module Models
|
6
|
-
|
7
|
-
class PluginInfo
|
8
|
-
include DataMapper::Resource
|
9
|
-
|
10
|
-
property :id, Serial
|
11
|
-
property :revision, Integer
|
12
|
-
property :created_at, DateTime, :default=>DateTime.now
|
13
|
-
property :updated_at, DateTime, :default=>DateTime.now
|
14
|
-
end
|
15
|
-
|
16
|
-
class Plugin
|
17
|
-
include DataMapper::Resource
|
18
|
-
|
19
|
-
property :id, Serial
|
20
|
-
property :name, String
|
21
|
-
property :link, String
|
22
|
-
property :created_at, DateTime, :default=>DateTime.now
|
23
|
-
property :updated_at, DateTime, :default=>DateTime.now
|
24
|
-
end
|
25
|
-
|
26
|
-
class Plugins
|
27
|
-
|
28
|
-
def initialize(options={:dbname=>"plugins.db"})
|
29
|
-
DataMapper.setup(:default, "sqlite3://#{File.join(Dir.pwd, options[:dbname])}")
|
30
|
-
DataMapper.finalize
|
31
|
-
DataMapper.auto_migrate!
|
32
|
-
end
|
33
|
-
|
34
|
-
def import_from_file(filename)
|
35
|
-
doc = Nokogiri::HTML(File.read(filename))
|
36
|
-
title = doc.at_css('title').children.text
|
37
|
-
|
38
|
-
return nil unless title.include?"Revision"
|
39
|
-
revision = title.split("Revision ")[1].split(':')[0].to_i
|
40
|
-
links = doc.xpath('//li//a')
|
41
|
-
|
42
|
-
puts "Plugin SVN revision is: #{revision}"
|
43
|
-
puts "#{links.count} plugins found"
|
44
|
-
|
45
|
-
i = PluginInfo.new
|
46
|
-
i.revision = revision
|
47
|
-
i.save
|
48
|
-
|
49
|
-
links.each do |link|
|
50
|
-
p = Plugin.new
|
51
|
-
p.name = link.text.chop
|
52
|
-
p.link = 'https://plugins.svn.wordpress.org/'+link.attr('href')
|
53
|
-
p.save
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'data_mapper'
|
2
|
-
require 'dm-sqlite-adapter'
|
3
|
-
|
4
|
-
module Wordstress
|
5
|
-
module Models
|
6
|
-
|
7
|
-
class Info
|
8
|
-
include DataMapper::Resource
|
9
|
-
|
10
|
-
property :id, Serial
|
11
|
-
property :revision, Integer
|
12
|
-
property :created_at, DateTime, :default=>DateTime.now
|
13
|
-
property :updated_at, DateTime, :default=>DateTime.now
|
14
|
-
end
|
15
|
-
|
16
|
-
class Theme
|
17
|
-
include DataMapper::Resource
|
18
|
-
|
19
|
-
property :id, Serial
|
20
|
-
property :name, String
|
21
|
-
property :link, String
|
22
|
-
property :created_at, DateTime, :default=>DateTime.now
|
23
|
-
property :updated_at, DateTime, :default=>DateTime.now
|
24
|
-
end
|
25
|
-
|
26
|
-
class Themes
|
27
|
-
|
28
|
-
def initialize(options={:dbname=>"themes.db"})
|
29
|
-
DataMapper.setup(:default, "sqlite3://#{File.join(Dir.pwd, options[:dbname])}")
|
30
|
-
DataMapper.finalize
|
31
|
-
DataMapper.auto_migrate!
|
32
|
-
end
|
33
|
-
|
34
|
-
def import_from_file(filename)
|
35
|
-
doc = Nokogiri::HTML(File.read(filename))
|
36
|
-
title = doc.at_css('title').children.text
|
37
|
-
|
38
|
-
return nil unless title.include?"Revision"
|
39
|
-
revision = title.split("Revision ")[1].split(':')[0].to_i
|
40
|
-
links = doc.xpath('//li//a')
|
41
|
-
|
42
|
-
|
43
|
-
puts "Theme SVN revision is: #{revision}"
|
44
|
-
puts "#{links.count} themes found"
|
45
|
-
|
46
|
-
i = Info.new
|
47
|
-
i.revision = revision
|
48
|
-
i.save
|
49
|
-
|
50
|
-
links.each do |link|
|
51
|
-
# puts "-> #{link.attr('href')} - #{link.text.chop}"
|
52
|
-
t = Theme.new
|
53
|
-
t.name = link.text.chop
|
54
|
-
t.link = 'https://themes.svn.wordpress.org/'+link.attr('href')
|
55
|
-
t.save
|
56
|
-
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|