From 3d40ac928c65a12cecda8ad0f002936f5051e875 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 94b4146ef19a91228859a909f702fde0dfe068f6 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 97efe5f0cb8669a7ae4b315784be6395472516df 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 1207f35322210cd6aaa32d762f4bc6f392536f1e 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 dea62e9eb0732ed7724024656a228e0b8d907ea1 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 99f1a4098587c0f6faec99422ee58ef8e7437410 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 75252ded529617f1f40b70296a4b8d2e9b811a21 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';