mirror of
https://github.com/tips-of-mine/GLPI-Plugin-CVE-Prototype.git
synced 2025-06-27 22:58:45 +02:00
358 lines
12 KiB
PHP
358 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* GLPI CVE Plugin - Software Inventory Analysis Class
|
|
* Analyzes GLPI software inventory and matches it with known CVEs
|
|
*/
|
|
|
|
if (!defined('GLPI_ROOT')) {
|
|
die("Sorry. You can't access this file directly");
|
|
}
|
|
|
|
/**
|
|
* PluginCveCveInventory class for analyzing software inventory for vulnerabilities
|
|
*/
|
|
class PluginCveCveInventory extends CommonDBTM {
|
|
|
|
static $rightname = 'plugin_cve_inventory';
|
|
|
|
/**
|
|
* Get name of this type by language of the user connected
|
|
*
|
|
* @param integer $nb number of elements
|
|
* @return string name of this type
|
|
*/
|
|
static function getTypeName($nb = 0) {
|
|
return _n('Software Vulnerability Analysis', 'Software Vulnerability Analyses', $nb, 'cve');
|
|
}
|
|
|
|
/**
|
|
* Cron task for software inventory analysis
|
|
*
|
|
* @param CronTask $task CronTask object
|
|
* @return integer
|
|
*/
|
|
static function cronAnalyzeInventory($task) {
|
|
global $DB;
|
|
|
|
$task->log("Starting software vulnerability analysis");
|
|
$task->setVolume(0);
|
|
|
|
// Get all active entities
|
|
$entity = new Entity();
|
|
$entities = $entity->find(['is_recursive' => 1]);
|
|
$entity_ids = array_column($entities, 'id');
|
|
|
|
// Get all software from inventory
|
|
$software = new Software();
|
|
$software_versions = new SoftwareVersion();
|
|
|
|
$matched_count = 0;
|
|
$alert_count = 0;
|
|
|
|
// For each entity, process software inventory
|
|
foreach ($entity_ids as $entity_id) {
|
|
$task->log("Processing entity ID: $entity_id");
|
|
|
|
// Get software in this entity
|
|
$software_list = $software->find(['entities_id' => $entity_id]);
|
|
|
|
foreach ($software_list as $sw) {
|
|
// Get versions of this software
|
|
$versions = $software_versions->find(['softwares_id' => $sw['id']]);
|
|
|
|
foreach ($versions as $version) {
|
|
// Search for vulnerabilities for this software/version
|
|
$vulnerabilities = self::findVulnerabilities($sw['name'], $version['name']);
|
|
|
|
if (!empty($vulnerabilities)) {
|
|
$matched_count += count($vulnerabilities);
|
|
|
|
// Process each vulnerability
|
|
foreach ($vulnerabilities as $cve_id) {
|
|
// Create alert if it doesn't already exist
|
|
if (self::createAlert($sw['id'], $version['id'], $cve_id, $entity_id)) {
|
|
$alert_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$task->setVolume($matched_count);
|
|
$task->log("Analysis completed. Found $matched_count potential vulnerabilities, created $alert_count new alerts");
|
|
|
|
return ($alert_count > 0) ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* Find vulnerabilities for a given software and version
|
|
*
|
|
* @param string $software_name Software name
|
|
* @param string $version_name Version string
|
|
* @return array Array of matching CVE IDs
|
|
*/
|
|
private static function findVulnerabilities($software_name, $version_name) {
|
|
global $DB;
|
|
|
|
$matches = [];
|
|
|
|
// Normalize software name for better matching
|
|
$normalized_name = strtolower(trim($software_name));
|
|
$normalized_version = trim($version_name);
|
|
|
|
// Search in affected_products field of CVEs
|
|
$query = "SELECT id, cve_id, affected_products
|
|
FROM `glpi_plugin_cve_cves`
|
|
WHERE `status` != 'RESOLVED'";
|
|
|
|
$result = $DB->query($query);
|
|
|
|
if ($result) {
|
|
while ($row = $DB->fetchAssoc($result)) {
|
|
$affected_products = json_decode($row['affected_products'], true) ?: [];
|
|
|
|
foreach ($affected_products as $product) {
|
|
// Simple matching for demonstration
|
|
// In a real implementation, this would use CPE matching or more sophisticated algorithms
|
|
if (self::matchesSoftware($normalized_name, $normalized_version, $product)) {
|
|
$matches[] = $row['id'];
|
|
break; // Found a match for this CVE
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $matches;
|
|
}
|
|
|
|
/**
|
|
* Check if software and version match an affected product string
|
|
*
|
|
* @param string $name Normalized software name
|
|
* @param string $version Normalized version
|
|
* @param string $product Affected product string
|
|
* @return boolean True if matches
|
|
*/
|
|
private static function matchesSoftware($name, $version, $product) {
|
|
// Normalize product string
|
|
$product = strtolower(trim($product));
|
|
|
|
// Check if product string contains both software name and version
|
|
// This is a simple implementation and would need to be more sophisticated in real use
|
|
if (strpos($product, $name) !== false) {
|
|
// If version is part of an affected range
|
|
if (strpos($product, ' < ' . $version) !== false ||
|
|
strpos($product, '<=' . $version) !== false ||
|
|
strpos($product, $version) !== false) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create a vulnerability alert
|
|
*
|
|
* @param integer $software_id Software ID
|
|
* @param integer $version_id Version ID
|
|
* @param integer $cve_id CVE ID
|
|
* @param integer $entity_id Entity ID
|
|
* @return boolean True if new alert was created
|
|
*/
|
|
private static function createAlert($software_id, $version_id, $cve_id, $entity_id) {
|
|
global $DB;
|
|
|
|
// Check if alert already exists
|
|
$query = "SELECT id FROM `glpi_plugin_cve_alerts`
|
|
WHERE `softwares_id` = $software_id
|
|
AND `softwareversions_id` = $version_id
|
|
AND `cves_id` = $cve_id";
|
|
|
|
$result = $DB->query($query);
|
|
|
|
if ($result && $DB->numrows($result) > 0) {
|
|
// Alert already exists
|
|
return false;
|
|
}
|
|
|
|
// Get CVE details
|
|
$cve = new PluginCveCve();
|
|
$cve->getFromDB($cve_id);
|
|
|
|
// Create new alert
|
|
$alert = new PluginCveCveAlert();
|
|
$alert_id = $alert->add([
|
|
'softwares_id' => $software_id,
|
|
'softwareversions_id' => $version_id,
|
|
'cves_id' => $cve_id,
|
|
'entities_id' => $entity_id,
|
|
'status' => 'NEW',
|
|
'severity' => $cve->fields['severity'],
|
|
'date_creation' => $_SESSION['glpi_currenttime']
|
|
]);
|
|
|
|
if ($alert_id) {
|
|
// Process the alert according to rules
|
|
self::processAlert($alert_id);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Process a vulnerability alert based on rules
|
|
*
|
|
* @param integer $alert_id Alert ID
|
|
* @return boolean Success
|
|
*/
|
|
private static function processAlert($alert_id) {
|
|
// Get alert details
|
|
$alert = new PluginCveCveAlert();
|
|
if (!$alert->getFromDB($alert_id)) {
|
|
return false;
|
|
}
|
|
|
|
// Get associated CVE
|
|
$cve = new PluginCveCve();
|
|
if (!$cve->getFromDB($alert->fields['cves_id'])) {
|
|
return false;
|
|
}
|
|
|
|
// Get software details
|
|
$software = new Software();
|
|
if (!$software->getFromDB($alert->fields['softwares_id'])) {
|
|
return false;
|
|
}
|
|
|
|
$version = new SoftwareVersion();
|
|
if (!$version->getFromDB($alert->fields['softwareversions_id'])) {
|
|
return false;
|
|
}
|
|
|
|
// Apply rules based on severity
|
|
if ($alert->fields['severity'] == 'CRITICAL' || $alert->fields['severity'] == 'HIGH') {
|
|
// Create a ticket for high/critical vulnerabilities
|
|
$ticket = new Ticket();
|
|
|
|
$content = __('A vulnerability has been detected in your software inventory', 'cve') . "\n\n";
|
|
$content .= __('Software', 'cve') . ': ' . $software->fields['name'] . ' ' . $version->fields['name'] . "\n";
|
|
$content .= __('CVE', 'cve') . ': ' . $cve->fields['cve_id'] . "\n";
|
|
$content .= __('Severity', 'cve') . ': ' . $cve->fields['severity'] . "\n";
|
|
$content .= __('CVSS Score', 'cve') . ': ' . $cve->fields['cvss_score'] . "\n\n";
|
|
$content .= __('Description', 'cve') . ":\n" . $cve->fields['description'] . "\n\n";
|
|
|
|
$affected_products = json_decode($cve->fields['affected_products'], true) ?: [];
|
|
if (!empty($affected_products)) {
|
|
$content .= __('Affected Products', 'cve') . ":\n" . implode("\n", $affected_products) . "\n\n";
|
|
}
|
|
|
|
$references = json_decode($cve->fields['references'], true) ?: [];
|
|
if (!empty($references)) {
|
|
$content .= __('References', 'cve') . ":\n" . implode("\n", $references) . "\n";
|
|
}
|
|
|
|
$ticket_id = $ticket->add([
|
|
'name' => __('Vulnerability', 'cve') . ' ' . $cve->fields['cve_id'] . ' - ' . $software->fields['name'],
|
|
'content' => $content,
|
|
'status' => Ticket::INCOMING,
|
|
'priority' => ($alert->fields['severity'] == 'CRITICAL') ? 5 : 4,
|
|
'urgency' => ($alert->fields['severity'] == 'CRITICAL') ? 5 : 4,
|
|
'impact' => ($alert->fields['severity'] == 'CRITICAL') ? 5 : 4,
|
|
'entities_id' => $alert->fields['entities_id'],
|
|
'date' => $_SESSION['glpi_currenttime'],
|
|
'itilcategories_id' => 0, // Default or security category if configured
|
|
'type' => Ticket::INCIDENT_TYPE
|
|
]);
|
|
|
|
if ($ticket_id) {
|
|
// Link the CVE to the ticket
|
|
$cveTicket = new PluginCveCveTicket();
|
|
$cveTicket->add([
|
|
'cves_id' => $alert->fields['cves_id'],
|
|
'tickets_id' => $ticket_id,
|
|
'creation_type' => 'AUTO',
|
|
'date_creation' => $_SESSION['glpi_currenttime']
|
|
]);
|
|
|
|
// Update alert status
|
|
$alert->update([
|
|
'id' => $alert_id,
|
|
'status' => 'PROCESSED',
|
|
'tickets_id' => $ticket_id
|
|
]);
|
|
}
|
|
} else {
|
|
// For medium/low severity, just mark as processed without ticket
|
|
$alert->update([
|
|
'id' => $alert_id,
|
|
'status' => 'PROCESSED'
|
|
]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Install the plugin database schema
|
|
*
|
|
* @return boolean
|
|
*/
|
|
static function install(Migration $migration) {
|
|
global $DB;
|
|
|
|
$table = 'glpi_plugin_cve_alerts';
|
|
|
|
if (!$DB->tableExists($table)) {
|
|
$migration->displayMessage("Installing $table");
|
|
|
|
$query = "CREATE TABLE IF NOT EXISTS `$table` (
|
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
`softwares_id` int(11) NOT NULL,
|
|
`softwareversions_id` int(11) NOT NULL,
|
|
`cves_id` int(11) NOT NULL,
|
|
`entities_id` int(11) NOT NULL DEFAULT '0',
|
|
`status` enum('NEW','PROCESSED','IGNORED') DEFAULT 'NEW',
|
|
`severity` enum('LOW','MEDIUM','HIGH','CRITICAL') DEFAULT NULL,
|
|
`tickets_id` int(11) DEFAULT NULL,
|
|
`date_creation` datetime DEFAULT NULL,
|
|
`date_mod` datetime DEFAULT NULL,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `unique_alert` (`softwares_id`, `softwareversions_id`, `cves_id`),
|
|
KEY `softwares_id` (`softwares_id`),
|
|
KEY `softwareversions_id` (`softwareversions_id`),
|
|
KEY `cves_id` (`cves_id`),
|
|
KEY `entities_id` (`entities_id`),
|
|
KEY `status` (`status`),
|
|
KEY `severity` (`severity`),
|
|
KEY `tickets_id` (`tickets_id`),
|
|
KEY `date_creation` (`date_creation`),
|
|
KEY `date_mod` (`date_mod`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
|
|
|
|
$DB->query($query) or die("Error creating $table " . $DB->error());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Uninstall the plugin database schema
|
|
*
|
|
* @return boolean
|
|
*/
|
|
static function uninstall(Migration $migration) {
|
|
global $DB;
|
|
|
|
$table = 'glpi_plugin_cve_alerts';
|
|
|
|
if ($DB->tableExists($table)) {
|
|
$migration->displayMessage("Uninstalling $table");
|
|
$migration->dropTable($table);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} |