VYPR
High severity7.5NVD Advisory· Published Aug 9, 2017· Updated May 13, 2026

CVE-2015-7764

CVE-2015-7764

Description

Lemur 0.1.4 does not use sufficient entropy in its IV when encrypting AES in CBC mode.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
lemurPyPI
< 0.1.50.1.5

Patches

1
394e18f76e5e

Merge pull request #123 from rpicard/master

https://github.com/Netflix/lemurkevglissOct 14, 2015via ghsa
7 files changed · +104 23
  • docs/administration/index.rst+11 5 modified
    @@ -87,17 +87,23 @@ Basic Configuration
             >>> secret_key = secret_key + ''.join(random.choice(string.digits) for x in range(6))
     
     
    -.. data:: LEMUR_ENCRYPTION_KEY
    +.. data:: LEMUR_ENCRYPTION_KEYS
         :noindex:
     
    -        The LEMUR_ENCRYPTION_KEY is used to encrypt data at rest within Lemur's database. Without this key Lemur will refuse
    -        to start.
    +        The LEMUR_ENCRYPTION_KEYS is used to encrypt data at rest within Lemur's database. Without a key Lemur will refuse
    +        to start. Multiple keys can be provided to facilitate key rotation. The first key in the list is used for
    +        encryption and all keys are tried for decryption until one works. Each key must be 32 URL safe base-64 encoded bytes.
     
    -        See `LEMUR_TOKEN_SECRET` for methods of secure secret generation.
    +        Running lemur create_config will securely generate a key for your configuration file.
    +        If you would like to generate your own, we recommend the following method:
    +
    +            >>> import os
    +            >>> import base64
    +            >>> base64.urlsafe_b64encode(os.urandom(32))
     
         ::
     
    -        LEMUR_ENCRYPTION_KEY = 'supersupersecret'
    +        LEMUR_ENCRYPTION_KEYS = ['1YeftooSbxCiX2zo8m1lXtpvQjy27smZcUUaGmffhMY=', 'LAfQt6yrkLqOK5lwpvQcT4jf2zdeTQJV1uYeh9coT5s=']
     
     
     Certificate Default Options
    
  • docs/faq.rst+2 2 modified
    @@ -4,8 +4,8 @@ Frequently Asked Questions
     Common Problems
     ---------------
     
    -In my startup logs I see *'Aborting... Lemur cannot locate db encryption key, is LEMUR_ENCRYPTION_KEY set?'*
    -  You likely have not correctly configured **LEMUR_ENCRYPTION_KEY**. See
    +In my startup logs I see *'Aborting... Lemur cannot locate db encryption key, is LEMUR_ENCRYPTION_KEYS set?'*
    +  You likely have not correctly configured **LEMUR_ENCRYPTION_KEYS**. See
       :doc:`administration/index` for more information.
     
     
    
  • lemur/certificates/models.py+2 4 modified
    @@ -13,9 +13,7 @@
     from sqlalchemy.orm import relationship
     from sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean
     
    -from sqlalchemy_utils import EncryptedType
    -
    -from lemur.utils import get_key
    +from lemur.utils import Vault
     from lemur.database import db
     from lemur.plugins.base import plugins
     
    @@ -213,7 +211,7 @@ class Certificate(db.Model):
         id = Column(Integer, primary_key=True)
         owner = Column(String(128))
         body = Column(Text())
    -    private_key = Column(EncryptedType(String, get_key))
    +    private_key = Column(Vault)
         status = Column(String(128))
         deleted = Column(Boolean, index=True)
         name = Column(String(128))
    
  • lemur/manage.py+4 2 modified
    @@ -72,7 +72,7 @@
     
     # You should consider storing these separately from your config
     LEMUR_TOKEN_SECRET = '{secret_token}'
    -LEMUR_ENCRYPTION_KEY = '{encryption_key}'
    +LEMUR_ENCRYPTION_KEYS = '{encryption_key}'
     
     # this is a list of domains as regexes that only admins can issue
     LEMUR_RESTRICTED_DOMAINS = []
    @@ -171,7 +171,9 @@ def generate_settings():
         settings file.
         """
         output = CONFIG_TEMPLATE.format(
    -        encryption_key=base64.b64encode(os.urandom(KEY_LENGTH)),
    +        # we use Fernet.generate_key to make sure that the key length is
    +        # compatible with Fernet
    +        encryption_key=Fernet.generate_key(),
             secret_token=base64.b64encode(os.urandom(KEY_LENGTH)),
             flask_secret_key=base64.b64encode(os.urandom(KEY_LENGTH)),
         )
    
  • lemur/roles/models.py+2 3 modified
    @@ -12,9 +12,8 @@
     from sqlalchemy.orm import relationship
     from sqlalchemy import Column, Integer, String, Text, ForeignKey
     
    -from sqlalchemy_utils import EncryptedType
     from lemur.database import db
    -from lemur.utils import get_key
    +from lemur.utils import Vault
     from lemur.models import roles_users
     
     
    @@ -23,7 +22,7 @@ class Role(db.Model):
         id = Column(Integer, primary_key=True)
         name = Column(String(128), unique=True)
         username = Column(String(128))
    -    password = Column(EncryptedType(String, get_key))
    +    password = Column(Vault)
         description = Column(Text)
         authority_id = Column(Integer, ForeignKey('authorities.id'))
         user_id = Column(Integer, ForeignKey('users.id'))
    
  • lemur/tests/conf.py+1 1 modified
    @@ -21,7 +21,7 @@
     
     # You should consider storing these separately from your config
     LEMUR_TOKEN_SECRET = 'test'
    -LEMUR_ENCRYPTION_KEY = 'jPd2xwxgVGXONqghHNq7/S761sffYSrT3UAgKwgtMxbqa0gmKYCfag=='
    +LEMUR_ENCRYPTION_KEYS = 'o61sBLNBSGtAckngtNrfVNd8xy8Hp9LBGDstTbMbqCY='
     
     # this is a list of domains as regexes that only admins can issue
     LEMUR_RESTRICTED_DOMAINS = []
    
  • lemur/utils.py+82 6 modified
    @@ -5,17 +5,93 @@
         :license: Apache, see LICENSE for more details.
     .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
     """
    +import six
     from flask import current_app
    +from cryptography.fernet import Fernet, MultiFernet
    +import sqlalchemy.types as types
     
     
    -def get_key():
    +def get_keys():
         """
    -    Gets the current encryption key
    +    Gets the encryption keys.
    +
    +    This supports multiple keys to facilitate key rotation. The first
    +    key in the list is used to encrypt. Decryption is attempted with
    +    each key in succession.
     
         :return:
         """
    +
    +    # when running lemur create_config, this code needs to work despite
    +    # the fact that there is not a current_app with a config at that point
         try:
    -        return current_app.config.get('LEMUR_ENCRYPTION_KEY').strip()
    -    except RuntimeError:
    -        print("No Encryption Key Found")
    -        return ''
    +        keys = current_app.config.get('LEMUR_ENCRYPTION_KEYS')
    +    except:
    +        print("no encryption keys")
    +        return []
    +
    +    # this function is expected to return a list of keys, but we want
    +    # to let people just specify a single key
    +    if not isinstance(keys, list):
    +        keys = [keys]
    +
    +    # make sure there is no accidental whitespace
    +    keys = [key.strip() for key in keys]
    +
    +    return keys
    +
    +
    +class Vault(types.TypeDecorator):
    +    """
    +    A custom SQLAlchemy column type that transparently handles encryption.
    +
    +    This uses the MultiFernet from the cryptography package to faciliate
    +    key rotation. That class handles encryption and signing.
    +
    +    Fernet uses AES in CBC mode with 128-bit keys and PKCS7 padding. It
    +    uses HMAC-SHA256 for ciphertext authentication. Initialization
    +    vectors are generated using os.urandom().
    +    """
    +
    +    # required by SQLAlchemy. defines the underlying column type
    +    impl = types.Binary
    +
    +    def process_bind_param(self, value, dialect):
    +        """
    +        Encrypt values on the way into the database.
    +
    +        MultiFernet.encrypt uses the first key in the list.
    +        """
    +
    +        # we assume that the user's keys are already Fernet keys (32 byte
    +        # keys that have been base64 encoded).
    +        self.keys = [Fernet(key) for key in get_keys()]
    +
    +        # we only support strings and they should be of type bytes for Fernet
    +        if not isinstance(value, six.string_types):
    +            return None
    +
    +        value = bytes(value)
    +
    +        return MultiFernet(self.keys).encrypt(value)
    +
    +    def process_result_value(self, value, dialect):
    +        """
    +        Decrypt values on the way out of the database.
    +
    +        MultiFernet tries each key until one works.
    +        """
    +
    +        # we assume that the user's keys are already Fernet keys (32 byte
    +        # keys that have been base64 encoded).
    +        self.keys = [Fernet(key) for key in get_keys()]
    +
    +        # if the value is not a string we aren't going to try to decrypt
    +        # it. this is for the case where the column is null
    +        if not isinstance(value, six.string_types):
    +            return None
    +
    +        # TODO this may raise an InvalidToken exception in certain
    +        # cases. Should we handle that?
    +        # https://cryptography.io/en/latest/fernet/#cryptography.fernet.Fernet.decrypt
    +        return MultiFernet(self.keys).decrypt(value)
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.