#!/usr/bin/perl ############################################################################### ## ## Functions for downloading and parsing Debian Security Advisories (DSAs) ## Currently also includes some FreeBSD Advisory parsing (which sucks) ## ############################################################################### use strict; use warnings; #use Storable; # persistant storage use LWP::UserAgent; #use Config::Auto; # libconfig-auto-perl use Time::ParseDate; # libtime-modules-perl #use IO::Uncompress::Bunzip2 qw(bunzip2 $Bunzip2Error); #use IO::Uncompress::Gunzip qw(gunzip $GunzipError); #use Digest::MD5 qw(md5_hex); use POSIX qw(mktime); #use File::Path qw(make_path); use HTML::Parser; use feature "switch"; # Track renamed packages here, easy but manual. We tried to automate it # using package metadata for distribution upgrades but that really sucked. sub unifySrcName { my $name = shift; given ($name) { ## TODO: it should map to the most recent version, not unversioned ## TODO: we can partially automate this.. ## -> make all lower-case ## -> replace -X.Y version numbers by highest encounter(?) ## -> handle special cases like xfree86 when ("proftp-dfsg") { $name = "proftp" }; when ("mozilla-firefox") { $name = "iceweasel" }; when ("mozilla-thunderbird") { $name = "icedove" }; when ("squid3") { $name = "squid" }; when ("squid/squid3") { $name = "squid" }; when ("tk8.3") { $name = "tk8.4" }; when ("tk8.4") { $name = "tk8.4" }; when ("xpdf-i") { $name = "xpdf" }; when ("zope2.10/zope2.9") { $name = "zope2.7" }; when ("zope-cmfplone") { $name = "zope2.7" }; when ("zope-ldapuserfolder") { $name = "zope2.7" }; when ("librmagick-ruby") { $name = "ruby-rmagick" }; when ("libcompass-ruby") { $name = "ruby-compass" }; when ("bio-ruby") { $name = "ruby-bio" }; when ("request-tracker3.4") { $name = "request-tracker3.8" }; when ("request-tracker3.6") { $name = "request-tracker3.8" }; when ("perl-5.005") { $name = "perl" }; when ("otrs2") { $name = "otrs" }; when ("openldap2.3") { $name = "openldap" }; when ("openldap2") { $name = "openldap" }; when ("libreoffice") { $name = "openoffice.org" }; when ("nsd3") { $name = "nsd" }; when ("network-manager/network-manager-applet") { $name = "network-manager" }; when ("nagios3") { $name = "nagios" }; when ("nagios2") { $name = "nagios" }; when ("mysql-dfsg-4.1") { $name = "mysql" }; when ("mysql-dfsg-5.0") { $name = "mysql" }; when ("mysql-dfsg") { $name = "mysql" }; when ("linux-2.6.24") { $name = "linux-2.6" }; when ("linux-kernel-alpha") { $name = "linux-2.4" }; when ("linux-kernel-i386") { $name = "linux-2.4" }; when ("libmusicbrainz-2.0") { $name = "libmusicbrainz3" }; when ("libmusicbrainz-2.1") { $name = "libmusicbrainz3" }; when ("libgtop1") { $name = "libgtop2" }; when ("libgd1") { $name = "libgd2" }; when ("libast1") { $name = "libast" }; when ("libmozjs0d") { $name = "libast" }; when (/^kernel-source-2.2.*/) { $name = "linux-2.2" }; when (/^kernel-patch-2.2.*/) { $name = "linux-2.2" }; when ("kernel") { $name = "linux-2.4" }; when (/^kernel-source-2.4.*/) { $name = "linux-2.4" }; when (/^kernel-image-2.2.*/) { $name = "linux-2.2" }; when (/^kernel-image-2.4.*/) { $name = "linux-2.4" }; when (/^kernel-patch-2.4.*/) { $name = "linux-2.4" }; when ("kernel-patch-benh") { $name = "linux-2.4" }; when ("kernel-patch-vserver") { $name = "linux-2.4" }; when (/^kernel-source-2.6.*/) { $name = "linux-2.6" }; when ("gnutls11") { $name = "gnutls26" }; when ("gnutls13") { $name = "gnutls26" }; when ("gallery2") { $name = "gallery" }; when ("firebird2") { $name = "firebird2.5" }; when ("firebird2.0") { $name = "firebird2.5" }; when ("firebird2.1") { $name = "firebird2.5" }; when ("fltk1.1") { $name = "fltk1.3" }; when ("fox1.4") { $name = "fox1.6" }; when ("exim-tls") { $name = "exim" }; when ("exim4") { $name = "exim" }; when ("epic4") { $name = "epic" }; when ("drupal6") { $name = "drupal" }; when ("dhcp") { $name = "dhcp3" }; when ("cyrus-sasl") { $name = "cyrus-sasl2" }; when (/^cyrus-imapd.*/) { $name = "cyrus-imapd" }; when (/^kolab-cyrus-imapd.*/) { $name = "cyrus-imapd" }; when ("cfengine") { $name = "cfengine2" }; when ("bind") { $name = "bind9" }; when ("apache") { $name = "apache2" }; when ("horde2") { $name = "horde3" }; when ("mediawiki1.7") { $name = "mediawiki" }; when ("ffmpeg-debian") { $name = "ffmpeg" }; when ("xserver-xorg") { $name = "xorg-server" }; when ("xfree86-1") { $name = "xorg-server" }; when ("xfree86v3") { $name = "xorg-server" }; when ("xfree86") { $name = "xorg-server" }; when ("xfree86") { $name = "xorg-server" }; when ("xorg") { $name = "xorg-server" }; when ("typo3") { $name = "typo3-src" }; when ("lvm10") { $name = "lvm2" }; when ("cupsys") { $name = "cups" }; when ("ethereal") { $name = "wireshark" }; when ("libboost1.42") { $name = "libboost1.46" }; when ("cinelerra-cv") { $name = "cinelerra" }; when ("mplayer-dmo") { $name = "mplayer" }; when ("libcap") { $name = "libgda2" }; when ("xkb-data-legacy") { $name = "xkeyboard-config" }; when ("boost-defaults") { $name = "boost" }; when ("xen-3") { $name = "xen" }; when ("kde-icons-gorilla") { $name = "kde-icons-korilla" }; when ("kde4libs") { $name = "kdelibs" }; when ("libcgi-application-extra-plugin-bundle-perl") { $name = "libcgi-application-plugins-perl"}; when (/^openssl\d$/) { $name = "openssl" }; when (/^tomcat\d/) { $name = "tomcat7" }; when (/^tomcat\d.\d$/) { $name = "tomcat7" }; when (/^libgda\d/) { $name = "libgda4" }; when (/^readline\d/) { $name = "readline6" }; when (/^libwnck\d/) { $name = "libwnck" }; when (/^xerces-c\d/) { $name = "xerces-c" }; when (/^libticalcs\d/) { $name = "libticals" }; when (/^libtifiles\d/) { $name = "libtifiles" }; when (/^db\d.\d$/) { $name = "db4.8" }; when (/^gcc-.*/) { $name = "gcc" }; when (/^automake\d+.*/) { $name = "automake" }; when (/^sun-java\d/) { $name = "sun-java6" }; when (/^open-jdk\d/) { $name = "open-jdk7" }; when (/^mbrola-es\d/) { $name = "mbrola-es" }; when (/^mgltools-.*/) { $name = "mgltools" }; when (/^coin\d$/) { $name = "coin" }; when (/^adobereader-\.*/) { $name = "adobereader" }; when (/^picon-\.*/) { $name = "picon" }; when (/^nvidia-graphics-drivers\.*/) { $name = "nvidia-graphics-drivers" }; when (/^boost\d\.\d\d/) { $name = "boost" }; when (/^llvm-\d.\d/) { $name = "llvm" }; when (/^octave\d.\d/) { $name = "octave" }; when (/^libjibx\d.\d-java/) { $name = "libjibx-java" }; when (/^emacs2\d/) { $name = "emacs23" }; when (/^emacs2\d-non-dfsg/) { $name = "emacs23" }; when (/^libupnp\d/) { $name = "libupnp" }; when (/^python\d.\d/) { $name = "python3.2" }; when (/^postgresql-\d.\d/) { $name = "postgresql-9.0" }; when (/^ruby\d.\d/) { $name = "ruby1.9" }; when (/^php\d/) { $name = "php5" }; when (/^PHP\d/) { $name = "php5" }; }; return $name; } ## Should this advisory be skipped? sub blacklistedDSA { my $dsa_id = shift or die "Advisory blacklist: no id given.."; ## check if DSA is blacklisted.. my @id_blacklist = ("DSA-1975", "DSA-2360"); grep ($_ eq $dsa_id, @id_blacklist) and return "true"; return; # FALSE } ## Static map to correct errors in DSAs ## Return fixed list of CVE IDs or 0 to skip DSA sub fixDSAquirks { my $dsa_id = shift; my $dsa_state = shift; my @new_names = @{@$dsa_state[0]}; my $new_date = @$dsa_state[1]; my @new_cves = @{@$dsa_state[2]}; ## most of these have dupe/wrong CVE referenced given ($dsa_id) { when ($_ eq "085") { @new_cves = ("CVE-2001-1562", "LOCAL-03/04/05", "LOCAL-08/24/08"); } when ($_ eq "745") { @new_cves = ("CVE-2005-1921", "CVE-2005-2106", "CVE-2005-1921"); } when ($_ eq "1095") { @new_cves = ("CVE-2006-0747", "CVE-2006-1861", "CVE-2006-2661"); } when ($_ eq "1284") { @new_cves = ("CVE-2007-1320", "CVE-2007-1321", "CVE-2007-1322", "CVE-2007-2893", "CVE-2007-1366"); } when ($_ eq "1502") { @new_cves = ("CVE-2007-2821", "CVE-2007-3238", "CVE-2008-0193", "CVE-2008-0194"); } when ($_ eq "1706") { @new_cves = ("CVE-2009-0135", "CVE-2009-0136"); } when ($_ eq "1757") { @new_cves = ("CVE-2007-2383", "CVE-2008-7220", "CVE-2009-1208"); } when ($_ eq "1896") { @new_cves = ("CVE-2009-3474", "CVE-2009-3475", "CVE-2009-3476"); } when ($_ eq "1896") { @new_cves = ("CVE-2009-3474", "CVE-2009-3475", "CVE-2009-3476"); } when ($_ eq "1931") { @new_cves = ("CVE-2009-0689", "CVE-2009-2463"); } when ($_ eq "1989") { @new_cves = ("CVE-2010-0789"); } when ($_ eq "1941") { @new_cves = ("CVE-2009-0755", "CVE-2009-3903", "CVE-2009-3904", "CVE-2009-3905", "CVE-2009-3606", "CVE-2009-3607", "CVE-2009-3608", "CVE-2009-3909", "CVE-2009-3938"); } when ($_ eq "2004") { @new_cves = ("CVE-2010-0787", "CVE-2010-0547"); } when ($_ eq "2008") { @new_cves = ("LOCAL-02/23/10", "LOCAL-02/23/10", "LOCAL-02/23/10", "LOCAL-02/23/10"); } when ($_ eq "2043") { @new_cves = ("CVE-2010-2062"); } when ($_ eq "2044") { @new_cves = ("CVE-2010-2062"); } when ($_ eq "2056") { @new_cves = ("CVE-2010-2155", "CVE-2009-4882"); } when ($_ eq "2092") { @new_cves = ("CVE-2010-1625", "CVE-2010-1448", "CVE-2009-4497"); } when ($_ eq "2098") { @new_cves = ("CVE-2010-3659", "CVE-2010-3660", "CVE-2010-3661", "CVE-2010-3662", "CVE-2010-3663", "CVE-2010-3664", "CVE-2010-3665", "CVE-2010-3666", "CVE-2010-3667", "CVE-2010-3668", "CVE-2010-3669", "CVE-2010-3670", "CVE-2010-3671", "CVE-2010-3672", "CVE-2010-3673", "CVE-2010-3674"); } when ($_ eq "2103") { @new_cves = ("CVE-2010-3076"); } when ($_ eq "2218") { @new_cves = ("CVE-2011-1684"); } when ($_ eq "2229") { @new_cves = ("CVE-2005-4494", "CVE-2006-0517", "CVE-2006-0518", "CVE-2006-0519", "CVE-2006-0625", "CVE-2006-0626", "CVE-2006-1295", "CVE-2006-1702", "CVE-2007-4525", "CVE-2008-5812", "CVE-2008-5813", "CVE-2009-3041"); } when ($_ eq "2261") { @new_cves = ("CVE-2009-4078", "CVE-2009-4079", "CVE-2009-4059", "LOCAL-12/30/10", "LOCAL-12/30/10"); } when ($_ eq "2262") { @new_cves = ("LOCAL-03/01/11", "LOCAL-03/01/11", "LOCAL-03/01/11", "LOCAL-03/01/11", "LOCAL-05/18/11", "LOCAL-05/18/11"); } when ($_ eq "2286") { @new_names = ("phpmyadmin"); } when ($_ eq "2289") { @new_cves = ( "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11", "LOCAL-07/27/11"); } }; return (\@new_names, $new_date, \@new_cves); } ## Fetch DSA from debian archive. Can't use tracker since dates are missing. ## DSA started counting in November 2000. We'll simply bruteforce which DSA ## was in which year and start in 2000 til current. sub fetchDSA { my $dsa_id = shift; my $base_url = shift; my $year= 2000; my $current_year = (gmtime())[5] + 1900; my $dsa; trace " Fetching DSA-$dsa_id\n"; given ($dsa_id) { when ($_ >= 2140) { $year=2011; } when ($_ >= 1965) { $year=2010; } when ($_ >= 1694) { $year=2009; } when ($_ >= 1443) { $year=2008; } when ($_ >= 1245) { $year=2007; } when ($_ >= 929) { $year=2006; } when ($_ >= 622) { $year=2005; } when ($_ >= 406) { $year=2004; } when ($_ >= 220) { $year=2003; } when ($_ >= 96) { $year=2002; } when ($_ >= 11) { $year=2001; } }; until (defined $dsa) { #my $url= $config->{"dsa_base_url"} . $year . "/dsa-" . sprintf("%03d",$dsa_id); my $url= $base_url . $year . "/dsa-" . sprintf("%03d",$dsa_id); $dsa = get $url; return $dsa if (defined $dsa); if ($year >= $current_year) { note " Could not fetch DSA $dsa_id\n"; return ""; } $year++; } } # fetch FSAs from freeside. returns 3-tuple (("kfreebsd-8"), $date, @CVE) if # kernel-fsa and () if not our @__FSAINDEX; our $__FSAIDXTIMESTAMP = 0; sub fetchFSA($$) { my $id = shift; our $fsa_base_url = shift; sub getFSAurls() { $\ = "\n"; #print $__FSAIDXTIMESTAMP; #print time; return @__FSAINDEX if $__FSAIDXTIMESTAMP + 60 >= time; #print "not caching"; $__FSAIDXTIMESTAMP = time(); our @fsaurls; sub start { my ($self, $tagname, $attr, $attrseq, $origtext) = @_; if ($tagname eq 'a') { if ($attr->{"href"} =~ /^FreeBSD-SA-/ && $attr->{"href"} ne "FreeBSD-SA-06%3a14-amd.txt") { # wtf? what douchbag added this as a FreeBSD-SA? push @fsaurls, $fsa_base_url . "/" . $attr->{href}; } } } sub getIDfromFSA ($) { $_ = shift; /FreeBSD-SA-(\d\d)%3a(\d\d)/; my $year = $1; my $num = $2; $year += 2000 if $year < 90; $year *= 1000; return $year + $num; } my $p = HTML::Parser->new; $p->handler(start => \&start); #$p->parse(get ($fsabase)); $p->parse(get ($_)) foreach @fsaurls; $p->eof; @__FSAINDEX = sort { getIDfromFSA $a > getIDfromFSA $b } @fsaurls; return @__FSAINDEX; } my @fsas = getFSAurls(); # die "unexisting FSA" if $id > $#fsas; return "" if ($id > $#fsas); return get $fsas[$id]; } ## Try to find new DSAs by iteration, return table of DSAs to process sub checkDSAs { my $state = shift; my $config = shift; my %dsatable; my $next_dsa = $state->{"next_adv"}; trace "Checking for new DSAs..\n"; if ($next_dsa < $config->{"first_dsa"}) { note "Cache was deleted, starting at DSA $next_dsa\n"; $next_dsa = $config->{"first_dsa"}; } $next_dsa++ if (blacklistedDSA("DSA-".$next_dsa)); my $dsa = fetchDSA($next_dsa, $config->{"dsa_base_url"}); until ($dsa eq "") { debug " Got DSA-$next_dsa\n"; $dsa =~ s/.*//si; $dsa =~ s/.*//si; $dsatable{$next_dsa} = $dsa; $next_dsa++; $next_dsa++ if (blacklistedDSA("DSA-".$next_dsa)); $dsa = fetchDSA($next_dsa, $config->{"dsa_base_url"}); } my $next_fsa = $state->{"next_fsa"}; if ($next_fsa < $config->{"first_fsa"}) { note "Cache was deleted, starting at FSA $next_fsa\n"; $next_fsa = $config->{"first_fsa"}; } my $fsa = fetchFSA($next_fsa, $config->{"fsa_base_url"}); until ($fsa eq "") { debug " Got FSA-$next_fsa\n"; $dsatable{"FSA-$next_fsa"} = $fsa; $next_fsa++; $fsa = fetchFSA($next_fsa, $config->{"fsa_base_url"}); } $state->{"next_adv"} = $next_dsa; $state->{"next_fsa"} = $next_fsa; return \%dsatable; } ## Parse DSA html data and return array ## (src-pkg-name date (CVE-id)*) sub parseDSAhtml { my $dsa = shift; my @dsa_names; my @dsa_CVEs; my @tmp; my %tmp; # Date Reported -> $dsa_date $dsa =~ /
Date\ Reported:<\/dt>\s+
(\d+)\s+(\w+)\s+(\d+)<\/dd>/s; my $dsa_date = parsedate("$1-$2-$3"); die " Unable to extract date from DSA" unless (defined $dsa_date); # Affected Packages -> @dsa_names $dsa =~ /
Affected\ Packages:<\/dt>(.*)<\/dd>\s+
Vulnerable:/s; die "Unable to find src package in DSA" unless defined $1; @tmp = split(/, /, $1); foreach my $tmpstr (@tmp) { if ($tmpstr =~ /(.*)<\/a>/s) { #push(@dsa_names, $1); #push(@dsa_names, unifySrcName($1)); $tmp{unifySrcName($1)} = 1; } } @dsa_names = keys %tmp; # Security database references (CVEs) -> @dsa_CVEs $dsa =~ /
Security\ database\ references:<\/dt>\s+
(.*)<\/dd>\s+
More\ information:/s; die " Unable to find CVEs in DSA" unless defined $1; @tmp = split(/(,|
)/, $1); foreach my $tmpstr (@tmp) { if ($tmpstr =~ /
(.*)<\/a>/s) { push(@dsa_CVEs, $1); } } return (\@dsa_names, $dsa_date, \@dsa_CVEs); } # parse freebsd security announcements sub parseFSA ($) { my $str = shift; my %FSA; my %details; ($_, %FSA) = split /\n[IVX]+\.\s+(.*)\n/, $str; $FSA{"Preamble"} = $_; my @src = ("kfreebsd"); # get cve and reldate /\nCVE Name:\s*(.*)\n/; my @CVE = split /, /, $1 if defined $1; /\nAnnounced:\**(.*)\n/; my $reldate = 0; $reldate = parsedate $1 if defined $1; return (\@src, $reldate, \@CVE) if /\nModule:\s*kernel\n/; # make sure this is a kernel package return () if !defined $FSA{"Correction details"}; foreach (split /\n/, $FSA{"Correction details"}) { return (\@src, $reldate, \@CVE) if /^ src\/sys\/(?!conf\/newvers\.sh)/; } return (); } 1;