File Coverage

blib/lib/Metabrik/Database/Nvd.pm
Criterion Covered Total %
statement 9 187 4.8
branch 0 88 0.0
condition 0 35 0.0
subroutine 3 14 21.4
pod 1 10 10.0
total 13 334 3.8


line stmt bran cond sub pod time code
1             #
2             # $Id$
3             #
4             # database::nvd Brik
5             #
6             package Metabrik::Database::Nvd;
7 1     1   670 use strict;
  1         3  
  1         29  
8 1     1   6 use warnings;
  1         2  
  1         29  
9              
10 1     1   5 use base qw(Metabrik::Client::Www);
  1         4  
  1         2382  
11              
12             sub brik_properties {
13             return {
14 0     0 1   revision => '$Revision$',
15             tags => [ qw(unstable cve cpe nist) ],
16             author => 'GomoR ',
17             license => 'http://opensource.org/licenses/BSD-3-Clause',
18             attributes => {
19             datadir => [ qw(datadir) ],
20             loaded_xml => [ qw(loaded_xml) ],
21             },
22             commands => {
23             update => [ qw(recent|modified|others|all) ],
24             load => [ qw(recent|modified|others|all year|OPTIONAL) ],
25             search_all => [ ],
26             cve_search => [ qw(pattern) ],
27             cpe_search => [ qw(pattern) ],
28             get_cve_xml => [ qw(cve_id) ],
29             to_hash => [ qw(entry_xml) ],
30             to_string => [ qw(entry_hash) ],
31             print => [ qw(entry_hash) ],
32             },
33             require_modules => {
34             'Metabrik::File::Xml' => [ ],
35             'Metabrik::File::Compress' => [ ],
36             },
37             };
38             }
39              
40             # http://nvd.nist.gov/download.cfm
41             # nvdcve-2.0-Modified.xml.gz includes all recently published and recently updated vulnerabilities.
42             # nvdcve-2.0-Recent.xml.gz includes all recently published vulnerabilities.
43             # nvdcve-2.0-2002.xml includes vulnerabilities prior to and including 2002.
44             my $resource = {
45             uri => 'http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-NAME.xml.gz',
46             gz => 'nvdcve-2.0-NAME.xml.gz',
47             xml => 'nvdcve-2.0-NAME.xml',
48             };
49              
50             sub update {
51 0     0 0   my $self = shift;
52 0           my ($type) = @_;
53              
54 0   0       $type ||= 'recent';
55              
56 0 0 0       if ($type ne 'recent'
      0        
      0        
57             && $type ne 'modified'
58             && $type ne 'others'
59             && $type ne 'all') {
60 0           return $self->log->error($self->brik_help_run('update'));
61             }
62              
63 0 0         if ($type eq 'all') {
64 0           my @output = ();
65 0           push @output, $self->update('recent');
66 0           push @output, $self->update('modified');
67 0           push @output, @{$self->update('others')};
  0            
68 0           return \@output;
69             }
70              
71 0           my $datadir = $self->datadir;
72              
73 0 0         my $fc = Metabrik::File::Compress->new_from_brik_init($self) or return;
74              
75 0 0         if ($type eq 'recent') {
    0          
    0          
76 0           (my $uri = $resource->{uri}) =~ s/NAME/Recent/;
77 0           (my $gz = $resource->{gz}) =~ s/NAME/Recent/;
78 0           (my $xml = $resource->{xml}) =~ s/NAME/Recent/;
79 0           my $output = "$datadir/$gz";
80 0 0         $self->mirror($uri, $gz, $datadir) or return;
81 0 0         my $files = $fc->uncompress($output, $xml, $datadir) or return;
82 0           return $files->[0];
83             }
84             elsif ($type eq 'modified') {
85 0           (my $uri = $resource->{uri}) =~ s/NAME/Modified/;
86 0           (my $gz = $resource->{gz}) =~ s/NAME/Modified/;
87 0           (my $xml = $resource->{xml}) =~ s/NAME/Modified/;
88 0           my $output = "$datadir/$gz";
89 0 0         $self->mirror($uri, $gz, $datadir) or return;
90 0 0         my $files = $fc->uncompress($output, $xml, $datadir) or return;
91 0           return $files->[0];
92             }
93             elsif ($type eq 'others') {
94 0           my @output = ();
95 0           for my $year (2002..2015) {
96 0           (my $uri = $resource->{uri}) =~ s/NAME/$year/;
97 0           (my $gz = $resource->{gz}) =~ s/NAME/$year/;
98 0           (my $xml = $resource->{xml}) =~ s/NAME/$year/;
99 0           my $output = "$datadir/$gz";
100 0 0         $self->mirror($uri, $gz, $datadir) or return;
101 0 0         my $files = $fc->uncompress($output, $xml, $datadir) or next;
102 0           push @output, @$files;
103             }
104 0           return \@output;
105             }
106              
107             # Error
108 0           return;
109             }
110              
111             sub _merge_xml {
112 0     0     my $self = shift;
113 0           my ($old, $new, $type) = @_;
114              
115             # Return $new, nothing to merge
116 0 0         if (! defined($old)) {
117 0           return $new;
118             }
119              
120 0           $self->log->verbose("_merge_xml: merging XML");
121              
122 0           for my $k (keys %{$new->{entry}}) {
  0            
123             # Check if it already exists
124 0 0         if (exists $old->{entry}->{$k}) {
125             # It exists. Do we load recent or modified data?
126             # If so, it takes precedence, and we overwrite it.
127 0 0 0       if ($type eq 'recent' || $type eq 'modified') {
128 0           $old->{entry}->{$k} = $new->{entry}->{$k};
129             }
130             }
131             # We add it directly if it does not exist yet.
132             else {
133 0           $old->{entry}->{$k} = $new->{entry}->{$k};
134             }
135             }
136              
137             # Return merged data into $old
138 0           return $old;
139             }
140              
141             sub load {
142 0     0 0   my $self = shift;
143 0           my ($type, $year) = @_;
144              
145 0   0       $type ||= 'recent';
146              
147 0 0 0       if ($type ne 'recent'
      0        
      0        
148             && $type ne 'modified'
149             && $type ne 'others'
150             && $type ne 'all') {
151 0           return $self->log->error($self->brik_help_run('load'));
152             }
153              
154 0 0         if ($type eq 'all') {
155 0 0         $self->load('recent') or return;
156 0 0         $self->load('modified') or return;
157 0           return $self->load('others');
158             }
159              
160 0           my $datadir = $self->datadir;
161              
162 0 0         my $fx = Metabrik::File::Xml->new_from_brik_init($self) or return;
163              
164 0           my $old = $self->loaded_xml;
165              
166 0 0         if ($type eq 'recent') {
    0          
    0          
167 0           (my $xml = $resource->{xml}) =~ s/NAME/Recent/;
168 0           my $file = $datadir.'/'.$xml;
169              
170 0 0         my $new = $fx->read($file) or return;
171              
172 0           my $merged = $self->_merge_xml($old, $new, $type);
173              
174 0           return $self->loaded_xml($merged);
175             }
176             elsif ($type eq 'modified') {
177 0           (my $xml = $resource->{xml}) =~ s/NAME/Modified/;
178 0           my $file = $datadir.'/'.$xml;
179              
180 0 0         my $new = $fx->read($file) or return;
181              
182 0           my $merged = $self->_merge_xml($old, $new, $type);
183              
184 0           return $self->loaded_xml($merged);
185             }
186             elsif ($type eq 'others') {
187 0           my $merged = $old;
188 0 0         my @years = defined($year) ? ( $year ) : ( 2002..2015 );
189 0           for my $year (@years) {
190 0           (my $xml = $resource->{xml}) =~ s/NAME/$year/;
191 0           my $file = $datadir.'/'.$xml;
192              
193 0           my $new = $fx->read($file);
194              
195 0           $merged = $self->_merge_xml($merged, $new, $type);
196             }
197              
198 0           return $self->loaded_xml($merged);
199             }
200              
201             # Error
202 0           return;
203             }
204              
205             sub to_string {
206 0     0 0   my $self = shift;
207 0           my ($h) = @_;
208              
209 0           my @buf = ();
210 0           push @buf, "CVE: ".$h->{cve_id};
211 0           push @buf, "CWE: ".$h->{cwe_id};
212 0           push @buf, "Published datetime: ".$h->{published_datetime};
213 0           push @buf, "Last modified datetime: ".$h->{last_modified_datetime};
214 0           push @buf, "URL: ".$h->{url};
215 0   0       push @buf, "Summary: ".($h->{summary} || '(undef)');
216 0           push @buf, "Vuln product:";
217 0           for my $vuln_product (@{$h->{vuln_product}}) {
  0            
218 0           push @buf, " $vuln_product";
219             }
220              
221 0           return \@buf;
222             }
223              
224             sub print {
225 0     0 0   my $self = shift;
226 0           my ($h) = @_;
227              
228 0 0         $self->brik_help_run_undef_arg('print', $h) or return;
229 0 0         $self->brik_help_run_invalid_arg('print', $h, 'HASH') or return;
230              
231 0           my $lines = $self->to_string($h);
232 0           for my $line (@$lines) {
233 0           print $line."\n";
234             }
235              
236 0           return 1;
237             }
238              
239             sub to_hash {
240 0     0 0   my $self = shift;
241 0           my ($h) = @_;
242              
243 0 0         $self->brik_help_run_undef_arg('to_hash', $h) or return;
244 0 0         $self->brik_help_run_invalid_arg('to_hash', $h, 'HASH') or return;
245              
246 0           my $cve = $h->{'vuln:cve-id'};
247              
248 0           my $published_datetime = $h->{'vuln:published-datetime'};
249 0           my $last_modified_datetime = $h->{'vuln:last-modified-datetime'};
250 0           my $summary = $h->{'vuln:summary'};
251 0           my $cvss = $h->{'vuln:cvss'}{'cvss:base_metrics'}{'cvss:score'};
252 0           my $vector = $h->{'vuln:cvss'}{'cvss:base_metrics'}{'cvss:access-vector'};
253 0           my $authentication = $h->{'vuln:cvss'}{'cvss:base_metrics'}{'cvss:authentication'};
254 0   0       my $cwe_id = $h->{'vuln:cwe'}->{id} || '(undef)';
255 0           $cwe_id =~ s/^CWE-//;
256              
257 0           my $vuln_product = [];
258 0 0 0       if (defined($h->{'vuln:vulnerable-software-list'})
259             && defined($h->{'vuln:vulnerable-software-list'}->{'vuln:product'})) {
260 0           my $e = $h->{'vuln:vulnerable-software-list'}->{'vuln:product'};
261 0 0         if (ref($e) eq 'ARRAY') {
262 0           $vuln_product = $e;
263             }
264             else {
265 0           $vuln_product = [ $e ];
266             }
267             }
268              
269             return {
270 0           cve_id => $cve,
271             cvss => $cvss,
272             access_vector => $vector,
273             authentication => $authentication,
274             url => 'http://web.nvd.nist.gov/view/vuln/detail?vulnId='.$cve,
275             published_datetime => $published_datetime,
276             last_modified_datetime => $last_modified_datetime,
277             summary => $summary,
278             cwe_id => $cwe_id,
279             vuln_product => $vuln_product,
280             };
281             }
282              
283             sub search_all {
284 0     0 0   my $self = shift;
285              
286 0           my $xml = $self->loaded_xml;
287 0 0         $self->brik_help_run_undef_arg('load', $xml) or return;
288              
289 0           my $entries = $xml->{entry};
290 0 0         if (! defined($entries)) {
291 0           return $self->log->error("search_all: no entry found");
292             }
293              
294 0           my @entries = ();
295 0           for my $cve (keys %$entries) {
296 0           my $this = $self->to_hash($entries->{$cve});
297 0           push @entries, $this;
298             }
299              
300 0           return \@entries;
301             }
302              
303             sub cve_search {
304 0     0 0   my $self = shift;
305 0           my ($pattern) = @_;
306              
307 0           my $xml = $self->loaded_xml;
308 0 0         $self->brik_help_run_undef_arg('load', $xml) or return;
309 0 0         $self->brik_help_run_undef_arg('cve_search', $pattern) or return;
310              
311 0           my $entries = $xml->{entry};
312 0 0         if (! defined($entries)) {
313 0           return $self->log->error("cve_search: no entry found");
314             }
315              
316 0           my @entries = ();
317 0           for my $cve (keys %$entries) {
318 0           my $this = $self->to_hash($entries->{$cve});
319              
320 0 0 0       if ($this->{cve_id} =~ /$pattern/ || $this->{summary} =~ /$pattern/i) {
321 0           push @entries, $this;
322 0           $self->print($this);
323             }
324             }
325              
326 0           return \@entries;
327             }
328              
329             sub cpe_search {
330 0     0 0   my $self = shift;
331 0           my ($cpe) = @_;
332              
333 0           my $xml = $self->loaded_xml;
334 0 0         $self->brik_help_run_undef_arg('load', $xml) or return;
335 0 0         $self->brik_help_run_undef_arg('cpe_search', $cpe) or return;
336              
337 0           my $entries = $xml->{entry};
338 0 0         if (! defined($entries)) {
339 0           return $self->log->error("cpe_search: no entry found");
340             }
341              
342 0           my @entries = ();
343 0           for my $cve (keys %$entries) {
344 0           my $this = $self->to_hash($entries->{$cve});
345              
346 0           for my $vuln_product (@{$this->{vuln_product}}) {
  0            
347 0 0         if ($vuln_product =~ /$cpe/i) {
348 0           push @entries, $this;
349 0           $self->print($this);
350 0           last;
351             }
352             }
353             }
354              
355 0           return \@entries;
356             }
357              
358             sub get_cve_xml {
359 0     0 0   my $self = shift;
360 0           my ($cve_id) = @_;
361              
362 0           my $xml = $self->loaded_xml;
363 0 0         $self->brik_help_run_undef_arg('load', $xml) or return;
364              
365 0 0         if (defined($xml->{entry})) {
366 0           return $xml->{entry}->{$cve_id};
367             }
368              
369 0           return;
370             }
371              
372             1;
373              
374             __END__