Skip to content

Commit 827f4e7

Browse files
fix: escape nulls in XML api responses (#9283)
* fix: escape nulls in XML api responses * refactor: use \u2400 instead of \0 Less likely to lead to null injection down the road * test: modern naming/python * test: test null char handling * test: remove unused vars
1 parent e0546b1 commit 827f4e7

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

ietf/api/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,26 @@ def dehydrate(self, bundle, for_list=True):
145145

146146

147147
class Serializer(tastypie.serializers.Serializer):
148+
OPTION_ESCAPE_NULLS = "datatracker-escape-nulls"
149+
148150
def format_datetime(self, data):
149151
return data.astimezone(datetime.timezone.utc).replace(tzinfo=None).isoformat(timespec="seconds") + "Z"
152+
153+
def to_simple(self, data, options):
154+
options = options or {}
155+
simple_data = super().to_simple(data, options)
156+
if (
157+
options.get(self.OPTION_ESCAPE_NULLS, False)
158+
and isinstance(simple_data, str)
159+
):
160+
# replace nulls with unicode "symbol for null character", \u2400
161+
simple_data = simple_data.replace("\x00", "\u2400")
162+
return simple_data
163+
164+
def to_etree(self, data, options=None, name=None, depth=0):
165+
# lxml does not escape nulls on its own, so ask to_simple() to do it.
166+
# This is mostly (only?) an issue when generating errors responses for
167+
# fuzzers.
168+
options = options or {}
169+
options[self.OPTION_ESCAPE_NULLS] = True
170+
return super().to_etree(data, options, name, depth)

ietf/api/tests.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from ietf.utils.models import DumpInfo
4242
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, reload_db_objects
4343

44+
from . import Serializer
4445
from .ietf_utils import is_valid_token, requires_api_token
4546
from .views import EmailIngestionError
4647

@@ -1496,7 +1497,7 @@ def test_good_password(self):
14961497
data = self.response_data(r)
14971498
self.assertEqual(data["result"], "success")
14981499

1499-
class TastypieApiTestCase(ResourceTestCaseMixin, TestCase):
1500+
class TastypieApiTests(ResourceTestCaseMixin, TestCase):
15001501
def __init__(self, *args, **kwargs):
15011502
self.apps = {}
15021503
for app_name in settings.INSTALLED_APPS:
@@ -1506,7 +1507,7 @@ def __init__(self, *args, **kwargs):
15061507
models_path = os.path.join(os.path.dirname(app.__file__), "models.py")
15071508
if os.path.exists(models_path):
15081509
self.apps[name] = app_name
1509-
super(TastypieApiTestCase, self).__init__(*args, **kwargs)
1510+
super().__init__(*args, **kwargs)
15101511

15111512
def test_api_top_level(self):
15121513
client = Client(Accept='application/json')
@@ -1541,6 +1542,21 @@ def test_all_model_resources_exist(self):
15411542
self.assertIn(model._meta.model_name, list(app_resources.keys()),
15421543
"There doesn't seem to be any API resource for model %s.models.%s"%(app.__name__,model.__name__,))
15431544

1545+
def test_serializer_to_etree_handles_nulls(self):
1546+
"""Serializer to_etree() should handle a null character"""
1547+
serializer = Serializer()
1548+
try:
1549+
serializer.to_etree("string with no nulls in it")
1550+
except ValueError:
1551+
self.fail("serializer.to_etree raised ValueError on an ordinary string")
1552+
try:
1553+
serializer.to_etree("string with a \x00 in it")
1554+
except ValueError:
1555+
self.fail(
1556+
"serializer.to_etree raised ValueError on a string "
1557+
"containing a null character"
1558+
)
1559+
15441560

15451561
class RfcdiffSupportTests(TestCase):
15461562

0 commit comments

Comments
 (0)