wordstress 0.30.0 → 0.40.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: {}
|