Still cheating tho...
Nice exploit, no talk. But even if it's "without any imports", it still feels a bit of cheating, because the whole concept lies on the idea (from a quick glance) that CPython doesn't validate Codeobject params well enough. I hope they'll plug it, but there can't be a talk about "safe" (better term "sandboxed") Python for as long as it's possible to create a Codeobject at all. And it's possible to create it, because Python allow you to take full control of your machine.
(I write that with the idea that this is PoC of security issue in Python, which can't be detected by naive measures like scanning imports. Because if you want to take control of your Python, the "without any imports" clause is superfluous, just import ctypes, and do whatever you want to the interpreter or the whole system with more comfort.)
I personally consider it not-cheating because:
a) The CPython team have been aware of the "bug" for many years, and have expressed no intent to patch it afaict. Therefore it is a language feature.
b) I am not a Python expert, but my understanding is that code objects are effectively a CPython implementation detail. Despite this, other Pythons also implement the same interface, for compatibility. As such, it also works in non-CPython implementations, like pypy (I haven't implemented a full exploit for pypy yet because the heap structure is very different, but the core bug still applies). At some point I will do a survey of all the python implementations I can find.
c) I make the rules :P More specifically, my personal rules were to pretend that this was an exploit for a browser's JavaScript engine, and apply similar methodologies. The end result isn't supposed to be particularly useful for anything, but it can certainly get you native code execution inside a SECCOMP (or similar) sandboxed python process - which gives you better access to the sandbox's attack surface, and more chance of breaking out of it.
d) If you were monitoring the python process with strace, ltrace, or similar, you wouldn't see anything out-of-the-ordinary.
Thanks for the response, and insightful. I won't argue re: c) on the actual difference between Python and in-browser JavaScript, because that's certainly how a security researcher should approach it. (And in our modern world the difference is more quantitative than qualitative.)
The CPython team have been aware of the "bug" for many years, and have expressed no intent to patch it afaict.
That's I usually put it too. But being on the receiving side, I feel obliged to mention that they're probably too busy with many other things on the plate, etc. But sometimes yeah, you think that a only a perfect storm could help them. A CVE storm in this case, though I'm not sue that would happen until a real trojan/worm using that will travel thru enough boxes (and maybe Python users are indeed different from the left-pad folk and that would never happen :-D).
At some point I will do a survey of all the python implementations I can find.
That's probably why I stopped for more than just look and pass by - I was curious how it would apply to my humble implementation, https://github.com/pfalcon/pycopy . So, mostly for my own checklist (as it's niche implementation and you can't judge the rest of Python ecosystem by it): First of all, in the "default" build, ability to create ~~code objects~~ functions from scratch isn't exposed at all (at least so far). Because that's indeed implementation detail, and I don't want "generic" applications running on the "default" version to rely on any implementation details. However, that ability is exposed in the "dev" version, which is clearly intended for development and experimentation (not for "production"). And there, it's exposed as pycopy.function(), not as a constructor on the function type (because it's again impl detail, and the signature is not compatible). If CPython did it like that, e.g. sys.function(), it would be readily stubbable, like anything else in sys. But stubbing out function constructor is not readily possible:
>>> f = lambda: None
>>> f.__class__.__init__ = lambda: None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'function'
Wrapping code object in a function is not the only way to execute it, it can also be passed to eval() and exec(), but those can be readily stubbed out (or filtering version installed, which would pass source string but not code objects):
>>> import builtins
>>> builtins.exec = builtins.eval = lambda *x: None
(All that also reminds an old Python anecdote from many years ago (I don't have a reference handy), where a guy made a sandboxing script, and asked people to break it. And after a dozen of iterations of him fixing it up, people were still finding ways to break thru it. That's of course both hilarious and anecdote, it shouldn't be like that.)