GitHub Advisoryadvisory · PyPI
GHSA-g7p5-89mh-248h Lemur: Sub-CA creation never checks `AuthorityPermission` on the parent authority
MEDIUM
Official page ↗
- Aliases
- CVE-2026-71317
- Ecosystem
- PyPI
- Packages
- lemur
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N
- Weaknesses
- CWE-862
- Reviewed
- 2026-08-18
Affected packages
• PyPI lemur: < 1.9.3 → fixed in 1.9.3
Description
Summary
Repo under test: https://github.com/Netflix/lemur
When `ADMIN_ONLY_AUTHORITY_CREATION=False` (an explicitly supported and documented configuration), `POST /api/1/authorities` with `type=subca` never verifies that the caller holds `AuthorityPermission` on the supplied `parent` authority. The `parent` field is resolved by `AssociatedAuthoritySchema` via a raw `fetch_objects(Authority, data)` lookup, then passed straight through `service.create → mint → cryptography-issuer.create_authority`, which loads `options["parent"].authority_certificate.private_key` and signs a brand-new intermediate CA on the caller's behalf.
Any authenticated non-read-only user can therefore mint a sub-CA chained to any internal root whose private key Lemur holds — including roots they hold no role on — attach a role they already belong to, and immediately issue or offline-sign trusted leaf certificates for arbitrary names.
Affected route
`POST /api/1/authorities` (with `type=subca`)
Affected code
• `lemur/authorities/views.py:231` — gates only on `AuthorityCreatorPermission()` + `StrictRolePermission()`; no `AuthorityPermission` on `data['parent']`
• `lemur/authorities/schemas.py:58` — `parent = fields.Nested(AssociatedAuthoritySchema)`; `validate_subca` only checks presence
• `lemur/schemas.py:107` — `AssociatedAuthoritySchema.get_object` → `fetch_objects(Authority, data)` resolves any authority by id/name with no permission check
• `lemur/plugins/lemur_cryptography/plugin.py:40` — `private_key = options["authority"].authority_certificate.private_key` (set from `options["parent"]`) signs the new intermediate
• `lemur/auth/permissions.py:62` — `AuthorityCreatorPermission` becomes always-allow when `ADMIN_ONLY_AUTHORITY_CREATION=False`
• `lemur/certificates/views.py:538` — `is_private_authority` (true for `cryptography-issuer`) skips `USER_DOMAIN_AUTHORIZATION_PROVIDER` for subsequent leaf issuance
Impact
In deployments that set `ADMIN_ONLY_AUTHORITY_CREATION=False` to enable self-service CA creation, any authenticated non-read-only user — with zero permission on a given internal root CA — can obtain a working intermediate CA chained to that root. They can then:
• Issue TLS certificates for arbitrary names trusted by every relying party that trusts the internal root, bypassing `LEMUR_ALLOWED_DOMAINS`, sensitive-domain flags, and the per-user domain-authorization plugin.
• Export the sub-CA private key and sign end-entity certificates entirely outside Lemur, defeating all in-product issuance controls.
This converts "can create a self-contained test CA" into "can mint trusted certs under any internal PKI root in the organisation". The `ADMIN_ONLY_AUTHORITY_CREATION` documentation does not warn operators of this consequence.
Root cause
`AuthoritiesList.post` evaluates `AuthorityCreatorPermission` (a global "may create authorities" flag) and `StrictRolePermission`, but never evaluates `AuthorityPermission(parent.id, parent.roles)` against the caller-supplied `parent`. `AssociatedAuthoritySchema` is a pure lookup schema with no authz hook, and neither `authorities.service.create` nor `mint` re-check before invoking `issuer_plugin.create_authority(options)`. The bundled `cryptography-issuer` then uses the parent's stored private key directly.
Validated evidence
Static trace, confirmed by code inspection (validation status: `CONFIRMED`):
• `parent` is loaded via `AssociatedAuthoritySchema` (raw `fetch_objects`), passed unchecked through `views.post → service.create → mint → plugin.create_authority → issue_certificate`, where the parent authority's stored private key is read and used to sign the new intermediate.
• No call to `AuthorityPermission(parent.id, ...)` exists anywhere on this path.
• Precondition `ADMIN_ONLY_AUTHORITY_CREATION=False` is an explicitly supported config (`docs/administration.rst:517`).
Proof of concept / reproducer
Status: reconstructed from source report (static control-flow trace; not executed against a live CA).
Preconditions: `ADMIN_ONLY_AUTHORITY_CREATION=False`; attacker is an authenticated Lemur user holding any role other than `read-only`; `` is any internal `cryptography-issuer` root CA the attacker holds no role on; `` is any role the attacker already belongs to.
```bash
curl -sS -X POST "/api/1/authorities" \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"name": "attacker-subca",
"owner": "[email protected]",
"description": "poc",
"type": "subca",
"parent": {"id": },
"plugin": {"slug": "cryptography-issuer"},
"roles": [{"name": ""}],
"commonName": "attacker-intermediate",
"validityYears": 1
}'
```
The response contains a new authority whose `authority_certificate` is signed by ``'s private key. The caller is recorded as creator and holds `` on it, so `POST /api/1/certificates` against the new authority succeeds (and skips `allowed_issuance_for_domain` because `is_private_authority` is true).
Static-trace validation command from the source report:
```bash
grep -n 'parent' lemur/authorities/schemas.py lemur/authorities/views.py lemur/authorities/service.py \
&& sed -n '37,55p' lemur/plugins/lemur_cryptography/plugin.py
```
Source artifact: `audit/harnesses/public-repo-threat-model-harness/results/netflix-lemur-100run-mythos-20260627T051129Z/findings.jsonl` (run_022, finding cluster `lemur-subca-parent-authz`, 5/100 runs).
Suggested fix
In `AuthoritiesList.post` (or `authorities.service.create`), when `data.get('parent')` is present, enforce `AuthorityPermission(parent.id, [r.name for r in parent.roles]).can()` before invoking the issuer plugin, regardless of `ADMIN_ONLY_AUTHORITY_CREATION`. Additionally, update the `ADMIN_ONLY_AUTHORITY_CREATION` documentation to state that disabling it currently grants every authenticated user the ability to chain sub-CAs off any internal root whose private key Lemur holds. Consider also requiring admin (or an explicit per-parent capability) for any `type=subca` creation independent of the global flag.
Related
Alias · CVE (1)
- CVE-2026-71317 Lemur manages TLS certificate creation. vulnerability
Weakness · CWE (1)
- CWE-862 Missing Authorization weakness