CVE-2026-52777
criticalpriority · track · 0EPSS 0.003 · 20 %ileUSEPSS · scored 2026-10-27
YesWiki is a wiki system written in PHP. Prior to version 4.6.6, there is an authenticated PHP object injection vulnerability in BazarImportAction via unserialize. This issue has been patched in version 4.6.6.
cvss
Technical severity, 0–10. One row per scoring source.
base score 0–10 · higher = more severe · hover a vector to decode⚠ sources disagree · high–critical · 7.6–9.4
ssvc
A stakeholder decision — act, attend, or track — from exploitation and impact.
weaknesses
The CWE weakness classes this flaw belongs to.
affected products (1)
Products and version ranges known to be affected.
advisories (1)
Vendor and ecosystem advisories describing this.
- GHSA-9369-69wj-7m2fYesWiki Vulnerable to Authenticated PHP Object Injection in BazarImportAction via unserialize2026-07-09ghsa · osvUS
## Details ### Sink `tools/bazar/services/CSVManager.php` line 372-399: ``` public function importEntry(array $importedEntries, string $formId): ?array { if (!$this->importdone) { // ... foreach ($importedEntries as $entry) { $entry = unserialize(base64_decode($entry)); // <-- SINK $entry = array_map('strval', $entry); // ... ``` There is no `['allowed_classes' => false]` argument; arbitrary classes are instantiated. The subsequent `array_map('strval', $entry)` additionally exercises `__toString` on each top-level array element, doubling the magic-method surface available to a gadget chain. ### Source `tools/bazar/actions/BazarImportAction.php`: ``` // formatArguments() 'mode' => (isset($_POST['submit_file']) && !empty($_FILES['fileimport']['name'])) ? 'submitfile' : (isset($_POST['importfiche']) ? 'importentries' : 'default'), 'importentries' => $_POST['importfiche'] ?? null, // run() case 'importentries': // ... $importedEntries = $this->CSVManager->importEntry($this->arguments['importentries'], $vID['id']); break; ``` `$_POST['importfiche']` flows directly to the sink. The `mode` switches to `'importentries'` whenever the request body contains the key, so an attacker need only POST `importfiche[0]=<payload>`. ### Reachability 1. The action is registered as `bazarimport`. The default `BazaR` page (`setup/sql/default-content.sql` -> `BazaR` page entry, ships with `{{bazar showexportbuttons="1"}}`) routes `?BazaR&vue=importer&id_typeannonce=<N>` to `BazarAction::run()` -> `case VOIR_IMPORTER -> callAction('bazarimport', ...)` (`tools/bazar/actions/BazarAction.php:257-258`). So the sink is reachable on a default install with no extra page authoring. 2. `BazarImportAction::run()` calls `$this->checkSecuredACL()` with the default `$adminOnly=true`. Only wiki admins (or accounts the admin has added to the `bazarimport` action ACL) can execute it. 3. The `importentries` branch does NOT invoke `CsrfTokenController::checkToken(...)`. Grepping `tools/bazar/actions/BazarImportAction.php` confirms the action class has no `csrf` or `checkToken` reference at all. This is asymmetric with sibling actions: `tools/bazar/controllers/FormController.php` does call `checkToken('main', 'POST', 'confirmDeleteToken')` for destructive operations. The import path skips the same protection. 4. Therefore the full kill chain for a remote attacker is: a. Identify any admin user on the target wiki. b. Deliver an HTML page (email, chat, link) that auto-POSTs `importfiche[0]=<base64-encoded PHPGGC payload>` to `https://<wiki>/?BazaR&vue=importer&id_typeannonce=1`. c. The admin's session cookie is sent automatically; the action passes `checkSecuredACL`; the unserialize fires. ### Gadget chain availability `composer.json` requires `doctrine/annotations ^1.11` and `doctrine/cache ^1.10`. Both have published PHPGGC chains (`Doctrine/RCE1`, `Doctrine/FW1`, `Doctrine/FW2`, etc., from https://github.com/ambionics/phpggc). These chains terminate in either `system($cmd)` (RCE1) or `file_put_contents($php_file, $contents)` (FW1) entry-points -- both sufficient to give the attacker shell on the YesWiki host. This advisory does not include a working PHPGGC chain end-to-end (writing a chain that survives YesWiki's exact dependency-resolved class graph is separate work). The PoC demonstrates the primitive (attacker-controlled class instantiation + magic-method execution); the chain is a downstream exercise using public tooling. ### Past advisories cross-check YesWiki's published GitHub advisories cover XSS, SQLi, arbitrary-PHP-file-write RCE, path traversal, and unauthenticated backup download. None covers an `unserialize` / PHP-object-injection sink, so this is a novel vulnerability class for the project. ## PoC A self-contained PoC reproducing the inner loop is available; it copies the exact two-line sink and proves that attacker-controlled `__destruct` runs without booting the full application. Run: ``` php poc.php ``` Output (verbatim): ``` Crafted importfiche[0] payload (form-ready, urlencoded): YToxOntpOjA7Tzo2OiJHYWRnZXQiOjE6e3M6NjoibWFya2VyIjtzOjIyOiJQV05FRC1GUk9NLVVOU0VSSUFMSVpFIjt9fQ%3D%3D == before importEntry == [Gadget] __destruct fired with marker='PWNED-FROM-UNSERIALIZE' PHP Fatal error: Uncaught Error: Object of class Gadget could not be converted to string ... [Gadget] __destruct fired with marker='PWNED-FROM-UNSERIALIZE' ``` The two `[Gadget] __destruct fired` lines (one from inside the loop, one from the engine shutdown after the TypeError) confirm that the attacker-defined `Gadget::__destruct` executed -- with the attacker-supplied marker -- inside the unmodified `importEntry` code path. End-to-end against a live YesWiki install: ``` curl -i -b "yeswiki_session=<admin_cookie>" \ -X POST "https://wiki.example.com/?BazaR&vue=importer&id_typeannonce=1" \ --data-urlencode \ "importfiche[0]=YToxOntpOjA7Tzo2OiJHYWRnZXQiOjE6e3M6NjoibWFya2VyIjtzOjIyOiJQV05FRC1GUk9NLVVOU0VSSUFMSVpFIjt9fQ==" ``` (replace the payload with a real PHPGGC `Doctrine/FW1` or `Doctrine/RCE1` output to obtain RCE on the target host). ## Impact - Authenticated wiki admin who lands on attacker-controlled HTML obtains remote code execution on the YesWiki server (via the cross-site forgery path; no admin interaction with the import UI is required). - An attacker who has already compromised an admin password upgrades from "wiki content management" to "OS shell on the hosting box". - The compromise survives the wiki layer entirely: the attacker can write web shells, exfiltrate other sites on shared hosting, modify `wakka.config.php`, dump the MySQL database, and pivot from there. ## Suggested fix 1. `tools/bazar/services/CSVManager.php::importEntry` -- pass `['allowed_classes' => false]` to `unserialize`, or, better, replace the base64+serialize transport with the JSON transport the current UI already uses (`?api/entries/{formId}` POST in `tools/bazar/presentation/javascripts/bazar-import.js`). The serialized-PHP transport appears to be an unused legacy path. 2. `tools/bazar/actions/BazarImportAction.php` -- add a `CsrfTokenController::checkToken('main', 'POST', 'csrf-token', false)` guard for the `'importentries'` mode (and any other state-changing modes). The existing `tools/bazar/controllers/FormController.php` pattern can be lifted directly.
sources
Which sources assert this record, and when.
references (3) nvd
External citations — patches, advisories, analysis.
Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.
Free scan