File CVE-2020-13254.patch of Package python-Django
commit dca6df3ed1d10f122917086529a925824ed17023
Author: Dan Palmer <dan@danpalmer.me>
Date: Wed May 20 11:45:31 2020 +0200
[2.2.x] Fixed CVE-2020-13254 -- Enforced cache key validation in memcached backends.
In cases where a memcached backend does not perform key validation, passing
malformed cache keys could result in a key collision, and potential data
leakage. In order to avoid this vulnerability, key validation is added to the
memcached cache backends.
Index: Django-1.8.19/django/core/cache/__init__.py
===================================================================
--- Django-1.8.19.orig/django/core/cache/__init__.py
+++ Django-1.8.19/django/core/cache/__init__.py
@@ -18,7 +18,7 @@ import warnings
from django.conf import settings
from django.core import signals
from django.core.cache.backends.base import (
- InvalidCacheBackendError, CacheKeyWarning, BaseCache)
+ InvalidCacheBackendError, InvalidCacheKey, CacheKeyWarning, BaseCache)
from django.core.exceptions import ImproperlyConfigured
from django.utils.deprecation import RemovedInDjango19Warning
from django.utils.module_loading import import_string
@@ -26,7 +26,7 @@ from django.utils.module_loading import
__all__ = [
'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS', 'InvalidCacheBackendError',
- 'CacheKeyWarning', 'BaseCache',
+ 'CacheKeyWarning', 'BaseCache', 'InvalidCacheKey',
]
DEFAULT_CACHE_ALIAS = 'default'
Index: Django-1.8.19/django/core/cache/backends/base.py
===================================================================
--- Django-1.8.19.orig/django/core/cache/backends/base.py
+++ Django-1.8.19/django/core/cache/backends/base.py
@@ -16,6 +16,10 @@ class CacheKeyWarning(DjangoRuntimeWarni
pass
+class InvalidCacheKey(ValueError):
+ pass
+
+
# Stub class to ensure not passing in a `timeout` argument results in
# the default timeout
DEFAULT_TIMEOUT = object()
@@ -213,15 +217,9 @@ class BaseCache(object):
cache code.
"""
- if len(key) > MEMCACHE_MAX_KEY_LENGTH:
- warnings.warn('Cache key will cause errors if used with memcached: '
- '%s (longer than %s)' % (key, MEMCACHE_MAX_KEY_LENGTH),
- CacheKeyWarning)
- for char in key:
- if ord(char) < 33 or ord(char) == 127:
- warnings.warn('Cache key contains characters that will cause '
- 'errors if used with memcached: %r' % key,
- CacheKeyWarning)
+ for warning in memcache_key_warnings(key):
+ warnings.warn(warning, CacheKeyWarning)
+
def incr_version(self, key, delta=1, version=None):
"""Adds delta to the cache version for the supplied key. Returns the
@@ -247,3 +245,18 @@ class BaseCache(object):
def close(self, **kwargs):
"""Close the cache connection"""
pass
+
+
+def memcache_key_warnings(key):
+ if len(key) > MEMCACHE_MAX_KEY_LENGTH:
+ yield (
+ 'Cache key will cause errors if used with memcached: %r '
+ '(longer than %s)' % (key, MEMCACHE_MAX_KEY_LENGTH)
+ )
+ for char in key:
+ if ord(char) < 33 or ord(char) == 127:
+ yield (
+ 'Cache key contains characters that will cause errors if '
+ 'used with memcached: %r' % key, CacheKeyWarning
+ )
+ break
Index: Django-1.8.19/django/core/cache/backends/memcached.py
===================================================================
--- Django-1.8.19.orig/django/core/cache/backends/memcached.py
+++ Django-1.8.19/django/core/cache/backends/memcached.py
@@ -3,7 +3,9 @@
import pickle
import time
-from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
+from django.core.cache.backends.base import (
+ DEFAULT_TIMEOUT, BaseCache, InvalidCacheKey, memcache_key_warnings,
+)
from django.utils import six
from django.utils.deprecation import (
RemovedInDjango19Warning, RenameMethodsBase,
@@ -77,10 +79,12 @@ class BaseMemcachedCache(six.with_metacl
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
+ self.validate_key(key)
return self._cache.add(key, value, self.get_backend_timeout(timeout))
def get(self, key, default=None, version=None):
key = self.make_key(key, version=version)
+ self.validate_key(key)
val = self._cache.get(key)
if val is None:
return default
@@ -88,16 +92,20 @@ class BaseMemcachedCache(six.with_metacl
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
+ self.validate_key(key)
if not self._cache.set(key, value, self.get_backend_timeout(timeout)):
# make sure the key doesn't keep its old value in case of failure to set (memcached's 1MB limit)
self._cache.delete(key)
def delete(self, key, version=None):
key = self.make_key(key, version=version)
+ self.validate_key(key)
self._cache.delete(key)
def get_many(self, keys, version=None):
new_keys = [self.make_key(x, version=version) for x in keys]
+ for key in new_keys:
+ self.validate_key(key)
ret = self._cache.get_multi(new_keys)
if ret:
_ = {}
@@ -112,6 +120,7 @@ class BaseMemcachedCache(six.with_metacl
def incr(self, key, delta=1, version=None):
key = self.make_key(key, version=version)
+ self.validate_key(key)
# memcached doesn't support a negative delta
if delta < 0:
return self._cache.decr(key, -delta)
@@ -130,6 +139,7 @@ class BaseMemcachedCache(six.with_metacl
def decr(self, key, delta=1, version=None):
key = self.make_key(key, version=version)
+ self.validate_key(key)
# memcached doesn't support a negative delta
if delta < 0:
return self._cache.incr(key, -delta)
@@ -150,6 +160,7 @@ class BaseMemcachedCache(six.with_metacl
safe_data = {}
for key, value in data.items():
key = self.make_key(key, version=version)
+ self.validate_key(key)
safe_data[key] = value
self._cache.set_multi(safe_data, self.get_backend_timeout(timeout))
@@ -160,6 +171,10 @@ class BaseMemcachedCache(six.with_metacl
def clear(self):
self._cache.flush_all()
+ def validate_key(self, key):
+ for warning in memcache_key_warnings(key):
+ raise InvalidCacheKey(warning)
+
class MemcachedCache(BaseMemcachedCache):
"An implementation of a cache binding using python-memcached"
Index: Django-1.8.19/tests/cache/tests.py
===================================================================
--- Django-1.8.19.orig/tests/cache/tests.py
+++ Django-1.8.19/tests/cache/tests.py
@@ -18,7 +18,7 @@ from django.conf import settings
from django.core import management, signals
from django.core.cache import (
DEFAULT_CACHE_ALIAS, CacheKeyWarning, InvalidCacheBackendError, cache,
- caches, close_caches, get_cache,
+ InvalidCacheKey, caches, close_caches, get_cache,
)
from django.core.cache.utils import make_template_fragment_key
from django.db import connection, connections, transaction
@@ -546,11 +546,10 @@ class BaseCacheTests(object):
def test_invalid_keys(self):
"""
- All the builtin backends (except memcached, see below) should warn on
- keys that would be refused by memcached. This encourages portable
- caching code without making it too difficult to use production backends
- with more liberal key rules. Refs #6447.
-
+ All the builtin backends should warn (except memcached that should
+ error) on keys that would be refused by memcached. This encourages
+ portable caching code without making it too difficult to use production
+ backends with more liberal key rules. Refs #6447.
"""
# mimic custom ``make_key`` method being defined since the default will
# never show the below warnings
@@ -1101,11 +1100,14 @@ class MemcachedCacheTests(BaseCacheTests
In order to be memcached-API-library agnostic, we only assert
that a generic exception of some kind is raised.
+ Whilst other backends merely warn, memcached should raise for an
+ invalid key.
"""
# memcached does not allow whitespace or control characters in keys
self.assertRaises(Exception, cache.set, 'key with spaces', 'value')
# memcached limits key length to 250
self.assertRaises(Exception, cache.set, 'a' * 251, 'value')
+ self.assertRaises(Exception, cache.set, 'a' * 250 + '清', 'value')
# Explicitly display a skipped test if no configured cache uses MemcachedCache
@unittest.skipUnless(