line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
2
|
|
|
2
|
|
134480
|
use v5.20; |
|
2
|
|
|
|
|
19
|
|
2
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
65
|
|
3
|
2
|
|
|
2
|
|
11
|
use feature 'signatures'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
260
|
|
4
|
2
|
|
|
2
|
|
14
|
no warnings qw(experimental::signatures); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
99
|
|
5
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
11
|
use Carp (); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
47
|
|
7
|
2
|
|
|
2
|
|
1377
|
use JSON (); |
|
2
|
|
|
|
|
25140
|
|
|
2
|
|
|
|
|
53
|
|
8
|
2
|
|
|
2
|
|
1382
|
use HTTP::Tiny; |
|
2
|
|
|
|
|
100470
|
|
|
2
|
|
|
|
|
2354
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = '0.0.3'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
package Net::NVD { |
13
|
2
|
|
|
2
|
1
|
950
|
sub new ($class, %args) { |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
5
|
|
14
|
|
|
|
|
|
|
Carp::croak('"format" must be "cve" or "complete"') |
15
|
2
|
0
|
33
|
|
|
11
|
if exists $args{format} && $args{format} ne 'complete' && $args{format} ne 'cve'; |
|
|
|
33
|
|
|
|
|
16
|
|
|
|
|
|
|
return bless { |
17
|
|
|
|
|
|
|
ua => _build_user_agent($args{api_key}), |
18
|
2
|
|
50
|
|
|
10
|
format => $args{format} // 'cve', |
19
|
|
|
|
|
|
|
}, $class; |
20
|
|
|
|
|
|
|
} |
21
|
|
|
|
|
|
|
|
22
|
1
|
|
|
1
|
1
|
145
|
sub get ($self, $cve_id) { |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
17
|
|
23
|
1
|
|
|
|
|
5
|
my ($single) = $self->search( cve_id => $cve_id ); |
24
|
1
|
|
|
|
|
5
|
return $single; |
25
|
|
|
|
|
|
|
} |
26
|
|
|
|
|
|
|
|
27
|
2
|
|
|
2
|
1
|
1723
|
sub search ($self, %params) { |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
2
|
|
28
|
2
|
|
|
|
|
14
|
my $res = $self->{ua}->request('GET', 'https://services.nvd.nist.gov/rest/json/cves/2.0?' . _build_url_params($self->{ua}, %params)); |
29
|
2
|
50
|
|
|
|
228
|
if ($res->{success}) { |
30
|
2
|
|
|
|
|
28
|
my $json = JSON::decode_json($res->{content}); |
31
|
|
|
|
|
|
|
return $self->{format} eq 'complete' |
32
|
2
|
50
|
|
|
|
23
|
? $json : map $_->{cve}, $json->{vulnerabilities}->@*; |
33
|
|
|
|
|
|
|
} |
34
|
0
|
|
0
|
|
|
0
|
Carp::carp($res->{headers}{message} // "error querying NVD service ($res->{status})"); |
35
|
0
|
|
|
|
|
0
|
return (); |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
|
38
|
2
|
|
|
2
|
|
4
|
sub _build_user_agent($api_key) { |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
3
|
|
39
|
2
|
50
|
|
|
|
18
|
return HTTP::Tiny->new( |
40
|
|
|
|
|
|
|
agent => __PACKAGE__ . '/' . $VERSION, |
41
|
|
|
|
|
|
|
verify_SSL => 1, |
42
|
|
|
|
|
|
|
($api_key ? (default_headers => { apiKey => $api_key }) : ()), |
43
|
|
|
|
|
|
|
); |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
2
|
|
|
2
|
|
3
|
sub _build_url_params ($ua, %params) { |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
3
|
|
47
|
2
|
|
|
|
|
11
|
my $iso8061 = qr{\A\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:[\+\-]\d{2}:\d{2})?\z}; |
48
|
2
|
|
|
|
|
105
|
my %translation = ( |
49
|
|
|
|
|
|
|
cpe_name => { name => 'cpeName' , validation => qr{\Acpe:2.3(\:[^*:]+){4}(\:[^:]+){7}\z} }, |
50
|
|
|
|
|
|
|
cve_id => { name => 'cveId' , validation => qr{\ACVE\-[0-9]{4}\-[0-9]+\z} }, |
51
|
|
|
|
|
|
|
cvssv2_metrics => { name => 'cvssV2Metrics' , validation => qr{.} }, |
52
|
|
|
|
|
|
|
cvssV2Severity => { name => 'cvssV2Severity' , validation => qr{\A(?:LOW|MEDIUM|HIGH)\z} }, |
53
|
|
|
|
|
|
|
cvssv3_metrics => { name => 'cvssV3Metrics' , validation => qr{.} }, |
54
|
|
|
|
|
|
|
cvssv3_severity => { name => 'cvssV3Severity' , validation => qr{\A(?:LOW|MEDIUM|HIGH|CRITICAL)\z} }, |
55
|
|
|
|
|
|
|
cwe_id => { name => 'cweId' , validation => qr{\ACWE\-\d+\z} }, |
56
|
|
|
|
|
|
|
keyword_search => { name => 'keywordSearch' , validation => qr{\A.+\z} }, |
57
|
|
|
|
|
|
|
last_mod_start_date => { name => 'lastModStartDate' , validation => $iso8061 }, |
58
|
|
|
|
|
|
|
last_mod_end_date => { name => 'lastModEndDate' , validation => $iso8061 }, |
59
|
|
|
|
|
|
|
pub_start_date => { name => 'pubStartDate' , validation => $iso8061 }, |
60
|
|
|
|
|
|
|
pub_end_date => { name => 'pubEndDate' , validation => $iso8061 }, |
61
|
|
|
|
|
|
|
results_per_page => { name => 'resultsPerPage' , validation => qr{\A\d+\z} }, |
62
|
|
|
|
|
|
|
start_index => { name => 'startIndex' , validation => qr{\A\d+\z} }, |
63
|
|
|
|
|
|
|
source_identifier => { name => 'sourceIdentifier' , validation => qr{.} }, |
64
|
|
|
|
|
|
|
version_end => { name => 'versionEnd' , validation => qr{.} }, |
65
|
|
|
|
|
|
|
version_end_type => { name => 'versionEndType' , validation => qr{\A(?:including|excluding)\z} }, |
66
|
|
|
|
|
|
|
version_start => { name => 'versionStart' , validation => qr{.} }, |
67
|
|
|
|
|
|
|
version_start_type => { name => 'versionStartType' , validation => qr{\A(?:including|excluding)\z} }, |
68
|
|
|
|
|
|
|
virtual_match_string => { name => 'virtualMatchString', validation => qr{.} }, |
69
|
|
|
|
|
|
|
has_cert_alerts => { name => 'hasCertAlerts' , boolean => 1 }, |
70
|
|
|
|
|
|
|
has_cert_notes => { name => 'hasCertNotes' , boolean => 1 }, |
71
|
|
|
|
|
|
|
has_kev => { name => 'hasKev' , boolean => 1 }, |
72
|
|
|
|
|
|
|
has_oval => { name => 'hasOval' , boolean => 1 }, |
73
|
|
|
|
|
|
|
is_vulnerable => { name => 'isVulnerable' , boolean => 1 }, |
74
|
|
|
|
|
|
|
keyword_exact_match => { name => 'keywordExactMatch' , boolean => 1 }, |
75
|
|
|
|
|
|
|
no_rejected => { name => 'noRejected' , boolean => 1 }, |
76
|
|
|
|
|
|
|
); |
77
|
|
|
|
|
|
|
|
78
|
2
|
|
|
|
|
8
|
my @params; |
79
|
|
|
|
|
|
|
my %translated; |
80
|
2
|
|
|
|
|
7
|
foreach my $p (keys %params) { |
81
|
4
|
50
|
|
|
|
10
|
Carp::croak("'$p' is not a valid search parameter") unless exists $translation{$p}; |
82
|
4
|
100
|
|
|
|
10
|
if ($translation{$p}{boolean}) { |
83
|
1
|
50
|
|
|
|
8
|
push @params, $translation{$p}{name} if delete $params{$p}; |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
else { |
86
|
3
|
50
|
|
|
|
29
|
Carp::croak("invalid value '$params{$p}' for '$p'") unless $params{$p} =~ $translation{$p}{validation}; |
87
|
3
|
|
|
|
|
10
|
$translated{$translation{$p}{name}} = $params{$p}; |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
} |
90
|
2
|
|
|
|
|
10
|
return join('&', @params, $ua->www_form_urlencode(\%translated)); |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
}; |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
1; |
95
|
|
|
|
|
|
|
__END__ |