GitHub Advisoryadvisory · Packagist

GHSA-9369-69wj-7m2f YesWiki Vulnerable to Authenticated PHP Object Injection in BazarImportAction via unserialize

CRITICAL Published 2026-07-09 Modified 2026-07-09 Official page ↗
Aliases
CVE-2026-52777
Ecosystem
Packagist
Packages
yeswiki/yeswiki
Severity
Critical
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Weaknesses
CWE-352, CWE-502
Reviewed
2026-07-09

Affected packages

• Packagist yeswiki/yeswiki: < 4.6.6 → fixed in 4.6.6

Description

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)); // 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]=`. 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=` 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]=` to `https:///?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=" \ -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.

Related

Alias · CVE (1)

  • CVE-2026-52777 YesWiki is a wiki system written in PHP. vulnerability

Weakness · CWE (2)

  • CWE-352 Cross-Site Request Forgery weakness
  • CWE-502 Deserialization of Untrusted Data weakness

References