From 78f67d51965db11cb1ed0003f1eb7926458b5c2c Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Sat, 17 Dec 2016 20:16:48 +0100 Subject: fix #913: redirect() doesn't filter "\r\n" leads to CRLF attack The previous fix (6d7e13d) was incomplete. --- bottle.py | 14 +++++++------- test/test_environ.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/bottle.py b/bottle.py index 2dcf86f..2665957 100644 --- a/bottle.py +++ b/bottle.py @@ -1413,21 +1413,21 @@ def _hval(value): class HeaderProperty(object): - def __init__(self, name, reader=None, writer=str, default=''): + def __init__(self, name, reader=None, writer=None, default=''): self.name, self.default = name, default self.reader, self.writer = reader, writer self.__doc__ = 'Current value of the %r header.' % name.title() def __get__(self, obj, cls): if obj is None: return self - value = obj.headers.get(self.name, self.default) + value = obj.get_header(self.name, self.default) return self.reader(value) if self.reader else value def __set__(self, obj, value): - obj.headers[self.name] = self.writer(value) + obj[self.name] = self.writer(value) if self.writer else value def __delete__(self, obj): - del obj.headers[self.name] + del obj[self.name] class BaseResponse(object): @@ -1534,7 +1534,7 @@ class BaseResponse(object): def __contains__(self, name): return _hkey(name) in self._headers def __delitem__(self, name): del self._headers[_hkey(name)] def __getitem__(self, name): return self._headers[_hkey(name)][-1] - def __setitem__(self, name, value): self._headers[_hkey(name)] = [str(value)] + def __setitem__(self, name, value): self._headers[_hkey(name)] = [_hval(value)] def get_header(self, name, default=None): ''' Return the value of a previously defined header. If there is no @@ -1544,11 +1544,11 @@ class BaseResponse(object): def set_header(self, name, value): ''' Create a new response header, replacing any previously defined headers with the same name. ''' - self._headers[_hkey(name)] = [str(value)] + self._headers[_hkey(name)] = [_hval(value)] def add_header(self, name, value): ''' Add an additional response header, not removing duplicates. ''' - self._headers.setdefault(_hkey(name), []).append(str(value)) + self._headers.setdefault(_hkey(name), []).append(_hval(value)) def iter_headers(self): ''' Yield (header, value) tuples, skipping headers that are not diff --git a/test/test_environ.py b/test/test_environ.py index e9062ae..c37af51 100755 --- a/test/test_environ.py +++ b/test/test_environ.py @@ -669,9 +669,11 @@ class TestResponse(unittest.TestCase): self.assertEqual('None', response['x-test']) def test_prevent_control_characters_in_headers(self): - apis = 'append', 'replace', '__setitem__', 'setdefault' masks = '{}test', 'test{}', 'te{}st' tests = '\n', '\r', '\n\r', '\0' + + # Test HeaderDict + apis = 'append', 'replace', '__setitem__', 'setdefault' for api, mask, test in product(apis, masks, tests): hd = bottle.HeaderDict() func = getattr(hd, api) @@ -679,6 +681,15 @@ class TestResponse(unittest.TestCase): self.assertRaises(ValueError, func, value, "test-value") self.assertRaises(ValueError, func, "test-name", value) + # Test functions on BaseResponse + apis = 'add_header', 'set_header', '__setitem__' + for api, mask, test in product(apis, masks, tests): + rs = bottle.BaseResponse() + func = getattr(rs, api) + value = mask.replace("{}", test) + self.assertRaises(ValueError, func, value, "test-value") + self.assertRaises(ValueError, func, "test-name", value) + def test_expires_header(self): import datetime response = BaseResponse() -- cgit v1.2.1