wordstress 0.50.0 → 0.70.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: 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