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 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: {}