diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2012-04-09 19:19:41 -0400 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2012-04-09 19:19:41 -0400 |
| commit | ce8e7d2438a3804b50e9af2712302de8d72c9f50 (patch) | |
| tree | 49628955655aca237687660f94938d0652d271cc | |
| parent | 34f766f4a2f11b19ce233e136e435c131531e42c (diff) | |
| download | passlib-ce8e7d2438a3804b50e9af2712302de8d72c9f50.tar.gz | |
*all* hashes now throw PasswordSizeError if password is larger than 4096 chars; to prevent DOS issues.
| -rw-r--r-- | CHANGES | 8 | ||||
| -rw-r--r-- | docs/lib/passlib.exc.rst | 1 | ||||
| -rw-r--r-- | passlib/exc.py | 22 | ||||
| -rw-r--r-- | passlib/handlers/misc.py | 2 | ||||
| -rw-r--r-- | passlib/handlers/mssql.py | 2 | ||||
| -rw-r--r-- | passlib/handlers/scram.py | 2 | ||||
| -rw-r--r-- | passlib/tests/utils.py | 13 | ||||
| -rw-r--r-- | passlib/utils/__init__.py | 3 | ||||
| -rw-r--r-- | passlib/utils/handlers.py | 10 |
9 files changed, 62 insertions, 1 deletions
@@ -50,6 +50,14 @@ Release History that sometimes occurred on platforms with a deviant implementation of :func:`!os_crypt`. + * All hashes will now throw :exc:`~passlib.exc.PasswordSizeError` + if the provided password is larger than 4096 characters. + + This limit should be above any reasonable password size, + and prevents various things including DOS abuse of hashes + that have an expensive password-length-dependant stage, + and for OS's which have a buggy :func:`!crypt.crypt` implementation. + CryptContext .. currentmodule:: passlib.context diff --git a/docs/lib/passlib.exc.rst b/docs/lib/passlib.exc.rst index 263f754..b60ba63 100644 --- a/docs/lib/passlib.exc.rst +++ b/docs/lib/passlib.exc.rst @@ -11,6 +11,7 @@ may be raised by Passlib. Exceptions ========== .. autoexception:: MissingBackendError +.. autoexception:: PasswordSizeError Warnings ======== diff --git a/passlib/exc.py b/passlib/exc.py index cb158e7..44fadd4 100644 --- a/passlib/exc.py +++ b/passlib/exc.py @@ -14,6 +14,28 @@ class MissingBackendError(RuntimeError): from :class:`~passlib.utils.handlers.HasManyBackends`. """ +class PasswordSizeError(ValueError): + """Error raised if the password provided exceeds the limit set by Passlib. + + Many password hashes take proportionately larger amounts of + time and/or memory depending on the size of the password provided. + This could present a potential denial of service (DOS) situation + if a maliciously large password was provided to the application. + + Because of this, Passlib enforces a maximum of 4096 characters. + This error will be thrown if a password larger than + this is provided to any of the hashes in Passlib. + + Applications wishing to use a different limit should set the + ``PASSLIB_MAX_PASSWORD_SIZE`` environmental variable before Passlib + is loaded. + """ + def __init__(self): + ValueError.__init__(self, "password exceeds maximum allowed size") + + # this also prevents a glibc crypt segfault issue, detailed here ... + # http://www.openwall.com/lists/oss-security/2011/11/15/1 + #========================================================================== # warnings #========================================================================== diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py index 38b8c25..2fb3f13 100644 --- a/passlib/handlers/misc.py +++ b/passlib/handlers/misc.py @@ -170,6 +170,8 @@ class plaintext(object): @classmethod def encrypt(cls, secret): + if secret and len(secret) > uh.MAX_PASSWORD_SIZE: + raise uh.exc.PasswordSizeError() return to_native_str(secret, cls._hash_encoding, "secret") @classmethod diff --git a/passlib/handlers/mssql.py b/passlib/handlers/mssql.py index 36bff13..4bc7e24 100644 --- a/passlib/handlers/mssql.py +++ b/passlib/handlers/mssql.py @@ -160,6 +160,8 @@ class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): chk = self.checksum if chk is None: raise uh.MissingDigestError(cls) + if secret and len(secret) > uh.MAX_PASSWORD_SIZE: + raise uh.exc.PasswordSizeError() secret = to_unicode(secret, 'utf-8', errname='secret') result = _raw_mssql(secret.upper(), self.salt) return consteq(result, chk[20:]) diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py index 5c9c65d..ab610c8 100644 --- a/passlib/handlers/scram.py +++ b/passlib/handlers/scram.py @@ -351,6 +351,8 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): @classmethod def verify(cls, secret, hash, full=False): + if secret and len(secret) > uh.MAX_PASSWORD_SIZE: + raise uh.exc.PasswordSizeError() self = cls.from_string(hash) chkmap = self.checksum if not chkmap: diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py index b3ce550..7c79975 100644 --- a/passlib/tests/utils.py +++ b/passlib/tests/utils.py @@ -1231,6 +1231,19 @@ class HandlerCase(TestCase): self.assertRaises(TypeError, self.do_genhash, None, hash) self.assertRaises(TypeError, self.do_verify, None, hash) + def test_63_max_password_size(self): + "test MAX_PASSWORD_SIZE is enforced" + if self.is_disabled_handler: + raise self.skipTest("not applicable") + from passlib.exc import PasswordSizeError + from passlib.utils import MAX_PASSWORD_SIZE + secret = '.' * (1+MAX_PASSWORD_SIZE) + config = self.do_genconfig() + hash = self.get_sample_hash()[1] + self.assertRaises(PasswordSizeError, self.do_genhash, secret, config) + self.assertRaises(PasswordSizeError, self.do_encrypt, secret) + self.assertRaises(PasswordSizeError, self.do_verify, secret, hash) + #============================================================== # check identify(), verify(), genhash() against test vectors #============================================================== diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index 24173f7..e8e5b27 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -105,6 +105,9 @@ _BEMPTY = b('') _UEMPTY = u("") _USPACE = u(" ") +# maximum password size which passlib will allow; see exc.PasswordSizeError +MAX_PASSWORD_SIZE = int(os.environ.get("PASSLIB_MAX_PASSWORD_SIZE") or 4096) + #================================================================================= #decorators and meta helpers #================================================================================= diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index 2d283c5..7c8b747 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -13,12 +13,14 @@ import os from warnings import warn # site # pkg +import passlib.exc as exc from passlib.exc import MissingBackendError, PasslibConfigWarning, \ PasslibHashWarning from passlib.registry import get_crypt_handler from passlib.utils import classproperty, consteq, getrandstr, getrandbytes,\ BASE64_CHARS, HASH64_CHARS, rng, to_native_str, \ - is_crypt_handler, deprecated_function, to_unicode + is_crypt_handler, deprecated_function, to_unicode, \ + MAX_PASSWORD_SIZE from passlib.utils.compat import b, join_byte_values, bytes, irange, u, \ uascii_to_str, join_unicode, unicode, str_to_uascii # local @@ -442,6 +444,8 @@ class GenericHandler(object): @classmethod def genhash(cls, secret, config, **context): + if secret and len(secret) > MAX_PASSWORD_SIZE: + raise exc.PasswordSizeError() self = cls.from_string(config, **context) self.checksum = self._calc_checksum(secret) return self.to_string() @@ -458,6 +462,8 @@ class GenericHandler(object): #========================================================= @classmethod def encrypt(cls, secret, **kwds): + if secret and len(secret) > MAX_PASSWORD_SIZE: + raise exc.PasswordSizeError() self = cls(use_defaults=True, **kwds) self.checksum = self._calc_checksum(secret) return self.to_string() @@ -467,6 +473,8 @@ class GenericHandler(object): # NOTE: classes with multiple checksum encodings should either # override this method, or ensure that from_string() / _norm_checksum() # ensures .checksum always uses a single canonical representation. + if secret and len(secret) > MAX_PASSWORD_SIZE: + raise exc.PasswordSizeError() self = cls.from_string(hash, **context) chk = self.checksum if chk is None: |
