ApiController::deletePage() interpolates a page tag retrieved from the database into a DELETE FROM …_links WHERE to_tag = '$tag' query without escaping. The page tag is attacker-controlled — the POST /api/pages/{tag} API accepts arbitrary URL-encoded values, including single quotes, and stores them. A low-privilege authenticated user can therefore create a page whose tag is a SQL fragment, make the page non-orphaned via the standard {{include page="…"}} link mechanism, and then invoke the delete endpoint to execute arbitrary SQL inside the wiki database - including time-based blind data exfiltration from any table.
This is a classic second-order SQL injection: the INSERT correctly escapes the value, so the malicious tag is stored intact and the input passes every "is this value safe to put in the database?" check; the sink is the read-back-and-reuse path, where escaping is omitted.
includes/controllers/ApiController.phpApiController::deletePage($tag)@Route("/api/pages/{tag}", methods={"DELETE"}, options={"acl":{"+"}}) — acl:"+" means any authenticated user.// includes/controllers/ApiController.php (v4.6.5 = origin/doryphore-dev HEAD,
// lines 607–631)
public function deletePage($tag)
{
$pageManager = $this->getService(PageManager::class);
$pageController = $this->getService(PageController::class);
$dbService = $this->getService(DbService::class);
...
try {
$page = $pageManager->getOne($tag, null, false); // (a) safe SELECT
if (empty($page)) { ... } else {
$tag = isset($page['tag']) ? $page['tag'] : $tag;// ^ raw tag from DB
$result['notDeleted'] = [$tag];
if ($this->wiki->UserIsOwner($tag) || $this->wiki->UserIsAdmin()) {
if (!$pageManager->isOrphaned($tag)) {
$dbService->query(...
4.6.6Exploitability
AV:NAC:LPR:LUI:NScope
S:UImpact
C:HI:HA:L8.3/CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:LInjection