mirror of
https://github.com/tips-of-mine/GLPI-Plugin-CVE-Prototype.git
synced 2025-06-28 07:08:44 +02:00
Start repository
This commit is contained in:
358
inc/cveinventory.class.php
Normal file
358
inc/cveinventory.class.php
Normal file
@ -0,0 +1,358 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user