| CTF | Infobahn CTF 2025 (CTFtime) |
| Author | t-chen |
| Category | jail |
| Solves | 13 |
| Files | very-safe-pickle.zip |
At first glance, it seems we cannot invoke a method or create a class instance due to the forbidden opcodes. However, we can use the BUILD opcode, which performs the following actions:
def load_build(self):
stack = self.stack
state = stack.pop()
inst = stack[-1]
setstate = getattr(inst, "__setstate__", _NoValue)
if setstate is not _NoValue:
setstate(state)
return
slotstate = None
if isinstance(state, tuple) and len(state) == 2:
state, slotstate = state
if state:
inst_dict = inst.__dict__
intern = sys.intern
for k, v in state.items():
if type(k) is str:
inst_dict[intern(k)] = v
else:
inst_dict[k] = v
if slotstate:
for k, v in slotstate.items():
setattr(inst, k, v)This allows us to overwrite an object's __dict__, including its magic methods. The __getattr__ magic method works like this:
import enum
def f(arg):
print('f is called with:', arg)
enum.__dict__["__getattr__"] = f
getattr(enum, 'foobar') # prints "f is called with: foobar"Since the GLOBAL opcode also uses getattr when importing values from a module, overriding this method will allow us to hijack the import process and invoke arbitrary functions.
We cannot directly import modules like os, subprocess, posix, or builtins because their names contain the prohibited bytes o or i. Instead, we can first import html.unescape and use it to create arbitrary strings from their HTML-encoded representations. Then, we can use the STACK_GLOBAL opcode, which imports modules using strings from the stack.
Here are the steps to solve this challenge:
- Import
unescapefrom thehtmlmodule. - Import
enumfrom theremodule, and use theBUILDopcode to overwrite its__getattr__method withunescape. - Attempt to import
builtinsfrom theenummodule. This triggers our hijacked__getattr__, which isunescape. This decodes the HTML entity into the stringbuiltinsand pushes it onto the stack. - Use the
STACK_GLOBALopcode to importevalfrom thebuiltinsmodule, whose name is now on the stack. - Import
_typesfrom thectypesmodule and use theBUILDopcode to overwrite its__getattr__method witheval. - Attempt to import an HTML-encoded RCE payload from the
enummodule. This again callsunescape, which decodes the payload and pushes the resulting RCE command string onto the stack. - Use the
STACK_GLOBALopcode to import the RCE string from thetypesmodule. This triggers the hijacked__getattr__(which is noweval), executing our RCE payload.
(re.enum and ctypes._types can be replaced with any module that is a submodule of another, as long as their names do not contain forbidden characters and is written in Python, not C.)
See solve.py for the full exploit code.