Files
GLPI-Plugin-CVE-Prototype/inc/cverule.class.php
2025-05-31 10:03:48 +02:00

497 lines
15 KiB
PHP

<?php
/**
* GLPI CVE Plugin - Rule Class
* Manages CVE processing rules
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* PluginCveCveRule class for managing CVE processing rules
*/
class PluginCveCveRule extends CommonDBTM {
static $rightname = 'plugin_cve_rule';
/**
* 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('CVE Rule', 'CVE Rules', $nb, 'cve');
}
/**
* Define tabs to display
*
* @param array $options
* @return array containing the tabs
*/
function defineTabs($options = []) {
$tabs = [];
$this->addDefaultFormTab($tabs);
$this->addStandardTab('Log', $tabs, $options);
return $tabs;
}
/**
* Display the CVE Rule form
*
* @param integer $ID ID of the item
* @param array $options
* @return boolean
*/
function showForm($ID, $options = []) {
global $CFG_GLPI;
$this->initForm($ID, $options);
$this->showFormHeader($options);
$canedit = $this->can($ID, UPDATE);
echo "<tr class='tab_bg_1'>";
// Rule Name
echo "<td>" . __('Rule Name', 'cve') . "</td>";
echo "<td>";
echo Html::input('name', ['value' => $this->fields['name'], 'size' => 40]);
echo "</td>";
// Priority
echo "<td>" . __('Priority', 'cve') . "</td>";
echo "<td>";
echo Html::input('priority', ['value' => $this->fields['priority'], 'size' => 5, 'type' => 'number', 'min' => 1]);
echo "<br><i>" . __('Lower numbers are processed first', 'cve') . "</i>";
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Criteria - Severity
echo "<td>" . __('Severity', 'cve') . "</td>";
echo "<td>";
$criteria = json_decode($this->fields['criteria'], true) ?: [];
$severity = $criteria['severity'] ?? 'CRITICAL';
$severity_options = [
'CRITICAL' => __('Critical', 'cve'),
'HIGH' => __('High', 'cve'),
'MEDIUM' => __('Medium', 'cve'),
'LOW' => __('Low', 'cve')
];
Dropdown::showFromArray('criteria_severity', $severity_options,
['value' => $severity]);
echo "</td>";
// Status
echo "<td>" . __('Status', 'cve') . "</td>";
echo "<td>";
$status_options = [
'NEW' => __('New', 'cve'),
'ANALYZED' => __('Analyzed', 'cve'),
'ASSIGNED' => __('Assigned', 'cve'),
'RESOLVED' => __('Resolved', 'cve')
];
Dropdown::showFromArray('rule_status', $status_options,
['value' => $this->fields['status'] ?? 'NEW']);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Actions - Create Ticket
echo "<td>" . __('Create Ticket', 'cve') . "</td>";
echo "<td>";
$actions = json_decode($this->fields['actions'], true) ?: [];
$create_ticket = isset($actions['create_ticket']) ? $actions['create_ticket'] : true;
Dropdown::showYesNo('actions_create_ticket', $create_ticket);
echo "</td>";
// Ticket Priority
echo "<td>" . __('Ticket Priority', 'cve') . "</td>";
echo "<td>";
$ticket_priority = $actions['ticket_priority'] ?? 'NORMAL';
$priority_options = [
'VERY HIGH' => __('Very High', 'cve'),
'HIGH' => __('High', 'cve'),
'NORMAL' => __('Normal', 'cve'),
'LOW' => __('Low', 'cve')
];
Dropdown::showFromArray('actions_ticket_priority', $priority_options,
['value' => $ticket_priority]);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Actions - Notify Admins
echo "<td>" . __('Notify Administrators', 'cve') . "</td>";
echo "<td>";
$notify_admins = isset($actions['notify_admins']) ? $actions['notify_admins'] : false;
Dropdown::showYesNo('actions_notify_admins', $notify_admins);
echo "</td>";
// Actions - Add to Report
echo "<td>" . __('Add to Vulnerability Report', 'cve') . "</td>";
echo "<td>";
$add_to_report = isset($actions['add_to_report']) ? $actions['add_to_report'] : false;
Dropdown::showYesNo('actions_add_to_report', $add_to_report);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
// Is Active
echo "<td>" . __('Active Rule', 'cve') . "</td>";
echo "<td>";
Dropdown::showYesNo('is_active', $this->fields['is_active']);
echo "</td>";
echo "<td colspan='2'></td>";
echo "</tr>";
$this->showFormButtons($options);
return true;
}
/**
* Process a CVE with rules
*
* @param PluginCveCve $cve CVE to process
* @return boolean True if any rule was applied
*/
static function processCVE(PluginCveCve $cve) {
$rule = new self();
// Get active rules sorted by priority
$rules = $rule->find(['is_active' => 1], ['priority' => 'ASC']);
$rule_applied = false;
foreach ($rules as $rule_data) {
// Load the rule
$rule->getFromDB($rule_data['id']);
// Check if the CVE matches the criteria
if (self::matchesCriteria($cve, $rule_data)) {
// Apply the actions
self::applyActions($cve, $rule_data);
$rule_applied = true;
}
}
return $rule_applied;
}
/**
* Check if a CVE matches rule criteria
*
* @param PluginCveCve $cve CVE to check
* @param array $rule_data Rule data
* @return boolean True if the CVE matches the criteria
*/
private static function matchesCriteria(PluginCveCve $cve, array $rule_data) {
$criteria = json_decode($rule_data['criteria'], true) ?: [];
// Check severity
if (isset($criteria['severity'])) {
// Get the numeric values for comparison
$severity_values = [
'CRITICAL' => 4,
'HIGH' => 3,
'MEDIUM' => 2,
'LOW' => 1
];
$rule_severity = $severity_values[$criteria['severity']] ?? 0;
$cve_severity = $severity_values[$cve->fields['severity']] ?? 0;
// Match if CVE severity is greater than or equal to rule severity
if ($cve_severity < $rule_severity) {
return false;
}
}
// Check affected products if specified
if (isset($criteria['affected_products']) && !empty($criteria['affected_products'])) {
$cve_products = json_decode($cve->fields['affected_products'], true) ?: [];
$rule_products = $criteria['affected_products'];
$found = false;
foreach ($rule_products as $product) {
if (in_array($product, $cve_products)) {
$found = true;
break;
}
}
if (!$found) {
return false;
}
}
// Check CVSS score range if specified
if (isset($criteria['cvss_min']) && $cve->fields['cvss_score'] < $criteria['cvss_min']) {
return false;
}
if (isset($criteria['cvss_max']) && $cve->fields['cvss_score'] > $criteria['cvss_max']) {
return false;
}
// All criteria matched or no criteria specified
return true;
}
/**
* Apply rule actions to a CVE
*
* @param PluginCveCve $cve CVE to process
* @param array $rule_data Rule data
* @return boolean True if actions were applied successfully
*/
private static function applyActions(PluginCveCve $cve, array $rule_data) {
$actions = json_decode($rule_data['actions'], true) ?: [];
// Create a ticket if needed
if (isset($actions['create_ticket']) && $actions['create_ticket']) {
$ticket_options = [];
// Set ticket priority
if (isset($actions['ticket_priority'])) {
switch ($actions['ticket_priority']) {
case 'VERY HIGH':
$ticket_options['priority'] = 5;
break;
case 'HIGH':
$ticket_options['priority'] = 4;
break;
case 'NORMAL':
$ticket_options['priority'] = 3;
break;
case 'LOW':
$ticket_options['priority'] = 2;
break;
default:
$ticket_options['priority'] = 3; // Default to normal
}
}
// Create the ticket
$cve->createTicket($cve->getID(), $ticket_options);
}
// Send notifications if needed
if (isset($actions['notify_admins']) && $actions['notify_admins']) {
// This would implement the notification logic
// For example: NotificationEvent::raiseEvent('new_cve', $cve);
}
// Add to report if needed
if (isset($actions['add_to_report']) && $actions['add_to_report']) {
// This would implement the reporting logic
}
return true;
}
/**
* Get search function for the class
*
* @return array of search options
*/
function rawSearchOptions() {
$tab = [];
$tab[] = [
'id' => 'common',
'name' => self::getTypeName(2)
];
$tab[] = [
'id' => '1',
'table' => $this->getTable(),
'field' => 'name',
'name' => __('Rule Name', 'cve'),
'datatype' => 'itemlink',
'massiveaction' => false
];
$tab[] = [
'id' => '2',
'table' => $this->getTable(),
'field' => 'priority',
'name' => __('Priority', 'cve'),
'datatype' => 'number',
'min' => 1,
'massiveaction' => true
];
$tab[] = [
'id' => '3',
'table' => $this->getTable(),
'field' => 'criteria',
'name' => __('Criteria', 'cve'),
'datatype' => 'text',
'massiveaction' => false
];
$tab[] = [
'id' => '4',
'table' => $this->getTable(),
'field' => 'actions',
'name' => __('Actions', 'cve'),
'datatype' => 'text',
'massiveaction' => false
];
$tab[] = [
'id' => '5',
'table' => $this->getTable(),
'field' => 'is_active',
'name' => __('Active', 'cve'),
'datatype' => 'bool',
'massiveaction' => true
];
$tab[] = [
'id' => '19',
'table' => $this->getTable(),
'field' => 'date_mod',
'name' => __('Last update', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
$tab[] = [
'id' => '16',
'table' => $this->getTable(),
'field' => 'date_creation',
'name' => __('Creation date', 'cve'),
'datatype' => 'datetime',
'massiveaction' => false
];
return $tab;
}
/**
* Install the plugin database schema
*
* @return boolean
*/
static function install(Migration $migration) {
global $DB;
$table = self::getTable();
if (!$DB->tableExists($table)) {
$migration->displayMessage("Installing $table");
$query = "CREATE TABLE IF NOT EXISTS `$table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`criteria` json DEFAULT NULL,
`actions` json DEFAULT NULL,
`priority` int(11) NOT NULL DEFAULT '1',
`is_active` tinyint(1) NOT NULL DEFAULT '0',
`status` varchar(20) DEFAULT 'NEW',
`date_creation` datetime DEFAULT NULL,
`date_mod` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`),
KEY `is_active` (`is_active`),
KEY `priority` (`priority`),
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());
// Add default rules
$default_rules = [
[
'name' => 'Critical Vulnerabilities - Immediate Ticket',
'criteria' => json_encode(['severity' => 'CRITICAL']),
'actions' => json_encode([
'create_ticket' => true,
'ticket_priority' => 'VERY HIGH',
'notify_admins' => true
]),
'priority' => 1,
'is_active' => 1,
'status' => 'NEW',
'date_creation' => $_SESSION['glpi_currenttime'],
'date_mod' => $_SESSION['glpi_currenttime']
],
[
'name' => 'High Risk Vulnerabilities - Create Ticket',
'criteria' => json_encode(['severity' => 'HIGH']),
'actions' => json_encode([
'create_ticket' => true,
'ticket_priority' => 'HIGH',
'notify_admins' => false
]),
'priority' => 2,
'is_active' => 1,
'status' => 'NEW',
'date_creation' => $_SESSION['glpi_currenttime'],
'date_mod' => $_SESSION['glpi_currenttime']
],
[
'name' => 'Medium Risk - Add to Report',
'criteria' => json_encode(['severity' => 'MEDIUM']),
'actions' => json_encode([
'create_ticket' => false,
'add_to_report' => true
]),
'priority' => 3,
'is_active' => 1,
'status' => 'NEW',
'date_creation' => $_SESSION['glpi_currenttime'],
'date_mod' => $_SESSION['glpi_currenttime']
]
];
$rule = new self();
foreach ($default_rules as $rule_data) {
$rule->add($rule_data);
}
}
return true;
}
/**
* Uninstall the plugin database schema
*
* @return boolean
*/
static function uninstall(Migration $migration) {
global $DB;
$table = self::getTable();
if ($DB->tableExists($table)) {
$migration->displayMessage("Uninstalling $table");
$migration->dropTable($table);
}
return true;
}
}