GHSA-w4rc-p66m-x6qq Grav Form Plugin has an Anonymous Page Content Overwrite via Form File Upload filename Override
Summary(Tested on Form 9.0.3 released on April, 28th)The Form plugin's file upload handler at `user/plugins/form/classes/Form.php:583` accepts a POST-supplied `filename` parameter (`$filename = $post['filename'] ?? $upload['file']['name']`) that overrides the original uploaded filename. The override passes through `Utils::checkFilename()`, which blocks only a narrow extension list (`.php*`, `.htm*`, `.js`, `.exe`). Markdown (`.md`) is **not** blocked.A page's directory under `user/pages/` contains its `.md` content file (e.g. `default.md`, `form.md`). When a form's file upload field has `accept: ['*']` (or any policy that admits text files), an unauthenticated visitor can:Upload **arbitrary content** with **`filename=form.md`** (or other page-content filenames),Submit the form to trigger `Form::copyFiles()`, which **overwrites the page's own `.md` file**.Details**Vulnerable code path**`user/plugins/form/classes/Form.php:580-606` (in `uploadFiles()`):`Utils::checkFilename()` (`system/src/Grav/Common/Utils.php:980`) blocks `..`, slashes, null bytes, leading/trailing dots, and the `uploads_dangerous_extensions` list. The default list contains: `php, php2-5, phar, phtml, html, htm, shtml, shtm, js, exe`. **`md` is not on the list**.The MIME check (lines 627-654) uses `Utils::getMimeByFilename($filename)` against the blueprint's `accept` list. With `accept: ['*']`, all filenames pass.After upload, the file is held in flash storage. When the form is submitted, `Form::copyFiles()` (`user/plugins/form/classes/Form.php:1041-1074`) calls `$upload->moveTo($destination)`:`moveTo()` does not check whether `$destination` is an existing protected file — if `form.md` (the page's own content) already exists at the destination, it is **overwritten**.A Grav page's `.md` file is parsed as YAML frontmatter + Markdown content. Whatever content the attacker uploaded becomes the new page definition.PoC**Setup** :Any existing page with a form like this — a "generic upload" form is the realistic case:Atacker uploads a malicious md file that replaces the form's md file. Lets say the form is under the path `/upload`.(Hash above is bcrypt for `PwnPass123!`.)Attacker accesses the new markdown file under the original path  and loads the new markdown file `GET /upload`.Attacker sends a form POST request to `/upload` and change the form_name to whatever the payload form name is.
Keep in mind the nonce has to be valid.Login as a newly created super admin user.ImpactGrav pages that allows user to uploads any file (besides the ones in the blocklist) with the default `self@` configuration  is able to upload a malicious markdown file to overwrite the existing markdown file. In this case, unauthenticated users were able to escalate their privileges to super-admin.RemediationBlock sensitive page-content filenames at uploadIn `user/plugins/form/classes/Form.php`, after `Utils::checkFilename()` succeeds, add a content-area-aware check:Add `md, yaml, yml, json, twig, ini` to the global `security.uploads_dangerous_extensions` list — these all carry executable semantics in Grav's runtime even though they are not "PHP".
CVSS v4.0 score: 7.7