cve_crawler 0.1.0 → 0.1.2
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.
- checksums.yaml +4 -4
- data/lib/cve_crawler/cve_core.rb +3 -3
- data/lib/cve_crawler/cve_crawler.rb +37 -37
- data/lib/cve_crawler/cve_filter.rb +35 -35
- data/lib/cve_crawler/cve_parser.rb +74 -74
- data/lib/cve_crawler/cve_vulnerability.rb +62 -62
- data/spec/crawler_spec.rb +15 -15
- data/spec/parser_spec.rb +38 -38
- data/spec/spec_helper.rb +84 -84
- data/spec/vulnerability_spec.rb +137 -137
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bb15083b60b4fdb80817982843e88e41af0e47c
|
4
|
+
data.tar.gz: e6b894f461ed3d6d452cf60bdde31979cf5db551
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e7fde51cb8b3c79811d2df649cab980f87929babd1e0d3dbc3766edaa3366865135742c2ccc7a33fb7500c6f93dc76a6f804f54f99a0ab484d90b8c5dcc91ec
|
7
|
+
data.tar.gz: ffe61027502d9a44c491e30faa142548cc58f197aff9bab1eebf2908b1b13d085d6804abf350f4dfeffe23851437e0c341b254e7996f55d7434fa21e535292d1
|
data/lib/cve_crawler/cve_core.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
require 'time'
|
2
|
-
require 'cve_crawler'
|
3
|
-
require 'cve_parser'
|
2
|
+
require 'cve_crawler/cve_crawler'
|
3
|
+
require 'cve_crawler/cve_parser'
|
4
4
|
|
5
5
|
module CVE
|
6
6
|
VERSION_MAJOR = 0
|
7
7
|
VERSION_MINOR = 1
|
8
|
-
VERSION_BUILD =
|
8
|
+
VERSION_BUILD = 1
|
9
9
|
VERSION = "#{VERSION_MAJOR}.#{VERSION_MINOR}.#{VERSION_BUILD}".freeze
|
10
10
|
|
11
11
|
class Core
|
@@ -1,37 +1,37 @@
|
|
1
|
-
require 'net/https'
|
2
|
-
require 'uri'
|
3
|
-
|
4
|
-
module CVE
|
5
|
-
class Crawler
|
6
|
-
DATA_FEED_DEFAULT = URI('https://nvd.nist.gov/download/nvd-rss.xml')
|
7
|
-
DATA_FEED_ANALYZED = URI('https://nvd.nist.gov/download/nvd-rss-analyzed.xml')
|
8
|
-
|
9
|
-
def initialize(type, verify_cert, user_agent)
|
10
|
-
@crawl_url = type.downcase == 'analyzed' ? DATA_FEED_ANALYZED : DATA_FEED_DEFAULT
|
11
|
-
@verify_cert = verify_cert ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
12
|
-
@user_agent = user_agent
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_reader :crawl_url, :verify_cert, :user_agent
|
16
|
-
|
17
|
-
def crawl
|
18
|
-
http = Net::HTTP.new(@crawl_url.host, @crawl_url.port)
|
19
|
-
http.use_ssl = @crawl_url.scheme == 'https'
|
20
|
-
|
21
|
-
if http.use_ssl?
|
22
|
-
http.verify_mode = @verify_cert
|
23
|
-
end
|
24
|
-
|
25
|
-
request = Net::HTTP::Get.new(@crawl_url, {'User-Agent' => @user_agent})
|
26
|
-
response = http.request(request)
|
27
|
-
|
28
|
-
response.value # Raise an error if status is not 200
|
29
|
-
|
30
|
-
response
|
31
|
-
end
|
32
|
-
|
33
|
-
def inspect
|
34
|
-
"#<CVE::Crawler url=#{@crawl_url.to_s}>"
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module CVE
|
5
|
+
class Crawler
|
6
|
+
DATA_FEED_DEFAULT = URI('https://nvd.nist.gov/download/nvd-rss.xml')
|
7
|
+
DATA_FEED_ANALYZED = URI('https://nvd.nist.gov/download/nvd-rss-analyzed.xml')
|
8
|
+
|
9
|
+
def initialize(type, verify_cert, user_agent)
|
10
|
+
@crawl_url = type.downcase == 'analyzed' ? DATA_FEED_ANALYZED : DATA_FEED_DEFAULT
|
11
|
+
@verify_cert = verify_cert ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
12
|
+
@user_agent = user_agent
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :crawl_url, :verify_cert, :user_agent
|
16
|
+
|
17
|
+
def crawl
|
18
|
+
http = Net::HTTP.new(@crawl_url.host, @crawl_url.port)
|
19
|
+
http.use_ssl = @crawl_url.scheme == 'https'
|
20
|
+
|
21
|
+
if http.use_ssl?
|
22
|
+
http.verify_mode = @verify_cert
|
23
|
+
end
|
24
|
+
|
25
|
+
request = Net::HTTP::Get.new(@crawl_url, {'User-Agent' => @user_agent})
|
26
|
+
response = http.request(request)
|
27
|
+
|
28
|
+
response.value # Raise an error if status is not 200
|
29
|
+
|
30
|
+
response
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"#<CVE::Crawler url=#{@crawl_url.to_s}>"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,35 +1,35 @@
|
|
1
|
-
module CVE
|
2
|
-
class Filter
|
3
|
-
def initialize(history_size=100)
|
4
|
-
@history = []
|
5
|
-
@history_size = history_size
|
6
|
-
end
|
7
|
-
|
8
|
-
attr_reader :history_size
|
9
|
-
|
10
|
-
def filter(contents)
|
11
|
-
return true unless contents.is_a?(Vulnerability)
|
12
|
-
return true if cve_exists?(contents.identifier)
|
13
|
-
|
14
|
-
history_check_limit
|
15
|
-
false
|
16
|
-
end
|
17
|
-
|
18
|
-
def cve_exists?(identifier)
|
19
|
-
return true if @history.include?(identifier)
|
20
|
-
|
21
|
-
@history << identifier
|
22
|
-
false
|
23
|
-
end
|
24
|
-
|
25
|
-
def history_check_limit
|
26
|
-
if @history.length >= @history_size
|
27
|
-
@history.drop((@history_size / 2).round)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def inspect
|
32
|
-
"#<CVE::Filter history=#{@history.count} limit=#{@history_size}>"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
1
|
+
module CVE
|
2
|
+
class Filter
|
3
|
+
def initialize(history_size=100)
|
4
|
+
@history = []
|
5
|
+
@history_size = history_size
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :history_size
|
9
|
+
|
10
|
+
def filter(contents)
|
11
|
+
return true unless contents.is_a?(Vulnerability)
|
12
|
+
return true if cve_exists?(contents.identifier)
|
13
|
+
|
14
|
+
history_check_limit
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def cve_exists?(identifier)
|
19
|
+
return true if @history.include?(identifier)
|
20
|
+
|
21
|
+
@history << identifier
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def history_check_limit
|
26
|
+
if @history.length >= @history_size
|
27
|
+
@history.drop((@history_size / 2).round)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def inspect
|
32
|
+
"#<CVE::Filter history=#{@history.count} limit=#{@history_size}>"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,74 +1,74 @@
|
|
1
|
-
require 'rss'
|
2
|
-
require 'cve_filter'
|
3
|
-
require 'cve_vulnerability'
|
4
|
-
|
5
|
-
module CVE
|
6
|
-
class Parser
|
7
|
-
def initialize(filters=nil)
|
8
|
-
@filters = []
|
9
|
-
|
10
|
-
@filters << CVE::Filter.new
|
11
|
-
@filters << filters if filters
|
12
|
-
end
|
13
|
-
|
14
|
-
def parse(content)
|
15
|
-
results = parse_rss_feed(content)
|
16
|
-
|
17
|
-
filter(results)
|
18
|
-
end
|
19
|
-
|
20
|
-
def parse_rss_feed(content)
|
21
|
-
rss = RSS::Parser.parse(content)
|
22
|
-
|
23
|
-
parse_items(rss)
|
24
|
-
end
|
25
|
-
|
26
|
-
def parse_items(rss)
|
27
|
-
items = []
|
28
|
-
|
29
|
-
if rss.nil?
|
30
|
-
raise 'RSS object failed to parse, is it valid XML?'
|
31
|
-
end
|
32
|
-
|
33
|
-
rss.items.each do |item|
|
34
|
-
items << parse_item(item)
|
35
|
-
end
|
36
|
-
|
37
|
-
items
|
38
|
-
end
|
39
|
-
|
40
|
-
def parse_item(item)
|
41
|
-
CVE::Vulnerability.new({
|
42
|
-
:identifier => extract_cve_identifier(item.title),
|
43
|
-
:title => item.title,
|
44
|
-
:link => item.link,
|
45
|
-
:description => item.description,
|
46
|
-
:date => item.date
|
47
|
-
})
|
48
|
-
end
|
49
|
-
|
50
|
-
def extract_cve_identifier(title)
|
51
|
-
title.split(' ')[0]
|
52
|
-
end
|
53
|
-
|
54
|
-
def filter(results)
|
55
|
-
filtered_contents = []
|
56
|
-
|
57
|
-
results.each do |result|
|
58
|
-
result_ok = true
|
59
|
-
|
60
|
-
@filters.each do |filter|
|
61
|
-
result_ok = result_ok && !filter.filter(result)
|
62
|
-
end
|
63
|
-
|
64
|
-
filtered_contents << result if result_ok
|
65
|
-
end
|
66
|
-
|
67
|
-
filtered_contents
|
68
|
-
end
|
69
|
-
|
70
|
-
def inspect
|
71
|
-
"<CVE::Parser filters=#{@filters.inspect}>"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
1
|
+
require 'rss'
|
2
|
+
require 'cve_crawler/cve_filter'
|
3
|
+
require 'cve_crawler/cve_vulnerability'
|
4
|
+
|
5
|
+
module CVE
|
6
|
+
class Parser
|
7
|
+
def initialize(filters=nil)
|
8
|
+
@filters = []
|
9
|
+
|
10
|
+
@filters << CVE::Filter.new
|
11
|
+
@filters << filters if filters
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse(content)
|
15
|
+
results = parse_rss_feed(content)
|
16
|
+
|
17
|
+
filter(results)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_rss_feed(content)
|
21
|
+
rss = RSS::Parser.parse(content)
|
22
|
+
|
23
|
+
parse_items(rss)
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_items(rss)
|
27
|
+
items = []
|
28
|
+
|
29
|
+
if rss.nil?
|
30
|
+
raise 'RSS object failed to parse, is it valid XML?'
|
31
|
+
end
|
32
|
+
|
33
|
+
rss.items.each do |item|
|
34
|
+
items << parse_item(item)
|
35
|
+
end
|
36
|
+
|
37
|
+
items
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_item(item)
|
41
|
+
CVE::Vulnerability.new({
|
42
|
+
:identifier => extract_cve_identifier(item.title),
|
43
|
+
:title => item.title,
|
44
|
+
:link => item.link,
|
45
|
+
:description => item.description,
|
46
|
+
:date => item.date
|
47
|
+
})
|
48
|
+
end
|
49
|
+
|
50
|
+
def extract_cve_identifier(title)
|
51
|
+
title.split(' ')[0]
|
52
|
+
end
|
53
|
+
|
54
|
+
def filter(results)
|
55
|
+
filtered_contents = []
|
56
|
+
|
57
|
+
results.each do |result|
|
58
|
+
result_ok = true
|
59
|
+
|
60
|
+
@filters.each do |filter|
|
61
|
+
result_ok = result_ok && !filter.filter(result)
|
62
|
+
end
|
63
|
+
|
64
|
+
filtered_contents << result if result_ok
|
65
|
+
end
|
66
|
+
|
67
|
+
filtered_contents
|
68
|
+
end
|
69
|
+
|
70
|
+
def inspect
|
71
|
+
"<CVE::Parser filters=#{@filters.inspect}>"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -1,62 +1,62 @@
|
|
1
|
-
module CVE
|
2
|
-
class Vulnerability
|
3
|
-
SOFTWARE_EXTRACT_REGEXP = Regexp.new('[(, ]([^(), ]+)')
|
4
|
-
|
5
|
-
def initialize(data)
|
6
|
-
unless data.instance_of?(Hash)
|
7
|
-
raise 'CVE Vulnerability needs to be initialized with a hash'
|
8
|
-
end
|
9
|
-
|
10
|
-
if malformed?(data)
|
11
|
-
raise 'CVE Vulnerability data is malformed'
|
12
|
-
end
|
13
|
-
|
14
|
-
@identifier = data[:identifier]
|
15
|
-
@date = data[:date]
|
16
|
-
@description = data[:description]
|
17
|
-
@link = data[:link]
|
18
|
-
@title = data[:title]
|
19
|
-
@affected_software = extract_software_from_title(data[:title])
|
20
|
-
end
|
21
|
-
|
22
|
-
attr_reader :identifier, :date, :description, :link, :title, :affected_software
|
23
|
-
|
24
|
-
def malformed?(data)
|
25
|
-
!(data.has_key?(:identifier) && data.has_key?(:date) && data.has_key?(:description) &&
|
26
|
-
data.has_key?(:link) && data.has_key?(:title))
|
27
|
-
end
|
28
|
-
|
29
|
-
def extract_software_from_title(title)
|
30
|
-
software = []
|
31
|
-
|
32
|
-
title.scan(SOFTWARE_EXTRACT_REGEXP) do |scan|
|
33
|
-
software << scan[0]
|
34
|
-
end
|
35
|
-
|
36
|
-
software.count == 0 ? nil : software
|
37
|
-
end
|
38
|
-
|
39
|
-
def affected_count
|
40
|
-
@affected_software.nil? ? 0 : @affected_software.count
|
41
|
-
end
|
42
|
-
|
43
|
-
def equal?(cve_item, strict=false)
|
44
|
-
return false unless cve_item.is_a?(Vulnerability)
|
45
|
-
|
46
|
-
if strict
|
47
|
-
return @identifier == cve_item.identifier && @link == cve_item.link && @date.utc.iso8601 == cve_item.date.utc.iso8601 &&
|
48
|
-
@title == cve_item.title && @description == cve_item.description
|
49
|
-
end
|
50
|
-
|
51
|
-
@identifier == cve_item.identifier && @link == cve_item.link
|
52
|
-
end
|
53
|
-
|
54
|
-
def to_s
|
55
|
-
"#{@title} - #{@link}"
|
56
|
-
end
|
57
|
-
|
58
|
-
def inspect
|
59
|
-
"#<CVE::Vulnerability id=#{@identifier} affected=#{affected_count}>"
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
1
|
+
module CVE
|
2
|
+
class Vulnerability
|
3
|
+
SOFTWARE_EXTRACT_REGEXP = Regexp.new('[(, ]([^(), ]+)')
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
unless data.instance_of?(Hash)
|
7
|
+
raise 'CVE Vulnerability needs to be initialized with a hash'
|
8
|
+
end
|
9
|
+
|
10
|
+
if malformed?(data)
|
11
|
+
raise 'CVE Vulnerability data is malformed'
|
12
|
+
end
|
13
|
+
|
14
|
+
@identifier = data[:identifier]
|
15
|
+
@date = data[:date]
|
16
|
+
@description = data[:description]
|
17
|
+
@link = data[:link]
|
18
|
+
@title = data[:title]
|
19
|
+
@affected_software = extract_software_from_title(data[:title])
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :identifier, :date, :description, :link, :title, :affected_software
|
23
|
+
|
24
|
+
def malformed?(data)
|
25
|
+
!(data.has_key?(:identifier) && data.has_key?(:date) && data.has_key?(:description) &&
|
26
|
+
data.has_key?(:link) && data.has_key?(:title))
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_software_from_title(title)
|
30
|
+
software = []
|
31
|
+
|
32
|
+
title.scan(SOFTWARE_EXTRACT_REGEXP) do |scan|
|
33
|
+
software << scan[0]
|
34
|
+
end
|
35
|
+
|
36
|
+
software.count == 0 ? nil : software
|
37
|
+
end
|
38
|
+
|
39
|
+
def affected_count
|
40
|
+
@affected_software.nil? ? 0 : @affected_software.count
|
41
|
+
end
|
42
|
+
|
43
|
+
def equal?(cve_item, strict=false)
|
44
|
+
return false unless cve_item.is_a?(Vulnerability)
|
45
|
+
|
46
|
+
if strict
|
47
|
+
return @identifier == cve_item.identifier && @link == cve_item.link && @date.utc.iso8601 == cve_item.date.utc.iso8601 &&
|
48
|
+
@title == cve_item.title && @description == cve_item.description
|
49
|
+
end
|
50
|
+
|
51
|
+
@identifier == cve_item.identifier && @link == cve_item.link
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#{@title} - #{@link}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect
|
59
|
+
"#<CVE::Vulnerability id=#{@identifier} affected=#{affected_count}>"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/spec/crawler_spec.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require_relative 'spec_helper'
|
2
|
-
|
3
|
-
describe CVE::Crawler do
|
4
|
-
it 'should crawl the analyzed file when constructed with the analyzed type' do
|
5
|
-
crawler = CVE::Crawler.new('analyzed', false, false)
|
6
|
-
|
7
|
-
expect(crawler.crawl_url).to equal(CVE::Crawler::DATA_FEED_ANALYZED)
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'should crawl the default file when constructed any non-analyzed type' do
|
11
|
-
crawler = CVE::Crawler.new('anything', false, false)
|
12
|
-
|
13
|
-
expect(crawler.crawl_url).to equal(CVE::Crawler::DATA_FEED_DEFAULT)
|
14
|
-
end
|
15
|
-
end
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe CVE::Crawler do
|
4
|
+
it 'should crawl the analyzed file when constructed with the analyzed type' do
|
5
|
+
crawler = CVE::Crawler.new('analyzed', false, false)
|
6
|
+
|
7
|
+
expect(crawler.crawl_url).to equal(CVE::Crawler::DATA_FEED_ANALYZED)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should crawl the default file when constructed any non-analyzed type' do
|
11
|
+
crawler = CVE::Crawler.new('anything', false, false)
|
12
|
+
|
13
|
+
expect(crawler.crawl_url).to equal(CVE::Crawler::DATA_FEED_DEFAULT)
|
14
|
+
end
|
15
|
+
end
|
data/spec/parser_spec.rb
CHANGED
@@ -1,38 +1,38 @@
|
|
1
|
-
require_relative 'spec_helper'
|
2
|
-
|
3
|
-
describe CVE::Parser do
|
4
|
-
crawler_mock = CrawlerResultMock.new
|
5
|
-
parser = CVE::Parser.new
|
6
|
-
|
7
|
-
it 'should parse a full xml body with three items and return an array of CVE items' do
|
8
|
-
result = parser.parse(crawler_mock.xml_full)
|
9
|
-
|
10
|
-
expect(result).to be_a(Array)
|
11
|
-
expect(result.length).to eq(3)
|
12
|
-
|
13
|
-
all_cve_items = true
|
14
|
-
result.each do |value|
|
15
|
-
all_cve_items = all_cve_items && value.is_a?(CVE::Vulnerability)
|
16
|
-
end
|
17
|
-
|
18
|
-
expect(all_cve_items).to equal(true)
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should raise an error when non-xml is passed' do
|
22
|
-
expect{ parser.parse('Not xml') }.to raise_error(RuntimeError)
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'should extract the CVE identifier from a title' do
|
26
|
-
title = VulnerabilityMock.new.title
|
27
|
-
|
28
|
-
expect(parser.extract_cve_identifier(title)).to match(/^CVE\-\d{4}\-\d+$/)
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'should accept a custom filter' do
|
32
|
-
CVE::Parser.new(CVE::Filter.new)
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'should accept an array of custom filters' do
|
36
|
-
CVE::Parser.new([CVE::Filter.new, CVE::Filter.new])
|
37
|
-
end
|
38
|
-
end
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe CVE::Parser do
|
4
|
+
crawler_mock = CrawlerResultMock.new
|
5
|
+
parser = CVE::Parser.new
|
6
|
+
|
7
|
+
it 'should parse a full xml body with three items and return an array of CVE items' do
|
8
|
+
result = parser.parse(crawler_mock.xml_full)
|
9
|
+
|
10
|
+
expect(result).to be_a(Array)
|
11
|
+
expect(result.length).to eq(3)
|
12
|
+
|
13
|
+
all_cve_items = true
|
14
|
+
result.each do |value|
|
15
|
+
all_cve_items = all_cve_items && value.is_a?(CVE::Vulnerability)
|
16
|
+
end
|
17
|
+
|
18
|
+
expect(all_cve_items).to equal(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should raise an error when non-xml is passed' do
|
22
|
+
expect{ parser.parse('Not xml') }.to raise_error(RuntimeError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should extract the CVE identifier from a title' do
|
26
|
+
title = VulnerabilityMock.new.title
|
27
|
+
|
28
|
+
expect(parser.extract_cve_identifier(title)).to match(/^CVE\-\d{4}\-\d+$/)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should accept a custom filter' do
|
32
|
+
CVE::Parser.new(CVE::Filter.new)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should accept an array of custom filters' do
|
36
|
+
CVE::Parser.new([CVE::Filter.new, CVE::Filter.new])
|
37
|
+
end
|
38
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,84 +1,84 @@
|
|
1
|
-
require 'rspec'
|
2
|
-
require_relative File.join('..', 'lib', 'cve_crawler'
|
3
|
-
|
4
|
-
class CrawlerResultMock
|
5
|
-
def xml_full
|
6
|
-
<<-eos
|
7
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
8
|
-
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
9
|
-
<channel rdf:about="http://web.nvd.nist.gov/view/vuln/search">
|
10
|
-
<title>National Vulnerability Database</title>
|
11
|
-
<link>http://web.nvd.nist.gov/view/vuln/search</link>
|
12
|
-
<description>This feed contains the most recent CVE cyber vulnerabilities published within the National Vulnerability Database.</description>
|
13
|
-
<items>
|
14
|
-
<rdf:Seq>
|
15
|
-
<rdf:li rdf:resource="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4476" />
|
16
|
-
<rdf:li rdf:resource="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4500" />
|
17
|
-
<rdf:li rdf:resource="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4501" />
|
18
|
-
</rdf:Seq>
|
19
|
-
</items>
|
20
|
-
<dc:date>2015-09-27T04:50:00Z</dc:date>
|
21
|
-
<dc:language>en-us</dc:language>
|
22
|
-
<dc:rights>This material is not copywritten and may be freely used, however, attribution is requested.</dc:rights>
|
23
|
-
</channel>
|
24
|
-
<item rdf:about="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4476">
|
25
|
-
<title>CVE-2015-4476 (firefox)</title>
|
26
|
-
<link>http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4476</link>
|
27
|
-
<description>Mozilla Firefox before 41.0 on Android allows user-assisted remote attackers to spoof address-bar attributes by leveraging lack of navigation after a paste of a URL with a nonstandard scheme, as demonstrated by spoofing an SSL attribute.</description>
|
28
|
-
<dc:date>2015-09-24T04:59:00Z</dc:date>
|
29
|
-
</item>
|
30
|
-
<item rdf:about="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4500">
|
31
|
-
<title>CVE-2015-4500 (firefox, firefox_esr)</title>
|
32
|
-
<link>http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4500</link>
|
33
|
-
<description>Multiple unspecified vulnerabilities in the browser engine in Mozilla Firefox before 41.0 and Firefox ESR 38.x before 38.3 allow remote attackers to cause a denial of service (memory corruption and application crash) or possibly execute arbitrary code via unknown vectors.</description>
|
34
|
-
<dc:date>2015-09-24T04:59:02Z</dc:date>
|
35
|
-
</item>
|
36
|
-
<item rdf:about="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4501">
|
37
|
-
<title>CVE-2015-4501 (firefox)</title>
|
38
|
-
<link>http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4501</link>
|
39
|
-
<description>Multiple unspecified vulnerabilities in the browser engine in Mozilla Firefox before 41.0 allow remote attackers to cause a denial of service (memory corruption and application crash) or possibly execute arbitrary code via unknown vectors.</description>
|
40
|
-
<dc:date>2015-09-24T04:59:03Z</dc:date>
|
41
|
-
</item>
|
42
|
-
</rdf:RDF>
|
43
|
-
eos
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
class VulnerabilityMock
|
48
|
-
def self.generate
|
49
|
-
mocker = VulnerabilityMock.new
|
50
|
-
id = mocker.identifier
|
51
|
-
|
52
|
-
CVE::Vulnerability.new({
|
53
|
-
:identifier => id,
|
54
|
-
:title => mocker.title(id),
|
55
|
-
:link => mocker.link(id),
|
56
|
-
:description => mocker.description,
|
57
|
-
:date => mocker.date
|
58
|
-
})
|
59
|
-
end
|
60
|
-
|
61
|
-
def identifier
|
62
|
-
year = Time.now.year
|
63
|
-
"CVE-#{rand(2000..year)}-#{rand(0..9999)}"
|
64
|
-
end
|
65
|
-
|
66
|
-
def title(id=nil)
|
67
|
-
(id || identifier) + ' (' + ['Lorem Ipsum', 'Some vulnerability', 'Your favourite software',
|
68
|
-
'Firefox', 'Chromium', 'Thunderbird'].sample.split('').shuffle.join + ')'
|
69
|
-
end
|
70
|
-
|
71
|
-
def link(id=nil)
|
72
|
-
'http://web.nvd.nist.gov/view/vuln/detail?vulnId=' + (id || identifier)
|
73
|
-
end
|
74
|
-
|
75
|
-
def description
|
76
|
-
['Hakuna Matata', 'What a wonderful phrase', 'Ain\'t no passing phrase', 'It means no worries',
|
77
|
-
'for the rest of your days', 'It\'s our problem-free philosophy', 'Yeah. That\'s our motto',
|
78
|
-
'What\'s a motto?'].sample.split('').shuffle.join
|
79
|
-
end
|
80
|
-
|
81
|
-
def date
|
82
|
-
Time.now
|
83
|
-
end
|
84
|
-
end
|
1
|
+
require 'rspec'
|
2
|
+
require_relative File.join('..', 'lib', 'cve_crawler')
|
3
|
+
|
4
|
+
class CrawlerResultMock
|
5
|
+
def xml_full
|
6
|
+
<<-eos
|
7
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
8
|
+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
9
|
+
<channel rdf:about="http://web.nvd.nist.gov/view/vuln/search">
|
10
|
+
<title>National Vulnerability Database</title>
|
11
|
+
<link>http://web.nvd.nist.gov/view/vuln/search</link>
|
12
|
+
<description>This feed contains the most recent CVE cyber vulnerabilities published within the National Vulnerability Database.</description>
|
13
|
+
<items>
|
14
|
+
<rdf:Seq>
|
15
|
+
<rdf:li rdf:resource="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4476" />
|
16
|
+
<rdf:li rdf:resource="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4500" />
|
17
|
+
<rdf:li rdf:resource="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4501" />
|
18
|
+
</rdf:Seq>
|
19
|
+
</items>
|
20
|
+
<dc:date>2015-09-27T04:50:00Z</dc:date>
|
21
|
+
<dc:language>en-us</dc:language>
|
22
|
+
<dc:rights>This material is not copywritten and may be freely used, however, attribution is requested.</dc:rights>
|
23
|
+
</channel>
|
24
|
+
<item rdf:about="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4476">
|
25
|
+
<title>CVE-2015-4476 (firefox)</title>
|
26
|
+
<link>http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4476</link>
|
27
|
+
<description>Mozilla Firefox before 41.0 on Android allows user-assisted remote attackers to spoof address-bar attributes by leveraging lack of navigation after a paste of a URL with a nonstandard scheme, as demonstrated by spoofing an SSL attribute.</description>
|
28
|
+
<dc:date>2015-09-24T04:59:00Z</dc:date>
|
29
|
+
</item>
|
30
|
+
<item rdf:about="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4500">
|
31
|
+
<title>CVE-2015-4500 (firefox, firefox_esr)</title>
|
32
|
+
<link>http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4500</link>
|
33
|
+
<description>Multiple unspecified vulnerabilities in the browser engine in Mozilla Firefox before 41.0 and Firefox ESR 38.x before 38.3 allow remote attackers to cause a denial of service (memory corruption and application crash) or possibly execute arbitrary code via unknown vectors.</description>
|
34
|
+
<dc:date>2015-09-24T04:59:02Z</dc:date>
|
35
|
+
</item>
|
36
|
+
<item rdf:about="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4501">
|
37
|
+
<title>CVE-2015-4501 (firefox)</title>
|
38
|
+
<link>http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-4501</link>
|
39
|
+
<description>Multiple unspecified vulnerabilities in the browser engine in Mozilla Firefox before 41.0 allow remote attackers to cause a denial of service (memory corruption and application crash) or possibly execute arbitrary code via unknown vectors.</description>
|
40
|
+
<dc:date>2015-09-24T04:59:03Z</dc:date>
|
41
|
+
</item>
|
42
|
+
</rdf:RDF>
|
43
|
+
eos
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class VulnerabilityMock
|
48
|
+
def self.generate
|
49
|
+
mocker = VulnerabilityMock.new
|
50
|
+
id = mocker.identifier
|
51
|
+
|
52
|
+
CVE::Vulnerability.new({
|
53
|
+
:identifier => id,
|
54
|
+
:title => mocker.title(id),
|
55
|
+
:link => mocker.link(id),
|
56
|
+
:description => mocker.description,
|
57
|
+
:date => mocker.date
|
58
|
+
})
|
59
|
+
end
|
60
|
+
|
61
|
+
def identifier
|
62
|
+
year = Time.now.year
|
63
|
+
"CVE-#{rand(2000..year)}-#{rand(0..9999)}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def title(id=nil)
|
67
|
+
(id || identifier) + ' (' + ['Lorem Ipsum', 'Some vulnerability', 'Your favourite software',
|
68
|
+
'Firefox', 'Chromium', 'Thunderbird'].sample.split('').shuffle.join + ')'
|
69
|
+
end
|
70
|
+
|
71
|
+
def link(id=nil)
|
72
|
+
'http://web.nvd.nist.gov/view/vuln/detail?vulnId=' + (id || identifier)
|
73
|
+
end
|
74
|
+
|
75
|
+
def description
|
76
|
+
['Hakuna Matata', 'What a wonderful phrase', 'Ain\'t no passing phrase', 'It means no worries',
|
77
|
+
'for the rest of your days', 'It\'s our problem-free philosophy', 'Yeah. That\'s our motto',
|
78
|
+
'What\'s a motto?'].sample.split('').shuffle.join
|
79
|
+
end
|
80
|
+
|
81
|
+
def date
|
82
|
+
Time.now
|
83
|
+
end
|
84
|
+
end
|
data/spec/vulnerability_spec.rb
CHANGED
@@ -1,137 +1,137 @@
|
|
1
|
-
require_relative 'spec_helper'
|
2
|
-
|
3
|
-
describe CVE::Vulnerability do
|
4
|
-
mocker = VulnerabilityMock.new
|
5
|
-
cve_vul_obj = VulnerabilityMock.generate
|
6
|
-
|
7
|
-
it 'should not error when passing a valid hash' do
|
8
|
-
id = mocker.identifier
|
9
|
-
|
10
|
-
item = CVE::Vulnerability.new({
|
11
|
-
:identifier => id,
|
12
|
-
:title => mocker.title(id),
|
13
|
-
:link => mocker.link(id),
|
14
|
-
:description => mocker.description,
|
15
|
-
:date => mocker.date
|
16
|
-
})
|
17
|
-
|
18
|
-
expect(item).to be_a(CVE::Vulnerability)
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should compare to a CVE with the same identifier and link' do
|
22
|
-
id = mocker.identifier
|
23
|
-
link = mocker.link(id)
|
24
|
-
item = CVE::Vulnerability.new({
|
25
|
-
:identifier => id,
|
26
|
-
:title => mocker.title(id),
|
27
|
-
:link => link,
|
28
|
-
:description => mocker.description,
|
29
|
-
:date => mocker.date
|
30
|
-
})
|
31
|
-
|
32
|
-
item_roughly_identical = CVE::Vulnerability.new({
|
33
|
-
:identifier => id,
|
34
|
-
:title => mocker.title(id),
|
35
|
-
:link => link,
|
36
|
-
:description => mocker.description,
|
37
|
-
:date => mocker.date
|
38
|
-
})
|
39
|
-
|
40
|
-
expect(item.equal?(item_roughly_identical, false)).to equal(true)
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'should compare strictly to an identical CVE' do
|
44
|
-
id = mocker.identifier
|
45
|
-
item = CVE::Vulnerability.new({
|
46
|
-
:identifier => id,
|
47
|
-
:title => mocker.title(id),
|
48
|
-
:link => mocker.link(id),
|
49
|
-
:description => mocker.description,
|
50
|
-
:date => mocker.date
|
51
|
-
})
|
52
|
-
|
53
|
-
item_identical = item.clone
|
54
|
-
|
55
|
-
expect(item.equal?(item_identical, true)).to equal(true)
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'should fail compare strictly to an unidentical CVE with the same ID' do
|
59
|
-
id = mocker.identifier
|
60
|
-
item = CVE::Vulnerability.new({
|
61
|
-
:identifier => id,
|
62
|
-
:title => mocker.title(id),
|
63
|
-
:link => mocker.link(id),
|
64
|
-
:description => mocker.description,
|
65
|
-
:date => mocker.date
|
66
|
-
})
|
67
|
-
|
68
|
-
item_unidentical = CVE::Vulnerability.new({
|
69
|
-
:identifier => id,
|
70
|
-
:title => mocker.title(id),
|
71
|
-
:link => mocker.link(id),
|
72
|
-
:description => mocker.description,
|
73
|
-
:date => mocker.date
|
74
|
-
})
|
75
|
-
|
76
|
-
expect(item.equal?(item_unidentical, true)).to equal(false)
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'should fail compare to an unidentical CVE' do
|
80
|
-
id = mocker.identifier
|
81
|
-
item = CVE::Vulnerability.new({
|
82
|
-
:identifier => id,
|
83
|
-
:title => mocker.title(id),
|
84
|
-
:link => mocker.link(id),
|
85
|
-
:description => mocker.description,
|
86
|
-
:date => mocker.date
|
87
|
-
})
|
88
|
-
|
89
|
-
id = mocker.identifier
|
90
|
-
item_unidentical = CVE::Vulnerability.new({
|
91
|
-
:identifier => id,
|
92
|
-
:title => mocker.title(id),
|
93
|
-
:link => mocker.link(id),
|
94
|
-
:description => mocker.description,
|
95
|
-
:date => mocker.date
|
96
|
-
})
|
97
|
-
|
98
|
-
expect(item.equal?(item_unidentical, false)).to equal(false)
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'should be able to extract a single software from the title' do
|
102
|
-
title = 'CVE-2015-123 (firefox)'
|
103
|
-
extract = cve_vul_obj.extract_software_from_title(title)
|
104
|
-
|
105
|
-
expect(extract.count).to eq(1)
|
106
|
-
expect(extract[0]).to eq('firefox')
|
107
|
-
end
|
108
|
-
|
109
|
-
it 'should be able to extract many software from the title' do
|
110
|
-
title = 'CVE-2015-123 (firefox, firefox_esr, flash)'
|
111
|
-
extract = cve_vul_obj.extract_software_from_title(title)
|
112
|
-
|
113
|
-
expect(extract.count).to eq(3)
|
114
|
-
expect(extract[0]).to eq('firefox')
|
115
|
-
expect(extract[1]).to eq('firefox_esr')
|
116
|
-
expect(extract[2]).to eq('flash')
|
117
|
-
end
|
118
|
-
|
119
|
-
it 'should return nil for affected software when not included' do
|
120
|
-
title = 'CVE-2015-123'
|
121
|
-
extract = cve_vul_obj.extract_software_from_title(title)
|
122
|
-
|
123
|
-
expect(extract).to eq(nil)
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'should error when not passing a hash' do
|
127
|
-
expect{ CVE::Vulnerability.new('') }.to raise_error(RuntimeError)
|
128
|
-
end
|
129
|
-
|
130
|
-
it 'should error when passing an empty hash' do
|
131
|
-
expect{ CVE::Vulnerability.new({}) }.to raise_error(RuntimeError)
|
132
|
-
end
|
133
|
-
|
134
|
-
it 'should error when passing a hash with missing keys' do
|
135
|
-
expect{ CVE::Vulnerability.new({:identifier => 'abc', :title => 'title'}) }.to raise_error(RuntimeError)
|
136
|
-
end
|
137
|
-
end
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe CVE::Vulnerability do
|
4
|
+
mocker = VulnerabilityMock.new
|
5
|
+
cve_vul_obj = VulnerabilityMock.generate
|
6
|
+
|
7
|
+
it 'should not error when passing a valid hash' do
|
8
|
+
id = mocker.identifier
|
9
|
+
|
10
|
+
item = CVE::Vulnerability.new({
|
11
|
+
:identifier => id,
|
12
|
+
:title => mocker.title(id),
|
13
|
+
:link => mocker.link(id),
|
14
|
+
:description => mocker.description,
|
15
|
+
:date => mocker.date
|
16
|
+
})
|
17
|
+
|
18
|
+
expect(item).to be_a(CVE::Vulnerability)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should compare to a CVE with the same identifier and link' do
|
22
|
+
id = mocker.identifier
|
23
|
+
link = mocker.link(id)
|
24
|
+
item = CVE::Vulnerability.new({
|
25
|
+
:identifier => id,
|
26
|
+
:title => mocker.title(id),
|
27
|
+
:link => link,
|
28
|
+
:description => mocker.description,
|
29
|
+
:date => mocker.date
|
30
|
+
})
|
31
|
+
|
32
|
+
item_roughly_identical = CVE::Vulnerability.new({
|
33
|
+
:identifier => id,
|
34
|
+
:title => mocker.title(id),
|
35
|
+
:link => link,
|
36
|
+
:description => mocker.description,
|
37
|
+
:date => mocker.date
|
38
|
+
})
|
39
|
+
|
40
|
+
expect(item.equal?(item_roughly_identical, false)).to equal(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should compare strictly to an identical CVE' do
|
44
|
+
id = mocker.identifier
|
45
|
+
item = CVE::Vulnerability.new({
|
46
|
+
:identifier => id,
|
47
|
+
:title => mocker.title(id),
|
48
|
+
:link => mocker.link(id),
|
49
|
+
:description => mocker.description,
|
50
|
+
:date => mocker.date
|
51
|
+
})
|
52
|
+
|
53
|
+
item_identical = item.clone
|
54
|
+
|
55
|
+
expect(item.equal?(item_identical, true)).to equal(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should fail compare strictly to an unidentical CVE with the same ID' do
|
59
|
+
id = mocker.identifier
|
60
|
+
item = CVE::Vulnerability.new({
|
61
|
+
:identifier => id,
|
62
|
+
:title => mocker.title(id),
|
63
|
+
:link => mocker.link(id),
|
64
|
+
:description => mocker.description,
|
65
|
+
:date => mocker.date
|
66
|
+
})
|
67
|
+
|
68
|
+
item_unidentical = CVE::Vulnerability.new({
|
69
|
+
:identifier => id,
|
70
|
+
:title => mocker.title(id),
|
71
|
+
:link => mocker.link(id),
|
72
|
+
:description => mocker.description,
|
73
|
+
:date => mocker.date
|
74
|
+
})
|
75
|
+
|
76
|
+
expect(item.equal?(item_unidentical, true)).to equal(false)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should fail compare to an unidentical CVE' do
|
80
|
+
id = mocker.identifier
|
81
|
+
item = CVE::Vulnerability.new({
|
82
|
+
:identifier => id,
|
83
|
+
:title => mocker.title(id),
|
84
|
+
:link => mocker.link(id),
|
85
|
+
:description => mocker.description,
|
86
|
+
:date => mocker.date
|
87
|
+
})
|
88
|
+
|
89
|
+
id = mocker.identifier
|
90
|
+
item_unidentical = CVE::Vulnerability.new({
|
91
|
+
:identifier => id,
|
92
|
+
:title => mocker.title(id),
|
93
|
+
:link => mocker.link(id),
|
94
|
+
:description => mocker.description,
|
95
|
+
:date => mocker.date
|
96
|
+
})
|
97
|
+
|
98
|
+
expect(item.equal?(item_unidentical, false)).to equal(false)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should be able to extract a single software from the title' do
|
102
|
+
title = 'CVE-2015-123 (firefox)'
|
103
|
+
extract = cve_vul_obj.extract_software_from_title(title)
|
104
|
+
|
105
|
+
expect(extract.count).to eq(1)
|
106
|
+
expect(extract[0]).to eq('firefox')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should be able to extract many software from the title' do
|
110
|
+
title = 'CVE-2015-123 (firefox, firefox_esr, flash)'
|
111
|
+
extract = cve_vul_obj.extract_software_from_title(title)
|
112
|
+
|
113
|
+
expect(extract.count).to eq(3)
|
114
|
+
expect(extract[0]).to eq('firefox')
|
115
|
+
expect(extract[1]).to eq('firefox_esr')
|
116
|
+
expect(extract[2]).to eq('flash')
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should return nil for affected software when not included' do
|
120
|
+
title = 'CVE-2015-123'
|
121
|
+
extract = cve_vul_obj.extract_software_from_title(title)
|
122
|
+
|
123
|
+
expect(extract).to eq(nil)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should error when not passing a hash' do
|
127
|
+
expect{ CVE::Vulnerability.new('') }.to raise_error(RuntimeError)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should error when passing an empty hash' do
|
131
|
+
expect{ CVE::Vulnerability.new({}) }.to raise_error(RuntimeError)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should error when passing a hash with missing keys' do
|
135
|
+
expect{ CVE::Vulnerability.new({:identifier => 'abc', :title => 'title'}) }.to raise_error(RuntimeError)
|
136
|
+
end
|
137
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cve_crawler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jos Ahrens
|
@@ -47,7 +47,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
47
|
version: '0'
|
48
48
|
requirements: []
|
49
49
|
rubyforge_project:
|
50
|
-
rubygems_version: 2.
|
50
|
+
rubygems_version: 2.2.2
|
51
51
|
signing_key:
|
52
52
|
specification_version: 4
|
53
53
|
summary: CVE Crawler
|