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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec2d1bdf19fed96cb2415b71df03a5a6e81422bb
4
- data.tar.gz: 653958b73a60f4dac746d88d7a3af073ee8eab83
3
+ metadata.gz: 8d1d25228ca184bd9640ee87d516d4f5ab6b4c73
4
+ data.tar.gz: cbeb681f9ddf891f3a67f34b03960a49c63b9e57
5
5
  SHA512:
6
- metadata.gz: 94d66e0a989016ffaac389867c1f214194b713a457eabf5f7e18cea65b5a601f111714ef01b79c488387e44661563e78d583a98fa207613e69b222aae1378118
7
- data.tar.gz: 68de0225374d311b219056ecc05b3c8698a29a58000c7286ab27a7e7dd91022aab7d637681a70f951db2a0a7eea2214e1be0d9cf025ee6f876b20ed469d4ee5f
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
- # + interactive: wordstress will use a target installed plugin to fetch
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,:interactive,:aggressive]
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
- scanning_mode = :gentleman
22
- interactive = {:url=>"", :pwd=>""}
24
+
25
+ scanning_mode = :whitebox
26
+ whitebox = {:url=>"", :key=>""}
27
+ basic_auth = {:user=>"", :pwd=>""}
23
28
 
24
29
  opts = GetoptLong.new(
25
- [ '--gentleman', '-G', GetoptLong::NO_ARGUMENT],
26
- [ '--interactive','-I', GetoptLong::NO_ARGUMENT],
27
- [ '--interactive-url', '-u', GetoptLong::REQUIRED_ARGUMENT],
28
- [ '--interactive-pwd', '-p', GetoptLong::REQUIRED_ARGUMENT],
29
- [ '--csv', '-C', GetoptLong::NO_ARGUMENT],
30
- [ '--version', '-v', GetoptLong::NO_ARGUMENT],
31
- [ '--help', '-h', GetoptLong::NO_ARGUMENT]
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 '--interactive'
40
- scanning_mode = :interactive
41
- when '--interactive-url'
42
- interactive[:url] = val
43
- when '--interactive-pwd'
44
- interactive[:pwd] = val
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.ok "creating output dir #{@output_root}"
77
+ $logger.log "creating output dir #{@output_root}"
65
78
  Dir.mkdir @output_root
66
79
  end
67
80
 
68
- unless Dir.exists?(@output_dir)
69
- $logger.ok "storing results to #{@output_dir}"
70
- end
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, :interactive=>interactive})
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]} detected"
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
- wp_vuln_hash = JSON.parse(site.wp_vuln_json)
87
- $logger.ok "#{wp_vuln_hash["wordpress"]["vulnerabilities"].size} vulnerabilities found due wordpress version"
88
- wp_vuln_hash["wordpress"]["vulnerabilities"].each do |v|
89
- $logger.log "#{v["id"]} - #{v["title"]}"
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
@@ -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, :wp_vuln_json, :plugins, :themes, :themes_vuln_json
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, :interactive=>{}})
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 = options[: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
- @wp_vuln_json = get_wp_vulnerabilities unless @version[:version] == "0.0.0"
25
- @wp_vuln_json = Hash.new.to_json if @version[:version] == "0.0.0"
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
- # @themes_vuln_json = get_themes_vulnerabilities
46
+ @theme_vulns = []
47
+ @plugin_vulns = []
32
48
  end
33
49
 
34
- def get_themes_vulnerabilities
35
- vuln = []
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 "Plugin #{theme} is not present on wpvulndb.com" if json.include?"The page you were looking for doesn't exist (404)"
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 "Theme #{theme} is not present on wpvulndb.com" if json.include?"The page you were looking for doesn't exist (404)"
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
- return get_https("https://wpvulndb.com/api/v1/wordpresses/#{version_pad(@version[:version])}").body
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
- # 3.2.1 => 321
77
- # 4.0 => 400
78
- return version.gsub('.', '') if version.split('.').count == 3
79
- return version.gsub('.', '')+'0' if version.split('.').count == 2
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 detect_version
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(@homepage.body)
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
- v_readme = ""
99
- doc = Nokogiri::HTML(@readme_html.body)
100
- v_readme = doc.at_css('h1').children.last.text.chop.lstrip.split(' ')[1]
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(@homepage.body)
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 find_themes_interactive if @scanning_mode == :interactive
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 find_plugins_interactive if @scanning_mode == :interactive
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 post_and_get(url, pass)
148
- uri = URI(url)
149
- res = Net::HTTP.post_form(uri, {'post_password' => pass, 'Submit'=>'Submit'})
150
- get(url)
151
- res.body
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 find_plugins_interactive
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 find_themes_interactive
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.parse(page)
225
- http = Net::HTTP.new(uri.host, uri.port)
226
- request = Net::HTTP::Get.new(uri.request_uri)
227
- return http.request(request)
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
@@ -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
- target.split("://")[1].gsub('.','_').gsub('/', '')
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
@@ -1,3 +1,3 @@
1
1
  module Wordstress
2
- VERSION = "0.30.0"
2
+ VERSION = "0.40.0"
3
3
  end
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 = ["thesp0nge@gmail.com"]
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 = "https://github.com/thesp0nge/wordstress"
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.30.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-01-19 00:00:00.000000000 Z
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
- - thesp0nge@gmail.com
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: https://github.com/thesp0nge/wordstress
118
+ homepage: http://wordstress.org
105
119
  licenses:
106
120
  - MIT
107
121
  metadata: {}