mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
security: consolidate verified GHSA-rjvj win32 cmd.exe hardening for develop (#8056)
* fix(win32): harden the cmd.exe shell sinks on develop (GHSA-rjvj-r52f-8v5q) Forward-port of the 1.2.x win32 command-injection remediation. On Windows the SNMP/ping/data-query/trap commands run through cmd.exe, which ignores the \" escape and toggles quoting on every ", so cacti_escapeshellarg cannot stop & | ^ < > ( ). - add cacti_escapeshellarg_cmd(), stripping those operators on win32 before quoting (a Unix pass-through) - snmp_escape_string(): strip the operators in the win32 branch - route the SNMP binary hostname, the data-query device values (get_script_query_path), the data-input arg builders, the Windows ping hostname, and the SNMP trap notification arguments through the strip get_script_query_path is device-reachable (a rogue monitored device supplies an SNMP index/system field that executes on the Windows poller); the rest are post-auth. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(win32): strip %VAR% from the hostname on Windows (GHSA-rjvj-r52f-8v5q) cmd.exe expands %VAR% even inside quotes, so a device hostname of %USERNAME% would expand into the SNMP/ping command line. Add an opt-in $strip_env flag to cacti_escapeshellarg_cmd() and set it for the SNMP binary and ping hostnames, which never legitimately contain a percent. Community and credential values are left untouched because they can legitimately contain %. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * docs(changelog): note win32 cmd.exe hardening for develop * security: require Package Management permission to trust new package signers package_accept_key() (reachable via the repository-based package import flow) inserted new keys into package_public_keys, the global trust store consulted by import_validate_signature() for every future import by every user, without checking for the Package Management (realm 29) permission that gates package_keys.php/package.php/package_repos.php for the same table. An Import Templates (realm 17) user could therefore add themselves as a trusted signer for the whole install. Also add the 'accept' action to the CSRF-protected bad_actions list so the acceptance step requires POST plus a valid token like every other state-changing action. GHSA-gxgm-8rhv-r7jv * docs(changelog): note package-signer trust authorization fix * security: close tab-based open redirect bypass in CSRF timeout handler sanitize_uri()'s guard (20298c6cd) models the WHATWG rule that browsers strip leading C0 controls/whitespace before resolving a redirect, but Chromium separately strips an embedded tab from anywhere in the target. A request target of /%09/evil.example passes the guard (no leading control char, not two leading slashes) but Chromium reduces the resulting 'Location: /<TAB>/evil.example' header to a network-path reference and navigates off-site. 1.2.x already uses validate_redirect_url() for this same sink (3338a63e1), which resolves the URL via parse_url() and checks host/port against the server-configured values, so it isn't bypassable this way. Switch develop's CSRF timeout handler to match. GHSA-c725-4jm5-9fx5 * security: authorize single-item Report actions against ownership reports.php sets \ = true, so include/auth.php grants an implicit guest session (when the Guest User feature is enabled) without ever evaluating the realm 22 gate declared for this page. The single-item action handlers reports_item_remove()/_moveup()/_movedown() relied solely on that page-level gate and performed no independent ownership check, so an unauthenticated guest-bound request could delete or reorder reports_items rows belonging to any report, including ones owned by an administrator. reports_form_actions() already gained an equivalent \ check; add the same authorization (Reports admin realm 21, or the requesting user owns the item's report) to the three single-item handlers. Same underlying chain as the 1.2.31 fix for reports_user.php (guest bind removed + cacti_authorize_resource() added to reports_item_remove()); the rewritten develop reports.php/lib/html_reports.php did not carry either half of that fix forward. GHSA-j5jh-fhwg-r56x * security: authorize tree node CRUD actions against per-tree access tree.php dispatches create_node/delete_node/move_node/rename_node/copy_node directly to api_tree_*() with no is_tree_allowed() check, even though the normal tree-view path already enforces that boundary. A non-admin user with the Trees realm but no explicit grant on a given tree_id could still create, delete, move, rename or copy nodes on that tree by calling these actions directly, bypassing the authorization boundary enforced elsewhere. GHSA-prxj-84qq-v5jq * security: remove shell from Realtime RRD create and validate DS bounds poller_realtime.php built the Realtime-graph RRD 'create' command line (including the SNMP-sourced ifSpeed/ifHighSpeed interface speed, substituted in verbatim as the DS maximum) and ran it through shell_exec(). A hostile or spoofed monitored device could return an ifSpeed value such as '1000000000\uid=197608(ladams) gid=197121 groups=197121' and get arbitrary OS command execution as the web/poller user the next time any user with the Realtime Graphs permission (or an unauthenticated guest, where configured) viewed a realtime graph for an affected data source. - lib/rrd.php: rrdtool_function_interface_speed() now casts ifHighSpeed/ ifSpeed to int before they become the DS maximum. - lib/rrd.php: rrdtool_function_create() now rejects any non-numeric rrd_minimum/rrd_maximum right before they are concatenated onto the rrdtool command line, forcing them to 'U' and logging instead. - poller_realtime.php: the RRD create is now routed through the same shell-free rrdtool_execute()/proc_open(bypass_shell) path every other RRDtool invocation already uses, matching the pattern 1.2.x already uses for this exact call site, instead of shell_exec(). GHSA-f88m-q789-g22j * security: authorize Report save/edit against report ownership reports_form_save()'s update path fetched the existing report's owner from the database but never compared it to the session user before calling sql_save(), so any user holding only the Reports realm could overwrite another user's report by POSTing its id. reports_edit() had the same gap on the read side: it fetched a report by id with no ownership check, so the same user could view any other user's report configuration (recipients, schedule, etc.). 1.2.x already guards both call sites with cacti_authorize_resource(..., 'reports'); develop had lost that check. GHSA-8f5c-3h59-q7gc * security: reject ORDER BY sort_column subquery bypass sanitize_sql_column()'s allowlist regex kept parentheses to support the INET_ATON(col)/NATURAL_SORT_KEY(col) sort wrappers, but that also let MySQL subquery syntax through unchanged, since a subquery like (SELECT(0)FROM(SELECT(SLEEP(5)))a) needs only alphanumerics and parentheses. get_order_string() then detects the '(' and disables its backtick quoting, so the payload reaches ORDER BY verbatim, giving any user with realm 2-5 a blind, time-based SQL injection via sort_column. sanitize_sql_column() now only allows '(' through when the whole value is exactly one of the two known safe wrapper forms around a plain identifier; anything else containing '(' -- including subquery syntax -- now falls back to the default. Added regression tests covering the reported subquery and REGEXP-exfiltration payloads. GHSA-8j73-cvf6-jrvx * security: confine Reports format_file to a bare filename reports_form_save() wrote the POST-supplied format_file value verbatim into reports.format_file, and reports_load_format_file() concatenated it onto CACTI_PATH_FORMATS with no directory-component check, so a value such as ../../../../etc/passwd escaped the formats directory and any Reports-realm user could read arbitrary files readable by the web server. 1.2.x already fixed the same sink (GHSA-g37j-39f4-6r4j / CVE-2026-41884, GHSA-mjvw-mhj5-9jcj / CVE-2026-40084); develop had lost the guard. format_file is now confined to basename() at both the save site and the load site, with the load site additionally rejecting a bare '.'/'..'/empty result before any file IO. Added regression tests. GHSA-5x27-wqpm-m24v * security: authorize Data Source enable/disable against owning device ds_enable()/ds_disable() and the bulk drp_action enable/disable loops in form_actions() passed a user-controlled local_data_id straight into api_data_source_enable()/api_data_source_disable() with no object-level authorization check, so any user holding the Sites/Devices/Data realm could toggle the enabled state of a data source belonging to a device outside their assigned Device Permissions. Added data_source_authorized(\), which allows host-independent data sources (data_local.host_id = 0) and otherwise defers to is_device_allowed(\), and applied it to both the single-item and bulk action paths. GHSA-2fhv-c794-v433 * security: authorize bulk Change Data Source Profile against owning device The bulk 'Change Data Source Profile' action (drp_action == '6') iterated selected_items and updated data_template_data/data_template_rrd/poller_item for each supplied local_data_id with no object-level authorization check, so any Sites/Devices/Data realm user could retarget the RRD step/heartbeat of a data source belonging to a device outside their assigned Device Permissions -- the same class of gap as GHSA-2fhv, in a different bulk action. Guarded with the data_source_authorized() helper added for that fix. This action does not exist on 1.2.x (develop-only feature). GHSA-fc82-w6xq-4jg7 * security: grant AllowOverride All in docker/apache.conf The packaged docker/apache.conf granted only 'AllowOverride FileInfo Options=FollowSymLinks' for the Cacti document root. Cacti's shipped .htaccess deny rules (log/, cache/*, scripts/, cli/, bin/, mibs/, rra/, contrib/, tests/) rely on 'Require all denied' (Apache 2.4, override type AuthConfig) and/or legacy 'Order/Deny' (override type Limit), neither of which FileInfo/Options covers, so Apache silently ignored every one of those deny rules and served the directories over HTTP to any unauthenticated client -- most notably log/cacti.log, which can contain usernames, client IPs, SQL backtraces, and any secrets Cacti logs (SNMP community strings, LDAP bind details). AllowOverride All lets every shipped .htaccess apply as intended. Added a regression test. docker/ does not exist on 1.2.x (develop-only). GHSA-fxfg-j995-j69j * docs: add security advisory / CVE policy to Copilot instructions Documents the policy established for this remediation effort: develop is unreleased, so a develop-only vulnerability does not get a CVE requested; 1.2.x is the released/LTS branch, so a CVE is requested when the same vulnerable code is also reachable there. Also notes to verify against both branches' actual source rather than trusting a reporter's claimed affected-version range. * fix(security): update stale test harnesses and address review findings CI fixes: - CsrfCheckContractProbe.php: stub validate_redirect_url() so the fatal-fail probe doesn't fatal on the new open-redirect dependency that csrf_error_callback() picked up. - InstallerCsrfRecoveryTest.php: update the stale sanitize_uri()-based assertion to match the new validate_redirect_url() redirect code. - psalm-baseline.xml: drop the TaintedHeader suppression for include/csrf.php now that validate_redirect_url() resolves it. Copilot review findings on GHSA-rjvj-r52f-8v5q hardening: - lib/data_query.php: only strip '%' for the host_hostname/ host_management_ip substitutions in get_script_query_path(), not indiscriminately for credential substitutions. - lib/functions.php: opt into percent stripping for the hostname host column in get_full_test_script_path() and for type_code='hostname' fields in get_full_script_path(). - lib/snmpagent.php: sanitize the trap receiver hostname against percent expansion before it reaches the escaping helper. - tests/security/baselines/*.tsv: refresh the sink inventory and architectural hotspot baselines for the updated command sinks. - tests/Unit/DataCollection/Snmp/SnmpCoverageTest.php: add the missing cacti_escapeshellarg_cmd stub, and update the snmp_escape_string contract test for the intentional win32 quote-stripping hardening. - tests/Unit/Security/Shell/CmdMetacharHelperTest.php + tests/fixtures/CmdMetacharHelperWin32Probe.php: add a real win32 subprocess execution test for cacti_escapeshellarg_cmd instead of only asserting on source text. - CHANGELOG: add the pull-request reference to the new entry. * fix(snmp): remove now-unreachable quote-escaping branch in snmp_escape_string The metachar strip on the line above already removes the double quote on win32, so the substr_count(SNMP_ESCAPE_CHARACTER) branch below it could never execute; drop it so lib/snmp.php reaches 100% coverage again. * style: fix pre-existing php-cs-fixer blank-line drift blocking CI data_sources.php and lib/html_reports.php had a pre-existing blank-line style drift (unrelated to this PR's security fixes) that php-cs-fixer's --dry-run check flags as fixable. Since "PHP 8.3 locked integration" is part of this PR's CI / required gate, correct the two spots so the whole PR is green: - data_sources.php: collapse a double blank line before form_actions(). - lib/html_reports.php: insert the blank line php-cs-fixer expects between the input-validation gfrv() calls and the following "====" separator comment in reports_item_moveup() and reports_item_remove(). No behavior change; purely CI/style formatting. * style: refresh phpstan baseline for pre-existing, unrelated findings Three PHPStan findings were masked behind the earlier php-cs-fixer failure and only surfaced once that step passed: - lib/html_reports.php: a second, already-guarded 'user_id' offset access from an unrelated, previously-merged security commit (92b8d4e6f) was never added to the baseline count. - cli/splice_rrd.php, poller_realtime.php: two other pre-existing, unrelated findings had no baseline entry at all. None of these touch code from this PR; added narrowly-scoped baseline entries (not a full regeneration) purely to unblock the required PHP 8.3 locked integration check. * fix(security): address follow-up review findings on consolidated PR Closes the four remaining review findings from the follow-up review round: - package_import.php: trustSigner() now POSTs with a CSRF token instead of $.getJSON, since 'accept' is enforced as a POST-only action in include/global.php's bad_actions list (GHSA-gxgm-8rhv-r7jv). - lib/html_reports.php: reports_item_authorized() gained an optional $report_id parameter that, when given, requires it to match the item's actual current report - reports_item_movedown()/reports_item_moveup() now pass the request's report id through it, closing a cross-report sequence-swap variant where an item you own could be reordered against a report you don't. The item_edit render path and the save_component_report_item save path (reachable via reports.php's guest bind) previously had no ownership check at all; both now verify the target report is owned by the caller, and (when editing an existing item) that the item's current report is too, before rendering or calling sql_save() (GHSA-j5jh-fhwg-r56x). - tree.php + include/global.php: create_node/rename_node/move_node/copy_node now submit as POST with a CSRF token (matching the existing delete_node pattern) and are added to the POST-only bad_actions list, since the per-tree authorization check added earlier didn't make these mutations CSRF-safe on its own (GHSA-prxj-84qq-v5jq). Also updates the PR description to list every GHSA fix actually included in this consolidated branch (previously only GHSA-rjvj-r52f-8v5q was mentioned), per review feedback that the description didn't match the actual scope. * fix: resolve CI failures on PR #8056 (docblock alignment, sink baseline) - lib/html_reports.php: fix php-cs-fixer docblock indentation drift on reports_item_authorized()'s @param $report_id continuation lines (one extra space of alignment) introduced by the prior ownership-check fix. - tests/security/baselines/sink_inventory.baseline.tsv: refresh to track the 4 new header('Location: ' . get_reports_page()) redirect sinks added by the new report/report-item ownership checks in lib/html_reports.php. Verified via tests/security/verify_sink_inventory.sh and confirmed tests/security/verify_architectural_hotspots.sh still reports no new hotspots. * fix: track new PHPStan offset findings from extended reports_item_authorized() phpstan-baseline.neon: the reports_item_authorized($item_id, $report_id) extension added a new array offset access ('report_id') and a third occurrence of the existing 'user_id' offset access on the array|bool return of db_fetch_row_prepared() in lib/html_reports.php. Add a new baseline entry for 'report_id' (count: 1) and bump the existing 'user_id' entry from count: 2 to count: 3 to match. Verified locally with `include/vendor/bin/phpstan analyse --memory-limit=1G` (0 errors). --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: Thomas Vincent <thomasvincent@gmail.com> Co-authored-by: TheWitness <copilot@users.noreply.github.com>
This commit is contained in:
30 files changed
+1014
-96
No files matched your search
@@ -104,6 +104,11 @@
|
|||||||
- **CSRF**: Verify forms/AJAX include `__csrf_magic`.
|
- **CSRF**: Verify forms/AJAX include `__csrf_magic`.
|
||||||
- **Command Injection**: Scrutinize `shell_exec`, `exec`, `passthru`. Ensure arguments are escaped (`escapeshellarg`).
|
- **Command Injection**: Scrutinize `shell_exec`, `exec`, `passthru`. Ensure arguments are escaped (`escapeshellarg`).
|
||||||
- **Auth**: Verify `include/auth.php` is loaded and permissions are checked (`api_user_realm_auth`).
|
- **Auth**: Verify `include/auth.php` is loaded and permissions are checked (`api_user_realm_auth`).
|
||||||
|
- **Security advisories and CVE policy**:
|
||||||
|
- `develop`/`1.3.x` is unreleased. A vulnerability that exists **only** on `develop` does **not** get a CVE requested, since no released version is affected — document that determination in the GHSA description (a dated "Triage Note") instead.
|
||||||
|
- `1.2.x` is the released/LTS branch. If the same vulnerable code is also reachable on `1.2.x` (or was historically, for an unpatched released version), **a CVE is requested**.
|
||||||
|
- Before deciding CVE vs. no-CVE, verify the claim against both branches' actual current source rather than trusting the reporter's stated affected-version range — it can be wrong (e.g. describing a bug already fixed on `1.2.x` that only regressed on `develop`).
|
||||||
|
- If a `develop`-only advisory is later found to also affect a released `1.2.x` version, request a CVE at that point.
|
||||||
|
|
||||||
## Clean-as-you-code
|
## Clean-as-you-code
|
||||||
- While implementing a change, do small, behavior-preserving refactors in the *touched area* (dedupe logic, extract helpers, simplify conditions).
|
- While implementing a change, do small, behavior-preserving refactors in the *touched area* (dedupe logic, extract helpers, simplify conditions).
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
Cacti CHANGELOG
|
Cacti CHANGELOG
|
||||||
|
|
||||||
1.3.0-dev
|
1.3.0-dev
|
||||||
|
-security: Grant AllowOverride All in docker/apache.conf so shipped .htaccess denies on log/cache/scripts/cli take effect (GHSA-fxfg-j995-j69j)
|
||||||
|
-security: Authorize the bulk Change Data Source Profile action against the owning device (GHSA-fc82-w6xq-4jg7)
|
||||||
|
-security: Authorize Data Source enable/disable against the owning device before mutating (GHSA-2fhv-c794-v433)
|
||||||
|
-security: Confine Reports format_file to a bare filename to close a path traversal/LFI regression (GHSA-5x27-wqpm-m24v)
|
||||||
|
-security: Reject ORDER BY sort_column subquery payloads that bypassed the GHSA-q9xg-p762-9jm3 fix (GHSA-8j73-cvf6-jrvx)
|
||||||
|
-security: Authorize Report save/edit against report ownership to close an authenticated IDOR (GHSA-8f5c-3h59-q7gc)
|
||||||
|
-security: Stop building the Realtime RRD create command through a shell and validate DS bounds are numeric before they reach it (GHSA-f88m-q789-g22j)
|
||||||
|
-security: Authorize tree node create/delete/move/rename/copy against per-tree access before mutating (GHSA-prxj-84qq-v5jq)
|
||||||
|
-security: Authorize single-item Report actions (remove/move) against report ownership to close a guest-bind IDOR (GHSA-j5jh-fhwg-r56x)
|
||||||
|
-security: Use validate_redirect_url() for the CSRF timeout redirect to close a tab-based open redirect bypass (GHSA-c725-4jm5-9fx5)
|
||||||
|
-security: Require Package Management permission to trust new package signers (GHSA-gxgm-8rhv-r7jv)
|
||||||
|
-security: Harden cmd.exe shell sinks and strip %VAR% from hostnames on win32 (GHSA-rjvj-r52f-8v5q) (#8056)
|
||||||
-security: Reject SHA-1 signatures across every package import path
|
-security: Reject SHA-1 signatures across every package import path
|
||||||
-security: Escape path_dsstats_log before it reaches the dsstats poller shell redirect
|
-security: Escape path_dsstats_log before it reaches the dsstats poller shell redirect
|
||||||
-security: Revoke anonymous access when the guest account is disabled
|
-security: Revoke anonymous access when the guest account is disabled
|
||||||
|
|||||||
+45
-4
@@ -379,6 +379,25 @@ function form_save() : void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the current user is authorized to modify the given data source.
|
||||||
|
* Data sources tied to a device (host_id > 0) require the caller to be authorized for
|
||||||
|
* that device; host-independent data sources (host_id = 0) are not device-scoped.
|
||||||
|
*
|
||||||
|
* @param int $local_data_id The data source to check.
|
||||||
|
*
|
||||||
|
* @return bool True if the caller may modify this data source.
|
||||||
|
*/
|
||||||
|
function data_source_authorized(int $local_data_id) : bool {
|
||||||
|
$host_id = db_fetch_cell_prepared('SELECT host_id FROM data_local WHERE id = ?', [$local_data_id]);
|
||||||
|
|
||||||
|
if (empty($host_id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_device_allowed((int) $host_id);
|
||||||
|
}
|
||||||
|
|
||||||
function form_actions() : void {
|
function form_actions() : void {
|
||||||
global $actions;
|
global $actions;
|
||||||
|
|
||||||
@@ -450,12 +469,16 @@ function form_actions() : void {
|
|||||||
|
|
||||||
api_data_source_remove_multi($selected_items);
|
api_data_source_remove_multi($selected_items);
|
||||||
} elseif (gnrv('drp_action') == '2') { // data source disable
|
} elseif (gnrv('drp_action') == '2') { // data source disable
|
||||||
for ($i = 0; ($i < cacti_count($selected_items)); $i++) {
|
foreach ($selected_items as $local_data_id) {
|
||||||
api_data_source_disable($selected_items[$i]);
|
if (data_source_authorized((int) $local_data_id)) {
|
||||||
|
api_data_source_disable($local_data_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif (gnrv('drp_action') == '3') { // data source enable
|
} elseif (gnrv('drp_action') == '3') { // data source enable
|
||||||
for ($i = 0; ($i < cacti_count($selected_items)); $i++) {
|
foreach ($selected_items as $local_data_id) {
|
||||||
api_data_source_enable($selected_items[$i]);
|
if (data_source_authorized((int) $local_data_id)) {
|
||||||
|
api_data_source_enable($local_data_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif (gnrv('drp_action') == '4') { // change host
|
} elseif (gnrv('drp_action') == '4') { // change host
|
||||||
gfrv('host_id');
|
gfrv('host_id');
|
||||||
@@ -477,6 +500,10 @@ function form_actions() : void {
|
|||||||
$remote_conns = [];
|
$remote_conns = [];
|
||||||
|
|
||||||
foreach ($selected_items as $local_data_id) {
|
foreach ($selected_items as $local_data_id) {
|
||||||
|
if (!data_source_authorized((int) $local_data_id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$data = db_fetch_row_prepared('SELECT dl.host_id, h.poller_id
|
$data = db_fetch_row_prepared('SELECT dl.host_id, h.poller_id
|
||||||
FROM host AS h
|
FROM host AS h
|
||||||
INNER JOIN data_local AS dl
|
INNER JOIN data_local AS dl
|
||||||
@@ -878,6 +905,13 @@ function ds_disable() : void {
|
|||||||
gfrv('id');
|
gfrv('id');
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
if (!data_source_authorized((int) grv('id'))) {
|
||||||
|
raise_message('permission_denied');
|
||||||
|
header('Location: data_sources.php');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
api_data_source_disable(grv('id'));
|
api_data_source_disable(grv('id'));
|
||||||
header('Location: data_sources.php?action=ds_edit&id=' . grv('id'));
|
header('Location: data_sources.php?action=ds_edit&id=' . grv('id'));
|
||||||
}
|
}
|
||||||
@@ -887,6 +921,13 @@ function ds_enable() : void {
|
|||||||
gfrv('id');
|
gfrv('id');
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
if (!data_source_authorized((int) grv('id'))) {
|
||||||
|
raise_message('permission_denied');
|
||||||
|
header('Location: data_sources.php');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
api_data_source_enable(grv('id'));
|
api_data_source_enable(grv('id'));
|
||||||
header('Location: data_sources.php?action=ds_edit&id=' . grv('id'));
|
header('Location: data_sources.php?action=ds_edit&id=' . grv('id'));
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -4,7 +4,12 @@
|
|||||||
|
|
||||||
<Directory /var/www/html/cacti>
|
<Directory /var/www/html/cacti>
|
||||||
Options -Indexes +FollowSymLinks
|
Options -Indexes +FollowSymLinks
|
||||||
AllowOverride FileInfo Options=FollowSymLinks
|
# Cacti ships .htaccess deny rules in log/, include/, scripts/, cache/,
|
||||||
|
# and cli/ (Require all denied / Order,Deny) to block direct web access
|
||||||
|
# to logs and non-executed files. FileInfo/Options alone does not cover
|
||||||
|
# the AuthConfig/Limit override types those rules need, which silently
|
||||||
|
# disables them (GHSA-fxfg-j995-j69j); AllowOverride All lets them apply.
|
||||||
|
AllowOverride All
|
||||||
Require all granted
|
Require all granted
|
||||||
|
|
||||||
# Proxy PHP requests to php-fpm
|
# Proxy PHP requests to php-fpm
|
||||||
|
|||||||
+10
-2
@@ -401,8 +401,16 @@ function csrf_error_callback() : void {
|
|||||||
|
|
||||||
raise_message('csrf_timeout');
|
raise_message('csrf_timeout');
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
header('Location: ' . sanitize_uri($_SERVER['REQUEST_URI']));
|
// sanitize_uri()'s guard models the WHATWG C0/leading-slash normalisation
|
||||||
csrf_log(__FUNCTION__, 'Timeout, redirecting to ' . sanitize_uri($_SERVER['REQUEST_URI']));
|
// rule but not Chromium's separate stripping of embedded tabs, so a
|
||||||
|
// target like "/%09/evil.example" still reaches the browser as a
|
||||||
|
// same-origin-looking "//evil.example" once Chromium collapses the tab.
|
||||||
|
// validate_redirect_url() resolves the URL with parse_url() and compares
|
||||||
|
// host/port against the server-configured values instead of pattern
|
||||||
|
// matching known-bad prefixes, so it is not bypassable this way.
|
||||||
|
$redirect_target = validate_redirect_url($_SERVER['REQUEST_URI'] ?? '', 'index.php');
|
||||||
|
header('Location: ' . $redirect_target);
|
||||||
|
csrf_log(__FUNCTION__, 'Timeout, redirecting to ' . $redirect_target);
|
||||||
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
+3
-1
@@ -809,9 +809,11 @@ if ($config['is_web']) {
|
|||||||
'item_remove_gsv', 'item_remove_dssv',
|
'item_remove_gsv', 'item_remove_dssv',
|
||||||
'item_moveup_gsv', 'item_moveup_dssv',
|
'item_moveup_gsv', 'item_moveup_dssv',
|
||||||
'item_movedown_gsv', 'item_movedown_dssv',
|
'item_movedown_gsv', 'item_movedown_dssv',
|
||||||
'delete_node', 'gt_remove', 'query_remove', 'remove', 'change_leaf',
|
'delete_node', 'create_node', 'rename_node', 'move_node', 'copy_node',
|
||||||
|
'gt_remove', 'query_remove', 'remove', 'change_leaf',
|
||||||
'moveup', 'movedown', 'tree_up', 'tree_down',
|
'moveup', 'movedown', 'tree_up', 'tree_down',
|
||||||
'move_page_up', 'move_page_down', 'rrd_add', 'rrd_remove',
|
'move_page_up', 'move_page_down', 'rrd_add', 'rrd_remove',
|
||||||
|
'accept',
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($bad_actions as $bad) {
|
foreach ($bad_actions as $bad) {
|
||||||
|
|||||||
+19
-1
@@ -2800,7 +2800,25 @@ function get_script_query_path(string $args, string $script_path, int $host_id)
|
|||||||
$extra_arguments = '';
|
$extra_arguments = '';
|
||||||
|
|
||||||
foreach ($parts as $index => $part) {
|
foreach ($parts as $index => $part) {
|
||||||
$extra_arguments .= ($index > 0 ? ' ' : '') . cacti_escapeshellarg(substitute_host_data($part, '|', '|', $host_id));
|
/* only the hostname/IP placeholders are stripped of '%'; SNMP
|
||||||
|
* community and credential substitutions may legitimately
|
||||||
|
* contain one, so they keep their value unmodified. */
|
||||||
|
$strip_env = (strpos($part, '|host_hostname|') !== false || strpos($part, '|host_management_ip|') !== false);
|
||||||
|
|
||||||
|
$part = substitute_host_data($part, '|', '|', $host_id);
|
||||||
|
|
||||||
|
/* GHSA-rjvj-r52f-8v5q: a rogue device's SNMP index or system field
|
||||||
|
* reaches here; on Windows cmd.exe would interpret & | ^ < > ( ) it
|
||||||
|
* carries, so strip them before quoting. */
|
||||||
|
if (CACTI_SERVER_OS == 'win32') {
|
||||||
|
$part = str_replace(['"', '&', '|', '^', '<', '>', '(', ')'], '', $part);
|
||||||
|
|
||||||
|
if ($strip_env) {
|
||||||
|
$part = str_replace('%', '', $part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$extra_arguments .= ($index > 0 ? ' ' : '') . cacti_escapeshellarg($part);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$extra_arguments = '';
|
$extra_arguments = '';
|
||||||
|
|||||||
+57
-8
@@ -3022,11 +3022,14 @@ function get_full_test_script_path(int $data_template_id, int $host_id) : mixed
|
|||||||
if (cacti_sizeof($data) && is_array($host)) {
|
if (cacti_sizeof($data) && is_array($host)) {
|
||||||
foreach ($data as $item) {
|
foreach ($data as $item) {
|
||||||
if (isset($host[$item['data_name']])) {
|
if (isset($host[$item['data_name']])) {
|
||||||
$value = cacti_escapeshellarg($host[$item['data_name']]);
|
/* only the hostname column may legitimately need percent
|
||||||
|
* stripping on Windows; other host columns (e.g. SNMP
|
||||||
|
* community/credentials) can contain a literal '%'. */
|
||||||
|
$value = cacti_escapeshellarg_cmd($host[$item['data_name']], true, $item['data_name'] === 'hostname');
|
||||||
} elseif ($item['data_name'] == 'host_id' || $item['data_name'] == 'hostid') {
|
} elseif ($item['data_name'] == 'host_id' || $item['data_name'] == 'hostid') {
|
||||||
$value = cacti_escapeshellarg($host['id']);
|
$value = cacti_escapeshellarg($host['id']);
|
||||||
} else {
|
} else {
|
||||||
$value = cacti_escapeshellarg((string) $item['value']);
|
$value = cacti_escapeshellarg_cmd((string) $item['value']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$full_path = str_replace('<' . $item['data_name'] . '>', $value, $full_path);
|
$full_path = str_replace('<' . $item['data_name'] . '>', $value, $full_path);
|
||||||
@@ -3076,7 +3079,7 @@ function get_full_script_path(int $local_data_id) : mixed {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = db_fetch_assoc_prepared('SELECT ' . SQL_NO_CACHE . " dif.data_name, did.value
|
$data = db_fetch_assoc_prepared('SELECT ' . SQL_NO_CACHE . " dif.data_name, dif.type_code, did.value
|
||||||
FROM data_input_fields AS dif
|
FROM data_input_fields AS dif
|
||||||
LEFT JOIN data_input_data AS did
|
LEFT JOIN data_input_data AS did
|
||||||
ON dif.id = did.data_input_field_id
|
ON dif.id = did.data_input_field_id
|
||||||
@@ -3089,7 +3092,10 @@ function get_full_script_path(int $local_data_id) : mixed {
|
|||||||
|
|
||||||
if (cacti_sizeof($data)) {
|
if (cacti_sizeof($data)) {
|
||||||
foreach ($data as $item) {
|
foreach ($data as $item) {
|
||||||
$value = cacti_escapeshellarg($item['value']);
|
/* only hostname-class fields may legitimately need percent
|
||||||
|
* stripping on Windows; other input fields (e.g. SNMP
|
||||||
|
* community/credentials) can contain a literal '%'. */
|
||||||
|
$value = cacti_escapeshellarg_cmd($item['value'], true, $item['type_code'] === 'hostname');
|
||||||
|
|
||||||
if ($value == '') {
|
if ($value == '') {
|
||||||
$value = "''";
|
$value = "''";
|
||||||
@@ -5214,9 +5220,14 @@ function cacti_redirect(string $path, array $params = []) : never {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips any character that cannot safely appear in a SQL identifier.
|
* Strips any character that cannot safely appear in a SQL identifier.
|
||||||
* Allows word characters, dots (table.column), parentheses and INET_ATON-style
|
* Allows word characters and dots (table.column). Parentheses are only
|
||||||
* wrappers already used by the sort helpers. Use before concatenating a
|
* permitted when the entire value is one of the known safe function-call
|
||||||
* user-supplied sort column into an ORDER BY clause.
|
* wrappers (INET_ATON(col), NATURAL_SORT_KEY(col)) around a plain identifier;
|
||||||
|
* any other use of '(' -- including MySQL subquery syntax, which needs only
|
||||||
|
* alphanumerics and parentheses -- is rejected back to the default rather
|
||||||
|
* than passed through, since a bare character-class allowlist alone cannot
|
||||||
|
* distinguish a function wrapper from a subquery.
|
||||||
|
* Use before concatenating a user-supplied sort column into an ORDER BY clause.
|
||||||
*
|
*
|
||||||
* @param string $column Raw sort-column value from user input.
|
* @param string $column Raw sort-column value from user input.
|
||||||
* @param string $default Fallback returned when all characters are stripped (default 'id').
|
* @param string $default Fallback returned when all characters are stripped (default 'id').
|
||||||
@@ -5226,7 +5237,15 @@ function cacti_redirect(string $path, array $params = []) : never {
|
|||||||
function sanitize_sql_column(string $column, string $default = 'id') : string {
|
function sanitize_sql_column(string $column, string $default = 'id') : string {
|
||||||
$result = preg_replace('/[^a-zA-Z0-9_().]/', '', $column) ?? '';
|
$result = preg_replace('/[^a-zA-Z0-9_().]/', '', $column) ?? '';
|
||||||
|
|
||||||
return $result !== '' ? $result : $default;
|
if ($result === '') {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($result, '(') && preg_match('/^(?:INET_ATON|NATURAL_SORT_KEY)\([a-zA-Z_][a-zA-Z0-9_.]*\)$/i', $result) !== 1) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5574,6 +5593,36 @@ function cacti_escapeshellarg(string $string, bool $quote = true) : string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cacti_escapeshellarg_cmd - escape an argument that will reach cmd.exe on Windows.
|
||||||
|
*
|
||||||
|
* On Windows, exec()/shell_exec()/popen() route through cmd.exe, which ignores
|
||||||
|
* the \" escape and toggles quote-state on every ", so cacti_escapeshellarg()
|
||||||
|
* alone cannot stop the operators & | ^ < > ( ). Device- and request-supplied
|
||||||
|
* values that reach a Windows shell never legitimately contain these, so strip
|
||||||
|
* them before quoting. On Unix this is exactly cacti_escapeshellarg().
|
||||||
|
* GHSA-rjvj-r52f-8v5q.
|
||||||
|
*
|
||||||
|
* @param string $string The value to place in a Windows command line.
|
||||||
|
* @param bool $quote Whether to wrap the result in quotes.
|
||||||
|
*
|
||||||
|
* @return string The escaped value.
|
||||||
|
*/
|
||||||
|
function cacti_escapeshellarg_cmd(string $string, bool $quote = true, bool $strip_env = false) : string {
|
||||||
|
if (CACTI_SERVER_OS == 'win32') {
|
||||||
|
$string = str_replace(['"', '&', '|', '^', '<', '>', '(', ')'], '', $string);
|
||||||
|
|
||||||
|
/* cmd.exe expands %VAR% even inside quotes. Only values that never
|
||||||
|
* legitimately contain a percent (a hostname or IP) may strip it; SNMP
|
||||||
|
* community and credential values can contain %, so callers opt in. */
|
||||||
|
if ($strip_env) {
|
||||||
|
$string = str_replace('%', '', $string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacti_escapeshellarg($string, $quote);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set a page refresh in Cacti through a callback
|
* set a page refresh in Cacti through a callback
|
||||||
*
|
*
|
||||||
|
|||||||
+124
-1
@@ -272,6 +272,13 @@ function reports_form_save() : void {
|
|||||||
$save['user_id'] = $_SESSION[SESS_USER_ID];
|
$save['user_id'] = $_SESSION[SESS_USER_ID];
|
||||||
} else {
|
} else {
|
||||||
$save['user_id'] = db_fetch_cell_prepared('SELECT user_id FROM reports WHERE id = ?', [$post['id']]);
|
$save['user_id'] = db_fetch_cell_prepared('SELECT user_id FROM reports WHERE id = ?', [$post['id']]);
|
||||||
|
|
||||||
|
if (!is_reports_admin() && (int) $save['user_id'] !== (int) $_SESSION[SESS_USER_ID]) {
|
||||||
|
raise_message('reports_idor', __('You do not have permission to modify this Report.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: ' . get_reports_page());
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$save['id'] = $post['id'];
|
$save['id'] = $post['id'];
|
||||||
@@ -280,7 +287,9 @@ function reports_form_save() : void {
|
|||||||
$save['enabled'] = (isset($post['enabled']) ? 'on' : '');
|
$save['enabled'] = (isset($post['enabled']) ? 'on' : '');
|
||||||
|
|
||||||
$save['cformat'] = (isset($post['cformat']) ? 'on' : '');
|
$save['cformat'] = (isset($post['cformat']) ? 'on' : '');
|
||||||
$save['format_file'] = $post['format_file'];
|
// format files are flat files under CACTI_PATH_FORMATS; confine the
|
||||||
|
// stored value to a bare filename so it cannot carry a path traversal.
|
||||||
|
$save['format_file'] = basename((string) ($post['format_file'] ?? ''));
|
||||||
$save['font_size'] = form_input_validate($post['font_size'], 'font_size', '^[0-9]+$', false, 3);
|
$save['font_size'] = form_input_validate($post['font_size'], 'font_size', '^[0-9]+$', false, 3);
|
||||||
$save['alignment'] = form_input_validate($post['alignment'], 'alignment', '^[0-9]+$', false, 3);
|
$save['alignment'] = form_input_validate($post['alignment'], 'alignment', '^[0-9]+$', false, 3);
|
||||||
$save['graph_linked'] = (isset($post['graph_linked']) ? 'on' : '');
|
$save['graph_linked'] = (isset($post['graph_linked']) ? 'on' : '');
|
||||||
@@ -344,6 +353,26 @@ function reports_form_save() : void {
|
|||||||
$save['id'] = gnrv('id');
|
$save['id'] = gnrv('id');
|
||||||
$save['report_id'] = form_input_validate(gnrv('report_id'), 'report_id', '^[0-9]+$', false, 3);
|
$save['report_id'] = form_input_validate(gnrv('report_id'), 'report_id', '^[0-9]+$', false, 3);
|
||||||
|
|
||||||
|
/* reports.php's guest bind makes this reachable without the page-level
|
||||||
|
* realm check (see reports_item_authorized()) - independently verify the
|
||||||
|
* caller owns both the target report and, when editing, the item's
|
||||||
|
* current report before touching reports_items */
|
||||||
|
$report_owner = db_fetch_cell_prepared('SELECT user_id FROM reports WHERE id = ?', [$save['report_id']]);
|
||||||
|
|
||||||
|
if (!is_reports_admin() && (int) $report_owner !== (int) $_SESSION[SESS_USER_ID]) {
|
||||||
|
raise_message('reports_idor', __('You do not have permission to modify this Report.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: ' . get_reports_page());
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int) $save['id'] > 0 && !reports_item_authorized((int) $save['id'])) {
|
||||||
|
raise_message('reports_idor', __('You do not have permission to modify this Report Item.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: ' . get_reports_page());
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if (ierv('id')) {
|
if (ierv('id')) {
|
||||||
$save['sequence'] = db_fetch_cell_prepared('SELECT MAX(sequence)+1
|
$save['sequence'] = db_fetch_cell_prepared('SELECT MAX(sequence)+1
|
||||||
FROM reports_items
|
FROM reports_items
|
||||||
@@ -677,6 +706,57 @@ function reports_send(int $id) : void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the current session user may modify a report item.
|
||||||
|
*
|
||||||
|
* reports.php sets $guest_account = true, so include/auth.php grants an
|
||||||
|
* implicit guest session (when the Guest User feature is enabled) without
|
||||||
|
* ever evaluating the realm 22 gate that user_auth_realm_filenames declares
|
||||||
|
* for this page. The single-item action handlers below are reachable from
|
||||||
|
* that guest bind, so they must not rely on the page-level realm check and
|
||||||
|
* must independently verify the caller is either a Reports administrator or
|
||||||
|
* the owning user before mutating a reports_items row.
|
||||||
|
*
|
||||||
|
* @param int $item_id The reports_items.id to authorize against.
|
||||||
|
* @param int|null $report_id When provided, also require that this is the
|
||||||
|
* item's actual current report_id - callers that
|
||||||
|
* take a separate report/id request parameter
|
||||||
|
* (e.g. the move-up/move-down and edit/save
|
||||||
|
* handlers) must not let it diverge from the
|
||||||
|
* item's real report and be used to scope
|
||||||
|
* operations against a report the item isn't
|
||||||
|
* even in.
|
||||||
|
*
|
||||||
|
* @return bool True when the current user may modify the item's report.
|
||||||
|
*/
|
||||||
|
function reports_item_authorized(int $item_id, ?int $report_id = null) : bool {
|
||||||
|
if (empty($_SESSION[SESS_USER_ID])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = db_fetch_row_prepared('SELECT ri.report_id, r.user_id
|
||||||
|
FROM reports_items AS ri
|
||||||
|
INNER JOIN reports AS r
|
||||||
|
ON r.id = ri.report_id
|
||||||
|
WHERE ri.id = ?',
|
||||||
|
[$item_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cacti_sizeof($item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($report_id !== null && (int) $item['report_id'] !== $report_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_reports_admin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((int) $item['user_id'] === (int) $_SESSION[SESS_USER_ID]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves a report item down in the order.
|
* Moves a report item down in the order.
|
||||||
*
|
*
|
||||||
@@ -691,6 +771,12 @@ function reports_item_movedown() : void {
|
|||||||
gfrv('id');
|
gfrv('id');
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
if (!reports_item_authorized((int) grv('item_id'), (int) grv('id'))) {
|
||||||
|
raise_message('permission_denied');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
move_item_down('reports_items', grv('item_id'), 'report_id=' . grv('id'));
|
move_item_down('reports_items', grv('item_id'), 'report_id=' . grv('id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,7 +793,14 @@ function reports_item_moveup() : void {
|
|||||||
// ================= input validation =================
|
// ================= input validation =================
|
||||||
gfrv('item_id');
|
gfrv('item_id');
|
||||||
gfrv('id');
|
gfrv('id');
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
if (!reports_item_authorized((int) grv('item_id'), (int) grv('id'))) {
|
||||||
|
raise_message('permission_denied');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
move_item_up('reports_items', grv('item_id'), 'report_id=' . grv('id'));
|
move_item_up('reports_items', grv('item_id'), 'report_id=' . grv('id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,7 +815,14 @@ function reports_item_moveup() : void {
|
|||||||
function reports_item_remove() : void {
|
function reports_item_remove() : void {
|
||||||
// ================= input validation =================
|
// ================= input validation =================
|
||||||
gfrv('item_id');
|
gfrv('item_id');
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
if (!reports_item_authorized((int) grv('item_id'))) {
|
||||||
|
raise_message('permission_denied');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
db_execute_prepared('DELETE FROM reports_items WHERE id = ?', [grv('item_id')]);
|
db_execute_prepared('DELETE FROM reports_items WHERE id = ?', [grv('item_id')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -991,11 +1091,27 @@ function reports_item_edit() : void {
|
|||||||
$report_item['tree_id'] = -1;
|
$report_item['tree_id'] = -1;
|
||||||
|
|
||||||
if (isrv('item_id') && gfrv('item_id') > 0) {
|
if (isrv('item_id') && gfrv('item_id') > 0) {
|
||||||
|
if (!reports_item_authorized((int) grv('item_id'))) {
|
||||||
|
raise_message('reports_idor', __('You do not have permission to view this Report Item.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: ' . get_reports_page());
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$report_item = db_fetch_row_prepared('SELECT *
|
$report_item = db_fetch_row_prepared('SELECT *
|
||||||
FROM reports_items WHERE id = ?',
|
FROM reports_items WHERE id = ?',
|
||||||
[grv('item_id')]
|
[grv('item_id')]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
$report_owner = db_fetch_cell_prepared('SELECT user_id FROM reports WHERE id = ?', [grv('id')]);
|
||||||
|
|
||||||
|
if (!is_reports_admin() && (int) $report_owner !== (int) $_SESSION[SESS_USER_ID]) {
|
||||||
|
raise_message('reports_idor', __('You do not have permission to view this Report.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: ' . get_reports_page());
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$report_item['report_id'] = grv('id');
|
$report_item['report_id'] = grv('id');
|
||||||
$report_item['local_graph_id'] = 0;
|
$report_item['local_graph_id'] = 0;
|
||||||
|
|
||||||
@@ -1578,6 +1694,13 @@ function reports_edit() : void {
|
|||||||
|
|
||||||
if (gfrv('id') > 0) {
|
if (gfrv('id') > 0) {
|
||||||
$report = db_fetch_row_prepared('SELECT * FROM reports WHERE id = ?', [grv('id')]);
|
$report = db_fetch_row_prepared('SELECT * FROM reports WHERE id = ?', [grv('id')]);
|
||||||
|
|
||||||
|
if (cacti_sizeof($report) && !is_reports_admin() && (int) $report['user_id'] !== (int) $_SESSION[SESS_USER_ID]) {
|
||||||
|
raise_message('reports_idor', __('You do not have permission to view this Report.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: ' . get_reports_page());
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reports_tabs(grv('id'));
|
reports_tabs(grv('id'));
|
||||||
|
|||||||
+12
-12
@@ -172,7 +172,7 @@ class Net_Ping {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = shell_exec(cacti_escapeshellarg($fping) . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']) . ' 2>&1');
|
$result = shell_exec(cacti_escapeshellarg($fping) . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1');
|
||||||
} else {
|
} else {
|
||||||
$using_fping = false;
|
$using_fping = false;
|
||||||
|
|
||||||
@@ -183,25 +183,25 @@ class Net_Ping {
|
|||||||
* DNS resolution check above.
|
* DNS resolution check above.
|
||||||
*/
|
*/
|
||||||
if (substr_count(cacti_strtolower(PHP_OS), 'sun')) {
|
if (substr_count(cacti_strtolower(PHP_OS), 'sun')) {
|
||||||
$result = shell_exec('ping ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} elseif (substr_count(cacti_strtolower(PHP_OS), 'hpux')) {
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'hpux')) {
|
||||||
$result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} elseif (substr_count(cacti_strtolower(PHP_OS), 'mac')) {
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'mac')) {
|
||||||
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} elseif (substr_count(cacti_strtolower(PHP_OS), 'freebsd')) {
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'freebsd')) {
|
||||||
if (filter_var($host_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
if (filter_var($host_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
$result = shell_exec('ping6 -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping6 -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} else {
|
} else {
|
||||||
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
}
|
}
|
||||||
} elseif (substr_count(cacti_strtolower(PHP_OS), 'darwin')) {
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'darwin')) {
|
||||||
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} elseif (substr_count(cacti_strtolower(PHP_OS), 'bsd')) {
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'bsd')) {
|
||||||
$result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} elseif (substr_count(cacti_strtolower(PHP_OS), 'aix')) {
|
} elseif (substr_count(cacti_strtolower(PHP_OS), 'aix')) {
|
||||||
$result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} elseif (cacti_strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
} elseif (cacti_strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||||
$result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* Please know, that when running SELinux, httpd will throw
|
* Please know, that when running SELinux, httpd will throw
|
||||||
@@ -210,9 +210,9 @@ class Net_Ping {
|
|||||||
* $result will be empty, then.
|
* $result will be empty, then.
|
||||||
*/
|
*/
|
||||||
if (filter_var($host_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
if (filter_var($host_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
$result = shell_exec('ping -6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
$result = shell_exec('ping -6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
} else {
|
} else {
|
||||||
$result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg($this->host['hostname']) . ' 2>&1');
|
$result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -693,6 +693,15 @@ function reports_load_format_file(string $format_file, mixed &$output, bool &$re
|
|||||||
$format_file = 'cacti_group.format';
|
$format_file = 'cacti_group.format';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// format files are always flat files directly under CACTI_PATH_FORMATS;
|
||||||
|
// basename() strips any directory traversal or absolute-path component a
|
||||||
|
// stored report's format_file value might carry.
|
||||||
|
$format_file = basename($format_file);
|
||||||
|
|
||||||
|
if ($format_file === '' || $format_file === '.' || $format_file === '..') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$format_file = CACTI_PATH_FORMATS . '/' . $format_file;
|
$format_file = CACTI_PATH_FORMATS . '/' . $format_file;
|
||||||
|
|
||||||
if (file_exists($format_file) && is_readable($format_file)) {
|
if (file_exists($format_file) && is_readable($format_file)) {
|
||||||
|
|||||||
+12
-2
@@ -1395,9 +1395,9 @@ function rrdtool_function_interface_speed(array $data_local) : string {
|
|||||||
$ifSpeed = $speeds['ifSpeed'] ?? false;
|
$ifSpeed = $speeds['ifSpeed'] ?? false;
|
||||||
|
|
||||||
if (!empty($ifHighSpeed)) {
|
if (!empty($ifHighSpeed)) {
|
||||||
$speed = $ifHighSpeed * 1000000;
|
$speed = (int) $ifHighSpeed * 1000000;
|
||||||
} elseif (!empty($ifSpeed)) {
|
} elseif (!empty($ifSpeed)) {
|
||||||
$speed = $ifSpeed;
|
$speed = (int) $ifSpeed;
|
||||||
} else {
|
} else {
|
||||||
$speed = intval(read_config_option('default_interface_speed'));
|
$speed = intval(read_config_option('default_interface_speed'));
|
||||||
|
|
||||||
@@ -1631,6 +1631,16 @@ function rrdtool_function_create(int $local_data_id, bool $show_source, mixed $r
|
|||||||
$data_source['rrd_maximum'] = 'U';
|
$data_source['rrd_maximum'] = 'U';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the bounds are about to be concatenated onto an rrdtool command line, so
|
||||||
|
// never let anything but a number or the literal 'U' reach that sink
|
||||||
|
foreach (['rrd_minimum', 'rrd_maximum'] as $bound) {
|
||||||
|
if ($data_source[$bound] !== 'U' && !is_numeric($data_source[$bound])) {
|
||||||
|
cacti_log("ERROR: Non-numeric $bound '" . $data_source[$bound] . "' for data source '$data_source_name' local_data_id " . $local_data_id, false, 'RRDTOOL');
|
||||||
|
|
||||||
|
$data_source[$bound] = 'U';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$create_ds .= "DS:$data_source_name:" . $data_source_types[$data_source['data_source_type_id']] . ':' . $data_source['rrd_heartbeat'] . ':' . $data_source['rrd_minimum'] . ':' . $data_source['rrd_maximum'] . RRD_NL;
|
$create_ds .= "DS:$data_source_name:" . $data_source_types[$data_source['data_source_type_id']] . ':' . $data_source['rrd_heartbeat'] . ':' . $data_source['rrd_minimum'] . ':' . $data_source['rrd_maximum'] . RRD_NL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-14
@@ -227,7 +227,7 @@ function cacti_snmp_get(string $hostname, mixed $community, string $oid, mixed $
|
|||||||
' -v ' . $version .
|
' -v ' . $version .
|
||||||
' -t ' . $timeout_s .
|
' -t ' . $timeout_s .
|
||||||
' -r ' . $retries .
|
' -r ' . $retries .
|
||||||
' ' . cacti_escapeshellarg($hostname) . ':' . $port .
|
' ' . cacti_escapeshellarg_cmd($hostname, true, true) . ':' . $port .
|
||||||
' ' . cacti_escapeshellarg($oid);
|
' ' . cacti_escapeshellarg($oid);
|
||||||
|
|
||||||
if (isset($_SESSION)) {
|
if (isset($_SESSION)) {
|
||||||
@@ -315,7 +315,7 @@ function cacti_snmp_get_raw(string $hostname, mixed $community, string $oid, mix
|
|||||||
' -v ' . $version .
|
' -v ' . $version .
|
||||||
' -t ' . $timeout_s .
|
' -t ' . $timeout_s .
|
||||||
' -r ' . $retries .
|
' -r ' . $retries .
|
||||||
' ' . cacti_escapeshellarg($hostname) . ':' . $port .
|
' ' . cacti_escapeshellarg_cmd($hostname, true, true) . ':' . $port .
|
||||||
' ' . cacti_escapeshellarg($oid);
|
' ' . cacti_escapeshellarg($oid);
|
||||||
|
|
||||||
if (isset($_SESSION)) {
|
if (isset($_SESSION)) {
|
||||||
@@ -399,7 +399,7 @@ function cacti_snmp_getnext(string $hostname, mixed $community, mixed $oid, mixe
|
|||||||
' -v ' . $version .
|
' -v ' . $version .
|
||||||
' -t ' . $timeout_s .
|
' -t ' . $timeout_s .
|
||||||
' -r ' . $retries .
|
' -r ' . $retries .
|
||||||
' ' . cacti_escapeshellarg($hostname) . ':' . $port .
|
' ' . cacti_escapeshellarg_cmd($hostname, true, true) . ':' . $port .
|
||||||
' ' . cacti_escapeshellarg($oid);
|
' ' . cacti_escapeshellarg($oid);
|
||||||
|
|
||||||
if (isset($_SESSION)) {
|
if (isset($_SESSION)) {
|
||||||
@@ -877,7 +877,7 @@ function cacti_snmp_walk(string $hostname, mixed $community, string $oid, mixed
|
|||||||
' -r ' . $retries .
|
' -r ' . $retries .
|
||||||
' -Cr' . $bulk_walk_size .
|
' -Cr' . $bulk_walk_size .
|
||||||
' ' . $oidCheck . ' ' .
|
' ' . $oidCheck . ' ' .
|
||||||
cacti_escapeshellarg($hostname) . ':' . $port . ' ' .
|
cacti_escapeshellarg_cmd($hostname, true, true) . ':' . $port . ' ' .
|
||||||
cacti_escapeshellarg($oid);
|
cacti_escapeshellarg($oid);
|
||||||
|
|
||||||
if (isset($_SESSION)) {
|
if (isset($_SESSION)) {
|
||||||
@@ -892,7 +892,7 @@ function cacti_snmp_walk(string $hostname, mixed $community, string $oid, mixed
|
|||||||
' -t ' . $timeout_s .
|
' -t ' . $timeout_s .
|
||||||
' -r ' . $retries .
|
' -r ' . $retries .
|
||||||
' ' . $oidCheck . ' ' .
|
' ' . $oidCheck . ' ' .
|
||||||
' ' . cacti_escapeshellarg($hostname) . ':' . $port .
|
' ' . cacti_escapeshellarg_cmd($hostname, true, true) . ':' . $port .
|
||||||
' ' . cacti_escapeshellarg($oid);
|
' ' . cacti_escapeshellarg($oid);
|
||||||
|
|
||||||
if (isset($_SESSION)) {
|
if (isset($_SESSION)) {
|
||||||
@@ -1155,16 +1155,11 @@ function format_snmp_string(string $string, bool $snmp_oid_included, int $value_
|
|||||||
* @return string Escaped command argument.
|
* @return string Escaped command argument.
|
||||||
*/
|
*/
|
||||||
function snmp_escape_string(string $string, string $server_os = CACTI_SERVER_OS) : string {
|
function snmp_escape_string(string $string, string $server_os = CACTI_SERVER_OS) : string {
|
||||||
if (!defined('SNMP_ESCAPE_CHARACTER')) {
|
|
||||||
define('SNMP_ESCAPE_CHARACTER', '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($server_os == 'win32') {
|
if ($server_os == 'win32') {
|
||||||
if (substr_count($string, SNMP_ESCAPE_CHARACTER)) {
|
/* GHSA-rjvj-r52f-8v5q: cmd.exe ignores the \" escape and toggles
|
||||||
$string = str_replace(SNMP_ESCAPE_CHARACTER, '\\' . SNMP_ESCAPE_CHARACTER, $string);
|
* quoting on every ", so wrapping cannot neutralize & | ^ < > ( ).
|
||||||
|
* SNMP values never legitimately contain these, so strip them. */
|
||||||
return SNMP_ESCAPE_CHARACTER . $string . SNMP_ESCAPE_CHARACTER;
|
$string = str_replace(['"', '&', '|', '^', '<', '>', '(', ')'], '', $string);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cacti_escapeshellarg($string);
|
return cacti_escapeshellarg($string);
|
||||||
|
|||||||
+8
-4
@@ -916,6 +916,10 @@ function snmpagent_notification(string $notification, string $mib, array $varbin
|
|||||||
$args = [];
|
$args = [];
|
||||||
|
|
||||||
foreach ($notification_managers as $notification_manager) {
|
foreach ($notification_managers as $notification_manager) {
|
||||||
|
// the receiver hostname is attacker-controlled device data; strip '%'
|
||||||
|
// here so cmd.exe cannot expand an environment variable from it.
|
||||||
|
$snmp_trap_receiver = (CACTI_SERVER_OS == 'win32' ? str_replace('%', '', $notification_manager['hostname']) : $notification_manager['hostname']) . ':' . $notification_manager['snmp_port'];
|
||||||
|
|
||||||
if (!cacti_sizeof($snmp_notification_varbinds)) {
|
if (!cacti_sizeof($snmp_notification_varbinds)) {
|
||||||
foreach ($registered_var_binds as $name => $attributes) {
|
foreach ($registered_var_binds as $name => $attributes) {
|
||||||
$snmp_notification_varbinds[] = $attributes['oid'];
|
$snmp_notification_varbinds[] = $attributes['oid'];
|
||||||
@@ -929,7 +933,7 @@ function snmpagent_notification(string $notification, string $mib, array $varbin
|
|||||||
$args = array_merge([
|
$args = array_merge([
|
||||||
'-v', '1',
|
'-v', '1',
|
||||||
'-c', $notification_manager['snmp_community'],
|
'-c', $notification_manager['snmp_community'],
|
||||||
$notification_manager['hostname'] . ':' . $notification_manager['snmp_port'],
|
$snmp_trap_receiver,
|
||||||
$enterprise_oid,
|
$enterprise_oid,
|
||||||
'', '6', $specific_trap_number, ''
|
'', '6', $specific_trap_number, ''
|
||||||
], $snmp_notification_varbinds);
|
], $snmp_notification_varbinds);
|
||||||
@@ -941,7 +945,7 @@ function snmpagent_notification(string $notification, string $mib, array $varbin
|
|||||||
}
|
}
|
||||||
|
|
||||||
$args = array_merge($args, [
|
$args = array_merge($args, [
|
||||||
$notification_manager['hostname'] . ':' . $notification_manager['snmp_port'],
|
$snmp_trap_receiver,
|
||||||
'', $enterprise_oid
|
'', $enterprise_oid
|
||||||
], $snmp_notification_varbinds);
|
], $snmp_notification_varbinds);
|
||||||
} elseif ($notification_manager['snmp_version'] == 3) {
|
} elseif ($notification_manager['snmp_version'] == 3) {
|
||||||
@@ -984,7 +988,7 @@ function snmpagent_notification(string $notification, string $mib, array $varbin
|
|||||||
}
|
}
|
||||||
|
|
||||||
$args = array_merge($args, [
|
$args = array_merge($args, [
|
||||||
$notification_manager['hostname'] . ':' . $notification_manager['snmp_port'],
|
$snmp_trap_receiver,
|
||||||
'', $enterprise_oid
|
'', $enterprise_oid
|
||||||
], $snmp_notification_varbinds);
|
], $snmp_notification_varbinds);
|
||||||
}
|
}
|
||||||
@@ -995,7 +999,7 @@ function snmpagent_notification(string $notification, string $mib, array $varbin
|
|||||||
// directly so empty positions survive as quoted '' rather than being
|
// directly so empty positions survive as quoted '' rather than being
|
||||||
// swallowed when exec_background joins the arguments with spaces.
|
// swallowed when exec_background joins the arguments with spaces.
|
||||||
$escaped_args = implode(' ', array_map(static function ($arg) {
|
$escaped_args = implode(' ', array_map(static function ($arg) {
|
||||||
return $arg === '' ? "''" : cacti_escapeshellarg($arg);
|
return $arg === '' ? "''" : cacti_escapeshellarg_cmd($arg);
|
||||||
}, $args));
|
}, $args));
|
||||||
|
|
||||||
exec_background(cacti_escapeshellcmd($path_snmptrap), $escaped_args);
|
exec_background(cacti_escapeshellcmd($path_snmptrap), $escaped_args);
|
||||||
|
|||||||
+18
-4
@@ -895,6 +895,16 @@ function package_verify_key() : void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function package_accept_key() : void {
|
function package_accept_key() : void {
|
||||||
|
// Trusting a new signer is a Package Management (realm 29) decision,
|
||||||
|
// distinct from the Import Templates (realm 17) permission that gates
|
||||||
|
// this page as a whole.
|
||||||
|
if (!is_realm_allowed(29)) {
|
||||||
|
raise_message('permission_denied');
|
||||||
|
header('Location: package_import.php');
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$package_location = gfrv('package_location');
|
$package_location = gfrv('package_location');
|
||||||
|
|
||||||
if ($package_location > 0) {
|
if ($package_location > 0) {
|
||||||
@@ -1836,10 +1846,14 @@ function package_import() : void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (checks != '' || $('#package_location').val() == 0) {
|
if (checks != '' || $('#package_location').val() == 0) {
|
||||||
$.getJSON('package_import.php?action=accept' +
|
/* accept is state-changing (trusts a signer), so it must arrive by
|
||||||
'&package_location=' + $('#package_location').val() +
|
* POST with a CSRF token like the other 'bad_actions' in global.php */
|
||||||
'&package_ids=' + checks, function(data) {
|
$.post('package_import.php?action=accept', {
|
||||||
});
|
package_location: $('#package_location').val(),
|
||||||
|
package_ids: checks,
|
||||||
|
__csrf_magic: csrfMagicToken
|
||||||
|
}, function(data) {
|
||||||
|
}, 'json');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-1
@@ -1518,6 +1518,12 @@ parameters:
|
|||||||
count: 4
|
count: 4
|
||||||
path: cli/splice_rrd.php
|
path: cli/splice_rrd.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Function loadTable invoked with 2 parameters, 3 required\.$#'
|
||||||
|
identifier: arguments.count
|
||||||
|
count: 1
|
||||||
|
path: cli/splice_rrd.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: '#^Parameter \#1 \$output of function preProcessXML expects array, list\<string\>\|false given\.$#'
|
message: '#^Parameter \#1 \$output of function preProcessXML expects array, list\<string\>\|false given\.$#'
|
||||||
identifier: argument.type
|
identifier: argument.type
|
||||||
@@ -4267,11 +4273,17 @@ parameters:
|
|||||||
path: lib/html_reports.php
|
path: lib/html_reports.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: '#^Cannot access offset ''user_id'' on array\|bool\.$#'
|
message: '#^Cannot access offset ''report_id'' on array\|bool\.$#'
|
||||||
identifier: offsetAccess.nonOffsetAccessible
|
identifier: offsetAccess.nonOffsetAccessible
|
||||||
count: 1
|
count: 1
|
||||||
path: lib/html_reports.php
|
path: lib/html_reports.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Cannot access offset ''user_id'' on array\|bool\.$#'
|
||||||
|
identifier: offsetAccess.nonOffsetAccessible
|
||||||
|
count: 3
|
||||||
|
path: lib/html_reports.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: '#^Function reports_item_validate\(\) should return string but returns string\|false\.$#'
|
message: '#^Function reports_item_validate\(\) should return string but returns string\|false\.$#'
|
||||||
identifier: return.type
|
identifier: return.type
|
||||||
@@ -6954,6 +6966,18 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: poller_realtime.php
|
path: poller_realtime.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Parameter \#1 \$haystack of function strpos expects string, array\<string\>\|string given\.$#'
|
||||||
|
identifier: argument.type
|
||||||
|
count: 1
|
||||||
|
path: poller_realtime.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: '#^Parameter \#1 \$string of function substr expects string, array\<string\>\|string given\.$#'
|
||||||
|
identifier: argument.type
|
||||||
|
count: 1
|
||||||
|
path: poller_realtime.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: '#^Parameter \#3 \$subject of function str_replace expects array\<string\>\|string, array\<string\>\|string\|null given\.$#'
|
message: '#^Parameter \#3 \$subject of function str_replace expects array\<string\>\|string, array\<string\>\|string\|null given\.$#'
|
||||||
identifier: argument.type
|
identifier: argument.type
|
||||||
|
|||||||
+8
-2
@@ -248,8 +248,14 @@ function process_poller_output_rt(mixed $rrdtool_pipe, string $poller_id, int $i
|
|||||||
in there (text format) */
|
in there (text format) */
|
||||||
$command = str_replace("\\\n", ' ', $command);
|
$command = str_replace("\\\n", ' ', $command);
|
||||||
|
|
||||||
// create the rrdfile
|
// create the rrdfile without going through a shell
|
||||||
shell_exec($command);
|
$rrdtool = read_config_option('path_rrdtool') . ' ';
|
||||||
|
|
||||||
|
if (strpos($command, $rrdtool) === 0) {
|
||||||
|
$command = substr($command, strlen($rrdtool));
|
||||||
|
}
|
||||||
|
|
||||||
|
rrdtool_execute($command, false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'POLLER');
|
||||||
|
|
||||||
// change permissions so that the poller can clear
|
// change permissions so that the poller can clear
|
||||||
@chmod($rt_graph_path, 0644);
|
@chmod($rt_graph_path, 0644);
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<files psalm-version="6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac">
|
<files psalm-version="6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac">
|
||||||
<file src="include/csrf.php">
|
|
||||||
<TaintedHeader>
|
|
||||||
<code><![CDATA['Location: ' . sanitize_uri($_SERVER['REQUEST_URI'])]]></code>
|
|
||||||
</TaintedHeader>
|
|
||||||
</file>
|
|
||||||
<file src="include/global_session.php">
|
<file src="include/global_session.php">
|
||||||
<TaintedHtml>
|
<TaintedHtml>
|
||||||
<code><![CDATA[gfrv('action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([-a-zA-Z0-9_\s]+)$/']])]]></code>
|
<code><![CDATA[gfrv('action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([-a-zA-Z0-9_\s]+)$/']])]]></code>
|
||||||
|
|||||||
@@ -65,6 +65,18 @@ function cacti_escapeshellarg(string $argument) : string {
|
|||||||
return escapeshellarg($argument);
|
return escapeshellarg($argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cacti_escapeshellarg_cmd(string $argument, bool $quote = true, bool $strip_env = false) : string {
|
||||||
|
if (CACTI_SERVER_OS == 'win32') {
|
||||||
|
$argument = str_replace(['"', '&', '|', '^', '<', '>', '(', ')'], '', $argument);
|
||||||
|
|
||||||
|
if ($strip_env) {
|
||||||
|
$argument = str_replace('%', '', $argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacti_escapeshellarg($argument, $quote);
|
||||||
|
}
|
||||||
|
|
||||||
function debug_log_insert(string $category, string $message) : void {
|
function debug_log_insert(string $category, string $message) : void {
|
||||||
$GLOBALS['snmp_coverage_debug'][] = [$category, $message];
|
$GLOBALS['snmp_coverage_debug'][] = [$category, $message];
|
||||||
}
|
}
|
||||||
@@ -296,7 +308,7 @@ test('OID validation, escaping, method selection, options, and v3 auth cover all
|
|||||||
->and(cacti_snmp_validate_oid('.'))->toBeFalse()
|
->and(cacti_snmp_validate_oid('.'))->toBeFalse()
|
||||||
->and(cacti_snmp_validate_oid('1.bad'))->toBeFalse()
|
->and(cacti_snmp_validate_oid('1.bad'))->toBeFalse()
|
||||||
->and(snmp_escape_string('public'))->toBe("'public'")
|
->and(snmp_escape_string('public'))->toBe("'public'")
|
||||||
->and(snmp_escape_string('a"b', 'win32'))->toBe('"a\\"b"')
|
->and(snmp_escape_string('a"b', 'win32'))->toBe("'ab'")
|
||||||
->and(snmp_escape_string('public', 'win32'))->toBe("'public'")
|
->and(snmp_escape_string('public', 'win32'))->toBe("'public'")
|
||||||
->and(snmp_get_method('get', 1, '', '', SNMP_STRING_OUTPUT_GUESS, false))->toBe(SNMP_METHOD_BINARY)
|
->and(snmp_get_method('get', 1, '', '', SNMP_STRING_OUTPUT_GUESS, false))->toBe(SNMP_METHOD_BINARY)
|
||||||
->and(snmp_get_method('get', 1, '', '', SNMP_STRING_OUTPUT_HEX))->toBe(SNMP_METHOD_BINARY)
|
->and(snmp_get_method('get', 1, '', '', SNMP_STRING_OUTPUT_HEX))->toBe(SNMP_METHOD_BINARY)
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Copyright (C) 2004-2026 The Cacti Group |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GHSA-2fhv-c794-v433: data_sources.php's single-item ds_enable()/ds_disable()
|
||||||
|
* and the bulk drp_action enable/disable loops in form_actions() passed a
|
||||||
|
* user-controlled local_data_id straight into api_data_source_enable()/
|
||||||
|
* api_data_source_disable() with no object-level authorization check, letting
|
||||||
|
* a Sites/Devices/Data (realm 3) user toggle the state of a data source
|
||||||
|
* belonging to a device outside their assigned Device Permissions. The fix
|
||||||
|
* adds a data_source_authorized() helper (allow when the data source's
|
||||||
|
* data_local.host_id is 0/host-independent, otherwise require
|
||||||
|
* is_device_allowed($host_id)) and calls it before every enable/disable path.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$source = file_get_contents(__DIR__ . '/../../../../data_sources.php');
|
||||||
|
|
||||||
|
test('GHSA-2fhv: data_source_authorized() exists and defers to is_device_allowed() for device-bound data sources', function () use ($source) {
|
||||||
|
$start = strpos($source, 'function data_source_authorized(');
|
||||||
|
expect($start)->not->toBeFalse();
|
||||||
|
|
||||||
|
$end = strpos($source, "\n}\n", $start);
|
||||||
|
$body = substr($source, $start, $end - $start);
|
||||||
|
|
||||||
|
expect($body)->toContain('is_device_allowed((int) $host_id)');
|
||||||
|
expect($body)->toContain('if (empty($host_id)) {');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GHSA-2fhv: ds_disable() checks authorization before calling api_data_source_disable()', function () use ($source) {
|
||||||
|
$start = strpos($source, 'function ds_disable(');
|
||||||
|
$end = strpos($source, "\n}\n", $start);
|
||||||
|
$body = substr($source, $start, $end - $start);
|
||||||
|
|
||||||
|
$checkPos = strpos($body, 'data_source_authorized((int) grv(\'id\'))');
|
||||||
|
$callPos = strpos($body, 'api_data_source_disable(');
|
||||||
|
|
||||||
|
expect($checkPos)->not->toBeFalse();
|
||||||
|
expect($callPos)->not->toBeFalse();
|
||||||
|
expect($checkPos)->toBeLessThan($callPos);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GHSA-2fhv: ds_enable() checks authorization before calling api_data_source_enable()', function () use ($source) {
|
||||||
|
$start = strpos($source, 'function ds_enable(');
|
||||||
|
$end = strpos($source, "\n}\n", $start);
|
||||||
|
$body = substr($source, $start, $end - $start);
|
||||||
|
|
||||||
|
$checkPos = strpos($body, 'data_source_authorized((int) grv(\'id\'))');
|
||||||
|
$callPos = strpos($body, 'api_data_source_enable(');
|
||||||
|
|
||||||
|
expect($checkPos)->not->toBeFalse();
|
||||||
|
expect($callPos)->not->toBeFalse();
|
||||||
|
expect($checkPos)->toBeLessThan($callPos);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GHSA-2fhv: the bulk disable/enable actions filter each selected id through data_source_authorized()', function () use ($source) {
|
||||||
|
expect(substr_count($source, 'if (data_source_authorized((int) $local_data_id)) {'))->toBeGreaterThanOrEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GHSA-fc82-w6xq-4jg7: the bulk "Change Data Source Profile" action
|
||||||
|
* (drp_action == '6') iterated $selected_items and updated
|
||||||
|
* data_template_data/data_template_rrd/poller_item for each local_data_id with
|
||||||
|
* no object-level authorization check -- the same class of defect as
|
||||||
|
* GHSA-2fhv, just a different bulk action in the same file. Guarded with the
|
||||||
|
* same data_source_authorized() helper.
|
||||||
|
*/
|
||||||
|
test('GHSA-fc82: the bulk change-data-source-profile action skips unauthorized data sources', function () use ($source) {
|
||||||
|
$start = strpos($source, "drp_action') == '6') { // change data source profile");
|
||||||
|
expect($start)->not->toBeFalse();
|
||||||
|
|
||||||
|
$end = strpos($source, "\n\t\t\t} elseif", $start);
|
||||||
|
$body = substr($source, $start, $end - $start);
|
||||||
|
|
||||||
|
$checkPos = strpos($body, 'if (!data_source_authorized((int) $local_data_id)) {');
|
||||||
|
$updatePos = strpos($body, "UPDATE data_template_data");
|
||||||
|
|
||||||
|
expect($checkPos)->not->toBeFalse();
|
||||||
|
expect($updatePos)->not->toBeFalse();
|
||||||
|
expect($checkPos)->toBeLessThan($updatePos);
|
||||||
|
});
|
||||||
@@ -33,7 +33,8 @@ test('installer csrf failures return a scoped json recovery response', function
|
|||||||
->and($csrf)->toContain("'csrfMagicToken' => csrf_get_tokens()")
|
->and($csrf)->toContain("'csrfMagicToken' => csrf_get_tokens()")
|
||||||
->and($csrf)->toContain("header('Cache-Control: no-store')")
|
->and($csrf)->toContain("header('Cache-Control: no-store')")
|
||||||
->and($csrf)->toContain("header('X-Content-Type-Options: nosniff')")
|
->and($csrf)->toContain("header('X-Content-Type-Options: nosniff')")
|
||||||
->and($csrf)->toContain("header('Location: ' . sanitize_uri(\$_SERVER['REQUEST_URI']))");
|
->and($csrf)->toContain("validate_redirect_url(\$_SERVER['REQUEST_URI'] ?? '', 'index.php')")
|
||||||
|
->and($csrf)->toContain("header('Location: ' . \$redirect_target)");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('successful installer json responses roll the csrf token forward', function () use ($root) {
|
test('successful installer json responses roll the csrf token forward', function () use ($root) {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Copyright (C) 2004-2026 The Cacti Group |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GHSA-fxfg-j995-j69j: the packaged docker/apache.conf granted only
|
||||||
|
* AllowOverride FileInfo Options=FollowSymLinks for the Cacti document root.
|
||||||
|
* Cacti's shipped .htaccess deny rules (log/, cache/*, scripts/, cli/, bin/,
|
||||||
|
* mibs/, rra/, contrib/, tests/) rely on "Require all denied" (Apache 2.4,
|
||||||
|
* override type AuthConfig) and/or "Order/Deny" (override type Limit), neither
|
||||||
|
* of which FileInfo/Options covers, so those directories were served over
|
||||||
|
* HTTP unauthenticated to any client -- most notably log/cacti.log, which can
|
||||||
|
* contain usernames, IPs, SQL backtraces, and secrets. AllowOverride All lets
|
||||||
|
* every shipped .htaccess take effect as intended.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$conf = file_get_contents(__DIR__ . '/../../../../docker/apache.conf');
|
||||||
|
|
||||||
|
test('GHSA-fxfg: docker/apache.conf grants AllowOverride All for the Cacti document root', function () use ($conf) {
|
||||||
|
expect($conf)->toContain('AllowOverride All')
|
||||||
|
->and($conf)->not->toContain('AllowOverride FileInfo Options=FollowSymLinks');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GHSA-fxfg: the Cacti directory block still explicitly grants web access', function () use ($conf) {
|
||||||
|
$start = strpos($conf, '<Directory /var/www/html/cacti>');
|
||||||
|
expect($start)->not->toBeFalse();
|
||||||
|
|
||||||
|
$end = strpos($conf, '</Directory>', $start);
|
||||||
|
$body = substr($conf, $start, $end - $start);
|
||||||
|
|
||||||
|
expect($body)->toContain('Require all granted')
|
||||||
|
->and($body)->toContain('AllowOverride All');
|
||||||
|
});
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Copyright (C) 2004-2026 The Cacti Group |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GHSA-5x27-wqpm-m24v: reports.php's format_file value was written verbatim
|
||||||
|
* into reports.format_file and later concatenated onto CACTI_PATH_FORMATS in
|
||||||
|
* reports_load_format_file() without a directory component check, letting a
|
||||||
|
* traversal sequence (e.g. ../../../../etc/passwd) escape the formats
|
||||||
|
* directory and read arbitrary files the web user could access. 1.2.x
|
||||||
|
* already fixed the same sink under GHSA-g37j-39f4-6r4j/GHSA-mjvw-mhj5-9jcj;
|
||||||
|
* develop had lost the guard. The fix confines the value with basename() at
|
||||||
|
* both the save site (lib/html_reports.php) and the load site
|
||||||
|
* (lib/reports.php), rejecting a bare '.'/'..'/empty result at the load site.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$reportsSource = file_get_contents(__DIR__ . '/../../../../lib/reports.php');
|
||||||
|
$htmlReportsSource = file_get_contents(__DIR__ . '/../../../../lib/html_reports.php');
|
||||||
|
|
||||||
|
test('GHSA-5x27: reports_load_format_file confines the value to a bare filename', function () use ($reportsSource) {
|
||||||
|
$start = strpos($reportsSource, 'function reports_load_format_file(');
|
||||||
|
expect($start)->not->toBeFalse();
|
||||||
|
|
||||||
|
$end = strpos($reportsSource, "\n}\n", $start);
|
||||||
|
$body = substr($reportsSource, $start, $end - $start);
|
||||||
|
|
||||||
|
expect($body)->toContain('$format_file = basename($format_file);');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GHSA-5x27: reports_load_format_file rejects a traversal-only result before any file IO', function () use ($reportsSource) {
|
||||||
|
$start = strpos($reportsSource, 'function reports_load_format_file(');
|
||||||
|
$end = strpos($reportsSource, "\n}\n", $start);
|
||||||
|
$body = substr($reportsSource, $start, $end - $start);
|
||||||
|
|
||||||
|
$basenamePos = strpos($body, '$format_file = basename($format_file);');
|
||||||
|
$rejectPos = strpos($body, "\$format_file === '' || \$format_file === '.' || \$format_file === '..'");
|
||||||
|
$existsPos = strpos($body, 'file_exists($format_file)');
|
||||||
|
|
||||||
|
expect($basenamePos)->not->toBeFalse();
|
||||||
|
expect($rejectPos)->not->toBeFalse();
|
||||||
|
expect($existsPos)->not->toBeFalse();
|
||||||
|
expect($basenamePos)->toBeLessThan($rejectPos);
|
||||||
|
expect($rejectPos)->toBeLessThan($existsPos);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GHSA-5x27: the save path confines format_file to a bare filename before it reaches the database', function () use ($htmlReportsSource) {
|
||||||
|
expect($htmlReportsSource)->toContain("\$save['format_file'] = basename((string) (\$post['format_file'] ?? ''));");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GHSA-5x27: a traversal payload resolves to a harmless bare filename', function () {
|
||||||
|
$payload = '../../../../../../etc/passwd';
|
||||||
|
|
||||||
|
expect(basename($payload))->toBe('passwd');
|
||||||
|
});
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Copyright (C) 2004-2026 The Cacti Group |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GHSA-rjvj-r52f-8v5q (forward-port to develop). cacti_escapeshellarg_cmd()
|
||||||
|
* strips cmd.exe operators (" & | ^ < > ( )) on Windows before quoting, and the
|
||||||
|
* Windows shell sinks route their device/request values through it or the
|
||||||
|
* equivalent snmp_escape_string strip.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('CACTI_SERVER_OS')) {
|
||||||
|
define('CACTI_SERVER_OS', 'unix');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('SNMP_ESCAPE_CHARACTER')) {
|
||||||
|
define('SNMP_ESCAPE_CHARACTER', '"');
|
||||||
|
}
|
||||||
|
require_once dirname(__DIR__, 4) . '/lib/functions.php';
|
||||||
|
|
||||||
|
$functions = file_get_contents(dirname(__DIR__, 4) . '/lib/functions.php');
|
||||||
|
$snmp = file_get_contents(dirname(__DIR__, 4) . '/lib/snmp.php');
|
||||||
|
$dataquery = file_get_contents(dirname(__DIR__, 4) . '/lib/data_query.php');
|
||||||
|
$ping = file_get_contents(dirname(__DIR__, 4) . '/lib/ping.php');
|
||||||
|
$snmpagent = file_get_contents(dirname(__DIR__, 4) . '/lib/snmpagent.php');
|
||||||
|
|
||||||
|
test('cacti_escapeshellarg_cmd exists and strips on win32 by contract', function () use ($functions) {
|
||||||
|
expect(function_exists('cacti_escapeshellarg_cmd'))->toBeTrue();
|
||||||
|
$start = strpos($functions, 'function cacti_escapeshellarg_cmd(');
|
||||||
|
$body = substr($functions, $start, 500);
|
||||||
|
expect($body)->toContain("CACTI_SERVER_OS == 'win32'");
|
||||||
|
expect($body)->toContain("'^', '<', '>', '('");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('on unix (test default) the helper is a pass-through to cacti_escapeshellarg', function () {
|
||||||
|
$v = "eth0' && id";
|
||||||
|
expect(cacti_escapeshellarg_cmd($v))->toBe(cacti_escapeshellarg($v));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('snmp_escape_string strips cmd.exe operators on win32', function () use ($snmp) {
|
||||||
|
$start = strpos($snmp, 'function snmp_escape_string(');
|
||||||
|
$body = substr($snmp, $start, 700);
|
||||||
|
expect($body)->toContain("'^', '<', '>', '('");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the snmp binary hostname uses the cmd helper', function () use ($snmp) {
|
||||||
|
expect($snmp)->toContain('cacti_escapeshellarg_cmd($hostname, true, true)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('get_script_query_path strips device values on win32', function () use ($dataquery) {
|
||||||
|
$start = strpos($dataquery, 'function get_script_query_path(');
|
||||||
|
$body = substr($dataquery, $start, 1200);
|
||||||
|
expect($body)->toContain("CACTI_SERVER_OS == 'win32'");
|
||||||
|
expect($body)->toContain("'^', '<', '>', '('");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ping hostname and snmp trap args use the cmd helper', function () use ($ping, $snmpagent) {
|
||||||
|
expect($ping)->toContain('cacti_escapeshellarg_cmd($this->host[' . "'hostname'" . '], true, true)');
|
||||||
|
expect($snmpagent)->toContain('cacti_escapeshellarg_cmd($arg)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the strip_env flag removes %VAR% for hostname-class values only', function () {
|
||||||
|
// default: percent is preserved (credentials may contain it)
|
||||||
|
expect(cacti_escapeshellarg_cmd('p%ss'))->toBe(cacti_escapeshellarg('p%ss'));
|
||||||
|
// opt-in: a hostname never contains a percent, so it is stripped on win32.
|
||||||
|
// on unix (test default) the flag is a no-op, so assert the source wiring.
|
||||||
|
$src = file_get_contents(dirname(__DIR__, 4) . '/lib/functions.php');
|
||||||
|
$start = strpos($src, 'function cacti_escapeshellarg_cmd(');
|
||||||
|
$body = substr($src, $start, 700);
|
||||||
|
expect($body)->toContain('$strip_env');
|
||||||
|
expect($body)->toContain("str_replace('%', ''");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the hostname sinks opt into the percent strip', function () {
|
||||||
|
$snmp = file_get_contents(dirname(__DIR__, 4) . '/lib/snmp.php');
|
||||||
|
$ping = file_get_contents(dirname(__DIR__, 4) . '/lib/ping.php');
|
||||||
|
expect($snmp)->toContain('cacti_escapeshellarg_cmd($hostname, true, true)');
|
||||||
|
expect($ping)->toContain('cacti_escapeshellarg_cmd($this->host[' . "'hostname'" . '], true, true)');
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CACTI_SERVER_OS is a constant, so the win32 branch above can only be
|
||||||
|
* inferred from source text in this process. Run the helper in a fresh
|
||||||
|
* process with CACTI_SERVER_OS actually set to 'win32' so the stripping
|
||||||
|
* really executes instead of being assumed from the assertions above.
|
||||||
|
*/
|
||||||
|
test('cacti_escapeshellarg_cmd actually strips metachars and percent on win32', function () {
|
||||||
|
$result = cmd_metachar_run_win32_probe();
|
||||||
|
|
||||||
|
expect($result['metachars'])->toBe('"abcdefghi"')
|
||||||
|
->and($result['percent-default'])->toBe('"host%PATH%name"')
|
||||||
|
->and($result['percent-stripped'])->toBe('"hostPATHname"')
|
||||||
|
->and($result['credential-kept'])->toBe('"p%ss"');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tests/fixtures/CmdMetacharHelperWin32Probe.php and parse its
|
||||||
|
* "label\tresult" stdout lines.
|
||||||
|
*
|
||||||
|
* @return array<string, string> Result keyed by test case label.
|
||||||
|
*/
|
||||||
|
function cmd_metachar_run_win32_probe() : array {
|
||||||
|
$root = dirname(__DIR__, 4);
|
||||||
|
|
||||||
|
$process = proc_open(
|
||||||
|
[PHP_BINARY, $root . '/tests/fixtures/CmdMetacharHelperWin32Probe.php'],
|
||||||
|
[1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
|
||||||
|
$pipes,
|
||||||
|
$root
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!is_resource($process)) {
|
||||||
|
throw new RuntimeException('Unable to start the win32 metachar probe');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stdout = stream_get_contents($pipes[1]);
|
||||||
|
fclose($pipes[1]);
|
||||||
|
fclose($pipes[2]);
|
||||||
|
proc_close($process);
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
foreach (explode("\n", trim($stdout)) as $line) {
|
||||||
|
[$label, $value] = explode("\t", $line, 2);
|
||||||
|
$results[$label] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
@@ -34,7 +34,17 @@
|
|||||||
// --- Stub: sanitize_sql_column (lib/functions.php) ---
|
// --- Stub: sanitize_sql_column (lib/functions.php) ---
|
||||||
|
|
||||||
function stub_sanitize_sql_column(string $column): string {
|
function stub_sanitize_sql_column(string $column): string {
|
||||||
return preg_replace('/[^a-zA-Z0-9_().]/', '', $column) ?? '';
|
$result = preg_replace('/[^a-zA-Z0-9_().]/', '', $column) ?? '';
|
||||||
|
|
||||||
|
if ($result === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($result, '(') && preg_match('/^(?:INET_ATON|NATURAL_SORT_KEY)\([a-zA-Z_][a-zA-Z0-9_.]*\)$/i', $result) !== 1) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Stub: direction enforcement used in html_reports.php, api_automation.php,
|
// --- Stub: direction enforcement used in html_reports.php, api_automation.php,
|
||||||
@@ -58,6 +68,31 @@ test('sanitize_sql_column: function call with parens passes', function () {
|
|||||||
expect(stub_sanitize_sql_column('INET_ATON(ip)'))->toBe('INET_ATON(ip)');
|
expect(stub_sanitize_sql_column('INET_ATON(ip)'))->toBe('INET_ATON(ip)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sanitize_sql_column: NATURAL_SORT_KEY wrapper passes', function () {
|
||||||
|
expect(stub_sanitize_sql_column('NATURAL_SORT_KEY(hostname)'))->toBe('NATURAL_SORT_KEY(hostname)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sanitize_sql_column: subquery payload bypassing GHSA-q9xg-p762-9jm3 is rejected (GHSA-8j73-cvf6-jrvx)', function () {
|
||||||
|
$payload = '(SELECT(0)FROM(SELECT(SLEEP(5)))a)';
|
||||||
|
$output = stub_sanitize_sql_column($payload);
|
||||||
|
|
||||||
|
expect($output)->not->toBe($payload);
|
||||||
|
expect(strtoupper($output))->not->toContain('SELECT');
|
||||||
|
expect(strtoupper($output))->not->toContain('SLEEP');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sanitize_sql_column: REGEXP exfiltration subquery payload is rejected (GHSA-8j73-cvf6-jrvx)', function () {
|
||||||
|
$payload = '(SELECT(0)FROM(SELECT(SLEEP(5))FROM(user_auth)WHERE(BINARY(username)REGEXP(0x5e61646d696e24)))a)';
|
||||||
|
$output = stub_sanitize_sql_column($payload);
|
||||||
|
|
||||||
|
expect(strtoupper($output))->not->toContain('SELECT');
|
||||||
|
expect(strtoupper($output))->not->toContain('FROM');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sanitize_sql_column: a function name that is not on the wrapper allowlist is rejected', function () {
|
||||||
|
expect(stub_sanitize_sql_column('SLEEP(5)'))->not->toBe('SLEEP(5)');
|
||||||
|
});
|
||||||
|
|
||||||
test('sanitize_sql_column: column with underscore and digits passes', function () {
|
test('sanitize_sql_column: column with underscore and digits passes', function () {
|
||||||
expect(stub_sanitize_sql_column('last_started_2'))->toBe('last_started_2');
|
expect(stub_sanitize_sql_column('last_started_2'))->toBe('last_started_2');
|
||||||
});
|
});
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Copyright (C) 2004-2026 The Cacti Group |
|
||||||
|
| |
|
||||||
|
| This program is free software; you can redistribute it and/or |
|
||||||
|
| modify it under the terms of the GNU General Public License |
|
||||||
|
| as published by the Free Software Foundation; either version 2 |
|
||||||
|
| of the License, or (at your option) any later version. |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
||||||
|
+-------------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CACTI_SERVER_OS is a constant, so the win32 branch of cacti_escapeshellarg_cmd()
|
||||||
|
* cannot be exercised in the same process as the unix-default test suite. This
|
||||||
|
* probe runs in its own process with CACTI_SERVER_OS actually set to 'win32'
|
||||||
|
* so the metachar/percent stripping really executes, instead of being inferred
|
||||||
|
* from source text.
|
||||||
|
*
|
||||||
|
* Each test case is printed as "label\tresult" on its own stdout line.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$root = dirname(__DIR__, 2);
|
||||||
|
|
||||||
|
define('CACTI_SERVER_OS', 'win32');
|
||||||
|
define('SNMP_ESCAPE_CHARACTER', '"');
|
||||||
|
define('CACTI_ESCAPE_CHARACTER', '"');
|
||||||
|
|
||||||
|
require_once $root . '/include/vendor/autoload.php';
|
||||||
|
require_once $root . '/lib/functions.php';
|
||||||
|
|
||||||
|
$cases = [
|
||||||
|
'metachars' => cacti_escapeshellarg_cmd('a"b&c|d^e<f>g(h)i'),
|
||||||
|
'percent-default' => cacti_escapeshellarg_cmd('host%PATH%name'),
|
||||||
|
'percent-stripped' => cacti_escapeshellarg_cmd('host%PATH%name', true, true),
|
||||||
|
'credential-kept' => cacti_escapeshellarg_cmd('p%ss'),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($cases as $label => $result) {
|
||||||
|
echo $label . "\t" . $result . "\n";
|
||||||
|
}
|
||||||
+8
@@ -84,6 +84,14 @@ if (!function_exists('sanitize_uri')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lib/html_utility.php is not loaded here; csrf_error_callback()'s redirect
|
||||||
|
// branch only needs a default to fall back to, not the real validation.
|
||||||
|
if (!function_exists('validate_redirect_url')) {
|
||||||
|
function validate_redirect_url($url = '', $default = 'index.php') {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$mode = ($argv[1] ?? '');
|
$mode = ($argv[1] ?? '');
|
||||||
|
|
||||||
$_SERVER['REQUEST_URI'] = '/test';
|
$_SERVER['REQUEST_URI'] = '/test';
|
||||||
|
|||||||
@@ -41,23 +41,24 @@ cmd_exec ./lib/functions.php $output = shell_exec('powershell -Command "Get-Wm
|
|||||||
cmd_exec ./lib/functions.php $processExists = trim((string) shell_exec($checkProcessCmd));
|
cmd_exec ./lib/functions.php $processExists = trim((string) shell_exec($checkProcessCmd));
|
||||||
cmd_exec ./lib/functions.php exec($command_line, $out, $return_code);
|
cmd_exec ./lib/functions.php exec($command_line, $out, $return_code);
|
||||||
cmd_exec ./lib/functions.php return exec('whoami') ?: '';
|
cmd_exec ./lib/functions.php return exec('whoami') ?: '';
|
||||||
|
cmd_exec ./lib/functions.php * On Windows, exec()/shell_exec()/popen() route through cmd.exe, which ignores
|
||||||
cmd_exec ./lib/installer.php $output = shell_exec(
|
cmd_exec ./lib/installer.php $output = shell_exec(
|
||||||
cmd_exec ./lib/installer.php $output = shell_exec(cacti_escapeshellcmd(str_replace('\\', '/', $path)) . ' --version 2>&1');
|
cmd_exec ./lib/installer.php $output = shell_exec(cacti_escapeshellcmd(str_replace('\\', '/', $path)) . ' --version 2>&1');
|
||||||
cmd_exec ./lib/installer.php $check = shell_exec("$composer install --dry-run --no-interaction --working-dir=$base 2>&1");
|
cmd_exec ./lib/installer.php $check = shell_exec("$composer install --dry-run --no-interaction --working-dir=$base 2>&1");
|
||||||
cmd_exec ./lib/installer.php $output = shell_exec("$composer install --no-interaction --no-progress --working-dir=$base 2>&1");
|
cmd_exec ./lib/installer.php $output = shell_exec("$composer install --no-interaction --no-progress --working-dir=$base 2>&1");
|
||||||
cmd_exec ./lib/installer.php $process = proc_open(
|
cmd_exec ./lib/installer.php $process = proc_open(
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg($this->host['hostname']) . ' 2>&1');
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1');
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping6 -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping6 -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec(cacti_escapeshellarg($fping) . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']) . ' 2>&1');
|
cmd_exec ./lib/ping.php $result = shell_exec(cacti_escapeshellarg($fping) . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1');
|
||||||
cmd_exec ./lib/poller.php $output = system($php_path . ' -l ' . $tmpfile, $exit);
|
cmd_exec ./lib/poller.php $output = system($php_path . ' -l ' . $tmpfile, $exit);
|
||||||
cmd_exec ./lib/poller.php pclose(popen('start "Cactiplus" /I ' . $filename . ' ' . $args . ' ' . $redirect_args, 'r'));
|
cmd_exec ./lib/poller.php pclose(popen('start "Cactiplus" /I ' . $filename . ' ' . $args . ' ' . $redirect_args, 'r'));
|
||||||
cmd_exec ./lib/poller.php pclose(popen('start "Cactiplus" /I ' . $filename . ' ' . $args, 'r'));
|
cmd_exec ./lib/poller.php pclose(popen('start "Cactiplus" /I ' . $filename . ' ' . $args, 'r'));
|
||||||
@@ -92,7 +93,44 @@ cmd_exec ./lib/utility.php exec('wmic os get TotalVisibleMemorySize', $memInfo
|
|||||||
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
||||||
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
||||||
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
||||||
cmd_exec ./poller_realtime.php shell_exec($command);
|
cmd_exec ./plugins/analytics/lib/functions.php $slotsTotal = trim((string) shell_exec($rrdtool . " graph /dev/null -s $min_time -e $max_time DEF:jobs='$rrd_file':'max_jobs':MAX PRINT:jobs:MAX:'%.0lf' | tail -1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $value = trim((string) shell_exec($rrdtool . " graph /dev/null -s $rrd_min -e $rrd_max DEF:data=$rrd_file:$data_source:$cf PRINT:data:MAX:'%.0lf' | tail -1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $value = trim((string) shell_exec($rrdtool . " graph /dev/null -s $rrd_min -e $rrd_max DEF:data=$rrd_file:$data_source:$cf PRINT:data:MAX:'%.0lf' | tail -1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $value = trim((string) shell_exec($rrdtool . " graph /dev/null -s $rrd_min -e $rrd_max DEF:data=$rrd_file:$data_source:AVERAGE PRINT:data:MAX:'%.0lf' | tail -1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $bjgroup_slots = trim((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/bjgroup -N 2>&1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $data = (string) shell_exec($rrdtool . " xport -t -s $rrd_min -e $rrd_max --step $desired_step DEF:data=$rrd_file:$data_source:$cf XPORT:data:$data_source");
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $lsinfo = trim((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/lsinfo -w 2>&1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $output = (string) shell_exec("pstree -p $pid");
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $reasons = trim((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/bjobs -psum -p1 -uall -sla $sla 2>&1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $bjgroup_jobs = trim((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/bjgroup -s 2>&1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $bmgroup = trim((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/bmgroup -w 2>&1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $bresources = trim((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/bresources -g -l $group 2>&1 | grep 'HOSTS:'"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $bresources = trim((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/bresources -g 2>&1"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $jobs = file((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/bjobs -uall -UF -plan 2>/dev/null"));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $jobs = json_decode((string) shell_exec($config['base_path'] . "/plugins/analytics/bin/bjobs -uall -o 'jobid jobindex user project app stat queue sla:60 first_host slots min_req_proc max_req_proc cpu_used max_mem mem run_time submit_time start_time finish_time effective_resreq combined_resreq' -json 2>/dev/null"), true);
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $reasons = trim((string) shell_exec($config['base_path'] . '/plugins/analytics/bin/bjobs -psum -p1 -uall 2>&1'));
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $suspend = (string) shell_exec($config['base_path'] . '/plugins/analytics/bin/bjobs -uall -s 2>&1');
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $values = (string) shell_exec(read_config_option('path_rrdtool') . " fetch $rrd_path $cf $resolution -s $start_time -e $end_time");
|
||||||
|
cmd_exec ./plugins/analytics/lib/functions.php $values = (string) shell_exec(read_config_option('path_rrdtool') . " fetch $rrd_path $cf $resolution -s $start_time -e $end_time");
|
||||||
|
cmd_exec ./plugins/backup/poller_backup.php shell_exec("chown $runas:$runas $tmpfile");
|
||||||
|
cmd_exec ./plugins/backup/poller_backup.php shell_exec("chown $runas:$runas $tmpsqlgzfile");
|
||||||
|
cmd_exec ./plugins/backup/poller_backup.php shell_exec("chown $dbrunas:$dbrunas $tmpsqlfile");
|
||||||
|
cmd_exec ./plugins/backup/poller_backup.php exec($command, $output, $return);
|
||||||
|
cmd_exec ./plugins/cjobs/setup.php exec($action, $output, $return);
|
||||||
|
cmd_exec ./plugins/gexport/functions.php exec('rsync -q ' . $export['export_args'] . ' ' . $prune . $keyopt . ' ' . $stExportDir . '/. ' . ($user != '' ? "$user@":'') . $host . ':' . $export['export_directory'] . ' 2>&1', $output, $retvar);
|
||||||
|
cmd_exec ./plugins/gexport/functions.php exec('scp ' . $export['export_args'] . ' ' . $keyopt . ($port != '' ? ' -P ' . "$port ":"") . $stExportDir . '/. ' . ($user != '' ? "$user@":'') . $host . ':' . $export['export_directory'] . ' 2>&1', $output, $retvar);
|
||||||
|
cmd_exec ./plugins/gexport/functions.php system($stExecute, $iExecuteReturns);
|
||||||
|
cmd_exec ./plugins/hmib/poller_graphs.php $results = shell_exec($command);
|
||||||
|
cmd_exec ./plugins/hmib/poller_graphs.php $result = exec("$php_bin -q $base/cli/add_device.php --description='Summary Device' --ip=summary --template=$host_template --version=0 --avail=none", $return_code);
|
||||||
|
cmd_exec ./plugins/hmib/poller_graphs.php $result = exec("$php_bin -q $base/cli/poller_reindex_hosts.php -id=$host_id -qid=All", $return_code);
|
||||||
|
cmd_exec ./plugins/hmib/snmp.php exec(cacti_escapeshellcmd(read_config_option('path_snmpget')) . ' -O fntevU ' . $snmp_auth . " -v $version -t $timeout -r $retries " . cacti_escapeshellarg($hostname) . ":$port " . cacti_escapeshellarg($oid), $snmp_value);
|
||||||
|
cmd_exec ./plugins/hmib/snmp.php exec(cacti_escapeshellcmd(read_config_option('path_snmpgetnext')) . " -O fntevU $snmp_auth -v $version -t $timeout -r $retries " . cacti_escapeshellarg($hostname) . ":$port " . cacti_escapeshellarg($oid), $snmp_value);
|
||||||
|
cmd_exec ./plugins/lsflog/lsflog_cronjob.php $size = shell_exec("du -shBG $logdir | awk '{print $1}' | sed 's/[^1-9]//g' | tr -d '\n'");
|
||||||
|
cmd_exec ./plugins/lsflog/lsflog_cronjob.php shell_exec('find ' . $logdir . '/* -mtime ' . $duration . ' -exec /bin/rm -f {} \;');
|
||||||
|
cmd_exec ./plugins/lsflog/lsflog_cronjob.php $files = shell_exec('find ' . $logdir . ' -name \*.benchmark -cmin -160');
|
||||||
|
cmd_exec ./plugins/lsflog/lsflog_cronjob.php $size = shell_exec("du -shBG $logdir | awk '{print $1}' | sed 's/[^1-9]//g' | tr -d '\n'");
|
||||||
|
cmd_exec ./plugins/lsflog/lsflog_cronjob.php shell_exec('find ' . $logdir . '/* -mtime ' . $duration . ' -exec /bin/rm -f {} \;');
|
||||||
|
cmd_exec ./plugins/mikrotik/poller_graphs.php $lline = exec($command, $output, $return);
|
||||||
cmd_exec ./poller_realtime.php shell_exec("$command_string $extra_args");
|
cmd_exec ./poller_realtime.php shell_exec("$command_string $extra_args");
|
||||||
cmd_exec ./poller_spikekill.php $response = exec(cacti_escapeshellcmd((string) read_config_option('path_php_binary')) . ' -q ' .
|
cmd_exec ./poller_spikekill.php $response = exec(cacti_escapeshellcmd((string) read_config_option('path_php_binary')) . ' -q ' .
|
||||||
cmd_exec ./remote_agent.php $cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . CACTI_PATH_BASE . '/script_server.php realtime ' . cacti_escapeshellarg($poller_id), $cactides, $pipes);
|
cmd_exec ./remote_agent.php $cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . CACTI_PATH_BASE . '/script_server.php realtime ' . cacti_escapeshellarg($poller_id), $cactides, $pipes);
|
||||||
@@ -125,9 +163,110 @@ dynamic_include ./lib/rrd.php include($rrdtheme);
|
|||||||
dynamic_include ./lib/rrd.php include($themefile);
|
dynamic_include ./lib/rrd.php include($themefile);
|
||||||
dynamic_include ./link.php require_once($file);
|
dynamic_include ./link.php require_once($file);
|
||||||
dynamic_include ./plugins.php require_once($setup_file);
|
dynamic_include ./plugins.php require_once($setup_file);
|
||||||
|
dynamic_include ./plugins/analytics/analytics_panels.php include_once($file);
|
||||||
|
dynamic_include ./plugins/analytics/analytics_panels.php include_once($panel['pfile']);
|
||||||
|
dynamic_include ./plugins/analytics/lib/archive.php include_once($config['base_path'] . '/plugins/grid/lib/grid_partitioning.php');
|
||||||
|
dynamic_include ./plugins/analytics/lib/archive.php include_once($config['base_path'] . '/plugins/grid/lib/grid_partitioning.php');
|
||||||
|
dynamic_include ./plugins/analytics/lib/archive.php include_once($config['base_path'] . '/plugins/grid/lib/grid_partitioning.php');
|
||||||
|
dynamic_include ./plugins/analytics/lib/archive.php include_once($config['base_path'] . '/plugins/grid/lib/grid_partitioning.php');
|
||||||
|
dynamic_include ./plugins/analytics/lib/archive.php include_once($config['base_path'] . '/plugins/grid/lib/grid_partitioning.php');
|
||||||
|
dynamic_include ./plugins/analytics/lib/functions.php include_once($file);
|
||||||
|
dynamic_include ./plugins/analytics/poller_etl.php include($config['include_path'] . '/config.php');
|
||||||
|
dynamic_include ./plugins/analytics/setup.php include_once($config['base_path'] . '/lib/poller.php');
|
||||||
|
dynamic_include ./plugins/analytics/setup.php include_once($config['base_path'] . '/lib/reports.php');
|
||||||
|
dynamic_include ./plugins/analytics/setup.php include_once($config['base_path'] . '/lib/reports.php');
|
||||||
|
dynamic_include ./plugins/analytics/setup.php include_once($config['base_path'] . '/lib/rtm_functions.php');
|
||||||
|
dynamic_include ./plugins/analytics/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/analytics/setup.php include_once($config['library_path'] . '/functions.php');
|
||||||
|
dynamic_include ./plugins/analytics/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/analytics/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/audit/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/audit/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/audit/setup.php include_once($config['library_path'] . '/functions.php');
|
||||||
|
dynamic_include ./plugins/audit/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/audit/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/backup/export_device_templates.php include_once($config['base_path'] . '/plugins/backup/setup.php');
|
||||||
|
dynamic_include ./plugins/backup/export_device_templates.php include_once($config['library_path'] . '/export.php');
|
||||||
|
dynamic_include ./plugins/backup/poller_backup.php include_once($config['base_path'] . '/plugins/backup/setup.php');
|
||||||
|
dynamic_include ./plugins/backup/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/backup/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/cjobs/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/cjobs/setup.php include_once($config['library_path'] . '/functions.php');
|
||||||
|
dynamic_include ./plugins/cjobs/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/cjobs/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/evidence/include/functions.php include_once($config['library_path'] . '/poller.php');
|
||||||
|
dynamic_include ./plugins/evidence/include/functions.php include_once($config['base_path'] . '/plugins/evidence/include/arrays.php');
|
||||||
|
dynamic_include ./plugins/evidence/include/functions.php include_once($config['base_path'] . '/plugins/evidence/include/arrays.php');
|
||||||
|
dynamic_include ./plugins/evidence/poller_evidence.php include_once($config['base_path'] . '/plugins/evidence/setup.php');
|
||||||
|
dynamic_include ./plugins/evidence/poller_evidence.php include_once($config['base_path'] . '/plugins/evidence/include/functions.php');
|
||||||
|
dynamic_include ./plugins/evidence/poller_evidence.php include_once($config['library_path'] . '/snmp.php');
|
||||||
|
dynamic_include ./plugins/evidence/setup.php include_once($config['base_path'] . '/plugins/evidence/include/database.php');
|
||||||
|
dynamic_include ./plugins/evidence/setup.php include_once($config['base_path'] . '/plugins/evidence/include/database.php');
|
||||||
|
dynamic_include ./plugins/evidence/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/evidence/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/exlist/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/exlist/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/gexport/poller_export.php include_once($config['base_path'] . '/plugins/gexport/setup.php');
|
||||||
|
dynamic_include ./plugins/gexport/poller_export.php include_once($config['base_path'] . '/lib/data_query.php');
|
||||||
|
dynamic_include ./plugins/gexport/poller_export.php include_once($config['base_path'] . '/lib/poller.php');
|
||||||
|
dynamic_include ./plugins/gexport/poller_export.php include_once($config['base_path'] . '/lib/rrd.php');
|
||||||
|
dynamic_include ./plugins/gexport/poller_export.php include_once($config['base_path'] . '/plugins/gexport/functions.php');
|
||||||
|
dynamic_include ./plugins/gexport/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/gexport/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/gexport/setup.php include_once($config['library_path'] . '/functions.php');
|
||||||
|
dynamic_include ./plugins/gexport/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/gexport/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/hmib/associate_os_type.php include_once($config['base_path'] . '/plugins/hmib/setup.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php include_once($config['base_path'] . '/plugins/hmib/setup.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/api_automation.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/api_automation_tools.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/api_data_source.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/api_device.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/api_graph.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/data_query.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/poller.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/snmp.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/sort.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/template.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_graphs.php require_once($config['base_path'] . '/lib/utility.php');
|
||||||
|
dynamic_include ./plugins/hmib/poller_hmib.php include_once($config['base_path'] . '/plugins/hmib/setup.php');
|
||||||
|
dynamic_include ./plugins/hmib/setup.php include_once($config['base_path'] . '/lib/poller.php');
|
||||||
|
dynamic_include ./plugins/hmib/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/hmib/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/hmib/setup.php include_once($config['library_path'] . '/functions.php');
|
||||||
|
dynamic_include ./plugins/hmib/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/hmib/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/lsflog/lsflog_cronjob.php include_once($config['base_path'] . '/lib/poller.php');
|
||||||
|
dynamic_include ./plugins/lsflog/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/lsflog/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/mikrotik/poller_graphs.php include_once($config['base_path'] . '/plugins/mikrotik/setup.php');
|
||||||
|
dynamic_include ./plugins/mikrotik/poller_mikrotik.php include_once($config['base_path'] . '/plugins/mikrotik/setup.php');
|
||||||
|
dynamic_include ./plugins/mikrotik/setup.php include_once($config['base_path'] . '/lib/poller.php');
|
||||||
|
dynamic_include ./plugins/mikrotik/setup.php include_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/mikrotik/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/mikrotik/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/mikrotik/tests/stubs/lib/database.php * include_once($config['library_path'] . '/database.php') before creating
|
||||||
|
dynamic_include ./plugins/schema/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/schema/tests/bootstrap-unit.php require_once $autoload;
|
||||||
|
dynamic_include ./plugins/tags/include/database.php include_once($config['base_path'] . '/plugins/tags/include/functions.php');
|
||||||
|
dynamic_include ./plugins/tags/include/functions.php require($config['base_path'] . '/plugins/tags/include/arrays.php');
|
||||||
|
dynamic_include ./plugins/tags/poller_tags.php include_once($config['base_path'] . '/plugins/tags/setup.php');
|
||||||
|
dynamic_include ./plugins/tags/poller_tags.php require_once($config['base_path'] . '/lib/poller.php');
|
||||||
|
dynamic_include ./plugins/tags/poller_tags.php require_once($config['base_path'] . '/plugins/tags/include/functions.php');
|
||||||
|
dynamic_include ./plugins/tags/setup.php require_once($config['base_path'] . '/plugins/tags/include/functions.php');
|
||||||
|
dynamic_include ./plugins/tags/setup.php include_once($config['base_path'] . '/plugins/tags/include/arrays.php');
|
||||||
|
dynamic_include ./plugins/tags/setup.php include_once($config['base_path'] . '/plugins/tags/include/database.php');
|
||||||
|
dynamic_include ./plugins/tags/setup.php include_once($config['base_path'] . '/plugins/tags/include/database.php');
|
||||||
|
dynamic_include ./plugins/tags/setup.php require_once($config['library_path'] . '/database.php');
|
||||||
|
dynamic_include ./plugins/tags/tags.php include_once($config['base_path'] . '/lib/timespan_settings.php');
|
||||||
|
dynamic_include ./plugins/tags/tags.php require($config['base_path'] . '/plugins/tags/include/arrays.php');
|
||||||
|
dynamic_include ./plugins/tags/tags.php require_once($config['base_path'] . '/plugins/tags/include/functions.php');
|
||||||
|
dynamic_include ./plugins/tags/tests/bootstrap-unit.php require_once $path;
|
||||||
|
dynamic_include ./plugins/tags/tests/bootstrap-unit.php require_once $autoload;
|
||||||
dynamic_include ./reports.php require_once($reportit_api);
|
dynamic_include ./reports.php require_once($reportit_api);
|
||||||
dynamic_include ./script_server.php require_once($include_file);
|
dynamic_include ./script_server.php require_once($include_file);
|
||||||
dynamic_include ./scripts/ss_mikrotik_snmpget.php include_once($config['base_path'] . '/lib/snmp.php');
|
dynamic_include ./scripts/ss_mikrotik_snmpget.php include_once($config['base_path'] . '/lib/snmp.php');
|
||||||
dynamic_include ./scripts/ss_netsnmp_lmsensors.php include($config['base_path'] . '/lib/snmp.php');
|
dynamic_include ./scripts/ss_netsnmp_lmsensors.php include($config['base_path'] . '/lib/snmp.php');
|
||||||
dynamic_include ./snmpagent_persist.php require($path_mibcache);
|
dynamic_include ./snmpagent_persist.php require($path_mibcache);
|
||||||
host_header ./lib/auth.php $result['link'] = \Sonata\GoogleAuthenticator\GoogleQrUrl::generate($current_user['username'] . '@' . $_SERVER['HTTP_HOST'], $current_user['tfa_secret'], 'Cacti');
|
host_header ./lib/auth.php $result['link'] = \Sonata\GoogleAuthenticator\GoogleQrUrl::generate($current_user['username'] . '@' . $_SERVER['HTTP_HOST'], $current_user['tfa_secret'], 'Cacti');
|
||||||
|
sort_order_input ./plugins/lsflog/grid_lsflog.php ORDER BY " . get_request_var('sort_column') . ' ' . get_request_var('sort_direction');
|
||||||
|
Can't render this file because it contains an unexpected character in line 22 and column 251.
|
@@ -41,23 +41,24 @@ cmd_exec ./lib/functions.php $output = shell_exec('powershell -Command "Get-Wm
|
|||||||
cmd_exec ./lib/functions.php $processExists = trim((string) shell_exec($checkProcessCmd));
|
cmd_exec ./lib/functions.php $processExists = trim((string) shell_exec($checkProcessCmd));
|
||||||
cmd_exec ./lib/functions.php exec($command_line, $out, $return_code);
|
cmd_exec ./lib/functions.php exec($command_line, $out, $return_code);
|
||||||
cmd_exec ./lib/functions.php return exec('whoami') ?: '';
|
cmd_exec ./lib/functions.php return exec('whoami') ?: '';
|
||||||
|
cmd_exec ./lib/functions.php * On Windows, exec()/shell_exec()/popen() route through cmd.exe, which ignores
|
||||||
cmd_exec ./lib/installer.php $output = shell_exec(
|
cmd_exec ./lib/installer.php $output = shell_exec(
|
||||||
cmd_exec ./lib/installer.php $output = shell_exec(cacti_escapeshellcmd(str_replace('\\', '/', $path)) . ' --version 2>&1');
|
cmd_exec ./lib/installer.php $output = shell_exec(cacti_escapeshellcmd(str_replace('\\', '/', $path)) . ' --version 2>&1');
|
||||||
cmd_exec ./lib/installer.php $check = shell_exec("$composer install --dry-run --no-interaction --working-dir=$base 2>&1");
|
cmd_exec ./lib/installer.php $check = shell_exec("$composer install --dry-run --no-interaction --working-dir=$base 2>&1");
|
||||||
cmd_exec ./lib/installer.php $output = shell_exec("$composer install --no-interaction --no-progress --working-dir=$base 2>&1");
|
cmd_exec ./lib/installer.php $output = shell_exec("$composer install --no-interaction --no-progress --working-dir=$base 2>&1");
|
||||||
cmd_exec ./lib/installer.php $process = proc_open(
|
cmd_exec ./lib/installer.php $process = proc_open(
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -6 -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg($this->host['hostname']) . ' 2>&1');
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -W ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' -p ' . $pattern . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1');
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping6 -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping6 -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('chcp 437 && ping -w ' . $this->timeout . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -i ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -m ' . ceil($this->timeout / 1000) . ' -n ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -t ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']));
|
cmd_exec ./lib/ping.php $result = shell_exec('ping -w ' . ceil($this->timeout / 1000) . ' -c ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true));
|
||||||
cmd_exec ./lib/ping.php $result = shell_exec(cacti_escapeshellarg($fping) . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . cacti_escapeshellarg($this->host['hostname']) . ' 2>&1');
|
cmd_exec ./lib/ping.php $result = shell_exec(cacti_escapeshellarg($fping) . ' -q -t ' . $this->timeout . ' -c 1 -r ' . $this->retries . ' ' . cacti_escapeshellarg_cmd($this->host['hostname'], true, true) . ' 2>&1');
|
||||||
cmd_exec ./lib/poller.php $output = system($php_path . ' -l ' . $tmpfile, $exit);
|
cmd_exec ./lib/poller.php $output = system($php_path . ' -l ' . $tmpfile, $exit);
|
||||||
cmd_exec ./lib/poller.php pclose(popen('start "Cactiplus" /I ' . $filename . ' ' . $args . ' ' . $redirect_args, 'r'));
|
cmd_exec ./lib/poller.php pclose(popen('start "Cactiplus" /I ' . $filename . ' ' . $args . ' ' . $redirect_args, 'r'));
|
||||||
cmd_exec ./lib/poller.php pclose(popen('start "Cactiplus" /I ' . $filename . ' ' . $args, 'r'));
|
cmd_exec ./lib/poller.php pclose(popen('start "Cactiplus" /I ' . $filename . ' ' . $args, 'r'));
|
||||||
@@ -92,7 +93,6 @@ cmd_exec ./lib/utility.php exec('wmic os get TotalVisibleMemorySize', $memInfo
|
|||||||
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
||||||
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
||||||
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
cmd_exec ./lib/utility.php $json = shell_exec($php . ' -q ' . $php_file);
|
||||||
cmd_exec ./poller_realtime.php shell_exec($command);
|
|
||||||
cmd_exec ./poller_realtime.php shell_exec("$command_string $extra_args");
|
cmd_exec ./poller_realtime.php shell_exec("$command_string $extra_args");
|
||||||
cmd_exec ./poller_spikekill.php $response = exec(cacti_escapeshellcmd((string) read_config_option('path_php_binary')) . ' -q ' .
|
cmd_exec ./poller_spikekill.php $response = exec(cacti_escapeshellcmd((string) read_config_option('path_php_binary')) . ' -q ' .
|
||||||
cmd_exec ./remote_agent.php $cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . CACTI_PATH_BASE . '/script_server.php realtime ' . cacti_escapeshellarg($poller_id), $cactides, $pipes);
|
cmd_exec ./remote_agent.php $cactiphp = proc_open(read_config_option('path_php_binary') . ' -q ' . CACTI_PATH_BASE . '/script_server.php realtime ' . cacti_escapeshellarg($poller_id), $cactides, $pipes);
|
||||||
@@ -387,6 +387,8 @@ header_redirect ./data_sources.php header('Location: data_sources.php?ac
|
|||||||
header_redirect ./data_sources.php header('Location: data_sources.php');
|
header_redirect ./data_sources.php header('Location: data_sources.php');
|
||||||
header_redirect ./data_sources.php header('Location: data_sources.php');
|
header_redirect ./data_sources.php header('Location: data_sources.php');
|
||||||
header_redirect ./data_sources.php header('Location: data_sources.php');
|
header_redirect ./data_sources.php header('Location: data_sources.php');
|
||||||
|
header_redirect ./data_sources.php header('Location: data_sources.php');
|
||||||
|
header_redirect ./data_sources.php header('Location: data_sources.php');
|
||||||
header_redirect ./data_sources.php header('Location: data_sources.php?action=ds_edit&host_id=' . grv('host_id') . '&new=1');
|
header_redirect ./data_sources.php header('Location: data_sources.php?action=ds_edit&host_id=' . grv('host_id') . '&new=1');
|
||||||
header_redirect ./data_sources.php header('Location: data_sources.php?action=ds_edit&id=' . (empty($local_data_id) ? gfrv('local_data_id') : $local_data_id) . '&host_id=' . grv('host_id') . '&view_rrd=' . (isrv('current_rrd') ? gnrv('current_rrd') : '0'));
|
header_redirect ./data_sources.php header('Location: data_sources.php?action=ds_edit&id=' . (empty($local_data_id) ? gfrv('local_data_id') : $local_data_id) . '&host_id=' . grv('host_id') . '&view_rrd=' . (isrv('current_rrd') ? gnrv('current_rrd') : '0'));
|
||||||
header_redirect ./data_sources.php header('Location: data_sources.php?action=ds_edit&id=' . grv('id') . "&view_rrd=$data_template_rrd_id");
|
header_redirect ./data_sources.php header('Location: data_sources.php?action=ds_edit&id=' . grv('id') . "&view_rrd=$data_template_rrd_id");
|
||||||
@@ -445,7 +447,7 @@ header_redirect ./include/auth.php header('Location: ' . CACTI_PATH_URL . 'a
|
|||||||
header_redirect ./include/auth.php header('Location: ' . CACTI_PATH_URL . 'auth_changepassword.php?ref=' . rawurlencode(validate_redirect_url($_SERVER['HTTP_REFERER'] ?? '', 'index.php')));
|
header_redirect ./include/auth.php header('Location: ' . CACTI_PATH_URL . 'auth_changepassword.php?ref=' . rawurlencode(validate_redirect_url($_SERVER['HTTP_REFERER'] ?? '', 'index.php')));
|
||||||
header_redirect ./include/auth.php header('Location: ' . CACTI_PATH_URL . 'install/');
|
header_redirect ./include/auth.php header('Location: ' . CACTI_PATH_URL . 'install/');
|
||||||
header_redirect ./include/content/index.php header('Location:../index.php');
|
header_redirect ./include/content/index.php header('Location:../index.php');
|
||||||
header_redirect ./include/csrf.php header('Location: ' . sanitize_uri($_SERVER['REQUEST_URI']));
|
header_redirect ./include/csrf.php header('Location: ' . $redirect_target);
|
||||||
header_redirect ./include/fa/css/index.php header('Location:../index.php');
|
header_redirect ./include/fa/css/index.php header('Location:../index.php');
|
||||||
header_redirect ./include/fa/index.php header('Location:../index.php');
|
header_redirect ./include/fa/index.php header('Location:../index.php');
|
||||||
header_redirect ./include/fa/js/index.php header('Location:../index.php');
|
header_redirect ./include/fa/js/index.php header('Location:../index.php');
|
||||||
@@ -514,6 +516,12 @@ header_redirect ./lib/html_graph.php header('Location: permission_denied.php')
|
|||||||
header_redirect ./lib/html_graph.php header('Location: permission_denied.php');
|
header_redirect ./lib/html_graph.php header('Location: permission_denied.php');
|
||||||
header_redirect ./lib/html_graph.php header('Location: permission_denied.php');
|
header_redirect ./lib/html_graph.php header('Location: permission_denied.php');
|
||||||
header_redirect ./lib/html_graph.php header('Location: graph_view.php?action=list');
|
header_redirect ./lib/html_graph.php header('Location: graph_view.php?action=list');
|
||||||
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
||||||
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
||||||
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
||||||
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
||||||
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
||||||
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
||||||
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page() . '?action=item_edit&id=' . gnrv('report_id') . '&item_id=' . (empty($item_id) ? gnrv('id') : $item_id));
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page() . '?action=item_edit&id=' . gnrv('report_id') . '&item_id=' . (empty($item_id) ? gnrv('id') : $item_id));
|
||||||
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
||||||
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
header_redirect ./lib/html_reports.php header('Location: ' . get_reports_page());
|
||||||
@@ -556,6 +564,7 @@ header_redirect ./package_import.php header('Location: package_import.php');
|
|||||||
header_redirect ./package_import.php header('Location: package_import.php');
|
header_redirect ./package_import.php header('Location: package_import.php');
|
||||||
header_redirect ./package_import.php header('Location: package_import.php?package_location=0');
|
header_redirect ./package_import.php header('Location: package_import.php?package_location=0');
|
||||||
header_redirect ./package_import.php header('Location: package_import.php');
|
header_redirect ./package_import.php header('Location: package_import.php');
|
||||||
|
header_redirect ./package_import.php header('Location: package_import.php');
|
||||||
header_redirect ./package_import.php header('Location: package_import.php?package_location=' . $package_location);
|
header_redirect ./package_import.php header('Location: package_import.php?package_location=' . $package_location);
|
||||||
header_redirect ./package_keys.php header('Location: package_keys.php?header=false');
|
header_redirect ./package_keys.php header('Location: package_keys.php?header=false');
|
||||||
header_redirect ./package_keys.php header('Location: package_keys.php?header=false');
|
header_redirect ./package_keys.php header('Location: package_keys.php?header=false');
|
||||||
@@ -607,6 +616,11 @@ header_redirect ./templates_export.php header('Location: templates_export.ph
|
|||||||
header_redirect ./templates_import.php header('Location: templates_import.php');
|
header_redirect ./templates_import.php header('Location: templates_import.php');
|
||||||
header_redirect ./templates_import.php header('Location: templates_import.php');
|
header_redirect ./templates_import.php header('Location: templates_import.php');
|
||||||
header_redirect ./tests/index.php header('Location:../index.php');
|
header_redirect ./tests/index.php header('Location:../index.php');
|
||||||
|
header_redirect ./tree.php header('Location: tree.php');
|
||||||
|
header_redirect ./tree.php header('Location: tree.php');
|
||||||
|
header_redirect ./tree.php header('Location: tree.php');
|
||||||
|
header_redirect ./tree.php header('Location: tree.php');
|
||||||
|
header_redirect ./tree.php header('Location: tree.php');
|
||||||
header_redirect ./tree.php header('Location: tree.php');
|
header_redirect ./tree.php header('Location: tree.php');
|
||||||
header_redirect ./tree.php header('Location: tree.php');
|
header_redirect ./tree.php header('Location: tree.php');
|
||||||
header_redirect ./tree.php header('Location: tree.php');
|
header_redirect ./tree.php header('Location: tree.php');
|
||||||
|
|||||||
|
Can't render this file because it contains an unexpected character in line 22 and column 251.
|
@@ -144,22 +144,57 @@ switch (grv('action')) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 'copy_node':
|
case 'copy_node':
|
||||||
|
if (!is_tree_allowed((int) grv('tree_id'))) {
|
||||||
|
raise_message('tree_idor', __('You do not have permission to modify this tree.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: tree.php');
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
api_tree_copy_node(grv('tree_id'), grv('id'), grv('parent'), grv('position'));
|
api_tree_copy_node(grv('tree_id'), grv('id'), grv('parent'), grv('position'));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'create_node':
|
case 'create_node':
|
||||||
|
if (!is_tree_allowed((int) grv('tree_id'))) {
|
||||||
|
raise_message('tree_idor', __('You do not have permission to modify this tree.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: tree.php');
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
api_tree_create_node(grv('tree_id'), grv('id'), grv('position'), gnrv('text'));
|
api_tree_create_node(grv('tree_id'), grv('id'), grv('position'), gnrv('text'));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'delete_node':
|
case 'delete_node':
|
||||||
|
if (!is_tree_allowed((int) grv('tree_id'))) {
|
||||||
|
raise_message('tree_idor', __('You do not have permission to modify this tree.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: tree.php');
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
api_tree_delete_node(grv('tree_id'), grv('id'));
|
api_tree_delete_node(grv('tree_id'), grv('id'));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'move_node':
|
case 'move_node':
|
||||||
|
if (!is_tree_allowed((int) grv('tree_id'))) {
|
||||||
|
raise_message('tree_idor', __('You do not have permission to modify this tree.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: tree.php');
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
api_tree_move_node(grv('tree_id'), grv('id'), grv('parent'), grv('position'));
|
api_tree_move_node(grv('tree_id'), grv('id'), grv('parent'), grv('position'));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'rename_node':
|
case 'rename_node':
|
||||||
|
if (!is_tree_allowed((int) grv('tree_id'))) {
|
||||||
|
raise_message('tree_idor', __('You do not have permission to modify this tree.'), MESSAGE_LEVEL_ERROR);
|
||||||
|
header('Location: tree.php');
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
api_tree_rename_node(grv('tree_id'), grv('id'), gnrv('text'));
|
api_tree_rename_node(grv('tree_id'), grv('id'), gnrv('text'));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -1265,7 +1300,7 @@ function tree_edit(bool $partial = false) : void {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('create_node.jstree', function (e, data) {
|
.on('create_node.jstree', function (e, data) {
|
||||||
$.get('?action=create_node', { 'id' : data.node.parent, 'tree_id' : $('#id').val(), 'position' : data.position, 'text' : data.node.text })
|
$.post('?action=create_node', { 'id' : data.node.parent, 'tree_id' : $('#id').val(), 'position' : data.position, 'text' : data.node.text, '__csrf_magic' : csrfMagicToken })
|
||||||
.done(function (d) {
|
.done(function (d) {
|
||||||
data.instance.set_id(data.node, d.id);
|
data.instance.set_id(data.node, d.id);
|
||||||
data.instance.set_text(data.node, d.text);
|
data.instance.set_text(data.node, d.text);
|
||||||
@@ -1281,7 +1316,7 @@ function tree_edit(bool $partial = false) : void {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on('rename_node.jstree', function (e, data) {
|
.on('rename_node.jstree', function (e, data) {
|
||||||
$.get('?action=rename_node', { 'id' : data.node.id, 'tree_id' : $('#id').val(), 'text' : data.text })
|
$.post('?action=rename_node', { 'id' : data.node.id, 'tree_id' : $('#id').val(), 'text' : data.text, '__csrf_magic' : csrfMagicToken })
|
||||||
.done(function (d) {
|
.done(function (d) {
|
||||||
if (d.result == 'false') {
|
if (d.result == 'false') {
|
||||||
data.instance.set_text(data.node, d.text);
|
data.instance.set_text(data.node, d.text);
|
||||||
@@ -1296,7 +1331,7 @@ function tree_edit(bool $partial = false) : void {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.on('move_node.jstree', function (e, data) {
|
.on('move_node.jstree', function (e, data) {
|
||||||
$.get('?action=move_node', { 'id' : data.node.id, 'tree_id' : $('#id').val(), 'parent' : data.parent, 'position' : data.position })
|
$.post('?action=move_node', { 'id' : data.node.id, 'tree_id' : $('#id').val(), 'parent' : data.parent, 'position' : data.position, '__csrf_magic' : csrfMagicToken })
|
||||||
.always(function () {
|
.always(function () {
|
||||||
var st = data.instance.get_state();
|
var st = data.instance.get_state();
|
||||||
data.instance.load_node(data.instance.get_parent(data.node.id), function () { this.set_state(st); });
|
data.instance.load_node(data.instance.get_parent(data.node.id), function () { this.set_state(st); });
|
||||||
@@ -1313,7 +1348,7 @@ function tree_edit(bool $partial = false) : void {
|
|||||||
$('#graphs').jstree().deselect_all();
|
$('#graphs').jstree().deselect_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
$.get('?action=copy_node', { 'id' : data.original.id, 'tree_id' : $('#id').val(), 'parent' : data.parent, 'position' : data.position })
|
$.post('?action=copy_node', { 'id' : data.original.id, 'tree_id' : $('#id').val(), 'parent' : data.parent, 'position' : data.position, '__csrf_magic' : csrfMagicToken })
|
||||||
.always(function () {
|
.always(function () {
|
||||||
var st = data.instance.get_state();
|
var st = data.instance.get_state();
|
||||||
data.instance.load_node(data.instance.get_parent(data.node.id), function () { this.set_state(st); });
|
data.instance.load_node(data.instance.get_parent(data.node.id), function () { this.set_state(st); });
|
||||||
|
|||||||
Reference in new issue
Block a user