Skip to content

Commit 0e40611

Browse files
committed
Prevents arbitrary code execution during python/object/new constructor
In FullLoader python/object/new constructor, implemented by construct_python_object_apply, has support for setting the state of a deserialized instance through the set_python_instance_state method. After setting the state, some operations are performed on the instance to complete its initialization, however it is possible for an attacker to set the instance' state in such a way that arbitrary code is executed by the FullLoader. This patch tries to block such attacks in FullLoader by preventing set_python_instance_state from setting arbitrary properties. It implements a blacklist that includes `extend` method (called by construct_python_object_apply) and all special methods (e.g. __set__, __setitem__, etc.). Users who need special attributes being set in the state of a deserialized object can still do it through the UnsafeLoader, which however should not be used on untrusted input. Additionally, they can subclass FullLoader and redefine `state_blacklist_regexp` to include the additional attributes they need, passing the subclassed loader to yaml.load.
1 parent 2f463cf commit 0e40611

File tree

1 file changed

+24
-1
lines changed

1 file changed

+24
-1
lines changed

lib3/yaml/constructor.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ def check_data(self):
3131
# If there are more documents available?
3232
return self.check_node()
3333

34+
def check_state_key(self, key):
35+
"""Block special attributes/methods from being set in a newly created
36+
object, to prevent user-controlled methods from being called during
37+
deserialization"""
38+
if self.state_blacklist_regexp.match(key):
39+
raise ConstructorError(None, None,
40+
"blacklisted key '%s' in instance state found" % (key,), None)
41+
3442
def get_data(self):
3543
# Construct and return the next document.
3644
if self.check_node():
@@ -472,6 +480,12 @@ def construct_undefined(self, node):
472480
SafeConstructor.construct_undefined)
473481

474482
class FullConstructor(SafeConstructor):
483+
# 'extend' is blacklisted because it is used by
484+
# construct_python_object_apply to add `listitems` to a newly generate
485+
# python instance
486+
STATE_BLACKLIST_KEYS = ['^extend$', '^__.*__$']
487+
488+
state_blacklist_regexp = re.compile('(' + '|'.join(STATE_BLACKLIST_KEYS) + ')')
475489

476490
def construct_python_str(self, node):
477491
return self.construct_scalar(node)
@@ -574,18 +588,23 @@ def make_python_instance(self, suffix, node,
574588
else:
575589
return cls(*args, **kwds)
576590

577-
def set_python_instance_state(self, instance, state):
591+
def set_python_instance_state(self, instance, state, unsafe=False):
578592
if hasattr(instance, '__setstate__'):
579593
instance.__setstate__(state)
580594
else:
581595
slotstate = {}
582596
if isinstance(state, tuple) and len(state) == 2:
583597
state, slotstate = state
584598
if hasattr(instance, '__dict__'):
599+
if not unsafe and state:
600+
for key in state.keys():
601+
self.check_state_key(key)
585602
instance.__dict__.update(state)
586603
elif state:
587604
slotstate.update(state)
588605
for key, value in slotstate.items():
606+
if not unsafe:
607+
self.check_state_key(key)
589608
setattr(instance, key, value)
590609

591610
def construct_python_object(self, suffix, node):
@@ -711,6 +730,10 @@ def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False)
711730
return super(UnsafeConstructor, self).make_python_instance(
712731
suffix, node, args, kwds, newobj, unsafe=True)
713732

733+
def set_python_instance_state(self, instance, state):
734+
return super(UnsafeConstructor, self).set_python_instance_state(
735+
instance, state, unsafe=True)
736+
714737
UnsafeConstructor.add_multi_constructor(
715738
'tag:yaml.org,2002:python/object/apply:',
716739
UnsafeConstructor.construct_python_object_apply)

0 commit comments

Comments
 (0)