From 9c6275705103bcc3f0a8f29cbac92e3bb1a116ac Mon Sep 17 00:00:00 2001 From: Lucian Branescu Mihaila Date: Tue, 13 Aug 2013 11:42:23 +0100 Subject: [PATCH 1/3] Replace all base64 decoding errors with BadData. --- itsdangerous.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/itsdangerous.py b/itsdangerous.py index e14055f..03b555e 100644 --- a/itsdangerous.py +++ b/itsdangerous.py @@ -213,7 +213,12 @@ def base64_decode(string): The result is also a bytestring. """ string = want_bytes(string, encoding='ascii', errors='ignore') - return base64.urlsafe_b64decode(string + b'=' * (-len(string) % 4)) + string += b'=' * (-len(string) % 4) + + try: + return base64.urlsafe_b64decode(string) + except Exception: + raise BadData('Invalid base64-encoded data') def int_to_bytes(num): From e284790bf5b278891764119deb8d9a37aa40a447 Mon Sep 17 00:00:00 2001 From: Lucian Branescu Mihaila Date: Tue, 13 Aug 2013 12:12:28 +0100 Subject: [PATCH 2/3] The base64 module appears to only throw TypeError and ValueError should also be possible. --- itsdangerous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/itsdangerous.py b/itsdangerous.py index 03b555e..727e6de 100644 --- a/itsdangerous.py +++ b/itsdangerous.py @@ -217,7 +217,7 @@ def base64_decode(string): try: return base64.urlsafe_b64decode(string) - except Exception: + except (TypeError, ValueError): raise BadData('Invalid base64-encoded data') From 7462ddbf3b0903f2893c479138e6fa5911b2d89a Mon Sep 17 00:00:00 2001 From: Lucian Branescu Mihaila Date: Tue, 13 Aug 2013 15:42:10 +0100 Subject: [PATCH 3/3] Ignore non-base64-valid characters when decoding. Base64 test. --- itsdangerous.py | 14 ++++++++++++-- tests.py | 10 +++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/itsdangerous.py b/itsdangerous.py index 727e6de..f482142 100644 --- a/itsdangerous.py +++ b/itsdangerous.py @@ -10,6 +10,7 @@ :license: BSD, see LICENSE for more details. """ +import re import sys import hmac import zlib @@ -26,12 +27,15 @@ text_type = unicode int_to_byte = chr number_types = (int, long, float) + import string + _maketrans = string.maketrans else: from functools import reduce izip = zip text_type = str int_to_byte = operator.methodcaller('to_bytes', 1, 'big') number_types = (int, float) + _maketrans = bytes.maketrans try: @@ -57,6 +61,10 @@ def dumps(self, obj): # 2011/01/01 in UTC EPOCH = 1293840000 +_urlsafe_decode_translation = _maketrans(b'-_', b'+/') +_b64_strip_re = re.compile(b'[^\+\/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + b'abcdefghijklmnopqrstuvwxyz]') + def want_bytes(s, encoding='utf-8', errors='strict'): if isinstance(s, text_type): @@ -213,10 +221,12 @@ def base64_decode(string): The result is also a bytestring. """ string = want_bytes(string, encoding='ascii', errors='ignore') - string += b'=' * (-len(string) % 4) + string = string.translate(_urlsafe_decode_translation) + string = _b64_strip_re.sub(b'', string) + string = string + b'=' * (-len(string) % 4) try: - return base64.urlsafe_b64decode(string) + return base64.standard_b64decode(string) except (TypeError, ValueError): raise BadData('Invalid base64-encoded data') diff --git a/tests.py b/tests.py index 69f601e..7c413ca 100644 --- a/tests.py +++ b/tests.py @@ -5,7 +5,7 @@ from datetime import datetime import itsdangerous as idmod -from itsdangerous import want_bytes, text_type, PY2 +from itsdangerous import want_bytes, text_type, PY2, base64_decode # Helper function for some unsafe string manipulation on encoded @@ -26,6 +26,14 @@ def test_want_bytes(self): self.assertEqual(want_bytes(b"foobar"), b"foobar") self.assertEqual(want_bytes(u"foobar"), b"foobar") + def test_base64_decode(self): + self.assertRaises(idmod.BadData, base64_decode, b'A') + + self.assertEqual(base64_decode(b'AA'), b'\x00') + self.assertEqual(base64_decode(b'AA=='), b'\x00') + self.assertEqual(base64_decode(b'AA\\\\'), b'\x00') + self.assertEqual(base64_decode(b'A!A'), b'\x00') + class SerializerTestCase(unittest.TestCase): serializer_class = idmod.Serializer