cve_monitor 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 866577f2a4228cefe67d204cd5f8a6c6e74bf7ab474666863882456e4a844110
4
+ data.tar.gz: 34fa84b9253ec670e6cd54f8c273cf2ae1993a126471fff9da78a40a5fee065d
5
+ SHA512:
6
+ metadata.gz: 2c168942f00174f244400e76d7f151be8146f2a401a9563365f91216d39da7acfb15b3d11309202c9db03bb141a52b46af7453f50be18a162df1b27f8e325351
7
+ data.tar.gz: 347b91776546ba071a428226ee0fae972ae17b96206bd7626222404008c06f8f74975e66ec094ca652ebd39359164b41e900fa4b1351b8a877f03af2f8cee9bc
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem "rake", "~> 12.0"
5
+ gem "minitest", "~> 5.0"
6
+ gem "citrus", "~> 3.0"
7
+ gem "pry", "~> 0.12.2", :groups => [:development, :test]
8
+ gem "pry-byebug", "~> 3.8", :groups => [:development, :test]
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Jeremy Symon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ = cve_monitor
2
+
3
+ Maintain a list of software/devices you care about (CPEs), and identify any new
4
+ vulnerabilties registered against them from NIST's CVE feed.
5
+
6
+ Uses https://github.com/jtsymon/cpe23 for parsing and comparing CPEs.
7
+
8
+ :include:cve_monitor.rdoc
9
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'gli'
5
+ require 'cve_monitor'
6
+ require 'cpe23'
7
+ require 'open-uri'
8
+ require 'zlib'
9
+ require 'json'
10
+ require 'pry'
11
+
12
+ class App
13
+ extend GLI::App
14
+ include CveMonitor
15
+
16
+ program_desc 'Monitor CVEs for a list of CPEs'
17
+
18
+ version CveMonitor::VERSION
19
+
20
+ subcommand_option_handling :normal
21
+ arguments :strict
22
+
23
+ desc 'Add CPE(s) to monitor'
24
+ arg_name '[CPE...]'
25
+ command :add do |c|
26
+ c.action do |_global_options, _options, args|
27
+ args = [CpeBuilder.build] if args.empty? || args.all?(&:empty?)
28
+
29
+ # TODO: de-duplicate entries.
30
+ # Need to implement a way of comparing CPEs and determining which one is
31
+ # more generic. The most generic CPE should be retained.
32
+ @cpe_list.add! args
33
+ @cpe_list.save!
34
+ end
35
+ end
36
+
37
+ desc 'Remove monitored CPE(s)'
38
+ arg_name '[CPE...]'
39
+ command :remove do |c|
40
+ c.action do |_global_options, _options, args|
41
+ args = [CpeBuilder.build] if args.empty? || args.all?(&:empty?)
42
+
43
+ # TODO: Should entries be removed if the CPE matches, or only if they are
44
+ # identical?
45
+ @cpe_list.remove! args
46
+ @cpe_list.save!
47
+ end
48
+ end
49
+
50
+ desc 'Displays monitored CPEs'
51
+ command :list do |c|
52
+ c.action do |_global_options, _options, _args|
53
+ puts @cpe_list.to_s
54
+ end
55
+ end
56
+
57
+ desc 'Check for CVEs against monitored CPEs'
58
+ arg_name 'year...'
59
+ command :check do |c|
60
+ c.desc 'Checks against recent CVE list'
61
+ c.command :recent do |c1|
62
+ c1.action do |_global_options, _options, _args|
63
+ CveFeed.new('recent').check(@cpe_list)
64
+ end
65
+ end
66
+
67
+ c.desc 'Check against modified CVE list'
68
+ c.command :modified do |c1|
69
+ c1.action do |_global_options, _options, _args|
70
+ CveFeed.new('modified').check(@cpe_list)
71
+ end
72
+ end
73
+
74
+ c.default_desc 'Check against CVEs for the given year(s)'
75
+ c.action do |_global_options, _options, args|
76
+ raise 'Must specify at least one year' unless args&.any?
77
+
78
+ args.each do |year|
79
+ CveFeed.new(year).check(@cpe_list)
80
+ end
81
+ end
82
+ end
83
+
84
+ pre do |_global, _command, _options, _args|
85
+ # Pre logic here
86
+ # Return true to proceed; false to abort and not call the
87
+ # chosen command
88
+ # Use skips_pre before a command to skip this block
89
+ # on that command only
90
+ @cpe_list = CpeList.new('cpes.txt')
91
+ true
92
+ end
93
+
94
+ post do |global, command, options, args|
95
+ # Post logic here
96
+ # Use skips_post before a command to skip this
97
+ # block on that command only
98
+ end
99
+
100
+ on_error do |_exception|
101
+ # Error logic here
102
+ # return false to skip default error handling
103
+ true
104
+ end
105
+ end
106
+
107
+ exit App.run(ARGV)
@@ -0,0 +1,23 @@
1
+ # Ensure we require the local version and not one we might have installed already
2
+ require File.join([File.dirname(__FILE__),'lib','cve_monitor','version.rb'])
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = 'cve_monitor'
5
+ s.version = CveMonitor::VERSION
6
+ s.author = 'Jeremy Symon'
7
+ s.email = 'jeremy@symon.nz'
8
+ s.homepage = 'https://github.com/jtsymon/cve_monitor'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'Monitor CVEs for a list of CPEs'
11
+ s.files = `git ls-files`.split("
12
+ ")
13
+ s.require_paths << 'lib'
14
+ s.extra_rdoc_files = ['README.rdoc','cve_monitor.rdoc']
15
+ s.rdoc_options << '--title' << 'cve_monitor' << '--main' << 'README.rdoc' << '-ri'
16
+ s.bindir = 'bin'
17
+ s.executables << 'cve_monitor'
18
+ s.add_development_dependency('rake')
19
+ s.add_development_dependency('rdoc')
20
+ s.add_development_dependency('aruba')
21
+ s.add_runtime_dependency('gli','2.19.0')
22
+ s.add_runtime_dependency('cpe23','0.1.0')
23
+ end
@@ -0,0 +1,51 @@
1
+ == cve_monitor - Monitor CVEs for a list of CPEs
2
+
3
+ v0.0.1
4
+
5
+ === Global Options
6
+ === --help
7
+ Show this message
8
+
9
+
10
+
11
+ === --version
12
+ Display the program version
13
+
14
+
15
+
16
+ === Commands
17
+ ==== Command: <tt>add [CPE...]</tt>
18
+ Add CPE(s) to monitor
19
+
20
+
21
+ ==== Command: <tt>check year...</tt>
22
+ Check for CVEs against monitored CPEs
23
+
24
+
25
+ ===== Commands
26
+ ====== Command: <tt>modified </tt>
27
+ Check against modified CVE list
28
+
29
+
30
+ ====== Command: <tt>recent </tt>
31
+ Checks against recent CVE list
32
+
33
+
34
+ ==== Command: <tt>help command</tt>
35
+ Shows a list of commands or help for one command
36
+
37
+ Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function
38
+ ===== Options
39
+ ===== -c
40
+ List commands one per line, to assist with shell completion
41
+
42
+
43
+
44
+ ==== Command: <tt>list </tt>
45
+ Displays monitored CPEs
46
+
47
+
48
+ ==== Command: <tt>remove [CPE...]</tt>
49
+ Remove monitored CPE(s)
50
+
51
+
@@ -0,0 +1,8 @@
1
+ Feature: My bootstrapped app kinda works
2
+ In order to get going on coding my awesome app
3
+ I want to have aruba and cucumber setup
4
+ So I don't have to do it myself
5
+
6
+ Scenario: App just runs
7
+ When I get help for "cve_monitor"
8
+ Then the exit status should be 0
@@ -0,0 +1,6 @@
1
+ When /^I get help for "([^"]*)"$/ do |app_name|
2
+ @app_name = app_name
3
+ step %(I run `#{app_name} help`)
4
+ end
5
+
6
+ # Add more step definitions here
@@ -0,0 +1,15 @@
1
+ require 'aruba/cucumber'
2
+
3
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
4
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
5
+
6
+ Before do
7
+ # Using "announce" causes massive warnings on 1.9.2
8
+ @puts = true
9
+ @original_rubylib = ENV['RUBYLIB']
10
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
11
+ end
12
+
13
+ After do
14
+ ENV['RUBYLIB'] = @original_rubylib
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cve_monitor/version'
4
+ require 'cve_monitor/lazy_hash'
5
+ require 'cve_monitor/cpe_builder'
6
+ require 'cve_monitor/cpe_list'
7
+ require 'cve_monitor/cve_feed'
8
+
9
+ # Add requires for other files you add to your project here, so
10
+ # you just need to require this one file in your bin file
11
+
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'cpe23'
5
+
6
+ module CveMonitor
7
+ class CpeBuilder
8
+ attr_accessor :skip
9
+
10
+ def initialize
11
+ @skip = false
12
+ end
13
+
14
+ def prompt(name, default = nil, *options)
15
+ print "#{name}: "
16
+ print "[#{default}] " unless default.nil?
17
+ options.each do |option|
18
+ print "| #{option} "
19
+ end
20
+ print '> '
21
+ default = default&.downcase
22
+ if @skip
23
+ puts
24
+ default
25
+ else
26
+ input = STDIN.gets
27
+ if input.nil?
28
+ @skip = true
29
+ puts
30
+ default
31
+ else
32
+ input.chomp!.downcase!
33
+ input = default if input.empty? && !default.nil?
34
+ input
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.build
40
+ instance = new
41
+ cpe = Cpe23.new
42
+
43
+ until %w[a o h].include? cpe.part
44
+ input = instance.prompt('Part', 'Application', 'Operating System', 'Hardware')
45
+ cpe.part = case input
46
+ when '', 'application', 'app' then 'a'
47
+ when 'operating system', 'os' then 'o'
48
+ when 'hardware', 'hw' then 'h'
49
+ else input
50
+ end
51
+ end
52
+
53
+ cpe.vendor = instance.prompt('Vendor') || '*'
54
+ cpe.product = instance.prompt('Product') || '*'
55
+ cpe.version = instance.prompt('Version', '*')
56
+ cpe.update = instance.prompt('Update', '*')
57
+ cpe.edition = '*' # deprecated
58
+ cpe.language = instance.prompt('Language', '*')
59
+ cpe.sw_edition = instance.prompt('Software Edition', '*')
60
+ cpe.target_sw = instance.prompt('Target Software', '*')
61
+ cpe.target_hw = instance.prompt('Target Hardware', '*')
62
+ cpe.other = instance.prompt('Other', '*')
63
+
64
+ cpe
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CveMonitor
4
+ class CpeList
5
+ attr_reader :cpes
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ @cpes = if File.exist?(path)
10
+ File.open(path).map do |line|
11
+ Cpe23.parse(line)
12
+ end
13
+ else
14
+ []
15
+ end
16
+ end
17
+
18
+ def add!(cpes)
19
+ @cpes += ensure_cpes(cpes)
20
+ end
21
+
22
+ def remove!(cpes)
23
+ ensure_cpes(cpes).each do |cpe|
24
+ @cpes.reject! { |e| e == cpe }
25
+ end
26
+ end
27
+
28
+ def save!
29
+ File.open(@path, 'w') do |file|
30
+ @cpes.each do |cpe|
31
+ file.write("#{cpe.to_str}\n")
32
+ end
33
+ end
34
+ end
35
+
36
+ def match?(cpes)
37
+ cpes.select do |cpe|
38
+ @cpes.any? { |e| e == cpe }
39
+ end
40
+ end
41
+
42
+ def to_s
43
+ @cpes.map(&:to_s).join("\n")
44
+ end
45
+
46
+ private
47
+
48
+ def ensure_cpes(arr)
49
+ arr.map { |elem| ensure_cpe(elem) }
50
+ end
51
+
52
+ def ensure_cpe(obj)
53
+ case obj
54
+ when Cpe23 then obj
55
+ when String then Cpe23.parse(obj)
56
+ else raise 'Arguments must be CPEs'
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CveMonitor
4
+ class CveFeed
5
+ def initialize(feed)
6
+ @uri = "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-#{feed}.json.gz"
7
+ end
8
+
9
+ def parse_node(node)
10
+ cpes = []
11
+ node['children']&.each do |child|
12
+ cpes += parse_node(child)
13
+ end
14
+
15
+ node['cpe_match']&.each do |cpe_match|
16
+ # This is used for vulnerable configurations where this particular
17
+ # component is not vulnerable.
18
+ # TODO: Handle "operator" key to handle combinations properly.
19
+ next if cpe_match['vulnerable'] == false
20
+
21
+ cpe_str = cpe_match['cpe23Uri'] || cpe_match['cpe22Uri']
22
+ # TODO: What about cpe_name array? Doesn't seem to be used.
23
+ next if cpe_str.nil?
24
+
25
+ cpes << Cpe23.parse(cpe_str)
26
+ # TODO: handle versionStart/versionEnd
27
+ end
28
+
29
+ cpes
30
+ end
31
+
32
+ def check(cpe_list)
33
+ cves = URI.open(@uri) do |f|
34
+ begin
35
+ gz = Zlib::GzipReader.new(f)
36
+ JSON.parse(gz.read)
37
+ ensure
38
+ gz&.close
39
+ end
40
+ end
41
+ cves['CVE_Items']&.each do |item|
42
+ cve = item['cve']
43
+ cve_id = cve.walk('CVE_data_meta', 'ID')
44
+ cpes = item.walk('configurations', 'nodes')&.flat_map do |node|
45
+ parse_node(node)
46
+ end
47
+ matching = cpe_list.match? cpes
48
+ next if matching.empty?
49
+
50
+ puts "https://nvd.nist.gov/vuln/detail/#{cve_id}", matching, "\n"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hash
4
+ def walk(*keys)
5
+ val = self
6
+ keys.each do |key|
7
+ val = val[key]
8
+ break if val.nil?
9
+ end
10
+ val
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module CveMonitor
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class CveMonitorTest < MiniTest::Test
6
+ def test_that_it_has_a_version_number
7
+ refute_nil ::CveMonitor::VERSION
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
4
+ require 'cve_monitor'
5
+
6
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cve_monitor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Symon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-02-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rdoc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aruba
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: gli
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.19.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 2.19.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: cpe23
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.1.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.1.0
83
+ description:
84
+ email: jeremy@symon.nz
85
+ executables:
86
+ - cve_monitor
87
+ extensions: []
88
+ extra_rdoc_files:
89
+ - README.rdoc
90
+ - cve_monitor.rdoc
91
+ files:
92
+ - ".gitignore"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.rdoc
96
+ - Rakefile
97
+ - bin/cve_monitor
98
+ - cve_monitor.gemspec
99
+ - cve_monitor.rdoc
100
+ - features/cve_monitor.feature
101
+ - features/step_definitions/cve_monitor_steps.rb
102
+ - features/support/env.rb
103
+ - lib/cve_monitor.rb
104
+ - lib/cve_monitor/cpe_builder.rb
105
+ - lib/cve_monitor/cpe_list.rb
106
+ - lib/cve_monitor/cve_feed.rb
107
+ - lib/cve_monitor/lazy_hash.rb
108
+ - lib/cve_monitor/version.rb
109
+ - test/cve_monitor_test.rb
110
+ - test/test_helper.rb
111
+ homepage: https://github.com/jtsymon/cve_monitor
112
+ licenses: []
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options:
116
+ - "--title"
117
+ - cve_monitor
118
+ - "--main"
119
+ - README.rdoc
120
+ - "-ri"
121
+ require_paths:
122
+ - lib
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubygems_version: 3.1.2
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Monitor CVEs for a list of CPEs
139
+ test_files: []