#!/usr/bin/php -q Microsoft, Apple, PHP Ruby * Between the vps tags, insert vendor, product pairs * Between the keyword tags, insert keywords. The descriptions will be searched * for those keywords. You may need to lookup the correct product and vendor * names from the NVD itself; there won't be any matches if the names are * incorrect. * * 3. Run the script ("./my_cassandra.php" or "cassandra.bat") at regular intervals (e.g., daily). The script will list new CVE entries, and if you are running MacOSX it will direct Safari to the appropriate web pages. * * ChangeLog: * 1.03: Bug fix to prevent warnings in case there are no keywords, products or vendors in the profile * 1.02: Tested on Windows XP SP2 and PHP 5.1.1; made changes to detect OS and start EXPLORER if a Windows OS is detected; changes to read a profile regardless of line termination model. * 1.01: Patch by Benjamin Lewis to improve the "die" error message, and add a case for empty lines in the profile * 1.0: developed and tested on MacOS 10.4.3 and PHP 4.3.11 (included in MacOS X) * */ error_reporting (E_ALL); $debug = true; // will print messages about every field found if true $count_entries = 0; // count number of new CVE entries found today // Choose a source for parsing // Check if a source was passed as argument // load profile // format: // // vendor, product // vendor, product // vendor, product // // // keyword // keyword // keyword // keyword // $vps = fopen ("profile.txt", "r"); if ($vps == false) { die ("Could not find a profile to process."); } $vendors = array(); $products = array(); $keywords = array(); while (!feof($vps)) { $type = trim(fgets($vps, 512)); switch ($type) { case "": $count = 0; $line = ""; while (($line = trim(fgets($vps, 1024))) != "" && !feof($vps)) { $vp_a = explode(",", $line); $vendors[$count] = trim($vp_a[0]); if (sizeof($vp_a) < 2) { $products[$count] = ""; } else { $products[$count] = trim($vp_a[1]); } //print "vendor ". $vendors[$count] . " and product " . $products[$count] . "\n"; $count++; } break; case "": while (($line = trim(fgets($vps, 1024))) != "" && !feof($vps)) { $keywords[] = $line; } break; case "": break; default: die ("type not understood: '" . $type . "'\n"); } } // // load already seen entries // CVE-1999-0001 $already_seen = @file("seen_CVE.txt"); if ($already_seen == false) { $already_seen = array(); } $found = array(); if (isset($argv[1])) { parse_this($argv[1]); } else { parse_this("http://nvd.nist.gov/download/nvdcve-recent.xml"); // other possibilities: //parse_this("http://nvd.nist.gov/download/nvdcve-2002.xml"); //parse_this("http://nvd.nist.gov/download/nvdcve-2003.xml"); //parse_this("http://nvd.nist.gov/download/nvdcve-2004.xml"); //parse_this("http://nvd.nist.gov/download/nvdcve-2005.xml"); //parse_this("http://nvd.nist.gov/download/nvdcve-2006.xml"); // // The following do not work with vanilla PHP installs // without the wrapper "https" (e.g., MacOS X default install) //parse_this("https://nvd.nist.gov/download/nvdcve-2002.xml"); //parse_this("https://nvd.nist.gov/download/nvdcve-2003.xml"); //parse_this("https://nvd.nist.gov/download/nvdcve-2004.xml"); //parse_this("https://nvd.nist.gov/download/nvdcve-2005.xml"); //parse_this("https://nvd.nist.gov/download/nvdcve-2006.xml"); } // remove duplicates $found = array_unique($found); echo "found these new entries:\n"; $fp = fopen("seen_CVE.txt", "w+"); foreach ($found as $entry) { fwrite($fp, $entry . "\n"); if (!in_array ($entry ."\n", $already_seen)) { echo $entry. "\n"; if (isset($_SERVER["windir"])) { system ("start EXPLORER \"http://nvd.nist.gov/nvd.cfm?cvename=". $entry. "\""); } if (isset($_SERVER["OSTYPE"])) { if ($_SERVER["OSTYPE"] == "darwin") { system ("/usr/bin/open \"http://nvd.nist.gov/nvd.cfm?cvename=". $entry. "\""); } } } } fclose($fp); // add to file die; // not necessary, added for clarity function startElement($parser, $name, $attrs) { global $all_data, $CVE, $debug, $insert, $vendor, $product, $count_entries, $desc_type, $already_seen, $vendors, $products, $found; // reset data accumulator $all_data = ''; // // the switch statement should enumerate all of the possible name tags. // State is kept using global variables, so that nested tags can // access their context. // switch ($name) { case 'ENTRY': $count_entries++; // get name and publication date $CVE = validate_CVE($attrs['NAME']); $published = validate_date($attrs['PUBLISHED']); break; case 'PROD': $vendor = $attrs['VENDOR']; $product = $attrs['NAME']; if ($CVE == '') { echo "error, no CVE number"; die; } if ($vendor == "") { // this happens // echo "NVD integrity alert: no vendor for product $product, CVE is $CVE\n"; } if ($product == "") { echo "NVD integrity alert: no product, CVE is $CVE\n"; } $index_product = array_search($product, $products); $index_vendor = array_search($vendor, $vendors); if ($index_product === false) { // product does not match if ($index_vendor !== false) { if ($products[$index_vendor] == "") { // empty string means match any product // add to found array $found[] = $CVE; //print "match found for vendor $vendor and product $product\n"; } else { //print "no match found for vendor $vendor and product $product"; }// else mismatch, ignore entry } else { //print "no match found for vendor $vendor and product $product"; }// else mismatch, ignore entry } else { if ($index_vendor === false) { if ($vendors[$index_product] == "") { // empty string means match any vendor // add to found array $found[] = $CVE; //print "match found for vendor $vendor and product $product\n"; } else { //print "no match found for vendor $vendor and product $product"; }// else mismatch, ignore entry } else { // both product and vendor match $found[] = $CVE; //print "match found for vendor $vendor and product $product\n"; } } break; } } function endElement($parser, $name) { global $all_data, $CVE, $debug, $found, $keywords; switch ($name) { case 'ENTRY': $CVE = ''; break; case 'DESCRIPT': if ($CVE == '') { echo "error, no CVE number"; die; } //print $all_data . "\n"; foreach ($keywords as $k) { if (strpos($all_data, $k) !== false) { $found[] = $CVE; } } break; } } function characterData($parser, $data) { global $all_data; // concatenate due to bug with & according to a user entry in PHP docs // entry by sam at cwa dot co dot nz, Sept 2000 // This issue might have been fixed later. The fix can't hurt though. $all_data .= $data; } function parse_this($url) { // code for this function is from http://us2.php.net/xml $xml_parser = xml_parser_create(); // use case-folding so we are sure to find the tag in $map_array xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true); xml_set_element_handler($xml_parser, "startElement", "endElement"); xml_set_character_data_handler($xml_parser, "characterData"); if (!($fp = fopen($url, "r"))) { die("could not open XML input"); } while ($data = fread($fp, 4096)) { if (!xml_parse($xml_parser, $data, feof($fp))) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } } xml_parser_free($xml_parser); } function validate_CVE($input) { // expecting something like CAN-2004-0002 // NVD documentation suggests this reg exp, // (http://nvd.nist.gov/download/nvdcve-xmldoc.cfm) // (CAN|CVE)-/d/d/d/d-/d/d/d/d // which doesn't work. See below if (preg_match('/(CAN|CVE)-\d\d\d\d-\d\d\d\d/', $input, $matches)) { return $matches[0]; } else { // if there was an error (false) or if 0 matches die ("Invalid CVE number encountered: $input"); } } function validate_Date($input) { // expecting something like 2004-03-03 if (preg_match('/\d\d\d\d-\d\d-\d\d/', $input, $matches)) { return $matches[0]; } else { // if there was an error (false) or if 0 matches die ("Invalid date encountered: $input"); } } ?>