high · 7.7CVE-2026-71303Aug 18, 2026

CVE-2026-71303: Lemur ACME Authority Update SSRF (Incomplete Fix)

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Lemur's certificate authority update endpoint lets any user with an authority role silently replace the ACME directory URL with an internal address, causing the server to make outbound requests to…

Packagelemur
Ecosystempip
Affected<= 1.9.2
Fixed in1.9.3
CVE-2026-71303: Lemur ACME Authority Update SSRF (Incomplete Fix)

The problem

Lemur 1.9.2 added _validate_acme_url() to block non-allowlisted ACME directory URLs, but only wired that check into the authority creation path (POST). The authority update endpoint (PUT /api/1/authorities/<id>) accepted and stored the options field verbatim, with no allowlist check at all.

Any authenticated user who has been granted membership in an ACME authority's role group (a routine operational grant that lets them issue certificates) can call the PUT endpoint to overwrite acme_url with an arbitrary URL. On the next certificate issuance via that authority, Lemur's celery worker reads the stored URL and issues an outbound HTTP request to it, achieving SSRF.

In cloud deployments this reaches AWS IMDSv1, GCP metadata, or Azure IMDS, and can yield IAM credentials.

Proof of concept

A working proof-of-concept for CVE-2026-71303 in lemur, with the exact payload below.

http
PUT /api/1/authorities/1 HTTP/1.1
Host: lemur.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "owner": "security@example.com",
  "description": "Let's Encrypt Production",
  "active": true,
  "roles": [{"id": 5}, {"id": 6}, {"id": 7}],
  "options": "[{\"name\": \"acme_url\", \"value\": \"http://169.254.169.254/latest/meta-data/\"}]"
}

The root cause is a missing call to _validate_acme_url() in the PUT code path. In lemur/authorities/service.py, the update() function writes options straight to the database when it is non-empty, with no inspection of individual option names or values.

The sink is in lemur/plugins/lemur_acme/acme_handlers.py, where setup_acme_client() reads acme_url from the stored JSON and passes it directly to ClientV2.get_directory(directory_url, net), an outbound HTTP call.

The patch (commit edca0390) closes the gap by calling _validate_acme_url() whenever options is provided on an update, mirroring the check that already existed on the POST creation path. CWE-918 (SSRF) applies because the application makes server-side network requests to a URL that a non-privileged user can fully control.

The fix

Upgrade to Lemur 1.9.3 (commit edca0390f930344d65ff4ca37a669c2320e3dfad). The patch adds an acme_url allowlist check inside the authority update path so that _validate_acme_url() is enforced on both POST and PUT. As a short-term workaround, set ACME_DIRECTORY_HOST_ALLOWLIST explicitly and restrict authority role membership to trusted operators only.

Reporter not attributed.

References: [1][2][3][4]

Related research