#!/usr/bin/perl # Based on the Debian Secure Testing 'updatelist' script # Copyright Joey Hess and Stefan Fritsch # Licensed under the GPL v2 or later # This script contains the NIST NVD Feed Parser # David Kyger 7-6-07 # License - GNU GPL # Modifications by Robert Buchholz # GPL2 or later. use XML::Simple qw(:strict); my $html=shift; my $nvdxml=shift; my $our_list=shift; my %cves; my %listedcves; # This parses the recent NVD XML entries # These sometimes contain items not yet in MITRE $xml = new XML::Simple; $data = $xml->XMLin( $nvdxml, KeyAttr => { cve => 'entry' }, ForceArray => [ 'cve', 'ref' ] ); my $cve; my $e; foreach $e ( @{ $data->{entry} } ) { $cve=$e->{name}; $cves{$cve}{cve}=$cve; $listedcves{$cve}=1; $desc=""; if(ref($e->{desc}->{descript}) eq 'ARRAY') { foreach $d ( @{ $e->{desc}->{descript} } ) { $desc = $desc . $d->{content} . "\n"; } } else { $desc=$e->{desc}->{descript}->{content}; } $desc =~ s/\n/ /g; if ($desc =~ /\*\*\s+REJECT\s+\*\*/) { $cves{$cve}{rejected}=1; } if ($cves{$cve}{rejected} != 1) { # We could use $e->{severity} here # Truncate desc if (length($desc) > 71) { my $lastspace = rindex($desc, " ", 71); $desc = substr($desc, 0, $lastspace) . " ..."; } $cves{$cve}{description}="($desc)"; } } # Parse all CVEs listed in the MITRE master copy # This includes rejected and reserved names open (HTML, "<$html") || die "$html: $!\n"; while () { if (m!Name:\s+(CVE-\d+-\d+)!) { $cve=$1; $cves{$cve}{cve}=$cve; $listedcves{$cve}=1; } if (m!\*\*\s+RESERVED\s+\*\*!) { if (!$cves{$cve}{description}) { $cves{$cve}{reserved}=1; } } if (m!\*\*\s+REJECT\s+\*\*!) { $cves{$cve}{rejected}=1; } if (m!Description:\s*
\s*(.*)! && ! m!\*\*\s+RESERVED\s+\*\*! && ! m!\*\*\s+REJECT\s+\*\*!) { my $desc; $desc=$1; if (! length $desc) { $desc=; chomp $desc; } $cves{$cve}{description}="($desc ...)"; } } close HTML; my $stopped=0; my @out; sub docve { my $cve=shift; push @out, "$cve".(length $cves{$cve}{description} ? " ".$cves{$cve}{description} : "")."\n"; if ($cves{$cve}{reserved}) { push @out, "\tRESERVED\n"; } if ($cves{$cve}{rejected}) { push @out, "\tREJECTED\n"; } if (scalar @{$cves{$cve}{xref}} > 0) { push @out, "\t{".join(" ", @{$cves{$cve}{xref}})."}\n"; } if ($cves{$cve}{notes}) { foreach (@{$cves{$cve}{notes}}) { push @out, "\t$_\n"; } } if (! $cves{$cve}{reserved} && ! $cves{$cve}{rejected} && ! $cves{$cve}{notes} && ! $stopped) { if ($cve =~ /^CVE-199|^CVE-200[0123456]/) { push @out, "\tNOT-FOR-US: Data pre-dating the Security Tracker\n"; } else { push @out, "\tTODO: check\n"; } } delete $cves{$cve}; } open (IN, "<$our_list") || die "$our_list: $!\n"; my $cve; while () { chomp; if (/^(CVE-(?:[0-9]+|[A-Z]+)-(?:[0-9]+|[A-Z]+))\s*(.*)/) { my $desc=$2; docve($cve) if $cve; $cve=$1; if (length $desc && $desc !~ /^\(.*\)$/ && (! exists $cves{$cve}{description} || ! length $cves{$cve}{description})) { $cves{$cve}{description}=$desc; } } elsif (/^\s+(RESERVED|REJECTED)\s*$/) { # skip it } elsif (/^\s+NOTE: covered by DT?SA.*/) { # skip it (old form) } elsif (/^\s+{\s*(.+?)\s*}/) { my @xrefs=split('\s+', $1); push @{$cves{$cve}{xref}}, grep(!/^DT?SA/, @xrefs); } elsif (/^\s+(.*)/ && $cve) { push @{$cves{$cve}{notes}}, $1; } elsif (/^STOP/) { docve($cve) if $cve; push @out, "$_\n"; $stopped=1; $cve=''; } else { docve($cve) if $cve; push @out, "$_\n" if length $_; $cve=''; } } close IN; docve($cve) if $cve; foreach my $cve (reverse sort { $cves{$a}{cve} cmp $cves{$b}{cve} } keys %cves) { next unless $listedcves{$cve}; print $cve.(length $cves{$cve}{description} ? " ".$cves{$cve}{description} : "")."\n"; if ($cves{$cve}{reserved}) { print "\tRESERVED\n"; } if ($cves{$cve}{rejected}) { print "\tREJECTED\n"; } if (scalar @{$cves{$cve}{xref}} > 0) { print "\t{".join(" ", @{$cves{$cve}{xref}})."}\n"; } if (!$cves{$cve}{reserved} || $cves{$cve}{rejected} ) { print "\tTODO: check\n"; } } print @out;