1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
|
#!/pro/bin/perl
use 5.014002;
use warnings;
our $VERSION = "0.04 - 20240408";
our $CMD = $0 =~ s{.*/}{}r;
sub usage {
my $err = shift and select STDERR;
say "usage: $CMD [-f] [-d] CVE ...";
say " -f --full Return full report (default: summary)";
say " -d --dump Full data dump of JSON structure";
say " Will use Data::Peek if available,";
say " otherwise Data::Dumper";
say " -j --json Dump as json";
say " -J --json-pp Dump as json (formatted)";
say " -Q --json-jq Dump as json (formatted by jq)";
say " -c --csv Dump as CSV (NYI)";
exit $err;
} # usage
use Net::CVE;
use Data::Peek;
use Data::Dumper;
use JSON::MaybeXS;
use List::Util qw( first );
use Getopt::Long qw(:config bundling);
GetOptions (
"help|?" => sub { usage (0); },
"V|version" => sub { say "$CMD [$VERSION]"; exit 0; },
"f|full!" => \ my $opt_f,
"d|dump!" => \ my $opt_d,
"j|json!" => \ my $opt_j,
"J|json-pp!" => \ my $opt_J,
"Q|json-jq|jq!" => \ my $opt_Q,
"v|verbose:1" => \(my $opt_v = 0),
) or usage (1);
my $cr = Net::CVE->new;
my %cve;
first { $_ !~ m/^(?:cve-)?[0-9]{4}-[0-9]+$/i } @ARGV and
die "Not all CVE's are of acceptable formate CVE-9999-99999\n";
$cve{$_} = $opt_f ? $cr->data ($_) : $cr->summary ($_) for @ARGV;
if ($opt_d) {
eval "local \$_; require Data::Peek;";
if ($@) {
print STDERR Data::Dumper->Dump (\%cve);
}
else {
print STDERR Data::Peek::DDumper (\%cve);
}
exit 0;
}
if ($opt_j || $opt_J || $opt_Q) {
$opt_Q and open STDOUT, "|-", "jq";
if ($opt_J) {
print JSON::MaybeXS->new (utf8 => 1, pretty => 1)->encode (\%cve);
}
else {
say encode_json (\%cve);
}
exit 0;
}
my @cve = sort keys %cve;
foreach my $cve (@cve) {
my $r = $cve{$cve};
if ($opt_f) {
say $cve;
say " State : ", $r->{cveMetadata}{state};
say " Published : ", $r->{cveMetadata}{datePublished};
my $cc = $r->{containers}{cna} or next;
say " Title : ", $cc->{title} // "-none-";
say " Public : ", $cc->{datePublic} // "-unknown-";
if (my $md = $cc->{providerMetadata}) {
printf " Provider : %s:%s\n", $md->{shortName}, $md->{orgId};
}
my $lead = " " x 19;
foreach my $rd (@{$cc->{descriptions} || []}) {
printf "%16s : %s\n", $rd->{lang}, $rd->{value} =~ s/\n\K/$lead/gr;
}
foreach my $rr (@{$cc->{references} || []}) {
my $trim = "References";
for (grep { length }
$rr->{name},
(join ", " => @{$rr->{tags} || []}),
$rr->{url}) {
printf " %-15s : %s\n", $trim, $_;
$trim = "";
}
}
foreach my $pt (@{$cc->{problemTypes} || []}) {
foreach my $ptd (@{$pt->{descriptions} || []}) {
printf " Problem %7s : %-12s %s\n",
$ptd->{lang}, $ptd->{type}, $ptd->{description};
}
}
foreach my $af (@{$cc->{affected} || []}) {
($af->{vendor} || "n/a") eq "n/a" &&
($af->{product} || "n/a") eq "n/a" and next;
say " Affected : ", $af->{vendor}, " : ", $af->{product};
if (my $p = $af->{platforms}) {
say " Platforms : ", join ", " => @$p;
}
foreach my $v (@{$af->{versions} || []}) {
my ($vs, $vv, $vvt) = delete @{$v}{qw( status version versionType )};
$vv && $vv eq "n/a" and $vvt ||= "n/a";
$vs && $vs eq "affected" && $vvt eq "n/a" and next;
printf " Versions : %-12s %s (%s)\n", $vs, $vv, $vvt;
foreach my $vc (sort keys %$v) {
printf "%16s : %12s %s\n", "", $vc, $v->{$vc};
}
}
say " CPE : ", $_ for @{$af->{cpes} || []};
}
foreach my $m (@{$cc->{metrics} || []}) {
say " Metric : ", $m->{format};
if (my $v31 = $m->{cvssV3_1}) {
printf " Severity %5s : %3d:%-8s %s\n", $v31->{version},
$v31->{baseScore}, $v31->{baseSeverity}, $v31->{vectorString};
}
foreach my $s (@{$m->{scenarios} || []}) {
printf " Scenario%7s : %s\n", $s->{lang}, $s->{value};
}
}
next;
}
# Print summary
printf "%-20s %-9s %4s %-25.25s %s\n",
map { $_ // "" } @{$r}{qw( id severity score problem description )};
}
|