wordstress 0.50.0 → 0.70.0

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