u5CMS: Session Forgery, Privilege Escalation, and RCE
A static SAML nonce in u5CMS allowed any authenticated user to forge sessions as an administrator, leading to RCE. Two additional XSS and open redirect issues were patched in the same release.
TL;DR: u5CMS contained a SAML authentication flaw where the session nonce was not bound to the authenticated user’s identity. Any authenticated low-privilege user could forge a session as an administrator by swapping a single cookie value. Since u5CMS allows admins to execute arbitrary PHP, this led to remote code execution on the host. Two additional issues — a reflected XSS and an open redirect — were patched in the same release cycle. All issues have since been patched.
Some time ago I had to test a website that was using u5CMS. u5cms is an open-source PHP content management system. It supports SAML-based single sign-on as an authentication mechanism for backend users. During the testing I had an authenticated user, which had basic permissions. That is, I could login and use the CMS to edit the content of the website.
The login worked like that: a user logs in using the SAML-based SSO. This triggered a typical SAML flow, which, after successful authentication on the IdP and after a successful SAML assertion, the CMS stores the authenticated identity in a browser cookie (u5samlusername) and a nonce in a second cookie (u5samlnonce). On subsequent requests, saml.inc.php validates both cookies to determine whether the session is legitimate.
The root cause is simple: the session nonce was a shared, static server-side value with no binding to the authenticated user’s identity.
After a successful SAML login, the server set:
u5samlusername = [email protected]u5samlnonce = <static_server_nonce>The verification in saml.inc.php was:
if ($_COOKIE['u5samlnonce'] != $u5samlnonce || !isset($_COOKIE['u5samlusername'])) die(...);$founduserincookie = $_COOKIE['u5samlusername'];The nonce check confirmed the cookie matched the server-side value — but that value was identical for every user. The username was then consumed directly from the cookie without any cryptographic tie to the nonce that “proved” the session was authentic.
In other words: the two cookies were completely independent. Anyone holding a valid nonce (i.e., any authenticated SAML user) could set u5samlusername to an arbitrary value and pass the auth check as that identity. Here’s were the interesting thing comes: if the arbitrary value of the u5samlusername was a valid use, we would get access to the CMS as this user. So what happens if we manage to find a valid high-privileged user? That should be possible doing some investigation about the website using the CMS.
- Attacker authenticates via SAML as a low-privilege contributor, receiving valid session cookies.
- Attacker reads
u5samlnoncefrom their own browser — this is the same value the server issues to every authenticated user. - Attacker overwrites
u5samlusernamecookie to[email protected](admin email addresses are often discoverable or guessable). saml.inc.phpevaluates$_COOKIE['u5samlnonce'] == $u5samlnonce→ passes.$founduserincookieis set to[email protected]— the CMS now treats the attacker as the admin.- Admin access in u5CMS includes a PHP template editor capable of executing arbitrary server-side code → remote code execution.
The only prerequisites were: a valid contributor-level SAML account and knowledge of an admin’s email address.
A contributor-level user could silently escalate to full administrator access without any interaction from the victim. Because u5CMS exposes a PHP execution interface to administrators, successful privilege escalation translated directly into arbitrary code execution on the host machine — access to the filesystem, environment, database credentials, and any other resources available to the web server process.
Two further issues were identified and patched in the same release cycle:
Reflected XSS (u5admin/totalsizes.php, u5admin/totalsizes.oneparent.php)
The ?t= query parameter was echoed directly into an inline <script> block without sanitisation:
// Beforeecho '<script>totalsizes1=' . $_GET['t'] . ';...</script>';
// Afterecho '<script>totalsizes1=' . intval($_GET['t']) . ';...</script>';An attacker could supply a crafted value to execute arbitrary JavaScript in the context of an authenticated session.
Open Redirect (u5sys._login.php)
The ?u= redirect parameter accepted fully-qualified external URLs with no validation, allowing an attacker to craft a login link that redirected victims to an arbitrary domain after authentication — a standard phishing vector.
// Before — no validation, $u is passed directly to redirect logic
// Afterif (isset($_GET['u'])) { $target = $_GET['u']; $parsed = parse_url($target); if (!empty($parsed['host']) && strcasecmp($parsed['host'], $_SERVER['HTTP_HOST']) !== 0) { die('<center style="color:red">Error: Redirect to a different domain is not allowed.</center>'); }}The fix parses the target URL and rejects any redirect whose host component differs from the current server’s hostname. Relative paths (no host component) are allowed through unchanged, preserving normal internal redirects.
The session forgery was resolved by binding the nonce to the authenticated username at issuance:
// saml/login.php — on successful SAML assertionsetcookie('u5samlnonce', sha1($samlsalt . $u5samlnonce . $username), ...);
// saml.inc.php — on each authenticated requestif ($_COOKIE['u5samlnonce'] != sha1($samlsalt . $u5samlnonce . $_COOKIE['u5samlusername'])) die(...);The nonce is now an HMAC-style digest over the server salt, the base nonce, and the username. Swapping the username cookie invalidates the nonce unless the attacker also knows $samlsalt, which never leaves the server. The fix is minimal and correct.
Patches shipped in:
- v12.7.13 — SAML session forgery fix
- v12.7.14 — Reflected XSS and open redirect fixes
I was already in direct contact with the u5CMS maintainers, which made the disclosure process straightforward. The issues were reported and patched promptly, with fixes landing in v12.7.13 and v12.7.14.