cve_monitor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []