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; } }