wordstress 0.30.0 → 0.40.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/bin/wordstress +68 -43
- data/lib/wordstress/site.rb +185 -59
- data/lib/wordstress/utils.rb +23 -1
- data/lib/wordstress/version.rb +1 -1
- data/wordstress.gemspec +3 -2
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d1d25228ca184bd9640ee87d516d4f5ab6b4c73
|
4
|
+
data.tar.gz: cbeb681f9ddf891f3a67f34b03960a49c63b9e57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7163eef62c310c477d7d247446ab7484ecda6e45543d3fc7d2a5c678d11e758aec38f039ebfdfe57cbfb15242522d71ea70c3687dd33da79da06bdc83522e77
|
7
|
+
data.tar.gz: 8dfceb6b7446fbb0282ddf24fc42ff6640df1238fb13971d65c3d7a4f855b41ca872d6a05f65e471e1953cbe687316e509bd9768ee6ea06c56643e582ec3ae66
|
data/bin/wordstress
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'getoptlong'
|
4
4
|
require 'json'
|
5
|
+
require 'fileutils'
|
5
6
|
require 'codesake-commons'
|
6
7
|
|
7
8
|
require 'wordstress'
|
@@ -9,26 +10,32 @@ require 'wordstress'
|
|
9
10
|
# Scanning modes for plugins and themes
|
10
11
|
# + gentleman: wordstress will try to fetch plugins and themes only using
|
11
12
|
# info in the HTML page (this is very polite but also very inaccurate).
|
12
|
-
# +
|
13
|
+
# + whitebox: wordstress will use a target installed plugin to fetch
|
13
14
|
# installed plugins and themes with their version
|
14
15
|
# + aggressive: wordstress will use enumeration to find installed plugins and
|
15
16
|
# themes. This will lead to false positives.
|
16
|
-
MODES = [:gentleman,:
|
17
|
+
MODES = [:gentleman,:whitebox,:aggressive]
|
17
18
|
APPNAME = File.basename($0)
|
18
19
|
|
19
20
|
$logger = Codesake::Commons::Logging.instance
|
21
|
+
$logger.debug = false
|
22
|
+
# $logger.toggle_silence
|
20
23
|
@output_root = File.join(Dir.home, '/wordstress')
|
21
|
-
|
22
|
-
|
24
|
+
|
25
|
+
scanning_mode = :whitebox
|
26
|
+
whitebox = {:url=>"", :key=>""}
|
27
|
+
basic_auth = {:user=>"", :pwd=>""}
|
23
28
|
|
24
29
|
opts = GetoptLong.new(
|
25
|
-
[ '--gentleman',
|
26
|
-
[ '--
|
27
|
-
[ '--
|
28
|
-
[ '--
|
29
|
-
[ '--
|
30
|
-
[ '--
|
31
|
-
[ '--
|
30
|
+
[ '--gentleman', '-G', GetoptLong::NO_ARGUMENT],
|
31
|
+
[ '--basic-auth', '-B', GetoptLong::REQUIRED_ARGUMENT],
|
32
|
+
[ '--whitebox' , '-W', GetoptLong::NO_ARGUMENT],
|
33
|
+
[ '--wordstress-url', '-u', GetoptLong::REQUIRED_ARGUMENT],
|
34
|
+
[ '--wordstress-api-key', '-k', GetoptLong::REQUIRED_ARGUMENT],
|
35
|
+
[ '--csv', '-C', GetoptLong::NO_ARGUMENT],
|
36
|
+
[ '--debug', '-D', GetoptLong::NO_ARGUMENT],
|
37
|
+
[ '--version', '-v', GetoptLong::NO_ARGUMENT],
|
38
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT]
|
32
39
|
)
|
33
40
|
|
34
41
|
opts.quiet=true
|
@@ -36,15 +43,20 @@ opts.quiet=true
|
|
36
43
|
begin
|
37
44
|
opts.each do |opt, val|
|
38
45
|
case opt
|
39
|
-
when '--
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
46
|
+
when '--basic-auth'
|
47
|
+
basic_auth[:user] = val.split(':')[0]
|
48
|
+
basic_auth[:pwd] = val.split(':')[1]
|
49
|
+
when '--whitebox'
|
50
|
+
scanning_mode = :whitebox
|
51
|
+
when '--wordstress-url'
|
52
|
+
whitebox[:url] = val
|
53
|
+
when '--wordstress-api-key'
|
54
|
+
whitebox[:key] = val
|
45
55
|
when '--version'
|
46
56
|
puts "#{Wordstress::VERSION}"
|
47
57
|
Kernel.exit(0)
|
58
|
+
when '--debug'
|
59
|
+
$logger.debug = true
|
48
60
|
when '--help'
|
49
61
|
Kernel.exit(0)
|
50
62
|
end
|
@@ -55,55 +67,68 @@ rescue GetoptLong::InvalidOption => e
|
|
55
67
|
Kernel.exit(-1)
|
56
68
|
end
|
57
69
|
|
58
|
-
target=ARGV.shift
|
70
|
+
target=ARGV.shift unless scanning_mode == :whitebox
|
71
|
+
target=whitebox[:url] if scanning_mode == :whitebox
|
72
|
+
|
73
|
+
|
59
74
|
$logger.helo APPNAME, Wordstress::VERSION
|
60
|
-
$logger.toggle_syslog
|
61
|
-
@output_dir = File.join(@output_root, Wordstress::Utils.target_to_dirname(target))
|
62
75
|
|
63
76
|
unless Dir.exists?(@output_root)
|
64
|
-
$logger.
|
77
|
+
$logger.log "creating output dir #{@output_root}"
|
65
78
|
Dir.mkdir @output_root
|
66
79
|
end
|
67
80
|
|
68
|
-
|
69
|
-
|
70
|
-
|
81
|
+
@output_dir = Wordstress::Utils.build_output_dir(@output_root, target)
|
82
|
+
$logger.log "storing results to #{@output_dir}"
|
83
|
+
FileUtils::mkdir_p(@output_dir)
|
71
84
|
|
72
85
|
trap("INT") { $logger.die('[INTERRUPTED]') }
|
73
86
|
$logger.die("missing target") if target.nil?
|
74
87
|
|
75
88
|
$logger.log "scanning #{target}"
|
76
|
-
site = Wordstress::Site.new({:target=>target, :scanning_mode=>scanning_mode, :
|
89
|
+
site = Wordstress::Site.new({:target=>target, :scanning_mode=>scanning_mode, :whitebox=>whitebox,:basic_auth=>basic_auth, :output_dir=>@output_dir})
|
77
90
|
|
78
91
|
if site.version[:version] == "0.0.0"
|
79
92
|
$logger.err "can't detect wordpress version running on #{target}. Giving up!"
|
80
93
|
Kernel.exit(-2)
|
81
94
|
end
|
82
95
|
|
83
|
-
$logger.ok "wordpress version #{site.version[:version]}
|
96
|
+
$logger.ok "#{target} is a wordpress version #{site.version[:version]} with #{site.themes.count} themes and #{site.plugins.count} plugins"
|
84
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
|
98
|
+
|
85
99
|
if site.online?
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
100
|
+
site.wp_vuln["wordpress"]["vulnerabilities"].each do |v|
|
101
|
+
$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
|
+
end
|
103
|
+
site.themes.each do |t|
|
104
|
+
v = site.get_theme_vulnerabilities(t[:name])
|
105
|
+
unless v["theme"].nil?
|
106
|
+
v["theme"]["vulnerabilities"].each do |vv|
|
107
|
+
if Gem::Version.new(t[:version]) <= Gem::Version.new(vv["fixed_in"])
|
108
|
+
$logger.err "#{vv["title"]}. Detected: #{t[:version]}. Safe: #{vv["fixed_in"]}"
|
109
|
+
site.theme_vulns << {:title=>vv["title"], :cve=>vv["cve"], :url=>vv["url"], :detected=>t[:version], :fixed_in=>vv["fixed_in"]}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
site.plugins.each do |t|
|
116
|
+
v = site.get_plugin_vulnerabilities(t[:name])
|
117
|
+
unless v["plugin"].nil?
|
118
|
+
v["plugin"]["vulnerabilities"].each do |vv|
|
119
|
+
if Gem::Version.new(t[:version]) <= Gem::Version.new(vv["fixed_in"])
|
120
|
+
$logger.err "#{vv["title"]}. Detected: #{t[:version]}. Safe: #{vv["fixed_in"]}"
|
121
|
+
site.plugin_vulns << {:title=>vv["title"], :cve=>vv["cve"], :url=>vv["url"], :detected=>t[:version], :fixed_in=>vv["fixed_in"]}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
90
125
|
end
|
91
126
|
else
|
127
|
+
site.online = false
|
92
128
|
$logger.err "it seems we are offline. wordstress can't reach https://wpvulndb.com"
|
93
129
|
$logger.err "wordpress can't enumerate vulnerabilities"
|
94
130
|
end
|
95
|
-
$logger.log "#{target} has #{site.themes.count} themes"
|
96
|
-
site.themes.each do |t|
|
97
|
-
$logger.log "searching wpvulndb for theme #{t[:name]} vulnerabilities"
|
98
|
-
v = site.get_theme_vulnerabilities(t[:name])
|
99
|
-
$logger.debug v
|
100
|
-
end
|
101
|
-
|
102
|
-
$logger.log "#{target} has #{site.plugins.count} plugins"
|
103
|
-
site.plugins.each do |t|
|
104
|
-
$logger.log "searching wpvulndb for plugin #{t[:name]} vulnerabilities"
|
105
|
-
v = site.get_plugin_vulnerabilities(t[:name])
|
106
|
-
$logger.debug v
|
107
|
-
end
|
108
131
|
|
132
|
+
site.stop_scan
|
133
|
+
site.ascii_report
|
109
134
|
$logger.bye
|
data/lib/wordstress/site.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'net/http'
|
2
|
+
require 'terminal-table'
|
2
3
|
|
3
4
|
module Wordstress
|
4
5
|
class Site
|
5
6
|
|
6
|
-
attr_reader :version, :scanning_mode, :
|
7
|
+
attr_reader :version, :scanning_mode, :wp_vuln, :plugins, :themes
|
8
|
+
attr_accessor :theme_vulns, :plugin_vulns, :online
|
7
9
|
|
8
|
-
def initialize(options={:target=>"http://localhost", :scanning_mode=>:gentleman, :
|
10
|
+
def initialize(options={:target=>"http://localhost", :scanning_mode=>:gentleman, :whitebox=>{}, :basic_auth=>{:user=>"", :pwd=>""}, :output_dir=>"./"})
|
11
|
+
@target = options[:target]
|
9
12
|
begin
|
10
13
|
@uri = URI(options[:target])
|
11
14
|
@raw_name = options[:target]
|
@@ -13,80 +16,105 @@ module Wordstress
|
|
13
16
|
rescue
|
14
17
|
@valid = false
|
15
18
|
end
|
16
|
-
@scanning_mode
|
19
|
+
@scanning_mode = options[:scanning_mode]
|
20
|
+
|
21
|
+
@basic_auth_user = options[:basic_auth][:user]
|
22
|
+
@basic_auth_pwd = options[:basic_auth][:pwd]
|
23
|
+
@output_dir = options[:output_dir]
|
24
|
+
|
25
|
+
@start_time = Time.now
|
26
|
+
@end_time = Time.now # I hate init variables to nil...
|
27
|
+
|
28
|
+
unless scanning_mode == :whitebox
|
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
|
17
38
|
|
18
|
-
@robots_txt = get(@raw_name + "/robots.txt")
|
19
|
-
@readme_html = get(@raw_name + "/readme.html")
|
20
|
-
@homepage = get(@raw_name)
|
21
|
-
@version = detect_version
|
22
39
|
@online = true
|
23
40
|
|
24
|
-
@
|
25
|
-
@
|
41
|
+
@wp_vuln = get_wp_vulnerabilities unless @version[:version] == "0.0.0"
|
42
|
+
@wp_vuln = JSON.parse("{}") if @version[:version] == "0.0.0"
|
26
43
|
|
27
|
-
@wordstress_page = post_and_get(options[:interactive][:url], options[:interactive][:pwd]) if options[:scanning_mode] == :interactive && !options[:interactive][:pwd].empty?
|
28
|
-
@wordstress_page = get(options[:interactive][:url]) if options[:scanning_mode] == :interactive && options[:interactive][:pwd].empty?
|
29
44
|
@plugins = find_plugins
|
30
45
|
@themes = find_themes
|
31
|
-
|
46
|
+
@theme_vulns = []
|
47
|
+
@plugin_vulns = []
|
32
48
|
end
|
33
49
|
|
34
|
-
def
|
35
|
-
|
36
|
-
@themes.each do |t|
|
37
|
-
vuln << {:theme=>t, :vulns=>get_theme_vulnerabilities(t)}
|
38
|
-
end
|
50
|
+
def stop_scan
|
51
|
+
@end_time = Time.now
|
39
52
|
end
|
40
53
|
|
41
54
|
def get_plugin_vulnerabilities(theme)
|
42
55
|
begin
|
43
56
|
json= get_https("https://wpvulndb.com/api/v1/plugins/#{theme}").body
|
44
|
-
return "
|
45
|
-
return json
|
57
|
+
return JSON.parse("{\"plugin\":{\"vulnerabilities\":[]}}") if json.include?"The page you were looking for doesn't exist (404)"
|
58
|
+
return JSON.parse(json)
|
46
59
|
rescue => e
|
47
60
|
$logger.err e.message
|
48
61
|
@online = false
|
49
|
-
return
|
62
|
+
return JSON.parse("{}")
|
50
63
|
end
|
51
64
|
end
|
52
65
|
|
53
66
|
def get_theme_vulnerabilities(theme)
|
54
67
|
begin
|
55
68
|
json=get_https("https://wpvulndb.com/api/v1/themes/#{theme}").body
|
56
|
-
return "
|
57
|
-
return json
|
69
|
+
return JSON.parse("{\"theme\":{\"vulnerabilities\":[]}}") if json.include?"The page you were looking for doesn't exist (404)"
|
70
|
+
return JSON.parse(json)
|
58
71
|
rescue => e
|
59
72
|
$logger.err e.message
|
60
73
|
@online = false
|
61
|
-
return
|
74
|
+
return JSON.parse("{}")
|
62
75
|
end
|
63
76
|
end
|
64
77
|
|
65
78
|
def get_wp_vulnerabilities
|
66
79
|
begin
|
67
|
-
|
80
|
+
page= get_https("https://wpvulndb.com/api/v1/wordpresses/#{version_pad(@version[:version])}")
|
81
|
+
return JSON.parse(page.body) unless page.class == Net::HTTPNotFound
|
82
|
+
return JSON.parse("{\"wordpress\":{\"vulnerabilities\":[]}}") if page.class == Net::HTTPNotFound
|
68
83
|
rescue => e
|
69
84
|
$logger.err e.message
|
70
85
|
@online = false
|
71
|
-
return ""
|
86
|
+
return JSON.parse("{}")
|
72
87
|
end
|
73
88
|
end
|
74
89
|
|
75
90
|
def version_pad(version)
|
76
|
-
#
|
77
|
-
#
|
78
|
-
|
79
|
-
|
91
|
+
return version.gsub('.', '') #if version.split('.').count == 3
|
92
|
+
# return version.gsub('.', '')+'0' if version.split('.').count == 2
|
93
|
+
end
|
94
|
+
|
95
|
+
def detect_version(page, whitebox=false)
|
96
|
+
detect_version_blackbox(page) unless whitebox
|
97
|
+
detect_version_whitebox(page) if whitebox
|
98
|
+
end
|
99
|
+
|
100
|
+
def detect_version_whitebox(page)
|
101
|
+
v_meta = '0.0.0'
|
102
|
+
doc = Nokogiri::HTML(page.body)
|
103
|
+
doc.css('#wp_version').each do |link|
|
104
|
+
v_meta = link.text
|
105
|
+
end
|
106
|
+
|
107
|
+
return {:version => v_meta, :accuracy => 1.0}
|
80
108
|
end
|
81
109
|
|
82
|
-
def
|
110
|
+
def detect_version_blackbox(page)
|
83
111
|
|
84
112
|
#
|
85
113
|
# 1. trying to detect wordpress version from homepage body meta generator
|
86
114
|
# tag
|
87
115
|
|
88
116
|
v_meta = ""
|
89
|
-
doc = Nokogiri::HTML(
|
117
|
+
doc = Nokogiri::HTML(page.body)
|
90
118
|
doc.xpath("//meta[@name='generator']/@content").each do |attr|
|
91
119
|
v_meta = attr.value.split(' ')[1]
|
92
120
|
end
|
@@ -94,13 +122,20 @@ module Wordstress
|
|
94
122
|
#
|
95
123
|
# 2. trying to detect wordpress version from readme.html in the root
|
96
124
|
# directory
|
125
|
+
#
|
126
|
+
# Not available if scanning
|
97
127
|
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
101
133
|
|
134
|
+
#
|
135
|
+
# 3. Detect from RSS link
|
136
|
+
#
|
102
137
|
v_rss = ""
|
103
|
-
rss_doc = Nokogiri::HTML(
|
138
|
+
rss_doc = Nokogiri::HTML(page.body)
|
104
139
|
begin
|
105
140
|
rss = Nokogiri::HTML(get(rss_doc.css('link[type="application/rss+xml"]').first.attr('href')).body) unless l.nil?
|
106
141
|
v_rss= rss.css('generator').text.split('=')[1]
|
@@ -130,12 +165,12 @@ module Wordstress
|
|
130
165
|
|
131
166
|
def find_themes
|
132
167
|
return find_themes_gentleman if @scanning_mode == :gentleman
|
133
|
-
return
|
168
|
+
return find_themes_whitebox if @scanning_mode == :whitebox
|
134
169
|
return []
|
135
170
|
end
|
136
171
|
def find_plugins
|
137
172
|
return find_plugins_gentleman if @scanning_mode == :gentleman
|
138
|
-
return
|
173
|
+
return find_plugins_whitebox if @scanning_mode == :whitebox
|
139
174
|
|
140
175
|
# bruteforce check must start with error page discovery.
|
141
176
|
# the idea is to send 2 random plugin names (e.g. 2 sha256 of time seed)
|
@@ -144,11 +179,89 @@ module Wordstress
|
|
144
179
|
return []
|
145
180
|
end
|
146
181
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
182
|
+
def ascii_report
|
183
|
+
# 0_Executive summary
|
184
|
+
rows = []
|
185
|
+
rows << ['Wordstress version', Wordstress::VERSION]
|
186
|
+
rows << ['Scan started',@start_time]
|
187
|
+
rows << ['Scan duration', "#{(@end_time - @start_time).round(3)} sec"]
|
188
|
+
rows << ['Target', @target]
|
189
|
+
rows << ['Wordpress version', version[:version]]
|
190
|
+
unless @online
|
191
|
+
rows << ['Scan status', 'During scan wordstress went offline. Results are incomplete / unreliable. Please make sure you are connected to the Internet']
|
192
|
+
else
|
193
|
+
rows << ['Scan status', 'Scan completed successfully']
|
194
|
+
end
|
195
|
+
table = Terminal::Table.new :title=>'Scan summary', :rows => rows
|
196
|
+
puts table
|
197
|
+
|
198
|
+
return table unless @online
|
199
|
+
# 1_vulnerability summary
|
200
|
+
rows = []
|
201
|
+
rows << ['Wordpress version', @wp_vuln["wordpress"]["vulnerabilities"].count]
|
202
|
+
rows << ['Plugins installed', @plugin_vulns.count]
|
203
|
+
rows << ['Themes installed', @theme_vulns.count]
|
204
|
+
|
205
|
+
table = Terminal::Table.new :title=>'Vulnerabilities found', :rows => rows
|
206
|
+
puts table
|
207
|
+
|
208
|
+
# 2_vulnerabilities detail
|
209
|
+
|
210
|
+
if @wp_vuln["wordpress"]["vulnerabilities"].count != 0
|
211
|
+
|
212
|
+
rows = []
|
213
|
+
@wp_vuln["wordpress"]["vulnerabilities"].each do |v|
|
214
|
+
rows << [v[:title], v[:cve], v[:url], v[:fixed_in]]
|
215
|
+
rows << :separator
|
216
|
+
end
|
217
|
+
table = Terminal::Table.new :title=>"Vulnerabilities in Wordpress version #{version[:version]}", :headings=>['Issue', 'CVE', 'Url', 'Fixed in version'], :rows=>rows
|
218
|
+
puts table
|
219
|
+
end
|
220
|
+
|
221
|
+
if @plugin_vulns.count != 0
|
222
|
+
rows = []
|
223
|
+
@plugin_vulns.each do |v|
|
224
|
+
rows << [v[:title], v[:cve], v[:detected], v[:fixed_in]]
|
225
|
+
rows << :separator
|
226
|
+
end
|
227
|
+
table = Terminal::Table.new :title=>"Vulnerabilities in installed plugins", :headings=>['Issue', 'CVE', 'Detected version', 'Fixed version'], :rows=>rows
|
228
|
+
puts table
|
229
|
+
end
|
230
|
+
|
231
|
+
if @theme_vulns.count != 0
|
232
|
+
|
233
|
+
rows = []
|
234
|
+
@theme_vulns.each do |v|
|
235
|
+
rows << [v[:title], v[:cve], v[:detected], v[:fixed_in]]
|
236
|
+
rows << :separator
|
237
|
+
end
|
238
|
+
table = Terminal::Table.new :title=>"Vulnerabilities in installed themes", :headings=>['Issue', 'CVE', 'Detected version', 'Fixed in version'], :rows=>rows
|
239
|
+
puts table
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
|
245
|
+
|
246
|
+
# File.open(File.join(@output_dir, "report.txt"), 'w') do |file|
|
247
|
+
|
248
|
+
# file.puts("target: #{@target}")
|
249
|
+
# file.puts("wordpress version: #{version[:version]}")
|
250
|
+
# file.puts("themes found: #{@themes.count}")
|
251
|
+
# file.puts("plugins found: #{@plugins.count}")
|
252
|
+
# file.puts("Vulnerabilities in wordpress")
|
253
|
+
# @wp_vuln["wordpress"]["vulnerabilities"].each do |v|
|
254
|
+
# file.puts "#{v[:title]} - fixed in #{v[:fixed_in]}"
|
255
|
+
# end
|
256
|
+
# file.puts("Vulnerabilities in themes")
|
257
|
+
# @theme_vulns.each do |v|
|
258
|
+
# file.puts "#{v[:title]} - fixed in #{v[:fixed_in]}"
|
259
|
+
# end
|
260
|
+
# file.puts("Vulnerabilities in plugins")
|
261
|
+
# @plugins_vulns.each do |v|
|
262
|
+
# file.puts "#{v[:title]} - fixed in #{v[:fixed_in]}"
|
263
|
+
# end
|
264
|
+
# end
|
152
265
|
end
|
153
266
|
|
154
267
|
private
|
@@ -158,24 +271,22 @@ module Wordstress
|
|
158
271
|
return (!a.nil?)
|
159
272
|
end
|
160
273
|
|
161
|
-
def
|
274
|
+
def find_plugins_whitebox
|
162
275
|
ret = []
|
163
276
|
doc = Nokogiri::HTML(@wordstress_page.body)
|
164
277
|
doc.css('#all_plugin').each do |link|
|
165
278
|
l=link.text.split(',')
|
166
|
-
ret << {:name=>l[2], :version=>l[1], :status=>l[3]} unless is_already_detected?(ret, l[2])
|
279
|
+
ret << {:name=>l[2].split('/')[0], :version=>l[1], :status=>l[3]} unless is_already_detected?(ret, l[2])
|
167
280
|
end
|
168
|
-
$logger.debug ret
|
169
281
|
ret
|
170
282
|
end
|
171
|
-
def
|
283
|
+
def find_themes_whitebox
|
172
284
|
ret = []
|
173
285
|
doc = Nokogiri::HTML(@wordstress_page.body)
|
174
286
|
doc.css('#all_theme').each do |link|
|
175
287
|
l=link.text.split(',')
|
176
|
-
ret << {:name=>l[2], :version=>l[1]} unless is_already_detected?(ret, l[2])
|
288
|
+
ret << {:name=>l[2], :version=>l[1], :status=>l[3]} unless is_already_detected?(ret, l[2])
|
177
289
|
end
|
178
|
-
$logger.debug ret
|
179
290
|
ret
|
180
291
|
end
|
181
292
|
|
@@ -220,19 +331,34 @@ module Wordstress
|
|
220
331
|
ret
|
221
332
|
end
|
222
333
|
|
223
|
-
def get_http(page)
|
224
|
-
uri = URI
|
225
|
-
|
226
|
-
|
227
|
-
|
334
|
+
def get_http(page, use_ssl=false)
|
335
|
+
uri = URI(page)
|
336
|
+
req = Net::HTTP::Get.new(uri)
|
337
|
+
req.basic_auth @basic_auth_user, @basic_auth_pwd unless @basic_auth_user == ""
|
338
|
+
|
339
|
+
|
340
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
341
|
+
http.use_ssl = use_ssl
|
342
|
+
|
343
|
+
res = http.start {|h|
|
344
|
+
h.request(req)
|
345
|
+
}
|
346
|
+
case res
|
347
|
+
when Net::HTTPSuccess then
|
348
|
+
return res
|
349
|
+
when Net::HTTPRedirection then
|
350
|
+
location = res['location']
|
351
|
+
$logger.debug "redirected to #{location}"
|
352
|
+
get_http(location)
|
353
|
+
when Net::HTTPNotFound
|
354
|
+
return res
|
355
|
+
else
|
356
|
+
return res.value
|
357
|
+
end
|
228
358
|
end
|
229
|
-
def get_https(page)
|
230
|
-
uri = URI.parse(page)
|
231
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
232
|
-
http.use_ssl = true
|
233
|
-
request = Net::HTTP::Get.new(uri.request_uri)
|
234
|
-
return http.request(request)
|
235
359
|
|
360
|
+
def get_https(page)
|
361
|
+
get_http(page, true)
|
236
362
|
end
|
237
363
|
end
|
238
364
|
end
|
data/lib/wordstress/utils.rb
CHANGED
@@ -3,7 +3,29 @@ module Wordstress
|
|
3
3
|
|
4
4
|
# Transform a given URL into a directory name to be used to store data
|
5
5
|
def self.target_to_dirname(target)
|
6
|
-
|
6
|
+
uri = URI.parse(target)
|
7
|
+
path = uri.request_uri.split('/')
|
8
|
+
blog_path = ""
|
9
|
+
blog_path = "_#{path[1]}" if path.count >= 2
|
10
|
+
return "#{uri.host}_#{uri.port}#{blog_path}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.build_output_dir(root, target)
|
14
|
+
attempt=0
|
15
|
+
today=Time.now.strftime("%Y%m%d")
|
16
|
+
|
17
|
+
while 1 do
|
18
|
+
|
19
|
+
proposed = File.join(root, Wordstress::Utils.target_to_dirname(target), today)
|
20
|
+
if attempt != 0
|
21
|
+
proposed += "_#{attempt}"
|
22
|
+
end
|
23
|
+
|
24
|
+
return proposed unless Dir.exists?(proposed)
|
25
|
+
attempt +=1 if Dir.exists?(proposed)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
7
29
|
end
|
8
30
|
|
9
31
|
end
|
data/lib/wordstress/version.rb
CHANGED
data/wordstress.gemspec
CHANGED
@@ -7,10 +7,10 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "wordstress"
|
8
8
|
spec.version = Wordstress::VERSION
|
9
9
|
spec.authors = ["Paolo Perego"]
|
10
|
-
spec.email = ["
|
10
|
+
spec.email = ["paolo@wordstress.org"]
|
11
11
|
spec.summary = %q{wordstress is a security scanner for wordpress powered websites}
|
12
12
|
spec.description = %q{wordstress is a security scanner for wordpress powered websites}
|
13
|
-
spec.homepage = "
|
13
|
+
spec.homepage = "http://wordstress.org"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -24,5 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency 'codesake-commons'
|
25
25
|
spec.add_dependency 'json'
|
26
26
|
spec.add_dependency 'ciphersurfer'
|
27
|
+
spec.add_dependency 'terminal-table'
|
27
28
|
|
28
29
|
end
|
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.40.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: 2015-
|
11
|
+
date: 2015-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,9 +80,23 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: terminal-table
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description: wordstress is a security scanner for wordpress powered websites
|
84
98
|
email:
|
85
|
-
-
|
99
|
+
- paolo@wordstress.org
|
86
100
|
executables:
|
87
101
|
- wordstress
|
88
102
|
extensions: []
|
@@ -101,7 +115,7 @@ files:
|
|
101
115
|
- lib/wordstress/utils.rb
|
102
116
|
- lib/wordstress/version.rb
|
103
117
|
- wordstress.gemspec
|
104
|
-
homepage:
|
118
|
+
homepage: http://wordstress.org
|
105
119
|
licenses:
|
106
120
|
- MIT
|
107
121
|
metadata: {}
|