Media

Lemur: Missing authorization check on POST /certificates/<id>/export for plugins with requires_key = False

  • Publicada: 18.08.2026
  • Actualizado: 11.10.2026 14:19

Summary

The CertificateExport handler in lemur/certificates/views.py nests its entire ownership / CertificatePermission check inside an if plugin.requires_key: branch. When the selected export plugin advertises requires_key = False, the authorization check is skipped entirely and any authenticated user can invoke plugin.export(cert.body, cert.chain, cert.private_key, options) against a certificate they do not own. The handler additionally writes a "key_view" audit-log event for every call, regardless of whether the plugin actually accessed the private key, polluting the audit trail with false positives.

Root Cause

lemur/certificates/views.py:1573:

python
if plugin.requires_key:
    if not cert.private_key:
        return (..., 400)
    else:
        if g.current_user != cert.user:
            owner_role = role_service.get_by_name(cert.owner)
            permission = CertificatePermission(owner_role, [x.name for x in cert.roles])
            if not permission.can():
                return (..., 403)
 
log_service.create(g.current_user, "key_view", certificate=cert)   # always logged
extension, passphrase, data = plugin.export(
    cert.body, cert.chain, cert.private_key, options
)

The authorization gate is structurally inside the if plugin.requires_key: block. With requires_key = False, control falls straight through to plugin.export(...) with no ownership check. The cert.private_key is passed to the plugin regardless of the flag — the flag only describes what the plugin advertises it needs, not what it actually receives.

The only currently shipping ExportPlugin with requires_key = False is JavaTruststoreExportPlugin (lemur/plugins/lemur_jks/plugin.py), whose export() ignores the key argument and emits a public-only Java truststore. The present-day data exposure is therefore limited to public certificate material. The bug is nonetheless filed as a real authorization gap because:

  1. The structural defect is latent and silent - any future requires_key = False ExportPlugin that does read cert.private_key will inherit the bypass with no test or code-review signal.
  2. The unconditional log_service.create(..., "key_view", ...) call falsely records key-view events for callers who never viewed a key, weakening incident-response signal.

Affected Endpoints

Method Path Source
POST /api/1/certificates/<id>/export lemur/certificates/views.py:1573

Impact

In the current codebase:

  • Any authenticated user can mint a Java truststore (java-truststore-jks plugin) containing any certificate's public body and chain, without owning the certificate or holding a role with permission over it.
  • The audit log records a key_view event for the calling user against that certificate, despite no private key having been accessed. Defenders investigating apparent key-view events will encounter false positives that they cannot distinguish from genuine accesses.

Latent risk:

  • A future ExportPlugin author who sets requires_key = False because their plugin can operate without a key (e.g., for a fall-back code path) but still uses the key when one is provided will silently leak private keys to any authenticated user. The same code review that approves the plugin will not flag this — the authorization invariant is held by a structurally distant if-branch in the view, not by the plugin itself.

Remediation

Lift the authorization check out of the if plugin.requires_key: block so it runs for every export call:

python
# Authorization first, unconditionally.
if g.current_user != cert.user:
    owner_role = role_service.get_by_name(cert.owner)
    permission = CertificatePermission(owner_role, [x.name for x in cert.roles])
    if not permission.can():
        return (dict(message="You are not authorized to export this certificate."), 403)
 
if plugin.requires_key:
    if not cert.private_key:
        return (dict(message="Plugin requires a key but none is present."), 400)
    log_service.create(g.current_user, "key_view", certificate=cert)   # only when key actually accessed
 
extension, passphrase, data = plugin.export(
    cert.body, cert.chain, cert.private_key, options
)

This makes the authorization gate independent of the plugin's requires_key flag and correctly scopes the key_view audit event to calls that actually involve key access.

Steps to Reproduce

  1. Set up Lemur with default configuration. Create an admin user admin and a non-admin user eve with the read-only role (or any role without certificate permissions).

  2. As admin, issue a certificate. Note its id.

  3. As eve, invoke export with the java-truststore-jks plugin:

bash
   curl -X POST https://lemur.local/api/1/certificates/<cert_id>/export \
        -H "Authorization: Bearer <eve_jwt>" \
        -H "Content-Type: application/json" \
        -d '{
              "plugin": {
                "slug": "java-truststore-jks",
                "plugin_options": [
                  {"name": "passphrase", "value": "test"}
                ]
              }
            }'
  1. Observe HTTP 200 with a base64-encoded JKS truststore in the response. eve had no permission over admin's certificate, yet successfully exported its public material.

  2. Inspect the audit log table or lemur logs list:

   psql lemur -c "SELECT user_id, log_type, certificate_id, logged_at FROM logs
                  WHERE certificate_id = <cert_id> ORDER BY logged_at DESC LIMIT 1;"

The log row shows log_type = 'key_view' for eve against admin's certificate, despite no private key actually being accessed by the truststore plugin - confirming the audit-log pollution facet of the bug.

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Python (PyPI) lemur < 1.9.3 1.9.3

Referencias

Puntuación CVSS 4.3/10
Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
Debilidad (CWE)
CWE-862
Plataforma
Python (PyPI)
Paquete
lemur
Publicada
18.08.2026
Actualizado
11.10.2026 14:19

Fuentes y licencias

OSV / GitHub Advisory Database - PYSEC-2026-3673 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-4h97-p9wq-chqj - CC BY 4.0

Historial de vulnerabilidades: lemur

Todas las vulnerabilidades publicadas de este paquete (Python (PyPI)), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Todas (18) en el archivo
Entradas en total
18
Primera → última
08.2017 08.2026
Sin corrección
0
Explotada en ataques
0

Distribución por gravedad

  • Crítica 1
  • Alta 9
  • Media 8

Divulgaciones por año

  1. 2017: 1
  2. 2018: 0
  3. 2019: 0
  4. 2020: 0
  5. 2021: 0
  6. 2022: 0
  7. 2023: 1
  8. 2024: 0
  9. 2025: 0
  10. 2026: 16
Gravedad:
  1. Lemur: Missing authorization check on POST /certificates/<id>/export for plugins with requires_key = False esta entrada
    Media · 4.3 CVE-2026-71322 < 1.9.3 1.9.3
  2. Alta · 7.5 CVE-2023-30797 < 1.3.2 1.3.2
  3. Alta · 7.5 CVE-2015-7764 < 0.1.5 0.1.5

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.