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