#!/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");
}
}
?>