From a7e2765e05ffbc3ec09186b3bd5f31f08766f857 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Wed, 8 Apr 2026 23:05:09 -0700 Subject: [PATCH 1/7] fix(security): defense-in-depth hardening for plugin_webseer Automated fixes: - XSS: escape request variables in HTML value attributes - SQLi: convert string-concat queries to prepared statements - Deserialization: add allowed_classes=>false - Temp files: replace rand() with tempnam() Signed-off-by: Thomas Vincent --- includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index f8dd2ed..84c3837 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -72,7 +72,7 @@ function plugin_webseer_refresh_servers() { foreach ($results as $r) { if (substr($r, 0, 8) == 'SERVERS=') { $servers = substr($r, 8); - $servers = unserialize(base64_decode($servers)); + $servers = unserialize(base64_decode($servers, array('allowed_classes' => false))); if (isset($servers[0]['id'])) { db_execute('TRUNCATE TABLE plugin_webseer_servers'); foreach ($servers as $save) { @@ -104,7 +104,7 @@ function plugin_webseer_refresh_urls () { foreach ($results as $r) { if (substr($r, 0, 5) == 'URLS=') { $urls = substr($r, 5); - $urls = unserialize(base64_decode($urls)); + $urls = unserialize(base64_decode($urls, array('allowed_classes' => false))); if (isset($urls[0]['id'])) { db_execute('TRUNCATE TABLE plugin_webseer_urls'); From de998627eaa4321ce1c88e7bb70aee9e9fadfa87 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 9 Apr 2026 01:24:54 -0700 Subject: [PATCH 2/7] fix(js): migrate deprecated jQuery shorthand events to .on()/.off() Replace .click(fn) with .on('click', fn), .change(fn) with .on('change', fn), .submit(fn) with .on('submit', fn), .unbind() with .off(), and .resize(fn) with .on('resize', fn). These shorthands were deprecated in jQuery 3.3 and will be removed in jQuery 4.0. Cacti core ships jQuery 3.x on develop. Signed-off-by: Thomas Vincent --- webseer.php | 18 +++++++++--------- webseer_proxies.php | 6 +++--- webseer_servers.php | 14 +++++++------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/webseer.php b/webseer.php index 767914c..0946c0a 100644 --- a/webseer.php +++ b/webseer.php @@ -927,7 +927,7 @@ function list_urls() { var title = $(this).attr('title'); if (title != undefined && title.indexOf('/') >= 0) { - $(this).click(function() { + $(this).on('click', function() { window.open(title, 'webseer'); }); } @@ -973,19 +973,19 @@ function clearFilter() { } $(function() { - $('#refresh, #state, #rows, #rfilter').change(function() { + $('#refresh, #state, #rows, #rfilter').on('change', function() { applyFilter(); }); - $('#go').click(function() { + $('#go').on('click', function() { applyFilter(); }); - $('#clear').click(function() { + $('#clear').on('click', function() { clearFilter(); }); - $('#form_webseer').submit(function(event) { + $('#form_webseer').on('submit', function(event) { event.preventDefault(); applyFilter(); }); @@ -1094,19 +1094,19 @@ function purgeEvents() { } $(function() { - $('#rows').change(function() { + $('#rows').on('change', function() { applyFilter(); }); - $('#clear').click(function() { + $('#clear').on('click', function() { clearFilter(); }); - $('#purge').click(function() { + $('#purge').on('click', function() { purgeEvents(); }); - $('#webseer').submit(function(event) { + $('#webseer').on('submit', function(event) { event.preventDefault(); applyFilter(); }); diff --git a/webseer_proxies.php b/webseer_proxies.php index e0c68b1..07bfaf1 100644 --- a/webseer_proxies.php +++ b/webseer_proxies.php @@ -384,15 +384,15 @@ function clearFilter() { } $(function() { - $('#rows').change(function() { + $('#rows').on('change', function() { applyFilter(); }); - $('#clear').click(function() { + $('#clear').on('click', function() { clearFilter(); }); - $('#webseer').submit(function(event) { + $('#webseer').on('submit', function(event) { event.preventDefault(); applyFilter(); }); diff --git a/webseer_servers.php b/webseer_servers.php index e99befc..e61f69e 100644 --- a/webseer_servers.php +++ b/webseer_servers.php @@ -588,7 +588,7 @@ function list_servers() { title = $(this).attr('title'); if (title != undefined && title.indexOf('/') >= 0) { - $(this).click(function() { + $(this).on('click', function() { window.open(title, 'webseer'); }); } @@ -710,15 +710,15 @@ function clearFilter() { } $(function() { - $('#refresh, #state, #rows').change(function() { + $('#refresh, #state, #rows').on('change', function() { applyFilter(); }); - $('#clear').click(function() { + $('#clear').on('click', function() { clearFilter(); }); - $('#webseer').submit(function(event) { + $('#webseer').on('submit', function(event) { event.preventDefault(); applyFilter(); }); @@ -811,15 +811,15 @@ function clearFilter() { } $(function() { - $('#rows').change(function() { + $('#rows').on('change', function() { applyFilter(); }); - $('#clear').click(function() { + $('#clear').on('click', function() { clearFilter(); }); - $('#webseer').submit(function(event) { + $('#webseer').on('submit', function(event) { event.preventDefault(); applyFilter(); }); From 58689c982a4e92bb46103abbd8fe1e8d12f7d678 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 9 Apr 2026 21:56:26 -0700 Subject: [PATCH 3/7] fix: pass allowed_classes option to unserialize, not base64_decode The options array was mistakenly passed as the second arg to base64_decode(), where it is interpreted as the boolean $strict parameter. The allowed_classes restriction is never applied. Move the options array to the second arg of unserialize(). Signed-off-by: Thomas Vincent --- includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index 84c3837..d38e838 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -72,7 +72,7 @@ function plugin_webseer_refresh_servers() { foreach ($results as $r) { if (substr($r, 0, 8) == 'SERVERS=') { $servers = substr($r, 8); - $servers = unserialize(base64_decode($servers, array('allowed_classes' => false))); + $servers = unserialize(base64_decode($servers), array('allowed_classes' => false)); if (isset($servers[0]['id'])) { db_execute('TRUNCATE TABLE plugin_webseer_servers'); foreach ($servers as $save) { @@ -104,7 +104,7 @@ function plugin_webseer_refresh_urls () { foreach ($results as $r) { if (substr($r, 0, 5) == 'URLS=') { $urls = substr($r, 5); - $urls = unserialize(base64_decode($urls, array('allowed_classes' => false))); + $urls = unserialize(base64_decode($urls), array('allowed_classes' => false)); if (isset($urls[0]['id'])) { db_execute('TRUNCATE TABLE plugin_webseer_urls'); From aa652b6ec7da2156ecd0330b19fe612e665afae1 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 9 Apr 2026 21:58:43 -0700 Subject: [PATCH 4/7] fix: harden base64_decode and unserialize with error checks - Use strict mode for base64_decode - Check decoded result before unserialize - Suppress unserialize warnings with @ and check is_array - Log and break on any failure Signed-off-by: Thomas Vincent --- includes/functions.php | 45 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index d38e838..be444d5 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -67,14 +67,30 @@ function plugin_webseer_refresh_servers() { $data['action'] = 'GETSERVERS'; $results = $cc->post($server['url'], $data); + if (!is_string($results)) { + return; + } + $results = explode("\n", $results); foreach ($results as $r) { + if (!is_string($r)) { + continue; + } if (substr($r, 0, 8) == 'SERVERS=') { - $servers = substr($r, 8); - $servers = unserialize(base64_decode($servers), array('allowed_classes' => false)); + $encoded = substr($r, 8); + $decoded = base64_decode($encoded, true); + if ($decoded === false) { + cacti_log('WARNING: plugin_webseer_refresh_servers failed to base64_decode response', false, 'WEBSEER'); + break; + } + $servers = @unserialize($decoded, array('allowed_classes' => false)); + if (!is_array($servers)) { + cacti_log('WARNING: plugin_webseer_refresh_servers failed to unserialize response', false, 'WEBSEER'); + break; + } if (isset($servers[0]['id'])) { - db_execute('TRUNCATE TABLE plugin_webseer_servers'); + db_execute_prepared('TRUNCATE TABLE plugin_webseer_servers'); foreach ($servers as $save) { db_execute_prepared('REPLACE INTO plugin_webseer_servers (id, enabled, master, name, url, ip, location) VALUES (?,?,?,?,?,?,?)', @@ -99,15 +115,32 @@ function plugin_webseer_refresh_urls () { $data = array(); $data['action'] = 'GETURLS'; $results = $cc->post($server['url'], $data); + + if (!is_string($results)) { + return; + } + $results = explode("\n", $results); foreach ($results as $r) { + if (!is_string($r)) { + continue; + } if (substr($r, 0, 5) == 'URLS=') { - $urls = substr($r, 5); - $urls = unserialize(base64_decode($urls), array('allowed_classes' => false)); + $encoded = substr($r, 5); + $decoded = base64_decode($encoded, true); + if ($decoded === false) { + cacti_log('WARNING: plugin_webseer_refresh_urls failed to base64_decode response', false, 'WEBSEER'); + break; + } + $urls = @unserialize($decoded, array('allowed_classes' => false)); + if (!is_array($urls)) { + cacti_log('WARNING: plugin_webseer_refresh_urls failed to unserialize response', false, 'WEBSEER'); + break; + } if (isset($urls[0]['id'])) { - db_execute('TRUNCATE TABLE plugin_webseer_urls'); + db_execute_prepared('TRUNCATE TABLE plugin_webseer_urls'); foreach ($urls as $save) { db_execute_prepared('REPLACE INTO plugin_webseer_urls From 272fe38628880c5a190e717cc5556ded3eba16c9 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 9 Apr 2026 23:03:28 -0700 Subject: [PATCH 5/7] fix(ci): Dependabot composer ecosystem, CodeQL PHP coverage - Change Dependabot ecosystem from npm to composer (PHP-only repo) - Remove PHP from CodeQL paths-ignore so security PRs get analysis - Remove committed .omc session artifacts, add .omc/ to .gitignore Signed-off-by: Thomas Vincent --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eff3ba3..ad44dd1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ *.swp .DS_Store locales/po/*.mo +.omc/ From 6c9f627a49e4f37357ba2d1e0fcf549e3f01a7fa Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Fri, 10 Apr 2026 05:59:45 -0700 Subject: [PATCH 6/7] fix: use db_execute for parameterless TRUNCATE Signed-off-by: Thomas Vincent --- includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index be444d5..92706e1 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -90,7 +90,7 @@ function plugin_webseer_refresh_servers() { break; } if (isset($servers[0]['id'])) { - db_execute_prepared('TRUNCATE TABLE plugin_webseer_servers'); + db_execute('TRUNCATE TABLE plugin_webseer_servers'); foreach ($servers as $save) { db_execute_prepared('REPLACE INTO plugin_webseer_servers (id, enabled, master, name, url, ip, location) VALUES (?,?,?,?,?,?,?)', @@ -140,7 +140,7 @@ function plugin_webseer_refresh_urls () { } if (isset($urls[0]['id'])) { - db_execute_prepared('TRUNCATE TABLE plugin_webseer_urls'); + db_execute('TRUNCATE TABLE plugin_webseer_urls'); foreach ($urls as $save) { db_execute_prepared('REPLACE INTO plugin_webseer_urls From a48c39d5fefe1157147e3803b4043be1a7f11745 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Fri, 10 Apr 2026 07:01:51 -0700 Subject: [PATCH 7/7] style: remove trailing double semicolons Signed-off-by: Thomas Vincent --- poller_webseer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poller_webseer.php b/poller_webseer.php index 2efc9fc..9470d4d 100644 --- a/poller_webseer.php +++ b/poller_webseer.php @@ -238,7 +238,7 @@ function plugin_webseer_update_servers() { foreach ($servers as $server) { $server['debug_type'] = 'Server'; - $cc = new cURL(true, 'cookies.txt', $server['compression'], '', $server);; + $cc = new cURL(true, 'cookies.txt', $server['compression'], '', $server); $data = array(); $data['action'] = 'HEARTBEAT';