Module: MyPrecious::CVEs

Extended by:
DataCaching
Defined in:
lib/myprecious/cves.rb

Defined Under Namespace

Classes: Applicability_V4_0, CVERecord

Constant Summary collapse

MIN_GAP_SECONDS =
5
CONFIG_FILE =
'.myprecious-cves.rb'
CVE_DATA_CACHE_DIR =
MyPrecious.data_cache(DATA_DIR / "cve-data")

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from DataCaching

print_error_info

Class Attribute Details

.config_dirObject

Returns the value of attribute config_dir.



20
21
22
# File 'lib/myprecious/cves.rb', line 20

def config_dir
  @config_dir
end

Class Method Details

.configObject



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/myprecious/cves.rb', line 78

def self.config
  if !@config && config_dir
    if (config_path = config_dir / CONFIG_FILE).exist?
      @config = begin
        config_prog_output, status = Open3.capture2(RbConfig.ruby, config_path.to_s)
        if status.success?
          JSON.parse(config_prog_output)
        else
          $stderr.puts "#{config_path} did not exit cleanly (code ${status.exitstatus})"
          {}
        end
      rescue StandardError
      end
      
      unless @config.kind_of?(Hash)
        $stderr.puts "#{config_path} did not output a JSON configuration"
        @config = {}
      end
    else
      @config = {}
    end
  end
  @config ||= {}
end

.get_for(package_name, version = '*') ⇒ Object

If you don’t specify version, you get to match against the applicable configurations on your own to determine which CVEs returned apply to the versions of the named package in which you are interested



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/myprecious/cves.rb', line 40

def self.get_for(package_name, version='*')
  nvd_url = URI("https://services.nvd.nist.gov/rest/json/cves/1.0")
  nvd_url.query = URI.encode_www_form(
    cpeMatchString: "cpe:2.3:a:*:#{package_name.downcase}:#{version}:*:*:*:*:*:*:*",
  )
  
  cache = CVE_DATA_CACHE_DIR / "#{Digest::SHA256.hexdigest(nvd_url.to_s)}.json"
  cve_data = apply_cache(cache) do
    # Use last_query_time to sleep if necessary
    wait_time = MIN_GAP_SECONDS - (DateTime.now - last_query_time) * 24 * 3600
    if wait_time > 0
      sleep(wait_time)
    end
    
    response = RestClient.get(nvd_url.to_s)
    queried!
    
    JSON.parse(response.body)
  end
  
  begin
    return cve_data['result']['CVE_Items'].map do |e|
      applicability = objectify_configurations(package_name, e['configurations'])
      score = (((e['impact'] || {})['baseMetricV3'] || {})['cvssV3'] || {})['baseScore']
      cve = CVERecord.new(
        e['cve']['CVE_data_meta']['ID'],
        applicability.respond_to?(:vendors) ? applicability.vendors : nil,
        score
      )
      
      [cve, applicability]
    end.reject {|cve, a| a.respond_to?(:applies_to?) && !a.applies_to?(version)}
  rescue StandardError => e
    $stderr.puts "[WARN] #{e}\n\n#{JSON.dump(cve_data)}\n\n"
    []
  end
end

.last_query_timeObject



27
28
29
# File 'lib/myprecious/cves.rb', line 27

def self.last_query_time
  @last_query_time ||= DateTime.now - 1
end

.objectify_configurations(package_name, configs) ⇒ Object



103
104
105
106
107
108
109
# File 'lib/myprecious/cves.rb', line 103

def self.objectify_configurations(package_name, configs)
  if configs.kind_of?(Hash) && configs['CVE_data_version'] == "4.0"
    Applicability_V4_0.new(package_name, configs)
  else
    configs
  end
end

.queried!Object



31
32
33
# File 'lib/myprecious/cves.rb', line 31

def self.queried!
  @last_query_time = DateTime.now
end