holepicker 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Changelog.markdown +9 -2
- data/Gemfile +2 -0
- data/Gemfile.lock +9 -1
- data/README.markdown +11 -0
- data/bin/holepicker +0 -5
- data/lib/holepicker/capistrano.rb +24 -0
- data/lib/holepicker/config_gemfile_finder.rb +19 -0
- data/lib/holepicker/config_reader.rb +13 -0
- data/lib/holepicker/data/data.json +2 -1
- data/lib/holepicker/direct_gemfile_finder.rb +31 -0
- data/lib/holepicker/file_finder.rb +7 -0
- data/lib/holepicker/scanner.rb +26 -43
- data/lib/holepicker/version.rb +1 -1
- metadata +10 -12
- data/lib/holepicker/validator.rb +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OTdjY2M3YzA1YTE1MTc4MmZiYzE5YzNkNGVlMWIwYmNlYjE0YmZmMQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NmNhZTIyZTQ5YzNjNDBjOWMzMGM2OGU0ZTZjMWFlNjhkOGYxODBjNA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NjNhMzhiNGM1YmFhNTYzNzc3YzgzMjRkNWQ4N2I3OWU2ZDFiMmNhNTc1YTEw
|
10
|
+
MzJmZDI4NWQ5MWE0OGUyODkzYWVmMTVlOTI0ZGQ5NTFhOWZkMTljYTMzOGVh
|
11
|
+
Yjg3MzRhMGE5YWY2MWUyMzQ2ZTJmZGQxMGMwMzg3N2NiMjI5YWE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NzQzMmNiOTJiZTdiZTYyODA1N2U3MzgyNjViNGRiY2RhYWMxMWE5NmI2OGNk
|
14
|
+
MmZkYWU2Y2MyM2ZlMjMyNzZhNTY2YjgzOTNkZTVmNDE1YTk3ODc3NDU4MDVl
|
15
|
+
MzIyNzBmNmQ1ODEzOTI0ZmNhNWFhNDExNzQwMzhiYTMzZDhmZWI=
|
data/Changelog.markdown
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
+
#### Version 0.2.0 (7.03.2013)
|
2
|
+
|
3
|
+
* Capistrano recipe (@manuelvanrijn)
|
4
|
+
* updated JSON vulnerability from February to also include json_pure
|
5
|
+
* print warning if no gemfiles are found
|
6
|
+
* print green checkmarks instead of OK for safe gemfiles
|
7
|
+
|
1
8
|
#### Version 0.1.2 (23.02.2013)
|
2
9
|
|
3
|
-
* fixed
|
4
|
-
* fixed issue with relative paths like '.' in parameters
|
10
|
+
* fixed some issues on Ruby 1.8 (@timpeters)
|
11
|
+
* fixed issue with relative paths like '.' in parameters (@bct)
|
5
12
|
* fixed parsing gemfiles with platform-specific gems (e.g. mingw32)
|
6
13
|
|
7
14
|
#### Version 0.1.1 (18.82.2013)
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
holepicker (0.
|
4
|
+
holepicker (0.2.0)
|
5
5
|
json (>= 1.7.7)
|
6
6
|
rainbow (>= 1.1.4)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: http://rubygems.org/
|
10
10
|
specs:
|
11
|
+
addressable (2.2.8)
|
12
|
+
crack (0.3.2)
|
11
13
|
diff-lcs (1.1.3)
|
14
|
+
fakefs (0.4.2)
|
12
15
|
json (1.7.7)
|
13
16
|
metaclass (0.0.1)
|
14
17
|
mocha (0.13.2)
|
@@ -22,11 +25,16 @@ GEM
|
|
22
25
|
rspec-expectations (2.12.1)
|
23
26
|
diff-lcs (~> 1.1.3)
|
24
27
|
rspec-mocks (2.12.2)
|
28
|
+
webmock (1.8.7)
|
29
|
+
addressable (>= 2.2.7)
|
30
|
+
crack (>= 0.1.7)
|
25
31
|
|
26
32
|
PLATFORMS
|
27
33
|
ruby
|
28
34
|
|
29
35
|
DEPENDENCIES
|
36
|
+
fakefs
|
30
37
|
holepicker!
|
31
38
|
mocha
|
32
39
|
rspec
|
40
|
+
webmock
|
data/README.markdown
CHANGED
@@ -40,6 +40,8 @@ You can also scan all apps deployed to a production or demo server; in this case
|
|
40
40
|
|
41
41
|
holepicker -c /var/www
|
42
42
|
|
43
|
+
HolePicker will return a non-zero status code if vulnerabilities are found, so you could wrap it in some kind of script that's run periodically from cron that notifies you when something is wrong.
|
44
|
+
|
43
45
|
### Scanning Nginx/Apache config directory
|
44
46
|
|
45
47
|
You might have a lot of random apps deployed in the `/var/www` directory, but only some of them currently enabled in the Nginx config files. In this case, you might want to only check the apps that are actually running. To do that, use the `-f` (`--follow-roots`) option and point HolePicker to your HTTP server's config directory. It will find all the `root` or `DocumentRoot` directives and follow the paths to find the gemfiles of enabled apps.
|
@@ -47,6 +49,15 @@ You might have a lot of random apps deployed in the `/var/www` directory, but on
|
|
47
49
|
holepicker -f /etc/nginx/sites-enabled
|
48
50
|
|
49
51
|
|
52
|
+
## Integration with capistrano
|
53
|
+
|
54
|
+
To automatically check for vulnerabilities before deployment, you can add the HolePicker Capistrano recipe:
|
55
|
+
|
56
|
+
1. Add `gem 'holepicker'` to your `Gemfile` (preferably with `:require => false`)
|
57
|
+
2. Add `require 'holepicker/capistrano'` to your `config/deploy.rb`
|
58
|
+
|
59
|
+
This will introduce a `cap holepicker` task which will be executed before the deploy.
|
60
|
+
|
50
61
|
## Results
|
51
62
|
|
52
63
|
This is more or less what you will get if you run HolePicker in a directory with some old Rails projects:
|
data/bin/holepicker
CHANGED
@@ -31,11 +31,6 @@ OptionParser.new do |opts|
|
|
31
31
|
options[:offline] = true
|
32
32
|
end
|
33
33
|
|
34
|
-
opts.on("-r", "--skip-releases",
|
35
|
-
"Skip gemfiles in 'releases' directory (like -c but will include non-Capistrano deploys)") do
|
36
|
-
options[:skip_releases] = true
|
37
|
-
end
|
38
|
-
|
39
34
|
opts.on("-h", "--help", "Display this help") do
|
40
35
|
puts opts
|
41
36
|
exit
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'holepicker/scanner'
|
2
|
+
|
3
|
+
Capistrano::Configuration.instance.load do
|
4
|
+
set(:holepicker_offline, false) unless exists?(:holepicker_offline)
|
5
|
+
set(:holepicker_ignored_gems, []) unless exists?(:holepicker_ignored_gems)
|
6
|
+
|
7
|
+
before "deploy:update_code", "holepicker"
|
8
|
+
|
9
|
+
namespace :holepicker do
|
10
|
+
desc "Look for vulnerabilities in your Gemfile"
|
11
|
+
task :default, :roles => :app do
|
12
|
+
options = {
|
13
|
+
:ignored_gems => holepicker_ignored_gems,
|
14
|
+
:offline => holepicker_offline
|
15
|
+
}
|
16
|
+
|
17
|
+
gemfile_lock = "#{ENV['BUNDLE_GEMFILE']}.lock"
|
18
|
+
success = HolePicker::Scanner.new(gemfile_lock, options).scan
|
19
|
+
unless success
|
20
|
+
raise Capistrano::CommandError.new("HolePicker found vulnerabilities!")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'holepicker/config_reader'
|
2
|
+
require 'holepicker/file_finder'
|
3
|
+
|
4
|
+
module HolePicker
|
5
|
+
class ConfigGemfileFinder
|
6
|
+
def find_gemfiles(path)
|
7
|
+
configs = find_configs(path)
|
8
|
+
configs.map { |f| ConfigReader.new(f).find_gemfiles }.flatten
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def find_configs(path)
|
14
|
+
full_path = File.expand_path(path)
|
15
|
+
|
16
|
+
FileFinder.find_files(full_path, '-type f -or -type l').select { |f| File.exist?(f) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module HolePicker
|
2
|
+
class ConfigReader
|
3
|
+
ROOT_LINE_PATTERN = %r{\b(?:root|DocumentRoot)\s+(.*)/public\b}
|
4
|
+
|
5
|
+
def initialize(path)
|
6
|
+
@contents = File.read(path)
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_gemfiles
|
10
|
+
@contents.scan(ROOT_LINE_PATTERN).map { |result| "#{result.first}/Gemfile.lock" }.select { |f| File.exist?(f) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -10,7 +10,8 @@
|
|
10
10
|
},
|
11
11
|
{
|
12
12
|
"gems": {
|
13
|
-
"json": ["1.7.7", "1.6.8", "1.5.5"]
|
13
|
+
"json": ["1.7.7", "1.6.8", "1.5.5"],
|
14
|
+
"json_pure": ["1.7.7", "1.6.8", "1.5.5"]
|
14
15
|
},
|
15
16
|
"url": "https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/4_YvCpLzL58",
|
16
17
|
"date": "2013-02-11T18:26Z"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'holepicker/file_finder'
|
2
|
+
|
3
|
+
module HolePicker
|
4
|
+
class DirectGemfileFinder
|
5
|
+
SKIPPED_DIRECTORIES = [
|
6
|
+
"-name cached-copy",
|
7
|
+
"-path '*/bundle/ruby'",
|
8
|
+
"-name tmp",
|
9
|
+
"-name '.*'"
|
10
|
+
]
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@skip_ignored = options.fetch(:skip_ignored, true)
|
14
|
+
@only_current = options.fetch(:only_current, false)
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_gemfiles(path)
|
18
|
+
full_path = File.expand_path(path)
|
19
|
+
gemfiles = @only_current ? "-path '*/current/Gemfile.lock'" : "-name 'Gemfile.lock'"
|
20
|
+
options = @skip_ignored ? "\\( #{skipped_directories} \\) -prune -or #{gemfiles} -print" : gemfiles
|
21
|
+
|
22
|
+
FileFinder.find_files(full_path, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def skipped_directories
|
28
|
+
SKIPPED_DIRECTORIES.join(" -or ")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/holepicker/scanner.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'holepicker/gem'
|
4
|
+
require 'holepicker/config_gemfile_finder'
|
5
|
+
require 'holepicker/direct_gemfile_finder'
|
2
6
|
require 'holepicker/offline_database'
|
3
7
|
require 'holepicker/online_database'
|
4
8
|
require 'holepicker/utils'
|
@@ -7,8 +11,6 @@ require 'set'
|
|
7
11
|
|
8
12
|
module HolePicker
|
9
13
|
class Scanner
|
10
|
-
SKIPPED_DIRECTORIES = ["-name cached-copy", "-path '*/bundle/ruby'", "-name tmp", "-name '.*'"]
|
11
|
-
ROOT_LINE_PATTERN = %r{\b(?:root|DocumentRoot)\s+(.*)/public\b}
|
12
14
|
GEMFILE_GEM_PATTERN = %r(^ {4}[^ ])
|
13
15
|
|
14
16
|
def initialize(paths, options = {})
|
@@ -16,16 +18,23 @@ module HolePicker
|
|
16
18
|
|
17
19
|
@database = options[:offline] ? OfflineDatabase.load : OnlineDatabase.load
|
18
20
|
|
21
|
+
@finder = if options[:follow_roots]
|
22
|
+
ConfigGemfileFinder.new
|
23
|
+
else
|
24
|
+
DirectGemfileFinder.new(
|
25
|
+
:skip_ignored => !options[:dont_skip],
|
26
|
+
:only_current => options[:current]
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
19
30
|
@ignored = options[:ignored_gems] || []
|
20
|
-
@skip = !options[:dont_skip]
|
21
|
-
@current = options[:current]
|
22
|
-
@roots = options[:follow_roots]
|
23
31
|
end
|
24
32
|
|
25
33
|
def scan
|
26
34
|
puts "Looking for gemfiles..."
|
27
35
|
|
28
36
|
@found_vulnerabilities = Set.new
|
37
|
+
@scanned_gemfiles = 0
|
29
38
|
@matched_gemfiles = 0
|
30
39
|
@matched_gems = 0
|
31
40
|
|
@@ -43,37 +52,12 @@ module HolePicker
|
|
43
52
|
@database.vulnerabilities.select { |v| v.gem_vulnerable?(gem) }
|
44
53
|
end
|
45
54
|
|
46
|
-
def find_gemfiles_in_path(path)
|
47
|
-
skips = SKIPPED_DIRECTORIES.join(" -or ")
|
48
|
-
gemfiles = @current ? "-path '*/current/Gemfile.lock'" : "-name 'Gemfile.lock'"
|
49
|
-
|
50
|
-
command = if @skip
|
51
|
-
"find -L #{path} \\( #{skips} \\) -prune -or #{gemfiles} -print"
|
52
|
-
else
|
53
|
-
"find -L #{path} #{gemfiles}"
|
54
|
-
end
|
55
|
-
|
56
|
-
run_and_read_lines(command)
|
57
|
-
end
|
58
|
-
|
59
|
-
def find_gemfiles_in_configs(path)
|
60
|
-
configs = run_and_read_lines("find -L #{path} -type f -or -type l")
|
61
|
-
configs = select_existing(configs)
|
62
|
-
|
63
|
-
directories = configs.map { |f| File.read(f).scan(ROOT_LINE_PATTERN) }
|
64
|
-
gemfiles = directories.flatten.map { |dir| "#{dir}/Gemfile.lock" }
|
65
|
-
|
66
|
-
select_existing(gemfiles)
|
67
|
-
end
|
68
|
-
|
69
55
|
def read_gemfile(path)
|
70
56
|
File.readlines(path).select { |l| l =~ GEMFILE_GEM_PATTERN }.map { |l| Gem.new(l) }
|
71
57
|
end
|
72
58
|
|
73
59
|
def scan_path(path)
|
74
|
-
path
|
75
|
-
gemfiles = @roots ? find_gemfiles_in_configs(path) : find_gemfiles_in_path(path)
|
76
|
-
gemfiles.each { |f| scan_gemfile(f) }
|
60
|
+
@finder.find_gemfiles(path).each { |f| scan_gemfile(f) }
|
77
61
|
end
|
78
62
|
|
79
63
|
def scan_gemfile(path)
|
@@ -88,7 +72,7 @@ module HolePicker
|
|
88
72
|
count = vulnerable_gems.length
|
89
73
|
|
90
74
|
if count == 0
|
91
|
-
puts "
|
75
|
+
puts "✔".color(:green)
|
92
76
|
else
|
93
77
|
puts "#{count} vulnerable #{Utils.pluralize(count, 'gem')} found!".color(:red)
|
94
78
|
|
@@ -103,27 +87,26 @@ module HolePicker
|
|
103
87
|
|
104
88
|
puts
|
105
89
|
end
|
90
|
+
|
91
|
+
@scanned_gemfiles += 1
|
106
92
|
end
|
107
93
|
|
108
94
|
def print_report
|
109
|
-
if @
|
95
|
+
if @scanned_gemfiles == 0
|
96
|
+
puts "No gemfiles found - are you sure the paths are correct?".color(:red)
|
97
|
+
elsif @matched_gemfiles == 0
|
110
98
|
puts "No vulnerabilities found."
|
111
99
|
else
|
112
|
-
|
113
|
-
|
100
|
+
gems = Utils.pluralize(@matched_gems, 'gem')
|
101
|
+
gemfiles = Utils.pluralize(@matched_gemfiles, 'gemfile')
|
102
|
+
|
103
|
+
warning = "#{@matched_gems} vulnerable #{gems} found in #{@matched_gemfiles} #{gemfiles}!\n"
|
104
|
+
puts warning.color(:red)
|
114
105
|
|
115
106
|
@found_vulnerabilities.sort_by(&:id).each do |v|
|
116
107
|
puts "[#{v.tag}] #{v.day}: #{v.url}"
|
117
108
|
end
|
118
109
|
end
|
119
110
|
end
|
120
|
-
|
121
|
-
def select_existing(files)
|
122
|
-
files.select { |f| File.exist?(f) }
|
123
|
-
end
|
124
|
-
|
125
|
-
def run_and_read_lines(command)
|
126
|
-
%x(#{command}).lines.map(&:strip)
|
127
|
-
end
|
128
111
|
end
|
129
112
|
end
|
data/lib/holepicker/version.rb
CHANGED
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: holepicker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jakub Suder
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-03-07 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: json
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ! '>='
|
20
18
|
- !ruby/object:Gem::Version
|
@@ -22,7 +20,6 @@ dependencies:
|
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - ! '>='
|
28
25
|
- !ruby/object:Gem::Version
|
@@ -30,7 +27,6 @@ dependencies:
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rainbow
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ! '>='
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ! '>='
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -55,40 +50,43 @@ files:
|
|
55
50
|
- Changelog.markdown
|
56
51
|
- Gemfile
|
57
52
|
- Gemfile.lock
|
53
|
+
- lib/holepicker/capistrano.rb
|
54
|
+
- lib/holepicker/config_gemfile_finder.rb
|
55
|
+
- lib/holepicker/config_reader.rb
|
58
56
|
- lib/holepicker/data/data.json
|
59
57
|
- lib/holepicker/database.rb
|
58
|
+
- lib/holepicker/direct_gemfile_finder.rb
|
59
|
+
- lib/holepicker/file_finder.rb
|
60
60
|
- lib/holepicker/gem.rb
|
61
61
|
- lib/holepicker/offline_database.rb
|
62
62
|
- lib/holepicker/online_database.rb
|
63
63
|
- lib/holepicker/scanner.rb
|
64
64
|
- lib/holepicker/utils.rb
|
65
|
-
- lib/holepicker/validator.rb
|
66
65
|
- lib/holepicker/version.rb
|
67
66
|
- lib/holepicker/vulnerability.rb
|
68
67
|
- lib/holepicker.rb
|
69
68
|
- bin/holepicker
|
70
69
|
homepage: http://github.com/jsuder/holepicker
|
71
70
|
licenses: []
|
71
|
+
metadata: {}
|
72
72
|
post_install_message:
|
73
73
|
rdoc_options: []
|
74
74
|
require_paths:
|
75
75
|
- lib
|
76
76
|
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
-
none: false
|
78
77
|
requirements:
|
79
78
|
- - ! '>='
|
80
79
|
- !ruby/object:Gem::Version
|
81
80
|
version: '0'
|
82
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
-
none: false
|
84
82
|
requirements:
|
85
83
|
- - ! '>='
|
86
84
|
- !ruby/object:Gem::Version
|
87
85
|
version: '0'
|
88
86
|
requirements: []
|
89
87
|
rubyforge_project:
|
90
|
-
rubygems_version:
|
88
|
+
rubygems_version: 2.0.0
|
91
89
|
signing_key:
|
92
|
-
specification_version:
|
90
|
+
specification_version: 4
|
93
91
|
summary: A tool for checking gem versions in Gemfile.lock files for known vulnerabilities
|
94
92
|
test_files: []
|
data/lib/holepicker/validator.rb
DELETED
File without changes
|