Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/plugin-ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
run: sudo apt-get update

- name: Install System Dependencies
run: sudo apt-get install -y apache2 snmp snmpd rrdtool fping libapache2-mod-php${{ matrix.php }}
run: sudo apt-get install -y apache2 snmp snmpd rrdtool fping

- name: Start SNMPD Agent and Test
run: |
Expand Down
31 changes: 23 additions & 8 deletions lib/mactrack_functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ function build_InterfacesTable(&$device, &$ifIndexes, $getLinkPorts = false, $ge
}

// required only for interfaces table
$db_data = db_fetch_assoc("SELECT * FROM mac_track_interfaces WHERE device_id='" . $device['device_id'] . "' ORDER BY ifIndex");
$db_data = db_fetch_assoc_prepared('SELECT * FROM mac_track_interfaces WHERE device_id = ? ORDER BY ifIndex', [$device['device_id']]);

if (cacti_sizeof($db_data)) {
foreach ($db_data as $interface) {
Expand Down Expand Up @@ -3118,6 +3118,9 @@ function mactrack_display_Octets($octets) {
function mactrack_rescan($web = false) {
global $config;

get_filter_request_var('device_id');
get_filter_request_var('ifIndex');

$device_id = get_request_var('device_id');
$ifIndex = get_request_var('ifIndex');

Expand All @@ -3135,7 +3138,7 @@ function mactrack_rescan($web = false) {

// create the command script
$command_string = $config['base_path'] . '/plugins/mactrack/mactrack_scanner.php';
$extra_args = ' -id=' . $dbinfo['device_id'] . ($web ? ' --web' : '');
$extra_args = ' -id=' . cacti_escapeshellarg($dbinfo['device_id']) . ($web ? ' --web' : '');

// print out the type, and device_id
$data['device_id'] = get_request_var('device_id');
Expand All @@ -3145,10 +3148,15 @@ function mactrack_rescan($web = false) {
ob_start();

// execute the command, and show the results
$command = read_config_option('path_php_binary') . ' -q ' . $command_string . $extra_args;
passthru($command);
$command = cacti_escapeshellarg(read_config_option('path_php_binary')) . ' -q ' . cacti_escapeshellarg($command_string) . $extra_args;
passthru($command, $exit_code);

$data['content'] = ob_get_clean();

if ($exit_code !== 0) {
$data['error'] = 'rescan process exited with code ' . intval($exit_code);
$data['content'] .= '<p class="textError">' . html_escape('Subprocess error: exit code ' . intval($exit_code)) . '</p>';
}
Comment thread
somethingwithproof marked this conversation as resolved.
}
}

Expand All @@ -3158,7 +3166,9 @@ function mactrack_rescan($web = false) {
}

function mactrack_site_scan($web = false) {
global $config, $web;
global $config;

get_filter_request_var('site_id');

$site_id = get_request_var('site_id');

Expand All @@ -3175,7 +3185,7 @@ function mactrack_site_scan($web = false) {

// create the command script
$command_string = $config['base_path'] . '/plugins/mactrack/poller_mactrack.php';
$extra_args = ' --web -sid=' . $dbinfo['site_id'];
$extra_args = ' -sid=' . cacti_escapeshellarg($dbinfo['site_id']) . ($web ? ' --web' : '');

// print out the type, and device_id
$data['site_id'] = $site_id;
Expand All @@ -3184,10 +3194,15 @@ function mactrack_site_scan($web = false) {
ob_start();

// execute the command, and show the results
$command = read_config_option('path_php_binary') . ' -q ' . $command_string . $extra_args;
passthru($command);
$command = cacti_escapeshellarg(read_config_option('path_php_binary')) . ' -q ' . cacti_escapeshellarg($command_string) . $extra_args;
passthru($command, $exit_code);

$data['content'] = ob_get_clean();

if ($exit_code !== 0) {
$data['error'] = 'site_scan process exited with code ' . intval($exit_code);
$data['content'] .= '<p class="textError">' . html_escape('Subprocess error: exit code ' . intval($exit_code)) . '</p>';
}
}

header('Content-Type: application/json; charset=utf-8');
Expand Down
4 changes: 2 additions & 2 deletions mactrack_actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ function sync_mactrack_to_cacti($mt_device) {
$mt_device['snmp_engine_id'] ??= '';

// fetch current data for cacti device
$cacti_device = db_fetch_row('SELECT * FROM host WHERE id=' . $mt_device['host_id']);
$cacti_device = db_fetch_row_prepared('SELECT * FROM host WHERE id = ?', [$mt_device['host_id']]);

if (cacti_sizeof($cacti_device)) {
// update cacti device
Expand Down Expand Up @@ -176,7 +176,7 @@ function sync_cacti_to_mactrack($device) {
if ((read_config_option('mt_update_policy', true) == 2) && ($device['id'] > 0)) {
// $devices holds the whole row from host table
// now fetch the related device from mac_track_devices, if any
$mt_device = db_fetch_row('SELECT * from mac_track_devices WHERE host_id=' . $device['id']);
$mt_device = db_fetch_row_prepared('SELECT * FROM mac_track_devices WHERE host_id = ?', [$device['id']]);

if (is_array($mt_device) && $mt_device) {
$mt_device['snmp_engine_id'] ??= '';
Expand Down
23 changes: 15 additions & 8 deletions mactrack_devices.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,19 +202,26 @@ function form_mactrack_actions() {

if (isset($cacti_host['id'])) {
reset($fields_mactrack_snmp_item);
$updates = '';
$set_clauses = [];
$set_params = [];

foreach ($fields_mactrack_snmp_item as $field_name => $field_array) {
if (isset($cacti_host[$field_name])) {
$updates .= ($updates != '' ? ', ' : '') . $field_name . "='" . $cacti_host[$field_name] . "'";
// $field_name is a trusted key from the static $fields_mactrack_snmp_item
// definition; the host-table value is bound as a parameter to avoid
// second-order SQL injection when it contains a quote.
$set_clauses[] = $field_name . ' = ?';
$set_params[] = $cacti_host[$field_name];
}
}

if ($updates != '') {
if (cacti_sizeof($set_clauses)) {
$set_params[] = $selected_items[$i];

db_execute_prepared('UPDATE mac_track_devices
SET ' . $updates . '
WHERE device_id=?',
[$selected_items[$i]]);
SET ' . implode(', ', $set_clauses) . '
WHERE device_id = ?',
$set_params);
}
} else {
// skip silently; possible enhancement: tell the user what we did
Expand Down Expand Up @@ -246,7 +253,7 @@ function form_mactrack_actions() {
FROM mac_track_devices
WHERE device_id = ?',
[$matches[1]]);
$device_list .= '<li>' . $device_info['device_name'] . ' (' . $device_info['hostname'] . ')</li>';
$device_list .= '<li>' . html_escape($device_info['device_name']) . ' (' . html_escape($device_info['hostname']) . ')</li>';
$device_array[$i] = $matches[1];
$i++;
}
Expand Down Expand Up @@ -879,7 +886,7 @@ function mactrack_device_edit() {
<table class='cactiTable'>
<tr>
<td class='textInfo' colspan='2'>
<?php print $device['device_name']; ?> (<?php print $device['hostname']; ?>)
<?php print html_escape($device['device_name']); ?> (<?php print html_escape($device['hostname']); ?>)
</td>
</tr>
<tr>
Expand Down
30 changes: 18 additions & 12 deletions mactrack_view_macs.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function form_actions() {

// if we are to save this form, instead of display it
if (isset_request_var('selected_items')) {
$selected_items = unserialize(get_nfilter_request_var('selected_items'), ['allowed_classes' => false]);
$selected_items = unserialize(get_nfilter_request_var('selected_items'), ['allowed_classes' => false]); // nosemgrep: php.lang.security.unserialize-use.unserialize-use -- allowed_classes mitigates gadget chaining; MAC/IP values validated below

if (!is_array($selected_items)) {
header('Location: mactrack_view_macs.php');
Expand Down Expand Up @@ -152,7 +152,7 @@ function form_actions() {
}

if (!isset($mac_address_array[$mac])) {
$mac_address_list .= '<li>' . mactrack_format_mac($mac) . '</li>';
$mac_address_list .= '<li>' . html_escape(mactrack_format_mac($mac)) . '</li>';
$mac_address_array[$mac] = $ip;
}
}
Expand Down Expand Up @@ -193,8 +193,8 @@ function form_actions() {
print "<tr>
<td colspan='2' class='saveRow'>
<input type='hidden' name='action' value='actions'>
<input type='hidden' name='selected_items' value='" . (isset($mac_address_array) ? serialize($mac_address_array) : '') . "'>
<input type='hidden' name='drp_action' value='" . get_request_var('drp_action') . "'>" . ($save_html != '' ? "
<input type='hidden' name='selected_items' value='" . (isset($mac_address_array) ? html_escape(serialize($mac_address_array)) : '') . "'>
<input type='hidden' name='drp_action' value='" . html_escape(get_request_var('drp_action')) . "'>" . ($save_html != '' ? "
<button type='button' class='ui-button ui-corner-all ui-widget' onClick='cactiReturnTo()'>" . __esc('Cancel', 'mactrack') . "</button>
$save_html" : "<button type='button' class='ui-button ui-corner-all ui-widget' onClick='cactiReturnTo()'>" . __esc('Return', 'mactrack') . '</button>') . '
</td>
Expand All @@ -221,7 +221,8 @@ function form_aggregated_actions() {
if ($selected_items != false) {
if (get_request_var('drp_action') == '3') { // Delete
if (cacti_sizeof($selected_items)) {
db_execute('DELETE FROM mac_track_aggregated_ports WHERE row_id IN (' . implode(',', $selected_items) . ')');
$placeholders = implode(',', array_fill(0, cacti_sizeof($selected_items), '?'));
db_execute_prepared('DELETE FROM mac_track_aggregated_ports WHERE row_id IN (' . $placeholders . ')', $selected_items);
}
}

Expand All @@ -235,7 +236,6 @@ function form_aggregated_actions() {
$mac_address_list = '';
$row_list = '';
$i = 0;
$row_ids = '';

// loop through each of the ports selected on the previous page and get more info about them
foreach ($_POST as $var => $val) {
Expand All @@ -249,14 +249,20 @@ function form_aggregated_actions() {
}

if (cacti_sizeof($row_array)) {
$row_ids = implode(',', $row_array);
$rows_info = db_fetch_assoc('SELECT device_name, mac_address, ip_address, port_number, count_rec
$row_placeholders = implode(',', array_fill(0, cacti_sizeof($row_array), '?'));
$rows_info = db_fetch_assoc_prepared('SELECT device_name, mac_address, ip_address, port_number, count_rec
FROM mac_track_aggregated_ports
WHERE row_id IN (' . implode(',', $row_array) . ')');
WHERE row_id IN (' . $row_placeholders . ')', $row_array);

if (isset($rows_info)) {
foreach ($rows_info as $row_info) {
$row_list .= '<li>' . __('Dev.:%s IP.:%s MAC.:%s PORT.:%s Count.: [%s]', $row_info['device_name'], $row_info['ip_address'], mactrack_format_mac($row_info['mac_address']), $row_info['port_number'], $row_info['count_rec'], 'mactrack') . '</li>';
$row_list .= '<li>' . __('Dev.:%s IP.:%s MAC.:%s PORT.:%s Count.: [%s]',
html_escape($row_info['device_name']),
html_escape($row_info['ip_address']),
html_escape(mactrack_format_mac($row_info['mac_address'])),
html_escape((string) $row_info['port_number']),
html_escape((string) $row_info['count_rec']),
'mactrack') . '</li>';
}
}
}
Expand Down Expand Up @@ -289,8 +295,8 @@ function form_aggregated_actions() {
print "<tr>
<td colspan='2' align='right' class='saveRow'>
<input type='hidden' name='action' value='actions'>
<input type='hidden' name='selected_items' value='" . (isset($row_array) ? serialize($row_array) : '') . "'>
<input type='hidden' name='drp_action' value='" . get_request_var('drp_action') . "'>" . ($save_html != '' ? "
<input type='hidden' name='selected_items' value='" . (isset($row_array) ? html_escape(serialize($row_array)) : '') . "'>
<input type='hidden' name='drp_action' value='" . html_escape(get_request_var('drp_action')) . "'>" . ($save_html != '' ? "
<button type='button' onClick='cactiReturnTo()' class='ui-button ui-corner-all ui-widget'>" . __esc('Cancel', 'mactrack') . "</button>
$save_html" : "<button type='button' onClick='cactiReturnTo()' class='ui-button ui-corner-all ui-widget'>" . __esc('Return', 'mactrack') . '</button>') . '
</td>
Expand Down
12 changes: 12 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

// Pest configuration file.

require_once __DIR__ . '/bootstrap.php';
108 changes: 108 additions & 0 deletions tests/Security/Php74CompatibilityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/

/*
* Verify plugin source files do not use PHP 8.0+ syntax.
* Cacti 1.2.x plugins must remain compatible with PHP 7.4.
*/

describe('PHP 7.4 compatibility in mactrack', function () {
$files = [
'mactrack_devices.php',
'mactrack_device_types.php',
'mactrack_interfaces.php',
'mactrack_sites.php',
'mactrack_snmp.php',
'mactrack_utilities.php',
'mactrack_view_arp.php',
'mactrack_view_macs.php',
'mactrack_view_sites.php',
'setup.php',
];

it('does not use str_contains (PHP 8.0)', function () use ($files) {
foreach ($files as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
}

expect(preg_match('/\bstr_contains\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_contains() which requires PHP 8.0"
);
}
});

it('does not use str_starts_with (PHP 8.0)', function () use ($files) {
foreach ($files as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
}

expect(preg_match('/\bstr_starts_with\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_starts_with() which requires PHP 8.0"
);
}
});

it('does not use str_ends_with (PHP 8.0)', function () use ($files) {
foreach ($files as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
}

expect(preg_match('/\bstr_ends_with\s*\(/', $contents))->toBe(0,
"{$relativeFile} uses str_ends_with() which requires PHP 8.0"
);
}
});

it('does not use nullsafe operator (PHP 8.0)', function () use ($files) {
foreach ($files as $relativeFile) {
$path = realpath(__DIR__ . '/../../' . $relativeFile);

if ($path === false) {
continue;
}

$contents = file_get_contents($path);

if ($contents === false) {
continue;
}

expect(preg_match('/\?->/', $contents))->toBe(0,
"{$relativeFile} uses nullsafe operator which requires PHP 8.0"
);
}
});
});
Loading
Loading