CVE-2015-4082 (Python, published 2017-08-18T16:29Z)
Description: value: attic before 0.15 does not confirm unencrypted backups with the user, which allows remote attackers with read and write privileges for the encrypted repository to obtain potentially sensitive information by changing the manifest type byte of the repository to "unencrypted / without key file".
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-264 \u2014 Permissions, Privileges, and Access Controls
Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Repository: https://github.com/jborg/attic
Files changed:
CHANGES
attic/archiver.py
attic/cache.py
attic/testsuite/archiver.py
Commit message: Require approval before accessing previously unknown unencrypted repositories

Closes #271


Vulnerable code:
    def __init__(self, repository, key, manifest, path=None, sync=True):
        self.lock.release()
        info_output2 = self.attic('info', self.repository_location + '::test')

Fixed code:
- Require approval before accessing previously unknown unencrypted repositories (#271)
        Cache(repository, key, manifest, warn_if_unencrypted=False)
import sys
from .key import PlaintextKey
    class CacheInitAbortedError(Error):
        """Cache initialization aborted"""


    class EncryptionMethodMismatch(Error):
        """Repository encryption method changed since last acccess, refusing to continue
        """

    def __init__(self, repository, key, manifest, path=None, sync=True, warn_if_unencrypted=True):
        self.lock = None
            if warn_if_unencrypted and isinstance(key, PlaintextKey):
                if 'ATTIC_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK' not in os.environ:
                    print("""Warning: Attempting to access a previously unknown unencrypted repository\n""", file=sys.stderr)
                    answer = input('Do you want to continue? [yN] ')
                    if not (answer and answer in 'Yy'):
                        raise self.CacheInitAbortedError()
            # Make sure an encrypted repository has not been swapped for an unencrypted repository
            if self.key_type is not None and self.key_type != str(key.TYPE):
                raise self.EncryptionMethodMismatch()
        self.key_type = self.config.get('cache', 'key_type', fallback=None)
        if self.lock:
            self.lock.release()
        self.config.set('cache', 'key_type', str(self.key.TYPE))
from binascii import hexlify
from configparser import RawConfigParser
from attic.cache import Cache
class environment_variable:
    def __init__(self, **values):
        self.values = values
        self.old_values = {}

    def __enter__(self):
        for k, v in self.values.items():
            self.old_values[k] = os.environ.get(k)
            os.environ[k] = v

    def __exit__(self, *args, **kw):
        for k, v in self.old_values.items():
            if v is not None:
                os.environ[k] = v


        with environment_variable(ATTIC_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK='1'):
            info_output2 = self.attic('info', self.repository_location + '::test')
    def _extract_repository_id(self, path):
        return Repository(self.repository_path).id

    def _set_repository_id(self, path, id):
        config = RawConfigParser()
        config.read(os.path.join(path, 'config'))
        config.set('repository', 'id', hexlify(id).decode('ascii'))
        with open(os.path.join(path, 'config'), 'w') as fd:
            config.write(fd)
        return Repository(self.repository_path).id

    def test_repository_swap_detection(self):
        self.create_test_files()
        os.environ['ATTIC_PASSPHRASE'] = 'passphrase'
        self.attic('init', '--encryption=passphrase', self.repository_location)
        repository_id = self._extract_repository_id(self.repository_path)
        self.attic('create', self.repository_location + '::test', 'input')
        shutil.rmtree(self.repository_path)
        self.attic('init', '--encryption=none', self.repository_location)
        self._set_repository_id(self.repository_path, repository_id)
        self.assert_equal(repository_id, self._extract_repository_id(self.repository_path))
        self.assert_raises(Cache.EncryptionMethodMismatch, lambda :self.attic('create', self.repository_location + '::test.2', 'input'))