It states in the Python documentation that pickle
is not secure and shouldn't parse untrusted user input. If you research this; almost all examples demonstrate this with a system()
call via os.system
.
Whats not clear to me, is how os.system
is interpreted correctly without the os
module being imported.
>>> import pickle
>>> pickle.loads("cos\nsystem\n(S'ls /'\ntR.") # This clearly works.
bin boot cgroup dev etc home lib lib64 lost+found media mnt opt proc root run sbin selinux srv sys tmp usr var
0
>>> dir() # no os module
['__builtins__', '__doc__', '__name__', '__package__', 'pickle']
>>> os.system('ls /')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>>
Can someone explain?
The name of the module (
os
) is part of the opcode, andpickle
automatically imports the module:Note the
__import__(module)
line.The function is called when the
GLOBAL 'os system'
pickle bytecode instruction is executed.This mechanism is necessary in order to be able to unpickle instances of classes whose modules haven't been explicitly imported into the caller's namespace.
If you use pickletools.dis to disassemble the pickle you can see how this is working:
Output:
Pickle uses a simple stack-based virtual machine that records the instructions used to reconstruct the object. In other words the pickled instructions in your example are:
Push self.find_class(module_name, class_name) i.e. push os.system Push the string 'ls ~' Build tuple from topmost stack items Apply callable to argtuple, both on stack. i.e. os.system(*('ls ~',))
Source
For altogether too much information on writing malicious Pickles that go much further than the standard os.system() example, see this presentation and its accompanying paper.
Importing a module only adds it to the local namespace, which is not necessarily the one you're in. Except when it doesn't: